UNPKG

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