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/Readme.md +2 -2
- package/dist/components/editable.d.ts.map +1 -1
- package/dist/components/slate.d.ts +3 -2
- package/dist/components/slate.d.ts.map +1 -1
- package/dist/components/text.d.ts.map +1 -1
- package/dist/hooks/use-editor.d.ts +5 -5
- package/dist/hooks/use-editor.d.ts.map +1 -1
- package/dist/hooks/use-slate.d.ts +5 -5
- package/dist/hooks/use-slate.d.ts.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.es.js +1712 -1535
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +1709 -1533
- package/dist/index.js.map +1 -1
- package/dist/plugin/react-editor.d.ts +13 -0
- package/dist/plugin/react-editor.d.ts.map +1 -1
- package/dist/plugin/with-react.d.ts +2 -1
- package/dist/plugin/with-react.d.ts.map +1 -1
- package/dist/slate-react.js +10436 -9604
- package/dist/slate-react.min.js +1 -6
- package/dist/utils/dom.d.ts +11 -0
- package/dist/utils/dom.d.ts.map +1 -1
- package/dist/utils/environment.d.ts +1 -0
- package/dist/utils/environment.d.ts.map +1 -1
- package/package.json +6 -6
- package/dist/plugin/react-command.d.ts +0 -18
- package/dist/plugin/react-command.d.ts.map +0 -1
package/dist/index.es.js
CHANGED
|
@@ -1,64 +1,75 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import { Path, Node as Node$1,
|
|
3
|
-
import
|
|
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
|
-
*
|
|
11
|
-
*/
|
|
12
|
-
|
|
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
|
-
*
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
*
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
608
|
-
var
|
|
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 =
|
|
639
|
-
var
|
|
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
|
-
|
|
1393
|
-
|
|
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
|
-
|
|
1410
|
-
|
|
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
|
-
|
|
222
|
+
if (isDOMText(domNode) && domNode.nodeValue) {
|
|
223
|
+
return domNode.nodeValue;
|
|
1416
224
|
}
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
var arrayWithoutHoles = _arrayWithoutHoles;
|
|
1420
225
|
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
226
|
+
if (isDOMElement(domNode)) {
|
|
227
|
+
for (var childNode of Array.from(domNode.childNodes)) {
|
|
228
|
+
text += getPlainText(childNode);
|
|
229
|
+
}
|
|
1424
230
|
|
|
1425
|
-
var
|
|
231
|
+
var display = getComputedStyle(domNode).getPropertyValue('display');
|
|
1426
232
|
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
}
|
|
233
|
+
if (display === 'block' || display === 'list' || domNode.tagName === 'BR') {
|
|
234
|
+
text += '\n';
|
|
235
|
+
}
|
|
236
|
+
}
|
|
1430
237
|
|
|
1431
|
-
|
|
238
|
+
return text;
|
|
239
|
+
};
|
|
1432
240
|
|
|
1433
|
-
|
|
1434
|
-
|
|
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
|
-
|
|
248
|
+
if (!key) {
|
|
249
|
+
key = new Key();
|
|
250
|
+
NODE_TO_KEY.set(node, key);
|
|
251
|
+
}
|
|
1438
252
|
|
|
1439
|
-
|
|
1440
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1552
|
-
var
|
|
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
|
-
|
|
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
|
|
348
|
+
hasDOMNode(editor, target) {
|
|
1568
349
|
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
1569
|
-
var
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
var
|
|
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
|
-
|
|
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 (!
|
|
367
|
+
if (!targetEl) {
|
|
1586
368
|
return false;
|
|
1587
369
|
}
|
|
1588
370
|
|
|
1589
|
-
return
|
|
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
|
|
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
|
|
1609
|
-
var
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1666
|
-
var
|
|
1667
|
-
|
|
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
|
|
1672
|
-
var
|
|
1673
|
-
|
|
1674
|
-
|
|
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
|
|
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
|
|
504
|
+
findEventRange(editor, event) {
|
|
1701
505
|
if ('nativeEvent' in event) {
|
|
1702
506
|
event = event.nativeEvent;
|
|
1703
507
|
}
|
|
1704
508
|
|
|
1705
|
-
var
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
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
|
|
1737
|
-
|
|
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
|
|
1764
|
-
var
|
|
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 = [
|
|
1786
|
-
removals.forEach(
|
|
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
|
|
1826
|
-
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
|
|
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
|
|
1865
|
-
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 =
|
|
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
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
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
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
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
|
-
|
|
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 =
|
|
1926
|
-
var
|
|
1927
|
-
|
|
1928
|
-
|
|
795
|
+
var withReact = editor => {
|
|
796
|
+
var e = editor;
|
|
797
|
+
var {
|
|
798
|
+
apply,
|
|
799
|
+
onChange
|
|
800
|
+
} = e;
|
|
1929
801
|
|
|
1930
|
-
|
|
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
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
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
|
|
1977
|
-
|
|
1978
|
-
|
|
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
|
-
|
|
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
|
|
2014
|
-
var
|
|
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
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
845
|
+
e.setFragmentData = data => {
|
|
846
|
+
var {
|
|
847
|
+
selection
|
|
848
|
+
} = e;
|
|
2030
849
|
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2041
|
-
|
|
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
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
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
|
-
|
|
2052
|
-
Editor.splitNodes(editor);
|
|
2053
|
-
}
|
|
940
|
+
var text = data.getData('text/plain');
|
|
2054
941
|
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
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
|
-
|
|
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(
|
|
2084
|
-
var onContextChange = EDITOR_TO_ON_CHANGE.get(
|
|
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
|
|
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,
|
|
2274
|
+
export { DefaultElement, DefaultLeaf, Editable, ReactEditor, Slate, useEditor, useFocused, useReadOnly, useSelected, useSlate, withReact };
|
|
2098
2275
|
//# sourceMappingURL=index.es.js.map
|