@lexical/code-core 0.44.1-nightly.20260518.0 → 0.45.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,50 @@
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
+ import type { DOMPreprocessFn } from '@lexical/html';
9
+ /**
10
+ * VS Code → browser code-block pastes ship the block as either:
11
+ *
12
+ * - **Chrome**: one outer
13
+ * `<div style="font-family: …monospace…; white-space: pre">…</div>`
14
+ * wrapping per-line `<div>`s and `<br>`s.
15
+ * - **Safari**: a flat run of sibling
16
+ * `<div style="…monospace…; white-space: pre">…</div>` and
17
+ * `<br style="…monospace…; …">` elements with no wrapping
18
+ * monospace ancestor (the styles are duplicated onto every
19
+ * element).
20
+ *
21
+ * The legacy `<div>` rule (and {@link DivRule}) produces one CodeNode
22
+ * per `<div>` on Safari and concatenates inner divs without
23
+ * separating `\n`s on Chrome. This preprocess scans once for the
24
+ * structural signature and, only when it matches, pushes
25
+ * {@link VscodeCodePasteOverlay} onto {@link ImportOverlays} so the
26
+ * VS Code-specific rules participate in the walk. Pastes from other
27
+ * sources pay only the detection cost.
28
+ *
29
+ * @experimental
30
+ */
31
+ export declare const $installVscodeCodePasteOverlay: DOMPreprocessFn;
32
+ /**
33
+ * Import rules for {@link CodeNode}.
34
+ *
35
+ * Specific class-restricted rules (GitHub raw-file-view detectors) are
36
+ * registered before the generic `<table>` / `<tr>` / `<td>` rules so
37
+ * they win dispatch.
38
+ *
39
+ * @experimental
40
+ */
41
+ export declare const CodeImportRules: import("@lexical/html").DOMImportRule<import("@lexical/html").ElementSelectorBuilder<HTMLElement, Record<string, never>>>[];
42
+ /**
43
+ * Bundles {@link CodeImportRules} (plus {@link CoreImportExtension}) into
44
+ * a single dependency. The legacy {@link CodeNode.importDOM} continues to
45
+ * work in parallel; depend on this extension to opt into the new
46
+ * pipeline.
47
+ *
48
+ * @experimental
49
+ */
50
+ export declare const CodeImportExtension: import("lexical").LexicalExtension<import("lexical").ExtensionConfigBase, "@lexical/code/Import", unknown, unknown>;
@@ -13,6 +13,7 @@ export type SerializedCodeNode = Spread<{
13
13
  theme?: string | undefined;
14
14
  }, SerializedElementNode>;
15
15
  export declare const DEFAULT_CODE_LANGUAGE = "javascript";
16
+ /** @internal Configurable through the extensions. */
16
17
  export declare const getDefaultCodeLanguage: () => string;
17
18
  /** @noInheritDoc */
