quill
Version:
Your powerful, rich text editor
95 lines (92 loc) • 3.23 kB
JavaScript
import { ParentBlot } from 'parchment';
import Module from '../core/module.js';
import Quill from '../core/quill.js';
const isMac = /Mac/i.test(navigator.platform);
// Export for testing
export const TTL_FOR_VALID_SELECTION_CHANGE = 100;
// A loose check to determine if the shortcut can move the caret before a UI node:
// <ANY_PARENT>[CARET]<div class="ql-ui"></div>[CONTENT]</ANY_PARENT>
const canMoveCaretBeforeUINode = event => {
if (event.key === 'ArrowLeft' || event.key === 'ArrowRight' ||
// RTL scripts or moving from the end of the previous line
event.key === 'ArrowUp' || event.key === 'ArrowDown' || event.key === 'Home') {
return true;
}
if (isMac && event.key === 'a' && event.ctrlKey === true) {
return true;
}
return false;
};
class UINode extends Module {
isListening = false;
selectionChangeDeadline = 0;
constructor(quill, options) {
super(quill, options);
this.handleArrowKeys();
this.handleNavigationShortcuts();
}
handleArrowKeys() {
this.quill.keyboard.addBinding({
key: ['ArrowLeft', 'ArrowRight'],
offset: 0,
shiftKey: null,
handler(range, _ref) {
let {
line,
event
} = _ref;
if (!(line instanceof ParentBlot) || !line.uiNode) {
return true;
}
const isRTL = getComputedStyle(line.domNode)['direction'] === 'rtl';
if (isRTL && event.key !== 'ArrowRight' || !isRTL && event.key !== 'ArrowLeft') {
return true;
}
this.quill.setSelection(range.index - 1, range.length + (event.shiftKey ? 1 : 0), Quill.sources.USER);
return false;
}
});
}
handleNavigationShortcuts() {
this.quill.root.addEventListener('keydown', event => {
if (!event.defaultPrevented && canMoveCaretBeforeUINode(event)) {
this.ensureListeningToSelectionChange();
}
});
}
/**
* We only listen to the `selectionchange` event when
* there is an intention of moving the caret to the beginning using shortcuts.
* This is primarily implemented to prevent infinite loops, as we are changing
* the selection within the handler of a `selectionchange` event.
*/
ensureListeningToSelectionChange() {
this.selectionChangeDeadline = Date.now() + TTL_FOR_VALID_SELECTION_CHANGE;
if (this.isListening) return;
this.isListening = true;
const listener = () => {
this.isListening = false;
if (Date.now() <= this.selectionChangeDeadline) {
this.handleSelectionChange();
}
};
document.addEventListener('selectionchange', listener, {
once: true
});
}
handleSelectionChange() {
const selection = document.getSelection();
if (!selection) return;
const range = selection.getRangeAt(0);
if (range.collapsed !== true || range.startOffset !== 0) return;
const line = this.quill.scroll.find(range.startContainer);
if (!(line instanceof ParentBlot) || !line.uiNode) return;
const newRange = document.createRange();
newRange.setStartAfter(line.uiNode);
newRange.setEndAfter(line.uiNode);
selection.removeAllRanges();
selection.addRange(newRange);
}
}
export default UINode;
//# sourceMappingURL=uiNode.js.map