Custom Rich-Text Editor Integration
To avoid the code layout corruption, styling pollution, and browser-specific inconsistencies that standard contenteditable actions cause, Canvus restricts inline text edits to plain-text by default. However, it provides a pluggable escape hatch callback so host applications can mount fully customized rich-text editors.
Default Behavior (Plain-Text Editing)
When a text-bearing node is double-clicked:
- Canvus marks the nearest wrapper child as
contenteditable="plaintext-only" - It intercepts copy-paste events and formatting hotkeys (e.g.,
Cmd+B,Cmd+I) to strip formatting tags - Upon loss of focus (
blurevent) or pressingEscape/Enter, editing mode finishes - It fires
onHTMLCommitvia the Flat String Bridge and emits anupdate-textoperation
Pluggable Escape Hatch (onTextEditRequest)
To bypass the plain-text default editor and inject your own custom editor, register the onTextEditRequest handler:
interface WorkspaceCallbacks {
onTextEditRequest?: (
nodeId: string,
element: HTMLElement,
commit: (newHTML: string) => void
) => void
}The Workflow
- The user double-clicks a text-bearing node
- Canvus checks if
onTextEditRequestis registered. If present, the default plain-text editor is skipped - Canvus invokes
onTextEditRequest(nodeId, element, commit):nodeId: The unique ID of the selected content nodeelement: The exact sub-element double-clicked (useful for positioning)commit: A callback function provided by the SDK. Callcommit(newHTML)when editing is finished
- Calling
commit(newHTML)updates the Shadow DOM, remeasures boundaries, redraws overlays, and generates anupdate-textoperation
Implementation Example
Here is a complete integration example mounting a custom editor overlay on double-click:
import { Workspace } from '@canvus/core'
const ws = new Workspace(document.getElementById('canvas-container')!, {
onTextEditRequest(nodeId, element, commit) {
// 1. Determine positioning of the target element on screen
const bounds = element.getBoundingClientRect()
// 2. Create your editor container element overlay
const editorOverlay = document.createElement('div')
editorOverlay.style.position = 'absolute'
editorOverlay.style.left = `${bounds.left + window.scrollX}px`
editorOverlay.style.top = `${bounds.top + window.scrollY}px`
editorOverlay.style.width = `${bounds.width}px`
editorOverlay.style.height = `${bounds.height}px`
editorOverlay.style.zIndex = '1000'
document.body.appendChild(editorOverlay)
// 3. Mount TipTap / Quill or a simple textarea
const textarea = document.createElement('textarea')
textarea.value = element.innerHTML
textarea.style.width = '100%'
textarea.style.height = '100%'
editorOverlay.appendChild(textarea)
textarea.focus()
// 4. Handle Save / Commit
const handleBlur = () => {
const updatedHTML = textarea.value
// Save changes back to the SDK
commit(updatedHTML)
// Clean up overlay element
editorOverlay.remove()
textarea.removeEventListener('blur', handleBlur)
}
textarea.addEventListener('blur', handleBlur)
textarea.addEventListener('keydown', (e) => {
// Enter key (without Shift) commits
if (e.key === 'Enter' && !e.shiftKey) {
textarea.blur()
}
})
},
})