@ekz/lexical-rich-text 0.40.0

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.
@@ -0,0 +1,854 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ var lexicalClipboard = require('@ekz/lexical-clipboard');
12
+ var lexicalDragon = require('@ekz/lexical-dragon');
13
+ var lexicalSelection = require('@ekz/lexical-selection');
14
+ var lexicalUtils = require('@ekz/lexical-utils');
15
+ var lexical = require('@ekz/lexical');
16
+
17
+ /**
18
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
19
+ *
20
+ * This source code is licensed under the MIT license found in the
21
+ * LICENSE file in the root directory of this source tree.
22
+ *
23
+ */
24
+
25
+ function caretFromPoint(x, y) {
26
+ if (typeof document.caretRangeFromPoint !== 'undefined') {
27
+ const range = document.caretRangeFromPoint(x, y);
28
+ if (range === null) {
29
+ return null;
30
+ }
31
+ return {
32
+ node: range.startContainer,
33
+ offset: range.startOffset
34
+ };
35
+ // @ts-ignore
36
+ } else if (document.caretPositionFromPoint !== 'undefined') {
37
+ // @ts-ignore FF - no types
38
+ const range = document.caretPositionFromPoint(x, y);
39
+ if (range === null) {
40
+ return null;
41
+ }
42
+ return {
43
+ node: range.offsetNode,
44
+ offset: range.offset
45
+ };
46
+ } else {
47
+ // Gracefully handle IE
48
+ return null;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
54
+ *
55
+ * This source code is licensed under the MIT license found in the
56
+ * LICENSE file in the root directory of this source tree.
57
+ *
58
+ */
59
+
60
+ const CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
61
+
62
+ /**
63
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
64
+ *
65
+ * This source code is licensed under the MIT license found in the
66
+ * LICENSE file in the root directory of this source tree.
67
+ *
68
+ */
69
+
70
+ const documentMode = CAN_USE_DOM && 'documentMode' in document ? document.documentMode : null;
71
+ const IS_APPLE = CAN_USE_DOM && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
72
+ const CAN_USE_BEFORE_INPUT = CAN_USE_DOM && 'InputEvent' in window && !documentMode ? 'getTargetRanges' in new window.InputEvent('input') : false;
73
+ const IS_SAFARI = CAN_USE_DOM && /Version\/[\d.]+.*Safari/.test(navigator.userAgent);
74
+ const IS_IOS = CAN_USE_DOM && /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
75
+
76
+ // Keep these in case we need to use them in the future.
77
+ // export const IS_WINDOWS: boolean = CAN_USE_DOM && /Win/.test(navigator.platform);
78
+ const IS_CHROME = CAN_USE_DOM && /^(?=.*Chrome).*/i.test(navigator.userAgent);
79
+ const IS_APPLE_WEBKIT = CAN_USE_DOM && /AppleWebKit\/[\d.]+/.test(navigator.userAgent) && IS_APPLE && !IS_CHROME;
80
+
81
+ /**
82
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
83
+ *
84
+ * This source code is licensed under the MIT license found in the
85
+ * LICENSE file in the root directory of this source tree.
86
+ *
87
+ */
88
+
89
+ const DRAG_DROP_PASTE = lexical.createCommand('DRAG_DROP_PASTE_FILE');
90
+ /** @noInheritDoc */
91
+ class QuoteNode extends lexical.ElementNode {
92
+ static getType() {
93
+ return 'quote';
94
+ }
95
+ static clone(node) {
96
+ return new QuoteNode(node.__key);
97
+ }
98
+
99
+ // View
100
+
101
+ createDOM(config) {
102
+ const element = document.createElement('blockquote');
103
+ lexicalUtils.addClassNamesToElement(element, config.theme.quote);
104
+ return element;
105
+ }
106
+ updateDOM(prevNode, dom) {
107
+ return false;
108
+ }
109
+ static importDOM() {
110
+ return {
111
+ blockquote: node => ({
112
+ conversion: $convertBlockquoteElement,
113
+ priority: 0
114
+ })
115
+ };
116
+ }
117
+ exportDOM(editor) {
118
+ const {
119
+ element
120
+ } = super.exportDOM(editor);
121
+ if (lexicalUtils.isHTMLElement(element)) {
122
+ if (this.isEmpty()) {
123
+ element.append(document.createElement('br'));
124
+ }
125
+ const formatType = this.getFormatType();
126
+ if (formatType) {
127
+ element.style.textAlign = formatType;
128
+ }
129
+ const direction = this.getDirection();
130
+ if (direction) {
131
+ element.dir = direction;
132
+ }
133
+ }
134
+ return {
135
+ element
136
+ };
137
+ }
138
+ static importJSON(serializedNode) {
139
+ return $createQuoteNode().updateFromJSON(serializedNode);
140
+ }
141
+
142
+ // Mutation
143
+
144
+ insertNewAfter(_, restoreSelection) {
145
+ const newBlock = lexical.$createParagraphNode();
146
+ const direction = this.getDirection();
147
+ newBlock.setDirection(direction);
148
+ this.insertAfter(newBlock, restoreSelection);
149
+ return newBlock;
150
+ }
151
+ collapseAtStart() {
152
+ const paragraph = lexical.$createParagraphNode();
153
+ const children = this.getChildren();
154
+ children.forEach(child => paragraph.append(child));
155
+ this.replace(paragraph);
156
+ return true;
157
+ }
158
+ canMergeWhenEmpty() {
159
+ return true;
160
+ }
161
+ }
162
+ function $createQuoteNode() {
163
+ return lexical.$applyNodeReplacement(new QuoteNode());
164
+ }
165
+ function $isQuoteNode(node) {
166
+ return node instanceof QuoteNode;
167
+ }
168
+ /** @noInheritDoc */
169
+ class HeadingNode extends lexical.ElementNode {
170
+ /** @internal */
171
+ __tag;
172
+ static getType() {
173
+ return 'heading';
174
+ }
175
+ static clone(node) {
176
+ return new HeadingNode(node.__tag, node.__key);
177
+ }
178
+ constructor(tag, key) {
179
+ super(key);
180
+ this.__tag = tag;
181
+ }
182
+ getTag() {
183
+ return this.__tag;
184
+ }
185
+ setTag(tag) {
186
+ const self = this.getWritable();
187
+ this.__tag = tag;
188
+ return self;
189
+ }
190
+
191
+ // View
192
+
193
+ createDOM(config) {
194
+ const tag = this.__tag;
195
+ const element = document.createElement(tag);
196
+ const theme = config.theme;
197
+ const classNames = theme.heading;
198
+ if (classNames !== undefined) {
199
+ const className = classNames[tag];
200
+ lexicalUtils.addClassNamesToElement(element, className);
201
+ }
202
+ return element;
203
+ }
204
+ updateDOM(prevNode, dom, config) {
205
+ return prevNode.__tag !== this.__tag;
206
+ }
207
+ static importDOM() {
208
+ return {
209
+ h1: node => ({
210
+ conversion: $convertHeadingElement,
211
+ priority: 0
212
+ }),
213
+ h2: node => ({
214
+ conversion: $convertHeadingElement,
215
+ priority: 0
216
+ }),
217
+ h3: node => ({
218
+ conversion: $convertHeadingElement,
219
+ priority: 0
220
+ }),
221
+ h4: node => ({
222
+ conversion: $convertHeadingElement,
223
+ priority: 0
224
+ }),
225
+ h5: node => ({
226
+ conversion: $convertHeadingElement,
227
+ priority: 0
228
+ }),
229
+ h6: node => ({
230
+ conversion: $convertHeadingElement,
231
+ priority: 0
232
+ }),
233
+ p: node => {
234
+ // domNode is a <p> since we matched it by nodeName
235
+ const paragraph = node;
236
+ const firstChild = paragraph.firstChild;
237
+ if (firstChild !== null && isGoogleDocsTitle(firstChild)) {
238
+ return {
239
+ conversion: () => ({
240
+ node: null
241
+ }),
242
+ priority: 3
243
+ };
244
+ }
245
+ return null;
246
+ },
247
+ span: node => {
248
+ if (isGoogleDocsTitle(node)) {
249
+ return {
250
+ conversion: domNode => {
251
+ return {
252
+ node: $createHeadingNode('h1')
253
+ };
254
+ },
255
+ priority: 3
256
+ };
257
+ }
258
+ return null;
259
+ }
260
+ };
261
+ }
262
+ exportDOM(editor) {
263
+ const {
264
+ element
265
+ } = super.exportDOM(editor);
266
+ if (lexicalUtils.isHTMLElement(element)) {
267
+ if (this.isEmpty()) {
268
+ element.append(document.createElement('br'));
269
+ }
270
+ const formatType = this.getFormatType();
271
+ if (formatType) {
272
+ element.style.textAlign = formatType;
273
+ }
274
+ const direction = this.getDirection();
275
+ if (direction) {
276
+ element.dir = direction;
277
+ }
278
+ }
279
+ return {
280
+ element
281
+ };
282
+ }
283
+ static importJSON(serializedNode) {
284
+ return $createHeadingNode(serializedNode.tag).updateFromJSON(serializedNode);
285
+ }
286
+ updateFromJSON(serializedNode) {
287
+ return super.updateFromJSON(serializedNode).setTag(serializedNode.tag);
288
+ }
289
+ exportJSON() {
290
+ return {
291
+ ...super.exportJSON(),
292
+ tag: this.getTag()
293
+ };
294
+ }
295
+
296
+ // Mutation
297
+ insertNewAfter(selection, restoreSelection = true) {
298
+ const anchorOffet = selection ? selection.anchor.offset : 0;
299
+ const lastDesc = this.getLastDescendant();
300
+ const isAtEnd = !lastDesc || selection && selection.anchor.key === lastDesc.getKey() && anchorOffet === lastDesc.getTextContentSize();
301
+ const newElement = isAtEnd || !selection ? lexical.$createParagraphNode() : $createHeadingNode(this.getTag());
302
+ const direction = this.getDirection();
303
+ newElement.setDirection(direction);
304
+ this.insertAfter(newElement, restoreSelection);
305
+ if (anchorOffet === 0 && !this.isEmpty() && selection) {
306
+ const paragraph = lexical.$createParagraphNode();
307
+ paragraph.select();
308
+ this.replace(paragraph, true);
309
+ }
310
+ return newElement;
311
+ }
312
+ collapseAtStart() {
313
+ const newElement = !this.isEmpty() ? $createHeadingNode(this.getTag()) : lexical.$createParagraphNode();
314
+ const children = this.getChildren();
315
+ children.forEach(child => newElement.append(child));
316
+ this.replace(newElement);
317
+ return true;
318
+ }
319
+ extractWithChild() {
320
+ return true;
321
+ }
322
+ }
323
+ function isGoogleDocsTitle(domNode) {
324
+ if (domNode.nodeName.toLowerCase() === 'span') {
325
+ return domNode.style.fontSize === '26pt';
326
+ }
327
+ return false;
328
+ }
329
+ function $convertHeadingElement(element) {
330
+ const nodeName = element.nodeName.toLowerCase();
331
+ let node = null;
332
+ if (nodeName === 'h1' || nodeName === 'h2' || nodeName === 'h3' || nodeName === 'h4' || nodeName === 'h5' || nodeName === 'h6') {
333
+ node = $createHeadingNode(nodeName);
334
+ if (element.style !== null) {
335
+ lexical.setNodeIndentFromDOM(element, node);
336
+ node.setFormat(element.style.textAlign);
337
+ }
338
+ }
339
+ return {
340
+ node
341
+ };
342
+ }
343
+ function $convertBlockquoteElement(element) {
344
+ const node = $createQuoteNode();
345
+ if (element.style !== null) {
346
+ node.setFormat(element.style.textAlign);
347
+ lexical.setNodeIndentFromDOM(element, node);
348
+ }
349
+ return {
350
+ node
351
+ };
352
+ }
353
+ function $createHeadingNode(headingTag = 'h1') {
354
+ return lexical.$applyNodeReplacement(new HeadingNode(headingTag));
355
+ }
356
+ function $isHeadingNode(node) {
357
+ return node instanceof HeadingNode;
358
+ }
359
+ function onPasteForRichText(event, editor) {
360
+ event.preventDefault();
361
+ editor.update(() => {
362
+ const selection = lexical.$getSelection();
363
+ const clipboardData = lexicalUtils.objectKlassEquals(event, InputEvent) || lexicalUtils.objectKlassEquals(event, KeyboardEvent) ? null : event.clipboardData;
364
+ if (clipboardData != null && selection !== null) {
365
+ lexicalClipboard.$insertDataTransferForRichText(clipboardData, selection, editor);
366
+ }
367
+ }, {
368
+ tag: lexical.PASTE_TAG
369
+ });
370
+ }
371
+ async function onCutForRichText(event, editor) {
372
+ await lexicalClipboard.copyToClipboard(editor, lexicalUtils.objectKlassEquals(event, ClipboardEvent) ? event : null);
373
+ editor.update(() => {
374
+ const selection = lexical.$getSelection();
375
+ if (lexical.$isRangeSelection(selection)) {
376
+ selection.removeText();
377
+ } else if (lexical.$isNodeSelection(selection)) {
378
+ selection.getNodes().forEach(node => node.remove());
379
+ }
380
+ });
381
+ }
382
+
383
+ // Clipboard may contain files that we aren't allowed to read. While the event is arguably useless,
384
+ // in certain occasions, we want to know whether it was a file transfer, as opposed to text. We
385
+ // control this with the first boolean flag.
386
+ function eventFiles(event) {
387
+ let dataTransfer = null;
388
+ if (lexicalUtils.objectKlassEquals(event, DragEvent)) {
389
+ dataTransfer = event.dataTransfer;
390
+ } else if (lexicalUtils.objectKlassEquals(event, ClipboardEvent)) {
391
+ dataTransfer = event.clipboardData;
392
+ }
393
+ if (dataTransfer === null) {
394
+ return [false, [], false];
395
+ }
396
+ const types = dataTransfer.types;
397
+ const hasFiles = types.includes('Files');
398
+ const hasContent = types.includes('text/html') || types.includes('text/plain');
399
+ return [hasFiles, Array.from(dataTransfer.files), hasContent];
400
+ }
401
+ function $handleIndentAndOutdent(indentOrOutdent) {
402
+ const selection = lexical.$getSelection();
403
+ if (!lexical.$isRangeSelection(selection)) {
404
+ return false;
405
+ }
406
+ const alreadyHandled = new Set();
407
+ const nodes = selection.getNodes();
408
+ for (let i = 0; i < nodes.length; i++) {
409
+ const node = nodes[i];
410
+ const key = node.getKey();
411
+ if (alreadyHandled.has(key)) {
412
+ continue;
413
+ }
414
+ const parentBlock = lexicalUtils.$findMatchingParent(node, parentNode => lexical.$isElementNode(parentNode) && !parentNode.isInline());
415
+ if (parentBlock === null) {
416
+ continue;
417
+ }
418
+ const parentKey = parentBlock.getKey();
419
+ if (parentBlock.canIndent() && !alreadyHandled.has(parentKey)) {
420
+ alreadyHandled.add(parentKey);
421
+ indentOrOutdent(parentBlock);
422
+ }
423
+ }
424
+ return alreadyHandled.size > 0;
425
+ }
426
+ function $isTargetWithinDecorator(target) {
427
+ const node = lexical.$getNearestNodeFromDOMNode(target);
428
+ return lexical.$isDecoratorNode(node);
429
+ }
430
+ function $isSelectionAtEndOfRoot(selection) {
431
+ const focus = selection.focus;
432
+ return focus.key === 'root' && focus.offset === lexical.$getRoot().getChildrenSize();
433
+ }
434
+ function $isSelectionCollapsedAtFrontOfIndentedBlock(selection) {
435
+ if (!selection.isCollapsed()) {
436
+ return false;
437
+ }
438
+ const {
439
+ anchor
440
+ } = selection;
441
+ if (anchor.offset !== 0) {
442
+ return false;
443
+ }
444
+ const anchorNode = anchor.getNode();
445
+ if (lexical.$isRootNode(anchorNode)) {
446
+ return false;
447
+ }
448
+ const element = lexicalUtils.$getNearestBlockElementAncestorOrThrow(anchorNode);
449
+ return element.getIndent() > 0 && (element.is(anchorNode) || anchorNode.is(element.getFirstDescendant()));
450
+ }
451
+
452
+ /**
453
+ * Resets the capitalization of the selection to default.
454
+ * Called when the user presses space, tab, or enter key.
455
+ * @param selection The selection to reset the capitalization of.
456
+ */
457
+ function $resetCapitalization(selection) {
458
+ for (const format of ['lowercase', 'uppercase', 'capitalize']) {
459
+ if (selection.hasFormat(format)) {
460
+ selection.toggleFormat(format);
461
+ }
462
+ }
463
+ }
464
+ function registerRichText(editor) {
465
+ const removeListener = lexicalUtils.mergeRegister(editor.registerCommand(lexical.CLICK_COMMAND, payload => {
466
+ const selection = lexical.$getSelection();
467
+ if (lexical.$isNodeSelection(selection)) {
468
+ selection.clear();
469
+ return true;
470
+ }
471
+ return false;
472
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.DELETE_CHARACTER_COMMAND, isBackward => {
473
+ const selection = lexical.$getSelection();
474
+ if (lexical.$isRangeSelection(selection)) {
475
+ selection.deleteCharacter(isBackward);
476
+ return true;
477
+ } else if (lexical.$isNodeSelection(selection)) {
478
+ selection.deleteNodes();
479
+ return true;
480
+ }
481
+ return false;
482
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.DELETE_WORD_COMMAND, isBackward => {
483
+ const selection = lexical.$getSelection();
484
+ if (!lexical.$isRangeSelection(selection)) {
485
+ return false;
486
+ }
487
+ selection.deleteWord(isBackward);
488
+ return true;
489
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.DELETE_LINE_COMMAND, isBackward => {
490
+ const selection = lexical.$getSelection();
491
+ if (!lexical.$isRangeSelection(selection)) {
492
+ return false;
493
+ }
494
+ selection.deleteLine(isBackward);
495
+ return true;
496
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.CONTROLLED_TEXT_INSERTION_COMMAND, eventOrText => {
497
+ const selection = lexical.$getSelection();
498
+ if (typeof eventOrText === 'string') {
499
+ if (selection !== null) {
500
+ selection.insertText(eventOrText);
501
+ }
502
+ } else {
503
+ if (selection === null) {
504
+ return false;
505
+ }
506
+ const dataTransfer = eventOrText.dataTransfer;
507
+ if (dataTransfer != null) {
508
+ lexicalClipboard.$insertDataTransferForRichText(dataTransfer, selection, editor);
509
+ } else if (lexical.$isRangeSelection(selection)) {
510
+ const data = eventOrText.data;
511
+ if (data) {
512
+ selection.insertText(data);
513
+ }
514
+ return true;
515
+ }
516
+ }
517
+ return true;
518
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.REMOVE_TEXT_COMMAND, () => {
519
+ const selection = lexical.$getSelection();
520
+ if (!lexical.$isRangeSelection(selection)) {
521
+ return false;
522
+ }
523
+ selection.removeText();
524
+ return true;
525
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.FORMAT_TEXT_COMMAND, format => {
526
+ const selection = lexical.$getSelection();
527
+ if (!lexical.$isRangeSelection(selection)) {
528
+ return false;
529
+ }
530
+ selection.formatText(format);
531
+ return true;
532
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.FORMAT_ELEMENT_COMMAND, format => {
533
+ const selection = lexical.$getSelection();
534
+ if (!lexical.$isRangeSelection(selection) && !lexical.$isNodeSelection(selection)) {
535
+ return false;
536
+ }
537
+ const nodes = selection.getNodes();
538
+ for (const node of nodes) {
539
+ const element = lexicalUtils.$findMatchingParent(node, parentNode => lexical.$isElementNode(parentNode) && !parentNode.isInline());
540
+ if (element !== null) {
541
+ element.setFormat(format);
542
+ }
543
+ }
544
+ return true;
545
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.INSERT_LINE_BREAK_COMMAND, selectStart => {
546
+ const selection = lexical.$getSelection();
547
+ if (!lexical.$isRangeSelection(selection)) {
548
+ return false;
549
+ }
550
+ selection.insertLineBreak(selectStart);
551
+ return true;
552
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.INSERT_PARAGRAPH_COMMAND, () => {
553
+ const selection = lexical.$getSelection();
554
+ if (!lexical.$isRangeSelection(selection)) {
555
+ return false;
556
+ }
557
+ selection.insertParagraph();
558
+ return true;
559
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.INSERT_TAB_COMMAND, () => {
560
+ lexical.$insertNodes([lexical.$createTabNode()]);
561
+ return true;
562
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.INDENT_CONTENT_COMMAND, () => {
563
+ return $handleIndentAndOutdent(block => {
564
+ const indent = block.getIndent();
565
+ block.setIndent(indent + 1);
566
+ });
567
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.OUTDENT_CONTENT_COMMAND, () => {
568
+ return $handleIndentAndOutdent(block => {
569
+ const indent = block.getIndent();
570
+ if (indent > 0) {
571
+ block.setIndent(Math.max(0, indent - 1));
572
+ }
573
+ });
574
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.KEY_ARROW_UP_COMMAND, event => {
575
+ const selection = lexical.$getSelection();
576
+ if (lexical.$isNodeSelection(selection)) {
577
+ // If selection is on a node, let's try and move selection
578
+ // back to being a range selection.
579
+ const nodes = selection.getNodes();
580
+ if (nodes.length > 0) {
581
+ event.preventDefault();
582
+ nodes[0].selectPrevious();
583
+ return true;
584
+ }
585
+ } else if (lexical.$isRangeSelection(selection)) {
586
+ const possibleNode = lexical.$getAdjacentNode(selection.focus, true);
587
+ if (!event.shiftKey && lexical.$isDecoratorNode(possibleNode) && !possibleNode.isIsolated() && !possibleNode.isInline()) {
588
+ possibleNode.selectPrevious();
589
+ event.preventDefault();
590
+ return true;
591
+ }
592
+ }
593
+ return false;
594
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.KEY_ARROW_DOWN_COMMAND, event => {
595
+ const selection = lexical.$getSelection();
596
+ if (lexical.$isNodeSelection(selection)) {
597
+ // If selection is on a node, let's try and move selection
598
+ // back to being a range selection.
599
+ const nodes = selection.getNodes();
600
+ if (nodes.length > 0) {
601
+ event.preventDefault();
602
+ nodes[0].selectNext(0, 0);
603
+ return true;
604
+ }
605
+ } else if (lexical.$isRangeSelection(selection)) {
606
+ if ($isSelectionAtEndOfRoot(selection)) {
607
+ event.preventDefault();
608
+ return true;
609
+ }
610
+ const possibleNode = lexical.$getAdjacentNode(selection.focus, false);
611
+ if (!event.shiftKey && lexical.$isDecoratorNode(possibleNode) && !possibleNode.isIsolated() && !possibleNode.isInline()) {
612
+ possibleNode.selectNext();
613
+ event.preventDefault();
614
+ return true;
615
+ }
616
+ }
617
+ return false;
618
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.KEY_ARROW_LEFT_COMMAND, event => {
619
+ const selection = lexical.$getSelection();
620
+ if (lexical.$isNodeSelection(selection)) {
621
+ // If selection is on a node, let's try and move selection
622
+ // back to being a range selection.
623
+ const nodes = selection.getNodes();
624
+ if (nodes.length > 0) {
625
+ event.preventDefault();
626
+ if (lexicalSelection.$isParentRTL(nodes[0])) {
627
+ nodes[0].selectNext(0, 0);
628
+ } else {
629
+ nodes[0].selectPrevious();
630
+ }
631
+ return true;
632
+ }
633
+ }
634
+ if (!lexical.$isRangeSelection(selection)) {
635
+ return false;
636
+ }
637
+ if (lexicalSelection.$shouldOverrideDefaultCharacterSelection(selection, true)) {
638
+ const isHoldingShift = event.shiftKey;
639
+ event.preventDefault();
640
+ lexicalSelection.$moveCharacter(selection, isHoldingShift, true);
641
+ return true;
642
+ }
643
+ return false;
644
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.KEY_ARROW_RIGHT_COMMAND, event => {
645
+ const selection = lexical.$getSelection();
646
+ if (lexical.$isNodeSelection(selection)) {
647
+ // If selection is on a node, let's try and move selection
648
+ // back to being a range selection.
649
+ const nodes = selection.getNodes();
650
+ if (nodes.length > 0) {
651
+ event.preventDefault();
652
+ if (lexicalSelection.$isParentRTL(nodes[0])) {
653
+ nodes[0].selectPrevious();
654
+ } else {
655
+ nodes[0].selectNext(0, 0);
656
+ }
657
+ return true;
658
+ }
659
+ }
660
+ if (!lexical.$isRangeSelection(selection)) {
661
+ return false;
662
+ }
663
+ const isHoldingShift = event.shiftKey;
664
+ if (lexicalSelection.$shouldOverrideDefaultCharacterSelection(selection, false)) {
665
+ event.preventDefault();
666
+ lexicalSelection.$moveCharacter(selection, isHoldingShift, false);
667
+ return true;
668
+ }
669
+ return false;
670
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.KEY_BACKSPACE_COMMAND, event => {
671
+ if ($isTargetWithinDecorator(event.target)) {
672
+ return false;
673
+ }
674
+ const selection = lexical.$getSelection();
675
+ if (lexical.$isRangeSelection(selection)) {
676
+ if ($isSelectionCollapsedAtFrontOfIndentedBlock(selection)) {
677
+ event.preventDefault();
678
+ return editor.dispatchCommand(lexical.OUTDENT_CONTENT_COMMAND, undefined);
679
+ }
680
+ // Exception handling for iOS native behavior instead of Lexical's behavior when using Korean on iOS devices.
681
+ // more details - https://github.com/facebook/lexical/issues/5841
682
+ if (IS_IOS && navigator.language === 'ko-KR') {
683
+ return false;
684
+ }
685
+ } else if (!lexical.$isNodeSelection(selection)) {
686
+ return false;
687
+ }
688
+ event.preventDefault();
689
+ return editor.dispatchCommand(lexical.DELETE_CHARACTER_COMMAND, true);
690
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.KEY_DELETE_COMMAND, event => {
691
+ if ($isTargetWithinDecorator(event.target)) {
692
+ return false;
693
+ }
694
+ const selection = lexical.$getSelection();
695
+ if (!(lexical.$isRangeSelection(selection) || lexical.$isNodeSelection(selection))) {
696
+ return false;
697
+ }
698
+ event.preventDefault();
699
+ return editor.dispatchCommand(lexical.DELETE_CHARACTER_COMMAND, false);
700
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.KEY_ENTER_COMMAND, event => {
701
+ const selection = lexical.$getSelection();
702
+ if (!lexical.$isRangeSelection(selection)) {
703
+ return false;
704
+ }
705
+ $resetCapitalization(selection);
706
+ if (event !== null) {
707
+ // If we have beforeinput, then we can avoid blocking
708
+ // the default behavior. This ensures that the iOS can
709
+ // intercept that we're actually inserting a paragraph,
710
+ // and autocomplete, autocapitalize etc work as intended.
711
+ // This can also cause a strange performance issue in
712
+ // Safari, where there is a noticeable pause due to
713
+ // preventing the key down of enter.
714
+ if ((IS_IOS || IS_SAFARI || IS_APPLE_WEBKIT) && CAN_USE_BEFORE_INPUT) {
715
+ return false;
716
+ }
717
+ event.preventDefault();
718
+ if (event.shiftKey) {
719
+ return editor.dispatchCommand(lexical.INSERT_LINE_BREAK_COMMAND, false);
720
+ }
721
+ }
722
+ return editor.dispatchCommand(lexical.INSERT_PARAGRAPH_COMMAND, undefined);
723
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.KEY_ESCAPE_COMMAND, () => {
724
+ const selection = lexical.$getSelection();
725
+ if (!lexical.$isRangeSelection(selection)) {
726
+ return false;
727
+ }
728
+ editor.blur();
729
+ return true;
730
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.DROP_COMMAND, event => {
731
+ const [, files] = eventFiles(event);
732
+ if (files.length > 0) {
733
+ const x = event.clientX;
734
+ const y = event.clientY;
735
+ const eventRange = caretFromPoint(x, y);
736
+ if (eventRange !== null) {
737
+ const {
738
+ offset: domOffset,
739
+ node: domNode
740
+ } = eventRange;
741
+ const node = lexical.$getNearestNodeFromDOMNode(domNode);
742
+ if (node !== null) {
743
+ const selection = lexical.$createRangeSelection();
744
+ if (lexical.$isTextNode(node)) {
745
+ selection.anchor.set(node.getKey(), domOffset, 'text');
746
+ selection.focus.set(node.getKey(), domOffset, 'text');
747
+ } else {
748
+ const parentKey = node.getParentOrThrow().getKey();
749
+ const offset = node.getIndexWithinParent() + 1;
750
+ selection.anchor.set(parentKey, offset, 'element');
751
+ selection.focus.set(parentKey, offset, 'element');
752
+ }
753
+ const normalizedSelection = lexical.$normalizeSelection__EXPERIMENTAL(selection);
754
+ lexical.$setSelection(normalizedSelection);
755
+ }
756
+ editor.dispatchCommand(DRAG_DROP_PASTE, files);
757
+ }
758
+ event.preventDefault();
759
+ return true;
760
+ }
761
+ const selection = lexical.$getSelection();
762
+ if (lexical.$isRangeSelection(selection)) {
763
+ return true;
764
+ }
765
+ return false;
766
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.DRAGSTART_COMMAND, event => {
767
+ const [isFileTransfer] = eventFiles(event);
768
+ const selection = lexical.$getSelection();
769
+ if (isFileTransfer && !lexical.$isRangeSelection(selection)) {
770
+ return false;
771
+ }
772
+ return true;
773
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.DRAGOVER_COMMAND, event => {
774
+ const [isFileTransfer] = eventFiles(event);
775
+ const selection = lexical.$getSelection();
776
+ if (isFileTransfer && !lexical.$isRangeSelection(selection)) {
777
+ return false;
778
+ }
779
+ const x = event.clientX;
780
+ const y = event.clientY;
781
+ const eventRange = caretFromPoint(x, y);
782
+ if (eventRange !== null) {
783
+ const node = lexical.$getNearestNodeFromDOMNode(eventRange.node);
784
+ if (lexical.$isDecoratorNode(node)) {
785
+ // Show browser caret as the user is dragging the media across the screen. Won't work
786
+ // for DecoratorNode nor it's relevant.
787
+ event.preventDefault();
788
+ }
789
+ }
790
+ return true;
791
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.SELECT_ALL_COMMAND, () => {
792
+ lexical.$selectAll();
793
+ return true;
794
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.COPY_COMMAND, event => {
795
+ lexicalClipboard.copyToClipboard(editor, lexicalUtils.objectKlassEquals(event, ClipboardEvent) ? event : null);
796
+ return true;
797
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.CUT_COMMAND, event => {
798
+ onCutForRichText(event, editor);
799
+ return true;
800
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.PASTE_COMMAND, event => {
801
+ const [, files, hasTextContent] = eventFiles(event);
802
+ if (files.length > 0 && !hasTextContent) {
803
+ editor.dispatchCommand(DRAG_DROP_PASTE, files);
804
+ return true;
805
+ }
806
+
807
+ // if inputs then paste within the input ignore creating a new node on paste event
808
+ if (lexical.isDOMNode(event.target) && lexical.isSelectionCapturedInDecoratorInput(event.target)) {
809
+ return false;
810
+ }
811
+ const selection = lexical.$getSelection();
812
+ if (selection !== null) {
813
+ onPasteForRichText(event, editor);
814
+ return true;
815
+ }
816
+ return false;
817
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.KEY_SPACE_COMMAND, _ => {
818
+ const selection = lexical.$getSelection();
819
+ if (lexical.$isRangeSelection(selection)) {
820
+ $resetCapitalization(selection);
821
+ }
822
+ return false;
823
+ }, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.KEY_TAB_COMMAND, _ => {
824
+ const selection = lexical.$getSelection();
825
+ if (lexical.$isRangeSelection(selection)) {
826
+ $resetCapitalization(selection);
827
+ }
828
+ return false;
829
+ }, lexical.COMMAND_PRIORITY_EDITOR));
830
+ return removeListener;
831
+ }
832
+
833
+ /**
834
+ * An extension to register \@ekz/lexical-rich-text behavior and nodes
835
+ * ({@link HeadingNode}, {@link QuoteNode})
836
+ */
837
+ const RichTextExtension = lexical.defineExtension({
838
+ conflictsWith: ['@ekz/lexical-plain-text'],
839
+ dependencies: [lexicalDragon.DragonExtension],
840
+ name: '@ekz/lexical-rich-text',
841
+ nodes: () => [HeadingNode, QuoteNode],
842
+ register: registerRichText
843
+ });
844
+
845
+ exports.$createHeadingNode = $createHeadingNode;
846
+ exports.$createQuoteNode = $createQuoteNode;
847
+ exports.$isHeadingNode = $isHeadingNode;
848
+ exports.$isQuoteNode = $isQuoteNode;
849
+ exports.DRAG_DROP_PASTE = DRAG_DROP_PASTE;
850
+ exports.HeadingNode = HeadingNode;
851
+ exports.QuoteNode = QuoteNode;
852
+ exports.RichTextExtension = RichTextExtension;
853
+ exports.eventFiles = eventFiles;
854
+ exports.registerRichText = registerRichText;