A full-featured rich text editor with formatting toolbar
+ +This is a feature-rich editor built with TipTap. You can:
inline code
or code blocksUse the toolbar above to explore all the formatting options available!
Start typing to see the magic happen...
'} + .description=${'Use the toolbar to format your content with headings, lists, links, and more'} + .showWordCount=${true} + >Empty editor with placeholder text
+ +Editors with different minimum heights for various use cases
+ +Editor pre-filled with code examples
+ +To install the package, run the following command:
npm install @design.estate/dees-catalog
Then import the component in your TypeScript file:
import { DeesInputRichtext } from "@design.estate/dees-catalog";
You can now use the <dees-input-richtext>
element in your templates.
Read-only rich text content
+ +Web Components have revolutionized how we build modern web applications...
'} + .disabled=${true} + .showWordCount=${true} + >"The future of web development lies in reusable, encapsulated components."
Type in the editor below and see the HTML output
+ +${this.placeholder}
` : ''), + onUpdate: ({ editor }) => { + this.value = editor.getHTML(); + this.updateWordCount(); + this.dispatchEvent( + new CustomEvent('input', { + detail: { value: this.value }, + bubbles: true, + composed: true, + }) + ); + this.dispatchEvent( + new CustomEvent('change', { + detail: { value: this.value }, + bubbles: true, + composed: true, + }) + ); + }, + onSelectionUpdate: () => { + this.requestUpdate(); + }, + onFocus: () => { + this.requestUpdate(); + }, + onBlur: () => { + this.requestUpdate(); + }, + }); + + this.updateWordCount(); + } + + private updateWordCount(): void { + if (!this.editor) return; + const text = this.editor.getText(); + this.wordCount = text.trim() ? text.trim().split(/\s+/).length : 0; + } + + private toggleLink(): void { + if (!this.editor) return; + + if (this.editor.isActive('link')) { + const href = this.editor.getAttributes('link').href; + this.showLinkInput = true; + requestAnimationFrame(() => { + if (this.linkInputElement) { + this.linkInputElement.value = href || ''; + this.linkInputElement.focus(); + this.linkInputElement.select(); + } + }); + } else { + this.showLinkInput = true; + requestAnimationFrame(() => { + if (this.linkInputElement) { + this.linkInputElement.value = ''; + this.linkInputElement.focus(); + } + }); + } + } + + private saveLink(): void { + if (!this.editor || !this.linkInputElement) return; + + const url = this.linkInputElement.value; + if (url) { + this.editor.chain().focus().setLink({ href: url }).run(); + } + this.hideLinkInput(); + } + + private removeLink(): void { + if (!this.editor) return; + this.editor.chain().focus().unsetLink().run(); + this.hideLinkInput(); + } + + private hideLinkInput(): void { + this.showLinkInput = false; + this.editor?.commands.focus(); + } + + private handleLinkInputKeydown(e: KeyboardEvent): void { + if (e.key === 'Enter') { + e.preventDefault(); + this.saveLink(); + } else if (e.key === 'Escape') { + e.preventDefault(); + this.hideLinkInput(); + } + } + + public setValue(value: string): void { + this.value = value; + if (this.editor && value !== this.editor.getHTML()) { + this.editor.commands.setContent(value); + } + } + + public getValue(): string { + return this.value; + } + + public clear(): void { + this.setValue(''); + } + + public focus(): void { + this.editor?.commands.focus(); + } + + public async disconnectedCallback(): Promise