@lexical/code-core 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.
@@ -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,24 @@ 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
+ lexical.tokenizeRawText(text, {
208
+ linebreak: () => out.push(lexical.$createLineBreakNode()),
209
+ tab: () => out.push(lexical.$createTabNode()),
210
+ text: part => out.push($createCodeHighlightNode(part))
211
+ });
212
+ return out;
213
+ }
214
+
195
215
  /**
196
216
  * Strip up to `tabSize` leading spaces from a {@link CodeHighlightNode} that
197
217
  * starts a code line, to support outdenting space-indented code lines (e.g.
@@ -238,6 +258,7 @@ function $outdentLeadingSpaces(node, tabSize, selection) {
238
258
  */
239
259
 
240
260
  const DEFAULT_CODE_LANGUAGE = 'javascript';
261
+ /** @internal Configurable through the extensions. */
241
262
  const getDefaultCodeLanguage = () => DEFAULT_CODE_LANGUAGE;
242
263
  function hasChildDOMNodeTag(node, tagName) {
243
264
  for (const child of node.childNodes) {
@@ -250,7 +271,7 @@ function hasChildDOMNodeTag(node, tagName) {
250
271
  }
251
272
  return false;
252
273
  }
253
- const LANGUAGE_DATA_ATTRIBUTE = 'data-language';
274
+ const LANGUAGE_DATA_ATTRIBUTE$1 = 'data-language';
254
275
  const HIGHLIGHT_LANGUAGE_DATA_ATTRIBUTE = 'data-highlight-language';
255
276
  const THEME_DATA_ATTRIBUTE = 'data-theme';
256
277
  const noExtensionDeprecation = warnOnlyOnce('Using CodeNode without CodeExtension is deprecated');
@@ -289,7 +310,7 @@ class CodeNode extends lexical.ElementNode {
289
310
  element.setAttribute('spellcheck', 'false');
290
311
  const language = this.getLanguage();
291
312
  if (language) {
292
- element.setAttribute(LANGUAGE_DATA_ATTRIBUTE, language);
313
+ element.setAttribute(LANGUAGE_DATA_ATTRIBUTE$1, language);
293
314
  if (this.getIsSyntaxHighlightSupported()) {
294
315
  element.setAttribute(HIGHLIGHT_LANGUAGE_DATA_ATTRIBUTE, language);
295
316
  }
@@ -309,10 +330,10 @@ class CodeNode extends lexical.ElementNode {
309
330
  const prevLanguage = prevNode.__language;
310
331
  if (language) {
311
332
  if (language !== prevLanguage) {
312
- dom.setAttribute(LANGUAGE_DATA_ATTRIBUTE, language);
333
+ dom.setAttribute(LANGUAGE_DATA_ATTRIBUTE$1, language);
313
334
  }
314
335
  } else if (prevLanguage) {
315
- dom.removeAttribute(LANGUAGE_DATA_ATTRIBUTE);
336
+ dom.removeAttribute(LANGUAGE_DATA_ATTRIBUTE$1);
316
337
  }
317
338
  const isSyntaxHighlightSupported = this.__isSyntaxHighlightSupported;
318
339
  const prevIsSyntaxHighlightSupported = prevNode.__isSyntaxHighlightSupported;
@@ -349,7 +370,7 @@ class CodeNode extends lexical.ElementNode {
349
370
  element.setAttribute('spellcheck', 'false');
350
371
  const language = this.getLanguage();
351
372
  if (language) {
352
- element.setAttribute(LANGUAGE_DATA_ATTRIBUTE, language);
373
+ element.setAttribute(LANGUAGE_DATA_ATTRIBUTE$1, language);
353
374
  if (this.getIsSyntaxHighlightSupported()) {
354
375
  element.setAttribute(HIGHLIGHT_LANGUAGE_DATA_ATTRIBUTE, language);
355
376
  }
@@ -548,7 +569,7 @@ function $isCodeNode(node) {
548
569
  return node instanceof CodeNode;
549
570
  }
550
571
  function $convertPreElement(domNode) {
551
- const language = domNode.getAttribute(LANGUAGE_DATA_ATTRIBUTE);
572
+ const language = domNode.getAttribute(LANGUAGE_DATA_ATTRIBUTE$1);
552
573
  return {
553
574
  node: $createCodeNode(language)
554
575
  };
@@ -736,6 +757,331 @@ const CodeExtension = lexical.defineExtension({
736
757
  }
737
758
  });
738
759
 
760
+ /**
761
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
762
+ *
763
+ * This source code is licensed under the MIT license found in the
764
+ * LICENSE file in the root directory of this source tree.
765
+ *
766
+ */
767
+
768
+ const LANGUAGE_DATA_ATTRIBUTE = 'data-language';
769
+
770
+ /**
771
+ * True for elements whose `font-family` mentions `monospace` — the
772
+ * heuristic the legacy `<div>` rule uses to spot copy-pasted code blocks
773
+ * (e.g. Google Docs serializes a code block as a styled `<div>`).
774
+ */
775
+ function isMonospaceElement(el) {
776
+ return el.style.fontFamily.match('monospace') !== null;
777
+ }
778
+ function isMonospaceDescendant(node) {
779
+ let parent = node.parentElement;
780
+ while (parent !== null) {
781
+ if (isMonospaceElement(parent)) {
782
+ return true;
783
+ }
784
+ parent = parent.parentElement;
785
+ }
786
+ return false;
787
+ }
788
+
789
+ /**
790
+ * Overlay rules active only while {@link GitHubCodeTableRule} is
791
+ * processing its children. Inside the code-table subtree, every `<tr>`
792
+ * and `<td>` unwraps unconditionally — they never become table-row /
793
+ * table-cell nodes (even when `@lexical/table` registers its rules for
794
+ * those tags). Outside the subtree, this overlay isn't installed, so
795
+ * the cost of these rules is never paid against unrelated `<tr>` /
796
+ * `<td>` pastes.
797
+ */
798
+ const GitHubCodeTableOverlayRules = html.defineOverlayRules([html.defineImportRule({
799
+ $import: (ctx, el) => ctx.$importChildren(el),
800
+ match: html.sel.tag('tr', 'td'),
801
+ name: '@lexical/code/github-code-table/unwrap'
802
+ })]);
803
+ const PreRule = html.defineImportRule({
804
+ $import: (ctx, el) => [$createCodeNode(el.getAttribute(LANGUAGE_DATA_ATTRIBUTE)).splice(0, 0, ctx.$importChildren(el))],
805
+ match: html.sel.tag('pre'),
806
+ name: '@lexical/code/pre'
807
+ });
808
+
809
+ /**
810
+ * Multi-line `<code>` (containing newlines or `<br>`) is treated as a
811
+ * block code element — mirrors the legacy behavior. Single-line `<code>`
812
+ * defers to the inline-format rule from `CoreImportExtension` so it
813
+ * becomes a TextNode with IS_CODE.
814
+ */
815
+ const MultilineCodeRule = html.defineImportRule({
816
+ $import: (ctx, el, $next) => {
817
+ const text = el.textContent || '';
818
+ const isMultiLine = /\r?\n/.test(text) || el.querySelector('br') !== null;
819
+ if (!isMultiLine) {
820
+ return $next();
821
+ }
822
+ return [$createCodeNode(el.getAttribute(LANGUAGE_DATA_ATTRIBUTE)).splice(0, 0, ctx.$importChildren(el))];
823
+ },
824
+ match: html.sel.tag('code'),
825
+ name: '@lexical/code/code-multiline'
826
+ });
827
+
828
+ /**
829
+ * True for elements carrying BOTH `font-family: …monospace…` and
830
+ * `white-space: pre*` inline — the shape VS Code uses for every line
831
+ * of a copied code block (on every per-line `<div>` on Safari, on
832
+ * the single outer wrapper on Chrome).
833
+ */
834
+ function isMonospacePreElement(el) {
835
+ if (!lexical.isHTMLElement(el)) {
836
+ return false;
837
+ }
838
+ const ff = el.style.fontFamily;
839
+ const ws = el.style.whiteSpace;
840
+ return typeof ff === 'string' && /monospace/i.test(ff) && typeof ws === 'string' && ws.startsWith('pre');
841
+ }
842
+
843
+ /**
844
+ * Split a monospace-pre wrapper element into logical code lines:
845
+ * `<div>` children contribute their text content as one line,
846
+ * `<br>` children contribute an empty line, inline children (spans
847
+ * and bare text) accumulate into the current line until the next
848
+ * block child.
849
+ *
850
+ * Returns `null` if `el` has no block children (i.e. it's a leaf
851
+ * line, not a wrapper) so the caller can leave it to the
852
+ * sibling-run pass.
853
+ */
854
+ function splitMonospaceWrapperLines(el) {
855
+ let hasBlockChild = false;
856
+ const lines = [];
857
+ let acc = '';
858
+ let hasAcc = false;
859
+ const flush = () => {
860
+ if (hasAcc) {
861
+ lines.push(acc);
862
+ acc = '';
863
+ hasAcc = false;
864
+ }
865
+ };
866
+ for (const child of Array.from(el.childNodes)) {
867
+ if (lexical.isHTMLElement(child)) {
868
+ if (child.tagName === 'DIV') {
869
+ flush();
870
+ lines.push(child.textContent || '');
871
+ hasBlockChild = true;
872
+ } else if (child.tagName === 'BR') {
873
+ flush();
874
+ lines.push('');
875
+ hasBlockChild = true;
876
+ } else {
877
+ acc += child.textContent || '';
878
+ hasAcc = true;
879
+ }
880
+ } else if (lexical.isDOMTextNode(child)) {
881
+ const t = child.textContent || '';
882
+ if (t.length > 0) {
883
+ acc += t;
884
+ hasAcc = true;
885
+ }
886
+ }
887
+ }
888
+ flush();
889
+ return hasBlockChild ? lines : null;
890
+ }
891
+
892
+ /**
893
+ * Returns `true` if `root` contains the structural signature of a
894
+ * VS Code code-block paste:
895
+ *
896
+ * - a monospace+pre `<div>` wrapper with at least one block (`<div>` /
897
+ * `<br>`) child — the Chrome shape, or
898
+ * - two or more consecutive monospace+pre siblings — the Safari shape.
899
+ *
900
+ * Walked once in preprocess; the matching overlay is only installed
901
+ * when this returns `true` so an unrelated paste doesn't pay for the
902
+ * detection or rule cost.
903
+ */
904
+ function looksLikeVscodePaste(root) {
905
+ for (const child of Array.from(root.children)) {
906
+ if (lexical.isHTMLElement(child) && isMonospacePreElement(child)) {
907
+ const lines = splitMonospaceWrapperLines(child);
908
+ if (lines !== null) {
909
+ return true;
910
+ }
911
+ const next = child.nextElementSibling;
912
+ if (next && isMonospacePreElement(next)) {
913
+ return true;
914
+ }
915
+ continue;
916
+ }
917
+ if (looksLikeVscodePaste(child)) {
918
+ return true;
919
+ }
920
+ }
921
+ return false;
922
+ }
923
+
924
+ /**
925
+ * Match a monospace+pre `<div>` whose direct children include block
926
+ * (`<div>` / `<br>`) elements — the Chrome shape, one outer wrapper
927
+ * around per-line `<div>`s and `<br>`s. Emits a single CodeNode whose
928
+ * text is the wrapper's lines joined by `\n`.
929
+ */
930
+ const VscodeWrapperRule = html.defineImportRule({
931
+ $import: (_ctx, el, $next) => {
932
+ if (!isMonospacePreElement(el) || isMonospaceDescendant(el)) {
933
+ return $next();
934
+ }
935
+ const lines = splitMonospaceWrapperLines(el);
936
+ if (lines === null || lines.length === 0) {
937
+ return $next();
938
+ }
939
+ return [$createCodeNode().splice(0, 0, lexical.$generateNodesFromRawText(lines.join('\n')))];
940
+ },
941
+ match: html.sel.tag('div'),
942
+ name: '@lexical/code/vscode-wrapper'
943
+ });
944
+
945
+ /**
946
+ * Match the first of a run of consecutive monospace+pre `<div>` /
947
+ * `<br>` siblings (the Safari shape) and emit one CodeNode for the
948
+ * whole run. When the framework's per-child dispatch lands on a
949
+ * subsequent sibling in the same run, the prev-sibling check below
950
+ * returns `[]` so the run is only emitted once.
951
+ */
952
+ const VscodeLineRunRule = html.defineImportRule({
953
+ $import: (_ctx, el, $next) => {
954
+ if (!isMonospacePreElement(el) || isMonospaceDescendant(el)) {
955
+ return $next();
956
+ }
957
+ const prev = el.previousElementSibling;
958
+ if (prev && isMonospacePreElement(prev)) {
959
+ // An earlier sibling's walk already absorbed `el` into its run.
960
+ return [];
961
+ }
962
+ const lines = [];
963
+ let cur = el;
964
+ while (cur && isMonospacePreElement(cur)) {
965
+ lines.push(cur.tagName === 'BR' ? '' : cur.textContent || '');
966
+ cur = cur.nextElementSibling;
967
+ }
968
+ if (lines.length < 2) {
969
+ return $next();
970
+ }
971
+ return [$createCodeNode().splice(0, 0, lexical.$generateNodesFromRawText(lines.join('\n')))];
972
+ },
973
+ match: html.sel.tag('div', 'br'),
974
+ name: '@lexical/code/vscode-line-run'
975
+ });
976
+ const VscodeCodePasteOverlay = html.defineOverlayRules([VscodeWrapperRule, VscodeLineRunRule]);
977
+
978
+ /**
979
+ * VS Code → browser code-block pastes ship the block as either:
980
+ *
981
+ * - **Chrome**: one outer
982
+ * `<div style="font-family: …monospace…; white-space: pre">…</div>`
983
+ * wrapping per-line `<div>`s and `<br>`s.
984
+ * - **Safari**: a flat run of sibling
985
+ * `<div style="…monospace…; white-space: pre">…</div>` and
986
+ * `<br style="…monospace…; …">` elements with no wrapping
987
+ * monospace ancestor (the styles are duplicated onto every
988
+ * element).
989
+ *
990
+ * The legacy `<div>` rule (and {@link DivRule}) produces one CodeNode
991
+ * per `<div>` on Safari and concatenates inner divs without
992
+ * separating `\n`s on Chrome. This preprocess scans once for the
993
+ * structural signature and, only when it matches, pushes
994
+ * {@link VscodeCodePasteOverlay} onto {@link ImportOverlays} so the
995
+ * VS Code-specific rules participate in the walk. Pastes from other
996
+ * sources pay only the detection cost.
997
+ *
998
+ * @experimental
999
+ */
1000
+ const $installVscodeCodePasteOverlay = (dom, ctx, $next) => {
1001
+ const root = lexical.isDOMDocumentNode(dom) ? dom.body : dom;
1002
+ if (looksLikeVscodePaste(root)) {
1003
+ ctx.session.update(html.ImportOverlays, prev => [...prev, VscodeCodePasteOverlay]);
1004
+ }
1005
+ $next();
1006
+ };
1007
+
1008
+ /**
1009
+ * A `<div style="font-family: …monospace…">` (Google-Docs-style code
1010
+ * block) creates a CodeNode. Descendant elements inside a monospace
1011
+ * wrapper just unwrap so their text content flows into the surrounding
1012
+ * CodeNode.
1013
+ */
1014
+ const DivRule = html.defineImportRule({
1015
+ $import: (ctx, el, $next) => {
1016
+ if (isMonospaceElement(el)) {
1017
+ return [$createCodeNode().splice(0, 0, ctx.$importChildren(el))];
1018
+ }
1019
+ if (isMonospaceDescendant(el)) {
1020
+ // Unwrap so children flow into the enclosing CodeNode.
1021
+ return ctx.$importChildren(el);
1022
+ }
1023
+ return $next();
1024
+ },
1025
+ match: html.sel.tag('div'),
1026
+ name: '@lexical/code/div'
1027
+ });
1028
+
1029
+ /**
1030
+ * GitHub raw-file-view `<table class="js-file-line-container">` becomes
1031
+ * a CodeNode. Walking the table's children pushes an overlay (see
1032
+ * {@link GitHubCodeTableOverlayRules}) so `<tr>` / `<td>` inside this
1033
+ * subtree unwrap unconditionally — without paying the predicate cost
1034
+ * on every other `<tr>` / `<td>` paste elsewhere.
1035
+ */
1036
+ const GitHubCodeTableRule = html.defineImportRule({
1037
+ $import: (ctx, el) => [$createCodeNode().splice(0, 0, ctx.$importChildren(el, {
1038
+ rules: GitHubCodeTableOverlayRules
1039
+ }))],
1040
+ match: html.sel.tag('table').classAll('js-file-line-container'),
1041
+ name: '@lexical/code/github-code-table'
1042
+ });
1043
+
1044
+ /**
1045
+ * Stray `<td class="js-file-line">` (cell with the explicit GitHub code-
1046
+ * line class but no surrounding code-table wrapper) — unwrap so the
1047
+ * descendant text flows up into whatever context the cell is in. The
1048
+ * class is part of the selector itself, so no runtime guard.
1049
+ */
1050
+ const GitHubCodeCellByClassRule = html.defineImportRule({
1051
+ $import: (ctx, el) => ctx.$importChildren(el),
1052
+ match: html.sel.tag('td').classAll('js-file-line'),
1053
+ name: '@lexical/code/github-code-cell-by-class'
1054
+ });
1055
+
1056
+ /**
1057
+ * Import rules for {@link CodeNode}.
1058
+ *
1059
+ * Specific class-restricted rules (GitHub raw-file-view detectors) are
1060
+ * registered before the generic `<table>` / `<tr>` / `<td>` rules so
1061
+ * they win dispatch.
1062
+ *
1063
+ * @experimental
1064
+ */
1065
+ const CodeImportRules = [
1066
+ // Higher-priority (more-specific) rules first:
1067
+ GitHubCodeTableRule, GitHubCodeCellByClassRule, MultilineCodeRule, PreRule, DivRule];
1068
+
1069
+ /**
1070
+ * Bundles {@link CodeImportRules} (plus {@link CoreImportExtension}) into
1071
+ * a single dependency. The legacy {@link CodeNode.importDOM} continues to
1072
+ * work in parallel; depend on this extension to opt into the new
1073
+ * pipeline.
1074
+ *
1075
+ * @experimental
1076
+ */
1077
+ const CodeImportExtension = lexical.defineExtension({
1078
+ dependencies: [html.CoreImportExtension, CodeExtension, lexical.configExtension(html.DOMImportExtension, {
1079
+ preprocess: [$installVscodeCodePasteOverlay],
1080
+ rules: CodeImportRules
1081
+ })],
1082
+ name: '@lexical/code/Import'
1083
+ });
1084
+
739
1085
  function $isSelectionInCode(selection) {
740
1086
  if (!lexical.$isRangeSelection(selection)) {
741
1087
  return false;
@@ -1029,6 +1375,15 @@ function $handleMoveTo(type, event) {
1029
1375
  const focusLineNode = focusNode;
1030
1376
  const direction = $getCodeLineDirection(focusLineNode);
1031
1377
  const moveToStart = direction === 'rtl' ? !isMoveToStart : isMoveToStart;
1378
+
1379
+ // Shift variant: let the non-shift branches resolve the target via
1380
+ // framework helpers (`selectNext` / `selectStart` / `setTextNodeRange` /
1381
+ // `node.select`), then restore the original anchor so we end up with an
1382
+ // extended selection rather than a collapsed caret. This keeps point
1383
+ // shapes (text vs. element) consistent between shift and non-shift.
1384
+ const originalAnchorKey = anchor.key;
1385
+ const originalAnchorOffset = anchor.offset;
1386
+ const originalAnchorType = anchor.type;
1032
1387
  if (moveToStart) {
1033
1388
  const start = $getStartOfCodeInLine(focusLineNode, focus.offset);
1034
1389
  if (start !== null) {
@@ -1048,6 +1403,9 @@ function $handleMoveTo(type, event) {
1048
1403
  const node = $getEndOfCodeInLine(focusLineNode);
1049
1404
  node.select();
1050
1405
  }
1406
+ if (event.shiftKey) {
1407
+ selection.anchor.set(originalAnchorKey, originalAnchorOffset, originalAnchorType);
1408
+ }
1051
1409
  event.preventDefault();
1052
1410
  event.stopPropagation();
1053
1411
  return true;
@@ -1162,8 +1520,11 @@ exports.$getStartOfCodeInLine = $getStartOfCodeInLine;
1162
1520
  exports.$isCodeHighlightNode = $isCodeHighlightNode;
1163
1521
  exports.$isCodeNode = $isCodeNode;
1164
1522
  exports.$outdentLeadingSpaces = $outdentLeadingSpaces;
1523
+ exports.$plainifyCodeContent = $plainifyCodeContent;
1165
1524
  exports.CodeExtension = CodeExtension;
1166
1525
  exports.CodeHighlightNode = CodeHighlightNode;
1526
+ exports.CodeImportExtension = CodeImportExtension;
1527
+ exports.CodeImportRules = CodeImportRules;
1167
1528
  exports.CodeIndentExtension = CodeIndentExtension;
1168
1529
  exports.CodeNode = CodeNode;
1169
1530
  exports.DEFAULT_CODE_LANGUAGE = DEFAULT_CODE_LANGUAGE;