UNPKG

179 kBJavaScriptView Raw
1import getDirection from 'direction';
2import debounce from 'lodash/debounce';
3import throttle from 'lodash/throttle';
4import React, { createContext, useContext, useRef, useEffect, useLayoutEffect, useState, memo, forwardRef, useCallback, Component, useReducer, useMemo } from 'react';
5import scrollIntoView from 'scroll-into-view-if-needed';
6import { Transforms, Element as Element$1, Editor, Scrubber, Range, Path, Point, Node, Text as Text$1 } from 'slate';
7import { ResizeObserver } from '@juggle/resize-observer';
8import { isHotkey } from 'is-hotkey';
9import ReactDOM from 'react-dom';
10
11function _objectWithoutPropertiesLoose(source, excluded) {
12 if (source == null) return {};
13 var target = {};
14 var sourceKeys = Object.keys(source);
15 var key, i;
16 for (i = 0; i < sourceKeys.length; i++) {
17 key = sourceKeys[i];
18 if (excluded.indexOf(key) >= 0) continue;
19 target[key] = source[key];
20 }
21 return target;
22}
23
24function _objectWithoutProperties(source, excluded) {
25 if (source == null) return {};
26 var target = _objectWithoutPropertiesLoose(source, excluded);
27 var key, i;
28 if (Object.getOwnPropertySymbols) {
29 var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
30 for (i = 0; i < sourceSymbolKeys.length; i++) {
31 key = sourceSymbolKeys[i];
32 if (excluded.indexOf(key) >= 0) continue;
33 if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
34 target[key] = source[key];
35 }
36 }
37 return target;
38}
39
40function _typeof(o) {
41 "@babel/helpers - typeof";
42
43 return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
44 return typeof o;
45 } : function (o) {
46 return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
47 }, _typeof(o);
48}
49
50function _toPrimitive(input, hint) {
51 if (_typeof(input) !== "object" || input === null) return input;
52 var prim = input[Symbol.toPrimitive];
53 if (prim !== undefined) {
54 var res = prim.call(input, hint || "default");
55 if (_typeof(res) !== "object") return res;
56 throw new TypeError("@@toPrimitive must return a primitive value.");
57 }
58 return (hint === "string" ? String : Number)(input);
59}
60
61function _toPropertyKey(arg) {
62 var key = _toPrimitive(arg, "string");
63 return _typeof(key) === "symbol" ? key : String(key);
64}
65
66function _defineProperty(obj, key, value) {
67 key = _toPropertyKey(key);
68 if (key in obj) {
69 Object.defineProperty(obj, key, {
70 value: value,
71 enumerable: true,
72 configurable: true,
73 writable: true
74 });
75 } else {
76 obj[key] = value;
77 }
78 return obj;
79}
80
81/**
82 * A React context for sharing the editor object.
83 */
84var EditorContext = /*#__PURE__*/createContext(null);
85/**
86 * Get the current editor object from the React context.
87 */
88var useSlateStatic = () => {
89 var editor = useContext(EditorContext);
90 if (!editor) {
91 throw new Error("The `useSlateStatic` hook must be used inside the <Slate> component's context.");
92 }
93 return editor;
94};
95
96var REACT_MAJOR_VERSION = parseInt(React.version.split('.')[0], 10);
97var IS_IOS = typeof navigator !== 'undefined' && typeof window !== 'undefined' && /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
98var IS_APPLE = typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent);
99var IS_ANDROID = typeof navigator !== 'undefined' && /Android/.test(navigator.userAgent);
100var IS_FIREFOX = typeof navigator !== 'undefined' && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
101var IS_WEBKIT = typeof navigator !== 'undefined' && /AppleWebKit(?!.*Chrome)/i.test(navigator.userAgent);
102// "modern" Edge was released at 79.x
103var IS_EDGE_LEGACY = typeof navigator !== 'undefined' && /Edge?\/(?:[0-6][0-9]|[0-7][0-8])(?:\.)/i.test(navigator.userAgent);
104var IS_CHROME = typeof navigator !== 'undefined' && /Chrome/i.test(navigator.userAgent);
105// Native `beforeInput` events don't work well with react on Chrome 75
106// and older, Chrome 76+ can use `beforeInput` though.
107var IS_CHROME_LEGACY = typeof navigator !== 'undefined' && /Chrome?\/(?:[0-7][0-5]|[0-6][0-9])(?:\.)/i.test(navigator.userAgent);
108var IS_ANDROID_CHROME_LEGACY = IS_ANDROID && typeof navigator !== 'undefined' && /Chrome?\/(?:[0-5]?\d)(?:\.)/i.test(navigator.userAgent);
109// Firefox did not support `beforeInput` until `v87`.
110var IS_FIREFOX_LEGACY = typeof navigator !== 'undefined' && /^(?!.*Seamonkey)(?=.*Firefox\/(?:[0-7][0-9]|[0-8][0-6])(?:\.)).*/i.test(navigator.userAgent);
111// UC mobile browser
112var IS_UC_MOBILE = typeof navigator !== 'undefined' && /.*UCBrowser/.test(navigator.userAgent);
113// Wechat browser (not including mac wechat)
114var IS_WECHATBROWSER = typeof navigator !== 'undefined' && /.*Wechat/.test(navigator.userAgent) && !/.*MacWechat/.test(navigator.userAgent); // avoid lookbehind (buggy in safari < 16.4)
115// Check if DOM is available as React does internally.
116// https://github.com/facebook/react/blob/master/packages/shared/ExecutionEnvironment.js
117var CAN_USE_DOM = !!(typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined');
118// COMPAT: Firefox/Edge Legacy don't support the `beforeinput` event
119// Chrome Legacy doesn't support `beforeinput` correctly
120var HAS_BEFORE_INPUT_SUPPORT = (!IS_CHROME_LEGACY || !IS_ANDROID_CHROME_LEGACY) && !IS_EDGE_LEGACY &&
121// globalThis is undefined in older browsers
122typeof globalThis !== 'undefined' && globalThis.InputEvent &&
123// @ts-ignore The `getTargetRanges` property isn't recognized.
124typeof globalThis.InputEvent.prototype.getTargetRanges === 'function';
125
126/**
127 * Two weak maps that allow us rebuild a path given a node. They are populated
128 * at render time such that after a render occurs we can always backtrack.
129 */
130var NODE_TO_INDEX = new WeakMap();
131var NODE_TO_PARENT = new WeakMap();
132/**
133 * Weak maps that allow us to go between Slate nodes and DOM nodes. These
134 * are used to resolve DOM event-related logic into Slate actions.
135 */
136var EDITOR_TO_WINDOW = new WeakMap();
137var EDITOR_TO_ELEMENT = new WeakMap();
138var EDITOR_TO_PLACEHOLDER_ELEMENT = new WeakMap();
139var ELEMENT_TO_NODE = new WeakMap();
140var NODE_TO_ELEMENT = new WeakMap();
141var NODE_TO_KEY = new WeakMap();
142var EDITOR_TO_KEY_TO_ELEMENT = new WeakMap();
143/**
144 * Weak maps for storing editor-related state.
145 */
146var IS_READ_ONLY = new WeakMap();
147var IS_FOCUSED = new WeakMap();
148var IS_COMPOSING = new WeakMap();
149var EDITOR_TO_USER_SELECTION = new WeakMap();
150/**
151 * Weak map for associating the context `onChange` context with the plugin.
152 */
153var EDITOR_TO_ON_CHANGE = new WeakMap();
154/**
155 * Weak maps for saving pending state on composition stage.
156 */
157var EDITOR_TO_SCHEDULE_FLUSH = new WeakMap();
158var EDITOR_TO_PENDING_INSERTION_MARKS = new WeakMap();
159var EDITOR_TO_USER_MARKS = new WeakMap();
160/**
161 * Android input handling specific weak-maps
162 */
163var EDITOR_TO_PENDING_DIFFS = new WeakMap();
164var EDITOR_TO_PENDING_ACTION = new WeakMap();
165var EDITOR_TO_PENDING_SELECTION = new WeakMap();
166var EDITOR_TO_FORCE_RENDER = new WeakMap();
167/**
168 * Symbols.
169 */
170var PLACEHOLDER_SYMBOL = Symbol('placeholder');
171var MARK_PLACEHOLDER_SYMBOL = Symbol('mark-placeholder');
172
173/**
174 * Types.
175 */
176// COMPAT: This is required to prevent TypeScript aliases from doing some very
177// weird things for Slate's types with the same name as globals. (2019/11/27)
178// https://github.com/microsoft/TypeScript/issues/35002
179var DOMText = globalThis.Text;
180/**
181 * Returns the host window of a DOM node
182 */
183var getDefaultView = value => {
184 return value && value.ownerDocument && value.ownerDocument.defaultView || null;
185};
186/**
187 * Check if a DOM node is a comment node.
188 */
189var isDOMComment = value => {
190 return isDOMNode(value) && value.nodeType === 8;
191};
192/**
193 * Check if a DOM node is an element node.
194 */
195var isDOMElement = value => {
196 return isDOMNode(value) && value.nodeType === 1;
197};
198/**
199 * Check if a value is a DOM node.
200 */
201var isDOMNode = value => {
202 var window = getDefaultView(value);
203 return !!window && value instanceof window.Node;
204};
205/**
206 * Check if a value is a DOM selection.
207 */
208var isDOMSelection = value => {
209 var window = value && value.anchorNode && getDefaultView(value.anchorNode);
210 return !!window && value instanceof window.Selection;
211};
212/**
213 * Check if a DOM node is an element node.
214 */
215var isDOMText = value => {
216 return isDOMNode(value) && value.nodeType === 3;
217};
218/**
219 * Checks whether a paste event is a plaintext-only event.
220 */
221var isPlainTextOnlyPaste = event => {
222 return event.clipboardData && event.clipboardData.getData('text/plain') !== '' && event.clipboardData.types.length === 1;
223};
224/**
225 * Normalize a DOM point so that it always refers to a text node.
226 */
227var normalizeDOMPoint = domPoint => {
228 var [node, offset] = domPoint;
229 // If it's an element node, its offset refers to the index of its children
230 // including comment nodes, so try to find the right text child node.
231 if (isDOMElement(node) && node.childNodes.length) {
232 var isLast = offset === node.childNodes.length;
233 var index = isLast ? offset - 1 : offset;
234 [node, index] = getEditableChildAndIndex(node, index, isLast ? 'backward' : 'forward');
235 // If the editable child found is in front of input offset, we instead seek to its end
236 isLast = index < offset;
237 // If the node has children, traverse until we have a leaf node. Leaf nodes
238 // can be either text nodes, or other void DOM nodes.
239 while (isDOMElement(node) && node.childNodes.length) {
240 var i = isLast ? node.childNodes.length - 1 : 0;
241 node = getEditableChild(node, i, isLast ? 'backward' : 'forward');
242 }
243 // Determine the new offset inside the text node.
244 offset = isLast && node.textContent != null ? node.textContent.length : 0;
245 }
246 // Return the node and offset.
247 return [node, offset];
248};
249/**
250 * Determines whether the active element is nested within a shadowRoot
251 */
252var hasShadowRoot = node => {
253 var parent = node && node.parentNode;
254 while (parent) {
255 if (parent.toString() === '[object ShadowRoot]') {
256 return true;
257 }
258 parent = parent.parentNode;
259 }
260 return false;
261};
262/**
263 * Get the nearest editable child and index at `index` in a `parent`, preferring
264 * `direction`.
265 */
266var getEditableChildAndIndex = (parent, index, direction) => {
267 var {
268 childNodes
269 } = parent;
270 var child = childNodes[index];
271 var i = index;
272 var triedForward = false;
273 var triedBackward = false;
274 // While the child is a comment node, or an element node with no children,
275 // keep iterating to find a sibling non-void, non-comment node.
276 while (isDOMComment(child) || isDOMElement(child) && child.childNodes.length === 0 || isDOMElement(child) && child.getAttribute('contenteditable') === 'false') {
277 if (triedForward && triedBackward) {
278 break;
279 }
280 if (i >= childNodes.length) {
281 triedForward = true;
282 i = index - 1;
283 direction = 'backward';
284 continue;
285 }
286 if (i < 0) {
287 triedBackward = true;
288 i = index + 1;
289 direction = 'forward';
290 continue;
291 }
292 child = childNodes[i];
293 index = i;
294 i += direction === 'forward' ? 1 : -1;
295 }
296 return [child, index];
297};
298/**
299 * Get the nearest editable child at `index` in a `parent`, preferring
300 * `direction`.
301 */
302var getEditableChild = (parent, index, direction) => {
303 var [child] = getEditableChildAndIndex(parent, index, direction);
304 return child;
305};
306/**
307 * Get a plaintext representation of the content of a node, accounting for block
308 * elements which get a newline appended.
309 *
310 * The domNode must be attached to the DOM.
311 */
312var getPlainText = domNode => {
313 var text = '';
314 if (isDOMText(domNode) && domNode.nodeValue) {
315 return domNode.nodeValue;
316 }
317 if (isDOMElement(domNode)) {
318 for (var childNode of Array.from(domNode.childNodes)) {
319 text += getPlainText(childNode);
320 }
321 var display = getComputedStyle(domNode).getPropertyValue('display');
322 if (display === 'block' || display === 'list' || domNode.tagName === 'BR') {
323 text += '\n';
324 }
325 }
326 return text;
327};
328/**
329 * Get x-slate-fragment attribute from data-slate-fragment
330 */
331var catchSlateFragment = /data-slate-fragment="(.+?)"/m;
332var getSlateFragmentAttribute = dataTransfer => {
333 var htmlData = dataTransfer.getData('text/html');
334 var [, fragment] = htmlData.match(catchSlateFragment) || [];
335 return fragment;
336};
337/**
338 * Check whether a mutation originates from a editable element inside the editor.
339 */
340var isTrackedMutation = (editor, mutation, batch) => {
341 var {
342 target
343 } = mutation;
344 if (isDOMElement(target) && target.matches('[contentEditable="false"]')) {
345 return false;
346 }
347 var {
348 document
349 } = ReactEditor.getWindow(editor);
350 if (document.contains(target)) {
351 return ReactEditor.hasDOMNode(editor, target, {
352 editable: true
353 });
354 }
355 var parentMutation = batch.find(_ref => {
356 var {
357 addedNodes,
358 removedNodes
359 } = _ref;
360 for (var node of addedNodes) {
361 if (node === target || node.contains(target)) {
362 return true;
363 }
364 }
365 for (var _node of removedNodes) {
366 if (_node === target || _node.contains(target)) {
367 return true;
368 }
369 }
370 });
371 if (!parentMutation || parentMutation === mutation) {
372 return false;
373 }
374 // Target add/remove is tracked. Track the mutation if we track the parent mutation.
375 return isTrackedMutation(editor, parentMutation, batch);
376};
377
378/**
379 * An auto-incrementing identifier for keys.
380 */
381var n = 0;
382/**
383 * A class that keeps track of a key string. We use a full class here because we
384 * want to be able to use them as keys in `WeakMap` objects.
385 */
386class Key {
387 constructor() {
388 _defineProperty(this, "id", void 0);
389 this.id = "".concat(n++);
390 }
391}
392
393// eslint-disable-next-line no-redeclare
394var ReactEditor = {
395 androidPendingDiffs: editor => EDITOR_TO_PENDING_DIFFS.get(editor),
396 androidScheduleFlush: editor => {
397 var _EDITOR_TO_SCHEDULE_F;
398 (_EDITOR_TO_SCHEDULE_F = EDITOR_TO_SCHEDULE_FLUSH.get(editor)) === null || _EDITOR_TO_SCHEDULE_F === void 0 || _EDITOR_TO_SCHEDULE_F();
399 },
400 blur: editor => {
401 var el = ReactEditor.toDOMNode(editor, editor);
402 var root = ReactEditor.findDocumentOrShadowRoot(editor);
403 IS_FOCUSED.set(editor, false);
404 if (root.activeElement === el) {
405 el.blur();
406 }
407 },
408 deselect: editor => {
409 var {
410 selection
411 } = editor;
412 var root = ReactEditor.findDocumentOrShadowRoot(editor);
413 var domSelection = root.getSelection();
414 if (domSelection && domSelection.rangeCount > 0) {
415 domSelection.removeAllRanges();
416 }
417 if (selection) {
418 Transforms.deselect(editor);
419 }
420 },
421 findDocumentOrShadowRoot: editor => {
422 var el = ReactEditor.toDOMNode(editor, editor);
423 var root = el.getRootNode();
424 if ((root instanceof Document || root instanceof ShadowRoot) && root.getSelection != null) {
425 return root;
426 }
427 return el.ownerDocument;
428 },
429 findEventRange: (editor, event) => {
430 if ('nativeEvent' in event) {
431 event = event.nativeEvent;
432 }
433 var {
434 clientX: x,
435 clientY: y,
436 target
437 } = event;
438 if (x == null || y == null) {
439 throw new Error("Cannot resolve a Slate range from a DOM event: ".concat(event));
440 }
441 var node = ReactEditor.toSlateNode(editor, event.target);
442 var path = ReactEditor.findPath(editor, node);
443 // If the drop target is inside a void node, move it into either the
444 // next or previous node, depending on which side the `x` and `y`
445 // coordinates are closest to.
446 if (Element$1.isElement(node) && Editor.isVoid(editor, node)) {
447 var rect = target.getBoundingClientRect();
448 var isPrev = editor.isInline(node) ? x - rect.left < rect.left + rect.width - x : y - rect.top < rect.top + rect.height - y;
449 var edge = Editor.point(editor, path, {
450 edge: isPrev ? 'start' : 'end'
451 });
452 var point = isPrev ? Editor.before(editor, edge) : Editor.after(editor, edge);
453 if (point) {
454 var _range = Editor.range(editor, point);
455 return _range;
456 }
457 }
458 // Else resolve a range from the caret position where the drop occured.
459 var domRange;
460 var {
461 document
462 } = ReactEditor.getWindow(editor);
463 // COMPAT: In Firefox, `caretRangeFromPoint` doesn't exist. (2016/07/25)
464 if (document.caretRangeFromPoint) {
465 domRange = document.caretRangeFromPoint(x, y);
466 } else {
467 var position = document.caretPositionFromPoint(x, y);
468 if (position) {
469 domRange = document.createRange();
470 domRange.setStart(position.offsetNode, position.offset);
471 domRange.setEnd(position.offsetNode, position.offset);
472 }
473 }
474 if (!domRange) {
475 throw new Error("Cannot resolve a Slate range from a DOM event: ".concat(event));
476 }
477 // Resolve a Slate range from the DOM range.
478 var range = ReactEditor.toSlateRange(editor, domRange, {
479 exactMatch: false,
480 suppressThrow: false
481 });
482 return range;
483 },
484 findKey: (editor, node) => {
485 var key = NODE_TO_KEY.get(node);
486 if (!key) {
487 key = new Key();
488 NODE_TO_KEY.set(node, key);
489 }
490 return key;
491 },
492 findPath: (editor, node) => {
493 var path = [];
494 var child = node;
495 while (true) {
496 var parent = NODE_TO_PARENT.get(child);
497 if (parent == null) {
498 if (Editor.isEditor(child)) {
499 return path;
500 } else {
501 break;
502 }
503 }
504 var i = NODE_TO_INDEX.get(child);
505 if (i == null) {
506 break;
507 }
508 path.unshift(i);
509 child = parent;
510 }
511 throw new Error("Unable to find the path for Slate node: ".concat(Scrubber.stringify(node)));
512 },
513 focus: function focus(editor) {
514 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
515 retries: 5
516 };
517 // Return if already focused
518 if (IS_FOCUSED.get(editor)) {
519 return;
520 }
521 // Retry setting focus if the editor has pending operations.
522 // The DOM (selection) is unstable while changes are applied.
523 // Retry until retries are exhausted or editor is focused.
524 if (options.retries <= 0) {
525 throw new Error('Could not set focus, editor seems stuck with pending operations');
526 }
527 if (editor.operations.length > 0) {
528 setTimeout(() => {
529 ReactEditor.focus(editor, {
530 retries: options.retries - 1
531 });
532 }, 10);
533 return;
534 }
535 var el = ReactEditor.toDOMNode(editor, editor);
536 var root = ReactEditor.findDocumentOrShadowRoot(editor);
537 if (root.activeElement !== el) {
538 // Ensure that the DOM selection state is set to the editor's selection
539 if (editor.selection && root instanceof Document) {
540 var domSelection = root.getSelection();
541 var domRange = ReactEditor.toDOMRange(editor, editor.selection);
542 domSelection === null || domSelection === void 0 || domSelection.removeAllRanges();
543 domSelection === null || domSelection === void 0 || domSelection.addRange(domRange);
544 }
545 // Create a new selection in the top of the document if missing
546 if (!editor.selection) {
547 Transforms.select(editor, Editor.start(editor, []));
548 editor.onChange();
549 }
550 // IS_FOCUSED should be set before calling el.focus() to ensure that
551 // FocusedContext is updated to the correct value
552 IS_FOCUSED.set(editor, true);
553 el.focus({
554 preventScroll: true
555 });
556 }
557 },
558 getWindow: editor => {
559 var window = EDITOR_TO_WINDOW.get(editor);
560 if (!window) {
561 throw new Error('Unable to find a host window element for this editor');
562 }
563 return window;
564 },
565 hasDOMNode: function hasDOMNode(editor, target) {
566 var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
567 var {
568 editable = false
569 } = options;
570 var editorEl = ReactEditor.toDOMNode(editor, editor);
571 var targetEl;
572 // COMPAT: In Firefox, reading `target.nodeType` will throw an error if
573 // target is originating from an internal "restricted" element (e.g. a
574 // stepper arrow on a number input). (2018/05/04)
575 // https://github.com/ianstormtaylor/slate/issues/1819
576 try {
577 targetEl = isDOMElement(target) ? target : target.parentElement;
578 } catch (err) {
579 if (err instanceof Error && !err.message.includes('Permission denied to access property "nodeType"')) {
580 throw err;
581 }
582 }
583 if (!targetEl) {
584 return false;
585 }
586 return targetEl.closest("[data-slate-editor]") === editorEl && (!editable || targetEl.isContentEditable ? true : typeof targetEl.isContentEditable === 'boolean' &&
587 // isContentEditable exists only on HTMLElement, and on other nodes it will be undefined
588 // this is the core logic that lets you know you got the right editor.selection instead of null when editor is contenteditable="false"(readOnly)
589 targetEl.closest('[contenteditable="false"]') === editorEl || !!targetEl.getAttribute('data-slate-zero-width'));
590 },
591 hasEditableTarget: (editor, target) => isDOMNode(target) && ReactEditor.hasDOMNode(editor, target, {
592 editable: true
593 }),
594 hasRange: (editor, range) => {
595 var {
596 anchor,
597 focus
598 } = range;
599 return Editor.hasPath(editor, anchor.path) && Editor.hasPath(editor, focus.path);
600 },
601 hasSelectableTarget: (editor, target) => ReactEditor.hasEditableTarget(editor, target) || ReactEditor.isTargetInsideNonReadonlyVoid(editor, target),
602 hasTarget: (editor, target) => isDOMNode(target) && ReactEditor.hasDOMNode(editor, target),
603 insertData: (editor, data) => {
604 editor.insertData(data);
605 },
606 insertFragmentData: (editor, data) => editor.insertFragmentData(data),
607 insertTextData: (editor, data) => editor.insertTextData(data),
608 isComposing: editor => {
609 return !!IS_COMPOSING.get(editor);
610 },
611 isFocused: editor => !!IS_FOCUSED.get(editor),
612 isReadOnly: editor => !!IS_READ_ONLY.get(editor),
613 isTargetInsideNonReadonlyVoid: (editor, target) => {
614 if (IS_READ_ONLY.get(editor)) return false;
615 var slateNode = ReactEditor.hasTarget(editor, target) && ReactEditor.toSlateNode(editor, target);
616 return Element$1.isElement(slateNode) && Editor.isVoid(editor, slateNode);
617 },
618 setFragmentData: (editor, data, originEvent) => editor.setFragmentData(data, originEvent),
619 toDOMNode: (editor, node) => {
620 var KEY_TO_ELEMENT = EDITOR_TO_KEY_TO_ELEMENT.get(editor);
621 var domNode = Editor.isEditor(node) ? EDITOR_TO_ELEMENT.get(editor) : KEY_TO_ELEMENT === null || KEY_TO_ELEMENT === void 0 ? void 0 : KEY_TO_ELEMENT.get(ReactEditor.findKey(editor, node));
622 if (!domNode) {
623 throw new Error("Cannot resolve a DOM node from Slate node: ".concat(Scrubber.stringify(node)));
624 }
625 return domNode;
626 },
627 toDOMPoint: (editor, point) => {
628 var [node] = Editor.node(editor, point.path);
629 var el = ReactEditor.toDOMNode(editor, node);
630 var domPoint;
631 // If we're inside a void node, force the offset to 0, otherwise the zero
632 // width spacing character will result in an incorrect offset of 1
633 if (Editor.void(editor, {
634 at: point
635 })) {
636 point = {
637 path: point.path,
638 offset: 0
639 };
640 }
641 // For each leaf, we need to isolate its content, which means filtering
642 // to its direct text and zero-width spans. (We have to filter out any
643 // other siblings that may have been rendered alongside them.)
644 var selector = "[data-slate-string], [data-slate-zero-width]";
645 var texts = Array.from(el.querySelectorAll(selector));
646 var start = 0;
647 for (var i = 0; i < texts.length; i++) {
648 var text = texts[i];
649 var domNode = text.childNodes[0];
650 if (domNode == null || domNode.textContent == null) {
651 continue;
652 }
653 var {
654 length
655 } = domNode.textContent;
656 var attr = text.getAttribute('data-slate-length');
657 var trueLength = attr == null ? length : parseInt(attr, 10);
658 var end = start + trueLength;
659 // Prefer putting the selection inside the mark placeholder to ensure
660 // composed text is displayed with the correct marks.
661 var nextText = texts[i + 1];
662 if (point.offset === end && nextText !== null && nextText !== void 0 && nextText.hasAttribute('data-slate-mark-placeholder')) {
663 var _nextText$textContent;
664 var domText = nextText.childNodes[0];
665 domPoint = [
666 // COMPAT: If we don't explicity set the dom point to be on the actual
667 // dom text element, chrome will put the selection behind the actual dom
668 // text element, causing domRange.getBoundingClientRect() calls on a collapsed
669 // selection to return incorrect zero values (https://bugs.chromium.org/p/chromium/issues/detail?id=435438)
670 // which will cause issues when scrolling to it.
671 domText instanceof DOMText ? domText : nextText, (_nextText$textContent = nextText.textContent) !== null && _nextText$textContent !== void 0 && _nextText$textContent.startsWith('\uFEFF') ? 1 : 0];
672 break;
673 }
674 if (point.offset <= end) {
675 var offset = Math.min(length, Math.max(0, point.offset - start));
676 domPoint = [domNode, offset];
677 break;
678 }
679 start = end;
680 }
681 if (!domPoint) {
682 throw new Error("Cannot resolve a DOM point from Slate point: ".concat(Scrubber.stringify(point)));
683 }
684 return domPoint;
685 },
686 toDOMRange: (editor, range) => {
687 var {
688 anchor,
689 focus
690 } = range;
691 var isBackward = Range.isBackward(range);
692 var domAnchor = ReactEditor.toDOMPoint(editor, anchor);
693 var domFocus = Range.isCollapsed(range) ? domAnchor : ReactEditor.toDOMPoint(editor, focus);
694 var window = ReactEditor.getWindow(editor);
695 var domRange = window.document.createRange();
696 var [startNode, startOffset] = isBackward ? domFocus : domAnchor;
697 var [endNode, endOffset] = isBackward ? domAnchor : domFocus;
698 // A slate Point at zero-width Leaf always has an offset of 0 but a native DOM selection at
699 // zero-width node has an offset of 1 so we have to check if we are in a zero-width node and
700 // adjust the offset accordingly.
701 var startEl = isDOMElement(startNode) ? startNode : startNode.parentElement;
702 var isStartAtZeroWidth = !!startEl.getAttribute('data-slate-zero-width');
703 var endEl = isDOMElement(endNode) ? endNode : endNode.parentElement;
704 var isEndAtZeroWidth = !!endEl.getAttribute('data-slate-zero-width');
705 domRange.setStart(startNode, isStartAtZeroWidth ? 1 : startOffset);
706 domRange.setEnd(endNode, isEndAtZeroWidth ? 1 : endOffset);
707 return domRange;
708 },
709 toSlateNode: (editor, domNode) => {
710 var domEl = isDOMElement(domNode) ? domNode : domNode.parentElement;
711 if (domEl && !domEl.hasAttribute('data-slate-node')) {
712 domEl = domEl.closest("[data-slate-node]");
713 }
714 var node = domEl ? ELEMENT_TO_NODE.get(domEl) : null;
715 if (!node) {
716 throw new Error("Cannot resolve a Slate node from DOM node: ".concat(domEl));
717 }
718 return node;
719 },
720 toSlatePoint: (editor, domPoint, options) => {
721 var {
722 exactMatch,
723 suppressThrow
724 } = options;
725 var [nearestNode, nearestOffset] = exactMatch ? domPoint : normalizeDOMPoint(domPoint);
726 var parentNode = nearestNode.parentNode;
727 var textNode = null;
728 var offset = 0;
729 if (parentNode) {
730 var _domNode$textContent, _domNode$textContent2;
731 var editorEl = ReactEditor.toDOMNode(editor, editor);
732 var potentialVoidNode = parentNode.closest('[data-slate-void="true"]');
733 // Need to ensure that the closest void node is actually a void node
734 // within this editor, and not a void node within some parent editor. This can happen
735 // if this editor is within a void node of another editor ("nested editors", like in
736 // the "Editable Voids" example on the docs site).
737 var voidNode = potentialVoidNode && editorEl.contains(potentialVoidNode) ? potentialVoidNode : null;
738 var leafNode = parentNode.closest('[data-slate-leaf]');
739 var domNode = null;
740 // Calculate how far into the text node the `nearestNode` is, so that we
741 // can determine what the offset relative to the text node is.
742 if (leafNode) {
743 textNode = leafNode.closest('[data-slate-node="text"]');
744 if (textNode) {
745 var window = ReactEditor.getWindow(editor);
746 var range = window.document.createRange();
747 range.setStart(textNode, 0);
748 range.setEnd(nearestNode, nearestOffset);
749 var contents = range.cloneContents();
750 var removals = [...Array.prototype.slice.call(contents.querySelectorAll('[data-slate-zero-width]')), ...Array.prototype.slice.call(contents.querySelectorAll('[contenteditable=false]'))];
751 removals.forEach(el => {
752 // COMPAT: While composing at the start of a text node, some keyboards put
753 // the text content inside the zero width space.
754 if (IS_ANDROID && !exactMatch && el.hasAttribute('data-slate-zero-width') && el.textContent.length > 0 && el.textContext !== '\uFEFF') {
755 if (el.textContent.startsWith('\uFEFF')) {
756 el.textContent = el.textContent.slice(1);
757 }
758 return;
759 }
760 el.parentNode.removeChild(el);
761 });
762 // COMPAT: Edge has a bug where Range.prototype.toString() will
763 // convert \n into \r\n. The bug causes a loop when slate-react
764 // attempts to reposition its cursor to match the native position. Use
765 // textContent.length instead.
766 // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10291116/
767 offset = contents.textContent.length;
768 domNode = textNode;
769 }
770 } else if (voidNode) {
771 // For void nodes, the element with the offset key will be a cousin, not an
772 // ancestor, so find it by going down from the nearest void parent and taking the
773 // first one that isn't inside a nested editor.
774 var leafNodes = voidNode.querySelectorAll('[data-slate-leaf]');
775 for (var index = 0; index < leafNodes.length; index++) {
776 var current = leafNodes[index];
777 if (ReactEditor.hasDOMNode(editor, current)) {
778 leafNode = current;
779 break;
780 }
781 }
782 // COMPAT: In read-only editors the leaf is not rendered.
783 if (!leafNode) {
784 offset = 1;
785 } else {
786 textNode = leafNode.closest('[data-slate-node="text"]');
787 domNode = leafNode;
788 offset = domNode.textContent.length;
789 domNode.querySelectorAll('[data-slate-zero-width]').forEach(el => {
790 offset -= el.textContent.length;
791 });
792 }
793 }
794 if (domNode && offset === domNode.textContent.length &&
795 // COMPAT: Android IMEs might remove the zero width space while composing,
796 // and we don't add it for line-breaks.
797 IS_ANDROID && domNode.getAttribute('data-slate-zero-width') === 'z' && (_domNode$textContent = domNode.textContent) !== null && _domNode$textContent !== void 0 && _domNode$textContent.startsWith('\uFEFF') && (
798 // COMPAT: If the parent node is a Slate zero-width space, editor is
799 // because the text node should have no characters. However, during IME
800 // composition the ASCII characters will be prepended to the zero-width
801 // space, so subtract 1 from the offset to account for the zero-width
802 // space character.
803 parentNode.hasAttribute('data-slate-zero-width') ||
804 // COMPAT: In Firefox, `range.cloneContents()` returns an extra trailing '\n'
805 // when the document ends with a new-line character. This results in the offset
806 // length being off by one, so we need to subtract one to account for this.
807 IS_FIREFOX && (_domNode$textContent2 = domNode.textContent) !== null && _domNode$textContent2 !== void 0 && _domNode$textContent2.endsWith('\n\n'))) {
808 offset--;
809 }
810 }
811 if (IS_ANDROID && !textNode && !exactMatch) {
812 var node = parentNode.hasAttribute('data-slate-node') ? parentNode : parentNode.closest('[data-slate-node]');
813 if (node && ReactEditor.hasDOMNode(editor, node, {
814 editable: true
815 })) {
816 var _slateNode = ReactEditor.toSlateNode(editor, node);
817 var {
818 path: _path,
819 offset: _offset
820 } = Editor.start(editor, ReactEditor.findPath(editor, _slateNode));
821 if (!node.querySelector('[data-slate-leaf]')) {
822 _offset = nearestOffset;
823 }
824 return {
825 path: _path,
826 offset: _offset
827 };
828 }
829 }
830 if (!textNode) {
831 if (suppressThrow) {
832 return null;
833 }
834 throw new Error("Cannot resolve a Slate point from DOM point: ".concat(domPoint));
835 }
836 // COMPAT: If someone is clicking from one Slate editor into another,
837 // the select event fires twice, once for the old editor's `element`
838 // first, and then afterwards for the correct `element`. (2017/03/03)
839 var slateNode = ReactEditor.toSlateNode(editor, textNode);
840 var path = ReactEditor.findPath(editor, slateNode);
841 return {
842 path,
843 offset
844 };
845 },
846 toSlateRange: (editor, domRange, options) => {
847 var _focusNode$textConten;
848 var {
849 exactMatch,
850 suppressThrow
851 } = options;
852 var el = isDOMSelection(domRange) ? domRange.anchorNode : domRange.startContainer;
853 var anchorNode;
854 var anchorOffset;
855 var focusNode;
856 var focusOffset;
857 var isCollapsed;
858 if (el) {
859 if (isDOMSelection(domRange)) {
860 // COMPAT: In firefox the normal seletion way does not work
861 // (https://github.com/ianstormtaylor/slate/pull/5486#issue-1820720223)
862 if (IS_FIREFOX && domRange.rangeCount > 1) {
863 focusNode = domRange.focusNode; // Focus node works fine
864 var firstRange = domRange.getRangeAt(0);
865 var lastRange = domRange.getRangeAt(domRange.rangeCount - 1);
866 // Here we are in the contenteditable mode of a table in firefox
867 if (focusNode instanceof HTMLTableRowElement && firstRange.startContainer instanceof HTMLTableRowElement && lastRange.startContainer instanceof HTMLTableRowElement) {
868 // HTMLElement, becouse Element is a slate element
869 function getLastChildren(element) {
870 if (element.childElementCount > 0) {
871 return getLastChildren(element.children[0]);
872 } else {
873 return element;
874 }
875 }
876 var firstNodeRow = firstRange.startContainer;
877 var lastNodeRow = lastRange.startContainer;
878 // This should never fail as "The HTMLElement interface represents any HTML element."
879 var firstNode = getLastChildren(firstNodeRow.children[firstRange.startOffset]);
880 var lastNode = getLastChildren(lastNodeRow.children[lastRange.startOffset]);
881 // Zero, as we allways take the right one as the anchor point
882 focusOffset = 0;
883 if (lastNode.childNodes.length > 0) {
884 anchorNode = lastNode.childNodes[0];
885 } else {
886 anchorNode = lastNode;
887 }
888 if (firstNode.childNodes.length > 0) {
889 focusNode = firstNode.childNodes[0];
890 } else {
891 focusNode = firstNode;
892 }
893 if (lastNode instanceof HTMLElement) {
894 anchorOffset = lastNode.innerHTML.length;
895 } else {
896 // Fallback option
897 anchorOffset = 0;
898 }
899 } else {
900 // This is the read only mode of a firefox table
901 // Right to left
902 if (firstRange.startContainer === focusNode) {
903 anchorNode = lastRange.endContainer;
904 anchorOffset = lastRange.endOffset;
905 focusOffset = firstRange.startOffset;
906 } else {
907 // Left to right
908 anchorNode = firstRange.startContainer;
909 anchorOffset = firstRange.endOffset;
910 focusOffset = lastRange.startOffset;
911 }
912 }
913 } else {
914 anchorNode = domRange.anchorNode;
915 anchorOffset = domRange.anchorOffset;
916 focusNode = domRange.focusNode;
917 focusOffset = domRange.focusOffset;
918 }
919 // COMPAT: There's a bug in chrome that always returns `true` for
920 // `isCollapsed` for a Selection that comes from a ShadowRoot.
921 // (2020/08/08)
922 // https://bugs.chromium.org/p/chromium/issues/detail?id=447523
923 // IsCollapsed might not work in firefox, but this will
924 if (IS_CHROME && hasShadowRoot(anchorNode) || IS_FIREFOX) {
925 isCollapsed = domRange.anchorNode === domRange.focusNode && domRange.anchorOffset === domRange.focusOffset;
926 } else {
927 isCollapsed = domRange.isCollapsed;
928 }
929 } else {
930 anchorNode = domRange.startContainer;
931 anchorOffset = domRange.startOffset;
932 focusNode = domRange.endContainer;
933 focusOffset = domRange.endOffset;
934 isCollapsed = domRange.collapsed;
935 }
936 }
937 if (anchorNode == null || focusNode == null || anchorOffset == null || focusOffset == null) {
938 throw new Error("Cannot resolve a Slate range from DOM range: ".concat(domRange));
939 }
940 // COMPAT: Firefox sometimes includes an extra \n (rendered by TextString
941 // when isTrailing is true) in the focusOffset, resulting in an invalid
942 // Slate point. (2023/11/01)
943 if (IS_FIREFOX && (_focusNode$textConten = focusNode.textContent) !== null && _focusNode$textConten !== void 0 && _focusNode$textConten.endsWith('\n\n') && focusOffset === focusNode.textContent.length) {
944 focusOffset--;
945 }
946 // COMPAT: Triple-clicking a word in chrome will sometimes place the focus
947 // inside a `contenteditable="false"` DOM node following the word, which
948 // will cause `toSlatePoint` to throw an error. (2023/03/07)
949 if ('getAttribute' in focusNode && focusNode.getAttribute('contenteditable') === 'false' && focusNode.getAttribute('data-slate-void') !== 'true') {
950 var _anchorNode$textConte;
951 focusNode = anchorNode;
952 focusOffset = ((_anchorNode$textConte = anchorNode.textContent) === null || _anchorNode$textConte === void 0 ? void 0 : _anchorNode$textConte.length) || 0;
953 }
954 var anchor = ReactEditor.toSlatePoint(editor, [anchorNode, anchorOffset], {
955 exactMatch,
956 suppressThrow
957 });
958 if (!anchor) {
959 return null;
960 }
961 var focus = isCollapsed ? anchor : ReactEditor.toSlatePoint(editor, [focusNode, focusOffset], {
962 exactMatch,
963 suppressThrow
964 });
965 if (!focus) {
966 return null;
967 }
968 var range = {
969 anchor: anchor,
970 focus: focus
971 };
972 // if the selection is a hanging range that ends in a void
973 // and the DOM focus is an Element
974 // (meaning that the selection ends before the element)
975 // unhang the range to avoid mistakenly including the void
976 if (Range.isExpanded(range) && Range.isForward(range) && isDOMElement(focusNode) && Editor.void(editor, {
977 at: range.focus,
978 mode: 'highest'
979 })) {
980 range = Editor.unhangRange(editor, range, {
981 voids: true
982 });
983 }
984 return range;
985 }
986};
987
988/**
989 * Check whether a text diff was applied in a way we can perform the pending action on /
990 * recover the pending selection.
991 */
992function verifyDiffState(editor, textDiff) {
993 var {
994 path,
995 diff
996 } = textDiff;
997 if (!Editor.hasPath(editor, path)) {
998 return false;
999 }
1000 var node = Node.get(editor, path);
1001 if (!Text$1.isText(node)) {
1002 return false;
1003 }
1004 if (diff.start !== node.text.length || diff.text.length === 0) {
1005 return node.text.slice(diff.start, diff.start + diff.text.length) === diff.text;
1006 }
1007 var nextPath = Path.next(path);
1008 if (!Editor.hasPath(editor, nextPath)) {
1009 return false;
1010 }
1011 var nextNode = Node.get(editor, nextPath);
1012 return Text$1.isText(nextNode) && nextNode.text.startsWith(diff.text);
1013}
1014function applyStringDiff(text) {
1015 for (var _len = arguments.length, diffs = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
1016 diffs[_key - 1] = arguments[_key];
1017 }
1018 return diffs.reduce((text, diff) => text.slice(0, diff.start) + diff.text + text.slice(diff.end), text);
1019}
1020function longestCommonPrefixLength(str, another) {
1021 var length = Math.min(str.length, another.length);
1022 for (var i = 0; i < length; i++) {
1023 if (str.charAt(i) !== another.charAt(i)) {
1024 return i;
1025 }
1026 }
1027 return length;
1028}
1029function longestCommonSuffixLength(str, another, max) {
1030 var length = Math.min(str.length, another.length, max);
1031 for (var i = 0; i < length; i++) {
1032 if (str.charAt(str.length - i - 1) !== another.charAt(another.length - i - 1)) {
1033 return i;
1034 }
1035 }
1036 return length;
1037}
1038/**
1039 * Remove redundant changes from the diff so that it spans the minimal possible range
1040 */
1041function normalizeStringDiff(targetText, diff) {
1042 var {
1043 start,
1044 end,
1045 text
1046 } = diff;
1047 var removedText = targetText.slice(start, end);
1048 var prefixLength = longestCommonPrefixLength(removedText, text);
1049 var max = Math.min(removedText.length - prefixLength, text.length - prefixLength);
1050 var suffixLength = longestCommonSuffixLength(removedText, text, max);
1051 var normalized = {
1052 start: start + prefixLength,
1053 end: end - suffixLength,
1054 text: text.slice(prefixLength, text.length - suffixLength)
1055 };
1056 if (normalized.start === normalized.end && normalized.text.length === 0) {
1057 return null;
1058 }
1059 return normalized;
1060}
1061/**
1062 * Return a string diff that is equivalent to applying b after a spanning the range of
1063 * both changes
1064 */
1065function mergeStringDiffs(targetText, a, b) {
1066 var start = Math.min(a.start, b.start);
1067 var overlap = Math.max(0, Math.min(a.start + a.text.length, b.end) - b.start);
1068 var applied = applyStringDiff(targetText, a, b);
1069 var sliceEnd = Math.max(b.start + b.text.length, a.start + a.text.length + (a.start + a.text.length > b.start ? b.text.length : 0) - overlap);
1070 var text = applied.slice(start, sliceEnd);
1071 var end = Math.max(a.end, b.end - a.text.length + (a.end - a.start));
1072 return normalizeStringDiff(targetText, {
1073 start,
1074 end,
1075 text
1076 });
1077}
1078/**
1079 * Get the slate range the text diff spans.
1080 */
1081function targetRange(textDiff) {
1082 var {
1083 path,
1084 diff
1085 } = textDiff;
1086 return {
1087 anchor: {
1088 path,
1089 offset: diff.start
1090 },
1091 focus: {
1092 path,
1093 offset: diff.end
1094 }
1095 };
1096}
1097/**
1098 * Normalize a 'pending point' a.k.a a point based on the dom state before applying
1099 * the pending diffs. Since the pending diffs might have been inserted with different
1100 * marks we have to 'walk' the offset from the starting position to ensure we still
1101 * have a valid point inside the document
1102 */
1103function normalizePoint(editor, point) {
1104 var {
1105 path,
1106 offset
1107 } = point;
1108 if (!Editor.hasPath(editor, path)) {
1109 return null;
1110 }
1111 var leaf = Node.get(editor, path);
1112 if (!Text$1.isText(leaf)) {
1113 return null;
1114 }
1115 var parentBlock = Editor.above(editor, {
1116 match: n => Element$1.isElement(n) && Editor.isBlock(editor, n),
1117 at: path
1118 });
1119 if (!parentBlock) {
1120 return null;
1121 }
1122 while (offset > leaf.text.length) {
1123 var entry = Editor.next(editor, {
1124 at: path,
1125 match: Text$1.isText
1126 });
1127 if (!entry || !Path.isDescendant(entry[1], parentBlock[1])) {
1128 return null;
1129 }
1130 offset -= leaf.text.length;
1131 leaf = entry[0];
1132 path = entry[1];
1133 }
1134 return {
1135 path,
1136 offset
1137 };
1138}
1139/**
1140 * Normalize a 'pending selection' to ensure it's valid in the current document state.
1141 */
1142function normalizeRange(editor, range) {
1143 var anchor = normalizePoint(editor, range.anchor);
1144 if (!anchor) {
1145 return null;
1146 }
1147 if (Range.isCollapsed(range)) {
1148 return {
1149 anchor,
1150 focus: anchor
1151 };
1152 }
1153 var focus = normalizePoint(editor, range.focus);
1154 if (!focus) {
1155 return null;
1156 }
1157 return {
1158 anchor,
1159 focus
1160 };
1161}
1162function transformPendingPoint(editor, point, op) {
1163 var pendingDiffs = EDITOR_TO_PENDING_DIFFS.get(editor);
1164 var textDiff = pendingDiffs === null || pendingDiffs === void 0 ? void 0 : pendingDiffs.find(_ref => {
1165 var {
1166 path
1167 } = _ref;
1168 return Path.equals(path, point.path);
1169 });
1170 if (!textDiff || point.offset <= textDiff.diff.start) {
1171 return Point.transform(point, op, {
1172 affinity: 'backward'
1173 });
1174 }
1175 var {
1176 diff
1177 } = textDiff;
1178 // Point references location inside the diff => transform the point based on the location
1179 // the diff will be applied to and add the offset inside the diff.
1180 if (point.offset <= diff.start + diff.text.length) {
1181 var _anchor = {
1182 path: point.path,
1183 offset: diff.start
1184 };
1185 var _transformed = Point.transform(_anchor, op, {
1186 affinity: 'backward'
1187 });
1188 if (!_transformed) {
1189 return null;
1190 }
1191 return {
1192 path: _transformed.path,
1193 offset: _transformed.offset + point.offset - diff.start
1194 };
1195 }
1196 // Point references location after the diff
1197 var anchor = {
1198 path: point.path,
1199 offset: point.offset - diff.text.length + diff.end - diff.start
1200 };
1201 var transformed = Point.transform(anchor, op, {
1202 affinity: 'backward'
1203 });
1204 if (!transformed) {
1205 return null;
1206 }
1207 if (op.type === 'split_node' && Path.equals(op.path, point.path) && anchor.offset < op.position && diff.start < op.position) {
1208 return transformed;
1209 }
1210 return {
1211 path: transformed.path,
1212 offset: transformed.offset + diff.text.length - diff.end + diff.start
1213 };
1214}
1215function transformPendingRange(editor, range, op) {
1216 var anchor = transformPendingPoint(editor, range.anchor, op);
1217 if (!anchor) {
1218 return null;
1219 }
1220 if (Range.isCollapsed(range)) {
1221 return {
1222 anchor,
1223 focus: anchor
1224 };
1225 }
1226 var focus = transformPendingPoint(editor, range.focus, op);
1227 if (!focus) {
1228 return null;
1229 }
1230 return {
1231 anchor,
1232 focus
1233 };
1234}
1235function transformTextDiff(textDiff, op) {
1236 var {
1237 path,
1238 diff,
1239 id
1240 } = textDiff;
1241 switch (op.type) {
1242 case 'insert_text':
1243 {
1244 if (!Path.equals(op.path, path) || op.offset >= diff.end) {
1245 return textDiff;
1246 }
1247 if (op.offset <= diff.start) {
1248 return {
1249 diff: {
1250 start: op.text.length + diff.start,
1251 end: op.text.length + diff.end,
1252 text: diff.text
1253 },
1254 id,
1255 path
1256 };
1257 }
1258 return {
1259 diff: {
1260 start: diff.start,
1261 end: diff.end + op.text.length,
1262 text: diff.text
1263 },
1264 id,
1265 path
1266 };
1267 }
1268 case 'remove_text':
1269 {
1270 if (!Path.equals(op.path, path) || op.offset >= diff.end) {
1271 return textDiff;
1272 }
1273 if (op.offset + op.text.length <= diff.start) {
1274 return {
1275 diff: {
1276 start: diff.start - op.text.length,
1277 end: diff.end - op.text.length,
1278 text: diff.text
1279 },
1280 id,
1281 path
1282 };
1283 }
1284 return {
1285 diff: {
1286 start: diff.start,
1287 end: diff.end - op.text.length,
1288 text: diff.text
1289 },
1290 id,
1291 path
1292 };
1293 }
1294 case 'split_node':
1295 {
1296 if (!Path.equals(op.path, path) || op.position >= diff.end) {
1297 return {
1298 diff,
1299 id,
1300 path: Path.transform(path, op, {
1301 affinity: 'backward'
1302 })
1303 };
1304 }
1305 if (op.position > diff.start) {
1306 return {
1307 diff: {
1308 start: diff.start,
1309 end: Math.min(op.position, diff.end),
1310 text: diff.text
1311 },
1312 id,
1313 path
1314 };
1315 }
1316 return {
1317 diff: {
1318 start: diff.start - op.position,
1319 end: diff.end - op.position,
1320 text: diff.text
1321 },
1322 id,
1323 path: Path.transform(path, op, {
1324 affinity: 'forward'
1325 })
1326 };
1327 }
1328 case 'merge_node':
1329 {
1330 if (!Path.equals(op.path, path)) {
1331 return {
1332 diff,
1333 id,
1334 path: Path.transform(path, op)
1335 };
1336 }
1337 return {
1338 diff: {
1339 start: diff.start + op.position,
1340 end: diff.end + op.position,
1341 text: diff.text
1342 },
1343 id,
1344 path: Path.transform(path, op)
1345 };
1346 }
1347 }
1348 var newPath = Path.transform(path, op);
1349 if (!newPath) {
1350 return null;
1351 }
1352 return {
1353 diff,
1354 path: newPath,
1355 id
1356 };
1357}
1358
1359function ownKeys$6(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
1360function _objectSpread$6(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$6(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$6(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
1361// https://github.com/facebook/draft-js/blob/main/src/component/handlers/composition/DraftEditorCompositionHandler.js#L41
1362// When using keyboard English association function, conpositionEnd triggered too fast, resulting in after `insertText` still maintain association state.
1363var RESOLVE_DELAY = 25;
1364// Time with no user interaction before the current user action is considered as done.
1365var FLUSH_DELAY = 200;
1366// Replace with `const debug = console.log` to debug
1367var debug = function debug() {};
1368// Type guard to check if a value is a DataTransfer
1369var isDataTransfer = value => (value === null || value === void 0 ? void 0 : value.constructor.name) === 'DataTransfer';
1370function createAndroidInputManager(_ref) {
1371 var {
1372 editor,
1373 scheduleOnDOMSelectionChange,
1374 onDOMSelectionChange
1375 } = _ref;
1376 var flushing = false;
1377 var compositionEndTimeoutId = null;
1378 var flushTimeoutId = null;
1379 var actionTimeoutId = null;
1380 var idCounter = 0;
1381 var insertPositionHint = false;
1382 var applyPendingSelection = () => {
1383 var pendingSelection = EDITOR_TO_PENDING_SELECTION.get(editor);
1384 EDITOR_TO_PENDING_SELECTION.delete(editor);
1385 if (pendingSelection) {
1386 var {
1387 selection
1388 } = editor;
1389 var normalized = normalizeRange(editor, pendingSelection);
1390 if (normalized && (!selection || !Range.equals(normalized, selection))) {
1391 Transforms.select(editor, normalized);
1392 }
1393 }
1394 };
1395 var performAction = () => {
1396 var action = EDITOR_TO_PENDING_ACTION.get(editor);
1397 EDITOR_TO_PENDING_ACTION.delete(editor);
1398 if (!action) {
1399 return;
1400 }
1401 if (action.at) {
1402 var target = Point.isPoint(action.at) ? normalizePoint(editor, action.at) : normalizeRange(editor, action.at);
1403 if (!target) {
1404 return;
1405 }
1406 var _targetRange = Editor.range(editor, target);
1407 if (!editor.selection || !Range.equals(editor.selection, _targetRange)) {
1408 Transforms.select(editor, target);
1409 }
1410 }
1411 action.run();
1412 };
1413 var flush = () => {
1414 if (flushTimeoutId) {
1415 clearTimeout(flushTimeoutId);
1416 flushTimeoutId = null;
1417 }
1418 if (actionTimeoutId) {
1419 clearTimeout(actionTimeoutId);
1420 actionTimeoutId = null;
1421 }
1422 if (!hasPendingDiffs() && !hasPendingAction()) {
1423 applyPendingSelection();
1424 return;
1425 }
1426 if (!flushing) {
1427 flushing = true;
1428 setTimeout(() => flushing = false);
1429 }
1430 if (hasPendingAction()) {
1431 flushing = 'action';
1432 }
1433 var selectionRef = editor.selection && Editor.rangeRef(editor, editor.selection, {
1434 affinity: 'forward'
1435 });
1436 EDITOR_TO_USER_MARKS.set(editor, editor.marks);
1437 debug('flush', EDITOR_TO_PENDING_ACTION.get(editor), EDITOR_TO_PENDING_DIFFS.get(editor));
1438 var scheduleSelectionChange = hasPendingDiffs();
1439 var diff;
1440 while (diff = (_EDITOR_TO_PENDING_DI = EDITOR_TO_PENDING_DIFFS.get(editor)) === null || _EDITOR_TO_PENDING_DI === void 0 ? void 0 : _EDITOR_TO_PENDING_DI[0]) {
1441 var _EDITOR_TO_PENDING_DI, _EDITOR_TO_PENDING_DI2;
1442 var pendingMarks = EDITOR_TO_PENDING_INSERTION_MARKS.get(editor);
1443 if (pendingMarks !== undefined) {
1444 EDITOR_TO_PENDING_INSERTION_MARKS.delete(editor);
1445 editor.marks = pendingMarks;
1446 }
1447 if (pendingMarks && insertPositionHint === false) {
1448 insertPositionHint = null;
1449 }
1450 var range = targetRange(diff);
1451 if (!editor.selection || !Range.equals(editor.selection, range)) {
1452 Transforms.select(editor, range);
1453 }
1454 if (diff.diff.text) {
1455 Editor.insertText(editor, diff.diff.text);
1456 } else {
1457 Editor.deleteFragment(editor);
1458 }
1459 // Remove diff only after we have applied it to account for it when transforming
1460 // pending ranges.
1461 EDITOR_TO_PENDING_DIFFS.set(editor, (_EDITOR_TO_PENDING_DI2 = EDITOR_TO_PENDING_DIFFS.get(editor)) === null || _EDITOR_TO_PENDING_DI2 === void 0 ? void 0 : _EDITOR_TO_PENDING_DI2.filter(_ref2 => {
1462 var {
1463 id
1464 } = _ref2;
1465 return id !== diff.id;
1466 }));
1467 if (!verifyDiffState(editor, diff)) {
1468 scheduleSelectionChange = false;
1469 EDITOR_TO_PENDING_ACTION.delete(editor);
1470 EDITOR_TO_USER_MARKS.delete(editor);
1471 flushing = 'action';
1472 // Ensure we don't restore the pending user (dom) selection
1473 // since the document and dom state do not match.
1474 EDITOR_TO_PENDING_SELECTION.delete(editor);
1475 scheduleOnDOMSelectionChange.cancel();
1476 onDOMSelectionChange.cancel();
1477 selectionRef === null || selectionRef === void 0 || selectionRef.unref();
1478 }
1479 }
1480 var selection = selectionRef === null || selectionRef === void 0 ? void 0 : selectionRef.unref();
1481 if (selection && !EDITOR_TO_PENDING_SELECTION.get(editor) && (!editor.selection || !Range.equals(selection, editor.selection))) {
1482 Transforms.select(editor, selection);
1483 }
1484 if (hasPendingAction()) {
1485 performAction();
1486 return;
1487 }
1488 // COMPAT: The selectionChange event is fired after the action is performed,
1489 // so we have to manually schedule it to ensure we don't 'throw away' the selection
1490 // while rendering if we have pending changes.
1491 if (scheduleSelectionChange) {
1492 scheduleOnDOMSelectionChange();
1493 }
1494 scheduleOnDOMSelectionChange.flush();
1495 onDOMSelectionChange.flush();
1496 applyPendingSelection();
1497 var userMarks = EDITOR_TO_USER_MARKS.get(editor);
1498 EDITOR_TO_USER_MARKS.delete(editor);
1499 if (userMarks !== undefined) {
1500 editor.marks = userMarks;
1501 editor.onChange();
1502 }
1503 };
1504 var handleCompositionEnd = _event => {
1505 if (compositionEndTimeoutId) {
1506 clearTimeout(compositionEndTimeoutId);
1507 }
1508 compositionEndTimeoutId = setTimeout(() => {
1509 IS_COMPOSING.set(editor, false);
1510 flush();
1511 }, RESOLVE_DELAY);
1512 };
1513 var handleCompositionStart = _event => {
1514 IS_COMPOSING.set(editor, true);
1515 if (compositionEndTimeoutId) {
1516 clearTimeout(compositionEndTimeoutId);
1517 compositionEndTimeoutId = null;
1518 }
1519 };
1520 var updatePlaceholderVisibility = function updatePlaceholderVisibility() {
1521 var forceHide = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
1522 var placeholderElement = EDITOR_TO_PLACEHOLDER_ELEMENT.get(editor);
1523 if (!placeholderElement) {
1524 return;
1525 }
1526 if (hasPendingDiffs() || forceHide) {
1527 placeholderElement.style.display = 'none';
1528 return;
1529 }
1530 placeholderElement.style.removeProperty('display');
1531 };
1532 var storeDiff = (path, diff) => {
1533 var _EDITOR_TO_PENDING_DI3;
1534 var pendingDiffs = (_EDITOR_TO_PENDING_DI3 = EDITOR_TO_PENDING_DIFFS.get(editor)) !== null && _EDITOR_TO_PENDING_DI3 !== void 0 ? _EDITOR_TO_PENDING_DI3 : [];
1535 EDITOR_TO_PENDING_DIFFS.set(editor, pendingDiffs);
1536 var target = Node.leaf(editor, path);
1537 var idx = pendingDiffs.findIndex(change => Path.equals(change.path, path));
1538 if (idx < 0) {
1539 var normalized = normalizeStringDiff(target.text, diff);
1540 if (normalized) {
1541 pendingDiffs.push({
1542 path,
1543 diff,
1544 id: idCounter++
1545 });
1546 }
1547 updatePlaceholderVisibility();
1548 return;
1549 }
1550 var merged = mergeStringDiffs(target.text, pendingDiffs[idx].diff, diff);
1551 if (!merged) {
1552 pendingDiffs.splice(idx, 1);
1553 updatePlaceholderVisibility();
1554 return;
1555 }
1556 pendingDiffs[idx] = _objectSpread$6(_objectSpread$6({}, pendingDiffs[idx]), {}, {
1557 diff: merged
1558 });
1559 };
1560 var scheduleAction = function scheduleAction(run) {
1561 var {
1562 at
1563 } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
1564 insertPositionHint = false;
1565 EDITOR_TO_PENDING_SELECTION.delete(editor);
1566 scheduleOnDOMSelectionChange.cancel();
1567 onDOMSelectionChange.cancel();
1568 if (hasPendingAction()) {
1569 flush();
1570 }
1571 EDITOR_TO_PENDING_ACTION.set(editor, {
1572 at,
1573 run
1574 });
1575 // COMPAT: When deleting before a non-contenteditable element chrome only fires a beforeinput,
1576 // (no input) and doesn't perform any dom mutations. Without a flush timeout we would never flush
1577 // in this case and thus never actually perform the action.
1578 actionTimeoutId = setTimeout(flush);
1579 };
1580 var handleDOMBeforeInput = event => {
1581 var _targetRange2;
1582 if (flushTimeoutId) {
1583 clearTimeout(flushTimeoutId);
1584 flushTimeoutId = null;
1585 }
1586 var {
1587 inputType: type
1588 } = event;
1589 var targetRange = null;
1590 var data = event.dataTransfer || event.data || undefined;
1591 if (insertPositionHint !== false && type !== 'insertText' && type !== 'insertCompositionText') {
1592 insertPositionHint = false;
1593 }
1594 var [nativeTargetRange] = event.getTargetRanges();
1595 if (nativeTargetRange) {
1596 targetRange = ReactEditor.toSlateRange(editor, nativeTargetRange, {
1597 exactMatch: false,
1598 suppressThrow: true
1599 });
1600 }
1601 // COMPAT: SelectionChange event is fired after the action is performed, so we
1602 // have to manually get the selection here to ensure it's up-to-date.
1603 var window = ReactEditor.getWindow(editor);
1604 var domSelection = window.getSelection();
1605 if (!targetRange && domSelection) {
1606 nativeTargetRange = domSelection;
1607 targetRange = ReactEditor.toSlateRange(editor, domSelection, {
1608 exactMatch: false,
1609 suppressThrow: true
1610 });
1611 }
1612 targetRange = (_targetRange2 = targetRange) !== null && _targetRange2 !== void 0 ? _targetRange2 : editor.selection;
1613 if (!targetRange) {
1614 return;
1615 }
1616 // By default, the input manager tries to store text diffs so that we can
1617 // defer flushing them at a later point in time. We don't want to flush
1618 // for every input event as this can be expensive. However, there are some
1619 // scenarios where we cannot safely store the text diff and must instead
1620 // schedule an action to let Slate normalize the editor state.
1621 var canStoreDiff = true;
1622 if (type.startsWith('delete')) {
1623 if (Range.isExpanded(targetRange)) {
1624 var [_start, _end] = Range.edges(targetRange);
1625 var _leaf = Node.leaf(editor, _start.path);
1626 if (_leaf.text.length === _start.offset && _end.offset === 0) {
1627 var next = Editor.next(editor, {
1628 at: _start.path,
1629 match: Text$1.isText
1630 });
1631 if (next && Path.equals(next[1], _end.path)) {
1632 targetRange = {
1633 anchor: _end,
1634 focus: _end
1635 };
1636 }
1637 }
1638 }
1639 var direction = type.endsWith('Backward') ? 'backward' : 'forward';
1640 var [start, end] = Range.edges(targetRange);
1641 var [leaf, path] = Editor.leaf(editor, start.path);
1642 var diff = {
1643 text: '',
1644 start: start.offset,
1645 end: end.offset
1646 };
1647 var pendingDiffs = EDITOR_TO_PENDING_DIFFS.get(editor);
1648 var relevantPendingDiffs = pendingDiffs === null || pendingDiffs === void 0 ? void 0 : pendingDiffs.find(change => Path.equals(change.path, path));
1649 var diffs = relevantPendingDiffs ? [relevantPendingDiffs.diff, diff] : [diff];
1650 var text = applyStringDiff(leaf.text, ...diffs);
1651 if (text.length === 0) {
1652 // Text leaf will be removed, so we need to schedule an
1653 // action to remove it so that Slate can normalize instead
1654 // of storing as a diff
1655 canStoreDiff = false;
1656 }
1657 if (Range.isExpanded(targetRange)) {
1658 if (canStoreDiff && Path.equals(targetRange.anchor.path, targetRange.focus.path)) {
1659 var point = {
1660 path: targetRange.anchor.path,
1661 offset: start.offset
1662 };
1663 var range = Editor.range(editor, point, point);
1664 handleUserSelect(range);
1665 return storeDiff(targetRange.anchor.path, {
1666 text: '',
1667 end: end.offset,
1668 start: start.offset
1669 });
1670 }
1671 return scheduleAction(() => Editor.deleteFragment(editor, {
1672 direction
1673 }), {
1674 at: targetRange
1675 });
1676 }
1677 }
1678 switch (type) {
1679 case 'deleteByComposition':
1680 case 'deleteByCut':
1681 case 'deleteByDrag':
1682 {
1683 return scheduleAction(() => Editor.deleteFragment(editor), {
1684 at: targetRange
1685 });
1686 }
1687 case 'deleteContent':
1688 case 'deleteContentForward':
1689 {
1690 var {
1691 anchor
1692 } = targetRange;
1693 if (canStoreDiff && Range.isCollapsed(targetRange)) {
1694 var targetNode = Node.leaf(editor, anchor.path);
1695 if (anchor.offset < targetNode.text.length) {
1696 return storeDiff(anchor.path, {
1697 text: '',
1698 start: anchor.offset,
1699 end: anchor.offset + 1
1700 });
1701 }
1702 }
1703 return scheduleAction(() => Editor.deleteForward(editor), {
1704 at: targetRange
1705 });
1706 }
1707 case 'deleteContentBackward':
1708 {
1709 var _nativeTargetRange;
1710 var {
1711 anchor: _anchor
1712 } = targetRange;
1713 // If we have a mismatch between the native and slate selection being collapsed
1714 // we are most likely deleting a zero-width placeholder and thus should perform it
1715 // as an action to ensure correct behavior (mostly happens with mark placeholders)
1716 var nativeCollapsed = isDOMSelection(nativeTargetRange) ? nativeTargetRange.isCollapsed : !!((_nativeTargetRange = nativeTargetRange) !== null && _nativeTargetRange !== void 0 && _nativeTargetRange.collapsed);
1717 if (canStoreDiff && nativeCollapsed && Range.isCollapsed(targetRange) && _anchor.offset > 0) {
1718 return storeDiff(_anchor.path, {
1719 text: '',
1720 start: _anchor.offset - 1,
1721 end: _anchor.offset
1722 });
1723 }
1724 return scheduleAction(() => Editor.deleteBackward(editor), {
1725 at: targetRange
1726 });
1727 }
1728 case 'deleteEntireSoftLine':
1729 {
1730 return scheduleAction(() => {
1731 Editor.deleteBackward(editor, {
1732 unit: 'line'
1733 });
1734 Editor.deleteForward(editor, {
1735 unit: 'line'
1736 });
1737 }, {
1738 at: targetRange
1739 });
1740 }
1741 case 'deleteHardLineBackward':
1742 {
1743 return scheduleAction(() => Editor.deleteBackward(editor, {
1744 unit: 'block'
1745 }), {
1746 at: targetRange
1747 });
1748 }
1749 case 'deleteSoftLineBackward':
1750 {
1751 return scheduleAction(() => Editor.deleteBackward(editor, {
1752 unit: 'line'
1753 }), {
1754 at: targetRange
1755 });
1756 }
1757 case 'deleteHardLineForward':
1758 {
1759 return scheduleAction(() => Editor.deleteForward(editor, {
1760 unit: 'block'
1761 }), {
1762 at: targetRange
1763 });
1764 }
1765 case 'deleteSoftLineForward':
1766 {
1767 return scheduleAction(() => Editor.deleteForward(editor, {
1768 unit: 'line'
1769 }), {
1770 at: targetRange
1771 });
1772 }
1773 case 'deleteWordBackward':
1774 {
1775 return scheduleAction(() => Editor.deleteBackward(editor, {
1776 unit: 'word'
1777 }), {
1778 at: targetRange
1779 });
1780 }
1781 case 'deleteWordForward':
1782 {
1783 return scheduleAction(() => Editor.deleteForward(editor, {
1784 unit: 'word'
1785 }), {
1786 at: targetRange
1787 });
1788 }
1789 case 'insertLineBreak':
1790 {
1791 return scheduleAction(() => Editor.insertSoftBreak(editor), {
1792 at: targetRange
1793 });
1794 }
1795 case 'insertParagraph':
1796 {
1797 return scheduleAction(() => Editor.insertBreak(editor), {
1798 at: targetRange
1799 });
1800 }
1801 case 'insertCompositionText':
1802 case 'deleteCompositionText':
1803 case 'insertFromComposition':
1804 case 'insertFromDrop':
1805 case 'insertFromPaste':
1806 case 'insertFromYank':
1807 case 'insertReplacementText':
1808 case 'insertText':
1809 {
1810 if (isDataTransfer(data)) {
1811 return scheduleAction(() => ReactEditor.insertData(editor, data), {
1812 at: targetRange
1813 });
1814 }
1815 var _text = data !== null && data !== void 0 ? data : '';
1816 // COMPAT: If we are writing inside a placeholder, the ime inserts the text inside
1817 // the placeholder itself and thus includes the zero-width space inside edit events.
1818 if (EDITOR_TO_PENDING_INSERTION_MARKS.get(editor)) {
1819 _text = _text.replace('\uFEFF', '');
1820 }
1821 // Pastes from the Android clipboard will generate `insertText` events.
1822 // If the copied text contains any newlines, Android will append an
1823 // extra newline to the end of the copied text.
1824 if (type === 'insertText' && /.*\n.*\n$/.test(_text)) {
1825 _text = _text.slice(0, -1);
1826 }
1827 // If the text includes a newline, split it at newlines and paste each component
1828 // string, with soft breaks in between each.
1829 if (_text.includes('\n')) {
1830 return scheduleAction(() => {
1831 var parts = _text.split('\n');
1832 parts.forEach((line, i) => {
1833 if (line) {
1834 Editor.insertText(editor, line);
1835 }
1836 if (i !== parts.length - 1) {
1837 Editor.insertSoftBreak(editor);
1838 }
1839 });
1840 }, {
1841 at: targetRange
1842 });
1843 }
1844 if (Path.equals(targetRange.anchor.path, targetRange.focus.path)) {
1845 var [_start2, _end2] = Range.edges(targetRange);
1846 var _diff = {
1847 start: _start2.offset,
1848 end: _end2.offset,
1849 text: _text
1850 };
1851 // COMPAT: Swiftkey has a weird bug where the target range of the 2nd word
1852 // inserted after a mark placeholder is inserted with an anchor offset off by 1.
1853 // So writing 'some text' will result in 'some ttext'. Luckily all 'normal' insert
1854 // text events are fired with the correct target ranges, only the final 'insertComposition'
1855 // isn't, so we can adjust the target range start offset if we are confident this is the
1856 // swiftkey insert causing the issue.
1857 if (_text && insertPositionHint && type === 'insertCompositionText') {
1858 var hintPosition = insertPositionHint.start + insertPositionHint.text.search(/\S|$/);
1859 var diffPosition = _diff.start + _diff.text.search(/\S|$/);
1860 if (diffPosition === hintPosition + 1 && _diff.end === insertPositionHint.start + insertPositionHint.text.length) {
1861 _diff.start -= 1;
1862 insertPositionHint = null;
1863 scheduleFlush();
1864 } else {
1865 insertPositionHint = false;
1866 }
1867 } else if (type === 'insertText') {
1868 if (insertPositionHint === null) {
1869 insertPositionHint = _diff;
1870 } else if (insertPositionHint && Range.isCollapsed(targetRange) && insertPositionHint.end + insertPositionHint.text.length === _start2.offset) {
1871 insertPositionHint = _objectSpread$6(_objectSpread$6({}, insertPositionHint), {}, {
1872 text: insertPositionHint.text + _text
1873 });
1874 } else {
1875 insertPositionHint = false;
1876 }
1877 } else {
1878 insertPositionHint = false;
1879 }
1880 if (canStoreDiff) {
1881 storeDiff(_start2.path, _diff);
1882 return;
1883 }
1884 }
1885 return scheduleAction(() => Editor.insertText(editor, _text), {
1886 at: targetRange
1887 });
1888 }
1889 }
1890 };
1891 var hasPendingAction = () => {
1892 return !!EDITOR_TO_PENDING_ACTION.get(editor);
1893 };
1894 var hasPendingDiffs = () => {
1895 var _EDITOR_TO_PENDING_DI4;
1896 return !!((_EDITOR_TO_PENDING_DI4 = EDITOR_TO_PENDING_DIFFS.get(editor)) !== null && _EDITOR_TO_PENDING_DI4 !== void 0 && _EDITOR_TO_PENDING_DI4.length);
1897 };
1898 var hasPendingChanges = () => {
1899 return hasPendingAction() || hasPendingDiffs();
1900 };
1901 var isFlushing = () => {
1902 return flushing;
1903 };
1904 var handleUserSelect = range => {
1905 EDITOR_TO_PENDING_SELECTION.set(editor, range);
1906 if (flushTimeoutId) {
1907 clearTimeout(flushTimeoutId);
1908 flushTimeoutId = null;
1909 }
1910 var {
1911 selection
1912 } = editor;
1913 if (!range) {
1914 return;
1915 }
1916 var pathChanged = !selection || !Path.equals(selection.anchor.path, range.anchor.path);
1917 var parentPathChanged = !selection || !Path.equals(selection.anchor.path.slice(0, -1), range.anchor.path.slice(0, -1));
1918 if (pathChanged && insertPositionHint || parentPathChanged) {
1919 insertPositionHint = false;
1920 }
1921 if (pathChanged || hasPendingDiffs()) {
1922 flushTimeoutId = setTimeout(flush, FLUSH_DELAY);
1923 }
1924 };
1925 var handleInput = () => {
1926 if (hasPendingAction() || !hasPendingDiffs()) {
1927 flush();
1928 }
1929 };
1930 var handleKeyDown = _ => {
1931 // COMPAT: Swiftkey closes the keyboard when typing inside a empty node
1932 // directly next to a non-contenteditable element (= the placeholder).
1933 // The only event fired soon enough for us to allow hiding the placeholder
1934 // without swiftkey picking it up is the keydown event, so we have to hide it
1935 // here. See https://github.com/ianstormtaylor/slate/pull/4988#issuecomment-1201050535
1936 if (!hasPendingDiffs()) {
1937 updatePlaceholderVisibility(true);
1938 setTimeout(updatePlaceholderVisibility);
1939 }
1940 };
1941 var scheduleFlush = () => {
1942 if (!hasPendingAction()) {
1943 actionTimeoutId = setTimeout(flush);
1944 }
1945 };
1946 var handleDomMutations = mutations => {
1947 if (hasPendingDiffs() || hasPendingAction()) {
1948 return;
1949 }
1950 if (mutations.some(mutation => isTrackedMutation(editor, mutation, mutations))) {
1951 var _EDITOR_TO_FORCE_REND;
1952 // Cause a re-render to restore the dom state if we encounter tracked mutations without
1953 // a corresponding pending action.
1954 (_EDITOR_TO_FORCE_REND = EDITOR_TO_FORCE_RENDER.get(editor)) === null || _EDITOR_TO_FORCE_REND === void 0 || _EDITOR_TO_FORCE_REND();
1955 }
1956 };
1957 return {
1958 flush,
1959 scheduleFlush,
1960 hasPendingDiffs,
1961 hasPendingAction,
1962 hasPendingChanges,
1963 isFlushing,
1964 handleUserSelect,
1965 handleCompositionEnd,
1966 handleCompositionStart,
1967 handleDOMBeforeInput,
1968 handleKeyDown,
1969 handleDomMutations,
1970 handleInput
1971 };
1972}
1973
1974function useIsMounted() {
1975 var isMountedRef = useRef(false);
1976 useEffect(() => {
1977 isMountedRef.current = true;
1978 return () => {
1979 isMountedRef.current = false;
1980 };
1981 }, []);
1982 return isMountedRef.current;
1983}
1984
1985/**
1986 * Prevent warning on SSR by falling back to useEffect when DOM isn't available
1987 */
1988var useIsomorphicLayoutEffect = CAN_USE_DOM ? useLayoutEffect : useEffect;
1989
1990function useMutationObserver(node, callback, options) {
1991 var [mutationObserver] = useState(() => new MutationObserver(callback));
1992 useIsomorphicLayoutEffect(() => {
1993 // Discard mutations caused during render phase. This works due to react calling
1994 // useLayoutEffect synchronously after the render phase before the next tick.
1995 mutationObserver.takeRecords();
1996 });
1997 useEffect(() => {
1998 if (!node.current) {
1999 throw new Error('Failed to attach MutationObserver, `node` is undefined');
2000 }
2001 mutationObserver.observe(node.current, options);
2002 return () => mutationObserver.disconnect();
2003 }, [mutationObserver, node, options]);
2004}
2005
2006var _excluded$3 = ["node"];
2007function ownKeys$5(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
2008function _objectSpread$5(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$5(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$5(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
2009var MUTATION_OBSERVER_CONFIG$1 = {
2010 subtree: true,
2011 childList: true,
2012 characterData: true
2013};
2014var useAndroidInputManager = !IS_ANDROID ? () => null : _ref => {
2015 var {
2016 node
2017 } = _ref,
2018 options = _objectWithoutProperties(_ref, _excluded$3);
2019 if (!IS_ANDROID) {
2020 return null;
2021 }
2022 var editor = useSlateStatic();
2023 var isMounted = useIsMounted();
2024 var [inputManager] = useState(() => createAndroidInputManager(_objectSpread$5({
2025 editor
2026 }, options)));
2027 useMutationObserver(node, inputManager.handleDomMutations, MUTATION_OBSERVER_CONFIG$1);
2028 EDITOR_TO_SCHEDULE_FLUSH.set(editor, inputManager.scheduleFlush);
2029 if (isMounted) {
2030 inputManager.flush();
2031 }
2032 return inputManager;
2033};
2034
2035var _excluded$2 = ["anchor", "focus"],
2036 _excluded2$1 = ["anchor", "focus"];
2037var shallowCompare = (obj1, obj2) => Object.keys(obj1).length === Object.keys(obj2).length && Object.keys(obj1).every(key => obj2.hasOwnProperty(key) && obj1[key] === obj2[key]);
2038var isDecorationFlagsEqual = (range, other) => {
2039 var rangeOwnProps = _objectWithoutProperties(range, _excluded$2);
2040 var otherOwnProps = _objectWithoutProperties(other, _excluded2$1);
2041 return range[PLACEHOLDER_SYMBOL] === other[PLACEHOLDER_SYMBOL] && shallowCompare(rangeOwnProps, otherOwnProps);
2042};
2043/**
2044 * Check if a list of decorator ranges are equal to another.
2045 *
2046 * PERF: this requires the two lists to also have the ranges inside them in the
2047 * same order, but this is an okay constraint for us since decorations are
2048 * kept in order, and the odd case where they aren't is okay to re-render for.
2049 */
2050var isElementDecorationsEqual = (list, another) => {
2051 if (list.length !== another.length) {
2052 return false;
2053 }
2054 for (var i = 0; i < list.length; i++) {
2055 var range = list[i];
2056 var other = another[i];
2057 if (!Range.equals(range, other) || !isDecorationFlagsEqual(range, other)) {
2058 return false;
2059 }
2060 }
2061 return true;
2062};
2063/**
2064 * Check if a list of decorator ranges are equal to another.
2065 *
2066 * PERF: this requires the two lists to also have the ranges inside them in the
2067 * same order, but this is an okay constraint for us since decorations are
2068 * kept in order, and the odd case where they aren't is okay to re-render for.
2069 */
2070var isTextDecorationsEqual = (list, another) => {
2071 if (list.length !== another.length) {
2072 return false;
2073 }
2074 for (var i = 0; i < list.length; i++) {
2075 var range = list[i];
2076 var other = another[i];
2077 // compare only offsets because paths doesn't matter for text
2078 if (range.anchor.offset !== other.anchor.offset || range.focus.offset !== other.focus.offset || !isDecorationFlagsEqual(range, other)) {
2079 return false;
2080 }
2081 }
2082 return true;
2083};
2084
2085function ownKeys$4(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
2086function _objectSpread$4(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$4(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$4(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
2087/**
2088 * Leaf content strings.
2089 */
2090var String$1 = props => {
2091 var {
2092 isLast,
2093 leaf,
2094 parent,
2095 text
2096 } = props;
2097 var editor = useSlateStatic();
2098 var path = ReactEditor.findPath(editor, text);
2099 var parentPath = Path.parent(path);
2100 var isMarkPlaceholder = Boolean(leaf[MARK_PLACEHOLDER_SYMBOL]);
2101 // COMPAT: Render text inside void nodes with a zero-width space.
2102 // So the node can contain selection but the text is not visible.
2103 if (editor.isVoid(parent)) {
2104 return /*#__PURE__*/React.createElement(ZeroWidthString, {
2105 length: Node.string(parent).length
2106 });
2107 }
2108 // COMPAT: If this is the last text node in an empty block, render a zero-
2109 // width space that will convert into a line break when copying and pasting
2110 // to support expected plain text.
2111 if (leaf.text === '' && parent.children[parent.children.length - 1] === text && !editor.isInline(parent) && Editor.string(editor, parentPath) === '') {
2112 return /*#__PURE__*/React.createElement(ZeroWidthString, {
2113 isLineBreak: true,
2114 isMarkPlaceholder: isMarkPlaceholder
2115 });
2116 }
2117 // COMPAT: If the text is empty, it's because it's on the edge of an inline
2118 // node, so we render a zero-width space so that the selection can be
2119 // inserted next to it still.
2120 if (leaf.text === '') {
2121 return /*#__PURE__*/React.createElement(ZeroWidthString, {
2122 isMarkPlaceholder: isMarkPlaceholder
2123 });
2124 }
2125 // COMPAT: Browsers will collapse trailing new lines at the end of blocks,
2126 // so we need to add an extra trailing new lines to prevent that.
2127 if (isLast && leaf.text.slice(-1) === '\n') {
2128 return /*#__PURE__*/React.createElement(TextString, {
2129 isTrailing: true,
2130 text: leaf.text
2131 });
2132 }
2133 return /*#__PURE__*/React.createElement(TextString, {
2134 text: leaf.text
2135 });
2136};
2137/**
2138 * Leaf strings with text in them.
2139 */
2140var TextString = props => {
2141 var {
2142 text,
2143 isTrailing = false
2144 } = props;
2145 var ref = useRef(null);
2146 var getTextContent = () => {
2147 return "".concat(text !== null && text !== void 0 ? text : '').concat(isTrailing ? '\n' : '');
2148 };
2149 var [initialText] = useState(getTextContent);
2150 // This is the actual text rendering boundary where we interface with the DOM
2151 // The text is not rendered as part of the virtual DOM, as since we handle basic character insertions natively,
2152 // updating the DOM is not a one way dataflow anymore. What we need here is not reconciliation and diffing
2153 // with previous version of the virtual DOM, but rather diffing with the actual DOM element, and replace the DOM <span> content
2154 // exactly if and only if its current content does not match our current virtual DOM.
2155 // Otherwise the DOM TextNode would always be replaced by React as the user types, which interferes with native text features,
2156 // eg makes native spellcheck opt out from checking the text node.
2157 // useLayoutEffect: updating our span before browser paint
2158 useIsomorphicLayoutEffect(() => {
2159 // null coalescing text to make sure we're not outputing "null" as a string in the extreme case it is nullish at runtime
2160 var textWithTrailing = getTextContent();
2161 if (ref.current && ref.current.textContent !== textWithTrailing) {
2162 ref.current.textContent = textWithTrailing;
2163 }
2164 // intentionally not specifying dependencies, so that this effect runs on every render
2165 // as this effectively replaces "specifying the text in the virtual DOM under the <span> below" on each render
2166 });
2167 // We intentionally render a memoized <span> that only receives the initial text content when the component is mounted.
2168 // We defer to the layout effect above to update the `textContent` of the span element when needed.
2169 return /*#__PURE__*/React.createElement(MemoizedText$1, {
2170 ref: ref
2171 }, initialText);
2172};
2173var MemoizedText$1 = /*#__PURE__*/memo( /*#__PURE__*/forwardRef((props, ref) => {
2174 return /*#__PURE__*/React.createElement("span", {
2175 "data-slate-string": true,
2176 ref: ref
2177 }, props.children);
2178}));
2179/**
2180 * Leaf strings without text, render as zero-width strings.
2181 */
2182var ZeroWidthString = props => {
2183 var {
2184 length = 0,
2185 isLineBreak = false,
2186 isMarkPlaceholder = false
2187 } = props;
2188 var attributes = {
2189 'data-slate-zero-width': isLineBreak ? 'n' : 'z',
2190 'data-slate-length': length
2191 };
2192 if (isMarkPlaceholder) {
2193 attributes['data-slate-mark-placeholder'] = true;
2194 }
2195 return /*#__PURE__*/React.createElement("span", _objectSpread$4({}, attributes), !IS_ANDROID || !isLineBreak ? '\uFEFF' : null, isLineBreak ? /*#__PURE__*/React.createElement("br", null) : null);
2196};
2197
2198function ownKeys$3(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
2199function _objectSpread$3(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$3(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$3(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
2200// Delay the placeholder on Android to prevent the keyboard from closing.
2201// (https://github.com/ianstormtaylor/slate/pull/5368)
2202var PLACEHOLDER_DELAY = IS_ANDROID ? 300 : 0;
2203function disconnectPlaceholderResizeObserver(placeholderResizeObserver, releaseObserver) {
2204 if (placeholderResizeObserver.current) {
2205 placeholderResizeObserver.current.disconnect();
2206 if (releaseObserver) {
2207 placeholderResizeObserver.current = null;
2208 }
2209 }
2210}
2211function clearTimeoutRef(timeoutRef) {
2212 if (timeoutRef.current) {
2213 clearTimeout(timeoutRef.current);
2214 timeoutRef.current = null;
2215 }
2216}
2217/**
2218 * Individual leaves in a text node with unique formatting.
2219 */
2220var Leaf = props => {
2221 var {
2222 leaf,
2223 isLast,
2224 text,
2225 parent,
2226 renderPlaceholder,
2227 renderLeaf = props => /*#__PURE__*/React.createElement(DefaultLeaf, _objectSpread$3({}, props))
2228 } = props;
2229 var editor = useSlateStatic();
2230 var placeholderResizeObserver = useRef(null);
2231 var placeholderRef = useRef(null);
2232 var [showPlaceholder, setShowPlaceholder] = useState(false);
2233 var showPlaceholderTimeoutRef = useRef(null);
2234 var callbackPlaceholderRef = useCallback(placeholderEl => {
2235 disconnectPlaceholderResizeObserver(placeholderResizeObserver, placeholderEl == null);
2236 if (placeholderEl == null) {
2237 var _leaf$onPlaceholderRe;
2238 EDITOR_TO_PLACEHOLDER_ELEMENT.delete(editor);
2239 (_leaf$onPlaceholderRe = leaf.onPlaceholderResize) === null || _leaf$onPlaceholderRe === void 0 || _leaf$onPlaceholderRe.call(leaf, null);
2240 } else {
2241 EDITOR_TO_PLACEHOLDER_ELEMENT.set(editor, placeholderEl);
2242 if (!placeholderResizeObserver.current) {
2243 // Create a new observer and observe the placeholder element.
2244 var ResizeObserver$1 = window.ResizeObserver || ResizeObserver;
2245 placeholderResizeObserver.current = new ResizeObserver$1(() => {
2246 var _leaf$onPlaceholderRe2;
2247 (_leaf$onPlaceholderRe2 = leaf.onPlaceholderResize) === null || _leaf$onPlaceholderRe2 === void 0 || _leaf$onPlaceholderRe2.call(leaf, placeholderEl);
2248 });
2249 }
2250 placeholderResizeObserver.current.observe(placeholderEl);
2251 placeholderRef.current = placeholderEl;
2252 }
2253 }, [placeholderRef, leaf, editor]);
2254 var children = /*#__PURE__*/React.createElement(String$1, {
2255 isLast: isLast,
2256 leaf: leaf,
2257 parent: parent,
2258 text: text
2259 });
2260 var leafIsPlaceholder = Boolean(leaf[PLACEHOLDER_SYMBOL]);
2261 useEffect(() => {
2262 if (leafIsPlaceholder) {
2263 if (!showPlaceholderTimeoutRef.current) {
2264 // Delay the placeholder, so it will not render in a selection
2265 showPlaceholderTimeoutRef.current = setTimeout(() => {
2266 setShowPlaceholder(true);
2267 showPlaceholderTimeoutRef.current = null;
2268 }, PLACEHOLDER_DELAY);
2269 }
2270 } else {
2271 clearTimeoutRef(showPlaceholderTimeoutRef);
2272 setShowPlaceholder(false);
2273 }
2274 return () => clearTimeoutRef(showPlaceholderTimeoutRef);
2275 }, [leafIsPlaceholder, setShowPlaceholder]);
2276 if (leafIsPlaceholder && showPlaceholder) {
2277 var placeholderProps = {
2278 children: leaf.placeholder,
2279 attributes: {
2280 'data-slate-placeholder': true,
2281 style: {
2282 position: 'absolute',
2283 top: 0,
2284 pointerEvents: 'none',
2285 width: '100%',
2286 maxWidth: '100%',
2287 display: 'block',
2288 opacity: '0.333',
2289 userSelect: 'none',
2290 textDecoration: 'none',
2291 // Fixes https://github.com/udecode/plate/issues/2315
2292 WebkitUserModify: IS_WEBKIT ? 'inherit' : undefined
2293 },
2294 contentEditable: false,
2295 ref: callbackPlaceholderRef
2296 }
2297 };
2298 children = /*#__PURE__*/React.createElement(React.Fragment, null, renderPlaceholder(placeholderProps), children);
2299 }
2300 // COMPAT: Having the `data-` attributes on these leaf elements ensures that
2301 // in certain misbehaving browsers they aren't weirdly cloned/destroyed by
2302 // contenteditable behaviors. (2019/05/08)
2303 var attributes = {
2304 'data-slate-leaf': true
2305 };
2306 return renderLeaf({
2307 attributes,
2308 children,
2309 leaf,
2310 text
2311 });
2312};
2313var MemoizedLeaf = /*#__PURE__*/React.memo(Leaf, (prev, next) => {
2314 return next.parent === prev.parent && next.isLast === prev.isLast && next.renderLeaf === prev.renderLeaf && next.renderPlaceholder === prev.renderPlaceholder && next.text === prev.text && Text$1.equals(next.leaf, prev.leaf) && next.leaf[PLACEHOLDER_SYMBOL] === prev.leaf[PLACEHOLDER_SYMBOL];
2315});
2316var DefaultLeaf = props => {
2317 var {
2318 attributes,
2319 children
2320 } = props;
2321 return /*#__PURE__*/React.createElement("span", _objectSpread$3({}, attributes), children);
2322};
2323
2324/**
2325 * Text.
2326 */
2327var Text = props => {
2328 var {
2329 decorations,
2330 isLast,
2331 parent,
2332 renderPlaceholder,
2333 renderLeaf,
2334 text
2335 } = props;
2336 var editor = useSlateStatic();
2337 var ref = useRef(null);
2338 var leaves = Text$1.decorations(text, decorations);
2339 var key = ReactEditor.findKey(editor, text);
2340 var children = [];
2341 for (var i = 0; i < leaves.length; i++) {
2342 var leaf = leaves[i];
2343 children.push( /*#__PURE__*/React.createElement(MemoizedLeaf, {
2344 isLast: isLast && i === leaves.length - 1,
2345 key: "".concat(key.id, "-").concat(i),
2346 renderPlaceholder: renderPlaceholder,
2347 leaf: leaf,
2348 text: text,
2349 parent: parent,
2350 renderLeaf: renderLeaf
2351 }));
2352 }
2353 // Update element-related weak maps with the DOM element ref.
2354 var callbackRef = useCallback(span => {
2355 var KEY_TO_ELEMENT = EDITOR_TO_KEY_TO_ELEMENT.get(editor);
2356 if (span) {
2357 KEY_TO_ELEMENT === null || KEY_TO_ELEMENT === void 0 || KEY_TO_ELEMENT.set(key, span);
2358 NODE_TO_ELEMENT.set(text, span);
2359 ELEMENT_TO_NODE.set(span, text);
2360 } else {
2361 KEY_TO_ELEMENT === null || KEY_TO_ELEMENT === void 0 || KEY_TO_ELEMENT.delete(key);
2362 NODE_TO_ELEMENT.delete(text);
2363 if (ref.current) {
2364 ELEMENT_TO_NODE.delete(ref.current);
2365 }
2366 }
2367 ref.current = span;
2368 }, [ref, editor, key, text]);
2369 return /*#__PURE__*/React.createElement("span", {
2370 "data-slate-node": "text",
2371 ref: callbackRef
2372 }, children);
2373};
2374var MemoizedText = /*#__PURE__*/React.memo(Text, (prev, next) => {
2375 return next.parent === prev.parent && next.isLast === prev.isLast && next.renderLeaf === prev.renderLeaf && next.renderPlaceholder === prev.renderPlaceholder && next.text === prev.text && isTextDecorationsEqual(next.decorations, prev.decorations);
2376});
2377
2378function ownKeys$2(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
2379function _objectSpread$2(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$2(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$2(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
2380/**
2381 * Element.
2382 */
2383var Element = props => {
2384 var {
2385 decorations,
2386 element,
2387 renderElement = p => /*#__PURE__*/React.createElement(DefaultElement, _objectSpread$2({}, p)),
2388 renderPlaceholder,
2389 renderLeaf,
2390 selection
2391 } = props;
2392 var editor = useSlateStatic();
2393 var readOnly = useReadOnly();
2394 var isInline = editor.isInline(element);
2395 var key = ReactEditor.findKey(editor, element);
2396 var ref = useCallback(ref => {
2397 // Update element-related weak maps with the DOM element ref.
2398 var KEY_TO_ELEMENT = EDITOR_TO_KEY_TO_ELEMENT.get(editor);
2399 if (ref) {
2400 KEY_TO_ELEMENT === null || KEY_TO_ELEMENT === void 0 || KEY_TO_ELEMENT.set(key, ref);
2401 NODE_TO_ELEMENT.set(element, ref);
2402 ELEMENT_TO_NODE.set(ref, element);
2403 } else {
2404 KEY_TO_ELEMENT === null || KEY_TO_ELEMENT === void 0 || KEY_TO_ELEMENT.delete(key);
2405 NODE_TO_ELEMENT.delete(element);
2406 }
2407 }, [editor, key, element]);
2408 var children = useChildren({
2409 decorations,
2410 node: element,
2411 renderElement,
2412 renderPlaceholder,
2413 renderLeaf,
2414 selection
2415 });
2416 // Attributes that the developer must mix into the element in their
2417 // custom node renderer component.
2418 var attributes = {
2419 'data-slate-node': 'element',
2420 ref
2421 };
2422 if (isInline) {
2423 attributes['data-slate-inline'] = true;
2424 }
2425 // If it's a block node with inline children, add the proper `dir` attribute
2426 // for text direction.
2427 if (!isInline && Editor.hasInlines(editor, element)) {
2428 var text = Node.string(element);
2429 var dir = getDirection(text);
2430 if (dir === 'rtl') {
2431 attributes.dir = dir;
2432 }
2433 }
2434 // If it's a void node, wrap the children in extra void-specific elements.
2435 if (Editor.isVoid(editor, element)) {
2436 attributes['data-slate-void'] = true;
2437 if (!readOnly && isInline) {
2438 attributes.contentEditable = false;
2439 }
2440 var Tag = isInline ? 'span' : 'div';
2441 var [[_text]] = Node.texts(element);
2442 children = /*#__PURE__*/React.createElement(Tag, {
2443 "data-slate-spacer": true,
2444 style: {
2445 height: '0',
2446 color: 'transparent',
2447 outline: 'none',
2448 position: 'absolute'
2449 }
2450 }, /*#__PURE__*/React.createElement(MemoizedText, {
2451 renderPlaceholder: renderPlaceholder,
2452 decorations: [],
2453 isLast: false,
2454 parent: element,
2455 text: _text
2456 }));
2457 NODE_TO_INDEX.set(_text, 0);
2458 NODE_TO_PARENT.set(_text, element);
2459 }
2460 return renderElement({
2461 attributes,
2462 children,
2463 element
2464 });
2465};
2466var MemoizedElement = /*#__PURE__*/React.memo(Element, (prev, next) => {
2467 return prev.element === next.element && prev.renderElement === next.renderElement && prev.renderLeaf === next.renderLeaf && prev.renderPlaceholder === next.renderPlaceholder && isElementDecorationsEqual(prev.decorations, next.decorations) && (prev.selection === next.selection || !!prev.selection && !!next.selection && Range.equals(prev.selection, next.selection));
2468});
2469/**
2470 * The default element renderer.
2471 */
2472var DefaultElement = props => {
2473 var {
2474 attributes,
2475 children,
2476 element
2477 } = props;
2478 var editor = useSlateStatic();
2479 var Tag = editor.isInline(element) ? 'span' : 'div';
2480 return /*#__PURE__*/React.createElement(Tag, _objectSpread$2(_objectSpread$2({}, attributes), {}, {
2481 style: {
2482 position: 'relative'
2483 }
2484 }), children);
2485};
2486
2487/**
2488 * A React context for sharing the `decorate` prop of the editable.
2489 */
2490var DecorateContext = /*#__PURE__*/createContext(() => []);
2491/**
2492 * Get the current `decorate` prop of the editable.
2493 */
2494var useDecorate = () => {
2495 return useContext(DecorateContext);
2496};
2497
2498/**
2499 * A React context for sharing the `selected` state of an element.
2500 */
2501var SelectedContext = /*#__PURE__*/createContext(false);
2502/**
2503 * Get the current `selected` state of an element.
2504 */
2505var useSelected = () => {
2506 return useContext(SelectedContext);
2507};
2508
2509/**
2510 * Children.
2511 */
2512var useChildren = props => {
2513 var {
2514 decorations,
2515 node,
2516 renderElement,
2517 renderPlaceholder,
2518 renderLeaf,
2519 selection
2520 } = props;
2521 var decorate = useDecorate();
2522 var editor = useSlateStatic();
2523 var path = ReactEditor.findPath(editor, node);
2524 var children = [];
2525 var isLeafBlock = Element$1.isElement(node) && !editor.isInline(node) && Editor.hasInlines(editor, node);
2526 for (var i = 0; i < node.children.length; i++) {
2527 var p = path.concat(i);
2528 var n = node.children[i];
2529 var key = ReactEditor.findKey(editor, n);
2530 var range = Editor.range(editor, p);
2531 var sel = selection && Range.intersection(range, selection);
2532 var ds = decorate([n, p]);
2533 for (var dec of decorations) {
2534 var d = Range.intersection(dec, range);
2535 if (d) {
2536 ds.push(d);
2537 }
2538 }
2539 if (Element$1.isElement(n)) {
2540 children.push( /*#__PURE__*/React.createElement(SelectedContext.Provider, {
2541 key: "provider-".concat(key.id),
2542 value: !!sel
2543 }, /*#__PURE__*/React.createElement(MemoizedElement, {
2544 decorations: ds,
2545 element: n,
2546 key: key.id,
2547 renderElement: renderElement,
2548 renderPlaceholder: renderPlaceholder,
2549 renderLeaf: renderLeaf,
2550 selection: sel
2551 })));
2552 } else {
2553 children.push( /*#__PURE__*/React.createElement(MemoizedText, {
2554 decorations: ds,
2555 key: key.id,
2556 isLast: isLeafBlock && i === node.children.length - 1,
2557 parent: node,
2558 renderPlaceholder: renderPlaceholder,
2559 renderLeaf: renderLeaf,
2560 text: n
2561 }));
2562 }
2563 NODE_TO_INDEX.set(n, i);
2564 NODE_TO_PARENT.set(n, node);
2565 }
2566 return children;
2567};
2568
2569/**
2570 * A React context for sharing the `readOnly` state of the editor.
2571 */
2572var ReadOnlyContext = /*#__PURE__*/createContext(false);
2573/**
2574 * Get the current `readOnly` state of the editor.
2575 */
2576var useReadOnly = () => {
2577 return useContext(ReadOnlyContext);
2578};
2579
2580var SlateContext = /*#__PURE__*/createContext(null);
2581/**
2582 * Get the current editor object from the React context.
2583 */
2584var useSlate = () => {
2585 var context = useContext(SlateContext);
2586 if (!context) {
2587 throw new Error("The `useSlate` hook must be used inside the <Slate> component's context.");
2588 }
2589 var {
2590 editor
2591 } = context;
2592 return editor;
2593};
2594var useSlateWithV = () => {
2595 var context = useContext(SlateContext);
2596 if (!context) {
2597 throw new Error("The `useSlate` hook must be used inside the <Slate> component's context.");
2598 }
2599 return context;
2600};
2601
2602function useTrackUserInput() {
2603 var editor = useSlateStatic();
2604 var receivedUserInput = useRef(false);
2605 var animationFrameIdRef = useRef(0);
2606 var onUserInput = useCallback(() => {
2607 if (receivedUserInput.current) {
2608 return;
2609 }
2610 receivedUserInput.current = true;
2611 var window = ReactEditor.getWindow(editor);
2612 window.cancelAnimationFrame(animationFrameIdRef.current);
2613 animationFrameIdRef.current = window.requestAnimationFrame(() => {
2614 receivedUserInput.current = false;
2615 });
2616 }, [editor]);
2617 useEffect(() => () => cancelAnimationFrame(animationFrameIdRef.current), []);
2618 return {
2619 receivedUserInput,
2620 onUserInput
2621 };
2622}
2623
2624var TRIPLE_CLICK = 3;
2625
2626/**
2627 * Hotkey mappings for each platform.
2628 */
2629var HOTKEYS = {
2630 bold: 'mod+b',
2631 compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],
2632 moveBackward: 'left',
2633 moveForward: 'right',
2634 moveWordBackward: 'ctrl+left',
2635 moveWordForward: 'ctrl+right',
2636 deleteBackward: 'shift?+backspace',
2637 deleteForward: 'shift?+delete',
2638 extendBackward: 'shift+left',
2639 extendForward: 'shift+right',
2640 italic: 'mod+i',
2641 insertSoftBreak: 'shift+enter',
2642 splitBlock: 'enter',
2643 undo: 'mod+z'
2644};
2645var APPLE_HOTKEYS = {
2646 moveLineBackward: 'opt+up',
2647 moveLineForward: 'opt+down',
2648 moveWordBackward: 'opt+left',
2649 moveWordForward: 'opt+right',
2650 deleteBackward: ['ctrl+backspace', 'ctrl+h'],
2651 deleteForward: ['ctrl+delete', 'ctrl+d'],
2652 deleteLineBackward: 'cmd+shift?+backspace',
2653 deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],
2654 deleteWordBackward: 'opt+shift?+backspace',
2655 deleteWordForward: 'opt+shift?+delete',
2656 extendLineBackward: 'opt+shift+up',
2657 extendLineForward: 'opt+shift+down',
2658 redo: 'cmd+shift+z',
2659 transposeCharacter: 'ctrl+t'
2660};
2661var WINDOWS_HOTKEYS = {
2662 deleteWordBackward: 'ctrl+shift?+backspace',
2663 deleteWordForward: 'ctrl+shift?+delete',
2664 redo: ['ctrl+y', 'ctrl+shift+z']
2665};
2666/**
2667 * Create a platform-aware hotkey checker.
2668 */
2669var create = key => {
2670 var generic = HOTKEYS[key];
2671 var apple = APPLE_HOTKEYS[key];
2672 var windows = WINDOWS_HOTKEYS[key];
2673 var isGeneric = generic && isHotkey(generic);
2674 var isApple = apple && isHotkey(apple);
2675 var isWindows = windows && isHotkey(windows);
2676 return event => {
2677 if (isGeneric && isGeneric(event)) return true;
2678 if (IS_APPLE && isApple && isApple(event)) return true;
2679 if (!IS_APPLE && isWindows && isWindows(event)) return true;
2680 return false;
2681 };
2682};
2683/**
2684 * Hotkeys.
2685 */
2686var Hotkeys = {
2687 isBold: create('bold'),
2688 isCompose: create('compose'),
2689 isMoveBackward: create('moveBackward'),
2690 isMoveForward: create('moveForward'),
2691 isDeleteBackward: create('deleteBackward'),
2692 isDeleteForward: create('deleteForward'),
2693 isDeleteLineBackward: create('deleteLineBackward'),
2694 isDeleteLineForward: create('deleteLineForward'),
2695 isDeleteWordBackward: create('deleteWordBackward'),
2696 isDeleteWordForward: create('deleteWordForward'),
2697 isExtendBackward: create('extendBackward'),
2698 isExtendForward: create('extendForward'),
2699 isExtendLineBackward: create('extendLineBackward'),
2700 isExtendLineForward: create('extendLineForward'),
2701 isItalic: create('italic'),
2702 isMoveLineBackward: create('moveLineBackward'),
2703 isMoveLineForward: create('moveLineForward'),
2704 isMoveWordBackward: create('moveWordBackward'),
2705 isMoveWordForward: create('moveWordForward'),
2706 isRedo: create('redo'),
2707 isSoftBreak: create('insertSoftBreak'),
2708 isSplitBlock: create('splitBlock'),
2709 isTransposeCharacter: create('transposeCharacter'),
2710 isUndo: create('undo')
2711};
2712
2713var createRestoreDomManager = (editor, receivedUserInput) => {
2714 var bufferedMutations = [];
2715 var clear = () => {
2716 bufferedMutations = [];
2717 };
2718 var registerMutations = mutations => {
2719 if (!receivedUserInput.current) {
2720 return;
2721 }
2722 var trackedMutations = mutations.filter(mutation => isTrackedMutation(editor, mutation, mutations));
2723 bufferedMutations.push(...trackedMutations);
2724 };
2725 function restoreDOM() {
2726 if (bufferedMutations.length > 0) {
2727 bufferedMutations.reverse().forEach(mutation => {
2728 if (mutation.type === 'characterData') {
2729 // We don't want to restore the DOM for characterData mutations
2730 // because this interrupts the composition.
2731 return;
2732 }
2733 mutation.removedNodes.forEach(node => {
2734 mutation.target.insertBefore(node, mutation.nextSibling);
2735 });
2736 mutation.addedNodes.forEach(node => {
2737 mutation.target.removeChild(node);
2738 });
2739 });
2740 // Clear buffered mutations to ensure we don't undo them twice
2741 clear();
2742 }
2743 }
2744 return {
2745 registerMutations,
2746 restoreDOM,
2747 clear
2748 };
2749};
2750
2751var MUTATION_OBSERVER_CONFIG = {
2752 subtree: true,
2753 childList: true,
2754 characterData: true,
2755 characterDataOldValue: true
2756};
2757// We have to use a class component here since we rely on `getSnapshotBeforeUpdate` which has no FC equivalent
2758// to run code synchronously immediately before react commits the component update to the DOM.
2759class RestoreDOMComponent extends Component {
2760 constructor() {
2761 super(...arguments);
2762 _defineProperty(this, "context", null);
2763 _defineProperty(this, "manager", null);
2764 _defineProperty(this, "mutationObserver", null);
2765 }
2766 observe() {
2767 var _this$mutationObserve;
2768 var {
2769 node
2770 } = this.props;
2771 if (!node.current) {
2772 throw new Error('Failed to attach MutationObserver, `node` is undefined');
2773 }
2774 (_this$mutationObserve = this.mutationObserver) === null || _this$mutationObserve === void 0 || _this$mutationObserve.observe(node.current, MUTATION_OBSERVER_CONFIG);
2775 }
2776 componentDidMount() {
2777 var {
2778 receivedUserInput
2779 } = this.props;
2780 var editor = this.context;
2781 this.manager = createRestoreDomManager(editor, receivedUserInput);
2782 this.mutationObserver = new MutationObserver(this.manager.registerMutations);
2783 this.observe();
2784 }
2785 getSnapshotBeforeUpdate() {
2786 var _this$mutationObserve2, _this$mutationObserve3, _this$manager2;
2787 var pendingMutations = (_this$mutationObserve2 = this.mutationObserver) === null || _this$mutationObserve2 === void 0 ? void 0 : _this$mutationObserve2.takeRecords();
2788 if (pendingMutations !== null && pendingMutations !== void 0 && pendingMutations.length) {
2789 var _this$manager;
2790 (_this$manager = this.manager) === null || _this$manager === void 0 || _this$manager.registerMutations(pendingMutations);
2791 }
2792 (_this$mutationObserve3 = this.mutationObserver) === null || _this$mutationObserve3 === void 0 || _this$mutationObserve3.disconnect();
2793 (_this$manager2 = this.manager) === null || _this$manager2 === void 0 || _this$manager2.restoreDOM();
2794 return null;
2795 }
2796 componentDidUpdate() {
2797 var _this$manager3;
2798 (_this$manager3 = this.manager) === null || _this$manager3 === void 0 || _this$manager3.clear();
2799 this.observe();
2800 }
2801 componentWillUnmount() {
2802 var _this$mutationObserve4;
2803 (_this$mutationObserve4 = this.mutationObserver) === null || _this$mutationObserve4 === void 0 || _this$mutationObserve4.disconnect();
2804 }
2805 render() {
2806 return this.props.children;
2807 }
2808}
2809_defineProperty(RestoreDOMComponent, "contextType", EditorContext);
2810var RestoreDOM = IS_ANDROID ? RestoreDOMComponent : _ref => {
2811 var {
2812 children
2813 } = _ref;
2814 return /*#__PURE__*/React.createElement(React.Fragment, null, children);
2815};
2816
2817var _excluded$1 = ["autoFocus", "decorate", "onDOMBeforeInput", "placeholder", "readOnly", "renderElement", "renderLeaf", "renderPlaceholder", "scrollSelectionIntoView", "style", "as", "disableDefaultStyles"],
2818 _excluded2 = ["text"];
2819function ownKeys$1(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
2820function _objectSpread$1(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$1(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$1(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
2821var Children = props => /*#__PURE__*/React.createElement(React.Fragment, null, useChildren(props));
2822/**
2823 * Editable.
2824 */
2825var Editable = props => {
2826 var defaultRenderPlaceholder = useCallback(props => /*#__PURE__*/React.createElement(DefaultPlaceholder, _objectSpread$1({}, props)), []);
2827 var {
2828 autoFocus,
2829 decorate = defaultDecorate,
2830 onDOMBeforeInput: propsOnDOMBeforeInput,
2831 placeholder,
2832 readOnly = false,
2833 renderElement,
2834 renderLeaf,
2835 renderPlaceholder = defaultRenderPlaceholder,
2836 scrollSelectionIntoView = defaultScrollSelectionIntoView,
2837 style: userStyle = {},
2838 as: Component = 'div',
2839 disableDefaultStyles = false
2840 } = props,
2841 attributes = _objectWithoutProperties(props, _excluded$1);
2842 var editor = useSlate();
2843 // Rerender editor when composition status changed
2844 var [isComposing, setIsComposing] = useState(false);
2845 var ref = useRef(null);
2846 var deferredOperations = useRef([]);
2847 var [placeholderHeight, setPlaceholderHeight] = useState();
2848 var {
2849 onUserInput,
2850 receivedUserInput
2851 } = useTrackUserInput();
2852 var [, forceRender] = useReducer(s => s + 1, 0);
2853 EDITOR_TO_FORCE_RENDER.set(editor, forceRender);
2854 // Update internal state on each render.
2855 IS_READ_ONLY.set(editor, readOnly);
2856 // Keep track of some state for the event handler logic.
2857 var state = useMemo(() => ({
2858 isDraggingInternally: false,
2859 isUpdatingSelection: false,
2860 latestElement: null,
2861 hasMarkPlaceholder: false
2862 }), []);
2863 // The autoFocus TextareaHTMLAttribute doesn't do anything on a div, so it
2864 // needs to be manually focused.
2865 useEffect(() => {
2866 if (ref.current && autoFocus) {
2867 ref.current.focus();
2868 }
2869 }, [autoFocus]);
2870 /**
2871 * The AndroidInputManager object has a cyclical dependency on onDOMSelectionChange
2872 *
2873 * It is defined as a reference to simplify hook dependencies and clarify that
2874 * it needs to be initialized.
2875 */
2876 var androidInputManagerRef = useRef();
2877 // Listen on the native `selectionchange` event to be able to update any time
2878 // the selection changes. This is required because React's `onSelect` is leaky
2879 // and non-standard so it doesn't fire until after a selection has been
2880 // released. This causes issues in situations where another change happens
2881 // while a selection is being dragged.
2882 var onDOMSelectionChange = useMemo(() => throttle(() => {
2883 var androidInputManager = androidInputManagerRef.current;
2884 if ((IS_ANDROID || !ReactEditor.isComposing(editor)) && (!state.isUpdatingSelection || androidInputManager !== null && androidInputManager !== void 0 && androidInputManager.isFlushing()) && !state.isDraggingInternally) {
2885 var root = ReactEditor.findDocumentOrShadowRoot(editor);
2886 var {
2887 activeElement
2888 } = root;
2889 var el = ReactEditor.toDOMNode(editor, editor);
2890 var domSelection = root.getSelection();
2891 if (activeElement === el) {
2892 state.latestElement = activeElement;
2893 IS_FOCUSED.set(editor, true);
2894 } else {
2895 IS_FOCUSED.delete(editor);
2896 }
2897 if (!domSelection) {
2898 return Transforms.deselect(editor);
2899 }
2900 var {
2901 anchorNode,
2902 focusNode
2903 } = domSelection;
2904 var anchorNodeSelectable = ReactEditor.hasEditableTarget(editor, anchorNode) || ReactEditor.isTargetInsideNonReadonlyVoid(editor, anchorNode);
2905 var focusNodeSelectable = ReactEditor.hasEditableTarget(editor, focusNode) || ReactEditor.isTargetInsideNonReadonlyVoid(editor, focusNode);
2906 if (anchorNodeSelectable && focusNodeSelectable) {
2907 var range = ReactEditor.toSlateRange(editor, domSelection, {
2908 exactMatch: false,
2909 suppressThrow: true
2910 });
2911 if (range) {
2912 if (!ReactEditor.isComposing(editor) && !(androidInputManager !== null && androidInputManager !== void 0 && androidInputManager.hasPendingChanges()) && !(androidInputManager !== null && androidInputManager !== void 0 && androidInputManager.isFlushing())) {
2913 Transforms.select(editor, range);
2914 } else {
2915 androidInputManager === null || androidInputManager === void 0 || androidInputManager.handleUserSelect(range);
2916 }
2917 }
2918 }
2919 // Deselect the editor if the dom selection is not selectable in readonly mode
2920 if (readOnly && (!anchorNodeSelectable || !focusNodeSelectable)) {
2921 Transforms.deselect(editor);
2922 }
2923 }
2924 }, 100), [editor, readOnly, state]);
2925 var scheduleOnDOMSelectionChange = useMemo(() => debounce(onDOMSelectionChange, 0), [onDOMSelectionChange]);
2926 androidInputManagerRef.current = useAndroidInputManager({
2927 node: ref,
2928 onDOMSelectionChange,
2929 scheduleOnDOMSelectionChange
2930 });
2931 useIsomorphicLayoutEffect(() => {
2932 var _androidInputManagerR, _androidInputManagerR2;
2933 // Update element-related weak maps with the DOM element ref.
2934 var window;
2935 if (ref.current && (window = getDefaultView(ref.current))) {
2936 EDITOR_TO_WINDOW.set(editor, window);
2937 EDITOR_TO_ELEMENT.set(editor, ref.current);
2938 NODE_TO_ELEMENT.set(editor, ref.current);
2939 ELEMENT_TO_NODE.set(ref.current, editor);
2940 } else {
2941 NODE_TO_ELEMENT.delete(editor);
2942 }
2943 // Make sure the DOM selection state is in sync.
2944 var {
2945 selection
2946 } = editor;
2947 var root = ReactEditor.findDocumentOrShadowRoot(editor);
2948 var domSelection = root.getSelection();
2949 if (!domSelection || !ReactEditor.isFocused(editor) || (_androidInputManagerR = androidInputManagerRef.current) !== null && _androidInputManagerR !== void 0 && _androidInputManagerR.hasPendingAction()) {
2950 return;
2951 }
2952 var setDomSelection = forceChange => {
2953 var hasDomSelection = domSelection.type !== 'None';
2954 // If the DOM selection is properly unset, we're done.
2955 if (!selection && !hasDomSelection) {
2956 return;
2957 }
2958 // Get anchorNode and focusNode
2959 var focusNode = domSelection.focusNode;
2960 var anchorNode;
2961 // COMPAT: In firefox the normal seletion way does not work
2962 // (https://github.com/ianstormtaylor/slate/pull/5486#issue-1820720223)
2963 if (IS_FIREFOX && domSelection.rangeCount > 1) {
2964 var firstRange = domSelection.getRangeAt(0);
2965 var lastRange = domSelection.getRangeAt(domSelection.rangeCount - 1);
2966 // Right to left
2967 if (firstRange.startContainer === focusNode) {
2968 anchorNode = lastRange.endContainer;
2969 } else {
2970 // Left to right
2971 anchorNode = firstRange.startContainer;
2972 }
2973 } else {
2974 anchorNode = domSelection.anchorNode;
2975 }
2976 // verify that the dom selection is in the editor
2977 var editorElement = EDITOR_TO_ELEMENT.get(editor);
2978 var hasDomSelectionInEditor = false;
2979 if (editorElement.contains(anchorNode) && editorElement.contains(focusNode)) {
2980 hasDomSelectionInEditor = true;
2981 }
2982 // If the DOM selection is in the editor and the editor selection is already correct, we're done.
2983 if (hasDomSelection && hasDomSelectionInEditor && selection && !forceChange) {
2984 var slateRange = ReactEditor.toSlateRange(editor, domSelection, {
2985 exactMatch: true,
2986 // domSelection is not necessarily a valid Slate range
2987 // (e.g. when clicking on contentEditable:false element)
2988 suppressThrow: true
2989 });
2990 if (slateRange && Range.equals(slateRange, selection)) {
2991 var _anchorNode;
2992 if (!state.hasMarkPlaceholder) {
2993 return;
2994 }
2995 // Ensure selection is inside the mark placeholder
2996 if ((_anchorNode = anchorNode) !== null && _anchorNode !== void 0 && (_anchorNode = _anchorNode.parentElement) !== null && _anchorNode !== void 0 && _anchorNode.hasAttribute('data-slate-mark-placeholder')) {
2997 return;
2998 }
2999 }
3000 }
3001 // when <Editable/> is being controlled through external value
3002 // then its children might just change - DOM responds to it on its own
3003 // but Slate's value is not being updated through any operation
3004 // and thus it doesn't transform selection on its own
3005 if (selection && !ReactEditor.hasRange(editor, selection)) {
3006 editor.selection = ReactEditor.toSlateRange(editor, domSelection, {
3007 exactMatch: false,
3008 suppressThrow: true
3009 });
3010 return;
3011 }
3012 // Otherwise the DOM selection is out of sync, so update it.
3013 state.isUpdatingSelection = true;
3014 var newDomRange = selection && ReactEditor.toDOMRange(editor, selection);
3015 if (newDomRange) {
3016 if (ReactEditor.isComposing(editor) && !IS_ANDROID) {
3017 domSelection.collapseToEnd();
3018 } else if (Range.isBackward(selection)) {
3019 domSelection.setBaseAndExtent(newDomRange.endContainer, newDomRange.endOffset, newDomRange.startContainer, newDomRange.startOffset);
3020 } else {
3021 domSelection.setBaseAndExtent(newDomRange.startContainer, newDomRange.startOffset, newDomRange.endContainer, newDomRange.endOffset);
3022 }
3023 scrollSelectionIntoView(editor, newDomRange);
3024 } else {
3025 domSelection.removeAllRanges();
3026 }
3027 return newDomRange;
3028 };
3029 // In firefox if there is more then 1 range and we call setDomSelection we remove the ability to select more cells in a table
3030 if (domSelection.rangeCount <= 1) {
3031 setDomSelection();
3032 }
3033 var ensureSelection = ((_androidInputManagerR2 = androidInputManagerRef.current) === null || _androidInputManagerR2 === void 0 ? void 0 : _androidInputManagerR2.isFlushing()) === 'action';
3034 if (!IS_ANDROID || !ensureSelection) {
3035 setTimeout(() => {
3036 state.isUpdatingSelection = false;
3037 });
3038 return;
3039 }
3040 var timeoutId = null;
3041 var animationFrameId = requestAnimationFrame(() => {
3042 if (ensureSelection) {
3043 var ensureDomSelection = forceChange => {
3044 try {
3045 var el = ReactEditor.toDOMNode(editor, editor);
3046 el.focus();
3047 setDomSelection(forceChange);
3048 } catch (e) {
3049 // Ignore, dom and state might be out of sync
3050 }
3051 };
3052 // Compat: Android IMEs try to force their selection by manually re-applying it even after we set it.
3053 // This essentially would make setting the slate selection during an update meaningless, so we force it
3054 // again here. We can't only do it in the setTimeout after the animation frame since that would cause a
3055 // visible flicker.
3056 ensureDomSelection();
3057 timeoutId = setTimeout(() => {
3058 // COMPAT: While setting the selection in an animation frame visually correctly sets the selection,
3059 // it doesn't update GBoards spellchecker state. We have to manually trigger a selection change after
3060 // the animation frame to ensure it displays the correct state.
3061 ensureDomSelection(true);
3062 state.isUpdatingSelection = false;
3063 });
3064 }
3065 });
3066 return () => {
3067 cancelAnimationFrame(animationFrameId);
3068 if (timeoutId) {
3069 clearTimeout(timeoutId);
3070 }
3071 };
3072 });
3073 // Listen on the native `beforeinput` event to get real "Level 2" events. This
3074 // is required because React's `beforeinput` is fake and never really attaches
3075 // to the real event sadly. (2019/11/01)
3076 // https://github.com/facebook/react/issues/11211
3077 var onDOMBeforeInput = useCallback(event => {
3078 onUserInput();
3079 if (!readOnly && ReactEditor.hasEditableTarget(editor, event.target) && !isDOMEventHandled(event, propsOnDOMBeforeInput)) {
3080 var _EDITOR_TO_USER_SELEC;
3081 // COMPAT: BeforeInput events aren't cancelable on android, so we have to handle them differently using the android input manager.
3082 if (androidInputManagerRef.current) {
3083 return androidInputManagerRef.current.handleDOMBeforeInput(event);
3084 }
3085 // Some IMEs/Chrome extensions like e.g. Grammarly set the selection immediately before
3086 // triggering a `beforeinput` expecting the change to be applied to the immediately before
3087 // set selection.
3088 scheduleOnDOMSelectionChange.flush();
3089 onDOMSelectionChange.flush();
3090 var {
3091 selection
3092 } = editor;
3093 var {
3094 inputType: type
3095 } = event;
3096 var data = event.dataTransfer || event.data || undefined;
3097 var isCompositionChange = type === 'insertCompositionText' || type === 'deleteCompositionText';
3098 // COMPAT: use composition change events as a hint to where we should insert
3099 // composition text if we aren't composing to work around https://github.com/ianstormtaylor/slate/issues/5038
3100 if (isCompositionChange && ReactEditor.isComposing(editor)) {
3101 return;
3102 }
3103 var native = false;
3104 if (type === 'insertText' && selection && Range.isCollapsed(selection) &&
3105 // Only use native character insertion for single characters a-z or space for now.
3106 // Long-press events (hold a + press 4 = ä) to choose a special character otherwise
3107 // causes duplicate inserts.
3108 event.data && event.data.length === 1 && /[a-z ]/i.test(event.data) &&
3109 // Chrome has issues correctly editing the start of nodes: https://bugs.chromium.org/p/chromium/issues/detail?id=1249405
3110 // When there is an inline element, e.g. a link, and you select
3111 // right after it (the start of the next node).
3112 selection.anchor.offset !== 0) {
3113 var _node$parentElement, _window$getComputedSt;
3114 native = true;
3115 // Skip native if there are marks, as
3116 // `insertText` will insert a node, not just text.
3117 if (editor.marks) {
3118 native = false;
3119 }
3120 // Chrome also has issues correctly editing the end of anchor elements: https://bugs.chromium.org/p/chromium/issues/detail?id=1259100
3121 // Therefore we don't allow native events to insert text at the end of anchor nodes.
3122 var {
3123 anchor
3124 } = selection;
3125 var [node, offset] = ReactEditor.toDOMPoint(editor, anchor);
3126 var anchorNode = (_node$parentElement = node.parentElement) === null || _node$parentElement === void 0 ? void 0 : _node$parentElement.closest('a');
3127 var window = ReactEditor.getWindow(editor);
3128 if (native && anchorNode && ReactEditor.hasDOMNode(editor, anchorNode)) {
3129 var _lastText$textContent;
3130 // Find the last text node inside the anchor.
3131 var lastText = window === null || window === void 0 ? void 0 : window.document.createTreeWalker(anchorNode, NodeFilter.SHOW_TEXT).lastChild();
3132 if (lastText === node && ((_lastText$textContent = lastText.textContent) === null || _lastText$textContent === void 0 ? void 0 : _lastText$textContent.length) === offset) {
3133 native = false;
3134 }
3135 }
3136 // Chrome has issues with the presence of tab characters inside elements with whiteSpace = 'pre'
3137 // causing abnormal insert behavior: https://bugs.chromium.org/p/chromium/issues/detail?id=1219139
3138 if (native && node.parentElement && (window === null || window === void 0 || (_window$getComputedSt = window.getComputedStyle(node.parentElement)) === null || _window$getComputedSt === void 0 ? void 0 : _window$getComputedSt.whiteSpace) === 'pre') {
3139 var block = Editor.above(editor, {
3140 at: anchor.path,
3141 match: n => Element$1.isElement(n) && Editor.isBlock(editor, n)
3142 });
3143 if (block && Node.string(block[0]).includes('\t')) {
3144 native = false;
3145 }
3146 }
3147 }
3148 // COMPAT: For the deleting forward/backward input types we don't want
3149 // to change the selection because it is the range that will be deleted,
3150 // and those commands determine that for themselves.
3151 if (!type.startsWith('delete') || type.startsWith('deleteBy')) {
3152 var [targetRange] = event.getTargetRanges();
3153 if (targetRange) {
3154 var range = ReactEditor.toSlateRange(editor, targetRange, {
3155 exactMatch: false,
3156 suppressThrow: false
3157 });
3158 if (!selection || !Range.equals(selection, range)) {
3159 native = false;
3160 var selectionRef = !isCompositionChange && editor.selection && Editor.rangeRef(editor, editor.selection);
3161 Transforms.select(editor, range);
3162 if (selectionRef) {
3163 EDITOR_TO_USER_SELECTION.set(editor, selectionRef);
3164 }
3165 }
3166 }
3167 }
3168 // Composition change types occur while a user is composing text and can't be
3169 // cancelled. Let them through and wait for the composition to end.
3170 if (isCompositionChange) {
3171 return;
3172 }
3173 if (!native) {
3174 event.preventDefault();
3175 }
3176 // COMPAT: If the selection is expanded, even if the command seems like
3177 // a delete forward/backward command it should delete the selection.
3178 if (selection && Range.isExpanded(selection) && type.startsWith('delete')) {
3179 var direction = type.endsWith('Backward') ? 'backward' : 'forward';
3180 Editor.deleteFragment(editor, {
3181 direction
3182 });
3183 return;
3184 }
3185 switch (type) {
3186 case 'deleteByComposition':
3187 case 'deleteByCut':
3188 case 'deleteByDrag':
3189 {
3190 Editor.deleteFragment(editor);
3191 break;
3192 }
3193 case 'deleteContent':
3194 case 'deleteContentForward':
3195 {
3196 Editor.deleteForward(editor);
3197 break;
3198 }
3199 case 'deleteContentBackward':
3200 {
3201 Editor.deleteBackward(editor);
3202 break;
3203 }
3204 case 'deleteEntireSoftLine':
3205 {
3206 Editor.deleteBackward(editor, {
3207 unit: 'line'
3208 });
3209 Editor.deleteForward(editor, {
3210 unit: 'line'
3211 });
3212 break;
3213 }
3214 case 'deleteHardLineBackward':
3215 {
3216 Editor.deleteBackward(editor, {
3217 unit: 'block'
3218 });
3219 break;
3220 }
3221 case 'deleteSoftLineBackward':
3222 {
3223 Editor.deleteBackward(editor, {
3224 unit: 'line'
3225 });
3226 break;
3227 }
3228 case 'deleteHardLineForward':
3229 {
3230 Editor.deleteForward(editor, {
3231 unit: 'block'
3232 });
3233 break;
3234 }
3235 case 'deleteSoftLineForward':
3236 {
3237 Editor.deleteForward(editor, {
3238 unit: 'line'
3239 });
3240 break;
3241 }
3242 case 'deleteWordBackward':
3243 {
3244 Editor.deleteBackward(editor, {
3245 unit: 'word'
3246 });
3247 break;
3248 }
3249 case 'deleteWordForward':
3250 {
3251 Editor.deleteForward(editor, {
3252 unit: 'word'
3253 });
3254 break;
3255 }
3256 case 'insertLineBreak':
3257 Editor.insertSoftBreak(editor);
3258 break;
3259 case 'insertParagraph':
3260 {
3261 Editor.insertBreak(editor);
3262 break;
3263 }
3264 case 'insertFromComposition':
3265 case 'insertFromDrop':
3266 case 'insertFromPaste':
3267 case 'insertFromYank':
3268 case 'insertReplacementText':
3269 case 'insertText':
3270 {
3271 if (type === 'insertFromComposition') {
3272 // COMPAT: in Safari, `compositionend` is dispatched after the
3273 // `beforeinput` for "insertFromComposition". But if we wait for it
3274 // then we will abort because we're still composing and the selection
3275 // won't be updated properly.
3276 // https://www.w3.org/TR/input-events-2/
3277 if (ReactEditor.isComposing(editor)) {
3278 setIsComposing(false);
3279 IS_COMPOSING.set(editor, false);
3280 }
3281 }
3282 // use a weak comparison instead of 'instanceof' to allow
3283 // programmatic access of paste events coming from external windows
3284 // like cypress where cy.window does not work realibly
3285 if ((data === null || data === void 0 ? void 0 : data.constructor.name) === 'DataTransfer') {
3286 ReactEditor.insertData(editor, data);
3287 } else if (typeof data === 'string') {
3288 // Only insertText operations use the native functionality, for now.
3289 // Potentially expand to single character deletes, as well.
3290 if (native) {
3291 deferredOperations.current.push(() => Editor.insertText(editor, data));
3292 } else {
3293 Editor.insertText(editor, data);
3294 }
3295 }
3296 break;
3297 }
3298 }
3299 // Restore the actual user section if nothing manually set it.
3300 var toRestore = (_EDITOR_TO_USER_SELEC = EDITOR_TO_USER_SELECTION.get(editor)) === null || _EDITOR_TO_USER_SELEC === void 0 ? void 0 : _EDITOR_TO_USER_SELEC.unref();
3301 EDITOR_TO_USER_SELECTION.delete(editor);
3302 if (toRestore && (!editor.selection || !Range.equals(editor.selection, toRestore))) {
3303 Transforms.select(editor, toRestore);
3304 }
3305 }
3306 }, [editor, onDOMSelectionChange, onUserInput, propsOnDOMBeforeInput, readOnly, scheduleOnDOMSelectionChange]);
3307 var callbackRef = useCallback(node => {
3308 if (node == null) {
3309 onDOMSelectionChange.cancel();
3310 scheduleOnDOMSelectionChange.cancel();
3311 EDITOR_TO_ELEMENT.delete(editor);
3312 NODE_TO_ELEMENT.delete(editor);
3313 if (ref.current && HAS_BEFORE_INPUT_SUPPORT) {
3314 // @ts-ignore The `beforeinput` event isn't recognized.
3315 ref.current.removeEventListener('beforeinput', onDOMBeforeInput);
3316 }
3317 } else {
3318 // Attach a native DOM event handler for `beforeinput` events, because React's
3319 // built-in `onBeforeInput` is actually a leaky polyfill that doesn't expose
3320 // real `beforeinput` events sadly... (2019/11/04)
3321 // https://github.com/facebook/react/issues/11211
3322 if (HAS_BEFORE_INPUT_SUPPORT) {
3323 // @ts-ignore The `beforeinput` event isn't recognized.
3324 node.addEventListener('beforeinput', onDOMBeforeInput);
3325 }
3326 }
3327 ref.current = node;
3328 }, [onDOMSelectionChange, scheduleOnDOMSelectionChange, editor, onDOMBeforeInput]);
3329 // Attach a native DOM event handler for `selectionchange`, because React's
3330 // built-in `onSelect` handler doesn't fire for all selection changes. It's a
3331 // leaky polyfill that only fires on keypresses or clicks. Instead, we want to
3332 // fire for any change to the selection inside the editor. (2019/11/04)
3333 // https://github.com/facebook/react/issues/5785
3334 useIsomorphicLayoutEffect(() => {
3335 var window = ReactEditor.getWindow(editor);
3336 window.document.addEventListener('selectionchange', scheduleOnDOMSelectionChange);
3337 return () => {
3338 window.document.removeEventListener('selectionchange', scheduleOnDOMSelectionChange);
3339 };
3340 }, [scheduleOnDOMSelectionChange]);
3341 var decorations = decorate([editor, []]);
3342 var showPlaceholder = placeholder && editor.children.length === 1 && Array.from(Node.texts(editor)).length === 1 && Node.string(editor) === '' && !isComposing;
3343 var placeHolderResizeHandler = useCallback(placeholderEl => {
3344 if (placeholderEl && showPlaceholder) {
3345 var _placeholderEl$getBou;
3346 setPlaceholderHeight((_placeholderEl$getBou = placeholderEl.getBoundingClientRect()) === null || _placeholderEl$getBou === void 0 ? void 0 : _placeholderEl$getBou.height);
3347 } else {
3348 setPlaceholderHeight(undefined);
3349 }
3350 }, [showPlaceholder]);
3351 if (showPlaceholder) {
3352 var start = Editor.start(editor, []);
3353 decorations.push({
3354 [PLACEHOLDER_SYMBOL]: true,
3355 placeholder,
3356 onPlaceholderResize: placeHolderResizeHandler,
3357 anchor: start,
3358 focus: start
3359 });
3360 }
3361 var {
3362 marks
3363 } = editor;
3364 state.hasMarkPlaceholder = false;
3365 if (editor.selection && Range.isCollapsed(editor.selection) && marks) {
3366 var {
3367 anchor
3368 } = editor.selection;
3369 var leaf = Node.leaf(editor, anchor.path);
3370 var rest = _objectWithoutProperties(leaf, _excluded2);
3371 // While marks isn't a 'complete' text, we can still use loose Text.equals
3372 // here which only compares marks anyway.
3373 if (!Text$1.equals(leaf, marks, {
3374 loose: true
3375 })) {
3376 state.hasMarkPlaceholder = true;
3377 var unset = Object.fromEntries(Object.keys(rest).map(mark => [mark, null]));
3378 decorations.push(_objectSpread$1(_objectSpread$1(_objectSpread$1({
3379 [MARK_PLACEHOLDER_SYMBOL]: true
3380 }, unset), marks), {}, {
3381 anchor,
3382 focus: anchor
3383 }));
3384 }
3385 }
3386 // Update EDITOR_TO_MARK_PLACEHOLDER_MARKS in setTimeout useEffect to ensure we don't set it
3387 // before we receive the composition end event.
3388 useEffect(() => {
3389 setTimeout(() => {
3390 var {
3391 selection
3392 } = editor;
3393 if (selection) {
3394 var {
3395 anchor: _anchor
3396 } = selection;
3397 var _text = Node.leaf(editor, _anchor.path);
3398 // While marks isn't a 'complete' text, we can still use loose Text.equals
3399 // here which only compares marks anyway.
3400 if (marks && !Text$1.equals(_text, marks, {
3401 loose: true
3402 })) {
3403 EDITOR_TO_PENDING_INSERTION_MARKS.set(editor, marks);
3404 return;
3405 }
3406 }
3407 EDITOR_TO_PENDING_INSERTION_MARKS.delete(editor);
3408 });
3409 });
3410 return /*#__PURE__*/React.createElement(ReadOnlyContext.Provider, {
3411 value: readOnly
3412 }, /*#__PURE__*/React.createElement(DecorateContext.Provider, {
3413 value: decorate
3414 }, /*#__PURE__*/React.createElement(RestoreDOM, {
3415 node: ref,
3416 receivedUserInput: receivedUserInput
3417 }, /*#__PURE__*/React.createElement(Component, _objectSpread$1(_objectSpread$1({
3418 role: readOnly ? undefined : 'textbox',
3419 "aria-multiline": readOnly ? undefined : true
3420 }, attributes), {}, {
3421 // COMPAT: Certain browsers don't support the `beforeinput` event, so we'd
3422 // have to use hacks to make these replacement-based features work.
3423 // For SSR situations HAS_BEFORE_INPUT_SUPPORT is false and results in prop
3424 // mismatch warning app moves to browser. Pass-through consumer props when
3425 // not CAN_USE_DOM (SSR) and default to falsy value
3426 spellCheck: HAS_BEFORE_INPUT_SUPPORT || !CAN_USE_DOM ? attributes.spellCheck : false,
3427 autoCorrect: HAS_BEFORE_INPUT_SUPPORT || !CAN_USE_DOM ? attributes.autoCorrect : 'false',
3428 autoCapitalize: HAS_BEFORE_INPUT_SUPPORT || !CAN_USE_DOM ? attributes.autoCapitalize : 'false',
3429 "data-slate-editor": true,
3430 "data-slate-node": "value",
3431 // explicitly set this
3432 contentEditable: !readOnly,
3433 // in some cases, a decoration needs access to the range / selection to decorate a text node,
3434 // then you will select the whole text node when you select part the of text
3435 // this magic zIndex="-1" will fix it
3436 zindex: -1,
3437 suppressContentEditableWarning: true,
3438 ref: callbackRef,
3439 style: _objectSpread$1(_objectSpread$1({}, disableDefaultStyles ? {} : _objectSpread$1({
3440 // Allow positioning relative to the editable element.
3441 position: 'relative',
3442 // Preserve adjacent whitespace and new lines.
3443 whiteSpace: 'pre-wrap',
3444 // Allow words to break if they are too long.
3445 wordWrap: 'break-word'
3446 }, placeholderHeight ? {
3447 minHeight: placeholderHeight
3448 } : {})), userStyle),
3449 onBeforeInput: useCallback(event => {
3450 // COMPAT: Certain browsers don't support the `beforeinput` event, so we
3451 // fall back to React's leaky polyfill instead just for it. It
3452 // only works for the `insertText` input type.
3453 if (!HAS_BEFORE_INPUT_SUPPORT && !readOnly && !isEventHandled(event, attributes.onBeforeInput) && ReactEditor.hasSelectableTarget(editor, event.target)) {
3454 event.preventDefault();
3455 if (!ReactEditor.isComposing(editor)) {
3456 var _text2 = event.data;
3457 Editor.insertText(editor, _text2);
3458 }
3459 }
3460 }, [attributes.onBeforeInput, editor, readOnly]),
3461 onInput: useCallback(event => {
3462 if (isEventHandled(event, attributes.onInput)) {
3463 return;
3464 }
3465 if (androidInputManagerRef.current) {
3466 androidInputManagerRef.current.handleInput();
3467 return;
3468 }
3469 // Flush native operations, as native events will have propogated
3470 // and we can correctly compare DOM text values in components
3471 // to stop rendering, so that browser functions like autocorrect
3472 // and spellcheck work as expected.
3473 for (var op of deferredOperations.current) {
3474 op();
3475 }
3476 deferredOperations.current = [];
3477 }, [attributes.onInput]),
3478 onBlur: useCallback(event => {
3479 if (readOnly || state.isUpdatingSelection || !ReactEditor.hasSelectableTarget(editor, event.target) || isEventHandled(event, attributes.onBlur)) {
3480 return;
3481 }
3482 // COMPAT: If the current `activeElement` is still the previous
3483 // one, this is due to the window being blurred when the tab
3484 // itself becomes unfocused, so we want to abort early to allow to
3485 // editor to stay focused when the tab becomes focused again.
3486 var root = ReactEditor.findDocumentOrShadowRoot(editor);
3487 if (state.latestElement === root.activeElement) {
3488 return;
3489 }
3490 var {
3491 relatedTarget
3492 } = event;
3493 var el = ReactEditor.toDOMNode(editor, editor);
3494 // COMPAT: The event should be ignored if the focus is returning
3495 // to the editor from an embedded editable element (eg. an <input>
3496 // element inside a void node).
3497 if (relatedTarget === el) {
3498 return;
3499 }
3500 // COMPAT: The event should be ignored if the focus is moving from
3501 // the editor to inside a void node's spacer element.
3502 if (isDOMElement(relatedTarget) && relatedTarget.hasAttribute('data-slate-spacer')) {
3503 return;
3504 }
3505 // COMPAT: The event should be ignored if the focus is moving to a
3506 // non- editable section of an element that isn't a void node (eg.
3507 // a list item of the check list example).
3508 if (relatedTarget != null && isDOMNode(relatedTarget) && ReactEditor.hasDOMNode(editor, relatedTarget)) {
3509 var node = ReactEditor.toSlateNode(editor, relatedTarget);
3510 if (Element$1.isElement(node) && !editor.isVoid(node)) {
3511 return;
3512 }
3513 }
3514 // COMPAT: Safari doesn't always remove the selection even if the content-
3515 // editable element no longer has focus. Refer to:
3516 // https://stackoverflow.com/questions/12353247/force-contenteditable-div-to-stop-accepting-input-after-it-loses-focus-under-web
3517 if (IS_WEBKIT) {
3518 var domSelection = root.getSelection();
3519 domSelection === null || domSelection === void 0 || domSelection.removeAllRanges();
3520 }
3521 IS_FOCUSED.delete(editor);
3522 }, [readOnly, state.isUpdatingSelection, state.latestElement, editor, attributes.onBlur]),
3523 onClick: useCallback(event => {
3524 if (ReactEditor.hasTarget(editor, event.target) && !isEventHandled(event, attributes.onClick) && isDOMNode(event.target)) {
3525 var node = ReactEditor.toSlateNode(editor, event.target);
3526 var path = ReactEditor.findPath(editor, node);
3527 // At this time, the Slate document may be arbitrarily different,
3528 // because onClick handlers can change the document before we get here.
3529 // Therefore we must check that this path actually exists,
3530 // and that it still refers to the same node.
3531 if (!Editor.hasPath(editor, path) || Node.get(editor, path) !== node) {
3532 return;
3533 }
3534 if (event.detail === TRIPLE_CLICK && path.length >= 1) {
3535 var blockPath = path;
3536 if (!(Element$1.isElement(node) && Editor.isBlock(editor, node))) {
3537 var _block$;
3538 var block = Editor.above(editor, {
3539 match: n => Element$1.isElement(n) && Editor.isBlock(editor, n),
3540 at: path
3541 });
3542 blockPath = (_block$ = block === null || block === void 0 ? void 0 : block[1]) !== null && _block$ !== void 0 ? _block$ : path.slice(0, 1);
3543 }
3544 var range = Editor.range(editor, blockPath);
3545 Transforms.select(editor, range);
3546 return;
3547 }
3548 if (readOnly) {
3549 return;
3550 }
3551 var _start = Editor.start(editor, path);
3552 var end = Editor.end(editor, path);
3553 var startVoid = Editor.void(editor, {
3554 at: _start
3555 });
3556 var endVoid = Editor.void(editor, {
3557 at: end
3558 });
3559 if (startVoid && endVoid && Path.equals(startVoid[1], endVoid[1])) {
3560 var _range = Editor.range(editor, _start);
3561 Transforms.select(editor, _range);
3562 }
3563 }
3564 }, [editor, attributes.onClick, readOnly]),
3565 onCompositionEnd: useCallback(event => {
3566 if (ReactEditor.hasSelectableTarget(editor, event.target)) {
3567 var _androidInputManagerR3;
3568 if (ReactEditor.isComposing(editor)) {
3569 Promise.resolve().then(() => {
3570 setIsComposing(false);
3571 IS_COMPOSING.set(editor, false);
3572 });
3573 }
3574 (_androidInputManagerR3 = androidInputManagerRef.current) === null || _androidInputManagerR3 === void 0 || _androidInputManagerR3.handleCompositionEnd(event);
3575 if (isEventHandled(event, attributes.onCompositionEnd) || IS_ANDROID) {
3576 return;
3577 }
3578 // COMPAT: In Chrome, `beforeinput` events for compositions
3579 // aren't correct and never fire the "insertFromComposition"
3580 // type that we need. So instead, insert whenever a composition
3581 // ends since it will already have been committed to the DOM.
3582 if (!IS_WEBKIT && !IS_FIREFOX_LEGACY && !IS_IOS && !IS_WECHATBROWSER && !IS_UC_MOBILE && event.data) {
3583 var placeholderMarks = EDITOR_TO_PENDING_INSERTION_MARKS.get(editor);
3584 EDITOR_TO_PENDING_INSERTION_MARKS.delete(editor);
3585 // Ensure we insert text with the marks the user was actually seeing
3586 if (placeholderMarks !== undefined) {
3587 EDITOR_TO_USER_MARKS.set(editor, editor.marks);
3588 editor.marks = placeholderMarks;
3589 }
3590 Editor.insertText(editor, event.data);
3591 var userMarks = EDITOR_TO_USER_MARKS.get(editor);
3592 EDITOR_TO_USER_MARKS.delete(editor);
3593 if (userMarks !== undefined) {
3594 editor.marks = userMarks;
3595 }
3596 }
3597 }
3598 }, [attributes.onCompositionEnd, editor]),
3599 onCompositionUpdate: useCallback(event => {
3600 if (ReactEditor.hasSelectableTarget(editor, event.target) && !isEventHandled(event, attributes.onCompositionUpdate)) {
3601 if (!ReactEditor.isComposing(editor)) {
3602 setIsComposing(true);
3603 IS_COMPOSING.set(editor, true);
3604 }
3605 }
3606 }, [attributes.onCompositionUpdate, editor]),
3607 onCompositionStart: useCallback(event => {
3608 if (ReactEditor.hasSelectableTarget(editor, event.target)) {
3609 var _androidInputManagerR4;
3610 (_androidInputManagerR4 = androidInputManagerRef.current) === null || _androidInputManagerR4 === void 0 || _androidInputManagerR4.handleCompositionStart(event);
3611 if (isEventHandled(event, attributes.onCompositionStart) || IS_ANDROID) {
3612 return;
3613 }
3614 setIsComposing(true);
3615 var {
3616 selection
3617 } = editor;
3618 if (selection && Range.isExpanded(selection)) {
3619 Editor.deleteFragment(editor);
3620 return;
3621 }
3622 }
3623 }, [attributes.onCompositionStart, editor]),
3624 onCopy: useCallback(event => {
3625 if (ReactEditor.hasSelectableTarget(editor, event.target) && !isEventHandled(event, attributes.onCopy) && !isDOMEventTargetInput(event)) {
3626 event.preventDefault();
3627 ReactEditor.setFragmentData(editor, event.clipboardData, 'copy');
3628 }
3629 }, [attributes.onCopy, editor]),
3630 onCut: useCallback(event => {
3631 if (!readOnly && ReactEditor.hasSelectableTarget(editor, event.target) && !isEventHandled(event, attributes.onCut) && !isDOMEventTargetInput(event)) {
3632 event.preventDefault();
3633 ReactEditor.setFragmentData(editor, event.clipboardData, 'cut');
3634 var {
3635 selection
3636 } = editor;
3637 if (selection) {
3638 if (Range.isExpanded(selection)) {
3639 Editor.deleteFragment(editor);
3640 } else {
3641 var node = Node.parent(editor, selection.anchor.path);
3642 if (Editor.isVoid(editor, node)) {
3643 Transforms.delete(editor);
3644 }
3645 }
3646 }
3647 }
3648 }, [readOnly, editor, attributes.onCut]),
3649 onDragOver: useCallback(event => {
3650 if (ReactEditor.hasTarget(editor, event.target) && !isEventHandled(event, attributes.onDragOver)) {
3651 // Only when the target is void, call `preventDefault` to signal
3652 // that drops are allowed. Editable content is droppable by
3653 // default, and calling `preventDefault` hides the cursor.
3654 var node = ReactEditor.toSlateNode(editor, event.target);
3655 if (Element$1.isElement(node) && Editor.isVoid(editor, node)) {
3656 event.preventDefault();
3657 }
3658 }
3659 }, [attributes.onDragOver, editor]),
3660 onDragStart: useCallback(event => {
3661 if (!readOnly && ReactEditor.hasTarget(editor, event.target) && !isEventHandled(event, attributes.onDragStart)) {
3662 var node = ReactEditor.toSlateNode(editor, event.target);
3663 var path = ReactEditor.findPath(editor, node);
3664 var voidMatch = Element$1.isElement(node) && Editor.isVoid(editor, node) || Editor.void(editor, {
3665 at: path,
3666 voids: true
3667 });
3668 // If starting a drag on a void node, make sure it is selected
3669 // so that it shows up in the selection's fragment.
3670 if (voidMatch) {
3671 var range = Editor.range(editor, path);
3672 Transforms.select(editor, range);
3673 }
3674 state.isDraggingInternally = true;
3675 ReactEditor.setFragmentData(editor, event.dataTransfer, 'drag');
3676 }
3677 }, [readOnly, editor, attributes.onDragStart, state]),
3678 onDrop: useCallback(event => {
3679 if (!readOnly && ReactEditor.hasTarget(editor, event.target) && !isEventHandled(event, attributes.onDrop)) {
3680 event.preventDefault();
3681 // Keep a reference to the dragged range before updating selection
3682 var draggedRange = editor.selection;
3683 // Find the range where the drop happened
3684 var range = ReactEditor.findEventRange(editor, event);
3685 var data = event.dataTransfer;
3686 Transforms.select(editor, range);
3687 if (state.isDraggingInternally) {
3688 if (draggedRange && !Range.equals(draggedRange, range) && !Editor.void(editor, {
3689 at: range,
3690 voids: true
3691 })) {
3692 Transforms.delete(editor, {
3693 at: draggedRange
3694 });
3695 }
3696 }
3697 ReactEditor.insertData(editor, data);
3698 // When dragging from another source into the editor, it's possible
3699 // that the current editor does not have focus.
3700 if (!ReactEditor.isFocused(editor)) {
3701 ReactEditor.focus(editor);
3702 }
3703 }
3704 state.isDraggingInternally = false;
3705 }, [readOnly, editor, attributes.onDrop, state]),
3706 onDragEnd: useCallback(event => {
3707 if (!readOnly && state.isDraggingInternally && attributes.onDragEnd && ReactEditor.hasTarget(editor, event.target)) {
3708 attributes.onDragEnd(event);
3709 }
3710 // When dropping on a different droppable element than the current editor,
3711 // `onDrop` is not called. So we need to clean up in `onDragEnd` instead.
3712 // Note: `onDragEnd` is only called when `onDrop` is not called
3713 state.isDraggingInternally = false;
3714 }, [readOnly, state, attributes, editor]),
3715 onFocus: useCallback(event => {
3716 if (!readOnly && !state.isUpdatingSelection && ReactEditor.hasEditableTarget(editor, event.target) && !isEventHandled(event, attributes.onFocus)) {
3717 var el = ReactEditor.toDOMNode(editor, editor);
3718 var root = ReactEditor.findDocumentOrShadowRoot(editor);
3719 state.latestElement = root.activeElement;
3720 // COMPAT: If the editor has nested editable elements, the focus
3721 // can go to them. In Firefox, this must be prevented because it
3722 // results in issues with keyboard navigation. (2017/03/30)
3723 if (IS_FIREFOX && event.target !== el) {
3724 el.focus();
3725 return;
3726 }
3727 IS_FOCUSED.set(editor, true);
3728 }
3729 }, [readOnly, state, editor, attributes.onFocus]),
3730 onKeyDown: useCallback(event => {
3731 if (!readOnly && ReactEditor.hasEditableTarget(editor, event.target)) {
3732 var _androidInputManagerR5;
3733 (_androidInputManagerR5 = androidInputManagerRef.current) === null || _androidInputManagerR5 === void 0 || _androidInputManagerR5.handleKeyDown(event);
3734 var {
3735 nativeEvent
3736 } = event;
3737 // COMPAT: The composition end event isn't fired reliably in all browsers,
3738 // so we sometimes might end up stuck in a composition state even though we
3739 // aren't composing any more.
3740 if (ReactEditor.isComposing(editor) && nativeEvent.isComposing === false) {
3741 IS_COMPOSING.set(editor, false);
3742 setIsComposing(false);
3743 }
3744 if (isEventHandled(event, attributes.onKeyDown) || ReactEditor.isComposing(editor)) {
3745 return;
3746 }
3747 var {
3748 selection
3749 } = editor;
3750 var element = editor.children[selection !== null ? selection.focus.path[0] : 0];
3751 var isRTL = getDirection(Node.string(element)) === 'rtl';
3752 // COMPAT: Since we prevent the default behavior on
3753 // `beforeinput` events, the browser doesn't think there's ever
3754 // any history stack to undo or redo, so we have to manage these
3755 // hotkeys ourselves. (2019/11/06)
3756 if (Hotkeys.isRedo(nativeEvent)) {
3757 event.preventDefault();
3758 var maybeHistoryEditor = editor;
3759 if (typeof maybeHistoryEditor.redo === 'function') {
3760 maybeHistoryEditor.redo();
3761 }
3762 return;
3763 }
3764 if (Hotkeys.isUndo(nativeEvent)) {
3765 event.preventDefault();
3766 var _maybeHistoryEditor = editor;
3767 if (typeof _maybeHistoryEditor.undo === 'function') {
3768 _maybeHistoryEditor.undo();
3769 }
3770 return;
3771 }
3772 // COMPAT: Certain browsers don't handle the selection updates
3773 // properly. In Chrome, the selection isn't properly extended.
3774 // And in Firefox, the selection isn't properly collapsed.
3775 // (2017/10/17)
3776 if (Hotkeys.isMoveLineBackward(nativeEvent)) {
3777 event.preventDefault();
3778 Transforms.move(editor, {
3779 unit: 'line',
3780 reverse: true
3781 });
3782 return;
3783 }
3784 if (Hotkeys.isMoveLineForward(nativeEvent)) {
3785 event.preventDefault();
3786 Transforms.move(editor, {
3787 unit: 'line'
3788 });
3789 return;
3790 }
3791 if (Hotkeys.isExtendLineBackward(nativeEvent)) {
3792 event.preventDefault();
3793 Transforms.move(editor, {
3794 unit: 'line',
3795 edge: 'focus',
3796 reverse: true
3797 });
3798 return;
3799 }
3800 if (Hotkeys.isExtendLineForward(nativeEvent)) {
3801 event.preventDefault();
3802 Transforms.move(editor, {
3803 unit: 'line',
3804 edge: 'focus'
3805 });
3806 return;
3807 }
3808 // COMPAT: If a void node is selected, or a zero-width text node
3809 // adjacent to an inline is selected, we need to handle these
3810 // hotkeys manually because browsers won't be able to skip over
3811 // the void node with the zero-width space not being an empty
3812 // string.
3813 if (Hotkeys.isMoveBackward(nativeEvent)) {
3814 event.preventDefault();
3815 if (selection && Range.isCollapsed(selection)) {
3816 Transforms.move(editor, {
3817 reverse: !isRTL
3818 });
3819 } else {
3820 Transforms.collapse(editor, {
3821 edge: isRTL ? 'end' : 'start'
3822 });
3823 }
3824 return;
3825 }
3826 if (Hotkeys.isMoveForward(nativeEvent)) {
3827 event.preventDefault();
3828 if (selection && Range.isCollapsed(selection)) {
3829 Transforms.move(editor, {
3830 reverse: isRTL
3831 });
3832 } else {
3833 Transforms.collapse(editor, {
3834 edge: isRTL ? 'start' : 'end'
3835 });
3836 }
3837 return;
3838 }
3839 if (Hotkeys.isMoveWordBackward(nativeEvent)) {
3840 event.preventDefault();
3841 if (selection && Range.isExpanded(selection)) {
3842 Transforms.collapse(editor, {
3843 edge: 'focus'
3844 });
3845 }
3846 Transforms.move(editor, {
3847 unit: 'word',
3848 reverse: !isRTL
3849 });
3850 return;
3851 }
3852 if (Hotkeys.isMoveWordForward(nativeEvent)) {
3853 event.preventDefault();
3854 if (selection && Range.isExpanded(selection)) {
3855 Transforms.collapse(editor, {
3856 edge: 'focus'
3857 });
3858 }
3859 Transforms.move(editor, {
3860 unit: 'word',
3861 reverse: isRTL
3862 });
3863 return;
3864 }
3865 // COMPAT: Certain browsers don't support the `beforeinput` event, so we
3866 // fall back to guessing at the input intention for hotkeys.
3867 // COMPAT: In iOS, some of these hotkeys are handled in the
3868 if (!HAS_BEFORE_INPUT_SUPPORT) {
3869 // We don't have a core behavior for these, but they change the
3870 // DOM if we don't prevent them, so we have to.
3871 if (Hotkeys.isBold(nativeEvent) || Hotkeys.isItalic(nativeEvent) || Hotkeys.isTransposeCharacter(nativeEvent)) {
3872 event.preventDefault();
3873 return;
3874 }
3875 if (Hotkeys.isSoftBreak(nativeEvent)) {
3876 event.preventDefault();
3877 Editor.insertSoftBreak(editor);
3878 return;
3879 }
3880 if (Hotkeys.isSplitBlock(nativeEvent)) {
3881 event.preventDefault();
3882 Editor.insertBreak(editor);
3883 return;
3884 }
3885 if (Hotkeys.isDeleteBackward(nativeEvent)) {
3886 event.preventDefault();
3887 if (selection && Range.isExpanded(selection)) {
3888 Editor.deleteFragment(editor, {
3889 direction: 'backward'
3890 });
3891 } else {
3892 Editor.deleteBackward(editor);
3893 }
3894 return;
3895 }
3896 if (Hotkeys.isDeleteForward(nativeEvent)) {
3897 event.preventDefault();
3898 if (selection && Range.isExpanded(selection)) {
3899 Editor.deleteFragment(editor, {
3900 direction: 'forward'
3901 });
3902 } else {
3903 Editor.deleteForward(editor);
3904 }
3905 return;
3906 }
3907 if (Hotkeys.isDeleteLineBackward(nativeEvent)) {
3908 event.preventDefault();
3909 if (selection && Range.isExpanded(selection)) {
3910 Editor.deleteFragment(editor, {
3911 direction: 'backward'
3912 });
3913 } else {
3914 Editor.deleteBackward(editor, {
3915 unit: 'line'
3916 });
3917 }
3918 return;
3919 }
3920 if (Hotkeys.isDeleteLineForward(nativeEvent)) {
3921 event.preventDefault();
3922 if (selection && Range.isExpanded(selection)) {
3923 Editor.deleteFragment(editor, {
3924 direction: 'forward'
3925 });
3926 } else {
3927 Editor.deleteForward(editor, {
3928 unit: 'line'
3929 });
3930 }
3931 return;
3932 }
3933 if (Hotkeys.isDeleteWordBackward(nativeEvent)) {
3934 event.preventDefault();
3935 if (selection && Range.isExpanded(selection)) {
3936 Editor.deleteFragment(editor, {
3937 direction: 'backward'
3938 });
3939 } else {
3940 Editor.deleteBackward(editor, {
3941 unit: 'word'
3942 });
3943 }
3944 return;
3945 }
3946 if (Hotkeys.isDeleteWordForward(nativeEvent)) {
3947 event.preventDefault();
3948 if (selection && Range.isExpanded(selection)) {
3949 Editor.deleteFragment(editor, {
3950 direction: 'forward'
3951 });
3952 } else {
3953 Editor.deleteForward(editor, {
3954 unit: 'word'
3955 });
3956 }
3957 return;
3958 }
3959 } else {
3960 if (IS_CHROME || IS_WEBKIT) {
3961 // COMPAT: Chrome and Safari support `beforeinput` event but do not fire
3962 // an event when deleting backwards in a selected void inline node
3963 if (selection && (Hotkeys.isDeleteBackward(nativeEvent) || Hotkeys.isDeleteForward(nativeEvent)) && Range.isCollapsed(selection)) {
3964 var currentNode = Node.parent(editor, selection.anchor.path);
3965 if (Element$1.isElement(currentNode) && Editor.isVoid(editor, currentNode) && (Editor.isInline(editor, currentNode) || Editor.isBlock(editor, currentNode))) {
3966 event.preventDefault();
3967 Editor.deleteBackward(editor, {
3968 unit: 'block'
3969 });
3970 return;
3971 }
3972 }
3973 }
3974 }
3975 }
3976 }, [readOnly, editor, attributes.onKeyDown]),
3977 onPaste: useCallback(event => {
3978 if (!readOnly && ReactEditor.hasEditableTarget(editor, event.target) && !isEventHandled(event, attributes.onPaste)) {
3979 // COMPAT: Certain browsers don't support the `beforeinput` event, so we
3980 // fall back to React's `onPaste` here instead.
3981 // COMPAT: Firefox, Chrome and Safari don't emit `beforeinput` events
3982 // when "paste without formatting" is used, so fallback. (2020/02/20)
3983 // COMPAT: Safari InputEvents generated by pasting won't include
3984 // application/x-slate-fragment items, so use the
3985 // ClipboardEvent here. (2023/03/15)
3986 if (!HAS_BEFORE_INPUT_SUPPORT || isPlainTextOnlyPaste(event.nativeEvent) || IS_WEBKIT) {
3987 event.preventDefault();
3988 ReactEditor.insertData(editor, event.clipboardData);
3989 }
3990 }
3991 }, [readOnly, editor, attributes.onPaste])
3992 }), /*#__PURE__*/React.createElement(Children, {
3993 decorations: decorations,
3994 node: editor,
3995 renderElement: renderElement,
3996 renderPlaceholder: renderPlaceholder,
3997 renderLeaf: renderLeaf,
3998 selection: editor.selection
3999 })))));
4000};
4001/**
4002 * The default placeholder element
4003 */
4004var DefaultPlaceholder = _ref => {
4005 var {
4006 attributes,
4007 children
4008 } = _ref;
4009 return (
4010 /*#__PURE__*/
4011 // COMPAT: Artificially add a line-break to the end on the placeholder element
4012 // to prevent Android IMEs to pick up its content in autocorrect and to auto-capitalize the first letter
4013 React.createElement("span", _objectSpread$1({}, attributes), children, IS_ANDROID && /*#__PURE__*/React.createElement("br", null))
4014 );
4015};
4016/**
4017 * A default memoized decorate function.
4018 */
4019var defaultDecorate = () => [];
4020/**
4021 * A default implement to scroll dom range into view.
4022 */
4023var defaultScrollSelectionIntoView = (editor, domRange) => {
4024 // This was affecting the selection of multiple blocks and dragging behavior,
4025 // so enabled only if the selection has been collapsed.
4026 if (domRange.getBoundingClientRect && (!editor.selection || editor.selection && Range.isCollapsed(editor.selection))) {
4027 var leafEl = domRange.startContainer.parentElement;
4028 leafEl.getBoundingClientRect = domRange.getBoundingClientRect.bind(domRange);
4029 scrollIntoView(leafEl, {
4030 scrollMode: 'if-needed'
4031 });
4032 // @ts-expect-error an unorthodox delete D:
4033 delete leafEl.getBoundingClientRect;
4034 }
4035};
4036/**
4037 * Check if an event is overrided by a handler.
4038 */
4039var isEventHandled = (event, handler) => {
4040 if (!handler) {
4041 return false;
4042 }
4043 // The custom event handler may return a boolean to specify whether the event
4044 // shall be treated as being handled or not.
4045 var shouldTreatEventAsHandled = handler(event);
4046 if (shouldTreatEventAsHandled != null) {
4047 return shouldTreatEventAsHandled;
4048 }
4049 return event.isDefaultPrevented() || event.isPropagationStopped();
4050};
4051/**
4052 * Check if the event's target is an input element
4053 */
4054var isDOMEventTargetInput = event => {
4055 return isDOMNode(event.target) && (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement);
4056};
4057/**
4058 * Check if a DOM event is overrided by a handler.
4059 */
4060var isDOMEventHandled = (event, handler) => {
4061 if (!handler) {
4062 return false;
4063 }
4064 // The custom event handler may return a boolean to specify whether the event
4065 // shall be treated as being handled or not.
4066 var shouldTreatEventAsHandled = handler(event);
4067 if (shouldTreatEventAsHandled != null) {
4068 return shouldTreatEventAsHandled;
4069 }
4070 return event.defaultPrevented;
4071};
4072
4073/**
4074 * A React context for sharing the `focused` state of the editor.
4075 */
4076var FocusedContext = /*#__PURE__*/createContext(false);
4077/**
4078 * Get the current `focused` state of the editor.
4079 */
4080var useFocused = () => {
4081 return useContext(FocusedContext);
4082};
4083
4084function isError(error) {
4085 return error instanceof Error;
4086}
4087/**
4088 * A React context for sharing the editor selector context in a way to control rerenders
4089 */
4090var SlateSelectorContext = /*#__PURE__*/createContext({});
4091var refEquality = (a, b) => a === b;
4092/**
4093 * use redux style selectors to prevent rerendering on every keystroke.
4094 * Bear in mind rerendering can only prevented if the returned value is a value type or for reference types (e.g. objects and arrays) add a custom equality function.
4095 *
4096 * Example:
4097 * ```
4098 * const isSelectionActive = useSlateSelector(editor => Boolean(editor.selection));
4099 * ```
4100 */
4101function useSlateSelector(selector) {
4102 var equalityFn = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : refEquality;
4103 var [, forceRender] = useReducer(s => s + 1, 0);
4104 var context = useContext(SlateSelectorContext);
4105 if (!context) {
4106 throw new Error("The `useSlateSelector` hook must be used inside the <Slate> component's context.");
4107 }
4108 var {
4109 getSlate,
4110 addEventListener
4111 } = context;
4112 var latestSubscriptionCallbackError = useRef();
4113 var latestSelector = useRef(() => null);
4114 var latestSelectedState = useRef(null);
4115 var selectedState;
4116 try {
4117 if (selector !== latestSelector.current || latestSubscriptionCallbackError.current) {
4118 selectedState = selector(getSlate());
4119 } else {
4120 selectedState = latestSelectedState.current;
4121 }
4122 } catch (err) {
4123 if (latestSubscriptionCallbackError.current && isError(err)) {
4124 err.message += "\nThe error may be correlated with this previous error:\n".concat(latestSubscriptionCallbackError.current.stack, "\n\n");
4125 }
4126 throw err;
4127 }
4128 useIsomorphicLayoutEffect(() => {
4129 latestSelector.current = selector;
4130 latestSelectedState.current = selectedState;
4131 latestSubscriptionCallbackError.current = undefined;
4132 });
4133 useIsomorphicLayoutEffect(() => {
4134 function checkForUpdates() {
4135 try {
4136 var newSelectedState = latestSelector.current(getSlate());
4137 if (equalityFn(newSelectedState, latestSelectedState.current)) {
4138 return;
4139 }
4140 latestSelectedState.current = newSelectedState;
4141 } catch (err) {
4142 // we ignore all errors here, since when the component
4143 // is re-rendered, the selectors are called again, and
4144 // will throw again, if neither props nor store state
4145 // changed
4146 if (err instanceof Error) {
4147 latestSubscriptionCallbackError.current = err;
4148 } else {
4149 latestSubscriptionCallbackError.current = new Error(String(err));
4150 }
4151 }
4152 forceRender();
4153 }
4154 var unsubscribe = addEventListener(checkForUpdates);
4155 checkForUpdates();
4156 return () => unsubscribe();
4157 },
4158 // don't rerender on equalityFn change since we want to be able to define it inline
4159 [addEventListener, getSlate]);
4160 return selectedState;
4161}
4162/**
4163 * Create selector context with editor updating on every editor change
4164 */
4165function useSelectorContext(editor) {
4166 var eventListeners = useRef([]).current;
4167 var slateRef = useRef({
4168 editor
4169 }).current;
4170 var onChange = useCallback(editor => {
4171 slateRef.editor = editor;
4172 eventListeners.forEach(listener => listener(editor));
4173 }, [eventListeners, slateRef]);
4174 var selectorContext = useMemo(() => {
4175 return {
4176 getSlate: () => slateRef.editor,
4177 addEventListener: callback => {
4178 eventListeners.push(callback);
4179 return () => {
4180 eventListeners.splice(eventListeners.indexOf(callback), 1);
4181 };
4182 }
4183 };
4184 }, [eventListeners, slateRef]);
4185 return {
4186 selectorContext,
4187 onChange
4188 };
4189}
4190
4191var _excluded = ["editor", "children", "onChange", "onSelectionChange", "onValueChange", "initialValue"];
4192/**
4193 * A wrapper around the provider to handle `onChange` events, because the editor
4194 * is a mutable singleton so it won't ever register as "changed" otherwise.
4195 */
4196var Slate = props => {
4197 var {
4198 editor,
4199 children,
4200 onChange,
4201 onSelectionChange,
4202 onValueChange,
4203 initialValue
4204 } = props,
4205 rest = _objectWithoutProperties(props, _excluded);
4206 var [context, setContext] = React.useState(() => {
4207 if (!Node.isNodeList(initialValue)) {
4208 throw new Error("[Slate] initialValue is invalid! Expected a list of elements but got: ".concat(Scrubber.stringify(initialValue)));
4209 }
4210 if (!Editor.isEditor(editor)) {
4211 throw new Error("[Slate] editor is invalid! You passed: ".concat(Scrubber.stringify(editor)));
4212 }
4213 editor.children = initialValue;
4214 Object.assign(editor, rest);
4215 return {
4216 v: 0,
4217 editor
4218 };
4219 });
4220 var {
4221 selectorContext,
4222 onChange: handleSelectorChange
4223 } = useSelectorContext(editor);
4224 var onContextChange = useCallback(options => {
4225 var _options$operation;
4226 if (onChange) {
4227 onChange(editor.children);
4228 }
4229 switch (options === null || options === void 0 || (_options$operation = options.operation) === null || _options$operation === void 0 ? void 0 : _options$operation.type) {
4230 case 'set_selection':
4231 onSelectionChange === null || onSelectionChange === void 0 || onSelectionChange(editor.selection);
4232 break;
4233 default:
4234 onValueChange === null || onValueChange === void 0 || onValueChange(editor.children);
4235 }
4236 setContext(prevContext => ({
4237 v: prevContext.v + 1,
4238 editor
4239 }));
4240 handleSelectorChange(editor);
4241 }, [editor, handleSelectorChange, onChange, onSelectionChange, onValueChange]);
4242 useEffect(() => {
4243 EDITOR_TO_ON_CHANGE.set(editor, onContextChange);
4244 return () => {
4245 EDITOR_TO_ON_CHANGE.set(editor, () => {});
4246 };
4247 }, [editor, onContextChange]);
4248 var [isFocused, setIsFocused] = useState(ReactEditor.isFocused(editor));
4249 useEffect(() => {
4250 setIsFocused(ReactEditor.isFocused(editor));
4251 }, [editor]);
4252 useIsomorphicLayoutEffect(() => {
4253 var fn = () => setIsFocused(ReactEditor.isFocused(editor));
4254 if (REACT_MAJOR_VERSION >= 17) {
4255 // In React >= 17 onFocus and onBlur listen to the focusin and focusout events during the bubbling phase.
4256 // Therefore in order for <Editable />'s handlers to run first, which is necessary for ReactEditor.isFocused(editor)
4257 // to return the correct value, we have to listen to the focusin and focusout events without useCapture here.
4258 document.addEventListener('focusin', fn);
4259 document.addEventListener('focusout', fn);
4260 return () => {
4261 document.removeEventListener('focusin', fn);
4262 document.removeEventListener('focusout', fn);
4263 };
4264 } else {
4265 document.addEventListener('focus', fn, true);
4266 document.addEventListener('blur', fn, true);
4267 return () => {
4268 document.removeEventListener('focus', fn, true);
4269 document.removeEventListener('blur', fn, true);
4270 };
4271 }
4272 }, []);
4273 return /*#__PURE__*/React.createElement(SlateSelectorContext.Provider, {
4274 value: selectorContext
4275 }, /*#__PURE__*/React.createElement(SlateContext.Provider, {
4276 value: context
4277 }, /*#__PURE__*/React.createElement(EditorContext.Provider, {
4278 value: context.editor
4279 }, /*#__PURE__*/React.createElement(FocusedContext.Provider, {
4280 value: isFocused
4281 }, children))));
4282};
4283
4284/**
4285 * Get the current editor object from the React context.
4286 * @deprecated Use useSlateStatic instead.
4287 */
4288var useEditor = () => {
4289 var editor = useContext(EditorContext);
4290 if (!editor) {
4291 throw new Error("The `useEditor` hook must be used inside the <Slate> component's context.");
4292 }
4293 return editor;
4294};
4295
4296/**
4297 * Get the current slate selection.
4298 * Only triggers a rerender when the selection actually changes
4299 */
4300var useSlateSelection = () => {
4301 return useSlateSelector(editor => editor.selection, isSelectionEqual);
4302};
4303var isSelectionEqual = (a, b) => {
4304 if (!a && !b) return true;
4305 if (!a || !b) return false;
4306 return Range.equals(a, b);
4307};
4308
4309/**
4310 * Utilities for single-line deletion
4311 */
4312var doRectsIntersect = (rect, compareRect) => {
4313 var middle = (compareRect.top + compareRect.bottom) / 2;
4314 return rect.top <= middle && rect.bottom >= middle;
4315};
4316var areRangesSameLine = (editor, range1, range2) => {
4317 var rect1 = ReactEditor.toDOMRange(editor, range1).getBoundingClientRect();
4318 var rect2 = ReactEditor.toDOMRange(editor, range2).getBoundingClientRect();
4319 return doRectsIntersect(rect1, rect2) && doRectsIntersect(rect2, rect1);
4320};
4321/**
4322 * A helper utility that returns the end portion of a `Range`
4323 * which is located on a single line.
4324 *
4325 * @param {Editor} editor The editor object to compare against
4326 * @param {Range} parentRange The parent range to compare against
4327 * @returns {Range} A valid portion of the parentRange which is one a single line
4328 */
4329var findCurrentLineRange = (editor, parentRange) => {
4330 var parentRangeBoundary = Editor.range(editor, Range.end(parentRange));
4331 var positions = Array.from(Editor.positions(editor, {
4332 at: parentRange
4333 }));
4334 var left = 0;
4335 var right = positions.length;
4336 var middle = Math.floor(right / 2);
4337 if (areRangesSameLine(editor, Editor.range(editor, positions[left]), parentRangeBoundary)) {
4338 return Editor.range(editor, positions[left], parentRangeBoundary);
4339 }
4340 if (positions.length < 2) {
4341 return Editor.range(editor, positions[positions.length - 1], parentRangeBoundary);
4342 }
4343 while (middle !== positions.length && middle !== left) {
4344 if (areRangesSameLine(editor, Editor.range(editor, positions[middle]), parentRangeBoundary)) {
4345 right = middle;
4346 } else {
4347 left = middle;
4348 }
4349 middle = Math.floor((left + right) / 2);
4350 }
4351 return Editor.range(editor, positions[right], parentRangeBoundary);
4352};
4353
4354function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
4355function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4356/**
4357 * `withReact` adds React and DOM specific behaviors to the editor.
4358 *
4359 * If you are using TypeScript, you must extend Slate's CustomTypes to use
4360 * this plugin.
4361 *
4362 * See https://docs.slatejs.org/concepts/11-typescript to learn how.
4363 */
4364var withReact = function withReact(editor) {
4365 var clipboardFormatKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'x-slate-fragment';
4366 var e = editor;
4367 var {
4368 apply,
4369 onChange,
4370 deleteBackward,
4371 addMark,
4372 removeMark
4373 } = e;
4374 // The WeakMap which maps a key to a specific HTMLElement must be scoped to the editor instance to
4375 // avoid collisions between editors in the DOM that share the same value.
4376 EDITOR_TO_KEY_TO_ELEMENT.set(e, new WeakMap());
4377 e.addMark = (key, value) => {
4378 var _EDITOR_TO_SCHEDULE_F, _EDITOR_TO_PENDING_DI;
4379 (_EDITOR_TO_SCHEDULE_F = EDITOR_TO_SCHEDULE_FLUSH.get(e)) === null || _EDITOR_TO_SCHEDULE_F === void 0 || _EDITOR_TO_SCHEDULE_F();
4380 if (!EDITOR_TO_PENDING_INSERTION_MARKS.get(e) && (_EDITOR_TO_PENDING_DI = EDITOR_TO_PENDING_DIFFS.get(e)) !== null && _EDITOR_TO_PENDING_DI !== void 0 && _EDITOR_TO_PENDING_DI.length) {
4381 // Ensure the current pending diffs originating from changes before the addMark
4382 // are applied with the current formatting
4383 EDITOR_TO_PENDING_INSERTION_MARKS.set(e, null);
4384 }
4385 EDITOR_TO_USER_MARKS.delete(e);
4386 addMark(key, value);
4387 };
4388 e.removeMark = key => {
4389 var _EDITOR_TO_PENDING_DI2;
4390 if (!EDITOR_TO_PENDING_INSERTION_MARKS.get(e) && (_EDITOR_TO_PENDING_DI2 = EDITOR_TO_PENDING_DIFFS.get(e)) !== null && _EDITOR_TO_PENDING_DI2 !== void 0 && _EDITOR_TO_PENDING_DI2.length) {
4391 // Ensure the current pending diffs originating from changes before the addMark
4392 // are applied with the current formatting
4393 EDITOR_TO_PENDING_INSERTION_MARKS.set(e, null);
4394 }
4395 EDITOR_TO_USER_MARKS.delete(e);
4396 removeMark(key);
4397 };
4398 e.deleteBackward = unit => {
4399 if (unit !== 'line') {
4400 return deleteBackward(unit);
4401 }
4402 if (e.selection && Range.isCollapsed(e.selection)) {
4403 var parentBlockEntry = Editor.above(e, {
4404 match: n => Element$1.isElement(n) && Editor.isBlock(e, n),
4405 at: e.selection
4406 });
4407 if (parentBlockEntry) {
4408 var [, parentBlockPath] = parentBlockEntry;
4409 var parentElementRange = Editor.range(e, parentBlockPath, e.selection.anchor);
4410 var currentLineRange = findCurrentLineRange(e, parentElementRange);
4411 if (!Range.isCollapsed(currentLineRange)) {
4412 Transforms.delete(e, {
4413 at: currentLineRange
4414 });
4415 }
4416 }
4417 }
4418 };
4419 // This attempts to reset the NODE_TO_KEY entry to the correct value
4420 // as apply() changes the object reference and hence invalidates the NODE_TO_KEY entry
4421 e.apply = op => {
4422 var matches = [];
4423 var pathRefMatches = [];
4424 var pendingDiffs = EDITOR_TO_PENDING_DIFFS.get(e);
4425 if (pendingDiffs !== null && pendingDiffs !== void 0 && pendingDiffs.length) {
4426 var transformed = pendingDiffs.map(textDiff => transformTextDiff(textDiff, op)).filter(Boolean);
4427 EDITOR_TO_PENDING_DIFFS.set(e, transformed);
4428 }
4429 var pendingSelection = EDITOR_TO_PENDING_SELECTION.get(e);
4430 if (pendingSelection) {
4431 EDITOR_TO_PENDING_SELECTION.set(e, transformPendingRange(e, pendingSelection, op));
4432 }
4433 var pendingAction = EDITOR_TO_PENDING_ACTION.get(e);
4434 if (pendingAction !== null && pendingAction !== void 0 && pendingAction.at) {
4435 var at = Point.isPoint(pendingAction === null || pendingAction === void 0 ? void 0 : pendingAction.at) ? transformPendingPoint(e, pendingAction.at, op) : transformPendingRange(e, pendingAction.at, op);
4436 EDITOR_TO_PENDING_ACTION.set(e, at ? _objectSpread(_objectSpread({}, pendingAction), {}, {
4437 at
4438 }) : null);
4439 }
4440 switch (op.type) {
4441 case 'insert_text':
4442 case 'remove_text':
4443 case 'set_node':
4444 case 'split_node':
4445 {
4446 matches.push(...getMatches(e, op.path));
4447 break;
4448 }
4449 case 'set_selection':
4450 {
4451 var _EDITOR_TO_USER_SELEC;
4452 // Selection was manually set, don't restore the user selection after the change.
4453 (_EDITOR_TO_USER_SELEC = EDITOR_TO_USER_SELECTION.get(e)) === null || _EDITOR_TO_USER_SELEC === void 0 || _EDITOR_TO_USER_SELEC.unref();
4454 EDITOR_TO_USER_SELECTION.delete(e);
4455 break;
4456 }
4457 case 'insert_node':
4458 case 'remove_node':
4459 {
4460 matches.push(...getMatches(e, Path.parent(op.path)));
4461 break;
4462 }
4463 case 'merge_node':
4464 {
4465 var prevPath = Path.previous(op.path);
4466 matches.push(...getMatches(e, prevPath));
4467 break;
4468 }
4469 case 'move_node':
4470 {
4471 var commonPath = Path.common(Path.parent(op.path), Path.parent(op.newPath));
4472 matches.push(...getMatches(e, commonPath));
4473 var changedPath;
4474 if (Path.isBefore(op.path, op.newPath)) {
4475 matches.push(...getMatches(e, Path.parent(op.path)));
4476 changedPath = op.newPath;
4477 } else {
4478 matches.push(...getMatches(e, Path.parent(op.newPath)));
4479 changedPath = op.path;
4480 }
4481 var changedNode = Node.get(editor, Path.parent(changedPath));
4482 var changedNodeKey = ReactEditor.findKey(e, changedNode);
4483 var changedPathRef = Editor.pathRef(e, Path.parent(changedPath));
4484 pathRefMatches.push([changedPathRef, changedNodeKey]);
4485 break;
4486 }
4487 }
4488 apply(op);
4489 for (var [path, key] of matches) {
4490 var [node] = Editor.node(e, path);
4491 NODE_TO_KEY.set(node, key);
4492 }
4493 for (var [pathRef, _key] of pathRefMatches) {
4494 if (pathRef.current) {
4495 var [_node] = Editor.node(e, pathRef.current);
4496 NODE_TO_KEY.set(_node, _key);
4497 }
4498 }
4499 };
4500 e.setFragmentData = data => {
4501 var {
4502 selection
4503 } = e;
4504 if (!selection) {
4505 return;
4506 }
4507 var [start, end] = Range.edges(selection);
4508 var startVoid = Editor.void(e, {
4509 at: start.path
4510 });
4511 var endVoid = Editor.void(e, {
4512 at: end.path
4513 });
4514 if (Range.isCollapsed(selection) && !startVoid) {
4515 return;
4516 }
4517 // Create a fake selection so that we can add a Base64-encoded copy of the
4518 // fragment to the HTML, to decode on future pastes.
4519 var domRange = ReactEditor.toDOMRange(e, selection);
4520 var contents = domRange.cloneContents();
4521 var attach = contents.childNodes[0];
4522 // Make sure attach is non-empty, since empty nodes will not get copied.
4523 contents.childNodes.forEach(node => {
4524 if (node.textContent && node.textContent.trim() !== '') {
4525 attach = node;
4526 }
4527 });
4528 // COMPAT: If the end node is a void node, we need to move the end of the
4529 // range from the void node's spacer span, to the end of the void node's
4530 // content, since the spacer is before void's content in the DOM.
4531 if (endVoid) {
4532 var [voidNode] = endVoid;
4533 var r = domRange.cloneRange();
4534 var domNode = ReactEditor.toDOMNode(e, voidNode);
4535 r.setEndAfter(domNode);
4536 contents = r.cloneContents();
4537 }
4538 // COMPAT: If the start node is a void node, we need to attach the encoded
4539 // fragment to the void node's content node instead of the spacer, because
4540 // attaching it to empty `<div>/<span>` nodes will end up having it erased by
4541 // most browsers. (2018/04/27)
4542 if (startVoid) {
4543 attach = contents.querySelector('[data-slate-spacer]');
4544 }
4545 // Remove any zero-width space spans from the cloned DOM so that they don't
4546 // show up elsewhere when pasted.
4547 Array.from(contents.querySelectorAll('[data-slate-zero-width]')).forEach(zw => {
4548 var isNewline = zw.getAttribute('data-slate-zero-width') === 'n';
4549 zw.textContent = isNewline ? '\n' : '';
4550 });
4551 // Set a `data-slate-fragment` attribute on a non-empty node, so it shows up
4552 // in the HTML, and can be used for intra-Slate pasting. If it's a text
4553 // node, wrap it in a `<span>` so we have something to set an attribute on.
4554 if (isDOMText(attach)) {
4555 var span = attach.ownerDocument.createElement('span');
4556 // COMPAT: In Chrome and Safari, if we don't add the `white-space` style
4557 // then leading and trailing spaces will be ignored. (2017/09/21)
4558 span.style.whiteSpace = 'pre';
4559 span.appendChild(attach);
4560 contents.appendChild(span);
4561 attach = span;
4562 }
4563 var fragment = e.getFragment();
4564 var string = JSON.stringify(fragment);
4565 var encoded = window.btoa(encodeURIComponent(string));
4566 attach.setAttribute('data-slate-fragment', encoded);
4567 data.setData("application/".concat(clipboardFormatKey), encoded);
4568 // Add the content to a <div> so that we can get its inner HTML.
4569 var div = contents.ownerDocument.createElement('div');
4570 div.appendChild(contents);
4571 div.setAttribute('hidden', 'true');
4572 contents.ownerDocument.body.appendChild(div);
4573 data.setData('text/html', div.innerHTML);
4574 data.setData('text/plain', getPlainText(div));
4575 contents.ownerDocument.body.removeChild(div);
4576 return data;
4577 };
4578 e.insertData = data => {
4579 if (!e.insertFragmentData(data)) {
4580 e.insertTextData(data);
4581 }
4582 };
4583 e.insertFragmentData = data => {
4584 /**
4585 * Checking copied fragment from application/x-slate-fragment or data-slate-fragment
4586 */
4587 var fragment = data.getData("application/".concat(clipboardFormatKey)) || getSlateFragmentAttribute(data);
4588 if (fragment) {
4589 var decoded = decodeURIComponent(window.atob(fragment));
4590 var parsed = JSON.parse(decoded);
4591 e.insertFragment(parsed);
4592 return true;
4593 }
4594 return false;
4595 };
4596 e.insertTextData = data => {
4597 var text = data.getData('text/plain');
4598 if (text) {
4599 var lines = text.split(/\r\n|\r|\n/);
4600 var split = false;
4601 for (var line of lines) {
4602 if (split) {
4603 Transforms.splitNodes(e, {
4604 always: true
4605 });
4606 }
4607 e.insertText(line);
4608 split = true;
4609 }
4610 return true;
4611 }
4612 return false;
4613 };
4614 e.onChange = options => {
4615 // COMPAT: React < 18 doesn't batch `setState` hook calls, which means
4616 // that the children and selection can get out of sync for one render
4617 // pass. So we have to use this unstable API to ensure it batches them.
4618 // (2019/12/03)
4619 // https://github.com/facebook/react/issues/14259#issuecomment-439702367
4620 var maybeBatchUpdates = REACT_MAJOR_VERSION < 18 ? ReactDOM.unstable_batchedUpdates : callback => callback();
4621 maybeBatchUpdates(() => {
4622 var onContextChange = EDITOR_TO_ON_CHANGE.get(e);
4623 if (onContextChange) {
4624 onContextChange(options);
4625 }
4626 onChange(options);
4627 });
4628 };
4629 return e;
4630};
4631var getMatches = (e, path) => {
4632 var matches = [];
4633 for (var [n, p] of Editor.levels(e, {
4634 at: path
4635 })) {
4636 var key = ReactEditor.findKey(e, n);
4637 matches.push([p, key]);
4638 }
4639 return matches;
4640};
4641
4642export { DefaultElement, DefaultLeaf, DefaultPlaceholder, Editable, ReactEditor, Slate, useEditor, useFocused, useReadOnly, useSelected, useSlate, useSlateSelection, useSlateSelector, useSlateStatic, useSlateWithV, withReact };
4643//# sourceMappingURL=index.es.js.map