slate-react 0.56.1 → 0.57.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.es.js CHANGED
@@ -1,64 +1,75 @@
1
- import React, { useLayoutEffect, useEffect, useRef, createContext, useContext, useMemo, useCallback, useState } from 'react';
2
- import { Path, Node as Node$1, Editor, Text as Text$1, Range, Element as Element$1, Command } from 'slate';
3
- import debounce from 'debounce';
1
+ import React, { createContext, useContext, useState, useMemo, useCallback, useEffect, useLayoutEffect, useRef } from 'react';
2
+ import { Editor, Transforms, Range, Path, Node as Node$1, Text as Text$1, Element as Element$1 } from 'slate';
3
+ import throttle from 'lodash/throttle';
4
4
  import scrollIntoView from 'scroll-into-view-if-needed';
5
5
  import getDirection from 'direction';
6
- import { isKeyHotkey } from 'is-hotkey';
7
6
  import ReactDOM from 'react-dom';
7
+ import { isKeyHotkey } from 'is-hotkey';
8
+
9
+ function _defineProperty(obj, key, value) {
10
+ if (key in obj) {
11
+ Object.defineProperty(obj, key, {
12
+ value: value,
13
+ enumerable: true,
14
+ configurable: true,
15
+ writable: true
16
+ });
17
+ } else {
18
+ obj[key] = value;
19
+ }
20
+
21
+ return obj;
22
+ }
23
+
24
+ function _objectWithoutPropertiesLoose(source, excluded) {
25
+ if (source == null) return {};
26
+ var target = {};
27
+ var sourceKeys = Object.keys(source);
28
+ var key, i;
29
+
30
+ for (i = 0; i < sourceKeys.length; i++) {
31
+ key = sourceKeys[i];
32
+ if (excluded.indexOf(key) >= 0) continue;
33
+ target[key] = source[key];
34
+ }
35
+
36
+ return target;
37
+ }
38
+
39
+ function _objectWithoutProperties(source, excluded) {
40
+ if (source == null) return {};
41
+ var target = _objectWithoutPropertiesLoose(source, excluded);
42
+ var key, i;
43
+
44
+ if (Object.getOwnPropertySymbols) {
45
+ var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
46
+
47
+ for (i = 0; i < sourceSymbolKeys.length; i++) {
48
+ key = sourceSymbolKeys[i];
49
+ if (excluded.indexOf(key) >= 0) continue;
50
+ if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
51
+ target[key] = source[key];
52
+ }
53
+ }
54
+
55
+ return target;
56
+ }
8
57
 
9
58
  /**
10
- * Leaf content strings.
11
- */
12
- const String = (props) => {
13
- const { isLast, leaf, parent, text } = props;
14
- const editor = useEditor();
15
- const path = ReactEditor.findPath(editor, text);
16
- const parentPath = Path.parent(path);
17
- // COMPAT: Render text inside void nodes with a zero-width space.
18
- // So the node can contain selection but the text is not visible.
19
- if (editor.isVoid(parent)) {
20
- return React.createElement(ZeroWidthString, { length: Node$1.string(parent).length });
21
- }
22
- // COMPAT: If this is the last text node in an empty block, render a zero-
23
- // width space that will convert into a line break when copying and pasting
24
- // to support expected plain text.
25
- if (leaf.text === '' &&
26
- parent.children[parent.children.length - 1] === text &&
27
- !editor.isInline(parent) &&
28
- Editor.string(editor, parentPath) === '') {
29
- return React.createElement(ZeroWidthString, { isLineBreak: true });
30
- }
31
- // COMPAT: If the text is empty, it's because it's on the edge of an inline
32
- // node, so we render a zero-width space so that the selection can be
33
- // inserted next to it still.
34
- if (leaf.text === '') {
35
- return React.createElement(ZeroWidthString, null);
36
- }
37
- // COMPAT: Browsers will collapse trailing new lines at the end of blocks,
38
- // so we need to add an extra trailing new lines to prevent that.
39
- if (isLast && leaf.text.slice(-1) === '\n') {
40
- return React.createElement(TextString, { isTrailing: true, text: leaf.text });
41
- }
42
- return React.createElement(TextString, { text: leaf.text });
43
- };
44
- /**
45
- * Leaf strings with text in them.
46
- */
47
- const TextString = (props) => {
48
- const { text, isTrailing = false } = props;
49
- return (React.createElement("span", { "data-slate-string": true },
50
- text,
51
- isTrailing ? '\n' : null));
52
- };
59
+ * An auto-incrementing identifier for keys.
60
+ */
61
+ var n = 0;
53
62
  /**
54
- * Leaf strings without text, render as zero-width strings.
55
- */
56
- const ZeroWidthString = (props) => {
57
- const { length = 0, isLineBreak = false } = props;
58
- return (React.createElement("span", { "data-slate-zero-width": isLineBreak ? 'n' : 'z', "data-slate-length": length },
59
- '\uFEFF',
60
- isLineBreak ? React.createElement("br", null) : null));
61
- };
63
+ * A class that keeps track of a key string. We use a full class here because we
64
+ * want to be able to use them as keys in `WeakMap` objects.
65
+ */
66
+
67
+ class Key {
68
+ constructor() {
69
+ this.id = "".concat(n++);
70
+ }
71
+
72
+ }
62
73
 
63
74
  /**
64
75
  * Two weak maps that allow us rebuild a path given a node. They are populated
@@ -94,523 +105,51 @@ var EDITOR_TO_ON_CHANGE = new WeakMap();
94
105
  var PLACEHOLDER_SYMBOL = Symbol('placeholder');
95
106
 
96
107
  /**
97
- * Individual leaves in a text node with unique formatting.
98
- */
99
- const Leaf = (props) => {
100
- const { leaf, isLast, text, parent, renderLeaf = (props) => React.createElement(DefaultLeaf, Object.assign({}, props)), } = props;
101
- let children = (React.createElement(String, { isLast: isLast, leaf: leaf, parent: parent, text: text }));
102
- if (leaf[PLACEHOLDER_SYMBOL]) {
103
- children = (React.createElement(React.Fragment, null,
104
- React.createElement("span", { contentEditable: false, style: {
105
- pointerEvents: 'none',
106
- display: 'inline-block',
107
- verticalAlign: 'text-top',
108
- width: '0',
109
- maxWidth: '100%',
110
- whiteSpace: 'nowrap',
111
- opacity: '0.333',
112
- } }, leaf.placeholder),
113
- children));
114
- }
115
- // COMPAT: Having the `data-` attributes on these leaf elements ensures that
116
- // in certain misbehaving browsers they aren't weirdly cloned/destroyed by
117
- // contenteditable behaviors. (2019/05/08)
118
- const attributes = {
119
- 'data-slate-leaf': true,
120
- };
121
- return renderLeaf({ attributes, children, leaf, text });
122
- };
123
- const MemoizedLeaf = React.memo(Leaf, (prev, next) => {
124
- return (next.parent === prev.parent &&
125
- next.isLast === prev.isLast &&
126
- next.renderLeaf === prev.renderLeaf &&
127
- next.text === prev.text &&
128
- Text$1.matches(next.leaf, prev.leaf));
129
- });
130
- /**
131
- * The default custom leaf renderer.
132
- */
133
- const DefaultLeaf = (props) => {
134
- const { attributes, children } = props;
135
- return React.createElement("span", Object.assign({}, attributes), children);
136
- };
137
-
138
- /**
139
- * Prevent warning on SSR by falling back to useEffect when window is not defined
140
- */
141
-
142
- var useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
143
-
144
- /**
145
- * Text.
146
- */
147
- const Text = (props) => {
148
- const { decorations, isLast, parent, renderLeaf, text } = props;
149
- const editor = useEditor();
150
- const ref = useRef(null);
151
- const leaves = getLeaves(text, decorations);
152
- const key = ReactEditor.findKey(editor, text);
153
- const children = [];
154
- for (let i = 0; i < leaves.length; i++) {
155
- const leaf = leaves[i];
156
- children.push(React.createElement(MemoizedLeaf, { isLast: isLast && i === leaves.length - 1, key: `${key.id}-${i}`, leaf: leaf, text: text, parent: parent, renderLeaf: renderLeaf }));
157
- }
158
- // Update element-related weak maps with the DOM element ref.
159
- useIsomorphicLayoutEffect(() => {
160
- if (ref.current) {
161
- KEY_TO_ELEMENT.set(key, ref.current);
162
- NODE_TO_ELEMENT.set(text, ref.current);
163
- ELEMENT_TO_NODE.set(ref.current, text);
164
- }
165
- else {
166
- KEY_TO_ELEMENT.delete(key);
167
- NODE_TO_ELEMENT.delete(text);
168
- }
169
- });
170
- return (React.createElement("span", { "data-slate-node": "text", ref: ref }, children));
171
- };
172
- /**
173
- * Get the leaves for a text node given decorations.
174
- */
175
- const getLeaves = (node, decorations) => {
176
- let leaves = [{ ...node }];
177
- for (const dec of decorations) {
178
- const { anchor, focus, ...rest } = dec;
179
- const [start, end] = Range.edges(dec);
180
- const next = [];
181
- let o = 0;
182
- for (const leaf of leaves) {
183
- const { length } = leaf.text;
184
- const offset = o;
185
- o += length;
186
- // If the range encompases the entire leaf, add the range.
187
- if (start.offset <= offset && end.offset >= offset + length) {
188
- Object.assign(leaf, rest);
189
- next.push(leaf);
190
- continue;
191
- }
192
- // If the range starts after the leaf, or ends before it, continue.
193
- if (start.offset > offset + length ||
194
- end.offset < offset ||
195
- (end.offset === offset && offset !== 0)) {
196
- next.push(leaf);
197
- continue;
198
- }
199
- // Otherwise we need to split the leaf, at the start, end, or both,
200
- // and add the range to the middle intersecting section. Do the end
201
- // split first since we don't need to update the offset that way.
202
- let middle = leaf;
203
- let before;
204
- let after;
205
- if (end.offset < offset + length) {
206
- const off = end.offset - offset;
207
- after = { ...middle, text: middle.text.slice(off) };
208
- middle = { ...middle, text: middle.text.slice(0, off) };
209
- }
210
- if (start.offset > offset) {
211
- const off = start.offset - offset;
212
- before = { ...middle, text: middle.text.slice(0, off) };
213
- middle = { ...middle, text: middle.text.slice(off) };
214
- }
215
- Object.assign(middle, rest);
216
- if (before) {
217
- next.push(before);
218
- }
219
- next.push(middle);
220
- if (after) {
221
- next.push(after);
222
- }
223
- }
224
- leaves = next;
225
- }
226
- return leaves;
227
- };
228
- const MemoizedText = React.memo(Text, (prev, next) => {
229
- return (next.parent === prev.parent &&
230
- next.isLast === prev.isLast &&
231
- next.renderLeaf === prev.renderLeaf &&
232
- next.text === prev.text);
233
- });
234
-
235
- /**
236
- * A React context for sharing the `selected` state of an element.
237
- */
238
-
239
- var SelectedContext = createContext(false);
240
- /**
241
- * Get the current `selected` state of an element.
242
- */
243
-
244
- var useSelected = function useSelected() {
245
- return useContext(SelectedContext);
246
- };
247
-
248
- /**
249
- * Element.
250
- */
251
- const Element = (props) => {
252
- const { decorate, decorations, element, renderElement = (p) => React.createElement(DefaultElement, Object.assign({}, p)), renderLeaf, selection, } = props;
253
- const ref = useRef(null);
254
- const editor = useEditor();
255
- const readOnly = useReadOnly();
256
- const isInline = editor.isInline(element);
257
- const key = ReactEditor.findKey(editor, element);
258
- let children = (React.createElement(Children, { decorate: decorate, decorations: decorations, node: element, renderElement: renderElement, renderLeaf: renderLeaf, selection: selection }));
259
- // Attributes that the developer must mix into the element in their
260
- // custom node renderer component.
261
- const attributes = {
262
- 'data-slate-node': 'element',
263
- ref,
264
- };
265
- if (isInline) {
266
- attributes['data-slate-inline'] = true;
267
- }
268
- // If it's a block node with inline children, add the proper `dir` attribute
269
- // for text direction.
270
- if (!isInline && Editor.hasInlines(editor, element)) {
271
- const text = Node$1.string(element);
272
- const dir = getDirection(text);
273
- if (dir === 'rtl') {
274
- attributes.dir = dir;
275
- }
276
- }
277
- // If it's a void node, wrap the children in extra void-specific elements.
278
- if (Editor.isVoid(editor, element)) {
279
- attributes['data-slate-void'] = true;
280
- if (!readOnly && isInline) {
281
- attributes.contentEditable = false;
282
- }
283
- const Tag = isInline ? 'span' : 'div';
284
- const [[text]] = Node$1.texts(element);
285
- children = readOnly ? null : (React.createElement(Tag, { "data-slate-spacer": true, style: {
286
- height: '0',
287
- color: 'transparent',
288
- outline: 'none',
289
- position: 'absolute',
290
- } },
291
- React.createElement(MemoizedText, { decorations: [], isLast: false, parent: element, text: text })));
292
- NODE_TO_INDEX.set(text, 0);
293
- NODE_TO_PARENT.set(text, element);
294
- }
295
- // Update element-related weak maps with the DOM element ref.
296
- useIsomorphicLayoutEffect(() => {
297
- if (ref.current) {
298
- KEY_TO_ELEMENT.set(key, ref.current);
299
- NODE_TO_ELEMENT.set(element, ref.current);
300
- ELEMENT_TO_NODE.set(ref.current, element);
301
- }
302
- else {
303
- KEY_TO_ELEMENT.delete(key);
304
- NODE_TO_ELEMENT.delete(element);
305
- }
306
- });
307
- return (React.createElement(SelectedContext.Provider, { value: !!selection }, renderElement({ attributes, children, element })));
308
- };
309
- const MemoizedElement = React.memo(Element, (prev, next) => {
310
- return (prev.decorate === next.decorate &&
311
- prev.element === next.element &&
312
- prev.renderElement === next.renderElement &&
313
- prev.renderLeaf === next.renderLeaf &&
314
- isRangeListEqual(prev.decorations, next.decorations) &&
315
- (prev.selection === next.selection ||
316
- (!!prev.selection &&
317
- !!next.selection &&
318
- Range.equals(prev.selection, next.selection))));
319
- });
320
- /**
321
- * The default element renderer.
322
- */
323
- const DefaultElement = (props) => {
324
- const { attributes, children, element } = props;
325
- const editor = useEditor();
326
- const Tag = editor.isInline(element) ? 'span' : 'div';
327
- return (React.createElement(Tag, Object.assign({}, attributes, { style: { position: 'relative' } }), children));
328
- };
329
- /**
330
- * Check if a list of ranges is equal to another.
331
- *
332
- * PERF: this requires the two lists to also have the ranges inside them in the
333
- * same order, but this is an okay constraint for us since decorations are
334
- * kept in order, and the odd case where they aren't is okay to re-render for.
335
- */
336
- const isRangeListEqual = (list, another) => {
337
- if (list.length !== another.length) {
338
- return false;
339
- }
340
- for (let i = 0; i < list.length; i++) {
341
- const range = list[i];
342
- const other = another[i];
343
- if (!Range.equals(range, other)) {
344
- return false;
345
- }
346
- }
347
- return true;
348
- };
349
-
350
- /**
351
- * A React context for sharing the `Editor` class.
352
- */
353
- const EditorContext = createContext(null);
354
- /**
355
- * Get the current `Editor` class that the component lives under.
356
- */
357
- const useEditor = () => {
358
- const editor = useContext(EditorContext);
359
- if (!editor) {
360
- throw new Error(`The \`useEditor\` hook must be used inside the <Slate> component's context.`);
361
- }
362
- return editor;
363
- };
364
-
365
- /**
366
- * Children.
367
- */
368
- const Children = (props) => {
369
- const { decorate, decorations, node, renderElement, renderLeaf, selection, } = props;
370
- const editor = useEditor();
371
- const path = ReactEditor.findPath(editor, node);
372
- const children = [];
373
- const isLeafBlock = Element$1.isElement(node) &&
374
- !editor.isInline(node) &&
375
- Editor.hasInlines(editor, node);
376
- for (let i = 0; i < node.children.length; i++) {
377
- const p = path.concat(i);
378
- const n = node.children[i];
379
- const key = ReactEditor.findKey(editor, n);
380
- const range = Editor.range(editor, p);
381
- const sel = selection && Range.intersection(range, selection);
382
- const ds = decorate([n, p]);
383
- for (const dec of decorations) {
384
- const d = Range.intersection(dec, range);
385
- if (d) {
386
- ds.push(d);
387
- }
388
- }
389
- if (Element$1.isElement(n)) {
390
- children.push(React.createElement(MemoizedElement, { decorate: decorate, decorations: ds, element: n, key: key.id, renderElement: renderElement, renderLeaf: renderLeaf, selection: sel }));
391
- }
392
- else {
393
- children.push(React.createElement(MemoizedText, { decorations: ds, key: key.id, isLast: isLeafBlock && i === node.children.length - 1, parent: node, renderLeaf: renderLeaf, text: n }));
394
- }
395
- NODE_TO_INDEX.set(n, i);
396
- NODE_TO_PARENT.set(n, node);
397
- }
398
- return React.createElement(React.Fragment, null, children);
399
- };
400
-
401
- var IS_IOS = typeof navigator !== 'undefined' && typeof window !== 'undefined' && /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
402
- var IS_APPLE = typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent);
403
- var IS_FIREFOX = typeof navigator !== 'undefined' && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
404
- var IS_SAFARI = typeof navigator !== 'undefined' && /Version\/[\d\.]+.*Safari/.test(navigator.userAgent);
405
-
406
- /**
407
- * Hotkey mappings for each platform.
408
- */
409
-
410
- var HOTKEYS = {
411
- bold: 'mod+b',
412
- compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],
413
- moveBackward: 'left',
414
- moveForward: 'right',
415
- moveWordBackward: 'ctrl+left',
416
- moveWordForward: 'ctrl+right',
417
- deleteBackward: 'shift?+backspace',
418
- deleteForward: 'shift?+delete',
419
- extendBackward: 'shift+left',
420
- extendForward: 'shift+right',
421
- italic: 'mod+i',
422
- splitBlock: 'shift?+enter',
423
- undo: 'mod+z'
424
- };
425
- var APPLE_HOTKEYS = {
426
- moveLineBackward: 'opt+up',
427
- moveLineForward: 'opt+down',
428
- moveWordBackward: 'opt+left',
429
- moveWordForward: 'opt+right',
430
- deleteBackward: ['ctrl+backspace', 'ctrl+h'],
431
- deleteForward: ['ctrl+delete', 'ctrl+d'],
432
- deleteLineBackward: 'cmd+shift?+backspace',
433
- deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],
434
- deleteWordBackward: 'opt+shift?+backspace',
435
- deleteWordForward: 'opt+shift?+delete',
436
- extendLineBackward: 'opt+shift+up',
437
- extendLineForward: 'opt+shift+down',
438
- redo: 'cmd+shift+z',
439
- transposeCharacter: 'ctrl+t'
440
- };
441
- var WINDOWS_HOTKEYS = {
442
- deleteWordBackward: 'ctrl+shift?+backspace',
443
- deleteWordForward: 'ctrl+shift?+delete',
444
- redo: ['ctrl+y', 'ctrl+shift+z']
445
- };
446
- /**
447
- * Create a platform-aware hotkey checker.
448
- */
449
-
450
- var create = function create(key) {
451
- var generic = HOTKEYS[key];
452
- var apple = APPLE_HOTKEYS[key];
453
- var windows = WINDOWS_HOTKEYS[key];
454
- var isGeneric = generic && isKeyHotkey(generic);
455
- var isApple = apple && isKeyHotkey(apple);
456
- var isWindows = windows && isKeyHotkey(windows);
457
- return function (event) {
458
- if (isGeneric && isGeneric(event)) return true;
459
- if (IS_APPLE && isApple && isApple(event)) return true;
460
- if (!IS_APPLE && isWindows && isWindows(event)) return true;
461
- return false;
462
- };
463
- };
464
- /**
465
- * Hotkeys.
466
- */
467
-
468
-
469
- var Hotkeys = {
470
- isBold: create('bold'),
471
- isCompose: create('compose'),
472
- isMoveBackward: create('moveBackward'),
473
- isMoveForward: create('moveForward'),
474
- isDeleteBackward: create('deleteBackward'),
475
- isDeleteForward: create('deleteForward'),
476
- isDeleteLineBackward: create('deleteLineBackward'),
477
- isDeleteLineForward: create('deleteLineForward'),
478
- isDeleteWordBackward: create('deleteWordBackward'),
479
- isDeleteWordForward: create('deleteWordForward'),
480
- isExtendBackward: create('extendBackward'),
481
- isExtendForward: create('extendForward'),
482
- isExtendLineBackward: create('extendLineBackward'),
483
- isExtendLineForward: create('extendLineForward'),
484
- isItalic: create('italic'),
485
- isMoveLineBackward: create('moveLineBackward'),
486
- isMoveLineForward: create('moveLineForward'),
487
- isMoveWordBackward: create('moveWordBackward'),
488
- isMoveWordForward: create('moveWordForward'),
489
- isRedo: create('redo'),
490
- isSplitBlock: create('splitBlock'),
491
- isTransposeCharacter: create('transposeCharacter'),
492
- isUndo: create('undo')
493
- };
494
-
495
- /**
496
- * A React context for sharing the `readOnly` state of the editor.
497
- */
498
-
499
- var ReadOnlyContext = createContext(false);
500
- /**
501
- * Get the current `readOnly` state of the editor.
108
+ * Types.
502
109
  */
