a730fdb908f461fc9b9f146810921bdfebc4aad5f135c026bc64dafb799038e0bd95825c4251ee06309fa4fad18d69532f3cc3f4b510ac13c7b1fff0911b6e 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import { ParentBlot } from 'parchment';
  2. import Module from '../core/module.js';
  3. import Quill from '../core/quill.js';
  4. const isMac = /Mac/i.test(navigator.platform);
  5. // Export for testing
  6. export const TTL_FOR_VALID_SELECTION_CHANGE = 100;
  7. // A loose check to determine if the shortcut can move the caret before a UI node:
  8. // <ANY_PARENT>[CARET]<div class="ql-ui"></div>[CONTENT]</ANY_PARENT>
  9. const canMoveCaretBeforeUINode = event => {
  10. if (event.key === 'ArrowLeft' || event.key === 'ArrowRight' ||
  11. // RTL scripts or moving from the end of the previous line
  12. event.key === 'ArrowUp' || event.key === 'ArrowDown' || event.key === 'Home') {
  13. return true;
  14. }
  15. if (isMac && event.key === 'a' && event.ctrlKey === true) {
  16. return true;
  17. }
  18. return false;
  19. };
  20. class UINode extends Module {
  21. isListening = false;
  22. selectionChangeDeadline = 0;
  23. constructor(quill, options) {
  24. super(quill, options);
  25. this.handleArrowKeys();
  26. this.handleNavigationShortcuts();
  27. }
  28. handleArrowKeys() {
  29. this.quill.keyboard.addBinding({
  30. key: ['ArrowLeft', 'ArrowRight'],
  31. offset: 0,
  32. shiftKey: null,
  33. handler(range, _ref) {
  34. let {
  35. line,
  36. event
  37. } = _ref;
  38. if (!(line instanceof ParentBlot) || !line.uiNode) {
  39. return true;
  40. }
  41. const isRTL = getComputedStyle(line.domNode)['direction'] === 'rtl';
  42. if (isRTL && event.key !== 'ArrowRight' || !isRTL && event.key !== 'ArrowLeft') {
  43. return true;
  44. }
  45. this.quill.setSelection(range.index - 1, range.length + (event.shiftKey ? 1 : 0), Quill.sources.USER);
  46. return false;
  47. }
  48. });
  49. }
  50. handleNavigationShortcuts() {
  51. this.quill.root.addEventListener('keydown', event => {
  52. if (!event.defaultPrevented && canMoveCaretBeforeUINode(event)) {
  53. this.ensureListeningToSelectionChange();
  54. }
  55. });
  56. }
  57. /**
  58. * We only listen to the `selectionchange` event when
  59. * there is an intention of moving the caret to the beginning using shortcuts.
  60. * This is primarily implemented to prevent infinite loops, as we are changing
  61. * the selection within the handler of a `selectionchange` event.
  62. */
  63. ensureListeningToSelectionChange() {
  64. this.selectionChangeDeadline = Date.now() + TTL_FOR_VALID_SELECTION_CHANGE;
  65. if (this.isListening) return;
  66. this.isListening = true;
  67. const listener = () => {
  68. this.isListening = false;
  69. if (Date.now() <= this.selectionChangeDeadline) {
  70. this.handleSelectionChange();
  71. }
  72. };
  73. document.addEventListener('selectionchange', listener, {
  74. once: true
  75. });
  76. }
  77. handleSelectionChange() {
  78. const selection = document.getSelection();
  79. if (!selection) return;
  80. const range = selection.getRangeAt(0);
  81. if (range.collapsed !== true || range.startOffset !== 0) return;
  82. const line = this.quill.scroll.find(range.startContainer);
  83. if (!(line instanceof ParentBlot) || !line.uiNode) return;
  84. const newRange = document.createRange();
  85. newRange.setStartAfter(line.uiNode);
  86. newRange.setEndAfter(line.uiNode);
  87. selection.removeAllRanges();
  88. selection.addRange(newRange);
  89. }
  90. }
  91. export default UINode;
  92. //# sourceMappingURL=uiNode.js.map