json-joy
Version:
Collection of libraries for building collaborative editing apps.
98 lines (97 loc) • 3.14 kB
JavaScript
"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;