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('@packages/vue/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="mt-2">
<p class="">Diplayed content</p>
<pre class="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('@packages/vue/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('@packages/vue/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('@packages/vue/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('@packages/vue/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 Button from '@packages/vue/src/components/Button/Button.vue';
import Input from '@packages/vue/src/components/Input/Input.vue';
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('@packages/vue/src/components/TextEditor/TextEditor.vue'));
const modelValue = ref('');
const bottomSpace = ref(true);
</script>
<template>
<Button class="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="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('@packages/vue/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('@packages/vue/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('@packages/vue/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 Button from '@packages/vue/src/components/Button/Button.vue';
import useValidation, { isDefined } from '@packages/vue/src/composables/useValidation/useValidation';
import { ValidationRules } from '@packages/vue/src/composables/useValidation/useValidation.types';
import { t } from '@packages/vue/src/locale';
import { defineAsyncComponent, Ref, 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('@packages/vue/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="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 { TextEditorControls } from '@packages/vue/src/components/TextEditor/TextEditor.vue';
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('@packages/vue/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 { TextEditorControls } from '@packages/vue/src/components/TextEditor/TextEditor.vue';
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('@packages/vue/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 { TextEditorControlHandlerMap } from '@packages/vue/src/components/TextEditor/TextEditor.vue';
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('@packages/vue/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.