UNPKG

json-joy

Version:

Collection of libraries for building collaborative editing apps.

98 lines (97 loc) 3.14 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WebUndo = void 0; const util_1 = require("../../util"); /** * A DOM-based undo manager. Integrates with native undo/redo functionality of * the browser. Supports user Ctrl+Z and Ctrl+Shift+Z shortcuts and application * context menu undo/redo events. */ class WebUndo { constructor() { /** Whether we are in a process of pushing a new undo item. */ this._push = false; /** Undo stack. */ this.uStack = []; /** Redo stack. */ this.rStack = []; this.onFocus = () => { const el = this.el; setTimeout(() => el.blur(), 0); }; this.onInput = () => { const tlen = this.el.innerText.length; if (!this._push) { const { uStack: undo, rStack: redo } = this; while (undo.length && undo.length > tlen) this._undo(); while (redo.length && undo.length < tlen) this._redo(); } }; } _undo() { const undo = this.uStack.pop(); if (undo) { const redo = undo[1](undo[0]); this.rStack.push(redo); } } _redo() { const redo = this.rStack.pop(); if (redo) { const undo = redo[1](redo[0]); this.uStack.push(undo); } } // /** ------------------------------------------------------ {@link UndoRedo} */ push(undo) { const el = this.el; const restoreSelection = (0, util_1.saveSelection)(); try { this._push = true; this.rStack = []; el.setAttribute('aria-hidden', 'false'); el.focus(); document.execCommand?.('insertText', false, '.'); const tlen = this.el.innerText.length; if (tlen - 1 === this.uStack.length) this.uStack.push(undo); } finally { el.blur(); this._push = false; el.setAttribute('aria-hidden', 'true'); restoreSelection?.(); } } undo() { document?.execCommand?.('undo'); } redo() { document?.execCommand?.('redo'); } /** -------------------------------------------------- {@link UiLifeCycles} */ start() { const el = (this.el = document.createElement('div')); el.tabIndex = -1; el.contentEditable = 'true'; el.setAttribute('aria-hidden', 'true'); const style = el.style; style.pointerEvents = 'none'; style.position = 'fixed'; style.fontSize = '1px'; style.top = '-1000px'; style.opacity = '0'; const body = document.body; body.appendChild(el); el.addEventListener('focus', this.onFocus); el.addEventListener('input', this.onInput); return () => { body.removeChild(el); el.removeEventListener('focus', this.onFocus); el.removeEventListener('input', this.onInput); }; } } exports.WebUndo = WebUndo;