18
19
  export declare class CodeNode extends ElementNode {
@@ -6,7 +6,7 @@
6
6
  *
7
7
  */
8
8
  import type { CodeHighlightNode } from './CodeHighlightNode';
9
- import type { LineBreakNode, RangeSelection, TabNode } from 'lexical';
9
+ import type { LexicalNode, LineBreakNode, RangeSelection, TabNode } from 'lexical';
10
10
  export declare function $getFirstCodeNodeOfLine(anchor: CodeHighlightNode | TabNode | LineBreakNode): CodeHighlightNode | TabNode | LineBreakNode;
11
11
  export declare function $getLastCodeNodeOfLine(anchor: CodeHighlightNode | TabNode | LineBreakNode): CodeHighlightNode | TabNode | LineBreakNode;
12
12
  /**
@@ -23,6 +23,15 @@ export declare function $getStartOfCodeInLine(anchor: CodeHighlightNode | TabNod
23
23
  offset: number;
24
24
  };
25
25
  export declare function $getEndOfCodeInLine(anchor: CodeHighlightNode | TabNode): CodeHighlightNode | TabNode;
26
+ /**
27
+ * Plain split of code text into CodeHighlightNodes (with no highlight
28
+ * type) + LineBreakNodes + TabNodes. Used when the tokenizer opts out
29
+ * of a default language so a previously highlighted block still
30
+ * renders its `\n` / `\t` as real line breaks / tabs, while staying
31
+ * compatible with the indent / shift-lines handlers that only accept
32
+ * CodeHighlightNode + TabNode + LineBreakNode inside a CodeNode.
33
+ */
34
+ export declare function $plainifyCodeContent(text: string): LexicalNode[];
26
35
  /**
27
36
  * Strip up to `tabSize` leading spaces from a {@link CodeHighlightNode} that
28
37
  * starts a code line, to support outdenting space-indented code lines (e.g.
@@ -10,6 +10,7 @@
10
10
 
11
11
  var lexical = require('lexical');
12
12
  var extension = require('@lexical/extension');
13
+ var html = require('@lexical/html');
13
14
 
14
15
  /**
15
16
  * Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -19,6 +20,7 @@ var extension = require('@lexical/extension');
19
20
  *
20
21
  */
21
22
 
23
+
22
24
  /*@__INLINE__*/
23
25
  function warnOnlyOnce(message) {
24
26
  {
@@ -192,6 +194,34 @@ function $getEndOfCodeInLine(anchor) {
192
194
  return lastNode;
193
195
  }
194
196
 
197
+ /**
198
+ * Plain split of code text into CodeHighlightNodes (with no highlight
199
+ * type) + LineBreakNodes + TabNodes. Used when the tokenizer opts out
200
+ * of a default language so a previously highlighted block still
201
+ * renders its `\n` / `\t` as real line breaks / tabs, while staying
202
+ * compatible with the indent / shift-lines handlers that only accept
203
+ * CodeHighlightNode + TabNode + LineBreakNode inside a CodeNode.
204
+ */
205
+ function $plainifyCodeContent(text) {
206
+ const out = [];
207
+ const lines = text.split('\n');
208
+ lines.forEach((line, lineIdx) => {
209
+ if (lineIdx > 0) {
210
+ out.push(lexical.$createLineBreakNode());
211
+ }
212
+ const tabParts = line.split('\t');
213
+ tabParts.forEach((part, partIdx) => {
214
+ if (partIdx > 0) {
215
+ out.push(lexical.$createTabNode());
216
+ }
217
+ if (part.length > 0) {
218
+ out.push($createCodeHighlightNode(part));
219
+ }
220
+ });
221
+ });
222
+ return out;
223
+ }
224
+
195
225
  /**
196
226
  * Strip up to `tabSize` leading spaces from a {@link CodeHighlightNode} that
197
227
  * starts a code line, to support outdenting space-indented code lines (e.g.
@@ -238,6 +268,7 @@ function $outdentLeadingSpaces(node, tabSize, selection) {
238
268
  */
239
269
 
240
270
  const DEFAULT_CODE_LANGUAGE = 'javascript';
271
+ /** @internal Configurable through the extensions. */
241
272
  const getDefaultCodeLanguage = () => DEFAULT_CODE_LANGUAGE;
242
273
  function hasChildDOMNodeTag(node, tagName) {
243
274
  for (const child of node.childNodes) {
@@ -250,7 +281,7 @@ function hasChildDOMNodeTag(node, tagName) {
250
281
  }
251
282
  return false;
252
283
  }
253
- const LANGUAGE_DATA_ATTRIBUTE = 'data-language';
284
+ const LANGUAGE_DATA_ATTRIBUTE$1 = 'data-language';
254
285
  const HIGHLIGHT_LANGUAGE_DATA_ATTRIBUTE = 'data-highlight-language';
255
286
  const THEME_DATA_ATTRIBUTE = 'data-theme';
256
287
  const noExtensionDeprecation = warnOnlyOnce('Using CodeNode without CodeExtension is deprecated');
@@ -289,7 +320,7 @@ class CodeNode extends lexical.ElementNode {
289
320
  element.setAttribute('spellcheck', 'false');
290
321
  const language = this.getLanguage();
291
322
  if (language) {
292
- element.setAttribute(LANGUAGE_DATA_ATTRIBUTE, language);
323
+ element.setAttribute(LANGUAGE_DATA_ATTRIBUTE$1, language);
293
324
  if (this.getIsSyntaxHighlightSupported()) {
294
325
  element.setAttribute(HIGHLIGHT_LANGUAGE_DATA_ATTRIBUTE, language);
295
326
  }
@@ -309,10 +340,10 @@ class CodeNode extends lexical.ElementNode {
309
340
  const prevLanguage = prevNode.__language;
310
341
  if (language) {
311
342
  if (language !== prevLanguage) {
312
- dom.setAttribute(LANGUAGE_DATA_ATTRIBUTE, language);
343
+ dom.setAttribute(LANGUAGE_DATA_ATTRIBUTE$1, language);
313
344
  }
314
345
  } else if (prevLanguage) {
315
- dom.removeAttribute(LANGUAGE_DATA_ATTRIBUTE);
346
+ dom.removeAttribute(LANGUAGE_DATA_ATTRIBUTE$1);
316
347
  }
317
348
  const isSyntaxHighlightSupported = this.__isSyntaxHighlightSupported;
318
349
  const prevIsSyntaxHighlightSupported = prevNode.__isSyntaxHighlightSupported;
@@ -349,7 +380,7 @@ class CodeNode extends lexical.ElementNode {
349
380
  element.setAttribute('spellcheck', 'false');
350
381
  const language = this.getLanguage();
351
382
  if (language) {
352
- element.setAttribute(LANGUAGE_DATA_ATTRIBUTE, language);
383
+ element.setAttribute(LANGUAGE_DATA_ATTRIBUTE$1, language);
353
384
  if (this.getIsSyntaxHighlightSupported()) {
354
385
  element.setAttribute(HIGHLIGHT_LANGUAGE_DATA_ATTRIBUTE, language);
355
386
  }
@@ -548,7 +579,7 @@ function $isCodeNode(node) {
548
579
  return node instanceof CodeNode;
549
580
  }
550
581
  function $convertPreElement(domNode) {
551
- const language = domNode.getAttribute(LANGUAGE_DATA_ATTRIBUTE);
582
+ const language = domNode.getAttribute(LANGUAGE_DATA_ATTRIBUTE$1);
552
583
  return {
553
584
  node: $createCodeNode(language)
554
585
  };
@@ -736,6 +767,331 @@ const CodeExtension = lexical.defineExtension({
736
767
  }
737
768
  });
738
769
 
770
+ /**
771
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
772
+ *
773
+ * This source code is licensed under the MIT license found in the
774
+ * LICENSE file in the root directory of this source tree.
775
+ *
776
+ */
777
+
778
+ const LANGUAGE_DATA_ATTRIBUTE = 'data-language';
779
+
780
+ /**
781
+ * True for elements whose `font-family` mentions `monospace` — the
782
+ * heuristic the legacy `<div>` rule uses to spot copy-pasted code blocks
783
+ * (e.g. Google Docs serializes a code block as a styled `<div>`).
784
+ */
785
+ function isMonospaceElement(el) {
786
+ return el.style.fontFamily.match('monospace') !== null;
787
+ }
788
+ function isMonospaceDescendant(node) {
789
+ let parent = node.parentElement;
790
+ while (parent !== null) {
791
+ if (isMonospaceElement(parent)) {
792
+ return true;
793
+ }
794
+ parent = parent.parentElement;
795
+ }
796
+ return false;
797
+ }
798
+
799
+ /**
800
+ * Overlay rules active only while {@link GitHubCodeTableRule} is
801
+ * processing its children. Inside the code-table subtree, every `<tr>`
802
+ * and `<td>` unwraps unconditionally — they never become table-row /
803
+ * table-cell nodes (even when `@lexical/table` registers its rules for
804
+ * those tags). Outside the subtree, this overlay isn't installed, so
805
+ * the cost of these rules is never paid against unrelated `<tr>` /
806
+ * `<td>` pastes.
807
+ */
808
+ const GitHubCodeTableOverlayRules = html.defineOverlayRules([html.defineImportRule({
809
+ $import: (ctx, el) => ctx.$importChildren(el),
810
+ match: html.sel.tag('tr', 'td'),
811
+ name: '@lexical/code/github-code-table/unwrap'
812
+ })]);
813
+ const PreRule = html.defineImportRule({
814
+ $import: (ctx, el) => [$createCodeNode(el.getAttribute(LANGUAGE_DATA_ATTRIBUTE)).splice(0, 0, ctx.$importChildren(el))],
815
+ match: html.sel.tag('pre'),
816
+ name: '@lexical/code/pre'
817
+ });
818
+
819
+ /**
820
+ * Multi-line `<code>` (containing newlines or `<br>`) is treated as a
821
+ * block code element — mirrors the legacy behavior. Single-line `<code>`
822
+ * defers to the inline-format rule from `CoreImportExtension` so it
823
+ * becomes a TextNode with IS_CODE.
824
+ */
825
+ const MultilineCodeRule = html.defineImportRule({
826
+ $import: (ctx, el, $next) => {
827
+ const text = el.textContent || '';
828
+ const isMultiLine = /\r?\n/.test(text) || el.querySelector('br') !== null;
829
+ if (!isMultiLine) {
830
+ return $next();
831
+ }
832
+ return [$createCodeNode(el.getAttribute(LANGUAGE_DATA_ATTRIBUTE)).splice(0, 0, ctx.$importChildren(el))];
833
+ },
834
+ match: html.sel.tag('code'),
835
+ name: '@lexical/code/code-multiline'
836
+ });
837
+
838
+ /**
839
+ * True for elements carrying BOTH `font-family: …monospace…` and
840
+ * `white-space: pre*` inline — the shape VS Code uses for every line
841
+ * of a copied code block (on every per-line `<div>` on Safari, on
842
+ * the single outer wrapper on Chrome).
843
+ */
844
+ function isMonospacePreElement(el) {
845
+ if (!lexical.isHTMLElement(el)) {
846
+ return false;
847
+ }
848
+ const ff = el.style.fontFamily;
849
+ const ws = el.style.whiteSpace;
850
+ return typeof ff === 'string' && /monospace/i.test(ff) && typeof ws === 'string' && ws.startsWith('pre');
851
+ }
852
+
853
+ /**
854
+ * Split a monospace-pre wrapper element into logical code lines:
855
+ * `<div>` children contribute their text content as one line,
856
+ * `<br>` children contribute an empty line, inline children (spans
857
+ * and bare text) accumulate into the current line until the next
858
+ * block child.
859
+ *
860
+ * Returns `null` if `el` has no block children (i.e. it's a leaf
861
+ * line, not a wrapper) so the caller can leave it to the
862
+ * sibling-run pass.
863
+ */
864
+ function splitMonospaceWrapperLines(el) {
865
+ let hasBlockChild = false;
866
+ const lines = [];
867
+ let acc = '';
868
+ let hasAcc = false;
869
+ const flush = () => {
870
+ if (hasAcc) {
871
+ lines.push(acc);
872
+ acc = '';
873
+ hasAcc = false;
874
+ }
875
+ };
876
+ for (const child of Array.from(el.childNodes)) {
877
+ if (lexical.isHTMLElement(child)) {
878
+ if (child.tagName === 'DIV') {
879
+ flush();
880
+ lines.push(child.textContent || '');
881
+ hasBlockChild = true;
882
+ } else if (child.tagName === 'BR') {
883
+ flush();
884
+ lines.push('');
885
+ hasBlockChild = true;
886
+ } else {
887
+ acc += child.textContent || '';
888
+ hasAcc = true;
889
+ }
890
+ } else if (lexical.isDOMTextNode(child)) {
891
+ const t = child.textContent || '';
892
+ if (t.length > 0) {
893
+ acc += t;
894
+ hasAcc = true;
895
+ }
896
+ }
897
+ }
898
+ flush();
899
+ return hasBlockChild ? lines : null;
900
+ }
901
+
902
+ /**
903
+ * Returns `true` if `root` contains the structural signature of a
904
+ * VS Code code-block paste:
905
+ *
906
+ * - a monospace+pre `<div>` wrapper with at least one block (`<div>` /
907
+ * `<br>`) child — the Chrome shape, or
908
+ * - two or more consecutive monospace+pre siblings — the Safari shape.
909
+ *
910
+ * Walked once in preprocess; the matching overlay is only installed
911
+ * when this returns `true` so an unrelated paste doesn't pay for the
912
+ * detection or rule cost.
913
+ */
914
+ function looksLikeVscodePaste(root) {
915
+ for (const child of Array.from(root.children)) {
916
+ if (lexical.isHTMLElement(child) && isMonospacePreElement(child)) {
917
+ const lines = splitMonospaceWrapperLines(child);
918
+ if (lines !== null) {
919
+ return true;
920
+ }
921
+ const next = child.nextElementSibling;
922
+ if (next && isMonospacePreElement(next)) {
923
+ return true;
924
+ }
925
+ continue;
926
+ }
927
+ if (looksLikeVscodePaste(child)) {
928
+ return true;
929
+ }
930
+ }
931
+ return false;
932
+ }
933
+
934
+ /**
935
+ * Match a monospace+pre `<div>` whose direct children include block
936
+ * (`<div>` / `<br>`) elements — the Chrome shape, one outer wrapper
937
+ * around per-line `<div>`s and `<br>`s. Emits a single CodeNode whose
938
+ * text is the wrapper's lines joined by `\n`.
939
+ */
940
+ const VscodeWrapperRule = html.defineImportRule({
941
+ $import: (_ctx, el, $next) => {
942
+ if (!isMonospacePreElement(el) || isMonospaceDescendant(el)) {
943
+ return $next();
944
+ }
945
+ const lines = splitMonospaceWrapperLines(el);
946
+ if (lines === null || lines.length === 0) {
947
+ return $next();
948
+ }
949
+ return [$createCodeNode().splice(0, 0, lexical.$generateNodesFromRawText(lines.join('\n')))];
950
+ },
951
+ match: html.sel.tag('div'),
952
+ name: '@lexical/code/vscode-wrapper'
953
+ });
954
+
955
+ /**
956
+ * Match the first of a run of consecutive monospace+pre `<div>` /
957
+ * `<br>` siblings (the Safari shape) and emit one CodeNode for the
958
+ * whole run. When the framework's per-child dispatch lands on a
959
+ * subsequent sibling in the same run, the prev-sibling check below
960
+ * returns `[]` so the run is only emitted once.
961
+ */
962
+ const VscodeLineRunRule = html.defineImportRule({
963
+ $import: (_ctx, el, $next) => {
964
+ if (!isMonospacePreElement(el) || isMonospaceDescendant(el)) {
965
+ return $next();
966
+ }
967
+ const prev = el.previousElementSibling;
968
+ if (prev && isMonospacePreElement(prev)) {
969
+ // An earlier sibling's walk already absorbed `el` into its run.
970
+ return [];
971
+ }
972
+ const lines = [];
973
+ let cur = el;
974
+ while (cur && isMonospacePreElement(cur)) {
975
+ lines.push(cur.tagName === 'BR' ? '' : cur.textContent || '');
976
+ cur = cur.nextElementSibling;
977
+ }
978
+ if (lines.length < 2) {
979
+ return $next();
980
+ }
981
+ return [$createCodeNode().splice(0, 0, lexical.$generateNodesFromRawText(lines.join('\n')))];
982
+ },
983
+ match: html.sel.tag('div', 'br'),
984
+ name: '@lexical/code/vscode-line-run'
985
+ });
986
+ const VscodeCodePasteOverlay = html.defineOverlayRules([VscodeWrapperRule, VscodeLineRunRule]);
987
+
988
+ /**
989
+ * VS Code → browser code-block pastes ship the block as either:
990
+ *
991
+ * - **Chrome**: one outer
992
+ * `<div style="font-family: …monospace…; white-space: pre">…</div>`
993
+ * wrapping per-line `<div>`s and `<br>`s.
994
+ * - **Safari**: a flat run of sibling
995
+ * `<div style="…monospace…; white-space: pre">…</div>` and
996
+ * `<br style="…monospace…; …">` elements with no wrapping
997
+ * monospace ancestor (the styles are duplicated onto every
998
+ * element).
999
+ *
1000
+ * The legacy `<div>` rule (and {@link DivRule}) produces one CodeNode
1001
+ * per `<div>` on Safari and concatenates inner divs without
1002
+ * separating `\n`s on Chrome. This preprocess scans once for the
1003
+ * structural signature and, only when it matches, pushes
1004
+ * {@link VscodeCodePasteOverlay} onto {@link ImportOverlays} so the
1005
+ * VS Code-specific rules participate in the walk. Pastes from other
1006
+ * sources pay only the detection cost.
1007
+ *
1008
+ * @experimental
1009
+ */
1010
+ const $installVscodeCodePasteOverlay = (dom, ctx, $next) => {
1011
+ const root = lexical.isDOMDocumentNode(dom) ? dom.body : dom;
1012
+ if (looksLikeVscodePaste(root)) {
1013
+ ctx.session.update(html.ImportOverlays, prev => [...prev, VscodeCodePasteOverlay]);
1014
+ }
1015
+ $next();
1016
+ };
1017
+
1018
+ /**
1019
+ * A `<div style="font-family: …monospace…">` (Google-Docs-style code
1020
+ * block) creates a CodeNode. Descendant elements inside a monospace
1021
+ * wrapper just unwrap so their text content flows into the surrounding
1022
+ * CodeNode.
1023
+ */
1024
+ const DivRule = html.defineImportRule({
1025
+ $import: (ctx, el, $next) => {
1026
+ if (isMonospaceElement(el)) {
1027
+ return [$createCodeNode().splice(0, 0, ctx.$importChildren(el))];
1028
+ }
1029
+ if (isMonospaceDescendant(el)) {
1030
+ // Unwrap so children flow into the enclosing CodeNode.
1031
+ return ctx.$importChildren(el);
1032
+ }
1033
+ return $next();
1034
+ },
1035
+ match: html.sel.tag('div'),
1036
+ name: '@lexical/code/div'
1037
+ });
1038
+
1039
+ /**
1040
+ * GitHub raw-file-view `<table class="js-file-line-container">` becomes
1041
+ * a CodeNode. Walking the table's children pushes an overlay (see
1042
+ * {@link GitHubCodeTableOverlayRules}) so `<tr>` / `<td>` inside this
1043
+ * subtree unwrap unconditionally — without paying the predicate cost
1044
+ * on every other `<tr>` / `<td>` paste elsewhere.
1045
+ */
1046
+ const GitHubCodeTableRule = html.defineImportRule({
1047
+ $import: (ctx, el) => [$createCodeNode().splice(0, 0, ctx.$importChildren(el, {
1048
+ rules: GitHubCodeTableOverlayRules
1049
+ }))],
1050
+ match: html.sel.tag('table').classAll('js-file-line-container'),
1051
+ name: '@lexical/code/github-code-table'
1052
+ });
1053
+
1054
+ /**
1055
+ * Stray `<td class="js-file-line">` (cell with the explicit GitHub code-
1056
+ * line class but no surrounding code-table wrapper) — unwrap so the
1057
+ * descendant text flows up into whatever context the cell is in. The
1058
+ * class is part of the selector itself, so no runtime guard.
1059
+ */
1060
+ const GitHubCodeCellByClassRule = html.defineImportRule({
1061
+ $import: (ctx, el) => ctx.$importChildren(el),
1062
+ match: html.sel.tag('td').classAll('js-file-line'),
1063
+ name: '@lexical/code/github-code-cell-by-class'
1064
+ });
1065
+
1066
+ /**
1067
+ * Import rules for {@link CodeNode}.
1068
+ *
1069
+ * Specific class-restricted rules (GitHub raw-file-view detectors) are
1070
+ * registered before the generic `<table>` / `<tr>` / `<td>` rules so
1071
+ * they win dispatch.
1072
+ *
1073
+ * @experimental
1074
+ */
1075
+ const CodeImportRules = [
1076
+ // Higher-priority (more-specific) rules first:
1077
+ GitHubCodeTableRule, GitHubCodeCellByClassRule, MultilineCodeRule, PreRule, DivRule];
1078
+
1079
+ /**
1080
+ * Bundles {@link CodeImportRules} (plus {@link CoreImportExtension}) into
1081
+ * a single dependency. The legacy {@link CodeNode.importDOM} continues to
1082
+ * work in parallel; depend on this extension to opt into the new
1083
+ * pipeline.
1084
+ *
1085
+ * @experimental
1086
+ */
1087
+ const CodeImportExtension = lexical.defineExtension({
1088
+ dependencies: [html.CoreImportExtension, CodeExtension, lexical.configExtension(html.DOMImportExtension, {
1089
+ preprocess: [$installVscodeCodePasteOverlay],
1090
+ rules: CodeImportRules
1091
+ })],
1092
+ name: '@lexical/code/Import'
1093
+ });
1094
+
739
1095
  function $isSelectionInCode(selection) {
740
1096
  if (!lexical.$isRangeSelection(selection)) {
741
1097
  return false;
@@ -1029,6 +1385,15 @@ function $handleMoveTo(type, event) {
1029
1385
  const focusLineNode = focusNode;
1030
1386
  const direction = $getCodeLineDirection(focusLineNode);
1031
1387
  const moveToStart = direction === 'rtl' ? !isMoveToStart : isMoveToStart;
1388
+
1389
+ // Shift variant: let the non-shift branches resolve the target via
1390
+ // framework helpers (`selectNext` / `selectStart` / `setTextNodeRange` /
1391
+ // `node.select`), then restore the original anchor so we end up with an
1392
+ // extended selection rather than a collapsed caret. This keeps point
1393
+ // shapes (text vs. element) consistent between shift and non-shift.
1394
+ const originalAnchorKey = anchor.key;
1395
+ const originalAnchorOffset = anchor.offset;
1396
+ const originalAnchorType = anchor.type;
1032
1397
  if (moveToStart) {
1033
1398
  const start = $getStartOfCodeInLine(focusLineNode, focus.offset);
1034
1399
  if (start !== null) {
@@ -1048,6 +1413,9 @@ function $handleMoveTo(type, event) {
1048
1413
  const node = $getEndOfCodeInLine(focusLineNode);
1049
1414
  node.select();
1050
1415
  }
1416
+ if (event.shiftKey) {
1417
+ selection.anchor.set(originalAnchorKey, originalAnchorOffset, originalAnchorType);
1418
+ }
1051
1419
  event.preventDefault();
1052
1420
  event.stopPropagation();
1053
1421
  return true;
@@ -1162,8 +1530,11 @@ exports.$getStartOfCodeInLine = $getStartOfCodeInLine;
1162
1530
  exports.$isCodeHighlightNode = $isCodeHighlightNode;
1163
1531
  exports.$isCodeNode = $isCodeNode;
1164
1532
  exports.$outdentLeadingSpaces = $outdentLeadingSpaces;
1533
+ exports.$plainifyCodeContent = $plainifyCodeContent;
1165
1534
  exports.CodeExtension = CodeExtension;
1166
1535
  exports.CodeHighlightNode = CodeHighlightNode;
1536
+ exports.CodeImportExtension = CodeImportExtension;
1537
+ exports.CodeImportRules = CodeImportRules;
1167
1538
  exports.CodeIndentExtension = CodeIndentExtension;
1168
1539
  exports.CodeNode = CodeNode;
1169
1540
  exports.DEFAULT_CODE_LANGUAGE = DEFAULT_CODE_LANGUAGE;