TextEditor
The <TextEditor>
is a WYSIWYG (What You See Is What You Get) editor - An editor that allows users to create and edit rich text content in a form that resembles its appearance when displayed as a finished product.
It leverages Quill.js as the underlying editor, which is a powerful, extensible, and a customizable rich text editor.
Basic Usage
Diplayed content
<script setup lang="ts">
import { defineAsyncComponent, ref } from 'vue';
// Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));
const modelValue = ref('');
</script>
<template>
<TextEditor
id="text-editor-basic"
v-model="modelValue"
label="Description"
placeholder="Insert a product description"
/>
<div class="tw-mt-2">
<p class="">Diplayed content</p>
<pre class="tw-text-wrap">
{{ modelValue }}
</pre>
</div>
</template>
Placeholder
The placeholder is a text that appears in the editor when it is empty. Quill.js adds the placeholder as a content property of a ::before
pseudo-element, but you can change its text with the placeholder
prop.
<script setup lang="ts">
import { defineAsyncComponent, ref } from 'vue';
// Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));
const modelValue = ref('');
</script>
<template>
<TextEditor id="text-editor-placeholder" v-model="modelValue" placeholder="Product description" />
</template>
Field props
The <TextEditor>
component uses the Field component internally, so you can pass most of the field properties to it; the Field props available for use are listed below.
Label
Adds a label above the text editor.
<script setup lang="ts">
import { defineAsyncComponent, ref } from 'vue';
// Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));
const modelValue = ref('');
</script>
<template>
<TextEditor id="text-editor-label" v-model="modelValue" label="Product description" />
</template>
Disabled
The is-disabled
prop disables the editor and prevents the user from interacting with it.
WARNING
If the user cannot take any action to make the field editable, the is-read-only
prop should be used instead.
See Disabled vs read-only for details.
INFO
Dev Note: Quill.js sets the editor's contenteditable
to false when isDisabled
is true to prevent the user from editing the content.
<script setup lang="ts">
import { defineAsyncComponent, ref } from 'vue';
// Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));
const modelValue = ref('');
</script>
<template>
<TextEditor
id="text-editor-read-only"
v-model="modelValue"
label="Product description"
placeholder="Insert a product description"
is-disabled
/>
</template>
Read only
Styles the field as read-only. This is useful when displaying the editor rich text because it will have all the same styles and spacing of a regular field.
INFO
Quill.js sets the editor's contenteditable
to false when isReadOnly
is true to prevent the user from editing the content.
<script setup lang="ts">
import { defineAsyncComponent, ref } from 'vue';
// Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));
const modelValue = ref(`
<p><strong>Quality Glass, For Every Stoner</strong></p>
<p></br></p>
<p>Thick Ass Glass (TAG) encompasses the meaning of high quality affordable borosilicate. <strong>These pieces can take a beating while delivering smooth hits from even the biggest clouds</strong>.</p>
<p></br></p>
<p>The TAG logo is sand-blasted on the neck of the bong.</p>
<p></br></p>
<ul>
<li>10" Bent Neck</li>
<li>Matrix Diffuser</li>
<li>65x5MM</li>
<li>14mm Female Down Stem</li>
</ul>
`);
</script>
<template>
<TextEditor id="text-editor-read-only" v-model="modelValue" label="Product description" is-read-only />
</template>
Add bottom space
Adds spacing under the field that is consistent whether hint/error text is displayed.
<script setup lang="ts">
import { defineAsyncComponent, ref } from 'vue';
import Button from '../../../src/components/Button/Button.vue';
import Input from '../../../src/components/Input/Input.vue';
// Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));
const modelValue = ref('');
const bottomSpace = ref(true);
</script>
<template>
<Button class="tw-mb-6" @click="bottomSpace = !bottomSpace">Toggle bottom space</Button>
<TextEditor
id="text-editor-bottom-space"
v-model="modelValue"
label="Description"
placeholder="Insert a product description"
class="tw-mb-2"
:add-bottom-space="bottomSpace"
/>
<Input label="Product Category" :add-bottom-space="bottomSpace" />
</template>
Required
The required
prop adds a red asterisk next to the label.
<script setup lang="ts">
import { defineAsyncComponent, ref } from 'vue';
// Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));
const modelValue = ref('');
</script>
<template>
<TextEditor id="text-editor-hint-text" v-model="modelValue" label="Product description" is-required />
</template>
Optional
The show-optional-in-label
prop displays (optional)
to the right of the label text.
<script setup lang="ts">
import { defineAsyncComponent, ref } from 'vue';
// Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));
const modelValue = ref('');
</script>
<template>
<TextEditor id="text-editor-hint-text" v-model="modelValue" label="Product description" show-optional-in-label />
</template>
Hint text
Displays the provided text below the TextEditor. See more about this property in the hint-text section of the Field component.
<script setup lang="ts">
import { defineAsyncComponent, ref } from 'vue';
// Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));
const modelValue = ref('');
</script>
<template>
<TextEditor id="text-editor-hint-text" v-model="modelValue" hint-text="The product description" />
</template>
Error Text
Displays the provided text in red below the input. See more about this property in the error-text section of the Field component.
INFO
The hint-text
will be replaced by the error-text
if both are provided.
<script setup lang="ts">
import { defineAsyncComponent, Ref, ref } from 'vue';
import Button from '../../../src/components/Button/Button.vue';
import useValidation, { isDefined } from '../../../src/composables/useValidation/useValidation';
import { ValidationRules } from '../../../src/composables/useValidation/useValidation.types';
import { t } from '../../../src/locale';
// Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));
interface FormState {
description?: string;
}
const formState: Ref<FormState> = ref(getInitialFormState());
function getInitialFormState() {
return {
description: undefined,
};
}
const validationRules: ValidationRules<FormState> = {
description: [
{
name: 'required',
validator(value) {
return isDefined(value) && value !== '<p><br></p>';
},
message: t('ll.validation.required'),
},
],
};
const validation = useValidation({ rules: validationRules, values: formState });
async function onSubmit() {
await validation.validate();
if (validation.hasErrors) {
return;
}
alert(`Form submitted\n ${JSON.stringify(formState.value, null, 2)}`);
}
</script>
<template>
<form @submit.prevent>
<TextEditor
id="text-editor-validation"
v-model="formState.description"
:error-text="validation.getError('description')"
label="Product template"
hint-text="The product description"
add-bottom-space
@blur="validation.touch('description')"
/>
<Button class="tw-mt-2" type="button" @click="onSubmit">Submit</Button>
</form>
</template>
Controls
The controls
prop can be a set of format bar buttons which can be either a list of single controls or a list of a grouped control arrays.
See the Quill.js documentation for more information about the available controls.
INFO
For a design reasons, the <TextEditor>
component only supports this small list of controls: bold
, underline
, italic
, list
(bullet
, ordered
), link
, divider
.
Simple controls
<script setup lang="ts">
import { defineAsyncComponent, ref } from 'vue';
import { TextEditorControls } from '../../../src/components/TextEditor/TextEditor.vue';
// Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));
const modelValue = ref('');
const controls: TextEditorControls = ['bold', 'italic'];
</script>
<template>
<TextEditor id="text-editor-controls" v-model="modelValue" :controls="controls" />
</template>
Grouped controls
The controls
props can receive a matrix indicating that each matrix row is a grouped set of controls like:
[
['bold', 'italic', 'underline'],
['link'],
['divider']
]
<script setup lang="ts">
import { defineAsyncComponent, ref } from 'vue';
import { TextEditorControls } from '../../../src/components/TextEditor/TextEditor.vue';
// Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));
const modelValue = ref('');
const controls: TextEditorControls = [
['bold', 'italic'],
[{ list: 'bullet' }, { list: 'ordered' }],
];
</script>
<template>
<TextEditor id="text-editor-grouped-controls" v-model="modelValue" :controls="controls" />
</template>
Control handlers
The handlers
prop can receive an object mapper with custom control handlers which will dictate each control behavior upon click.
<script setup lang="ts">
import { defineAsyncComponent, ref } from 'vue';
import { TextEditorControlHandlerMap } from '../../../src/components/TextEditor/TextEditor.vue';
// Since Quill uses the browser api, defineAsyncComponent can lazy load the Text Editor in a non SSR environment
const TextEditor = defineAsyncComponent(() => import('../../../src/components/TextEditor/TextEditor.vue'));
const modelValue = ref('');
const handlers: TextEditorControlHandlerMap = {
bold: () => alert('Bold clicked'),
italic: () => alert('Italic clicked'),
};
</script>
<template>
<TextEditor id="text-editor-control-handlers" v-model="modelValue" :handlers="handlers" />
</template>
API
See the documentation below for a complete reference to all the props and classes available to the components mentioned here.