@lexical/rich-text 0.44.1-nightly.20260519.0 → 0.45.1-dev.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.
- package/{LexicalRichText.dev.js → dist/LexicalRichText.dev.js} +268 -147
- package/{LexicalRichText.dev.mjs → dist/LexicalRichText.dev.mjs} +269 -150
- package/{LexicalRichText.mjs → dist/LexicalRichText.mjs} +2 -0
- package/{LexicalRichText.node.mjs → dist/LexicalRichText.node.mjs} +2 -0
- package/dist/LexicalRichText.prod.js +9 -0
- package/dist/LexicalRichText.prod.mjs +9 -0
- package/dist/LexicalRichTextExtension.d.ts +37 -0
- package/dist/RichTextImportExtension.d.ts +27 -0
- package/{index.d.ts → dist/index.d.ts} +2 -53
- package/package.json +35 -20
- package/src/LexicalRichTextExtension.ts +116 -0
- package/src/RichTextImportExtension.ts +124 -0
- package/src/index.ts +1386 -0
- package/LexicalRichText.prod.js +0 -9
- package/LexicalRichText.prod.mjs +0 -9
- /package/{LexicalRichText.js → dist/LexicalRichText.js} +0 -0
- /package/{LexicalRichText.js.flow → dist/LexicalRichText.js.flow} +0 -0
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
11
|
var clipboard = require('@lexical/clipboard');
|
|
12
|
-
var dragon = require('@lexical/dragon');
|
|
13
12
|
var extension = require('@lexical/extension');
|
|
14
13
|
var selection = require('@lexical/selection');
|
|
15
14
|
var utils = require('@lexical/utils');
|
|
16
15
|
var lexical = require('lexical');
|
|
16
|
+
var dragon = require('@lexical/dragon');
|
|
17
|
+
var html = require('@lexical/html');
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
@@ -23,32 +24,82 @@ var lexical = require('lexical');
|
|
|
23
24
|
*
|
|
24
25
|
*/
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Configuration for {@link RichTextExtension}.
|
|
30
|
+
*
|
|
31
|
+
* @property escapeFormatTriggers - Per-format trigger configuration that
|
|
32
|
+
* controls which text formats are automatically cleared from the selection
|
|
33
|
+
* on specific user interactions.
|
|
34
|
+
*
|
|
35
|
+
* Defaults to:
|
|
36
|
+
* ```ts
|
|
37
|
+
* {
|
|
38
|
+
* capitalize: {enter: true, space: true, tab: true},
|
|
39
|
+
* lowercase: {enter: true, space: true, tab: true},
|
|
40
|
+
* uppercase: {enter: true, space: true, tab: true},
|
|
41
|
+
* }
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* To opt in to escaping `code` formatting at text node boundaries:
|
|
45
|
+
* ```ts
|
|
46
|
+
* configExtension(RichTextExtension, {
|
|
47
|
+
* escapeFormatTriggers: {
|
|
48
|
+
* code: {onlyAtBoundary: true, enter: true, click: true, arrow: true},
|
|
49
|
+
* },
|
|
50
|
+
* })
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
const DEFAULT_RICH_TEXT_CONFIG = {
|
|
55
|
+
escapeFormatTriggers: {
|
|
56
|
+
capitalize: {
|
|
57
|
+
enter: true,
|
|
58
|
+
space: true,
|
|
59
|
+
tab: true
|
|
60
|
+
},
|
|
61
|
+
lowercase: {
|
|
62
|
+
enter: true,
|
|
63
|
+
space: true,
|
|
64
|
+
tab: true
|
|
65
|
+
},
|
|
66
|
+
uppercase: {
|
|
67
|
+
enter: true,
|
|
68
|
+
space: true,
|
|
69
|
+
tab: true
|
|
42
70
|
}
|
|
43
|
-
return {
|
|
44
|
-
node: range.offsetNode,
|
|
45
|
-
offset: range.offset
|
|
46
|
-
};
|
|
47
|
-
} else {
|
|
48
|
-
// Gracefully handle IE
|
|
49
|
-
return null;
|
|
50
71
|
}
|
|
72
|
+
};
|
|
73
|
+
function mergeTriggerConfig(config, override) {
|
|
74
|
+
if (!config || override === null) {
|
|
75
|
+
return override;
|
|
76
|
+
}
|
|
77
|
+
return lexical.shallowMergeConfig(config, override);
|
|
51
78
|
}
|
|
79
|
+
function mergeEscapeFormatTriggers(config, overrides) {
|
|
80
|
+
const merged = lexical.shallowMergeConfig(config, overrides);
|
|
81
|
+
for (const k of Object.keys(overrides)) {
|
|
82
|
+
merged[k] = mergeTriggerConfig(config[k], overrides[k]);
|
|
83
|
+
}
|
|
84
|
+
return merged;
|
|
85
|
+
}
|
|
86
|
+
function mergeRichTextConfig(config, overrides) {
|
|
87
|
+
const merged = lexical.shallowMergeConfig(config, overrides);
|
|
88
|
+
if (overrides.escapeFormatTriggers) {
|
|
89
|
+
merged.escapeFormatTriggers = mergeEscapeFormatTriggers(config.escapeFormatTriggers, overrides.escapeFormatTriggers);
|
|
90
|
+
}
|
|
91
|
+
return merged;
|
|
92
|
+
}
|
|
93
|
+
const RichTextExtension = lexical.defineExtension({
|
|
94
|
+
build: (_editor, config) => extension.namedSignals(config),
|
|
95
|
+
config: lexical.safeCast(DEFAULT_RICH_TEXT_CONFIG),
|
|
96
|
+
conflictsWith: ['@lexical/plain-text'],
|
|
97
|
+
dependencies: [dragon.DragonExtension, extension.NormalizeInlineElementsExtension, extension.NormalizeTripleClickSelectionExtension],
|
|
98
|
+
mergeConfig: mergeRichTextConfig,
|
|
99
|
+
name: '@lexical/rich-text',
|
|
100
|
+
nodes: () => [HeadingNode, QuoteNode],
|
|
101
|
+
register: (editor, _config, state) => extension.effect(() => registerRichText(editor, state.getOutput().escapeFormatTriggers))
|
|
102
|
+
});
|
|
52
103
|
|
|
53
104
|
/**
|
|
54
105
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
@@ -58,30 +109,90 @@ function caretFromPoint(x, y) {
|
|
|
58
109
|
*
|
|
59
110
|
*/
|
|
60
111
|
|
|
61
|
-
const CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
|
|
62
112
|
|
|
63
113
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
114
|
+
* Heuristic copied (in spirit) from the legacy `isGoogleDocsTitle`:
|
|
115
|
+
* Google Docs serializes the document title as a `<span style="font-size:
|
|
116
|
+
* 26pt">` (sometimes wrapped in a `<p>`). The presence of a 26pt span at
|
|
117
|
+
* the start of a `<p>` is treated as a heading-1 marker.
|
|
69
118
|
*/
|
|
119
|
+
function isGoogleDocsTitleSpan(node) {
|
|
120
|
+
return lexical.isHTMLElement(node) && node.nodeName === 'SPAN' && node.style.fontSize === '26pt';
|
|
121
|
+
}
|
|
122
|
+
const HeadingRule = html.defineImportRule({
|
|
123
|
+
$import: (ctx, el) => {
|
|
124
|
+
const tag = el.nodeName.toLowerCase();
|
|
125
|
+
const node = $createHeadingNode(tag);
|
|
126
|
+
lexical.setNodeIndentFromDOM(el, node);
|
|
127
|
+
lexical.$setFormatFromDOM(node, el);
|
|
128
|
+
lexical.$setDirectionFromDOM(node, el);
|
|
129
|
+
return [node.splice(0, 0, ctx.$importChildren(el))];
|
|
130
|
+
},
|
|
131
|
+
match: html.sel.tag('h1', 'h2', 'h3', 'h4', 'h5', 'h6'),
|
|
132
|
+
name: '@lexical/rich-text/heading'
|
|
133
|
+
});
|
|
134
|
+
const QuoteRule = html.defineImportRule({
|
|
135
|
+
$import: (ctx, el) => {
|
|
136
|
+
const node = $createQuoteNode();
|
|
137
|
+
lexical.$setFormatFromDOM(node, el);
|
|
138
|
+
lexical.setNodeIndentFromDOM(el, node);
|
|
139
|
+
lexical.$setDirectionFromDOM(node, el);
|
|
140
|
+
return [node.splice(0, 0, ctx.$importChildren(el))];
|
|
141
|
+
},
|
|
142
|
+
match: html.sel.tag('blockquote'),
|
|
143
|
+
name: '@lexical/rich-text/blockquote'
|
|
144
|
+
});
|
|
70
145
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
146
|
+
/**
|
|
147
|
+
* Google-Docs paragraph wrapper around a title span: drop the paragraph,
|
|
148
|
+
* let the span rule below promote to a heading. The body deliberately
|
|
149
|
+
* returns the children unwrapped (no schema, no own node) so the
|
|
150
|
+
* descendant rules — including {@link GoogleDocsTitleSpanRule} — fire and
|
|
151
|
+
* produce the heading at this level.
|
|
152
|
+
*/
|
|
153
|
+
const GoogleDocsTitleParagraphRule = html.defineImportRule({
|
|
154
|
+
$import: (ctx, el, $next) => {
|
|
155
|
+
const first = el.firstChild;
|
|
156
|
+
if (first && isGoogleDocsTitleSpan(first)) {
|
|
157
|
+
return ctx.$importChildren(el);
|
|
158
|
+
}
|
|
159
|
+
return $next();
|
|
160
|
+
},
|
|
161
|
+
match: html.sel.tag('p'),
|
|
162
|
+
name: '@lexical/rich-text/google-docs-title-p'
|
|
163
|
+
});
|
|
164
|
+
const GoogleDocsTitleSpanRule = html.defineImportRule({
|
|
165
|
+
$import: (ctx, el, $next) => el.style.fontSize !== '26pt' ? $next() : [$createHeadingNode('h1').splice(0, 0, ctx.$importChildren(el))],
|
|
166
|
+
match: html.sel.tag('span'),
|
|
167
|
+
name: '@lexical/rich-text/google-docs-title-span'
|
|
168
|
+
});
|
|
76
169
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
170
|
+
/**
|
|
171
|
+
* Import rules for {@link HeadingNode} and {@link QuoteNode}, including
|
|
172
|
+
* the Google Docs title heuristic that the legacy `HeadingNode.importDOM`
|
|
173
|
+
* declared. The Google-Docs rules are registered last (highest priority)
|
|
174
|
+
* so they precede the generic `<p>` and `<span>` rules from
|
|
175
|
+
* {@link CoreImportRules}.
|
|
176
|
+
*
|
|
177
|
+
* @experimental
|
|
178
|
+
*/
|
|
179
|
+
const RichTextImportRules = [HeadingRule, QuoteRule, GoogleDocsTitleParagraphRule, GoogleDocsTitleSpanRule];
|
|
80
180
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
181
|
+
/**
|
|
182
|
+
* Bundles {@link RichTextImportRules} together with the runtime
|
|
183
|
+
* {@link RichTextExtension}. The application is expected to already
|
|
184
|
+
* have `CoreImportExtension` (or some equivalent) in its dependency
|
|
185
|
+
* graph — the core/text/paragraph/inline-format rules are a shared
|
|
186
|
+
* baseline, not something this leaf importer should re-declare.
|
|
187
|
+
*
|
|
188
|
+
* @experimental
|
|
189
|
+
*/
|
|
190
|
+
const RichTextImportExtension = lexical.defineExtension({
|
|
191
|
+
dependencies: [RichTextExtension, lexical.configExtension(html.DOMImportExtension, {
|
|
192
|
+
rules: RichTextImportRules
|
|
193
|
+
})],
|
|
194
|
+
name: '@lexical/rich-text/Import'
|
|
195
|
+
});
|
|
85
196
|
|
|
86
197
|
/**
|
|
87
198
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
@@ -459,59 +570,6 @@ function $isSelectionCollapsedAtFrontOfIndentedBlock(selection) {
|
|
|
459
570
|
* (useful when overriding defaults via `configExtension`).
|
|
460
571
|
*/
|
|
461
572
|
|
|
462
|
-
/**
|
|
463
|
-
* Configuration for {@link RichTextExtension}.
|
|
464
|
-
*
|
|
465
|
-
* @property escapeFormatTriggers - Per-format trigger configuration that
|
|
466
|
-
* controls which text formats are automatically cleared from the selection
|
|
467
|
-
* on specific user interactions.
|
|
468
|
-
*
|
|
469
|
-
* Defaults to:
|
|
470
|
-
* ```ts
|
|
471
|
-
* {
|
|
472
|
-
* capitalize: {enter: true, space: true, tab: true},
|
|
473
|
-
* lowercase: {enter: true, space: true, tab: true},
|
|
474
|
-
* uppercase: {enter: true, space: true, tab: true},
|
|
475
|
-
* }
|
|
476
|
-
* ```
|
|
477
|
-
*
|
|
478
|
-
* To opt in to escaping `code` formatting at text node boundaries:
|
|
479
|
-
* ```ts
|
|
480
|
-
* configExtension(RichTextExtension, {
|
|
481
|
-
* escapeFormatTriggers: {
|
|
482
|
-
* code: {onlyAtBoundary: true, enter: true, click: true, arrow: true},
|
|
483
|
-
* },
|
|
484
|
-
* })
|
|
485
|
-
* ```
|
|
486
|
-
*
|
|
487
|
-
* @property normalizeInlineElements - Adds normalization for each
|
|
488
|
-
* subclass of ElementNode, which removes empty inline elements.
|
|
489
|
-
* This option is intended to facilitate a smooth migration
|
|
490
|
-
* from the plugin API and may be removed in the future
|
|
491
|
-
*
|
|
492
|
-
* Default: true
|
|
493
|
-
*
|
|
494
|
-
*/
|
|
495
|
-
|
|
496
|
-
const DEFAULT_RICH_TEXT_CONFIG = {
|
|
497
|
-
escapeFormatTriggers: {
|
|
498
|
-
capitalize: {
|
|
499
|
-
enter: true,
|
|
500
|
-
space: true,
|
|
501
|
-
tab: true
|
|
502
|
-
},
|
|
503
|
-
lowercase: {
|
|
504
|
-
enter: true,
|
|
505
|
-
space: true,
|
|
506
|
-
tab: true
|
|
507
|
-
},
|
|
508
|
-
uppercase: {
|
|
509
|
-
enter: true,
|
|
510
|
-
space: true,
|
|
511
|
-
tab: true
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
};
|
|
515
573
|
function $escapeFormatsForTrigger(selection, trigger, direction, config) {
|
|
516
574
|
let isBoundary = false;
|
|
517
575
|
let anchorNode = null;
|
|
@@ -545,27 +603,24 @@ function $escapeFormatsForTrigger(selection, trigger, direction, config) {
|
|
|
545
603
|
selection.setStyle('');
|
|
546
604
|
}
|
|
547
605
|
}
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
606
|
+
const DEFAULT_ESCAPE_FORMAT_TRIGGERS = {
|
|
607
|
+
capitalize: {
|
|
608
|
+
enter: true,
|
|
609
|
+
space: true,
|
|
610
|
+
tab: true
|
|
611
|
+
},
|
|
612
|
+
lowercase: {
|
|
613
|
+
enter: true,
|
|
614
|
+
space: true,
|
|
615
|
+
tab: true
|
|
616
|
+
},
|
|
617
|
+
uppercase: {
|
|
618
|
+
enter: true,
|
|
619
|
+
space: true,
|
|
620
|
+
tab: true
|
|
552
621
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
function mergeTriggerConfig(config, override) {
|
|
556
|
-
if (!config || override === null) {
|
|
557
|
-
return override;
|
|
558
|
-
}
|
|
559
|
-
return lexical.shallowMergeConfig(config, override);
|
|
560
|
-
}
|
|
561
|
-
function mergeRichTextConfig(config, overrides) {
|
|
562
|
-
const merged = lexical.shallowMergeConfig(config, overrides);
|
|
563
|
-
if (overrides.escapeFormatTriggers) {
|
|
564
|
-
merged.escapeFormatTriggers = mergeEscapeFormatTriggers(config.escapeFormatTriggers, overrides.escapeFormatTriggers);
|
|
565
|
-
}
|
|
566
|
-
return merged;
|
|
567
|
-
}
|
|
568
|
-
function registerRichText(editor, escapeFormatTriggers = extension.signal(DEFAULT_RICH_TEXT_CONFIG.escapeFormatTriggers)) {
|
|
622
|
+
};
|
|
623
|
+
function registerRichText(editor, escapeFormatTriggers = extension.signal(DEFAULT_ESCAPE_FORMAT_TRIGGERS)) {
|
|
569
624
|
const removeListener = utils.mergeRegister(editor.registerCommand(lexical.CLICK_COMMAND, () => {
|
|
570
625
|
const selection = lexical.$getSelection();
|
|
571
626
|
if (lexical.$isNodeSelection(selection)) {
|
|
@@ -798,7 +853,7 @@ function registerRichText(editor, escapeFormatTriggers = extension.signal(DEFAUL
|
|
|
798
853
|
}
|
|
799
854
|
// Exception handling for iOS native behavior instead of Lexical's behavior when using Korean on iOS devices.
|
|
800
855
|
// more details - https://github.com/facebook/lexical/issues/5841
|
|
801
|
-
if (IS_IOS && navigator.language === 'ko-KR') {
|
|
856
|
+
if (lexical.IS_IOS && navigator.language === 'ko-KR') {
|
|
802
857
|
return false;
|
|
803
858
|
}
|
|
804
859
|
} else if (!lexical.$isNodeSelection(selection)) {
|
|
@@ -817,7 +872,20 @@ function registerRichText(editor, escapeFormatTriggers = extension.signal(DEFAUL
|
|
|
817
872
|
event.preventDefault();
|
|
818
873
|
return editor.dispatchCommand(lexical.DELETE_CHARACTER_COMMAND, false);
|
|
819
874
|
}, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.KEY_ENTER_COMMAND, event => {
|
|
820
|
-
|
|
875
|
+
let selection = lexical.$getSelection();
|
|
876
|
+
// When a block-level DecoratorNode is selected as a NodeSelection
|
|
877
|
+
// (e.g. it is the only root child after the user removed all
|
|
878
|
+
// surrounding paragraphs), Enter has no RangeSelection to act on
|
|
879
|
+
// and the default handler bails out, leaving the editor stuck.
|
|
880
|
+
// Convert to a RangeSelection past the decorator so the default
|
|
881
|
+
// RangeSelection handler below inserts a paragraph and places
|
|
882
|
+
// the caret.
|
|
883
|
+
if (lexical.$isNodeSelection(selection)) {
|
|
884
|
+
const nodes = selection.getNodes();
|
|
885
|
+
if (nodes.length === 1 && lexical.$isDecoratorNode(nodes[0]) && !nodes[0].isInline()) {
|
|
886
|
+
selection = nodes[0].selectNext();
|
|
887
|
+
}
|
|
888
|
+
}
|
|
821
889
|
if (!lexical.$isRangeSelection(selection)) {
|
|
822
890
|
return false;
|
|
823
891
|
}
|
|
@@ -830,7 +898,7 @@ function registerRichText(editor, escapeFormatTriggers = extension.signal(DEFAUL
|
|
|
830
898
|
// This can also cause a strange performance issue in
|
|
831
899
|
// Safari, where there is a noticeable pause due to
|
|
832
900
|
// preventing the key down of enter.
|
|
833
|
-
if ((IS_IOS || IS_SAFARI || IS_APPLE_WEBKIT) && CAN_USE_BEFORE_INPUT) {
|
|
901
|
+
if ((lexical.IS_IOS || lexical.IS_SAFARI || lexical.IS_APPLE_WEBKIT) && lexical.CAN_USE_BEFORE_INPUT) {
|
|
834
902
|
return false;
|
|
835
903
|
}
|
|
836
904
|
event.preventDefault();
|
|
@@ -851,7 +919,7 @@ function registerRichText(editor, escapeFormatTriggers = extension.signal(DEFAUL
|
|
|
851
919
|
if (files.length > 0) {
|
|
852
920
|
const x = event.clientX;
|
|
853
921
|
const y = event.clientY;
|
|
854
|
-
const eventRange = caretFromPoint(x, y);
|
|
922
|
+
const eventRange = clipboard.caretFromPoint(x, y);
|
|
855
923
|
if (eventRange !== null) {
|
|
856
924
|
const {
|
|
857
925
|
offset: domOffset,
|
|
@@ -902,7 +970,7 @@ function registerRichText(editor, escapeFormatTriggers = extension.signal(DEFAUL
|
|
|
902
970
|
}
|
|
903
971
|
const x = event.clientX;
|
|
904
972
|
const y = event.clientY;
|
|
905
|
-
const eventRange = caretFromPoint(x, y);
|
|
973
|
+
const eventRange = clipboard.caretFromPoint(x, y);
|
|
906
974
|
if (eventRange !== null) {
|
|
907
975
|
const node = lexical.$getNearestNodeFromDOMNode(eventRange.node);
|
|
908
976
|
if (lexical.$isDecoratorNode(node)) {
|
|
@@ -950,37 +1018,88 @@ function registerRichText(editor, escapeFormatTriggers = extension.signal(DEFAUL
|
|
|
950
1018
|
$escapeFormatsForTrigger(selection, 'tab', 'both', escapeFormatTriggers.peek());
|
|
951
1019
|
}
|
|
952
1020
|
return false;
|
|
1021
|
+
}, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.MOVE_TO_END, event => {
|
|
1022
|
+
const selection = lexical.$getSelection();
|
|
1023
|
+
if (!lexical.$isRangeSelection(selection)) {
|
|
1024
|
+
return false;
|
|
1025
|
+
}
|
|
1026
|
+
const {
|
|
1027
|
+
anchor
|
|
1028
|
+
} = selection;
|
|
1029
|
+
if (anchor.type !== 'element' || anchor.offset !== 0) {
|
|
1030
|
+
return false;
|
|
1031
|
+
}
|
|
1032
|
+
const element = anchor.getNode();
|
|
1033
|
+
if (!lexical.$isElementNode(element)) {
|
|
1034
|
+
return false;
|
|
1035
|
+
}
|
|
1036
|
+
const firstChild = element.getFirstChild();
|
|
1037
|
+
if (!lexical.$isDecoratorNode(firstChild) || !firstChild.isInline()) {
|
|
1038
|
+
return false;
|
|
1039
|
+
}
|
|
1040
|
+
const lastDescendant = element.getLastDescendant();
|
|
1041
|
+
if (lastDescendant == null || lexical.$isDecoratorNode(lastDescendant)) {
|
|
1042
|
+
// No selectable text — fall through to native browser behavior.
|
|
1043
|
+
return false;
|
|
1044
|
+
}
|
|
1045
|
+
// Native browser cursor traversal stops at the inline decorator's
|
|
1046
|
+
// contenteditable=false boundary when the caret starts at element
|
|
1047
|
+
// offset 0, so MOVE_TO_END leaves the caret stuck. Move it ourselves.
|
|
1048
|
+
const elementKey = element.getKey();
|
|
1049
|
+
const ending = element.selectEnd();
|
|
1050
|
+
if (event.shiftKey) {
|
|
1051
|
+
ending.anchor.set(elementKey, 0, 'element');
|
|
1052
|
+
}
|
|
1053
|
+
event.preventDefault();
|
|
1054
|
+
event.stopPropagation();
|
|
1055
|
+
return true;
|
|
1056
|
+
}, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.MOVE_TO_START, event => {
|
|
1057
|
+
const selection = lexical.$getSelection();
|
|
1058
|
+
if (!lexical.$isRangeSelection(selection)) {
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
const {
|
|
1062
|
+
anchor,
|
|
1063
|
+
focus
|
|
1064
|
+
} = selection;
|
|
1065
|
+
const focusBlock = utils.$findMatchingParent(focus.getNode(), node => lexical.$isElementNode(node) && !node.isInline());
|
|
1066
|
+
if (focusBlock === null) {
|
|
1067
|
+
return false;
|
|
1068
|
+
}
|
|
1069
|
+
const firstChild = focusBlock.getFirstChild();
|
|
1070
|
+
if (!lexical.$isDecoratorNode(firstChild) || !firstChild.isInline()) {
|
|
1071
|
+
return false;
|
|
1072
|
+
}
|
|
1073
|
+
const lastDescendant = focusBlock.getLastDescendant();
|
|
1074
|
+
if (lastDescendant == null || lexical.$isDecoratorNode(lastDescendant)) {
|
|
1075
|
+
// No selectable text — fall through to native browser behavior.
|
|
1076
|
+
return false;
|
|
1077
|
+
}
|
|
1078
|
+
// Cross-block selections fall through to native handling. The
|
|
1079
|
+
// Chromium boundary bug only matters when both endpoints sit
|
|
1080
|
+
// inside the block whose first child is the inline decorator.
|
|
1081
|
+
const anchorBlock = utils.$findMatchingParent(anchor.getNode(), node => lexical.$isElementNode(node) && !node.isInline());
|
|
1082
|
+
if (anchorBlock !== focusBlock) {
|
|
1083
|
+
return false;
|
|
1084
|
+
}
|
|
1085
|
+
const blockKey = focusBlock.getKey();
|
|
1086
|
+
if (focus.type === 'element' && focus.key === blockKey && focus.offset === 0) {
|
|
1087
|
+
return false;
|
|
1088
|
+
}
|
|
1089
|
+
// Symmetric to the MOVE_TO_END case: Chromium stops the native
|
|
1090
|
+
// caret at the inline decorator's contenteditable=false boundary
|
|
1091
|
+
// when moving backwards, so element offset 0 is unreachable.
|
|
1092
|
+
selection.focus.set(blockKey, 0, 'element');
|
|
1093
|
+
if (!event.shiftKey) {
|
|
1094
|
+
selection.anchor.set(blockKey, 0, 'element');
|
|
1095
|
+
}
|
|
1096
|
+
event.preventDefault();
|
|
1097
|
+
event.stopPropagation();
|
|
1098
|
+
return true;
|
|
953
1099
|
}, lexical.COMMAND_PRIORITY_EDITOR));
|
|
954
1100
|
return removeListener;
|
|
955
1101
|
}
|
|
956
1102
|
|
|
957
|
-
/**
|
|
958
|
-
* An extension to register \@lexical/rich-text behavior and nodes
|
|
959
|
-
* ({@link HeadingNode}, {@link QuoteNode}).
|
|
960
|
-
*
|
|
961
|
-
* Includes configurable format escape via `escapeFormatTriggers`.
|
|
962
|
-
* Use `configExtension` to customize which formats escape on which triggers.
|
|
963
|
-
*
|
|
964
|
-
* @example
|
|
965
|
-
* ```ts
|
|
966
|
-
* configExtension(RichTextExtension, {
|
|
967
|
-
* escapeFormatTriggers: {
|
|
968
|
-
* code: {click: true, arrow: true},
|
|
969
|
-
* },
|
|
970
|
-
* })
|
|
971
|
-
* ```
|
|
972
|
-
*/
|
|
973
|
-
const RichTextExtension = lexical.defineExtension({
|
|
974
|
-
build: (_editor, config) => extension.namedSignals(config),
|
|
975
|
-
config: lexical.safeCast(DEFAULT_RICH_TEXT_CONFIG),
|
|
976
|
-
conflictsWith: ['@lexical/plain-text'],
|
|
977
|
-
dependencies: [dragon.DragonExtension, extension.NormalizeInlineElementsExtension],
|
|
978
|
-
mergeConfig: mergeRichTextConfig,
|
|
979
|
-
name: '@lexical/rich-text',
|
|
980
|
-
nodes: () => [HeadingNode, QuoteNode],
|
|
981
|
-
register: (editor, _config, state) => extension.effect(() => registerRichText(editor, state.getOutput().escapeFormatTriggers))
|
|
982
|
-
});
|
|
983
|
-
|
|
984
1103
|
exports.$createHeadingNode = $createHeadingNode;
|
|
985
1104
|
exports.$createQuoteNode = $createQuoteNode;
|
|
986
1105
|
exports.$isHeadingNode = $isHeadingNode;
|
|
@@ -989,5 +1108,7 @@ exports.DRAG_DROP_PASTE = DRAG_DROP_PASTE;
|
|
|
989
1108
|
exports.HeadingNode = HeadingNode;
|
|
990
1109
|
exports.QuoteNode = QuoteNode;
|
|
991
1110
|
exports.RichTextExtension = RichTextExtension;
|
|
1111
|
+
exports.RichTextImportExtension = RichTextImportExtension;
|
|
1112
|
+
exports.RichTextImportRules = RichTextImportRules;
|
|
992
1113
|
exports.eventFiles = eventFiles;
|
|
993
1114
|
exports.registerRichText = registerRichText;
|