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