503
-
504
- var useReadOnly = function useReadOnly() {
505
- return useContext(ReadOnlyContext);
506
- };
507
-
508
- /**
509
- * A React context for sharing the `Editor` class, in a way that re-renders the
510
- * context whenever changes occur.
511
- */
512
- const SlateContext = createContext(null);
513
- /**
514
- * Get the current `Editor` class that the component lives under.
515
- */
516
- const useSlate = () => {
517
- const context = useContext(SlateContext);
518
- if (!context) {
519
- throw new Error(`The \`useSlate\` hook must be used inside the <SlateProvider> component's context.`);
520
- }
521
- const [editor] = context;
522
- return editor;
523
- };
524
-
525
- function _arrayWithHoles(arr) {
526
- if (Array.isArray(arr)) return arr;
527
- }
528
-
529
- var arrayWithHoles = _arrayWithHoles;
530
-
531
- function _iterableToArrayLimit(arr, i) {
532
- if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) {
533
- return;
534
- }
535
-
536
- var _arr = [];
537
- var _n = true;
538
- var _d = false;
539
- var _e = undefined;
540
-
541
- try {
542
- for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
543
- _arr.push(_s.value);
544
-
545
- if (i && _arr.length === i) break;
546
- }
547
- } catch (err) {
548
- _d = true;
549
- _e = err;
550
- } finally {
551
- try {
552
- if (!_n && _i["return"] != null) _i["return"]();
553
- } finally {
554
- if (_d) throw _e;
555
- }
556
- }
557
-
558
- return _arr;
559
- }
560
-
561
- var iterableToArrayLimit = _iterableToArrayLimit;
562
-
563
- function _nonIterableRest() {
564
- throw new TypeError("Invalid attempt to destructure non-iterable instance");
565
- }
566
-
567
- var nonIterableRest = _nonIterableRest;
568
-
569
- function _slicedToArray(arr, i) {
570
- return arrayWithHoles(arr) || iterableToArrayLimit(arr, i) || nonIterableRest();
571
- }
572
-
573
- var slicedToArray = _slicedToArray;
574
-
575
110
  /**
576
111
  * Check if a DOM node is a comment node.
577
112
  */
578
113
 
