UNPKG

typewriter-editor

Version:

A rich text editor using the Delta format with decorations and rendered with a tiny virtual dom

137 lines (136 loc) 5.59 kB
import { isEqual } from '@typewriter/document'; import { Editor, EditorChangeEvent } from '../Editor'; import { getLineNodeStart } from '../rendering/rendering'; import { getSelection, setSelection } from '../rendering/selection'; export function selection(editor) { let rootDocument; let rootWindow; let paused = false; function onSelectionChange() { if (!editor.enabled) return; const selection = getSelection(editor); const originalSelection = selection?.slice() || null; if (!selection && paused) return; if (paused) paused = false; if (selection) { if (selection[0] === selection[1] && selection[0] === editor.doc.length) { selection[0]--; } let line = editor.doc.getLineAt(selection[0]); let type = editor.typeset.lines.findByAttributes(line.attributes, true); if (selection && selection[0] === selection[1] && editor.doc.selection && editor.doc.selection[0] === selection[0] && editor.doc.selection[1] === selection[0] + 1) { // Allow a frozen line (e.g. hr) to move the cursor left with a left arrow key if (type.frozen) { selection[0]--; selection[1]--; } line = editor.doc.getLineAt(selection[0]); type = editor.typeset.lines.findByAttributes(line.attributes, true); } if (type.frozen && selection[0] === selection[1]) { selection[1]++; } } const { doc } = editor; if (!isEqual(doc.selection, selection)) { if (selection && selection[0] === selection[1] && selection[0] >= doc.length) { return; // Assuming this is a text composition at the end of the document, allow the entry } editor.select(selection); } else if (!isEqual(originalSelection, selection)) { setSelection(editor, selection); } } function renderSelection() { if (paused || !editor.enabled) return; setSelection(editor, editor.doc.selection); } function onDecorate() { const { doc, typeset: { lines }, } = editor; const decorator = editor.modules.decorations.getDecorator('selection'); decorator.clear(); const selection = doc.selection; if (selection) { doc.getLinesAt(selection).forEach(line => { if (lines.findByAttributes(line.attributes, true).frozen) { const focused = isEqual(selection, doc.getLineRange(line)); decorator.decorateLine(doc.getLineRange(line)[0], { class: 'selected' + (focused ? ' focus' : '') }); } }); } decorator.apply(); } function onMouseDown(event) { // Helps select lines that are not easily selectable (e.g. <hr>) let node = event.target; while (node.parentNode && node.parentNode !== editor.root) node = node.parentNode; const start = getLineNodeStart(editor.root, node); const line = start != null && editor.doc.getLineAt(start); const type = line && editor.typeset.lines.findByAttributes(line.attributes); if (start != null && line && type && type.frozen) { event.preventDefault(); editor.select([start, start + line.length]); } } function onChange(event) { const selection = event.doc?.selection || editor.doc.selection; setSelection(editor, selection); } function onWindowFocus() { editor.root.classList.toggle('window-inactive', !rootDocument.hasFocus()); } function pause() { paused = true; const { selection } = editor.doc; rootDocument.getSelection()?.empty(); const { decorations } = editor.modules; if (selection && selection[0] !== selection[1] && decorations) { const decorator = decorations.getDecorator('pausedSelection'); decorator.decorateText(selection, { class: 'selected' }).apply(); } } function resume() { paused = false; const { decorations } = editor.modules; if (decorations) { decorations.removeDecorations('pausedSelection'); } setTimeout(renderSelection); } return { pause, resume, renderSelection, init() { rootDocument = editor.root.ownerDocument; rootWindow = rootDocument.defaultView; rootDocument.addEventListener('selectionchange', onSelectionChange); rootWindow.addEventListener('focus', onWindowFocus); rootWindow.addEventListener('blur', onWindowFocus); editor.root.addEventListener('mousedown', onMouseDown); editor.on('change', onChange); editor.on('decorate', onDecorate); }, destroy() { rootDocument.removeEventListener('selectionchange', onSelectionChange); rootWindow.removeEventListener('focus', onWindowFocus); rootWindow.removeEventListener('blur', onWindowFocus); editor.root.removeEventListener('mousedown', onMouseDown); editor.off('change', onChange); editor.off('decorate', onDecorate); paused = false; rootDocument = null; rootWindow = null; }, }; }