579
- var isDOMComment = function isDOMComment(value) {
114
+ var isDOMComment = value => {
580
115
  return isDOMNode(value) && value.nodeType === 8;
581
116
  };
582
117
  /**
583
118
  * Check if a DOM node is an element node.
584
119
  */
585
120
 
586
- var isDOMElement = function isDOMElement(value) {
121
+ var isDOMElement = value => {
587
122
  return isDOMNode(value) && value.nodeType === 1;
588
123
  };
589
124
  /**
590
125
  * Check if a value is a DOM node.
591
126
  */
592
127
 
593
- var isDOMNode = function isDOMNode(value) {
128
+ var isDOMNode = value => {
594
129
  return value instanceof Node;
595
130
  };
596
131
  /**
597
132
  * Check if a DOM node is an element node.
598
133
  */
599
134
 
600
- var isDOMText = function isDOMText(value) {
135
+ var isDOMText = value => {
601
136
  return isDOMNode(value) && value.nodeType === 3;
602
137
  };
138
+ /**
139
+ * Checks whether a paste event is a plaintext-only event.
140
+ */
141
+
142
+ var isPlainTextOnlyPaste = event => {
143
+ return event.clipboardData && event.clipboardData.getData('text/plain') !== '' && event.clipboardData.types.length === 1;
144
+ };
603
145
  /**
604
146
  * Normalize a DOM point so that it always refers to a text node.
605
147
  */
606
148
 
607
- var normalizeDOMPoint = function normalizeDOMPoint(domPoint) {
608
- var _domPoint = slicedToArray(domPoint, 2),
609
- node = _domPoint[0],
610
- offset = _domPoint[1]; // If it's an element node, its offset refers to the index of its children
149
+ var normalizeDOMPoint = domPoint => {
150
+ var [node, offset] = domPoint; // If it's an element node, its offset refers to the index of its children
611
151
  // including comment nodes, so try to find the right text child node.
612
152
 
613
-
614
153
  if (isDOMElement(node) && node.childNodes.length) {
615
154
  var isLast = offset === node.childNodes.length;
616
155
  var direction = isLast ? 'backward' : 'forward';
@@ -635,8 +174,10 @@ var normalizeDOMPoint = function normalizeDOMPoint(domPoint) {
635
174
  * `direction`.
636
175
  */
637
176
 
638
- var getEditableChild = function getEditableChild(parent, index, direction) {
639
- var childNodes = parent.childNodes;
177
+ var getEditableChild = (parent, index, direction) => {
178
+ var {
179
+ childNodes
180
+ } = parent;
640
181
  var child = childNodes[index];
641
182
  var i = index;
642
183
  var triedForward = false;
@@ -668,816 +209,54 @@ var getEditableChild = function getEditableChild(parent, index, direction) {
668
209
 
669
210
  return child;
670
211
  };
671
-
672
- /**
673
- * Editable.
674
- */
675
- const Editable = (props) => {
676
- const { autoFocus, decorate = defaultDecorate, onDOMBeforeInput: propsOnDOMBeforeInput, placeholder, readOnly = false, renderElement, renderLeaf, style = {}, as: Component = 'div', ...attributes } = props;
677
- const editor = useSlate();
678
- const ref = useRef(null);
679
- // Update internal state on each render.
680
- IS_READ_ONLY.set(editor, readOnly);
681
- // Keep track of some state for the event handler logic.
682
- const state = useMemo(() => ({
683
- isComposing: false,
684
- isUpdatingSelection: false,
685
- latestElement: null,
686
- }), []);
687
- // Update element-related weak maps with the DOM element ref.
688
- useIsomorphicLayoutEffect(() => {
689
- if (ref.current) {
690
- EDITOR_TO_ELEMENT.set(editor, ref.current);
691
- NODE_TO_ELEMENT.set(editor, ref.current);
692
- ELEMENT_TO_NODE.set(ref.current, editor);
693
- }
694
- else {
695
- NODE_TO_ELEMENT.delete(editor);
696
- }
697
- });
698
- // Attach a native DOM event handler for `selectionchange`, because React's
699
- // built-in `onSelect` handler doesn't fire for all selection changes. It's a
700
- // leaky polyfill that only fires on keypresses or clicks. Instead, we want to
701
- // fire for any change to the selection inside the editor. (2019/11/04)
702
- // https://github.com/facebook/react/issues/5785
703
- useIsomorphicLayoutEffect(() => {
704
- window.document.addEventListener('selectionchange', onDOMSelectionChange);
705
- return () => {
706
- window.document.removeEventListener('selectionchange', onDOMSelectionChange);
707
- };
708
- }, []);
709
- // Attach a native DOM event handler for `beforeinput` events, because React's
710
- // built-in `onBeforeInput` is actually a leaky polyfill that doesn't expose
711
- // real `beforeinput` events sadly... (2019/11/04)
712
- // https://github.com/facebook/react/issues/11211
713
- useIsomorphicLayoutEffect(() => {
714
- if (ref.current) {
715
- // @ts-ignore The `beforeinput` event isn't recognized.
716
- ref.current.addEventListener('beforeinput', onDOMBeforeInput);
717
- }
718
- return () => {
719
- if (ref.current) {
720
- // @ts-ignore The `beforeinput` event isn't recognized.
721
- ref.current.removeEventListener('beforeinput', onDOMBeforeInput);
722
- }
723
- };
724
- }, []);
725
- // Whenever the editor updates, make sure the DOM selection state is in sync.
726
- useIsomorphicLayoutEffect(() => {
727
- const { selection } = editor;
728
- const domSelection = window.getSelection();
729
- if (state.isComposing || !domSelection || !ReactEditor.isFocused(editor)) {
730
- return;
731
- }
732
- const hasDomSelection = domSelection.type !== 'None';
733
- // If the DOM selection is properly unset, we're done.
734
- if (!selection && !hasDomSelection) {
735
- return;
736
- }
737
- const newDomRange = selection && ReactEditor.toDOMRange(editor, selection);
738
- // If the DOM selection is already correct, we're done.
739
- if (hasDomSelection &&
740
- newDomRange &&
741
- isRangeEqual(domSelection.getRangeAt(0), newDomRange)) {
742
- return;
743
- }
744
- // Otherwise the DOM selection is out of sync, so update it.
745
- const el = ReactEditor.toDOMNode(editor, editor);
746
- state.isUpdatingSelection = true;
747
- domSelection.removeAllRanges();
748
- if (newDomRange) {
749
- domSelection.addRange(newDomRange);
750
- const leafEl = newDomRange.startContainer.parentElement;
751
- scrollIntoView(leafEl, { scrollMode: 'if-needed' });
752
- }
753
- setTimeout(() => {
754
- // COMPAT: In Firefox, it's not enough to create a range, you also need
755
- // to focus the contenteditable element too. (2016/11/16)
756
- if (newDomRange && IS_FIREFOX) {
757
- el.focus();
758
- }
759
- state.isUpdatingSelection = false;
760
- });
761
- });
762
- // The autoFocus TextareaHTMLAttribute doesn't do anything on a div, so it
763
- // needs to be manually focused.
764
- useEffect(() => {
765
- if (ref.current && autoFocus) {
766
- ref.current.focus();
767
- }
768
- }, [autoFocus]);
769
- // Listen on the native `beforeinput` event to get real "Level 2" events. This
770
- // is required because React's `beforeinput` is fake and never really attaches
771
- // to the real event sadly. (2019/11/01)
772
- // https://github.com/facebook/react/issues/11211
773
- const onDOMBeforeInput = useCallback((event) => {
774
- if (!readOnly &&
775
- hasEditableTarget(editor, event.target) &&
776
- !isDOMEventHandled(event, propsOnDOMBeforeInput)) {
777
- const { selection } = editor;
778
- const { inputType: type } = event;
779
- const data = event.dataTransfer || event.data || undefined;
780
- // These two types occur while a user is composing text and can't be
781
- // cancelled. Let them through and wait for the composition to end.
782
- if (type === 'insertCompositionText' ||
783
- type === 'deleteCompositionText') {
784
- return;
785
- }
786
- event.preventDefault();
787
- // COMPAT: For the deleting forward/backward input types we don't want
788
- // to change the selection because it is the range that will be deleted,
789
- // and those commands determine that for themselves.
790
- if (!type.startsWith('delete') || type.startsWith('deleteBy')) {
791
- const [targetRange] = event.getTargetRanges();
792
- if (targetRange) {
793
- const range = ReactEditor.toSlateRange(editor, targetRange);
794
- if (!selection || !Range.equals(selection, range)) {
795
- Editor.select(editor, range);
796
- }
797
- }
798
- }
799
- // COMPAT: If the selection is expanded, even if the command seems like
800
- // a delete forward/backward command it should delete the selection.
801
- if (selection &&
802
- Range.isExpanded(selection) &&
803
- type.startsWith('delete')) {
804
- editor.exec({ type: 'delete_fragment' });
805
- return;
806
- }
807
- switch (type) {
808
- case 'deleteByComposition':
809
- case 'deleteByCut':
810
- case 'deleteByDrag': {
811
- editor.exec({ type: 'delete_fragment' });
812
- break;
813
- }
814
- case 'deleteContent':
815
- case 'deleteContentForward': {
816
- editor.exec({ type: 'delete_forward', unit: 'character' });
817
- break;
818
- }
819
- case 'deleteContentBackward': {
820
- editor.exec({ type: 'delete_backward', unit: 'character' });
821
- break;
822
- }
823
- case 'deleteEntireSoftLine': {
824
- editor.exec({ type: 'delete_backward', unit: 'line' });
825
- editor.exec({ type: 'delete_forward', unit: 'line' });
826
- break;
827
- }
828
- case 'deleteHardLineBackward': {
829
- editor.exec({ type: 'delete_backward', unit: 'block' });
830
- break;
831
- }
832
- case 'deleteSoftLineBackward': {
833
- editor.exec({ type: 'delete_backward', unit: 'line' });
834
- break;
835
- }
836
- case 'deleteHardLineForward': {
837
- editor.exec({ type: 'delete_forward', unit: 'block' });
838
- break;
839
- }
840
- case 'deleteSoftLineForward': {
841
- editor.exec({ type: 'delete_forward', unit: 'line' });
842
- break;
843
- }
844
- case 'deleteWordBackward': {
845
- editor.exec({ type: 'delete_backward', unit: 'word' });
846
- break;
847
- }
848
- case 'deleteWordForward': {
849
- editor.exec({ type: 'delete_forward', unit: 'word' });
850
- break;
851
- }
852
- case 'insertLineBreak':
853
- case 'insertParagraph': {
854
- editor.exec({ type: 'insert_break' });
855
- break;
856
- }
857
- case 'insertFromComposition':
858
- case 'insertFromDrop':
859
- case 'insertFromPaste':
860
- case 'insertFromYank':
861
- case 'insertReplacementText':
862
- case 'insertText': {
863
- if (data instanceof DataTransfer) {
864
- editor.exec({ type: 'insert_data', data });
865
- }
866
- else if (typeof data === 'string') {
867
- editor.exec({ type: 'insert_text', text: data });
868
- }
869
- break;
870
- }
871
- }
872
- }
873
- }, []);
874
- // Listen on the native `selectionchange` event to be able to update any time
875
- // the selection changes. This is required because React's `onSelect` is leaky
876
- // and non-standard so it doesn't fire until after a selection has been
877
- // released. This causes issues in situations where another change happens
878
- // while a selection is being dragged.
879
- const onDOMSelectionChange = useCallback(debounce(() => {
880
- if (!readOnly && !state.isComposing && !state.isUpdatingSelection) {
881
- const { activeElement } = window.document;
882
- const el = ReactEditor.toDOMNode(editor, editor);
883
- const domSelection = window.getSelection();
884
- const domRange = domSelection &&
885
- domSelection.rangeCount > 0 &&
886
- domSelection.getRangeAt(0);
887
- if (activeElement === el) {
888
- state.latestElement = activeElement;
889
- IS_FOCUSED.set(editor, true);
890
- }
891
- else {
892
- IS_FOCUSED.delete(editor);
893
- }
894
- if (domRange &&
895
- hasEditableTarget(editor, domRange.startContainer) &&
896
- hasEditableTarget(editor, domRange.endContainer)) {
897
- const range = ReactEditor.toSlateRange(editor, domRange);
898
- Editor.select(editor, range);
899
- }
900
- else {
901
- Editor.deselect(editor);
902
- }
903
- }
904
- }, 100), []);
905
- const decorations = decorate([editor, []]);
906
- if (placeholder &&
907
- editor.children.length === 1 &&
908
- Array.from(Node$1.texts(editor)).length === 1 &&
909
- Node$1.string(editor) === '') {
910
- const start = Editor.start(editor, []);
911
- decorations.push({
912
- [PLACEHOLDER_SYMBOL]: true,
913
- placeholder,
914
- anchor: start,
915
- focus: start,
916
- });
917
- }
918
- return (React.createElement(ReadOnlyContext.Provider, { value: readOnly },
919
- React.createElement(Component
920
- // COMPAT: The Grammarly Chrome extension works by changing the DOM
921
- // out from under `contenteditable` elements, which leads to weird
922
- // behaviors so we have to disable it like editor. (2017/04/24)
923
- , Object.assign({ "data-gramm": false, role: readOnly ? undefined : 'textbox' }, attributes, {
924
- // COMPAT: Firefox doesn't support the `beforeinput` event, so we'd
925
- // have to use hacks to make these replacement-based features work.
926
- spellCheck: IS_FIREFOX ? undefined : attributes.spellCheck, autoCorrect: IS_FIREFOX ? undefined : attributes.autoCorrect, autoCapitalize: IS_FIREFOX ? undefined : attributes.autoCapitalize, "data-slate-editor": true, "data-slate-node": "value", contentEditable: readOnly ? undefined : true, suppressContentEditableWarning: true, ref: ref, style: {
927
- // Prevent the default outline styles.
928
- outline: 'none',
929
- // Preserve adjacent whitespace and new lines.
930
- whiteSpace: 'pre-wrap',
931
- // Allow words to break if they are too long.
932
- wordWrap: 'break-word',
933
- // Allow for passed-in styles to override anything.
934
- ...style,
935
- }, onBeforeInput: useCallback((event) => {
936
- // COMPAT: Firefox doesn't support the `beforeinput` event, so we
937
- // fall back to React's leaky polyfill instead just for it. It
938
- // only works for the `insertText` input type.
939
- if (IS_FIREFOX && !readOnly) {
940
- event.preventDefault();
941
- const text = event.data;
942
- editor.exec({ type: 'insert_text', text });
943
- }
944
- }, [readOnly]), onBlur: useCallback((event) => {
945
- if (readOnly ||
946
- state.isUpdatingSelection ||
947
- !hasEditableTarget(editor, event.target) ||
948
- isEventHandled(event, attributes.onBlur)) {
949
- return;
950
- }
951
- // COMPAT: If the current `activeElement` is still the previous
952
- // one, this is due to the window being blurred when the tab
953
- // itself becomes unfocused, so we want to abort early to allow to
954
- // editor to stay focused when the tab becomes focused again.
955
- if (state.latestElement === window.document.activeElement) {
956
- return;
957
- }
958
- const { relatedTarget } = event;
959
- const el = ReactEditor.toDOMNode(editor, editor);
960
- // COMPAT: The event should be ignored if the focus is returning
961
- // to the editor from an embedded editable element (eg. an <input>
962
- // element inside a void node).
963
- if (relatedTarget === el) {
964
- return;
965
- }
966
- // COMPAT: The event should be ignored if the focus is moving from
967
- // the editor to inside a void node's spacer element.
968
- if (isDOMElement(relatedTarget) &&
969
- relatedTarget.hasAttribute('data-slate-spacer')) {
970
- return;
971
- }
972
- // COMPAT: The event should be ignored if the focus is moving to a
973
- // non- editable section of an element that isn't a void node (eg.
974
- // a list item of the check list example).
975
- if (relatedTarget != null &&
976
- isDOMNode(relatedTarget) &&
977
- ReactEditor.hasDOMNode(editor, relatedTarget)) {
978
- const node = ReactEditor.toSlateNode(editor, relatedTarget);
979
- if (Element$1.isElement(node) && !editor.isVoid(node)) {
980
- return;
981
- }
982
- }
983
- IS_FOCUSED.delete(editor);
984
- }, [readOnly, attributes.onBlur]), onClick: useCallback((event) => {
985
- if (!readOnly &&
986
- hasTarget(editor, event.target) &&
987
- !isEventHandled(event, attributes.onClick) &&
988
- isDOMNode(event.target)) {
989
- const node = ReactEditor.toSlateNode(editor, event.target);
990
- const path = ReactEditor.findPath(editor, node);
991
- const start = Editor.start(editor, path);
992
- if (Editor.void(editor, { at: start })) {
993
- const range = Editor.range(editor, start);
994
- Editor.select(editor, range);
995
- }
996
- }
997
- }, [readOnly, attributes.onClick]), onCompositionEnd: useCallback((event) => {
998
- if (hasEditableTarget(editor, event.target) &&
999
- !isEventHandled(event, attributes.onCompositionEnd)) {
1000
- state.isComposing = false;
1001
- // COMPAT: In Chrome, `beforeinput` events for compositions
1002
- // aren't correct and never fire the "insertFromComposition"
1003
- // type that we need. So instead, insert whenever a composition
1004
- // ends since it will already have been committed to the DOM.
1005
- if (!IS_SAFARI && !IS_FIREFOX && event.data) {
1006
- editor.exec({ type: 'insert_text', text: event.data });
1007
- }
1008
- }
1009
- }, [attributes.onCompositionEnd]), onCompositionStart: useCallback((event) => {
1010
- if (hasEditableTarget(editor, event.target) &&
1011
- !isEventHandled(event, attributes.onCompositionStart)) {
1012
- state.isComposing = true;
1013
- }
1014
- }, [attributes.onCompositionStart]), onCopy: useCallback((event) => {
1015
- if (hasEditableTarget(editor, event.target) &&
1016
- !isEventHandled(event, attributes.onCopy)) {
1017
- event.preventDefault();
1018
- setFragmentData(event.clipboardData, editor);
1019
- }
1020
- }, [attributes.onCopy]), onCut: useCallback((event) => {
1021
- if (!readOnly &&
1022
- hasEditableTarget(editor, event.target) &&
1023
- !isEventHandled(event, attributes.onCut)) {
1024
- event.preventDefault();
1025
- setFragmentData(event.clipboardData, editor);
1026
- const { selection } = editor;
1027
- if (selection && Range.isExpanded(selection)) {
1028
- editor.exec({ type: 'delete_fragment' });
1029
- }
1030
- }
1031
- }, [readOnly, attributes.onCut]), onDragOver: useCallback((event) => {
1032
- if (hasTarget(editor, event.target) &&
1033
- !isEventHandled(event, attributes.onDragOver)) {
1034
- // Only when the target is void, call `preventDefault` to signal
1035
- // that drops are allowed. Editable content is droppable by
1036
- // default, and calling `preventDefault` hides the cursor.
1037
- const node = ReactEditor.toSlateNode(editor, event.target);
1038
- if (Editor.isVoid(editor, node)) {
1039
- event.preventDefault();
1040
- }
1041
- }
1042
- }, [attributes.onDragOver]), onDragStart: useCallback((event) => {
1043
- if (hasTarget(editor, event.target) &&
1044
- !isEventHandled(event, attributes.onDragStart)) {
1045
- const node = ReactEditor.toSlateNode(editor, event.target);
1046
- const path = ReactEditor.findPath(editor, node);
1047
- const voidMatch = Editor.void(editor, { at: path });
1048
- // If starting a drag on a void node, make sure it is selected
1049
- // so that it shows up in the selection's fragment.
1050
- if (voidMatch) {
1051
- const range = Editor.range(editor, path);
1052
- Editor.select(editor, range);
1053
- }
1054
- setFragmentData(event.dataTransfer, editor);
1055
- }
1056
- }, [attributes.onDragStart]), onDrop: useCallback((event) => {
1057
- if (hasTarget(editor, event.target) &&
1058
- !readOnly &&
1059
- !isEventHandled(event, attributes.onDrop)) {
1060
- // COMPAT: Firefox doesn't fire `beforeinput` events at all, and
1061
- // Chromium browsers don't properly fire them for files being
1062
- // dropped into a `contenteditable`. (2019/11/26)
1063
- // https://bugs.chromium.org/p/chromium/issues/detail?id=1028668
1064
- if (IS_FIREFOX ||
1065
- (!IS_SAFARI && event.dataTransfer.files.length > 0)) {
1066
- event.preventDefault();
1067
- const range = ReactEditor.findEventRange(editor, event);
1068
- const data = event.dataTransfer;
1069
- Editor.select(editor, range);
1070
- editor.exec({ type: 'insert_data', data });
1071
- }
1072
- }
1073
- }, [readOnly, attributes.onDrop]), onFocus: useCallback((event) => {
1074
- if (!readOnly &&
1075
- !state.isUpdatingSelection &&
1076
- hasEditableTarget(editor, event.target) &&
1077
- !isEventHandled(event, attributes.onFocus)) {
1078
- const el = ReactEditor.toDOMNode(editor, editor);
1079
- state.latestElement = window.document.activeElement;
1080
- // COMPAT: If the editor has nested editable elements, the focus
1081
- // can go to them. In Firefox, this must be prevented because it
1082
- // results in issues with keyboard navigation. (2017/03/30)
1083
- if (IS_FIREFOX && event.target !== el) {
1084
- el.focus();
1085
- return;
1086
- }
1087
- IS_FOCUSED.set(editor, true);
1088
- }
1089
- }, [readOnly, attributes.onFocus]), onKeyDown: useCallback((event) => {
1090
- if (!readOnly &&
1091
- hasEditableTarget(editor, event.target) &&
1092
- !isEventHandled(event, attributes.onKeyDown)) {
1093
- const { nativeEvent } = event;
1094
- const { selection } = editor;
1095
- // COMPAT: Since we prevent the default behavior on
1096
- // `beforeinput` events, the browser doesn't think there's ever
1097
- // any history stack to undo or redo, so we have to manage these
1098
- // hotkeys ourselves. (2019/11/06)
1099
- if (Hotkeys.isRedo(nativeEvent)) {
1100
- event.preventDefault();
1101
- editor.exec({ type: 'redo' });
1102
- return;
1103
- }
1104
- if (Hotkeys.isUndo(nativeEvent)) {
1105
- event.preventDefault();
1106
- editor.exec({ type: 'undo' });
1107
- return;
1108
- }
1109
- // COMPAT: Certain browsers don't handle the selection updates
1110
- // properly. In Chrome, the selection isn't properly extended.
1111
- // And in Firefox, the selection isn't properly collapsed.
1112
- // (2017/10/17)
1113
- if (Hotkeys.isMoveLineBackward(nativeEvent)) {
1114
- event.preventDefault();
1115
- Editor.move(editor, { unit: 'line', reverse: true });
1116
- return;
1117
- }
1118
- if (Hotkeys.isMoveLineForward(nativeEvent)) {
1119
- event.preventDefault();
1120
- Editor.move(editor, { unit: 'line' });
1121
- return;
1122
- }
1123
- if (Hotkeys.isExtendLineBackward(nativeEvent)) {
1124
- event.preventDefault();
1125
- Editor.move(editor, {
1126
- unit: 'line',
1127
- edge: 'focus',
1128
- reverse: true,
1129
- });
1130
- return;
1131
- }
1132
- if (Hotkeys.isExtendLineForward(nativeEvent)) {
1133
- event.preventDefault();
1134
- Editor.move(editor, { unit: 'line', edge: 'focus' });
1135
- return;
1136
- }
1137
- // COMPAT: If a void node is selected, or a zero-width text node
1138
- // adjacent to an inline is selected, we need to handle these
1139
- // hotkeys manually because browsers won't be able to skip over
1140
- // the void node with the zero-width space not being an empty
1141
- // string.
1142
- if (Hotkeys.isMoveBackward(nativeEvent)) {
1143
- event.preventDefault();
1144
- if (selection && Range.isCollapsed(selection)) {
1145
- Editor.move(editor, { reverse: true });
1146
- }
1147
- else {
1148
- Editor.collapse(editor, { edge: 'start' });
1149
- }
1150
- return;
1151
- }
1152
- if (Hotkeys.isMoveForward(nativeEvent)) {
1153
- event.preventDefault();
1154
- if (selection && Range.isCollapsed(selection)) {
1155
- Editor.move(editor);
1156
- }
1157
- else {
1158
- Editor.collapse(editor, { edge: 'end' });
1159
- }
1160
- return;
1161
- }
1162
- if (Hotkeys.isMoveWordBackward(nativeEvent)) {
1163
- event.preventDefault();
1164
- Editor.move(editor, { unit: 'word', reverse: true });
1165
- return;
1166
- }
1167
- if (Hotkeys.isMoveWordForward(nativeEvent)) {
1168
- event.preventDefault();
1169
- Editor.move(editor, { unit: 'word' });
1170
- return;
1171
- }
1172
- // COMPAT: Firefox doesn't support the `beforeinput` event, so we
1173
- // fall back to guessing at the input intention for hotkeys.
1174
- // COMPAT: In iOS, some of these hotkeys are handled in the
1175
- if (IS_FIREFOX) {
1176
- // We don't have a core behavior for these, but they change the
1177
- // DOM if we don't prevent them, so we have to.
1178
- if (Hotkeys.isBold(nativeEvent) ||
1179
- Hotkeys.isItalic(nativeEvent) ||
1180
- Hotkeys.isTransposeCharacter(nativeEvent)) {
1181
- event.preventDefault();
1182
- return;
1183
- }
1184
- if (Hotkeys.isSplitBlock(nativeEvent)) {
1185
- event.preventDefault();
1186
- editor.exec({ type: 'insert_break' });
1187
- return;
1188
- }
1189
- if (Hotkeys.isDeleteBackward(nativeEvent)) {
1190
- event.preventDefault();
1191
- if (selection && Range.isExpanded(selection)) {
1192
- editor.exec({ type: 'delete_fragment' });
1193
- }
1194
- else {
1195
- editor.exec({ type: 'delete_backward', unit: 'character' });
1196
- }
1197
- return;
1198
- }
1199
- if (Hotkeys.isDeleteForward(nativeEvent)) {
1200
- event.preventDefault();
1201
- if (selection && Range.isExpanded(selection)) {
1202
- editor.exec({ type: 'delete_fragment' });
1203
- }
1204
- else {
1205
- editor.exec({ type: 'delete_forward', unit: 'character' });
1206
- }
1207
- return;
1208
- }
1209
- if (Hotkeys.isDeleteLineBackward(nativeEvent)) {
1210
- event.preventDefault();
1211
- if (selection && Range.isExpanded(selection)) {
1212
- editor.exec({ type: 'delete_fragment' });
1213
- }
1214
- else {
1215
- editor.exec({ type: 'delete_backward', unit: 'line' });
1216
- }
1217
- return;
1218
- }
1219
- if (Hotkeys.isDeleteLineForward(nativeEvent)) {
1220
- event.preventDefault();
1221
- if (selection && Range.isExpanded(selection)) {
1222
- editor.exec({ type: 'delete_fragment' });
1223
- }
1224
- else {
1225
- editor.exec({ type: 'delete_forward', unit: 'line' });
1226
- }
1227
- return;
1228
- }
1229
- if (Hotkeys.isDeleteWordBackward(nativeEvent)) {
1230
- event.preventDefault();
1231
- if (selection && Range.isExpanded(selection)) {
1232
- editor.exec({ type: 'delete_fragment' });
1233
- }
1234
- else {
1235
- editor.exec({ type: 'delete_backward', unit: 'word' });
1236
- }
1237
- return;
1238
- }
1239
- if (Hotkeys.isDeleteWordForward(nativeEvent)) {
1240
- event.preventDefault();
1241
- if (selection && Range.isExpanded(selection)) {
1242
- editor.exec({ type: 'delete_fragment' });
1243
- }
1244
- else {
1245
- editor.exec({ type: 'delete_forward', unit: 'word' });
1246
- }
1247
- return;
1248
- }
1249
- }
1250
- }
1251
- }, [readOnly, attributes.onKeyDown]), onPaste: useCallback((event) => {
1252
- // COMPAT: Firefox doesn't support the `beforeinput` event, so we
1253
- // fall back to React's `onPaste` here instead.
1254
- if (IS_FIREFOX &&
1255
- !readOnly &&
1256
- hasEditableTarget(editor, event.target) &&
1257
- !isEventHandled(event, attributes.onPaste)) {
1258
- event.preventDefault();
1259
- editor.exec({
1260
- type: 'insert_data',
1261
- data: event.clipboardData,
1262
- });
1263
- }
1264
- }, [readOnly, attributes.onPaste]) }),
1265
- React.createElement(Children, { decorate: decorate, decorations: decorations, node: editor, renderElement: renderElement, renderLeaf: renderLeaf, selection: editor.selection }))));
1266
- };
1267
- /**
1268
- * A default memoized decorate function.
1269
- */
1270
- const defaultDecorate = () => [];
1271
- /**
1272
- * Check if two DOM range objects are equal.
1273
- */
1274
- const isRangeEqual = (a, b) => {
1275
- return ((a.startContainer === b.startContainer &&
1276
- a.startOffset === b.startOffset &&
1277
- a.endContainer === b.endContainer &&
1278
- a.endOffset === b.endOffset) ||
1279
- (a.startContainer === b.endContainer &&
1280
- a.startOffset === b.endOffset &&
1281
- a.endContainer === b.startContainer &&
1282
- a.endOffset === b.startOffset));
1283
- };
1284
- /**
1285
- * Check if the target is in the editor.
1286
- */
1287
- const hasTarget = (editor, target) => {
1288
- return isDOMNode(target) && ReactEditor.hasDOMNode(editor, target);
1289
- };
1290
- /**
1291
- * Check if the target is editable and in the editor.
1292
- */
1293
- const hasEditableTarget = (editor, target) => {
1294
- return (isDOMNode(target) &&
1295
- ReactEditor.hasDOMNode(editor, target, { editable: true }));
1296
- };
1297
- /**
1298
- * Check if an event is overrided by a handler.
1299
- */
1300
- const isEventHandled = (event, handler) => {
1301
- if (!handler) {
1302
- return false;
1303
- }
1304
- handler(event);
1305
- return event.isDefaultPrevented() || event.isPropagationStopped();
1306
- };
1307
- /**
1308
- * Check if a DOM event is overrided by a handler.
1309
- */
1310
- const isDOMEventHandled = (event, handler) => {
1311
- if (!handler) {
1312
- return false;
1313
- }
1314
- handler(event);
1315
- return event.defaultPrevented;
1316
- };
1317
- /**
1318
- * Set the currently selected fragment to the clipboard.
1319
- */
1320
- const setFragmentData = (dataTransfer, editor) => {
1321
- const { selection } = editor;
1322
- if (!selection) {
1323
- return;
1324
- }
1325
- const [start, end] = Range.edges(selection);
1326
- const startVoid = Editor.void(editor, { at: start.path });
1327
- const endVoid = Editor.void(editor, { at: end.path });
1328
- if (Range.isCollapsed(selection) && !startVoid) {
1329
- return;
1330
- }
1331
- // Create a fake selection so that we can add a Base64-encoded copy of the
1332
- // fragment to the HTML, to decode on future pastes.
1333
- const domRange = ReactEditor.toDOMRange(editor, selection);
1334
- let contents = domRange.cloneContents();
1335
- let attach = contents.childNodes[0];
1336
- // Make sure attach is non-empty, since empty nodes will not get copied.
1337
- contents.childNodes.forEach(node => {
1338
- if (node.textContent && node.textContent.trim() !== '') {
1339
- attach = node;
1340
- }
1341
- });
1342
- // COMPAT: If the end node is a void node, we need to move the end of the
1343
- // range from the void node's spacer span, to the end of the void node's
1344
- // content, since the spacer is before void's content in the DOM.
1345
- if (endVoid) {
1346
- const [voidNode] = endVoid;
1347
- const r = domRange.cloneRange();
1348
- const domNode = ReactEditor.toDOMNode(editor, voidNode);
1349
- r.setEndAfter(domNode);
1350
- contents = r.cloneContents();
1351
- }
1352
- // COMPAT: If the start node is a void node, we need to attach the encoded
1353
- // fragment to the void node's content node instead of the spacer, because
1354
- // attaching it to empty `<div>/<span>` nodes will end up having it erased by
1355
- // most browsers. (2018/04/27)
1356
- if (startVoid) {
1357
- attach = contents.querySelector('[data-slate-spacer]');
1358
- }
1359
- // Remove any zero-width space spans from the cloned DOM so that they don't
1360
- // show up elsewhere when pasted.
1361
- Array.from(contents.querySelectorAll('[data-slate-zero-width]')).forEach(zw => {
1362
- const isNewline = zw.getAttribute('data-slate-zero-width') === 'n';
1363
- zw.textContent = isNewline ? '\n' : '';
1364
- });
1365
- // Set a `data-slate-fragment` attribute on a non-empty node, so it shows up
1366
- // in the HTML, and can be used for intra-Slate pasting. If it's a text
1367
- // node, wrap it in a `<span>` so we have something to set an attribute on.
1368
- if (isDOMText(attach)) {
1369
- const span = document.createElement('span');
1370
- // COMPAT: In Chrome and Safari, if we don't add the `white-space` style
1371
- // then leading and trailing spaces will be ignored. (2017/09/21)
1372
- span.style.whiteSpace = 'pre';
1373
- span.appendChild(attach);
1374
- contents.appendChild(span);
1375
- attach = span;
1376
- }
1377
- const fragment = Node$1.fragment(editor, selection);
1378
- const string = JSON.stringify(fragment);
1379
- const encoded = window.btoa(encodeURIComponent(string));
1380
- attach.setAttribute('data-slate-fragment', encoded);
1381
- dataTransfer.setData('application/x-slate-fragment', encoded);
1382
- // Add the content to a <div> so that we can get its inner HTML.
1383
- const div = document.createElement('div');
1384
- div.appendChild(contents);
1385
- dataTransfer.setData('text/html', div.innerHTML);
1386
- dataTransfer.setData('text/plain', getPlainText(div));
1387
- };
1388
212
  /**
1389
213
  * Get a plaintext representation of the content of a node, accounting for block
1390
214
  * elements which get a newline appended.
1391
- */
1392
- const getPlainText = (domNode) => {
1393
- let text = '';
1394
- if (isDOMText(domNode) && domNode.nodeValue) {
1395
- return domNode.nodeValue;
1396
- }
1397
- if (isDOMElement(domNode)) {
1398
- for (const childNode of Array.from(domNode.childNodes)) {
1399
- text += getPlainText(childNode);
1400
- }
1401
- const display = getComputedStyle(domNode).getPropertyValue('display');
1402
- if (display === 'block' || display === 'list' || domNode.tagName === 'BR') {
1403
- text += '\n';
1404
- }
1405
- }
1406
- return text;
1407
- };
215
+ *
216
+ * The domNode must be attached to the DOM.
217
+ */
1408
218
 
1409
- function _arrayWithoutHoles(arr) {
1410
- if (Array.isArray(arr)) {
1411
- for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) {
1412
- arr2[i] = arr[i];
1413
- }
219
+ var getPlainText = domNode => {
220
+ var text = '';
1414
221
 
1415
- return arr2;
222
+ if (isDOMText(domNode) && domNode.nodeValue) {
223
+ return domNode.nodeValue;
1416
224
  }
1417
- }
1418
-
1419
- var arrayWithoutHoles = _arrayWithoutHoles;
1420
225
 
1421
- function _iterableToArray(iter) {
1422
- if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter);
1423
- }
226
+ if (isDOMElement(domNode)) {
227
+ for (var childNode of Array.from(domNode.childNodes)) {
228
+ text += getPlainText(childNode);
229
+ }
1424
230
 
1425
- var iterableToArray = _iterableToArray;
231
+ var display = getComputedStyle(domNode).getPropertyValue('display');
1426
232
 
1427
- function _nonIterableSpread() {
1428
- throw new TypeError("Invalid attempt to spread non-iterable instance");
1429
- }
233
+ if (display === 'block' || display === 'list' || domNode.tagName === 'BR') {
234
+ text += '\n';
235
+ }
236
+ }
1430
237
 
1431
- var nonIterableSpread = _nonIterableSpread;
238
+ return text;
239
+ };
1432
240
 
1433
- function _toConsumableArray(arr) {
1434
- return arrayWithoutHoles(arr) || iterableToArray(arr) || nonIterableSpread();
1435
- }
241
+ var ReactEditor = {
242
+ /**
243
+ * Find a key for a Slate node.
244
+ */
245
+ findKey(editor, node) {
246
+ var key = NODE_TO_KEY.get(node);
1436
247
 
1437
- var toConsumableArray = _toConsumableArray;
248
+ if (!key) {
249
+ key = new Key();
250
+ NODE_TO_KEY.set(node, key);
251
+ }
1438
252
 
1439
- function _classCallCheck(instance, Constructor) {
1440
- if (!(instance instanceof Constructor)) {
1441
- throw new TypeError("Cannot call a class as a function");
1442
- }
1443
- }
1444
-
1445
- var classCallCheck = _classCallCheck;
1446
-
1447
- /**
1448
- * An auto-incrementing identifier for keys.
1449
- */
1450
- var n = 0;
1451
- /**
1452
- * A class that keeps track of a key string. We use a full class here because we
1453
- * want to be able to use them as keys in `WeakMap` objects.
1454
- */
1455
-
1456
- var Key = function Key() {
1457
- classCallCheck(this, Key);
1458
-
1459
- this.id = "".concat(n++);
1460
- };
1461
-
1462
- var ReactEditor = {
1463
- /**
1464
- * Find a key for a Slate node.
1465
- */
1466
- findKey: function findKey(editor, node) {
1467
- var key = NODE_TO_KEY.get(node);
1468
-
1469
- if (!key) {
1470
- key = new Key();
1471
- NODE_TO_KEY.set(node, key);
1472
- }
1473
-
1474
- return key;
1475
- },
253
+ return key;
254
+ },
1476
255
 
1477
256
  /**
1478
257
  * Find the path of Slate node.
1479
258
  */
1480
- findPath: function findPath(editor, node) {
259
+ findPath(editor, node) {
1481
260
  var path = [];
1482
261
  var child = node;
1483
262
 
@@ -1508,21 +287,21 @@ var ReactEditor = {
1508
287
  /**
1509
288
  * Check if the editor is focused.
1510
289
  */
1511
- isFocused: function isFocused(editor) {
290
+ isFocused(editor) {
1512
291
  return !!IS_FOCUSED.get(editor);
1513
292
  },
1514
293
 
1515
294
  /**
1516
295
  * Check if the editor is in read-only mode.
1517
296
  */
1518
- isReadOnly: function isReadOnly(editor) {
297
+ isReadOnly(editor) {
1519
298
  return !!IS_READ_ONLY.get(editor);
1520
299
  },
1521
300
 
1522
301
  /**
1523
302
  * Blur the editor.
1524
303
  */
1525
- blur: function blur(editor) {
304
+ blur(editor) {
1526
305
  var el = ReactEditor.toDOMNode(editor, editor);
1527
306
  IS_FOCUSED.set(editor, false);
1528
307
 
@@ -1534,7 +313,7 @@ var ReactEditor = {
1534
313
  /**
1535
314
  * Focus the editor.
1536
315
  */
1537
- focus: function focus(editor) {
316
+ focus(editor) {
1538
317
  var el = ReactEditor.toDOMNode(editor, editor);
1539
318
  IS_FOCUSED.set(editor, true);
1540
319
 
@@ -1548,8 +327,10 @@ var ReactEditor = {
1548
327
  /**
1549
328
  * Deselect the editor.
1550
329
  */
1551
- deselect: function deselect(editor) {
1552
- var selection = editor.selection;
330
+ deselect(editor) {
331
+ var {
332
+ selection
333
+ } = editor;
1553
334
  var domSelection = window.getSelection();
1554
335
 
1555
336
  if (domSelection && domSelection.rangeCount > 0) {
@@ -1557,42 +338,57 @@ var ReactEditor = {
1557
338
  }
1558
339
 
1559
340
  if (selection) {
1560
- Editor.deselect(editor);
341
+ Transforms.deselect(editor);
1561
342
  }
1562
343
  },
1563
344
 
1564
345
  /**
1565
346
  * Check if a DOM node is within the editor.
1566
347
  */
1567
- hasDOMNode: function hasDOMNode(editor, target) {
348
+ hasDOMNode(editor, target) {
1568
349
  var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
1569
- var _options$editable = options.editable,
1570
- editable = _options$editable === void 0 ? false : _options$editable;
1571
- var el = ReactEditor.toDOMNode(editor, editor);
1572
- var element; // COMPAT: In Firefox, reading `target.nodeType` will throw an error if
350
+ var {
351
+ editable = false
352
+ } = options;
353
+ var editorEl = ReactEditor.toDOMNode(editor, editor);
354
+ var targetEl; // COMPAT: In Firefox, reading `target.nodeType` will throw an error if
1573
355
  // target is originating from an internal "restricted" element (e.g. a
1574
356
  // stepper arrow on a number input). (2018/05/04)
1575
357
  // https://github.com/ianstormtaylor/slate/issues/1819
1576
358
 
1577
359
  try {
1578
- element = isDOMElement(target) ? target : target.parentElement;
360
+ targetEl = isDOMElement(target) ? target : target.parentElement;
1579
361
  } catch (err) {
1580
362
  if (!err.message.includes('Permission denied to access property "nodeType"')) {
1581
363
  throw err;
1582
364
  }
1583
365
  }
1584
366
 
1585
- if (!element) {
367
+ if (!targetEl) {
1586
368
  return false;
1587
369
  }
1588
370
 
1589
- return element.closest("[data-slate-editor]") === el && (!editable || el.isContentEditable);
371
+ return targetEl.closest("[data-slate-editor]") === editorEl && (!editable || targetEl.isContentEditable || !!targetEl.getAttribute('data-slate-zero-width'));
372
+ },
373
+
374
+ /**
375
+ * Insert data from a `DataTransfer` into the editor.
376
+ */
377
+ insertData(editor, data) {
378
+ editor.insertData(data);
379
+ },
380
+
381
+ /**
382
+ * Sets data from the currently selected fragment on a `DataTransfer`.
383
+ */
384
+ setFragmentData(editor, data) {
385
+ editor.setFragmentData(data);
1590
386
  },
1591
387
 
1592
388
  /**
1593
389
  * Find the native DOM element from a Slate node.
1594
390
  */
1595
- toDOMNode: function toDOMNode(editor, node) {
391
+ toDOMNode(editor, node) {
1596
392
  var domNode = Editor.isEditor(node) ? EDITOR_TO_ELEMENT.get(editor) : KEY_TO_ELEMENT.get(ReactEditor.findKey(editor, node));
1597
393
 
1598
394
  if (!domNode) {
@@ -1605,16 +401,13 @@ var ReactEditor = {
1605
401
  /**
1606
402
  * Find a native DOM selection point from a Slate point.
1607
403
  */
1608
- toDOMPoint: function toDOMPoint(editor, point) {
1609
- var _Editor$node = Editor.node(editor, point.path),
1610
- _Editor$node2 = slicedToArray(_Editor$node, 1),
1611
- node = _Editor$node2[0];
1612
-
404
+ toDOMPoint(editor, point) {
405
+ var [node] = Editor.node(editor, point.path);
1613
406
  var el = ReactEditor.toDOMNode(editor, node);
1614
407
  var domPoint; // If we're inside a void node, force the offset to 0, otherwise the zero
1615
408
  // width spacing character will result in an incorrect offset of 1
1616
409
 
1617
- if (Editor["void"](editor, {
410
+ if (Editor.void(editor, {
1618
411
  at: point
1619
412
  })) {
1620
413
  point = {
@@ -1630,15 +423,16 @@ var ReactEditor = {
1630
423
  var texts = Array.from(el.querySelectorAll(selector));
1631
424
  var start = 0;
1632
425
 
1633
- for (var _i = 0, _texts = texts; _i < _texts.length; _i++) {
1634
- var text = _texts[_i];
426
+ for (var text of texts) {
1635
427
  var domNode = text.childNodes[0];
1636
428
 
1637
429
  if (domNode == null || domNode.textContent == null) {
1638
430
  continue;
1639
431
  }
1640
432
 
1641
- var length = domNode.textContent.length;
433
+ var {
434
+ length
435
+ } = domNode.textContent;
1642
436
  var attr = text.getAttribute('data-slate-length');
1643
437
  var trueLength = attr == null ? length : parseInt(attr, 10);
1644
438
  var end = start + trueLength;
@@ -1662,23 +456,33 @@ var ReactEditor = {
1662
456
  /**
1663
457
  * Find a native DOM range from a Slate `range`.
1664
458
  */
1665
- toDOMRange: function toDOMRange(editor, range) {
1666
- var anchor = range.anchor,
1667
- focus = range.focus;
459
+ toDOMRange(editor, range) {
460
+ var {
461
+ anchor,
462
+ focus
463
+ } = range;
464
+ var isBackward = Range.isBackward(range);
1668
465
  var domAnchor = ReactEditor.toDOMPoint(editor, anchor);
1669
466
  var domFocus = Range.isCollapsed(range) ? domAnchor : ReactEditor.toDOMPoint(editor, focus);
1670
467
  var domRange = window.document.createRange();
1671
- var start = Range.isBackward(range) ? domFocus : domAnchor;
1672
- var end = Range.isBackward(range) ? domAnchor : domFocus;
1673
- domRange.setStart(start[0], start[1]);
1674
- domRange.setEnd(end[0], end[1]);
468
+ var [startNode, startOffset] = isBackward ? domFocus : domAnchor;
469
+ 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
470
+ // zero-width node has an offset of 1 so we have to check if we are in a zero-width node and
471
+ // adjust the offset accordingly.
472
+
473
+ var startEl = isDOMElement(startNode) ? startNode : startNode.parentElement;
474
+ var isStartAtZeroWidth = !!startEl.getAttribute('data-slate-zero-width');
475
+ var endEl = isDOMElement(endNode) ? endNode : endNode.parentElement;
476
+ var isEndAtZeroWidth = !!endEl.getAttribute('data-slate-zero-width');
477
+ domRange.setStart(startNode, isStartAtZeroWidth ? 1 : startOffset);
478
+ domRange.setEnd(endNode, isEndAtZeroWidth ? 1 : endOffset);
1675
479
  return domRange;
1676
480
  },
1677
481
 
1678
482
  /**
1679
483
  * Find a Slate node from a native DOM `element`.
1680
484
  */
1681
- toSlateNode: function toSlateNode(editor, domNode) {
485
+ toSlateNode(editor, domNode) {
1682
486
  var domEl = isDOMElement(domNode) ? domNode : domNode.parentElement;
1683
487
 
1684
488
  if (domEl && !domEl.hasAttribute('data-slate-node')) {
@@ -1697,15 +501,16 @@ var ReactEditor = {
1697
501
  /**
1698
502
  * Get the target range from a DOM `event`.
1699
503
  */
1700
- findEventRange: function findEventRange(editor, event) {
504
+ findEventRange(editor, event) {
1701
505
  if ('nativeEvent' in event) {
1702
506
  event = event.nativeEvent;
1703
507
  }
1704
508
 
1705
- var _event = event,
1706
- x = _event.clientX,
1707
- y = _event.clientY,
1708
- target = _event.target;
509
+ var {
510
+ clientX: x,
511
+ clientY: y,
512
+ target
513
+ } = event;
1709
514
 
1710
515
  if (x == null || y == null) {
1711
516
  throw new Error("Cannot resolve a Slate range from a DOM event: ".concat(event));
@@ -1733,8 +538,9 @@ var ReactEditor = {
1733
538
 
1734
539
 
1735
540
  var domRange;
1736
- var _window = window,
1737
- document = _window.document; // COMPAT: In Firefox, `caretRangeFromPoint` doesn't exist. (2016/07/25)
541
+ var {
542
+ document
543
+ } = window; // COMPAT: In Firefox, `caretRangeFromPoint` doesn't exist. (2016/07/25)
1738
544
 
1739
545
  if (document.caretRangeFromPoint) {
1740
546
  domRange = document.caretRangeFromPoint(x, y);
@@ -1760,12 +566,8 @@ var ReactEditor = {
1760
566
  /**
1761
567
  * Find a Slate point from a DOM selection's `domNode` and `domOffset`.
1762
568
  */
1763
- toSlatePoint: function toSlatePoint(editor, domPoint) {
1764
- var _normalizeDOMPoint = normalizeDOMPoint(domPoint),
1765
- _normalizeDOMPoint2 = slicedToArray(_normalizeDOMPoint, 2),
1766
- nearestNode = _normalizeDOMPoint2[0],
1767
- nearestOffset = _normalizeDOMPoint2[1];
1768
-
569
+ toSlatePoint(editor, domPoint) {
570
+ var [nearestNode, nearestOffset] = normalizeDOMPoint(domPoint);
1769
571
  var parentNode = nearestNode.parentNode;
1770
572
  var textNode = null;
1771
573
  var offset = 0;
@@ -1782,8 +584,8 @@ var ReactEditor = {
1782
584
  range.setStart(textNode, 0);
1783
585
  range.setEnd(nearestNode, nearestOffset);
1784
586
  var contents = range.cloneContents();
1785
- var removals = [].concat(toConsumableArray(contents.querySelectorAll('[data-slate-zero-width]')), toConsumableArray(contents.querySelectorAll('[contenteditable=false]')));
1786
- removals.forEach(function (el) {
587
+ var removals = [...contents.querySelectorAll('[data-slate-zero-width]'), ...contents.querySelectorAll('[contenteditable=false]')];
588
+ removals.forEach(el => {
1787
589
  el.parentNode.removeChild(el);
1788
590
  }); // COMPAT: Edge has a bug where Range.prototype.toString() will
1789
591
  // convert \n into \r\n. The bug causes a loop when slate-react
@@ -1822,15 +624,15 @@ var ReactEditor = {
1822
624
  var slateNode = ReactEditor.toSlateNode(editor, textNode);
1823
625
  var path = ReactEditor.findPath(editor, slateNode);
1824
626
  return {
1825
- path: path,
1826
- offset: offset
627
+ path,
628
+ offset
1827
629
  };
1828
630
  },
1829
631
 
1830
632
  /**
1831
633
  * Find a Slate range from a DOM range or selection.
1832
634
  */
1833
- toSlateRange: function toSlateRange(editor, domRange) {
635
+ toSlateRange(editor, domRange) {
1834
636
  var el = domRange instanceof Selection ? domRange.anchorNode : domRange.startContainer;
1835
637
  var anchorNode;
1836
638
  var anchorOffset;
@@ -1861,10 +663,11 @@ var ReactEditor = {
1861
663
  var anchor = ReactEditor.toSlatePoint(editor, [anchorNode, anchorOffset]);
1862
664
  var focus = isCollapsed ? anchor : ReactEditor.toSlatePoint(editor, [focusNode, focusOffset]);
1863
665
  return {
1864
- anchor: anchor,
1865
- focus: focus
666
+ anchor,
667
+ focus
1866
668
  };
1867
669
  }
670
+
1868
671
  };
1869
672
 
1870
673
  /**
@@ -1876,58 +679,127 @@ var FocusedContext = createContext(false);
1876
679
  * Get the current `focused` state of the editor.
1877
680
  */
1878
681
 
1879
- var useFocused = function useFocused() {
682
+ var useFocused = () => {
1880
683
  return useContext(FocusedContext);
1881
684
  };
1882
685
 
686
+ /**
687
+ * A React context for sharing the editor object.
688
+ */
689
+
690
+ var EditorContext = createContext(null);
691
+ /**
692
+ * Get the current editor object from the React context.
693
+ */
694
+
695
+ var useEditor = () => {
696
+ var editor = useContext(EditorContext);
697
+
698
+ if (!editor) {
699
+ throw new Error("The `useEditor` hook must be used inside the <Slate> component's context.");
700
+ }
701
+
702
+ return editor;
703
+ };
704
+
705
+ /**
706
+ * A React context for sharing the editor object, in a way that re-renders the
707
+ * context whenever changes occur.
708
+ */
709
+
710
+ var SlateContext = createContext(null);
711
+ /**
712
+ * Get the current editor object from the React context.
713
+ */
714
+
715
+ var useSlate = () => {
716
+ var context = useContext(SlateContext);
717
+
718
+ if (!context) {
719
+ throw new Error("The `useSlate` hook must be used inside the <SlateProvider> component's context.");
720
+ }
721
+
722
+ var [editor] = context;
723
+ return editor;
724
+ };
725
+
1883
726
  /**
1884
727
  * A wrapper around the provider to handle `onChange` events, because the editor
1885
728
  * is a mutable singleton so it won't ever register as "changed" otherwise.
1886
- */
1887
- const Slate = (props) => {
1888
- const { editor, children, onChange, value, ...rest } = props;
1889
- const [key, setKey] = useState(0);
1890
- const context = useMemo(() => {
1891
- editor.children = value;
1892
- Object.assign(editor, rest);
1893
- return [editor];
1894
- }, [key, value, ...Object.values(rest)]);
1895
- const onContextChange = useCallback(() => {
1896
- onChange(editor.children);
1897
- setKey(key + 1);
1898
- }, [key, onChange]);
1899
- EDITOR_TO_ON_CHANGE.set(editor, onContextChange);
1900
- return (React.createElement(SlateContext.Provider, { value: context },
1901
- React.createElement(EditorContext.Provider, { value: editor },
1902
- React.createElement(FocusedContext.Provider, { value: ReactEditor.isFocused(editor) }, children))));
729
+ */
730
+
731
+ var Slate = props => {
732
+ var {
733
+ editor,
734
+ children,
735
+ onChange,
736
+ value
737
+ } = props,
738
+ rest = _objectWithoutProperties(props, ["editor", "children", "onChange", "value"]);
739
+
740
+ var [key, setKey] = useState(0);
741
+ var context = useMemo(() => {
742
+ editor.children = value;
743
+ Object.assign(editor, rest);
744
+ return [editor];
745
+ }, [key, value, ...Object.values(rest)]);
746
+ var onContextChange = useCallback(() => {
747
+ onChange(editor.children);
748
+ setKey(key + 1);
749
+ }, [key, onChange]);
750
+ EDITOR_TO_ON_CHANGE.set(editor, onContextChange);
751
+ useEffect(() => {
752
+ return () => {
753
+ EDITOR_TO_ON_CHANGE.set(editor, () => {});
754
+ };
755
+ }, []);
756
+ return React.createElement(SlateContext.Provider, {
757
+ value: context
758
+ }, React.createElement(EditorContext.Provider, {
759
+ value: editor
760
+ }, React.createElement(FocusedContext.Provider, {
761
+ value: ReactEditor.isFocused(editor)
762
+ }, children)));
1903
763
  };
1904
764
 
1905
- var ReactCommand = {
1906
- /**
1907
- * Check if a value is a `ReactCommand` object.
1908
- */
1909
- isReactCommand: function isReactCommand(value) {
1910
- if (Command.isCommand(value)) {
1911
- switch (value.type) {
1912
- case 'insert_data':
1913
- return value.data instanceof DataTransfer;
1914
- }
1915
- }
765
+ /**
766
+ * A React context for sharing the `readOnly` state of the editor.
767
+ */
1916
768
 
1917
- return false;
1918
- }
769
+ var ReadOnlyContext = createContext(false);
770
+ /**
771
+ * Get the current `readOnly` state of the editor.
772
+ */
773
+
774
+ var useReadOnly = () => {
775
+ return useContext(ReadOnlyContext);
776
+ };
777
+
778
+ /**
779
+ * A React context for sharing the `selected` state of an element.
780
+ */
781
+
782
+ var SelectedContext = createContext(false);
783
+ /**
784
+ * Get the current `selected` state of an element.
785
+ */
786
+
787
+ var useSelected = () => {
788
+ return useContext(SelectedContext);
1919
789
  };
1920
790
 
1921
791
  /**
1922
792
  * `withReact` adds React and DOM specific behaviors to the editor.
1923
793
  */
1924
794
 
1925
- var withReact = function withReact(editor) {
1926
- var apply = editor.apply,
1927
- exec = editor.exec,
1928
- onChange = editor.onChange;
795
+ var withReact = editor => {
796
+ var e = editor;
797
+ var {
798
+ apply,
799
+ onChange
800
+ } = e;
1929
801
 
1930
- editor.apply = function (op) {
802
+ e.apply = op => {
1931
803
  var matches = [];
1932
804
 
1933
805
  switch (op.type) {
@@ -1935,34 +807,11 @@ var withReact = function withReact(editor) {
1935
807
  case 'remove_text':
1936
808
  case 'set_node':
1937
809
  {
1938
- var _iteratorNormalCompletion = true;
1939
- var _didIteratorError = false;
1940
- var _iteratorError = undefined;
1941
-
1942
- try {
1943
- for (var _iterator = Editor.levels(editor, {
1944
- at: op.path
1945
- })[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
1946
- var _step$value = slicedToArray(_step.value, 2),
1947
- node = _step$value[0],
1948
- path = _step$value[1];
1949
-
1950
- var key = ReactEditor.findKey(editor, node);
1951
- matches.push([path, key]);
1952
- }
1953
- } catch (err) {
1954
- _didIteratorError = true;
1955
- _iteratorError = err;
1956
- } finally {
1957
- try {
1958
- if (!_iteratorNormalCompletion && _iterator["return"] != null) {
1959
- _iterator["return"]();
1960
- }
1961
- } finally {
1962
- if (_didIteratorError) {
1963
- throw _iteratorError;
1964
- }
1965
- }
810
+ for (var [node, path] of Editor.levels(e, {
811
+ at: op.path
812
+ })) {
813
+ var key = ReactEditor.findKey(e, node);
814
+ matches.push([path, key]);
1966
815
  }
1967
816
 
1968
817
  break;
@@ -1973,35 +822,12 @@ var withReact = function withReact(editor) {
1973
822
  case 'merge_node':
1974
823
  case 'split_node':
1975
824
  {
1976
- var _iteratorNormalCompletion2 = true;
1977
- var _didIteratorError2 = false;
1978
- var _iteratorError2 = undefined;
825
+ for (var [_node, _path] of Editor.levels(e, {
826
+ at: Path.parent(op.path)
827
+ })) {
828
+ var _key = ReactEditor.findKey(e, _node);
1979
829
 
1980
- try {
1981
- for (var _iterator2 = Editor.levels(editor, {
1982
- at: Path.parent(op.path)
1983
- })[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
1984
- var _step2$value = slicedToArray(_step2.value, 2),
1985
- _node = _step2$value[0],
1986
- _path = _step2$value[1];
1987
-
1988
- var _key = ReactEditor.findKey(editor, _node);
1989
-
1990
- matches.push([_path, _key]);
1991
- }
1992
- } catch (err) {
1993
- _didIteratorError2 = true;
1994
- _iteratorError2 = err;
1995
- } finally {
1996
- try {
1997
- if (!_iteratorNormalCompletion2 && _iterator2["return"] != null) {
1998
- _iterator2["return"]();
1999
- }
2000
- } finally {
2001
- if (_didIteratorError2) {
2002
- throw _iteratorError2;
2003
- }
2004
- }
830
+ matches.push([_path, _key]);
2005
831
  }
2006
832
 
2007
833
  break;
@@ -2010,78 +836,133 @@ var withReact = function withReact(editor) {
2010
836
 
2011
837
  apply(op);
2012
838
 
2013
- for (var _i = 0, _matches = matches; _i < _matches.length; _i++) {
2014
- var _matches$_i = slicedToArray(_matches[_i], 2),
2015
- _path2 = _matches$_i[0],
2016
- _key2 = _matches$_i[1];
2017
-
2018
- var _Editor$node = Editor.node(editor, _path2),
2019
- _Editor$node2 = slicedToArray(_Editor$node, 1),
2020
- _node2 = _Editor$node2[0];
2021
-
839
+ for (var [_path2, _key2] of matches) {
840
+ var [_node2] = Editor.node(e, _path2);
2022
841
  NODE_TO_KEY.set(_node2, _key2);
2023
842
  }
2024
843
  };
2025
844
 
2026
- editor.exec = function (command) {
2027
- if (ReactCommand.isReactCommand(command) && command.type === 'insert_data') {
2028
- var data = command.data;
2029
- var fragment = data.getData('application/x-slate-fragment');
845
+ e.setFragmentData = data => {
846
+ var {
847
+ selection
848
+ } = e;
2030
849
 
2031
- if (fragment) {
2032
- var decoded = decodeURIComponent(window.atob(fragment));
2033
- var parsed = JSON.parse(decoded);
2034
- Editor.insertFragment(editor, parsed);
2035
- return;
850
+ if (!selection) {
851
+ return;
852
+ }
853
+
854
+ var [start, end] = Range.edges(selection);
855
+ var startVoid = Editor.void(e, {
856
+ at: start.path
857
+ });
858
+ var endVoid = Editor.void(e, {
859
+ at: end.path
860
+ });
861
+
862
+ if (Range.isCollapsed(selection) && !startVoid) {
863
+ return;
864
+ } // Create a fake selection so that we can add a Base64-encoded copy of the
865
+ // fragment to the HTML, to decode on future pastes.
866
+
867
+
868
+ var domRange = ReactEditor.toDOMRange(e, selection);
869
+ var contents = domRange.cloneContents();
870
+ var attach = contents.childNodes[0]; // Make sure attach is non-empty, since empty nodes will not get copied.
871
+
872
+ contents.childNodes.forEach(node => {
873
+ if (node.textContent && node.textContent.trim() !== '') {
874
+ attach = node;
2036
875
  }
876
+ }); // COMPAT: If the end node is a void node, we need to move the end of the
877
+ // range from the void node's spacer span, to the end of the void node's
878
+ // content, since the spacer is before void's content in the DOM.
879
+
880
+ if (endVoid) {
881
+ var [voidNode] = endVoid;
882
+ var r = domRange.cloneRange();
883
+ var domNode = ReactEditor.toDOMNode(e, voidNode);
884
+ r.setEndAfter(domNode);
885
+ contents = r.cloneContents();
886
+ } // COMPAT: If the start node is a void node, we need to attach the encoded
887
+ // fragment to the void node's content node instead of the spacer, because
888
+ // attaching it to empty `<div>/<span>` nodes will end up having it erased by
889
+ // most browsers. (2018/04/27)
890
+
891
+
892
+ if (startVoid) {
893
+ attach = contents.querySelector('[data-slate-spacer]');
894
+ } // Remove any zero-width space spans from the cloned DOM so that they don't
895
+ // show up elsewhere when pasted.
896
+
897
+
898
+ Array.from(contents.querySelectorAll('[data-slate-zero-width]')).forEach(zw => {
899
+ var isNewline = zw.getAttribute('data-slate-zero-width') === 'n';
900
+ zw.textContent = isNewline ? '\n' : '';
901
+ }); // Set a `data-slate-fragment` attribute on a non-empty node, so it shows up
902
+ // in the HTML, and can be used for intra-Slate pasting. If it's a text
903
+ // node, wrap it in a `<span>` so we have something to set an attribute on.
904
+
905
+ if (isDOMText(attach)) {
906
+ var span = document.createElement('span'); // COMPAT: In Chrome and Safari, if we don't add the `white-space` style
907
+ // then leading and trailing spaces will be ignored. (2017/09/21)
908
+
909
+ span.style.whiteSpace = 'pre';
910
+ span.appendChild(attach);
911
+ contents.appendChild(span);
912
+ attach = span;
913
+ }
2037
914
 
2038
- var text = data.getData('text/plain');
915
+ var fragment = e.getFragment();
916
+ var string = JSON.stringify(fragment);
917
+ var encoded = window.btoa(encodeURIComponent(string));
918
+ attach.setAttribute('data-slate-fragment', encoded);
919
+ data.setData('application/x-slate-fragment', encoded); // Add the content to a <div> so that we can get its inner HTML.
920
+
921
+ var div = document.createElement('div');
922
+ div.appendChild(contents);
923
+ div.setAttribute('hidden', 'true');
924
+ document.body.appendChild(div);
925
+ data.setData('text/html', div.innerHTML);
926
+ data.setData('text/plain', getPlainText(div));
927
+ document.body.removeChild(div);
928
+ };
2039
929
 
2040
- if (text) {
2041
- var lines = text.split('\n');
2042
- var split = false;
2043
- var _iteratorNormalCompletion3 = true;
2044
- var _didIteratorError3 = false;
2045
- var _iteratorError3 = undefined;
930
+ e.insertData = data => {
931
+ var fragment = data.getData('application/x-slate-fragment');
2046
932
 
2047
- try {
2048
- for (var _iterator3 = lines[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
2049
- var line = _step3.value;
933
+ if (fragment) {
934
+ var decoded = decodeURIComponent(window.atob(fragment));
935
+ var parsed = JSON.parse(decoded);
936
+ Transforms.insertFragment(e, parsed);
937
+ return;
938
+ }
2050
939
 
2051
- if (split) {
2052
- Editor.splitNodes(editor);
2053
- }
940
+ var text = data.getData('text/plain');
2054
941
 
2055
- Editor.insertText(editor, line);
2056
- split = true;
2057
- }
2058
- } catch (err) {
2059
- _didIteratorError3 = true;
2060
- _iteratorError3 = err;
2061
- } finally {
2062
- try {
2063
- if (!_iteratorNormalCompletion3 && _iterator3["return"] != null) {
2064
- _iterator3["return"]();
2065
- }
2066
- } finally {
2067
- if (_didIteratorError3) {
2068
- throw _iteratorError3;
2069
- }
2070
- }
942
+ if (text) {
943
+ var lines = text.split(/\r\n|\r|\n/);
944
+ var split = false;
945
+
946
+ for (var line of lines) {
947
+ if (split) {
948
+ Transforms.splitNodes(e, {
949
+ always: true
950
+ });
2071
951
  }
952
+
953
+ Transforms.insertText(e, line);
954
+ split = true;
2072
955
  }
2073
- } else {
2074
- exec(command);
2075
956
  }
2076
957
  };
2077
958
 
2078
- editor.onChange = function () {
959
+ e.onChange = () => {
2079
960
  // COMPAT: React doesn't batch `setState` hook calls, which means that the
2080
961
  // children and selection can get out of sync for one render pass. So we
2081
962
  // have to use this unstable API to ensure it batches them. (2019/12/03)
2082
963
  // https://github.com/facebook/react/issues/14259#issuecomment-439702367
2083
- ReactDOM.unstable_batchedUpdates(function () {
2084
- var onContextChange = EDITOR_TO_ON_CHANGE.get(editor);
964
+ ReactDOM.unstable_batchedUpdates(() => {
965
+ var onContextChange = EDITOR_TO_ON_CHANGE.get(e);
2085
966
 
2086
967
  if (onContextChange) {
2087
968
  onContextChange();
@@ -2091,8 +972,1304 @@ var withReact = function withReact(editor) {
2091
972
  });
2092
973
  };
2093
974
 
2094
- return editor;
975
+ return e;
976
+ };
977
+
978
+ /**
979
+ * Leaf content strings.
980
+ */
981
+
982
+ var String = props => {
983
+ var {
984
+ isLast,
985
+ leaf,
986
+ parent,
987
+ text
988
+ } = props;
989
+ var editor = useEditor();
990
+ var path = ReactEditor.findPath(editor, text);
991
+ var parentPath = Path.parent(path); // COMPAT: Render text inside void nodes with a zero-width space.
992
+ // So the node can contain selection but the text is not visible.
993
+
994
+ if (editor.isVoid(parent)) {
995
+ return React.createElement(ZeroWidthString, {
996
+ length: Node$1.string(parent).length
997
+ });
998
+ } // COMPAT: If this is the last text node in an empty block, render a zero-
999
+ // width space that will convert into a line break when copying and pasting
1000
+ // to support expected plain text.
1001
+
1002
+
1003
+ if (leaf.text === '' && parent.children[parent.children.length - 1] === text && !editor.isInline(parent) && Editor.string(editor, parentPath) === '') {
1004
+ return React.createElement(ZeroWidthString, {
1005
+ isLineBreak: true
1006
+ });
1007
+ } // COMPAT: If the text is empty, it's because it's on the edge of an inline
1008
+ // node, so we render a zero-width space so that the selection can be
1009
+ // inserted next to it still.
1010
+
1011
+
1012
+ if (leaf.text === '') {
1013
+ return React.createElement(ZeroWidthString, null);
1014
+ } // COMPAT: Browsers will collapse trailing new lines at the end of blocks,
1015
+ // so we need to add an extra trailing new lines to prevent that.
1016
+
1017
+
1018
+ if (isLast && leaf.text.slice(-1) === '\n') {
1019
+ return React.createElement(TextString, {
1020
+ isTrailing: true,
1021
+ text: leaf.text
1022
+ });
1023
+ }
1024
+
1025
+ return React.createElement(TextString, {
1026
+ text: leaf.text
1027
+ });
1028
+ };
1029
+ /**
1030
+ * Leaf strings with text in them.
1031
+ */
1032
+
1033
+
1034
+ var TextString = props => {
1035
+ var {
1036
+ text,
1037
+ isTrailing = false
1038
+ } = props;
1039
+ return React.createElement("span", {
1040
+ "data-slate-string": true
1041
+ }, text, isTrailing ? '\n' : null);
1042
+ };
1043
+ /**
1044
+ * Leaf strings without text, render as zero-width strings.
1045
+ */
1046
+
1047
+
1048
+ var ZeroWidthString = props => {
1049
+ var {
1050
+ length = 0,
1051
+ isLineBreak = false
1052
+ } = props;
1053
+ return React.createElement("span", {
1054
+ "data-slate-zero-width": isLineBreak ? 'n' : 'z',
1055
+ "data-slate-length": length
1056
+ }, '\uFEFF', isLineBreak ? React.createElement("br", null) : null);
1057
+ };
1058
+
1059
+ /**
1060
+ * Individual leaves in a text node with unique formatting.
1061
+ */
1062
+
1063
+ var Leaf = props => {
1064
+ var {
1065
+ leaf,
1066
+ isLast,
1067
+ text,
1068
+ parent,
1069
+ renderLeaf = props => React.createElement(DefaultLeaf, Object.assign({}, props))
1070
+ } = props;
1071
+ var children = React.createElement(String, {
1072
+ isLast: isLast,
1073
+ leaf: leaf,
1074
+ parent: parent,
1075
+ text: text
1076
+ });
1077
+
1078
+ if (leaf[PLACEHOLDER_SYMBOL]) {
1079
+ children = React.createElement(React.Fragment, null, React.createElement("span", {
1080
+ contentEditable: false,
1081
+ style: {
1082
+ pointerEvents: 'none',
1083
+ display: 'inline-block',
1084
+ verticalAlign: 'text-top',
1085
+ width: '0',
1086
+ maxWidth: '100%',
1087
+ whiteSpace: 'nowrap',
1088
+ opacity: '0.333'
1089
+ }
1090
+ }, leaf.placeholder), children);
1091
+ } // COMPAT: Having the `data-` attributes on these leaf elements ensures that
1092
+ // in certain misbehaving browsers they aren't weirdly cloned/destroyed by
1093
+ // contenteditable behaviors. (2019/05/08)
1094
+
1095
+
1096
+ var attributes = {
1097
+ 'data-slate-leaf': true
1098
+ };
1099
+ return renderLeaf({
1100
+ attributes,
1101
+ children,
1102
+ leaf,
1103
+ text
1104
+ });
1105
+ };
1106
+
1107
+ var MemoizedLeaf = React.memo(Leaf, (prev, next) => {
1108
+ return next.parent === prev.parent && next.isLast === prev.isLast && next.renderLeaf === prev.renderLeaf && next.text === prev.text && Text$1.matches(next.leaf, prev.leaf);
1109
+ });
1110
+ /**
1111
+ * The default custom leaf renderer.
1112
+ */
1113
+
1114
+ var DefaultLeaf = props => {
1115
+ var {
1116
+ attributes,
1117
+ children
1118
+ } = props;
1119
+ return React.createElement("span", Object.assign({}, attributes), children);
1120
+ };
1121
+
1122
+ /**
1123
+ * Prevent warning on SSR by falling back to useEffect when window is not defined
1124
+ */
1125
+
1126
+ var useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
1127
+
1128
+ /**
1129
+ * Text.
1130
+ */
1131
+
1132
+ var Text = props => {
1133
+ var {
1134
+ decorations,
1135
+ isLast,
1136
+ parent,
1137
+ renderLeaf,
1138
+ text
1139
+ } = props;
1140
+ var editor = useEditor();
1141
+ var ref = useRef(null);
1142
+ var leaves = Text$1.decorations(text, decorations);
1143
+ var key = ReactEditor.findKey(editor, text);
1144
+ var children = [];
1145
+
1146
+ for (var i = 0; i < leaves.length; i++) {
1147
+ var leaf = leaves[i];
1148
+ children.push(React.createElement(MemoizedLeaf, {
1149
+ isLast: isLast && i === leaves.length - 1,
1150
+ key: "".concat(key.id, "-").concat(i),
1151
+ leaf: leaf,
1152
+ text: text,
1153
+ parent: parent,
1154
+ renderLeaf: renderLeaf
1155
+ }));
1156
+ } // Update element-related weak maps with the DOM element ref.
1157
+
1158
+
1159
+ useIsomorphicLayoutEffect(() => {
1160
+ if (ref.current) {
1161
+ KEY_TO_ELEMENT.set(key, ref.current);
1162
+ NODE_TO_ELEMENT.set(text, ref.current);
1163
+ ELEMENT_TO_NODE.set(ref.current, text);
1164
+ } else {
1165
+ KEY_TO_ELEMENT.delete(key);
1166
+ NODE_TO_ELEMENT.delete(text);
1167
+ }
1168
+ });
1169
+ return React.createElement("span", {
1170
+ "data-slate-node": "text",
1171
+ ref: ref
1172
+ }, children);
1173
+ };
1174
+
1175
+ var MemoizedText = React.memo(Text, (prev, next) => {
1176
+ return next.parent === prev.parent && next.isLast === prev.isLast && next.renderLeaf === prev.renderLeaf && next.text === prev.text;
1177
+ });
1178
+
1179
+ /**
1180
+ * Element.
1181
+ */
1182
+
1183
+ var Element = props => {
1184
+ var {
1185
+ decorate,
1186
+ decorations,
1187
+ element,
1188
+ renderElement = p => React.createElement(DefaultElement, Object.assign({}, p)),
1189
+ renderLeaf,
1190
+ selection
1191
+ } = props;
1192
+ var ref = useRef(null);
1193
+ var editor = useEditor();
1194
+ var readOnly = useReadOnly();
1195
+ var isInline = editor.isInline(element);
1196
+ var key = ReactEditor.findKey(editor, element);
1197
+ var children = React.createElement(Children, {
1198
+ decorate: decorate,
1199
+ decorations: decorations,
1200
+ node: element,
1201
+ renderElement: renderElement,
1202
+ renderLeaf: renderLeaf,
1203
+ selection: selection
1204
+ }); // Attributes that the developer must mix into the element in their
1205
+ // custom node renderer component.
1206
+
1207
+ var attributes = {
1208
+ 'data-slate-node': 'element',
1209
+ ref
1210
+ };
1211
+
1212
+ if (isInline) {
1213
+ attributes['data-slate-inline'] = true;
1214
+ } // If it's a block node with inline children, add the proper `dir` attribute
1215
+ // for text direction.
1216
+
1217
+
1218
+ if (!isInline && Editor.hasInlines(editor, element)) {
1219
+ var text = Node$1.string(element);
1220
+ var dir = getDirection(text);
1221
+
1222
+ if (dir === 'rtl') {
1223
+ attributes.dir = dir;
1224
+ }
1225
+ } // If it's a void node, wrap the children in extra void-specific elements.
1226
+
1227
+
1228
+ if (Editor.isVoid(editor, element)) {
1229
+ attributes['data-slate-void'] = true;
1230
+
1231
+ if (!readOnly && isInline) {
1232
+ attributes.contentEditable = false;
1233
+ }
1234
+
1235
+ var Tag = isInline ? 'span' : 'div';
1236
+ var [[_text]] = Node$1.texts(element);
1237
+ children = readOnly ? null : React.createElement(Tag, {
1238
+ "data-slate-spacer": true,
1239
+ style: {
1240
+ height: '0',
1241
+ color: 'transparent',
1242
+ outline: 'none',
1243
+ position: 'absolute'
1244
+ }
1245
+ }, React.createElement(MemoizedText, {
1246
+ decorations: [],
1247
+ isLast: false,
1248
+ parent: element,
1249
+ text: _text
1250
+ }));
1251
+ NODE_TO_INDEX.set(_text, 0);
1252
+ NODE_TO_PARENT.set(_text, element);
1253
+ } // Update element-related weak maps with the DOM element ref.
1254
+
1255
+
1256
+ useIsomorphicLayoutEffect(() => {
1257
+ if (ref.current) {
1258
+ KEY_TO_ELEMENT.set(key, ref.current);
1259
+ NODE_TO_ELEMENT.set(element, ref.current);
1260
+ ELEMENT_TO_NODE.set(ref.current, element);
1261
+ } else {
1262
+ KEY_TO_ELEMENT.delete(key);
1263
+ NODE_TO_ELEMENT.delete(element);
1264
+ }
1265
+ });
1266
+ return React.createElement(SelectedContext.Provider, {
1267
+ value: !!selection
1268
+ }, renderElement({
1269
+ attributes,
1270
+ children,
1271
+ element
1272
+ }));
1273
+ };
1274
+
1275
+ var MemoizedElement = React.memo(Element, (prev, next) => {
1276
+ return prev.decorate === next.decorate && prev.element === next.element && prev.renderElement === next.renderElement && prev.renderLeaf === next.renderLeaf && isRangeListEqual(prev.decorations, next.decorations) && (prev.selection === next.selection || !!prev.selection && !!next.selection && Range.equals(prev.selection, next.selection));
1277
+ });
1278
+ /**
1279
+ * The default element renderer.
1280
+ */
1281
+
1282
+ var DefaultElement = props => {
1283
+ var {
1284
+ attributes,
1285
+ children,
1286
+ element
1287
+ } = props;
1288
+ var editor = useEditor();
1289
+ var Tag = editor.isInline(element) ? 'span' : 'div';
1290
+ return React.createElement(Tag, Object.assign({}, attributes, {
1291
+ style: {
1292
+ position: 'relative'
1293
+ }
1294
+ }), children);
1295
+ };
1296
+ /**
1297
+ * Check if a list of ranges is equal to another.
1298
+ *
1299
+ * PERF: this requires the two lists to also have the ranges inside them in the
1300
+ * same order, but this is an okay constraint for us since decorations are
1301
+ * kept in order, and the odd case where they aren't is okay to re-render for.
1302
+ */
1303
+
1304
+ var isRangeListEqual = (list, another) => {
1305
+ if (list.length !== another.length) {
1306
+ return false;
1307
+ }
1308
+
1309
+ for (var i = 0; i < list.length; i++) {
1310
+ var range = list[i];
1311
+ var other = another[i];
1312
+
1313
+ if (!Range.equals(range, other)) {
1314
+ return false;
1315
+ }
1316
+ }
1317
+
1318
+ return true;
1319
+ };
1320
+
1321
+ /**
1322
+ * Children.
1323
+ */
1324
+
1325
+ var Children = props => {
1326
+ var {
1327
+ decorate,
1328
+ decorations,
1329
+ node,
1330
+ renderElement,
1331
+ renderLeaf,
1332
+ selection
1333
+ } = props;
1334
+ var editor = useEditor();
1335
+ var path = ReactEditor.findPath(editor, node);
1336
+ var children = [];
1337
+ var isLeafBlock = Element$1.isElement(node) && !editor.isInline(node) && Editor.hasInlines(editor, node);
1338
+
1339
+ for (var i = 0; i < node.children.length; i++) {
1340
+ var p = path.concat(i);
1341
+ var n = node.children[i];
1342
+ var key = ReactEditor.findKey(editor, n);
1343
+ var range = Editor.range(editor, p);
1344
+ var sel = selection && Range.intersection(range, selection);
1345
+ var ds = decorate([n, p]);
1346
+
1347
+ for (var dec of decorations) {
1348
+ var d = Range.intersection(dec, range);
1349
+
1350
+ if (d) {
1351
+ ds.push(d);
1352
+ }
1353
+ }
1354
+
1355
+ if (Element$1.isElement(n)) {
1356
+ children.push(React.createElement(MemoizedElement, {
1357
+ decorate: decorate,
1358
+ decorations: ds,
1359
+ element: n,
1360
+ key: key.id,
1361
+ renderElement: renderElement,
1362
+ renderLeaf: renderLeaf,
1363
+ selection: sel
1364
+ }));
1365
+ } else {
1366
+ children.push(React.createElement(MemoizedText, {
1367
+ decorations: ds,
1368
+ key: key.id,
1369
+ isLast: isLeafBlock && i === node.children.length - 1,
1370
+ parent: node,
1371
+ renderLeaf: renderLeaf,
1372
+ text: n
1373
+ }));
1374
+ }
1375
+
1376
+ NODE_TO_INDEX.set(n, i);
1377
+ NODE_TO_PARENT.set(n, node);
1378
+ }
1379
+
1380
+ return React.createElement(React.Fragment, null, children);
1381
+ };
1382
+
1383
+ var IS_IOS = typeof navigator !== 'undefined' && typeof window !== 'undefined' && /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
1384
+ var IS_APPLE = typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent);
1385
+ var IS_FIREFOX = typeof navigator !== 'undefined' && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
1386
+ var IS_SAFARI = typeof navigator !== 'undefined' && /Version\/[\d\.]+.*Safari/.test(navigator.userAgent); // "modern" Edge was released at 79.x
1387
+
1388
+ var IS_EDGE_LEGACY = typeof navigator !== 'undefined' && /Edge?\/(?:[0-6][0-9]|[0-7][0-8])/i.test(navigator.userAgent);
1389
+
1390
+ /**
1391
+ * Hotkey mappings for each platform.
1392
+ */
1393
+
1394
+ var HOTKEYS = {
1395
+ bold: 'mod+b',
1396
+ compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],
1397
+ moveBackward: 'left',
1398
+ moveForward: 'right',
1399
+ moveWordBackward: 'ctrl+left',
1400
+ moveWordForward: 'ctrl+right',
1401
+ deleteBackward: 'shift?+backspace',
1402
+ deleteForward: 'shift?+delete',
1403
+ extendBackward: 'shift+left',
1404
+ extendForward: 'shift+right',
1405
+ italic: 'mod+i',
1406
+ splitBlock: 'shift?+enter',
1407
+ undo: 'mod+z'
1408
+ };
1409
+ var APPLE_HOTKEYS = {
1410
+ moveLineBackward: 'opt+up',
1411
+ moveLineForward: 'opt+down',
1412
+ moveWordBackward: 'opt+left',
1413
+ moveWordForward: 'opt+right',
1414
+ deleteBackward: ['ctrl+backspace', 'ctrl+h'],
1415
+ deleteForward: ['ctrl+delete', 'ctrl+d'],
1416
+ deleteLineBackward: 'cmd+shift?+backspace',
1417
+ deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],
1418
+ deleteWordBackward: 'opt+shift?+backspace',
1419
+ deleteWordForward: 'opt+shift?+delete',
1420
+ extendLineBackward: 'opt+shift+up',
1421
+ extendLineForward: 'opt+shift+down',
1422
+ redo: 'cmd+shift+z',
1423
+ transposeCharacter: 'ctrl+t'
1424
+ };
1425
+ var WINDOWS_HOTKEYS = {
1426
+ deleteWordBackward: 'ctrl+shift?+backspace',
1427
+ deleteWordForward: 'ctrl+shift?+delete',
1428
+ redo: ['ctrl+y', 'ctrl+shift+z']
1429
+ };
1430
+ /**
1431
+ * Create a platform-aware hotkey checker.
1432
+ */
1433
+
1434
+ var create = key => {
1435
+ var generic = HOTKEYS[key];
1436
+ var apple = APPLE_HOTKEYS[key];
1437
+ var windows = WINDOWS_HOTKEYS[key];
1438
+ var isGeneric = generic && isKeyHotkey(generic);
1439
+ var isApple = apple && isKeyHotkey(apple);
1440
+ var isWindows = windows && isKeyHotkey(windows);
1441
+ return event => {
1442
+ if (isGeneric && isGeneric(event)) return true;
1443
+ if (IS_APPLE && isApple && isApple(event)) return true;
1444
+ if (!IS_APPLE && isWindows && isWindows(event)) return true;
1445
+ return false;
1446
+ };
1447
+ };
1448
+ /**
1449
+ * Hotkeys.
1450
+ */
1451
+
1452
+
1453
+ var Hotkeys = {
1454
+ isBold: create('bold'),
1455
+ isCompose: create('compose'),
1456
+ isMoveBackward: create('moveBackward'),
1457
+ isMoveForward: create('moveForward'),
1458
+ isDeleteBackward: create('deleteBackward'),
1459
+ isDeleteForward: create('deleteForward'),
1460
+ isDeleteLineBackward: create('deleteLineBackward'),
1461
+ isDeleteLineForward: create('deleteLineForward'),
1462
+ isDeleteWordBackward: create('deleteWordBackward'),
1463
+ isDeleteWordForward: create('deleteWordForward'),
1464
+ isExtendBackward: create('extendBackward'),
1465
+ isExtendForward: create('extendForward'),
1466
+ isExtendLineBackward: create('extendLineBackward'),
1467
+ isExtendLineForward: create('extendLineForward'),
1468
+ isItalic: create('italic'),
1469
+ isMoveLineBackward: create('moveLineBackward'),
1470
+ isMoveLineForward: create('moveLineForward'),
1471
+ isMoveWordBackward: create('moveWordBackward'),
1472
+ isMoveWordForward: create('moveWordForward'),
1473
+ isRedo: create('redo'),
1474
+ isSplitBlock: create('splitBlock'),
1475
+ isTransposeCharacter: create('transposeCharacter'),
1476
+ isUndo: create('undo')
1477
+ };
1478
+
1479
+ function 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; }
1480
+
1481
+ function _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; }
1482
+
1483
+ var HAS_BEFORE_INPUT_SUPPORT = !(IS_FIREFOX || IS_EDGE_LEGACY);
1484
+ /**
1485
+ * Editable.
1486
+ */
1487
+
1488
+ var Editable = props => {
1489
+ var {
1490
+ autoFocus,
1491
+ decorate = defaultDecorate,
1492
+ onDOMBeforeInput: propsOnDOMBeforeInput,
1493
+ placeholder,
1494
+ readOnly = false,
1495
+ renderElement,
1496
+ renderLeaf,
1497
+ style = {},
1498
+ as: Component = 'div'
1499
+ } = props,
1500
+ attributes = _objectWithoutProperties(props, ["autoFocus", "decorate", "onDOMBeforeInput", "placeholder", "readOnly", "renderElement", "renderLeaf", "style", "as"]);
1501
+
1502
+ var editor = useSlate();
1503
+ var ref = useRef(null); // Update internal state on each render.
1504
+
1505
+ IS_READ_ONLY.set(editor, readOnly); // Keep track of some state for the event handler logic.
1506
+
1507
+ var state = useMemo(() => ({
1508
+ isComposing: false,
1509
+ isUpdatingSelection: false,
1510
+ latestElement: null
1511
+ }), []); // Update element-related weak maps with the DOM element ref.
1512
+
1513
+ useIsomorphicLayoutEffect(() => {
1514
+ if (ref.current) {
1515
+ EDITOR_TO_ELEMENT.set(editor, ref.current);
1516
+ NODE_TO_ELEMENT.set(editor, ref.current);
1517
+ ELEMENT_TO_NODE.set(ref.current, editor);
1518
+ } else {
1519
+ NODE_TO_ELEMENT.delete(editor);
1520
+ }
1521
+ }); // Whenever the editor updates, make sure the DOM selection state is in sync.
1522
+
1523
+ useIsomorphicLayoutEffect(() => {
1524
+ var {
1525
+ selection
1526
+ } = editor;
1527
+ var domSelection = window.getSelection();
1528
+
1529
+ if (state.isComposing || !domSelection || !ReactEditor.isFocused(editor)) {
1530
+ return;
1531
+ }
1532
+
1533
+ var hasDomSelection = domSelection.type !== 'None'; // If the DOM selection is properly unset, we're done.
1534
+
1535
+ if (!selection && !hasDomSelection) {
1536
+ return;
1537
+ } // If the DOM selection is already correct, we're done.
1538
+
1539
+
1540
+ if (hasDomSelection && selection && Range.equals(ReactEditor.toSlateRange(editor, domSelection), selection)) {
1541
+ return;
1542
+ } // Otherwise the DOM selection is out of sync, so update it.
1543
+
1544
+
1545
+ var el = ReactEditor.toDOMNode(editor, editor);
1546
+ state.isUpdatingSelection = true;
1547
+ domSelection.removeAllRanges();
1548
+ var newDomRange = selection && ReactEditor.toDOMRange(editor, selection);
1549
+
1550
+ if (newDomRange) {
1551
+ domSelection.addRange(newDomRange);
1552
+ var leafEl = newDomRange.startContainer.parentElement;
1553
+ scrollIntoView(leafEl, {
1554
+ scrollMode: 'if-needed'
1555
+ });
1556
+ }
1557
+
1558
+ setTimeout(() => {
1559
+ // COMPAT: In Firefox, it's not enough to create a range, you also need
1560
+ // to focus the contenteditable element too. (2016/11/16)
1561
+ if (newDomRange && IS_FIREFOX) {
1562
+ el.focus();
1563
+ }
1564
+
1565
+ state.isUpdatingSelection = false;
1566
+ });
1567
+ }); // The autoFocus TextareaHTMLAttribute doesn't do anything on a div, so it
1568
+ // needs to be manually focused.
1569
+
1570
+ useEffect(() => {
1571
+ if (ref.current && autoFocus) {
1572
+ ref.current.focus();
1573
+ }
1574
+ }, [autoFocus]); // Listen on the native `beforeinput` event to get real "Level 2" events. This
1575
+ // is required because React's `beforeinput` is fake and never really attaches
1576
+ // to the real event sadly. (2019/11/01)
1577
+ // https://github.com/facebook/react/issues/11211
1578
+
1579
+ var onDOMBeforeInput = useCallback(event => {
1580
+ if (!readOnly && hasEditableTarget(editor, event.target) && !isDOMEventHandled(event, propsOnDOMBeforeInput)) {
1581
+ var {
1582
+ selection
1583
+ } = editor;
1584
+ var {
1585
+ inputType: type
1586
+ } = event;
1587
+ var data = event.dataTransfer || event.data || undefined; // These two types occur while a user is composing text and can't be
1588
+ // cancelled. Let them through and wait for the composition to end.
1589
+
1590
+ if (type === 'insertCompositionText' || type === 'deleteCompositionText') {
1591
+ return;
1592
+ }
1593
+
1594
+ event.preventDefault(); // COMPAT: For the deleting forward/backward input types we don't want
1595
+ // to change the selection because it is the range that will be deleted,
1596
+ // and those commands determine that for themselves.
1597
+
1598
+ if (!type.startsWith('delete') || type.startsWith('deleteBy')) {
1599
+ var [targetRange] = event.getTargetRanges();
1600
+
1601
+ if (targetRange) {
1602
+ var range = ReactEditor.toSlateRange(editor, targetRange);
1603
+
1604
+ if (!selection || !Range.equals(selection, range)) {
1605
+ Transforms.select(editor, range);
1606
+ }
1607
+ }
1608
+ } // COMPAT: If the selection is expanded, even if the command seems like
1609
+ // a delete forward/backward command it should delete the selection.
1610
+
1611
+
1612
+ if (selection && Range.isExpanded(selection) && type.startsWith('delete')) {
1613
+ Editor.deleteFragment(editor);
1614
+ return;
1615
+ }
1616
+
1617
+ switch (type) {
1618
+ case 'deleteByComposition':
1619
+ case 'deleteByCut':
1620
+ case 'deleteByDrag':
1621
+ {
1622
+ Editor.deleteFragment(editor);
1623
+ break;
1624
+ }
1625
+
1626
+ case 'deleteContent':
1627
+ case 'deleteContentForward':
1628
+ {
1629
+ Editor.deleteForward(editor);
1630
+ break;
1631
+ }
1632
+
1633
+ case 'deleteContentBackward':
1634
+ {
1635
+ Editor.deleteBackward(editor);
1636
+ break;
1637
+ }
1638
+
1639
+ case 'deleteEntireSoftLine':
1640
+ {
1641
+ Editor.deleteBackward(editor, {
1642
+ unit: 'line'
1643
+ });
1644
+ Editor.deleteForward(editor, {
1645
+ unit: 'line'
1646
+ });
1647
+ break;
1648
+ }
1649
+
1650
+ case 'deleteHardLineBackward':
1651
+ {
1652
+ Editor.deleteBackward(editor, {
1653
+ unit: 'block'
1654
+ });
1655
+ break;
1656
+ }
1657
+
1658
+ case 'deleteSoftLineBackward':
1659
+ {
1660
+ Editor.deleteBackward(editor, {
1661
+ unit: 'line'
1662
+ });
1663
+ break;
1664
+ }
1665
+
1666
+ case 'deleteHardLineForward':
1667
+ {
1668
+ Editor.deleteForward(editor, {
1669
+ unit: 'block'
1670
+ });
1671
+ break;
1672
+ }
1673
+
1674
+ case 'deleteSoftLineForward':
1675
+ {
1676
+ Editor.deleteForward(editor, {
1677
+ unit: 'line'
1678
+ });
1679
+ break;
1680
+ }
1681
+
1682
+ case 'deleteWordBackward':
1683
+ {
1684
+ Editor.deleteBackward(editor, {
1685
+ unit: 'word'
1686
+ });
1687
+ break;
1688
+ }
1689
+
1690
+ case 'deleteWordForward':
1691
+ {
1692
+ Editor.deleteForward(editor, {
1693
+ unit: 'word'
1694
+ });
1695
+ break;
1696
+ }
1697
+
1698
+ case 'insertLineBreak':
1699
+ case 'insertParagraph':
1700
+ {
1701
+ Editor.insertBreak(editor);
1702
+ break;
1703
+ }
1704
+
1705
+ case 'insertFromComposition':
1706
+ case 'insertFromDrop':
1707
+ case 'insertFromPaste':
1708
+ case 'insertFromYank':
1709
+ case 'insertReplacementText':
1710
+ case 'insertText':
1711
+ {
1712
+ if (data instanceof DataTransfer) {
1713
+ ReactEditor.insertData(editor, data);
1714
+ } else if (typeof data === 'string') {
1715
+ Editor.insertText(editor, data);
1716
+ }
1717
+
1718
+ break;
1719
+ }
1720
+ }
1721
+ }
1722
+ }, [readOnly]); // Attach a native DOM event handler for `beforeinput` events, because React's
1723
+ // built-in `onBeforeInput` is actually a leaky polyfill that doesn't expose
1724
+ // real `beforeinput` events sadly... (2019/11/04)
1725
+ // https://github.com/facebook/react/issues/11211
1726
+
1727
+ useIsomorphicLayoutEffect(() => {
1728
+ if (ref.current) {
1729
+ // @ts-ignore The `beforeinput` event isn't recognized.
1730
+ ref.current.addEventListener('beforeinput', onDOMBeforeInput);
1731
+ }
1732
+
1733
+ return () => {
1734
+ if (ref.current) {
1735
+ // @ts-ignore The `beforeinput` event isn't recognized.
1736
+ ref.current.removeEventListener('beforeinput', onDOMBeforeInput);
1737
+ }
1738
+ };
1739
+ }, [onDOMBeforeInput]); // Listen on the native `selectionchange` event to be able to update any time
1740
+ // the selection changes. This is required because React's `onSelect` is leaky
1741
+ // and non-standard so it doesn't fire until after a selection has been
1742
+ // released. This causes issues in situations where another change happens
1743
+ // while a selection is being dragged.
1744
+
1745
+ var onDOMSelectionChange = useCallback(throttle(() => {
1746
+ if (!readOnly && !state.isComposing && !state.isUpdatingSelection) {
1747
+ var {
1748
+ activeElement
1749
+ } = window.document;
1750
+ var el = ReactEditor.toDOMNode(editor, editor);
1751
+ var domSelection = window.getSelection();
1752
+
1753
+ if (activeElement === el) {
1754
+ state.latestElement = activeElement;
1755
+ IS_FOCUSED.set(editor, true);
1756
+ } else {
1757
+ IS_FOCUSED.delete(editor);
1758
+ }
1759
+
1760
+ if (domSelection && hasEditableTarget(editor, domSelection.anchorNode) && hasEditableTarget(editor, domSelection.focusNode)) {
1761
+ var range = ReactEditor.toSlateRange(editor, domSelection);
1762
+ Transforms.select(editor, range);
1763
+ } else {
1764
+ Transforms.deselect(editor);
1765
+ }
1766
+ }
1767
+ }, 100), [readOnly]); // Attach a native DOM event handler for `selectionchange`, because React's
1768
+ // built-in `onSelect` handler doesn't fire for all selection changes. It's a
1769
+ // leaky polyfill that only fires on keypresses or clicks. Instead, we want to
1770
+ // fire for any change to the selection inside the editor. (2019/11/04)
1771
+ // https://github.com/facebook/react/issues/5785
1772
+
1773
+ useIsomorphicLayoutEffect(() => {
1774
+ window.document.addEventListener('selectionchange', onDOMSelectionChange);
1775
+ return () => {
1776
+ window.document.removeEventListener('selectionchange', onDOMSelectionChange);
1777
+ };
1778
+ }, [onDOMSelectionChange]);
1779
+ var decorations = decorate([editor, []]);
1780
+
1781
+ if (placeholder && editor.children.length === 1 && Array.from(Node$1.texts(editor)).length === 1 && Node$1.string(editor) === '') {
1782
+ var start = Editor.start(editor, []);
1783
+ decorations.push({
1784
+ [PLACEHOLDER_SYMBOL]: true,
1785
+ placeholder,
1786
+ anchor: start,
1787
+ focus: start
1788
+ });
1789
+ }
1790
+
1791
+ return React.createElement(ReadOnlyContext.Provider, {
1792
+ value: readOnly
1793
+ }, React.createElement(Component // COMPAT: The Grammarly Chrome extension works by changing the DOM
1794
+ // out from under `contenteditable` elements, which leads to weird
1795
+ // behaviors so we have to disable it like editor. (2017/04/24)
1796
+ , Object.assign({
1797
+ "data-gramm": false,
1798
+ role: readOnly ? undefined : 'textbox'
1799
+ }, attributes, {
1800
+ // COMPAT: Certain browsers don't support the `beforeinput` event, so we'd
1801
+ // have to use hacks to make these replacement-based features work.
1802
+ spellCheck: !HAS_BEFORE_INPUT_SUPPORT ? undefined : attributes.spellCheck,
1803
+ autoCorrect: !HAS_BEFORE_INPUT_SUPPORT ? undefined : attributes.autoCorrect,
1804
+ autoCapitalize: !HAS_BEFORE_INPUT_SUPPORT ? undefined : attributes.autoCapitalize,
1805
+ "data-slate-editor": true,
1806
+ "data-slate-node": "value",
1807
+ contentEditable: readOnly ? undefined : true,
1808
+ suppressContentEditableWarning: true,
1809
+ ref: ref,
1810
+ style: _objectSpread({
1811
+ // Prevent the default outline styles.
1812
+ outline: 'none',
1813
+ // Preserve adjacent whitespace and new lines.
1814
+ whiteSpace: 'pre-wrap',
1815
+ // Allow words to break if they are too long.
1816
+ wordWrap: 'break-word'
1817
+ }, style),
1818
+ onBeforeInput: useCallback(event => {
1819
+ // COMPAT: Certain browsers don't support the `beforeinput` event, so we
1820
+ // fall back to React's leaky polyfill instead just for it. It
1821
+ // only works for the `insertText` input type.
1822
+ if (!HAS_BEFORE_INPUT_SUPPORT && !readOnly && !isEventHandled(event, attributes.onBeforeInput) && hasEditableTarget(editor, event.target)) {
1823
+ event.preventDefault();
1824
+ var text = event.data;
1825
+ Editor.insertText(editor, text);
1826
+ }
1827
+ }, [readOnly]),
1828
+ onBlur: useCallback(event => {
1829
+ if (readOnly || state.isUpdatingSelection || !hasEditableTarget(editor, event.target) || isEventHandled(event, attributes.onBlur)) {
1830
+ return;
1831
+ } // COMPAT: If the current `activeElement` is still the previous
1832
+ // one, this is due to the window being blurred when the tab
1833
+ // itself becomes unfocused, so we want to abort early to allow to
1834
+ // editor to stay focused when the tab becomes focused again.
1835
+
1836
+
1837
+ if (state.latestElement === window.document.activeElement) {
1838
+ return;
1839
+ }
1840
+
1841
+ var {
1842
+ relatedTarget
1843
+ } = event;
1844
+ var el = ReactEditor.toDOMNode(editor, editor); // COMPAT: The event should be ignored if the focus is returning
1845
+ // to the editor from an embedded editable element (eg. an <input>
1846
+ // element inside a void node).
1847
+
1848
+ if (relatedTarget === el) {
1849
+ return;
1850
+ } // COMPAT: The event should be ignored if the focus is moving from
1851
+ // the editor to inside a void node's spacer element.
1852
+
1853
+
1854
+ if (isDOMElement(relatedTarget) && relatedTarget.hasAttribute('data-slate-spacer')) {
1855
+ return;
1856
+ } // COMPAT: The event should be ignored if the focus is moving to a
1857
+ // non- editable section of an element that isn't a void node (eg.
1858
+ // a list item of the check list example).
1859
+
1860
+
1861
+ if (relatedTarget != null && isDOMNode(relatedTarget) && ReactEditor.hasDOMNode(editor, relatedTarget)) {
1862
+ var node = ReactEditor.toSlateNode(editor, relatedTarget);
1863
+
1864
+ if (Element$1.isElement(node) && !editor.isVoid(node)) {
1865
+ return;
1866
+ }
1867
+ }
1868
+
1869
+ IS_FOCUSED.delete(editor);
1870
+ }, [readOnly, attributes.onBlur]),
1871
+ onClick: useCallback(event => {
1872
+ if (!readOnly && hasTarget(editor, event.target) && !isEventHandled(event, attributes.onClick) && isDOMNode(event.target)) {
1873
+ var node = ReactEditor.toSlateNode(editor, event.target);
1874
+ var path = ReactEditor.findPath(editor, node);
1875
+
1876
+ var _start = Editor.start(editor, path);
1877
+
1878
+ if (Editor.void(editor, {
1879
+ at: _start
1880
+ })) {
1881
+ var range = Editor.range(editor, _start);
1882
+ Transforms.select(editor, range);
1883
+ }
1884
+ }
1885
+ }, [readOnly, attributes.onClick]),
1886
+ onCompositionEnd: useCallback(event => {
1887
+ if (hasEditableTarget(editor, event.target) && !isEventHandled(event, attributes.onCompositionEnd)) {
1888
+ state.isComposing = false; // COMPAT: In Chrome, `beforeinput` events for compositions
1889
+ // aren't correct and never fire the "insertFromComposition"
1890
+ // type that we need. So instead, insert whenever a composition
1891
+ // ends since it will already have been committed to the DOM.
1892
+
1893
+ if (!IS_SAFARI && !IS_FIREFOX && event.data) {
1894
+ Editor.insertText(editor, event.data);
1895
+ }
1896
+ }
1897
+ }, [attributes.onCompositionEnd]),
1898
+ onCompositionStart: useCallback(event => {
1899
+ if (hasEditableTarget(editor, event.target) && !isEventHandled(event, attributes.onCompositionStart)) {
1900
+ state.isComposing = true;
1901
+ }
1902
+ }, [attributes.onCompositionStart]),
1903
+ onCopy: useCallback(event => {
1904
+ if (hasEditableTarget(editor, event.target) && !isEventHandled(event, attributes.onCopy)) {
1905
+ event.preventDefault();
1906
+ ReactEditor.setFragmentData(editor, event.clipboardData);
1907
+ }
1908
+ }, [attributes.onCopy]),
1909
+ onCut: useCallback(event => {
1910
+ if (!readOnly && hasEditableTarget(editor, event.target) && !isEventHandled(event, attributes.onCut)) {
1911
+ event.preventDefault();
1912
+ ReactEditor.setFragmentData(editor, event.clipboardData);
1913
+ var {
1914
+ selection
1915
+ } = editor;
1916
+
1917
+ if (selection && Range.isExpanded(selection)) {
1918
+ Editor.deleteFragment(editor);
1919
+ }
1920
+ }
1921
+ }, [readOnly, attributes.onCut]),
1922
+ onDragOver: useCallback(event => {
1923
+ if (hasTarget(editor, event.target) && !isEventHandled(event, attributes.onDragOver)) {
1924
+ // Only when the target is void, call `preventDefault` to signal
1925
+ // that drops are allowed. Editable content is droppable by
1926
+ // default, and calling `preventDefault` hides the cursor.
1927
+ var node = ReactEditor.toSlateNode(editor, event.target);
1928
+
1929
+ if (Editor.isVoid(editor, node)) {
1930
+ event.preventDefault();
1931
+ }
1932
+ }
1933
+ }, [attributes.onDragOver]),
1934
+ onDragStart: useCallback(event => {
1935
+ if (hasTarget(editor, event.target) && !isEventHandled(event, attributes.onDragStart)) {
1936
+ var node = ReactEditor.toSlateNode(editor, event.target);
1937
+ var path = ReactEditor.findPath(editor, node);
1938
+ var voidMatch = Editor.void(editor, {
1939
+ at: path
1940
+ }); // If starting a drag on a void node, make sure it is selected
1941
+ // so that it shows up in the selection's fragment.
1942
+
1943
+ if (voidMatch) {
1944
+ var range = Editor.range(editor, path);
1945
+ Transforms.select(editor, range);
1946
+ }
1947
+
1948
+ ReactEditor.setFragmentData(editor, event.dataTransfer);
1949
+ }
1950
+ }, [attributes.onDragStart]),
1951
+ onDrop: useCallback(event => {
1952
+ if (hasTarget(editor, event.target) && !readOnly && !isEventHandled(event, attributes.onDrop)) {
1953
+ // COMPAT: Certain browsers don't fire `beforeinput` events at all, and
1954
+ // Chromium browsers don't properly fire them for files being
1955
+ // dropped into a `contenteditable`. (2019/11/26)
1956
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=1028668
1957
+ if (!HAS_BEFORE_INPUT_SUPPORT || !IS_SAFARI && event.dataTransfer.files.length > 0) {
1958
+ event.preventDefault();
1959
+ var range = ReactEditor.findEventRange(editor, event);
1960
+ var data = event.dataTransfer;
1961
+ Transforms.select(editor, range);
1962
+ ReactEditor.insertData(editor, data);
1963
+ }
1964
+ }
1965
+ }, [readOnly, attributes.onDrop]),
1966
+ onFocus: useCallback(event => {
1967
+ if (!readOnly && !state.isUpdatingSelection && hasEditableTarget(editor, event.target) && !isEventHandled(event, attributes.onFocus)) {
1968
+ var el = ReactEditor.toDOMNode(editor, editor);
1969
+ state.latestElement = window.document.activeElement; // COMPAT: If the editor has nested editable elements, the focus
1970
+ // can go to them. In Firefox, this must be prevented because it
1971
+ // results in issues with keyboard navigation. (2017/03/30)
1972
+
1973
+ if (IS_FIREFOX && event.target !== el) {
1974
+ el.focus();
1975
+ return;
1976
+ }
1977
+
1978
+ IS_FOCUSED.set(editor, true);
1979
+ }
1980
+ }, [readOnly, attributes.onFocus]),
1981
+ onKeyDown: useCallback(event => {
1982
+ if (!readOnly && hasEditableTarget(editor, event.target) && !isEventHandled(event, attributes.onKeyDown)) {
1983
+ var {
1984
+ nativeEvent
1985
+ } = event;
1986
+ var {
1987
+ selection
1988
+ } = editor; // COMPAT: Since we prevent the default behavior on
1989
+ // `beforeinput` events, the browser doesn't think there's ever
1990
+ // any history stack to undo or redo, so we have to manage these
1991
+ // hotkeys ourselves. (2019/11/06)
1992
+
1993
+ if (Hotkeys.isRedo(nativeEvent)) {
1994
+ event.preventDefault();
1995
+
1996
+ if (editor.redo) {
1997
+ editor.redo();
1998
+ }
1999
+
2000
+ return;
2001
+ }
2002
+
2003
+ if (Hotkeys.isUndo(nativeEvent)) {
2004
+ event.preventDefault();
2005
+
2006
+ if (editor.undo) {
2007
+ editor.undo();
2008
+ }
2009
+
2010
+ return;
2011
+ } // COMPAT: Certain browsers don't handle the selection updates
2012
+ // properly. In Chrome, the selection isn't properly extended.
2013
+ // And in Firefox, the selection isn't properly collapsed.
2014
+ // (2017/10/17)
2015
+
2016
+
2017
+ if (Hotkeys.isMoveLineBackward(nativeEvent)) {
2018
+ event.preventDefault();
2019
+ Transforms.move(editor, {
2020
+ unit: 'line',
2021
+ reverse: true
2022
+ });
2023
+ return;
2024
+ }
2025
+
2026
+ if (Hotkeys.isMoveLineForward(nativeEvent)) {
2027
+ event.preventDefault();
2028
+ Transforms.move(editor, {
2029
+ unit: 'line'
2030
+ });
2031
+ return;
2032
+ }
2033
+
2034
+ if (Hotkeys.isExtendLineBackward(nativeEvent)) {
2035
+ event.preventDefault();
2036
+ Transforms.move(editor, {
2037
+ unit: 'line',
2038
+ edge: 'focus',
2039
+ reverse: true
2040
+ });
2041
+ return;
2042
+ }
2043
+
2044
+ if (Hotkeys.isExtendLineForward(nativeEvent)) {
2045
+ event.preventDefault();
2046
+ Transforms.move(editor, {
2047
+ unit: 'line',
2048
+ edge: 'focus'
2049
+ });
2050
+ return;
2051
+ } // COMPAT: If a void node is selected, or a zero-width text node
2052
+ // adjacent to an inline is selected, we need to handle these
2053
+ // hotkeys manually because browsers won't be able to skip over
2054
+ // the void node with the zero-width space not being an empty
2055
+ // string.
2056
+
2057
+
2058
+ if (Hotkeys.isMoveBackward(nativeEvent)) {
2059
+ event.preventDefault();
2060
+
2061
+ if (selection && Range.isCollapsed(selection)) {
2062
+ Transforms.move(editor, {
2063
+ reverse: true
2064
+ });
2065
+ } else {
2066
+ Transforms.collapse(editor, {
2067
+ edge: 'start'
2068
+ });
2069
+ }
2070
+
2071
+ return;
2072
+ }
2073
+
2074
+ if (Hotkeys.isMoveForward(nativeEvent)) {
2075
+ event.preventDefault();
2076
+
2077
+ if (selection && Range.isCollapsed(selection)) {
2078
+ Transforms.move(editor);
2079
+ } else {
2080
+ Transforms.collapse(editor, {
2081
+ edge: 'end'
2082
+ });
2083
+ }
2084
+
2085
+ return;
2086
+ }
2087
+
2088
+ if (Hotkeys.isMoveWordBackward(nativeEvent)) {
2089
+ event.preventDefault();
2090
+ Transforms.move(editor, {
2091
+ unit: 'word',
2092
+ reverse: true
2093
+ });
2094
+ return;
2095
+ }
2096
+
2097
+ if (Hotkeys.isMoveWordForward(nativeEvent)) {
2098
+ event.preventDefault();
2099
+ Transforms.move(editor, {
2100
+ unit: 'word'
2101
+ });
2102
+ return;
2103
+ } // COMPAT: Certain browsers don't support the `beforeinput` event, so we
2104
+ // fall back to guessing at the input intention for hotkeys.
2105
+ // COMPAT: In iOS, some of these hotkeys are handled in the
2106
+
2107
+
2108
+ if (!HAS_BEFORE_INPUT_SUPPORT) {
2109
+ // We don't have a core behavior for these, but they change the
2110
+ // DOM if we don't prevent them, so we have to.
2111
+ if (Hotkeys.isBold(nativeEvent) || Hotkeys.isItalic(nativeEvent) || Hotkeys.isTransposeCharacter(nativeEvent)) {
2112
+ event.preventDefault();
2113
+ return;
2114
+ }
2115
+
2116
+ if (Hotkeys.isSplitBlock(nativeEvent)) {
2117
+ event.preventDefault();
2118
+ Editor.insertBreak(editor);
2119
+ return;
2120
+ }
2121
+
2122
+ if (Hotkeys.isDeleteBackward(nativeEvent)) {
2123
+ event.preventDefault();
2124
+
2125
+ if (selection && Range.isExpanded(selection)) {
2126
+ Editor.deleteFragment(editor);
2127
+ } else {
2128
+ Editor.deleteBackward(editor);
2129
+ }
2130
+
2131
+ return;
2132
+ }
2133
+
2134
+ if (Hotkeys.isDeleteForward(nativeEvent)) {
2135
+ event.preventDefault();
2136
+
2137
+ if (selection && Range.isExpanded(selection)) {
2138
+ Editor.deleteFragment(editor);
2139
+ } else {
2140
+ Editor.deleteForward(editor);
2141
+ }
2142
+
2143
+ return;
2144
+ }
2145
+
2146
+ if (Hotkeys.isDeleteLineBackward(nativeEvent)) {
2147
+ event.preventDefault();
2148
+
2149
+ if (selection && Range.isExpanded(selection)) {
2150
+ Editor.deleteFragment(editor);
2151
+ } else {
2152
+ Editor.deleteBackward(editor, {
2153
+ unit: 'line'
2154
+ });
2155
+ }
2156
+
2157
+ return;
2158
+ }
2159
+
2160
+ if (Hotkeys.isDeleteLineForward(nativeEvent)) {
2161
+ event.preventDefault();
2162
+
2163
+ if (selection && Range.isExpanded(selection)) {
2164
+ Editor.deleteFragment(editor);
2165
+ } else {
2166
+ Editor.deleteForward(editor, {
2167
+ unit: 'line'
2168
+ });
2169
+ }
2170
+
2171
+ return;
2172
+ }
2173
+
2174
+ if (Hotkeys.isDeleteWordBackward(nativeEvent)) {
2175
+ event.preventDefault();
2176
+
2177
+ if (selection && Range.isExpanded(selection)) {
2178
+ Editor.deleteFragment(editor);
2179
+ } else {
2180
+ Editor.deleteBackward(editor, {
2181
+ unit: 'word'
2182
+ });
2183
+ }
2184
+
2185
+ return;
2186
+ }
2187
+
2188
+ if (Hotkeys.isDeleteWordForward(nativeEvent)) {
2189
+ event.preventDefault();
2190
+
2191
+ if (selection && Range.isExpanded(selection)) {
2192
+ Editor.deleteFragment(editor);
2193
+ } else {
2194
+ Editor.deleteForward(editor, {
2195
+ unit: 'word'
2196
+ });
2197
+ }
2198
+
2199
+ return;
2200
+ }
2201
+ }
2202
+ }
2203
+ }, [readOnly, attributes.onKeyDown]),
2204
+ onPaste: useCallback(event => {
2205
+ // COMPAT: Certain browsers don't support the `beforeinput` event, so we
2206
+ // fall back to React's `onPaste` here instead.
2207
+ // COMPAT: Firefox, Chrome and Safari are not emitting `beforeinput` events
2208
+ // when "paste without formatting" option is used.
2209
+ // This unfortunately needs to be handled with paste events instead.
2210
+ if ((!HAS_BEFORE_INPUT_SUPPORT || isPlainTextOnlyPaste(event.nativeEvent)) && !readOnly && hasEditableTarget(editor, event.target) && !isEventHandled(event, attributes.onPaste)) {
2211
+ event.preventDefault();
2212
+ ReactEditor.insertData(editor, event.clipboardData);
2213
+ }
2214
+ }, [readOnly, attributes.onPaste])
2215
+ }), React.createElement(Children, {
2216
+ decorate: decorate,
2217
+ decorations: decorations,
2218
+ node: editor,
2219
+ renderElement: renderElement,
2220
+ renderLeaf: renderLeaf,
2221
+ selection: editor.selection
2222
+ })));
2223
+ };
2224
+ /**
2225
+ * A default memoized decorate function.
2226
+ */
2227
+
2228
+ var defaultDecorate = () => [];
2229
+ /**
2230
+ * Check if the target is in the editor.
2231
+ */
2232
+
2233
+
2234
+ var hasTarget = (editor, target) => {
2235
+ return isDOMNode(target) && ReactEditor.hasDOMNode(editor, target);
2236
+ };
2237
+ /**
2238
+ * Check if the target is editable and in the editor.
2239
+ */
2240
+
2241
+
2242
+ var hasEditableTarget = (editor, target) => {
2243
+ return isDOMNode(target) && ReactEditor.hasDOMNode(editor, target, {
2244
+ editable: true
2245
+ });
2246
+ };
2247
+ /**
2248
+ * Check if an event is overrided by a handler.
2249
+ */
2250
+
2251
+
2252
+ var isEventHandled = (event, handler) => {
2253
+ if (!handler) {
2254
+ return false;
2255
+ }
2256
+
2257
+ handler(event);
2258
+ return event.isDefaultPrevented() || event.isPropagationStopped();
2259
+ };
2260
+ /**
2261
+ * Check if a DOM event is overrided by a handler.
2262
+ */
2263
+
2264
+
2265
+ var isDOMEventHandled = (event, handler) => {
2266
+ if (!handler) {
2267
+ return false;
2268
+ }
2269
+
2270
+ handler(event);
2271
+ return event.defaultPrevented;
2095
2272
  };
2096
2273
 
2097
- export { DefaultElement, DefaultLeaf, Editable, ReactCommand, ReactEditor, Slate, useEditor, useFocused, useReadOnly, useSelected, useSlate, withReact };
2274
+ export { DefaultElement, DefaultLeaf, Editable, ReactEditor, Slate, useEditor, useFocused, useReadOnly, useSelected, useSlate, withReact };
2098
2275
  //# sourceMappingURL=index.es.js.map