@lexical/code-core 0.44.1-nightly.20260430.0 → 0.44.1-nightly.20260504.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,54 @@
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 { LexicalEditor } from 'lexical';
9
+ /**
10
+ * @internal
11
+ * Register the keyboard and command handlers that drive code-block
12
+ * indentation: Tab / Shift+Tab, INDENT/OUTDENT_CONTENT_COMMAND,
13
+ * INSERT_TAB_COMMAND, alt+arrow line shifting, and Home/End movement.
14
+ *
15
+ * Both `@lexical/code-shiki` and `@lexical/code-prism` use this via
16
+ * {@link CodeIndentExtension}; callers using `registerCodeHighlighting`
17
+ * will implicitly call this with tabSize of `undefined`.
18
+ *
19
+ * @param editor The editor to register on.
20
+ * @param tabSize When set, OUTDENT_CONTENT_COMMAND (Shift+Tab) also strips
21
+ * up to that many leading spaces from a code line. See
22
+ * {@link $outdentLeadingSpaces}.
23
+ */
24
+ export declare function registerCodeIndentation(editor: LexicalEditor, tabSize?: number): () => void;
25
+ export interface CodeIndentConfig {
26
+ /**
27
+ * When true, the indent commands are not registered on the editor.
28
+ * This signal can be flipped at runtime to enable or disable indent
29
+ * handling without rebuilding the editor.
30
+ */
31
+ disabled: boolean;
32
+ /**
33
+ * When set, treats that many leading spaces on a code line as one indent
34
+ * level for the OUTDENT_CONTENT_COMMAND (Shift+Tab). See
35
+ * {@link registerCodeIndentation}. When undefined (the default), only
36
+ * TabNode removal is supported on outdent.
37
+ *
38
+ * Tab and INSERT_TAB_COMMAND continue to insert a TabNode regardless of
39
+ * this option.
40
+ */
41
+ tabSize: number | undefined;
42
+ }
43
+ /**
44
+ * Adds keyboard-driven indentation to code blocks (Tab / Shift+Tab,
45
+ * alt+arrow line shifts, Home/End within a line). Both
46
+ * {@link "@lexical/code-shiki".CodeShikiExtension} and
47
+ * {@link "@lexical/code-prism".CodePrismExtension} declare this as a
48
+ * dependency, so it is activated automatically alongside either
49
+ * highlighter.
50
+ *
51
+ * Code blocks without syntax highlighting can use this extension on its
52
+ * own.
53
+ */
54
+ export declare const CodeIndentExtension: import("lexical").LexicalExtension<CodeIndentConfig, "@lexical/code-indent", import("@lexical/extension").NamedSignalsOutput<CodeIndentConfig>, unknown>;
@@ -6,7 +6,7 @@
6
6
  *
7
7
  */
8
8
  import type { CodeHighlightNode } from './CodeHighlightNode';
9
- import type { LineBreakNode, TabNode } from 'lexical';
9
+ import type { 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,3 +23,18 @@ 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
+ * Strip up to `tabSize` leading spaces from a {@link CodeHighlightNode} that
28
+ * starts a code line, to support outdenting space-indented code lines (e.g.
29
+ * code formatted with prettier). Returns true if any spaces were stripped.
30
+ *
31
+ * Best-effort: a line with fewer than `tabSize` leading spaces has all of
32
+ * them stripped, matching VS Code / IntelliJ behavior.
33
+ *
34
+ * Selection is preserved relative to line content. Anchor/focus offsets
35
+ * pointing into `node` shift left by the number of stripped characters
36
+ * (clamped to 0). The underlying TextNode mutation does not adjust
37
+ * selection offsets that already point into the old text, so we patch
38
+ * them up explicitly.
39
+ */
40
+ export declare function $outdentLeadingSpaces(node: CodeHighlightNode, tabSize: number, selection: RangeSelection): boolean;
@@ -192,6 +192,43 @@ function $getEndOfCodeInLine(anchor) {
192
192
  return lastNode;
193
193
  }
194
194
 
195
+ /**
196
+ * Strip up to `tabSize` leading spaces from a {@link CodeHighlightNode} that
197
+ * starts a code line, to support outdenting space-indented code lines (e.g.
198
+ * code formatted with prettier). Returns true if any spaces were stripped.
199
+ *
200
+ * Best-effort: a line with fewer than `tabSize` leading spaces has all of
201
+ * them stripped, matching VS Code / IntelliJ behavior.
202
+ *
203
+ * Selection is preserved relative to line content. Anchor/focus offsets
204
+ * pointing into `node` shift left by the number of stripped characters
205
+ * (clamped to 0). The underlying TextNode mutation does not adjust
206
+ * selection offsets that already point into the old text, so we patch
207
+ * them up explicitly.
208
+ */
209
+ function $outdentLeadingSpaces(node, tabSize, selection) {
210
+ if (!Number.isInteger(tabSize) || tabSize <= 0) {
211
+ return false;
212
+ }
213
+ const text = node.getTextContent();
214
+ const leading = /^ +/.exec(text);
215
+ if (!leading) {
216
+ return false;
217
+ }
218
+ const stripCount = Math.min(tabSize, leading[0].length);
219
+ const lineKey = node.getKey();
220
+ const oldAnchorOffset = selection.anchor.key === lineKey && selection.anchor.type === 'text' ? selection.anchor.offset : null;
221
+ const oldFocusOffset = selection.focus.key === lineKey && selection.focus.type === 'text' ? selection.focus.offset : null;
222
+ node.spliceText(0, stripCount, '');
223
+ if (oldAnchorOffset !== null) {
224
+ selection.anchor.set(lineKey, Math.max(0, oldAnchorOffset - stripCount), 'text');
225
+ }
226
+ if (oldFocusOffset !== null) {
227
+ selection.focus.set(lineKey, Math.max(0, oldFocusOffset - stripCount), 'text');
228
+ }
229
+ return true;
230
+ }
231
+
195
232
  /**
196
233
  * Copyright (c) Meta Platforms, Inc. and affiliates.
197
234
  *
@@ -697,6 +734,422 @@ const CodeExtension = lexical.defineExtension({
697
734
  }
698
735
  });
699
736
 
737
+ function $isSelectionInCode(selection) {
738
+ if (!lexical.$isRangeSelection(selection)) {
739
+ return false;
740
+ }
741
+ const anchorNode = selection.anchor.getNode();
742
+ const maybeAnchorCodeNode = $isCodeNode(anchorNode) ? anchorNode : anchorNode.getParent();
743
+ const focusNode = selection.focus.getNode();
744
+ const maybeFocusCodeNode = $isCodeNode(focusNode) ? focusNode : focusNode.getParent();
745
+ return $isCodeNode(maybeAnchorCodeNode) && maybeAnchorCodeNode.is(maybeFocusCodeNode);
746
+ }
747
+
748
+ /**
749
+ * Returns an Array of code lines
750
+ * Take the sequence of LineBreakNode | TabNode | CodeHighlightNode forming
751
+ * the selection and split it by LineBreakNode.
752
+ * If the selection ends at the start of the last line, it is considered empty.
753
+ * Empty lines are discarded.
754
+ */
755
+ function $getCodeLines(selection) {
756
+ const nodes = selection.getNodes();
757
+ const lines = [];
758
+ if (nodes.length === 1 && $isCodeNode(nodes[0])) {
759
+ return lines;
760
+ }
761
+ let lastLine = [];
762
+ for (let i = 0; i < nodes.length; i++) {
763
+ const node = nodes[i];
764
+ if (!($isCodeHighlightNode(node) || lexical.$isTabNode(node) || lexical.$isLineBreakNode(node))) {
765
+ formatDevErrorMessage(`Expected selection to be inside CodeBlock and consisting of CodeHighlightNode, TabNode and LineBreakNode`);
766
+ }
767
+ if (lexical.$isLineBreakNode(node)) {
768
+ if (lastLine.length > 0) {
769
+ lines.push(lastLine);
770
+ lastLine = [];
771
+ }
772
+ } else {
773
+ lastLine.push(node);
774
+ }
775
+ }
776
+ if (lastLine.length > 0) {
777
+ const selectionEnd = selection.isBackward() ? selection.anchor : selection.focus;
778
+
779
+ // Discard the last line if the selection ends exactly at the
780
+ // start of the line (no real selection)
781
+ const lastPoint = lexical.$createPoint(lastLine[0].getKey(), 0, 'text');
782
+ if (!selectionEnd.is(lastPoint)) {
783
+ lines.push(lastLine);
784
+ }
785
+ }
786
+ return lines;
787
+ }
788
+ function $handleTab(shiftKey) {
789
+ const selection = lexical.$getSelection();
790
+ if (!lexical.$isRangeSelection(selection) || !$isSelectionInCode(selection)) {
791
+ return null;
792
+ }
793
+ const indentOrOutdent = !shiftKey ? lexical.INDENT_CONTENT_COMMAND : lexical.OUTDENT_CONTENT_COMMAND;
794
+ const tabOrOutdent = !shiftKey ? lexical.INSERT_TAB_COMMAND : lexical.OUTDENT_CONTENT_COMMAND;
795
+ const anchor = selection.anchor;
796
+ const focus = selection.focus;
797
+
798
+ // 1. early decision when there is no real selection
799
+ if (anchor.is(focus)) {
800
+ return tabOrOutdent;
801
+ }
802
+
803
+ // 2. If only empty lines or multiple non-empty lines are selected: indent/outdent
804
+ const codeLines = $getCodeLines(selection);
805
+ if (codeLines.length !== 1) {
806
+ return indentOrOutdent;
807
+ }
808
+ const codeLine = codeLines[0];
809
+ const codeLineLength = codeLine.length;
810
+ if (!(codeLineLength !== 0)) {
811
+ formatDevErrorMessage(`$getCodeLines only extracts non-empty lines`);
812
+ } // Take into account the direction of the selection
813
+ let selectionFirst;
814
+ let selectionLast;
815
+ if (selection.isBackward()) {
816
+ selectionFirst = focus;
817
+ selectionLast = anchor;
818
+ } else {
819
+ selectionFirst = anchor;
820
+ selectionLast = focus;
821
+ }
822
+
823
+ // find boundary elements of the line
824
+ // since codeLine only contains TabNode | CodeHighlightNode
825
+ // the result of these functions should is of Type TabNode | CodeHighlightNode
826
+ const firstOfLine = $getFirstCodeNodeOfLine(codeLine[0]);
827
+ const lastOfLine = $getLastCodeNodeOfLine(codeLine[0]);
828
+ const anchorOfLine = lexical.$createPoint(firstOfLine.getKey(), 0, 'text');
829
+ const focusOfLine = lexical.$createPoint(lastOfLine.getKey(), lastOfLine.getTextContentSize(), 'text');
830
+
831
+ // 3. multiline because selection started strictly before the line
832
+ if (selectionFirst.isBefore(anchorOfLine)) {
833
+ return indentOrOutdent;
834
+ }
835
+
836
+ // 4. multiline because the selection stops strictly after the line
837
+ if (focusOfLine.isBefore(selectionLast)) {
838
+ return indentOrOutdent;
839
+ }
840
+
841
+ // The selection if within the line.
842
+ // 4. If it does not touch both borders, it needs a tab
843
+ if (anchorOfLine.isBefore(selectionFirst) || selectionLast.isBefore(focusOfLine)) {
844
+ return tabOrOutdent;
845
+ }
846
+
847
+ // 5. Selection is matching a full line on non-empty code
848
+ return indentOrOutdent;
849
+ }
850
+ function $handleMultilineIndent(type, tabSize) {
851
+ const selection = lexical.$getSelection();
852
+ if (!lexical.$isRangeSelection(selection) || !$isSelectionInCode(selection)) {
853
+ return false;
854
+ }
855
+ const codeLines = $getCodeLines(selection);
856
+ const codeLinesLength = codeLines.length;
857
+
858
+ // Special Indent case
859
+ // Selection is collapsed at the beginning of a line
860
+ if (codeLinesLength === 0 && selection.isCollapsed()) {
861
+ if (type === lexical.INDENT_CONTENT_COMMAND) {
862
+ selection.insertNodes([lexical.$createTabNode()]);
863
+ }
864
+ return true;
865
+ }
866
+
867
+ // Special Indent case
868
+ // Selection is matching only one LineBreak
869
+ if (codeLinesLength === 0 && type === lexical.INDENT_CONTENT_COMMAND && selection.getTextContent() === '\n') {
870
+ const tabNode = lexical.$createTabNode();
871
+ const lineBreakNode = lexical.$createLineBreakNode();
872
+ const direction = selection.isBackward() ? 'previous' : 'next';
873
+ selection.insertNodes([tabNode, lineBreakNode]);
874
+ lexical.$setSelectionFromCaretRange(lexical.$getCaretRangeInDirection(lexical.$getCaretRange(lexical.$getTextPointCaret(tabNode, 'next', 0), lexical.$normalizeCaret(lexical.$getSiblingCaret(lineBreakNode, 'next'))), direction));
875
+ return true;
876
+ }
877
+
878
+ // Indent Non Empty Lines
879
+ for (let i = 0; i < codeLinesLength; i++) {
880
+ const line = codeLines[i];
881
+ // a line here is never empty
882
+ if (line.length > 0) {
883
+ let firstOfLine = line[0];
884
+
885
+ // make sure to consider the first node on the first line
886
+ // because the line might not be fully selected
887
+ if (i === 0) {
888
+ firstOfLine = $getFirstCodeNodeOfLine(firstOfLine);
889
+ }
890
+ if (type === lexical.INDENT_CONTENT_COMMAND) {
891
+ const tabNode = lexical.$createTabNode();
892
+ firstOfLine.insertBefore(tabNode);
893
+ // First real code line may need selection adjustment
894
+ // when firstOfLine is at the selection boundary
895
+ if (i === 0) {
896
+ const anchorKey = selection.isBackward() ? 'focus' : 'anchor';
897
+ const anchorLine = lexical.$createPoint(firstOfLine.getKey(), 0, 'text');
898
+ if (selection[anchorKey].is(anchorLine)) {
899
+ selection[anchorKey].set(tabNode.getKey(), 0, 'text');
900
+ }
901
+ }
902
+ } else if (lexical.$isTabNode(firstOfLine)) {
903
+ firstOfLine.remove();
904
+ } else if (tabSize !== undefined && $isCodeHighlightNode(firstOfLine)) {
905
+ // Outdent space-indented lines (e.g. code formatted with prettier).
906
+ $outdentLeadingSpaces(firstOfLine, tabSize, selection);
907
+ }
908
+ }
909
+ }
910
+ return true;
911
+ }
912
+ function $handleShiftLines(type, event) {
913
+ // We only care about the alt+arrow keys
914
+ const selection = lexical.$getSelection();
915
+ if (!lexical.$isRangeSelection(selection)) {
916
+ return false;
917
+ }
918
+
919
+ // I'm not quite sure why, but it seems like calling anchor.getNode() collapses the selection here
920
+ // So first, get the anchor and the focus, then get their nodes
921
+ const {
922
+ anchor,
923
+ focus
924
+ } = selection;
925
+ const anchorOffset = anchor.offset;
926
+ const focusOffset = focus.offset;
927
+ const anchorNode = anchor.getNode();
928
+ const focusNode = focus.getNode();
929
+ const arrowIsUp = type === lexical.KEY_ARROW_UP_COMMAND;
930
+
931
+ // Ensure the selection is within the codeblock
932
+ if (!$isSelectionInCode(selection) || !($isCodeHighlightNode(anchorNode) || lexical.$isTabNode(anchorNode)) || !($isCodeHighlightNode(focusNode) || lexical.$isTabNode(focusNode))) {
933
+ return false;
934
+ }
935
+ if (!event.altKey) {
936
+ // Handle moving selection out of the code block, given there are no
937
+ // siblings that can natively take the selection.
938
+ if (selection.isCollapsed()) {
939
+ const codeNode = anchorNode.getParentOrThrow();
940
+ if (arrowIsUp && anchorOffset === 0 && anchorNode.getPreviousSibling() === null) {
941
+ const codeNodeSibling = codeNode.getPreviousSibling();
942
+ if (codeNodeSibling === null) {
943
+ codeNode.selectPrevious();
944
+ event.preventDefault();
945
+ return true;
946
+ }
947
+ } else if (!arrowIsUp && anchorOffset === anchorNode.getTextContentSize() && anchorNode.getNextSibling() === null) {
948
+ const codeNodeSibling = codeNode.getNextSibling();
949
+ if (codeNodeSibling === null) {
950
+ codeNode.selectNext();
951
+ event.preventDefault();
952
+ return true;
953
+ }
954
+ }
955
+ }
956
+ return false;
957
+ }
958
+ let start;
959
+ let end;
960
+ if (anchorNode.isBefore(focusNode)) {
961
+ start = $getFirstCodeNodeOfLine(anchorNode);
962
+ end = $getLastCodeNodeOfLine(focusNode);
963
+ } else {
964
+ start = $getFirstCodeNodeOfLine(focusNode);
965
+ end = $getLastCodeNodeOfLine(anchorNode);
966
+ }
967
+ if (start == null || end == null) {
968
+ return false;
969
+ }
970
+ const range = start.getNodesBetween(end);
971
+ for (let i = 0; i < range.length; i++) {
972
+ const node = range[i];
973
+ if (!$isCodeHighlightNode(node) && !lexical.$isTabNode(node) && !lexical.$isLineBreakNode(node)) {
974
+ return false;
975
+ }
976
+ }
977
+
978
+ // After this point, we know the selection is within the codeblock. We may not be able to
979
+ // actually move the lines around, but we want to return true either way to prevent
980
+ // the event's default behavior
981
+ event.preventDefault();
982
+ event.stopPropagation(); // required to stop cursor movement under Firefox
983
+
984
+ const linebreak = arrowIsUp ? start.getPreviousSibling() : end.getNextSibling();
985
+ if (!lexical.$isLineBreakNode(linebreak)) {
986
+ return true;
987
+ }
988
+ const sibling = arrowIsUp ? linebreak.getPreviousSibling() : linebreak.getNextSibling();
989
+ if (sibling == null) {
990
+ return true;
991
+ }
992
+ const maybeInsertionPoint = $isCodeHighlightNode(sibling) || lexical.$isTabNode(sibling) || lexical.$isLineBreakNode(sibling) ? arrowIsUp ? $getFirstCodeNodeOfLine(sibling) : $getLastCodeNodeOfLine(sibling) : null;
993
+ let insertionPoint = maybeInsertionPoint != null ? maybeInsertionPoint : sibling;
994
+ linebreak.remove();
995
+ range.forEach(node => node.remove());
996
+ if (type === lexical.KEY_ARROW_UP_COMMAND) {
997
+ range.forEach(node => insertionPoint.insertBefore(node));
998
+ insertionPoint.insertBefore(linebreak);
999
+ } else {
1000
+ insertionPoint.insertAfter(linebreak);
1001
+ insertionPoint = linebreak;
1002
+ range.forEach(node => {
1003
+ insertionPoint.insertAfter(node);
1004
+ insertionPoint = node;
1005
+ });
1006
+ }
1007
+ selection.setTextNodeRange(anchorNode, anchorOffset, focusNode, focusOffset);
1008
+ return true;
1009
+ }
1010
+ function $handleMoveTo(type, event) {
1011
+ const selection = lexical.$getSelection();
1012
+ if (!lexical.$isRangeSelection(selection)) {
1013
+ return false;
1014
+ }
1015
+ const {
1016
+ anchor,
1017
+ focus
1018
+ } = selection;
1019
+ const anchorNode = anchor.getNode();
1020
+ const focusNode = focus.getNode();
1021
+ const isMoveToStart = type === lexical.MOVE_TO_START;
1022
+
1023
+ // Ensure the selection is within the codeblock
1024
+ if (!$isSelectionInCode(selection) || !($isCodeHighlightNode(anchorNode) || lexical.$isTabNode(anchorNode)) || !($isCodeHighlightNode(focusNode) || lexical.$isTabNode(focusNode))) {
1025
+ return false;
1026
+ }
1027
+ const focusLineNode = focusNode;
1028
+ const direction = $getCodeLineDirection(focusLineNode);
1029
+ const moveToStart = direction === 'rtl' ? !isMoveToStart : isMoveToStart;
1030
+ if (moveToStart) {
1031
+ const start = $getStartOfCodeInLine(focusLineNode, focus.offset);
1032
+ if (start !== null) {
1033
+ const {
1034
+ node,
1035
+ offset
1036
+ } = start;
1037
+ if (lexical.$isLineBreakNode(node)) {
1038
+ node.selectNext(0, 0);
1039
+ } else {
1040
+ selection.setTextNodeRange(node, offset, node, offset);
1041
+ }
1042
+ } else {
1043
+ focusLineNode.getParentOrThrow().selectStart();
1044
+ }
1045
+ } else {
1046
+ const node = $getEndOfCodeInLine(focusLineNode);
1047
+ node.select();
1048
+ }
1049
+ event.preventDefault();
1050
+ event.stopPropagation();
1051
+ return true;
1052
+ }
1053
+
1054
+ /**
1055
+ * @internal
1056
+ * Register the keyboard and command handlers that drive code-block
1057
+ * indentation: Tab / Shift+Tab, INDENT/OUTDENT_CONTENT_COMMAND,
1058
+ * INSERT_TAB_COMMAND, alt+arrow line shifting, and Home/End movement.
1059
+ *
1060
+ * Both `@lexical/code-shiki` and `@lexical/code-prism` use this via
1061
+ * {@link CodeIndentExtension}; callers using `registerCodeHighlighting`
1062
+ * will implicitly call this with tabSize of `undefined`.
1063
+ *
1064
+ * @param editor The editor to register on.
1065
+ * @param tabSize When set, OUTDENT_CONTENT_COMMAND (Shift+Tab) also strips
1066
+ * up to that many leading spaces from a code line. See
1067
+ * {@link $outdentLeadingSpaces}.
1068
+ */
1069
+ function registerCodeIndentation(editor, tabSize) {
1070
+ return lexical.mergeRegister(editor.registerCommand(lexical.KEY_TAB_COMMAND, event => {
1071
+ const command = $handleTab(event.shiftKey);
1072
+ if (command === null) {
1073
+ return false;
1074
+ }
1075
+ event.preventDefault();
1076
+ editor.dispatchCommand(command, undefined);
1077
+ return true;
1078
+ }, lexical.COMMAND_PRIORITY_LOW), editor.registerCommand(lexical.INSERT_TAB_COMMAND, () => {
1079
+ const selection = lexical.$getSelection();
1080
+ if (!$isSelectionInCode(selection)) {
1081
+ return false;
1082
+ }
1083
+ lexical.$insertNodes([lexical.$createTabNode()]);
1084
+ return true;
1085
+ }, lexical.COMMAND_PRIORITY_LOW), editor.registerCommand(lexical.INDENT_CONTENT_COMMAND, () => $handleMultilineIndent(lexical.INDENT_CONTENT_COMMAND), lexical.COMMAND_PRIORITY_LOW), editor.registerCommand(lexical.OUTDENT_CONTENT_COMMAND, () => $handleMultilineIndent(lexical.OUTDENT_CONTENT_COMMAND, tabSize), lexical.COMMAND_PRIORITY_LOW), editor.registerCommand(lexical.KEY_ARROW_UP_COMMAND, event => {
1086
+ const selection = lexical.$getSelection();
1087
+ if (!lexical.$isRangeSelection(selection)) {
1088
+ return false;
1089
+ }
1090
+ const {
1091
+ anchor
1092
+ } = selection;
1093
+ const anchorNode = anchor.getNode();
1094
+ if (!$isSelectionInCode(selection)) {
1095
+ return false;
1096
+ }
1097
+ // If at the start of a code block, prevent selection from moving out
1098
+ if (selection.isCollapsed() && anchor.offset === 0 && anchorNode.getPreviousSibling() === null && $isCodeNode(anchorNode.getParentOrThrow())) {
1099
+ event.preventDefault();
1100
+ return true;
1101
+ }
1102
+ return $handleShiftLines(lexical.KEY_ARROW_UP_COMMAND, event);
1103
+ }, lexical.COMMAND_PRIORITY_LOW), editor.registerCommand(lexical.KEY_ARROW_DOWN_COMMAND, event => {
1104
+ const selection = lexical.$getSelection();
1105
+ if (!lexical.$isRangeSelection(selection)) {
1106
+ return false;
1107
+ }
1108
+ const {
1109
+ anchor
1110
+ } = selection;
1111
+ const anchorNode = anchor.getNode();
1112
+ if (!$isSelectionInCode(selection)) {
1113
+ return false;
1114
+ }
1115
+ // If at the end of a code block, prevent selection from moving out
1116
+ if (selection.isCollapsed() && anchor.offset === anchorNode.getTextContentSize() && anchorNode.getNextSibling() === null && $isCodeNode(anchorNode.getParentOrThrow())) {
1117
+ event.preventDefault();
1118
+ return true;
1119
+ }
1120
+ return $handleShiftLines(lexical.KEY_ARROW_DOWN_COMMAND, event);
1121
+ }, lexical.COMMAND_PRIORITY_LOW), editor.registerCommand(lexical.MOVE_TO_START, event => $handleMoveTo(lexical.MOVE_TO_START, event), lexical.COMMAND_PRIORITY_LOW), editor.registerCommand(lexical.MOVE_TO_END, event => $handleMoveTo(lexical.MOVE_TO_END, event), lexical.COMMAND_PRIORITY_LOW));
1122
+ }
1123
+ /**
1124
+ * Adds keyboard-driven indentation to code blocks (Tab / Shift+Tab,
1125
+ * alt+arrow line shifts, Home/End within a line). Both
1126
+ * {@link "@lexical/code-shiki".CodeShikiExtension} and
1127
+ * {@link "@lexical/code-prism".CodePrismExtension} declare this as a
1128
+ * dependency, so it is activated automatically alongside either
1129
+ * highlighter.
1130
+ *
1131
+ * Code blocks without syntax highlighting can use this extension on its
1132
+ * own.
1133
+ */
1134
+ const CodeIndentExtension = lexical.defineExtension({
1135
+ build: (editor, config) => extension.namedSignals(config),
1136
+ config: lexical.safeCast({
1137
+ disabled: false,
1138
+ tabSize: undefined
1139
+ }),
1140
+ dependencies: [CodeExtension],
1141
+ name: '@lexical/code-indent',
1142
+ register: (editor, config, state) => {
1143
+ const stores = state.getOutput();
1144
+ return extension.effect(() => {
1145
+ if (stores.disabled.value) {
1146
+ return;
1147
+ }
1148
+ return registerCodeIndentation(editor, stores.tabSize.value);
1149
+ });
1150
+ }
1151
+ });
1152
+
700
1153
  exports.$createCodeHighlightNode = $createCodeHighlightNode;
701
1154
  exports.$createCodeNode = $createCodeNode;
702
1155
  exports.$getCodeLineDirection = $getCodeLineDirection;
@@ -706,8 +1159,11 @@ exports.$getLastCodeNodeOfLine = $getLastCodeNodeOfLine;
706
1159
  exports.$getStartOfCodeInLine = $getStartOfCodeInLine;
707
1160
  exports.$isCodeHighlightNode = $isCodeHighlightNode;
708
1161
  exports.$isCodeNode = $isCodeNode;
1162
+ exports.$outdentLeadingSpaces = $outdentLeadingSpaces;
709
1163
  exports.CodeExtension = CodeExtension;
710
1164
  exports.CodeHighlightNode = CodeHighlightNode;
1165
+ exports.CodeIndentExtension = CodeIndentExtension;
711
1166
  exports.CodeNode = CodeNode;
712
1167
  exports.DEFAULT_CODE_LANGUAGE = DEFAULT_CODE_LANGUAGE;
713
1168
  exports.getDefaultCodeLanguage = getDefaultCodeLanguage;
1169
+ exports.registerCodeIndentation = registerCodeIndentation;
@@ -6,8 +6,8 @@
6
6
  *
7
7
  */
8
8
 
9
- import { getTextDirection, $isElementNode, $isTabNode, $isLineBreakNode, $getSiblingCaret, $create, ElementNode, addClassNamesToElement, setDOMStyleFromCSS, $getEditor, $isTextNode, $createTabNode, $createLineBreakNode, $createParagraphNode, isHTMLElement, $applyNodeReplacement, TextNode, removeClassNamesFromElement, defineExtension, KEY_ENTER_COMMAND, $getSelection, $isRangeSelection, COMMAND_PRIORITY_LOW } from 'lexical';
10
- import { getPeerDependencyFromEditor } from '@lexical/extension';
9
+ import { getTextDirection, $isElementNode, $isTabNode, $isLineBreakNode, $getSiblingCaret, $create, ElementNode, addClassNamesToElement, setDOMStyleFromCSS, $getEditor, $isTextNode, $createTabNode, $createLineBreakNode, $createParagraphNode, isHTMLElement, $applyNodeReplacement, TextNode, removeClassNamesFromElement, defineExtension, KEY_ENTER_COMMAND, $getSelection, $isRangeSelection, COMMAND_PRIORITY_LOW, safeCast, mergeRegister, KEY_TAB_COMMAND, INSERT_TAB_COMMAND, $insertNodes, INDENT_CONTENT_COMMAND, OUTDENT_CONTENT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_DOWN_COMMAND, MOVE_TO_START, MOVE_TO_END, $createPoint, $setSelectionFromCaretRange, $getCaretRangeInDirection, $getCaretRange, $getTextPointCaret, $normalizeCaret } from 'lexical';
10
+ import { getPeerDependencyFromEditor, effect, namedSignals } from '@lexical/extension';
11
11
 
12
12
  /**
13
13
  * Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -190,6 +190,43 @@ function $getEndOfCodeInLine(anchor) {
190
190
  return lastNode;
191
191
  }
192
192
 
193
+ /**
194
+ * Strip up to `tabSize` leading spaces from a {@link CodeHighlightNode} that
195
+ * starts a code line, to support outdenting space-indented code lines (e.g.
196
+ * code formatted with prettier). Returns true if any spaces were stripped.
197
+ *
198
+ * Best-effort: a line with fewer than `tabSize` leading spaces has all of
199
+ * them stripped, matching VS Code / IntelliJ behavior.
200
+ *
201
+ * Selection is preserved relative to line content. Anchor/focus offsets
202
+ * pointing into `node` shift left by the number of stripped characters
203
+ * (clamped to 0). The underlying TextNode mutation does not adjust
204
+ * selection offsets that already point into the old text, so we patch
205
+ * them up explicitly.
206
+ */
207
+ function $outdentLeadingSpaces(node, tabSize, selection) {
208
+ if (!Number.isInteger(tabSize) || tabSize <= 0) {
209
+ return false;
210
+ }
211
+ const text = node.getTextContent();
212
+ const leading = /^ +/.exec(text);
213
+ if (!leading) {
214
+ return false;
215
+ }
216
+ const stripCount = Math.min(tabSize, leading[0].length);
217
+ const lineKey = node.getKey();
218
+ const oldAnchorOffset = selection.anchor.key === lineKey && selection.anchor.type === 'text' ? selection.anchor.offset : null;
219
+ const oldFocusOffset = selection.focus.key === lineKey && selection.focus.type === 'text' ? selection.focus.offset : null;
220
+ node.spliceText(0, stripCount, '');
221
+ if (oldAnchorOffset !== null) {
222
+ selection.anchor.set(lineKey, Math.max(0, oldAnchorOffset - stripCount), 'text');
223
+ }
224
+ if (oldFocusOffset !== null) {
225
+ selection.focus.set(lineKey, Math.max(0, oldFocusOffset - stripCount), 'text');
226
+ }
227
+ return true;
228
+ }
229
+
193
230
  /**
194
231
  * Copyright (c) Meta Platforms, Inc. and affiliates.
195
232
  *
@@ -695,4 +732,420 @@ const CodeExtension = defineExtension({
695
732
  }
696
733
  });
697
734
 
698
- export { $createCodeHighlightNode, $createCodeNode, $getCodeLineDirection, $getEndOfCodeInLine, $getFirstCodeNodeOfLine, $getLastCodeNodeOfLine, $getStartOfCodeInLine, $isCodeHighlightNode, $isCodeNode, CodeExtension, CodeHighlightNode, CodeNode, DEFAULT_CODE_LANGUAGE, getDefaultCodeLanguage };
735
+ function $isSelectionInCode(selection) {
736
+ if (!$isRangeSelection(selection)) {
737
+ return false;
738
+ }
739
+ const anchorNode = selection.anchor.getNode();
740
+ const maybeAnchorCodeNode = $isCodeNode(anchorNode) ? anchorNode : anchorNode.getParent();
741
+ const focusNode = selection.focus.getNode();
742
+ const maybeFocusCodeNode = $isCodeNode(focusNode) ? focusNode : focusNode.getParent();
743
+ return $isCodeNode(maybeAnchorCodeNode) && maybeAnchorCodeNode.is(maybeFocusCodeNode);
744
+ }
745
+
746
+ /**
747
+ * Returns an Array of code lines
748
+ * Take the sequence of LineBreakNode | TabNode | CodeHighlightNode forming
749
+ * the selection and split it by LineBreakNode.
750
+ * If the selection ends at the start of the last line, it is considered empty.
751
+ * Empty lines are discarded.
752
+ */
753
+ function $getCodeLines(selection) {
754
+ const nodes = selection.getNodes();
755
+ const lines = [];
756
+ if (nodes.length === 1 && $isCodeNode(nodes[0])) {
757
+ return lines;
758
+ }
759
+ let lastLine = [];
760
+ for (let i = 0; i < nodes.length; i++) {
761
+ const node = nodes[i];
762
+ if (!($isCodeHighlightNode(node) || $isTabNode(node) || $isLineBreakNode(node))) {
763
+ formatDevErrorMessage(`Expected selection to be inside CodeBlock and consisting of CodeHighlightNode, TabNode and LineBreakNode`);
764
+ }
765
+ if ($isLineBreakNode(node)) {
766
+ if (lastLine.length > 0) {
767
+ lines.push(lastLine);
768
+ lastLine = [];
769
+ }
770
+ } else {
771
+ lastLine.push(node);
772
+ }
773
+ }
774
+ if (lastLine.length > 0) {
775
+ const selectionEnd = selection.isBackward() ? selection.anchor : selection.focus;
776
+
777
+ // Discard the last line if the selection ends exactly at the
778
+ // start of the line (no real selection)
779
+ const lastPoint = $createPoint(lastLine[0].getKey(), 0, 'text');
780
+ if (!selectionEnd.is(lastPoint)) {
781
+ lines.push(lastLine);
782
+ }
783
+ }
784
+ return lines;
785
+ }
786
+ function $handleTab(shiftKey) {
787
+ const selection = $getSelection();
788
+ if (!$isRangeSelection(selection) || !$isSelectionInCode(selection)) {
789
+ return null;
790
+ }
791
+ const indentOrOutdent = !shiftKey ? INDENT_CONTENT_COMMAND : OUTDENT_CONTENT_COMMAND;
792
+ const tabOrOutdent = !shiftKey ? INSERT_TAB_COMMAND : OUTDENT_CONTENT_COMMAND;
793
+ const anchor = selection.anchor;
794
+ const focus = selection.focus;
795
+
796
+ // 1. early decision when there is no real selection
797
+ if (anchor.is(focus)) {
798
+ return tabOrOutdent;
799
+ }
800
+
801
+ // 2. If only empty lines or multiple non-empty lines are selected: indent/outdent
802
+ const codeLines = $getCodeLines(selection);
803
+ if (codeLines.length !== 1) {
804
+ return indentOrOutdent;
805
+ }
806
+ const codeLine = codeLines[0];
807
+ const codeLineLength = codeLine.length;
808
+ if (!(codeLineLength !== 0)) {
809
+ formatDevErrorMessage(`$getCodeLines only extracts non-empty lines`);
810
+ } // Take into account the direction of the selection
811
+ let selectionFirst;
812
+ let selectionLast;
813
+ if (selection.isBackward()) {
814
+ selectionFirst = focus;
815
+ selectionLast = anchor;
816
+ } else {
817
+ selectionFirst = anchor;
818
+ selectionLast = focus;
819
+ }
820
+
821
+ // find boundary elements of the line
822
+ // since codeLine only contains TabNode | CodeHighlightNode
823
+ // the result of these functions should is of Type TabNode | CodeHighlightNode
824
+ const firstOfLine = $getFirstCodeNodeOfLine(codeLine[0]);
825
+ const lastOfLine = $getLastCodeNodeOfLine(codeLine[0]);
826
+ const anchorOfLine = $createPoint(firstOfLine.getKey(), 0, 'text');
827
+ const focusOfLine = $createPoint(lastOfLine.getKey(), lastOfLine.getTextContentSize(), 'text');
828
+
829
+ // 3. multiline because selection started strictly before the line
830
+ if (selectionFirst.isBefore(anchorOfLine)) {
831
+ return indentOrOutdent;
832
+ }
833
+
834
+ // 4. multiline because the selection stops strictly after the line
835
+ if (focusOfLine.isBefore(selectionLast)) {
836
+ return indentOrOutdent;
837
+ }
838
+
839
+ // The selection if within the line.
840
+ // 4. If it does not touch both borders, it needs a tab
841
+ if (anchorOfLine.isBefore(selectionFirst) || selectionLast.isBefore(focusOfLine)) {
842
+ return tabOrOutdent;
843
+ }
844
+
845
+ // 5. Selection is matching a full line on non-empty code
846
+ return indentOrOutdent;
847
+ }
848
+ function $handleMultilineIndent(type, tabSize) {
849
+ const selection = $getSelection();
850
+ if (!$isRangeSelection(selection) || !$isSelectionInCode(selection)) {
851
+ return false;
852
+ }
853
+ const codeLines = $getCodeLines(selection);
854
+ const codeLinesLength = codeLines.length;
855
+
856
+ // Special Indent case
857
+ // Selection is collapsed at the beginning of a line
858
+ if (codeLinesLength === 0 && selection.isCollapsed()) {
859
+ if (type === INDENT_CONTENT_COMMAND) {
860
+ selection.insertNodes([$createTabNode()]);
861
+ }
862
+ return true;
863
+ }
864
+
865
+ // Special Indent case
866
+ // Selection is matching only one LineBreak
867
+ if (codeLinesLength === 0 && type === INDENT_CONTENT_COMMAND && selection.getTextContent() === '\n') {
868
+ const tabNode = $createTabNode();
869
+ const lineBreakNode = $createLineBreakNode();
870
+ const direction = selection.isBackward() ? 'previous' : 'next';
871
+ selection.insertNodes([tabNode, lineBreakNode]);
872
+ $setSelectionFromCaretRange($getCaretRangeInDirection($getCaretRange($getTextPointCaret(tabNode, 'next', 0), $normalizeCaret($getSiblingCaret(lineBreakNode, 'next'))), direction));
873
+ return true;
874
+ }
875
+
876
+ // Indent Non Empty Lines
877
+ for (let i = 0; i < codeLinesLength; i++) {
878
+ const line = codeLines[i];
879
+ // a line here is never empty
880
+ if (line.length > 0) {
881
+ let firstOfLine = line[0];
882
+
883
+ // make sure to consider the first node on the first line
884
+ // because the line might not be fully selected
885
+ if (i === 0) {
886
+ firstOfLine = $getFirstCodeNodeOfLine(firstOfLine);
887
+ }
888
+ if (type === INDENT_CONTENT_COMMAND) {
889
+ const tabNode = $createTabNode();
890
+ firstOfLine.insertBefore(tabNode);
891
+ // First real code line may need selection adjustment
892
+ // when firstOfLine is at the selection boundary
893
+ if (i === 0) {
894
+ const anchorKey = selection.isBackward() ? 'focus' : 'anchor';
895
+ const anchorLine = $createPoint(firstOfLine.getKey(), 0, 'text');
896
+ if (selection[anchorKey].is(anchorLine)) {
897
+ selection[anchorKey].set(tabNode.getKey(), 0, 'text');
898
+ }
899
+ }
900
+ } else if ($isTabNode(firstOfLine)) {
901
+ firstOfLine.remove();
902
+ } else if (tabSize !== undefined && $isCodeHighlightNode(firstOfLine)) {
903
+ // Outdent space-indented lines (e.g. code formatted with prettier).
904
+ $outdentLeadingSpaces(firstOfLine, tabSize, selection);
905
+ }
906
+ }
907
+ }
908
+ return true;
909
+ }
910
+ function $handleShiftLines(type, event) {
911
+ // We only care about the alt+arrow keys
912
+ const selection = $getSelection();
913
+ if (!$isRangeSelection(selection)) {
914
+ return false;
915
+ }
916
+
917
+ // I'm not quite sure why, but it seems like calling anchor.getNode() collapses the selection here
918
+ // So first, get the anchor and the focus, then get their nodes
919
+ const {
920
+ anchor,
921
+ focus
922
+ } = selection;
923
+ const anchorOffset = anchor.offset;
924
+ const focusOffset = focus.offset;
925
+ const anchorNode = anchor.getNode();
926
+ const focusNode = focus.getNode();
927
+ const arrowIsUp = type === KEY_ARROW_UP_COMMAND;
928
+
929
+ // Ensure the selection is within the codeblock
930
+ if (!$isSelectionInCode(selection) || !($isCodeHighlightNode(anchorNode) || $isTabNode(anchorNode)) || !($isCodeHighlightNode(focusNode) || $isTabNode(focusNode))) {
931
+ return false;
932
+ }
933
+ if (!event.altKey) {
934
+ // Handle moving selection out of the code block, given there are no
935
+ // siblings that can natively take the selection.
936
+ if (selection.isCollapsed()) {
937
+ const codeNode = anchorNode.getParentOrThrow();
938
+ if (arrowIsUp && anchorOffset === 0 && anchorNode.getPreviousSibling() === null) {
939
+ const codeNodeSibling = codeNode.getPreviousSibling();
940
+ if (codeNodeSibling === null) {
941
+ codeNode.selectPrevious();
942
+ event.preventDefault();
943
+ return true;
944
+ }
945
+ } else if (!arrowIsUp && anchorOffset === anchorNode.getTextContentSize() && anchorNode.getNextSibling() === null) {
946
+ const codeNodeSibling = codeNode.getNextSibling();
947
+ if (codeNodeSibling === null) {
948
+ codeNode.selectNext();
949
+ event.preventDefault();
950
+ return true;
951
+ }
952
+ }
953
+ }
954
+ return false;
955
+ }
956
+ let start;
957
+ let end;
958
+ if (anchorNode.isBefore(focusNode)) {
959
+ start = $getFirstCodeNodeOfLine(anchorNode);
960
+ end = $getLastCodeNodeOfLine(focusNode);
961
+ } else {
962
+ start = $getFirstCodeNodeOfLine(focusNode);
963
+ end = $getLastCodeNodeOfLine(anchorNode);
964
+ }
965
+ if (start == null || end == null) {
966
+ return false;
967
+ }
968
+ const range = start.getNodesBetween(end);
969
+ for (let i = 0; i < range.length; i++) {
970
+ const node = range[i];
971
+ if (!$isCodeHighlightNode(node) && !$isTabNode(node) && !$isLineBreakNode(node)) {
972
+ return false;
973
+ }
974
+ }
975
+
976
+ // After this point, we know the selection is within the codeblock. We may not be able to
977
+ // actually move the lines around, but we want to return true either way to prevent
978
+ // the event's default behavior
979
+ event.preventDefault();
980
+ event.stopPropagation(); // required to stop cursor movement under Firefox
981
+
982
+ const linebreak = arrowIsUp ? start.getPreviousSibling() : end.getNextSibling();
983
+ if (!$isLineBreakNode(linebreak)) {
984
+ return true;
985
+ }
986
+ const sibling = arrowIsUp ? linebreak.getPreviousSibling() : linebreak.getNextSibling();
987
+ if (sibling == null) {
988
+ return true;
989
+ }
990
+ const maybeInsertionPoint = $isCodeHighlightNode(sibling) || $isTabNode(sibling) || $isLineBreakNode(sibling) ? arrowIsUp ? $getFirstCodeNodeOfLine(sibling) : $getLastCodeNodeOfLine(sibling) : null;
991
+ let insertionPoint = maybeInsertionPoint != null ? maybeInsertionPoint : sibling;
992
+ linebreak.remove();
993
+ range.forEach(node => node.remove());
994
+ if (type === KEY_ARROW_UP_COMMAND) {
995
+ range.forEach(node => insertionPoint.insertBefore(node));
996
+ insertionPoint.insertBefore(linebreak);
997
+ } else {
998
+ insertionPoint.insertAfter(linebreak);
999
+ insertionPoint = linebreak;
1000
+ range.forEach(node => {
1001
+ insertionPoint.insertAfter(node);
1002
+ insertionPoint = node;
1003
+ });
1004
+ }
1005
+ selection.setTextNodeRange(anchorNode, anchorOffset, focusNode, focusOffset);
1006
+ return true;
1007
+ }
1008
+ function $handleMoveTo(type, event) {
1009
+ const selection = $getSelection();
1010
+ if (!$isRangeSelection(selection)) {
1011
+ return false;
1012
+ }
1013
+ const {
1014
+ anchor,
1015
+ focus
1016
+ } = selection;
1017
+ const anchorNode = anchor.getNode();
1018
+ const focusNode = focus.getNode();
1019
+ const isMoveToStart = type === MOVE_TO_START;
1020
+
1021
+ // Ensure the selection is within the codeblock
1022
+ if (!$isSelectionInCode(selection) || !($isCodeHighlightNode(anchorNode) || $isTabNode(anchorNode)) || !($isCodeHighlightNode(focusNode) || $isTabNode(focusNode))) {
1023
+ return false;
1024
+ }
1025
+ const focusLineNode = focusNode;
1026
+ const direction = $getCodeLineDirection(focusLineNode);
1027
+ const moveToStart = direction === 'rtl' ? !isMoveToStart : isMoveToStart;
1028
+ if (moveToStart) {
1029
+ const start = $getStartOfCodeInLine(focusLineNode, focus.offset);
1030
+ if (start !== null) {
1031
+ const {
1032
+ node,
1033
+ offset
1034
+ } = start;
1035
+ if ($isLineBreakNode(node)) {
1036
+ node.selectNext(0, 0);
1037
+ } else {
1038
+ selection.setTextNodeRange(node, offset, node, offset);
1039
+ }
1040
+ } else {
1041
+ focusLineNode.getParentOrThrow().selectStart();
1042
+ }
1043
+ } else {
1044
+ const node = $getEndOfCodeInLine(focusLineNode);
1045
+ node.select();
1046
+ }
1047
+ event.preventDefault();
1048
+ event.stopPropagation();
1049
+ return true;
1050
+ }
1051
+
1052
+ /**
1053
+ * @internal
1054
+ * Register the keyboard and command handlers that drive code-block
1055
+ * indentation: Tab / Shift+Tab, INDENT/OUTDENT_CONTENT_COMMAND,
1056
+ * INSERT_TAB_COMMAND, alt+arrow line shifting, and Home/End movement.
1057
+ *
1058
+ * Both `@lexical/code-shiki` and `@lexical/code-prism` use this via
1059
+ * {@link CodeIndentExtension}; callers using `registerCodeHighlighting`
1060
+ * will implicitly call this with tabSize of `undefined`.
1061
+ *
1062
+ * @param editor The editor to register on.
1063
+ * @param tabSize When set, OUTDENT_CONTENT_COMMAND (Shift+Tab) also strips
1064
+ * up to that many leading spaces from a code line. See
1065
+ * {@link $outdentLeadingSpaces}.
1066
+ */
1067
+ function registerCodeIndentation(editor, tabSize) {
1068
+ return mergeRegister(editor.registerCommand(KEY_TAB_COMMAND, event => {
1069
+ const command = $handleTab(event.shiftKey);
1070
+ if (command === null) {
1071
+ return false;
1072
+ }
1073
+ event.preventDefault();
1074
+ editor.dispatchCommand(command, undefined);
1075
+ return true;
1076
+ }, COMMAND_PRIORITY_LOW), editor.registerCommand(INSERT_TAB_COMMAND, () => {
1077
+ const selection = $getSelection();
1078
+ if (!$isSelectionInCode(selection)) {
1079
+ return false;
1080
+ }
1081
+ $insertNodes([$createTabNode()]);
1082
+ return true;
1083
+ }, COMMAND_PRIORITY_LOW), editor.registerCommand(INDENT_CONTENT_COMMAND, () => $handleMultilineIndent(INDENT_CONTENT_COMMAND), COMMAND_PRIORITY_LOW), editor.registerCommand(OUTDENT_CONTENT_COMMAND, () => $handleMultilineIndent(OUTDENT_CONTENT_COMMAND, tabSize), COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_ARROW_UP_COMMAND, event => {
1084
+ const selection = $getSelection();
1085
+ if (!$isRangeSelection(selection)) {
1086
+ return false;
1087
+ }
1088
+ const {
1089
+ anchor
1090
+ } = selection;
1091
+ const anchorNode = anchor.getNode();
1092
+ if (!$isSelectionInCode(selection)) {
1093
+ return false;
1094
+ }
1095
+ // If at the start of a code block, prevent selection from moving out
1096
+ if (selection.isCollapsed() && anchor.offset === 0 && anchorNode.getPreviousSibling() === null && $isCodeNode(anchorNode.getParentOrThrow())) {
1097
+ event.preventDefault();
1098
+ return true;
1099
+ }
1100
+ return $handleShiftLines(KEY_ARROW_UP_COMMAND, event);
1101
+ }, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_ARROW_DOWN_COMMAND, event => {
1102
+ const selection = $getSelection();
1103
+ if (!$isRangeSelection(selection)) {
1104
+ return false;
1105
+ }
1106
+ const {
1107
+ anchor
1108
+ } = selection;
1109
+ const anchorNode = anchor.getNode();
1110
+ if (!$isSelectionInCode(selection)) {
1111
+ return false;
1112
+ }
1113
+ // If at the end of a code block, prevent selection from moving out
1114
+ if (selection.isCollapsed() && anchor.offset === anchorNode.getTextContentSize() && anchorNode.getNextSibling() === null && $isCodeNode(anchorNode.getParentOrThrow())) {
1115
+ event.preventDefault();
1116
+ return true;
1117
+ }
1118
+ return $handleShiftLines(KEY_ARROW_DOWN_COMMAND, event);
1119
+ }, COMMAND_PRIORITY_LOW), editor.registerCommand(MOVE_TO_START, event => $handleMoveTo(MOVE_TO_START, event), COMMAND_PRIORITY_LOW), editor.registerCommand(MOVE_TO_END, event => $handleMoveTo(MOVE_TO_END, event), COMMAND_PRIORITY_LOW));
1120
+ }
1121
+ /**
1122
+ * Adds keyboard-driven indentation to code blocks (Tab / Shift+Tab,
1123
+ * alt+arrow line shifts, Home/End within a line). Both
1124
+ * {@link "@lexical/code-shiki".CodeShikiExtension} and
1125
+ * {@link "@lexical/code-prism".CodePrismExtension} declare this as a
1126
+ * dependency, so it is activated automatically alongside either
1127
+ * highlighter.
1128
+ *
1129
+ * Code blocks without syntax highlighting can use this extension on its
1130
+ * own.
1131
+ */
1132
+ const CodeIndentExtension = defineExtension({
1133
+ build: (editor, config) => namedSignals(config),
1134
+ config: safeCast({
1135
+ disabled: false,
1136
+ tabSize: undefined
1137
+ }),
1138
+ dependencies: [CodeExtension],
1139
+ name: '@lexical/code-indent',
1140
+ register: (editor, config, state) => {
1141
+ const stores = state.getOutput();
1142
+ return effect(() => {
1143
+ if (stores.disabled.value) {
1144
+ return;
1145
+ }
1146
+ return registerCodeIndentation(editor, stores.tabSize.value);
1147
+ });
1148
+ }
1149
+ });
1150
+
1151
+ export { $createCodeHighlightNode, $createCodeNode, $getCodeLineDirection, $getEndOfCodeInLine, $getFirstCodeNodeOfLine, $getLastCodeNodeOfLine, $getStartOfCodeInLine, $isCodeHighlightNode, $isCodeNode, $outdentLeadingSpaces, CodeExtension, CodeHighlightNode, CodeIndentExtension, CodeNode, DEFAULT_CODE_LANGUAGE, getDefaultCodeLanguage, registerCodeIndentation };
@@ -73,6 +73,12 @@ declare export function $getLastCodeNodeOfLine(
73
73
  anchor: CodeHighlightNode | TabNode | LineBreakNode,
74
74
  ): CodeHighlightNode | TabNode | LineBreakNode;
75
75
 
76
+ declare export function $outdentLeadingSpaces(
77
+ node: CodeHighlightNode,
78
+ tabSize: number,
79
+ selection: RangeSelection,
80
+ ): boolean;
81
+
76
82
  /**
77
83
  * CodeNode
78
84
  */
@@ -105,3 +111,15 @@ declare export class CodeNode extends ElementNode {
105
111
  }
106
112
 
107
113
  declare export var CodeExtension: LexicalExtension<ExtensionConfigBase, "@lexical/code", unknown, unknown>;
114
+
115
+ export type CodeIndentConfig = {
116
+ disabled: boolean,
117
+ tabSize: number | void,
118
+ };
119
+
120
+ declare export function registerCodeIndentation(
121
+ editor: LexicalEditor,
122
+ tabSize?: number,
123
+ ): () => void;
124
+
125
+ declare export var CodeIndentExtension: LexicalExtension<CodeIndentConfig, "@lexical/code-indent", unknown, unknown>;
@@ -18,8 +18,11 @@ export const $getLastCodeNodeOfLine = mod.$getLastCodeNodeOfLine;
18
18
  export const $getStartOfCodeInLine = mod.$getStartOfCodeInLine;
19
19
  export const $isCodeHighlightNode = mod.$isCodeHighlightNode;
20
20
  export const $isCodeNode = mod.$isCodeNode;
21
+ export const $outdentLeadingSpaces = mod.$outdentLeadingSpaces;
21
22
  export const CodeExtension = mod.CodeExtension;
22
23
  export const CodeHighlightNode = mod.CodeHighlightNode;
24
+ export const CodeIndentExtension = mod.CodeIndentExtension;
23
25
  export const CodeNode = mod.CodeNode;
24
26
  export const DEFAULT_CODE_LANGUAGE = mod.DEFAULT_CODE_LANGUAGE;
25
- export const getDefaultCodeLanguage = mod.getDefaultCodeLanguage;
27
+ export const getDefaultCodeLanguage = mod.getDefaultCodeLanguage;
28
+ export const registerCodeIndentation = mod.registerCodeIndentation;
@@ -16,8 +16,11 @@ export const $getLastCodeNodeOfLine = mod.$getLastCodeNodeOfLine;
16
16
  export const $getStartOfCodeInLine = mod.$getStartOfCodeInLine;
17
17
  export const $isCodeHighlightNode = mod.$isCodeHighlightNode;
18
18
  export const $isCodeNode = mod.$isCodeNode;
19
+ export const $outdentLeadingSpaces = mod.$outdentLeadingSpaces;
19
20
  export const CodeExtension = mod.CodeExtension;
20
21
  export const CodeHighlightNode = mod.CodeHighlightNode;
22
+ export const CodeIndentExtension = mod.CodeIndentExtension;
21
23
  export const CodeNode = mod.CodeNode;
22
24
  export const DEFAULT_CODE_LANGUAGE = mod.DEFAULT_CODE_LANGUAGE;
23
- export const getDefaultCodeLanguage = mod.getDefaultCodeLanguage;
25
+ export const getDefaultCodeLanguage = mod.getDefaultCodeLanguage;
26
+ export const registerCodeIndentation = mod.registerCodeIndentation;
@@ -6,4 +6,4 @@
6
6
  *
7
7
  */
8
8
 
9
- "use strict";var e=require("lexical"),t=require("@lexical/extension");function n(e,...t){const n=new URL("https://lexical.dev/docs/error"),r=new URLSearchParams;r.append("code",e);for(const e of t)r.append("v",e);throw n.search=r.toString(),Error(`Minified Lexical error #${e}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}function r(t,n){let r=t;for(let i=e.$getSiblingCaret(t,n);i&&(L(i.origin)||e.$isTabNode(i.origin));i=i.getAdjacentCaret())r=i.origin;return r}function i(e){return r(e,"previous")}function o(e){return r(e,"next")}const s="javascript";function l(t,n){for(const r of t.childNodes){if(e.isHTMLElement(r)&&r.tagName===n)return!0;l(r,n)}return!1}const a="data-language",g="data-highlight-language",u="data-theme",c=()=>{};class h extends e.ElementNode{__language;__theme;__isSyntaxHighlightSupported;static getType(){return"code"}static clone(e){return new h(e.__language,e.__key)}constructor(e,t){super(t),this.__language=e||void 0,this.__isSyntaxHighlightSupported=!1,this.__theme=void 0}afterCloneFrom(e){super.afterCloneFrom(e),this.__language=e.__language,this.__theme=e.__theme,this.__isSyntaxHighlightSupported=e.__isSyntaxHighlightSupported}createDOM(t){const n=document.createElement("code");e.addClassNamesToElement(n,t.theme.code),n.setAttribute("spellcheck","false");const r=this.getLanguage();r&&(n.setAttribute(a,r),this.getIsSyntaxHighlightSupported()&&n.setAttribute(g,r));const i=this.getTheme();i&&n.setAttribute(u,i);const o=this.getStyle();return o&&e.setDOMStyleFromCSS(n.style,o),n}updateDOM(t,n,r){const i=this.__language,o=t.__language;i?i!==o&&n.setAttribute(a,i):o&&n.removeAttribute(a);const s=this.__isSyntaxHighlightSupported;t.__isSyntaxHighlightSupported&&o?s&&i?i!==o&&n.setAttribute(g,i):n.removeAttribute(g):s&&i&&n.setAttribute(g,i);const l=this.__theme,c=t.__theme;l?l!==c&&n.setAttribute(u,l):c&&n.removeAttribute(u);const h=this.__style,d=t.__style;return h!==d&&e.setDOMStyleFromCSS(n.style,h,d),!1}exportDOM(t){const n=document.createElement("pre");e.addClassNamesToElement(n,t._config.theme.code),n.setAttribute("spellcheck","false");const r=this.getLanguage();r&&(n.setAttribute(a,r),this.getIsSyntaxHighlightSupported()&&n.setAttribute(g,r));const i=this.getTheme();i&&n.setAttribute(u,i);const o=this.getStyle();return o&&e.setDOMStyleFromCSS(n.style,o),{element:n}}static importDOM(){return{code:e=>null!=e.textContent&&(/\r?\n/.test(e.textContent)||l(e,"BR"))?{conversion:f,priority:1}:null,div:()=>({conversion:_,priority:1}),pre:()=>({conversion:f,priority:0}),table:e=>N(e)?{conversion:m,priority:3}:null,td:e=>{const t=e,n=t.closest("table");return t.classList.contains("js-file-line")||n&&N(n)?{conversion:x,priority:3}:null},tr:e=>{const t=e.closest("table");return t&&N(t)?{conversion:x,priority:3}:null}}}static importJSON(e){return d().updateFromJSON(e)}updateFromJSON(e){return super.updateFromJSON(e).setLanguage(e.language).setTheme(e.theme)}exportJSON(){return{...super.exportJSON(),language:this.getLanguage(),theme:this.getTheme()}}insertNewAfter(n,r=!0){if(!t.getPeerDependencyFromEditor(e.$getEditor(),"@lexical/code")){c();const e=T(n);if(e)return e}const{anchor:o,focus:s}=n,l=(o.isBefore(s)?o:s).getNode();if(e.$isTextNode(l)){let t=i(l);const n=[];for(;;)if(e.$isTabNode(t))n.push(e.$createTabNode()),t=t.getNextSibling();else{if(!L(t))break;{let e=0;const r=t.getTextContent(),i=t.getTextContentSize();for(;e<i&&" "===r[e];)e++;if(0!==e&&n.push(b(" ".repeat(e))),e!==i)break;t=t.getNextSibling()}}const r=l.splitText(o.offset)[0],s=0===o.offset?0:1,a=r.getIndexWithinParent()+s,g=l.getParentOrThrow(),u=[e.$createLineBreakNode(),...n];g.splice(a,0,u);const c=n[n.length-1];c?c.select():0===o.offset?r.selectPrevious():r.getNextSibling().selectNext(0,0)}if(p(l)){const{offset:t}=n.anchor;l.splice(t,0,[e.$createLineBreakNode()]),l.select(t+1,t+1)}return null}canIndent(){return!1}collapseAtStart(){const t=e.$createParagraphNode();return this.getChildren().forEach(e=>t.append(e)),this.replace(t),!0}setLanguage(e){const t=this.getWritable();return t.__language=e||void 0,t}getLanguage(){return this.getLatest().__language}setIsSyntaxHighlightSupported(e){const t=this.getWritable();return t.__isSyntaxHighlightSupported=e,t}getIsSyntaxHighlightSupported(){return this.getLatest().__isSyntaxHighlightSupported}setTheme(e){const t=this.getWritable();return t.__theme=e||void 0,t}getTheme(){return this.getLatest().__theme}}function d(t,n){return e.$create(h).setLanguage(t).setTheme(n)}function p(e){return e instanceof h}function f(e){return{node:d(e.getAttribute(a))}}function _(e){const t=e,n=S(t);return n||function(e){let t=e.parentElement;for(;null!==t;){if(S(t))return!0;t=t.parentElement}return!1}(t)?{node:n?d():null}:{node:null}}function m(){return{node:d()}}function x(){return{node:null}}function S(e){return null!==e.style.fontFamily.match("monospace")}function N(e){return e.classList.contains("js-file-line-container")}function T(t){const{anchor:n}=t;if(t.isCollapsed()&&"element"===n.type){const t=n.getNode();if(p(t)){const r=t.getChildrenSize();if(r>=2&&n.offset===r){const n=t.getLastChild();if(e.$isLineBreakNode(n)&&e.$isLineBreakNode(n.getPreviousSibling())){const n=e.$createParagraphNode();return t.splice(r-2,2,[]).insertAfter(n,!1),n.select(),n}}}}return null}class y extends e.TextNode{__highlightType;constructor(e="",t,n){super(e,n),this.__highlightType=t}static getType(){return"code-highlight"}static clone(e){return new y(e.__text,e.__highlightType||void 0,e.__key)}afterCloneFrom(e){super.afterCloneFrom(e),this.__highlightType=e.__highlightType}getHighlightType(){return this.getLatest().__highlightType}setHighlightType(e){const t=this.getWritable();return t.__highlightType=e||void 0,t}canHaveFormat(){return!1}createDOM(t){const n=super.createDOM(t),r=C(t.theme,this.__highlightType);return e.addClassNamesToElement(n,r),n}updateDOM(t,n,r){const i=super.updateDOM(t,n,r),o=C(r.theme,t.__highlightType),s=C(r.theme,this.__highlightType);return o!==s&&(o&&e.removeClassNamesFromElement(n,o),s&&e.addClassNamesToElement(n,s)),i}static importJSON(e){return b().updateFromJSON(e)}updateFromJSON(e){return super.updateFromJSON(e).setHighlightType(e.highlightType)}exportJSON(){return{...super.exportJSON(),highlightType:this.getHighlightType()}}setFormat(e){return this}isParentRequired(){return!0}createParentElementNode(){return d()}}function C(e,t){return t&&e&&e.codeHighlight&&e.codeHighlight[t]}function b(t="",n){return e.$applyNodeReplacement(new y(t,n))}function L(e){return e instanceof y}const O=e.defineExtension({name:"@lexical/code",nodes:()=>[h,y],register:t=>t.registerCommand(e.KEY_ENTER_COMMAND,t=>{const n=e.$getSelection();return!(!e.$isRangeSelection(n)||!T(n))&&(t.preventDefault(),!0)},e.COMMAND_PRIORITY_LOW)});exports.$createCodeHighlightNode=b,exports.$createCodeNode=d,exports.$getCodeLineDirection=function(t){const n=i(t),r=o(t);let s=n;for(;null!==s;){if(L(s)){const t=e.getTextDirection(s.getTextContent());if(null!==t)return t}if(s===r)break;s=s.getNextSibling()}const l=n.getParent();if(e.$isElementNode(l)){const e=l.getDirection();if("ltr"===e||"rtl"===e)return e}return null},exports.$getEndOfCodeInLine=function(t){const r=o(t);return e.$isLineBreakNode(r)&&n(168),r},exports.$getFirstCodeNodeOfLine=i,exports.$getLastCodeNodeOfLine=o,exports.$getStartOfCodeInLine=function(t,r){let i=null,o=null,s=t,l=r,a=t.getTextContent();for(;;){if(0===l){if(s=s.getPreviousSibling(),null===s)break;if(L(s)||e.$isTabNode(s)||e.$isLineBreakNode(s)||n(167),e.$isLineBreakNode(s)){i={node:s,offset:1};break}l=Math.max(0,s.getTextContentSize()-1),a=s.getTextContent()}else l--;const t=a[l];L(s)&&" "!==t&&(o={node:s,offset:l})}if(null!==o)return o;let g=null;if(r<t.getTextContentSize())L(t)&&(g=t.getTextContent()[r]);else{const e=t.getNextSibling();L(e)&&(g=e.getTextContent()[0])}if(null!==g&&" "!==g)return i;{const n=function(t,n){let r=t,i=n,o=t.getTextContent(),s=t.getTextContentSize();for(;;){if(!L(r)||i===s){if(r=r.getNextSibling(),null===r||e.$isLineBreakNode(r))return null;L(r)&&(i=0,o=r.getTextContent(),s=r.getTextContentSize())}if(L(r)){if(" "!==o[i])return{node:r,offset:i};i++}}}(t,r);return null!==n?n:i}},exports.$isCodeHighlightNode=L,exports.$isCodeNode=p,exports.CodeExtension=O,exports.CodeHighlightNode=y,exports.CodeNode=h,exports.DEFAULT_CODE_LANGUAGE=s,exports.getDefaultCodeLanguage=()=>s;
9
+ "use strict";var e=require("lexical"),t=require("@lexical/extension");function n(e,...t){const n=new URL("https://lexical.dev/docs/error"),r=new URLSearchParams;r.append("code",e);for(const e of t)r.append("v",e);throw n.search=r.toString(),Error(`Minified Lexical error #${e}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}function r(t,n){let r=t;for(let i=e.$getSiblingCaret(t,n);i&&(A(i.origin)||e.$isTabNode(i.origin));i=i.getAdjacentCaret())r=i.origin;return r}function i(e){return r(e,"previous")}function o(e){return r(e,"next")}function s(t){const n=i(t),r=o(t);let s=n;for(;null!==s;){if(A(s)){const t=e.getTextDirection(s.getTextContent());if(null!==t)return t}if(s===r)break;s=s.getNextSibling()}const l=n.getParent();if(e.$isElementNode(l)){const e=l.getDirection();if("ltr"===e||"rtl"===e)return e}return null}function l(t,r){let i=null,o=null,s=t,l=r,a=t.getTextContent();for(;;){if(0===l){if(s=s.getPreviousSibling(),null===s)break;if(A(s)||e.$isTabNode(s)||e.$isLineBreakNode(s)||n(167),e.$isLineBreakNode(s)){i={node:s,offset:1};break}l=Math.max(0,s.getTextContentSize()-1),a=s.getTextContent()}else l--;const t=a[l];A(s)&&" "!==t&&(o={node:s,offset:l})}if(null!==o)return o;let g=null;if(r<t.getTextContentSize())A(t)&&(g=t.getTextContent()[r]);else{const e=t.getNextSibling();A(e)&&(g=e.getTextContent()[0])}if(null!==g&&" "!==g)return i;{const n=function(t,n){let r=t,i=n,o=t.getTextContent(),s=t.getTextContentSize();for(;;){if(!A(r)||i===s){if(r=r.getNextSibling(),null===r||e.$isLineBreakNode(r))return null;A(r)&&(i=0,o=r.getTextContent(),s=r.getTextContentSize())}if(A(r)){if(" "!==o[i])return{node:r,offset:i};i++}}}(t,r);return null!==n?n:i}}function a(t){const r=o(t);return e.$isLineBreakNode(r)&&n(168),r}function g(e,t,n){if(!Number.isInteger(t)||t<=0)return!1;const r=e.getTextContent(),i=/^ +/.exec(r);if(!i)return!1;const o=Math.min(t,i[0].length),s=e.getKey(),l=n.anchor.key===s&&"text"===n.anchor.type?n.anchor.offset:null,a=n.focus.key===s&&"text"===n.focus.type?n.focus.offset:null;return e.spliceText(0,o,""),null!==l&&n.anchor.set(s,Math.max(0,l-o),"text"),null!==a&&n.focus.set(s,Math.max(0,a-o),"text"),!0}const u="javascript";function c(t,n){for(const r of t.childNodes){if(e.isHTMLElement(r)&&r.tagName===n)return!0;c(r,n)}return!1}const d="data-language",h="data-highlight-language",f="data-theme",N=()=>{};class _ extends e.ElementNode{__language;__theme;__isSyntaxHighlightSupported;static getType(){return"code"}static clone(e){return new _(e.__language,e.__key)}constructor(e,t){super(t),this.__language=e||void 0,this.__isSyntaxHighlightSupported=!1,this.__theme=void 0}afterCloneFrom(e){super.afterCloneFrom(e),this.__language=e.__language,this.__theme=e.__theme,this.__isSyntaxHighlightSupported=e.__isSyntaxHighlightSupported}createDOM(t){const n=document.createElement("code");e.addClassNamesToElement(n,t.theme.code),n.setAttribute("spellcheck","false");const r=this.getLanguage();r&&(n.setAttribute(d,r),this.getIsSyntaxHighlightSupported()&&n.setAttribute(h,r));const i=this.getTheme();i&&n.setAttribute(f,i);const o=this.getStyle();return o&&e.setDOMStyleFromCSS(n.style,o),n}updateDOM(t,n,r){const i=this.__language,o=t.__language;i?i!==o&&n.setAttribute(d,i):o&&n.removeAttribute(d);const s=this.__isSyntaxHighlightSupported;t.__isSyntaxHighlightSupported&&o?s&&i?i!==o&&n.setAttribute(h,i):n.removeAttribute(h):s&&i&&n.setAttribute(h,i);const l=this.__theme,a=t.__theme;l?l!==a&&n.setAttribute(f,l):a&&n.removeAttribute(f);const g=this.__style,u=t.__style;return g!==u&&e.setDOMStyleFromCSS(n.style,g,u),!1}exportDOM(t){const n=document.createElement("pre");e.addClassNamesToElement(n,t._config.theme.code),n.setAttribute("spellcheck","false");const r=this.getLanguage();r&&(n.setAttribute(d,r),this.getIsSyntaxHighlightSupported()&&n.setAttribute(h,r));const i=this.getTheme();i&&n.setAttribute(f,i);const o=this.getStyle();return o&&e.setDOMStyleFromCSS(n.style,o),{element:n}}static importDOM(){return{code:e=>null!=e.textContent&&(/\r?\n/.test(e.textContent)||c(e,"BR"))?{conversion:C,priority:1}:null,div:()=>({conversion:O,priority:1}),pre:()=>({conversion:C,priority:0}),table:e=>M(e)?{conversion:m,priority:3}:null,td:e=>{const t=e,n=t.closest("table");return t.classList.contains("js-file-line")||n&&M(n)?{conversion:x,priority:3}:null},tr:e=>{const t=e.closest("table");return t&&M(t)?{conversion:x,priority:3}:null}}}static importJSON(e){return p().updateFromJSON(e)}updateFromJSON(e){return super.updateFromJSON(e).setLanguage(e.language).setTheme(e.theme)}exportJSON(){return{...super.exportJSON(),language:this.getLanguage(),theme:this.getTheme()}}insertNewAfter(n,r=!0){if(!t.getPeerDependencyFromEditor(e.$getEditor(),"@lexical/code")){N();const e=$(n);if(e)return e}const{anchor:o,focus:s}=n,l=(o.isBefore(s)?o:s).getNode();if(e.$isTextNode(l)){let t=i(l);const n=[];for(;;)if(e.$isTabNode(t))n.push(e.$createTabNode()),t=t.getNextSibling();else{if(!A(t))break;{let e=0;const r=t.getTextContent(),i=t.getTextContentSize();for(;e<i&&" "===r[e];)e++;if(0!==e&&n.push(E(" ".repeat(e))),e!==i)break;t=t.getNextSibling()}}const r=l.splitText(o.offset)[0],s=0===o.offset?0:1,a=r.getIndexWithinParent()+s,g=l.getParentOrThrow(),u=[e.$createLineBreakNode(),...n];g.splice(a,0,u);const c=n[n.length-1];c?c.select():0===o.offset?r.selectPrevious():r.getNextSibling().selectNext(0,0)}if(T(l)){const{offset:t}=n.anchor;l.splice(t,0,[e.$createLineBreakNode()]),l.select(t+1,t+1)}return null}canIndent(){return!1}collapseAtStart(){const t=e.$createParagraphNode();return this.getChildren().forEach(e=>t.append(e)),this.replace(t),!0}setLanguage(e){const t=this.getWritable();return t.__language=e||void 0,t}getLanguage(){return this.getLatest().__language}setIsSyntaxHighlightSupported(e){const t=this.getWritable();return t.__isSyntaxHighlightSupported=e,t}getIsSyntaxHighlightSupported(){return this.getLatest().__isSyntaxHighlightSupported}setTheme(e){const t=this.getWritable();return t.__theme=e||void 0,t}getTheme(){return this.getLatest().__theme}}function p(t,n){return e.$create(_).setLanguage(t).setTheme(n)}function T(e){return e instanceof _}function C(e){return{node:p(e.getAttribute(d))}}function O(e){const t=e,n=S(t);return n||function(e){let t=e.parentElement;for(;null!==t;){if(S(t))return!0;t=t.parentElement}return!1}(t)?{node:n?p():null}:{node:null}}function m(){return{node:p()}}function x(){return{node:null}}function S(e){return null!==e.style.fontFamily.match("monospace")}function M(e){return e.classList.contains("js-file-line-container")}function $(t){const{anchor:n}=t;if(t.isCollapsed()&&"element"===n.type){const t=n.getNode();if(T(t)){const r=t.getChildrenSize();if(r>=2&&n.offset===r){const n=t.getLastChild();if(e.$isLineBreakNode(n)&&e.$isLineBreakNode(n.getPreviousSibling())){const n=e.$createParagraphNode();return t.splice(r-2,2,[]).insertAfter(n,!1),n.select(),n}}}}return null}class b extends e.TextNode{__highlightType;constructor(e="",t,n){super(e,n),this.__highlightType=t}static getType(){return"code-highlight"}static clone(e){return new b(e.__text,e.__highlightType||void 0,e.__key)}afterCloneFrom(e){super.afterCloneFrom(e),this.__highlightType=e.__highlightType}getHighlightType(){return this.getLatest().__highlightType}setHighlightType(e){const t=this.getWritable();return t.__highlightType=e||void 0,t}canHaveFormat(){return!1}createDOM(t){const n=super.createDOM(t),r=D(t.theme,this.__highlightType);return e.addClassNamesToElement(n,r),n}updateDOM(t,n,r){const i=super.updateDOM(t,n,r),o=D(r.theme,t.__highlightType),s=D(r.theme,this.__highlightType);return o!==s&&(o&&e.removeClassNamesFromElement(n,o),s&&e.addClassNamesToElement(n,s)),i}static importJSON(e){return E().updateFromJSON(e)}updateFromJSON(e){return super.updateFromJSON(e).setHighlightType(e.highlightType)}exportJSON(){return{...super.exportJSON(),highlightType:this.getHighlightType()}}setFormat(e){return this}isParentRequired(){return!0}createParentElementNode(){return p()}}function D(e,t){return t&&e&&e.codeHighlight&&e.codeHighlight[t]}function E(t="",n){return e.$applyNodeReplacement(new b(t,n))}function A(e){return e instanceof b}const y=e.defineExtension({name:"@lexical/code",nodes:()=>[_,b],register:t=>t.registerCommand(e.KEY_ENTER_COMMAND,t=>{const n=e.$getSelection();return!(!e.$isRangeSelection(n)||!$(n))&&(t.preventDefault(),!0)},e.COMMAND_PRIORITY_LOW)});function R(t){if(!e.$isRangeSelection(t))return!1;const n=t.anchor.getNode(),r=T(n)?n:n.getParent(),i=t.focus.getNode(),o=T(i)?i:i.getParent();return T(r)&&r.is(o)}function v(t){const r=t.getNodes(),i=[];if(1===r.length&&T(r[0]))return i;let o=[];for(let t=0;t<r.length;t++){const s=r[t];A(s)||e.$isTabNode(s)||e.$isLineBreakNode(s)||n(169),e.$isLineBreakNode(s)?o.length>0&&(i.push(o),o=[]):o.push(s)}if(o.length>0){const n=t.isBackward()?t.anchor:t.focus,r=e.$createPoint(o[0].getKey(),0,"text");n.is(r)||i.push(o)}return i}function L(t,n){const r=e.$getSelection();if(!e.$isRangeSelection(r)||!R(r))return!1;const o=v(r),s=o.length;if(0===s&&r.isCollapsed())return t===e.INDENT_CONTENT_COMMAND&&r.insertNodes([e.$createTabNode()]),!0;if(0===s&&t===e.INDENT_CONTENT_COMMAND&&"\n"===r.getTextContent()){const t=e.$createTabNode(),n=e.$createLineBreakNode(),i=r.isBackward()?"previous":"next";return r.insertNodes([t,n]),e.$setSelectionFromCaretRange(e.$getCaretRangeInDirection(e.$getCaretRange(e.$getTextPointCaret(t,"next",0),e.$normalizeCaret(e.$getSiblingCaret(n,"next"))),i)),!0}for(let l=0;l<s;l++){const s=o[l];if(s.length>0){let o=s[0];if(0===l&&(o=i(o)),t===e.INDENT_CONTENT_COMMAND){const t=e.$createTabNode();if(o.insertBefore(t),0===l){const n=r.isBackward()?"focus":"anchor",i=e.$createPoint(o.getKey(),0,"text");r[n].is(i)&&r[n].set(t.getKey(),0,"text")}}else e.$isTabNode(o)?o.remove():void 0!==n&&A(o)&&g(o,n,r)}}return!0}function P(t,n){const r=e.$getSelection();if(!e.$isRangeSelection(r))return!1;const{anchor:s,focus:l}=r,a=s.offset,g=l.offset,u=s.getNode(),c=l.getNode(),d=t===e.KEY_ARROW_UP_COMMAND;if(!R(r)||!A(u)&&!e.$isTabNode(u)||!A(c)&&!e.$isTabNode(c))return!1;if(!n.altKey){if(r.isCollapsed()){const e=u.getParentOrThrow();if(d&&0===a&&null===u.getPreviousSibling()){if(null===e.getPreviousSibling())return e.selectPrevious(),n.preventDefault(),!0}else if(!d&&a===u.getTextContentSize()&&null===u.getNextSibling()){if(null===e.getNextSibling())return e.selectNext(),n.preventDefault(),!0}}return!1}let h,f;if(u.isBefore(c)?(h=i(u),f=o(c)):(h=i(c),f=o(u)),null==h||null==f)return!1;const N=h.getNodesBetween(f);for(let t=0;t<N.length;t++){const n=N[t];if(!A(n)&&!e.$isTabNode(n)&&!e.$isLineBreakNode(n))return!1}n.preventDefault(),n.stopPropagation();const _=d?h.getPreviousSibling():f.getNextSibling();if(!e.$isLineBreakNode(_))return!0;const p=d?_.getPreviousSibling():_.getNextSibling();if(null==p)return!0;const T=A(p)||e.$isTabNode(p)||e.$isLineBreakNode(p)?d?i(p):o(p):null;let C=null!=T?T:p;return _.remove(),N.forEach(e=>e.remove()),t===e.KEY_ARROW_UP_COMMAND?(N.forEach(e=>C.insertBefore(e)),C.insertBefore(_)):(C.insertAfter(_),C=_,N.forEach(e=>{C.insertAfter(e),C=e})),r.setTextNodeRange(u,a,c,g),!0}function I(t,n){const r=e.$getSelection();if(!e.$isRangeSelection(r))return!1;const{anchor:i,focus:o}=r,g=i.getNode(),u=o.getNode(),c=t===e.MOVE_TO_START;if(!R(r)||!A(g)&&!e.$isTabNode(g)||!A(u)&&!e.$isTabNode(u))return!1;const d=u;if("rtl"===s(d)?!c:c){const t=l(d,o.offset);if(null!==t){const{node:n,offset:i}=t;e.$isLineBreakNode(n)?n.selectNext(0,0):r.setTextNodeRange(n,i,n,i)}else d.getParentOrThrow().selectStart()}else{a(d).select()}return n.preventDefault(),n.stopPropagation(),!0}function B(t,r){return e.mergeRegister(t.registerCommand(e.KEY_TAB_COMMAND,r=>{const s=function(t){const r=e.$getSelection();if(!e.$isRangeSelection(r)||!R(r))return null;const s=t?e.OUTDENT_CONTENT_COMMAND:e.INDENT_CONTENT_COMMAND,l=t?e.OUTDENT_CONTENT_COMMAND:e.INSERT_TAB_COMMAND,a=r.anchor,g=r.focus;if(a.is(g))return l;const u=v(r);if(1!==u.length)return s;const c=u[0];let d,h;0===c.length&&n(285),r.isBackward()?(d=g,h=a):(d=a,h=g);const f=i(c[0]),N=o(c[0]),_=e.$createPoint(f.getKey(),0,"text"),p=e.$createPoint(N.getKey(),N.getTextContentSize(),"text");return d.isBefore(_)||p.isBefore(h)?s:_.isBefore(d)||h.isBefore(p)?l:s}(r.shiftKey);return null!==s&&(r.preventDefault(),t.dispatchCommand(s,void 0),!0)},e.COMMAND_PRIORITY_LOW),t.registerCommand(e.INSERT_TAB_COMMAND,()=>!!R(e.$getSelection())&&(e.$insertNodes([e.$createTabNode()]),!0),e.COMMAND_PRIORITY_LOW),t.registerCommand(e.INDENT_CONTENT_COMMAND,()=>L(e.INDENT_CONTENT_COMMAND),e.COMMAND_PRIORITY_LOW),t.registerCommand(e.OUTDENT_CONTENT_COMMAND,()=>L(e.OUTDENT_CONTENT_COMMAND,r),e.COMMAND_PRIORITY_LOW),t.registerCommand(e.KEY_ARROW_UP_COMMAND,t=>{const n=e.$getSelection();if(!e.$isRangeSelection(n))return!1;const{anchor:r}=n,i=r.getNode();return!!R(n)&&(n.isCollapsed()&&0===r.offset&&null===i.getPreviousSibling()&&T(i.getParentOrThrow())?(t.preventDefault(),!0):P(e.KEY_ARROW_UP_COMMAND,t))},e.COMMAND_PRIORITY_LOW),t.registerCommand(e.KEY_ARROW_DOWN_COMMAND,t=>{const n=e.$getSelection();if(!e.$isRangeSelection(n))return!1;const{anchor:r}=n,i=r.getNode();return!!R(n)&&(n.isCollapsed()&&r.offset===i.getTextContentSize()&&null===i.getNextSibling()&&T(i.getParentOrThrow())?(t.preventDefault(),!0):P(e.KEY_ARROW_DOWN_COMMAND,t))},e.COMMAND_PRIORITY_LOW),t.registerCommand(e.MOVE_TO_START,t=>I(e.MOVE_TO_START,t),e.COMMAND_PRIORITY_LOW),t.registerCommand(e.MOVE_TO_END,t=>I(e.MOVE_TO_END,t),e.COMMAND_PRIORITY_LOW))}const k=e.defineExtension({build:(e,n)=>t.namedSignals(n),config:e.safeCast({disabled:!1,tabSize:void 0}),dependencies:[y],name:"@lexical/code-indent",register:(e,n,r)=>{const i=r.getOutput();return t.effect(()=>{if(!i.disabled.value)return B(e,i.tabSize.value)})}});exports.$createCodeHighlightNode=E,exports.$createCodeNode=p,exports.$getCodeLineDirection=s,exports.$getEndOfCodeInLine=a,exports.$getFirstCodeNodeOfLine=i,exports.$getLastCodeNodeOfLine=o,exports.$getStartOfCodeInLine=l,exports.$isCodeHighlightNode=A,exports.$isCodeNode=T,exports.$outdentLeadingSpaces=g,exports.CodeExtension=y,exports.CodeHighlightNode=b,exports.CodeIndentExtension=k,exports.CodeNode=_,exports.DEFAULT_CODE_LANGUAGE=u,exports.getDefaultCodeLanguage=()=>u,exports.registerCodeIndentation=B;
@@ -6,4 +6,4 @@
6
6
  *
7
7
  */
8
8
 
9
- import{getTextDirection as t,$isElementNode as e,$isTabNode as n,$isLineBreakNode as r,$getSiblingCaret as i,$create as o,ElementNode as s,addClassNamesToElement as l,setDOMStyleFromCSS as u,$getEditor as g,$isTextNode as a,$createTabNode as h,$createLineBreakNode as c,$createParagraphNode as p,isHTMLElement as f,$applyNodeReplacement as d,TextNode as _,removeClassNamesFromElement as m,defineExtension as S,KEY_ENTER_COMMAND as x,$getSelection as y,$isRangeSelection as T,COMMAND_PRIORITY_LOW as b}from"lexical";import{getPeerDependencyFromEditor as v}from"@lexical/extension";function C(t,...e){const n=new URL("https://lexical.dev/docs/error"),r=new URLSearchParams;r.append("code",t);for(const t of e)r.append("v",t);throw n.search=r.toString(),Error(`Minified Lexical error #${t}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}function N(t,e){let r=t;for(let o=i(t,e);o&&(X(o.origin)||n(o.origin));o=o.getAdjacentCaret())r=o.origin;return r}function O(t){return N(t,"previous")}function A(t){return N(t,"next")}function H(n){const r=O(n),i=A(n);let o=r;for(;null!==o;){if(X(o)){const e=t(o.getTextContent());if(null!==e)return e}if(o===i)break;o=o.getNextSibling()}const s=r.getParent();if(e(s)){const t=s.getDirection();if("ltr"===t||"rtl"===t)return t}return null}function L(t,e){let i=null,o=null,s=t,l=e,u=t.getTextContent();for(;;){if(0===l){if(s=s.getPreviousSibling(),null===s)break;if(X(s)||n(s)||r(s)||C(167),r(s)){i={node:s,offset:1};break}l=Math.max(0,s.getTextContentSize()-1),u=s.getTextContent()}else l--;const t=u[l];X(s)&&" "!==t&&(o={node:s,offset:l})}if(null!==o)return o;let g=null;if(e<t.getTextContentSize())X(t)&&(g=t.getTextContent()[e]);else{const e=t.getNextSibling();X(e)&&(g=e.getTextContent()[0])}if(null!==g&&" "!==g)return i;{const n=function(t,e){let n=t,i=e,o=t.getTextContent(),s=t.getTextContentSize();for(;;){if(!X(n)||i===s){if(n=n.getNextSibling(),null===n||r(n))return null;X(n)&&(i=0,o=n.getTextContent(),s=n.getTextContentSize())}if(X(n)){if(" "!==o[i])return{node:n,offset:i};i++}}}(t,e);return null!==n?n:i}}function F(t){const e=A(t);return r(e)&&C(168),e}const J="javascript",D=()=>J;function M(t,e){for(const n of t.childNodes){if(f(n)&&n.tagName===e)return!0;M(n,e)}return!1}const k="data-language",w="data-highlight-language",P="data-theme",E=()=>{};class z extends s{__language;__theme;__isSyntaxHighlightSupported;static getType(){return"code"}static clone(t){return new z(t.__language,t.__key)}constructor(t,e){super(e),this.__language=t||void 0,this.__isSyntaxHighlightSupported=!1,this.__theme=void 0}afterCloneFrom(t){super.afterCloneFrom(t),this.__language=t.__language,this.__theme=t.__theme,this.__isSyntaxHighlightSupported=t.__isSyntaxHighlightSupported}createDOM(t){const e=document.createElement("code");l(e,t.theme.code),e.setAttribute("spellcheck","false");const n=this.getLanguage();n&&(e.setAttribute(k,n),this.getIsSyntaxHighlightSupported()&&e.setAttribute(w,n));const r=this.getTheme();r&&e.setAttribute(P,r);const i=this.getStyle();return i&&u(e.style,i),e}updateDOM(t,e,n){const r=this.__language,i=t.__language;r?r!==i&&e.setAttribute(k,r):i&&e.removeAttribute(k);const o=this.__isSyntaxHighlightSupported;t.__isSyntaxHighlightSupported&&i?o&&r?r!==i&&e.setAttribute(w,r):e.removeAttribute(w):o&&r&&e.setAttribute(w,r);const s=this.__theme,l=t.__theme;s?s!==l&&e.setAttribute(P,s):l&&e.removeAttribute(P);const g=this.__style,a=t.__style;return g!==a&&u(e.style,g,a),!1}exportDOM(t){const e=document.createElement("pre");l(e,t._config.theme.code),e.setAttribute("spellcheck","false");const n=this.getLanguage();n&&(e.setAttribute(k,n),this.getIsSyntaxHighlightSupported()&&e.setAttribute(w,n));const r=this.getTheme();r&&e.setAttribute(P,r);const i=this.getStyle();return i&&u(e.style,i),{element:e}}static importDOM(){return{code:t=>null!=t.textContent&&(/\r?\n/.test(t.textContent)||M(t,"BR"))?{conversion:j,priority:1}:null,div:()=>({conversion:R,priority:1}),pre:()=>({conversion:j,priority:0}),table:t=>q(t)?{conversion:B,priority:3}:null,td:t=>{const e=t,n=e.closest("table");return e.classList.contains("js-file-line")||n&&q(n)?{conversion:U,priority:3}:null},tr:t=>{const e=t.closest("table");return e&&q(e)?{conversion:U,priority:3}:null}}}static importJSON(t){return I().updateFromJSON(t)}updateFromJSON(t){return super.updateFromJSON(t).setLanguage(t.language).setTheme(t.theme)}exportJSON(){return{...super.exportJSON(),language:this.getLanguage(),theme:this.getTheme()}}insertNewAfter(t,e=!0){if(!v(g(),"@lexical/code")){E();const e=G(t);if(e)return e}const{anchor:r,focus:i}=t,o=(r.isBefore(i)?r:i).getNode();if(a(o)){let t=O(o);const e=[];for(;;)if(n(t))e.push(h()),t=t.getNextSibling();else{if(!X(t))break;{let n=0;const r=t.getTextContent(),i=t.getTextContentSize();for(;n<i&&" "===r[n];)n++;if(0!==n&&e.push(V(" ".repeat(n))),n!==i)break;t=t.getNextSibling()}}const i=o.splitText(r.offset)[0],s=0===r.offset?0:1,l=i.getIndexWithinParent()+s,u=o.getParentOrThrow(),g=[c(),...e];u.splice(l,0,g);const a=e[e.length-1];a?a.select():0===r.offset?i.selectPrevious():i.getNextSibling().selectNext(0,0)}if(W(o)){const{offset:e}=t.anchor;o.splice(e,0,[c()]),o.select(e+1,e+1)}return null}canIndent(){return!1}collapseAtStart(){const t=p();return this.getChildren().forEach(e=>t.append(e)),this.replace(t),!0}setLanguage(t){const e=this.getWritable();return e.__language=t||void 0,e}getLanguage(){return this.getLatest().__language}setIsSyntaxHighlightSupported(t){const e=this.getWritable();return e.__isSyntaxHighlightSupported=t,e}getIsSyntaxHighlightSupported(){return this.getLatest().__isSyntaxHighlightSupported}setTheme(t){const e=this.getWritable();return e.__theme=t||void 0,e}getTheme(){return this.getLatest().__theme}}function I(t,e){return o(z).setLanguage(t).setTheme(e)}function W(t){return t instanceof z}function j(t){return{node:I(t.getAttribute(k))}}function R(t){const e=t,n=$(e);return n||function(t){let e=t.parentElement;for(;null!==e;){if($(e))return!0;e=e.parentElement}return!1}(e)?{node:n?I():null}:{node:null}}function B(){return{node:I()}}function U(){return{node:null}}function $(t){return null!==t.style.fontFamily.match("monospace")}function q(t){return t.classList.contains("js-file-line-container")}function G(t){const{anchor:e}=t;if(t.isCollapsed()&&"element"===e.type){const t=e.getNode();if(W(t)){const n=t.getChildrenSize();if(n>=2&&e.offset===n){const e=t.getLastChild();if(r(e)&&r(e.getPreviousSibling())){const e=p();return t.splice(n-2,2,[]).insertAfter(e,!1),e.select(),e}}}}return null}class K extends _{__highlightType;constructor(t="",e,n){super(t,n),this.__highlightType=e}static getType(){return"code-highlight"}static clone(t){return new K(t.__text,t.__highlightType||void 0,t.__key)}afterCloneFrom(t){super.afterCloneFrom(t),this.__highlightType=t.__highlightType}getHighlightType(){return this.getLatest().__highlightType}setHighlightType(t){const e=this.getWritable();return e.__highlightType=t||void 0,e}canHaveFormat(){return!1}createDOM(t){const e=super.createDOM(t),n=Q(t.theme,this.__highlightType);return l(e,n),e}updateDOM(t,e,n){const r=super.updateDOM(t,e,n),i=Q(n.theme,t.__highlightType),o=Q(n.theme,this.__highlightType);return i!==o&&(i&&m(e,i),o&&l(e,o)),r}static importJSON(t){return V().updateFromJSON(t)}updateFromJSON(t){return super.updateFromJSON(t).setHighlightType(t.highlightType)}exportJSON(){return{...super.exportJSON(),highlightType:this.getHighlightType()}}setFormat(t){return this}isParentRequired(){return!0}createParentElementNode(){return I()}}function Q(t,e){return e&&t&&t.codeHighlight&&t.codeHighlight[e]}function V(t="",e){return d(new K(t,e))}function X(t){return t instanceof K}const Y=S({name:"@lexical/code",nodes:()=>[z,K],register:t=>t.registerCommand(x,t=>{const e=y();return!(!T(e)||!G(e))&&(t.preventDefault(),!0)},b)});export{V as $createCodeHighlightNode,I as $createCodeNode,H as $getCodeLineDirection,F as $getEndOfCodeInLine,O as $getFirstCodeNodeOfLine,A as $getLastCodeNodeOfLine,L as $getStartOfCodeInLine,X as $isCodeHighlightNode,W as $isCodeNode,Y as CodeExtension,K as CodeHighlightNode,z as CodeNode,J as DEFAULT_CODE_LANGUAGE,D as getDefaultCodeLanguage};
9
+ import{getTextDirection as t,$isElementNode as e,$isTabNode as n,$isLineBreakNode as r,$getSiblingCaret as i,$create as o,ElementNode as s,addClassNamesToElement as l,setDOMStyleFromCSS as u,$getEditor as g,$isTextNode as c,$createTabNode as a,$createLineBreakNode as h,$createParagraphNode as f,isHTMLElement as p,$applyNodeReplacement as d,TextNode as m,removeClassNamesFromElement as x,defineExtension as _,KEY_ENTER_COMMAND as S,$getSelection as y,$isRangeSelection as b,COMMAND_PRIORITY_LOW as T,safeCast as v,mergeRegister as N,KEY_TAB_COMMAND as C,INSERT_TAB_COMMAND as O,$insertNodes as P,INDENT_CONTENT_COMMAND as A,OUTDENT_CONTENT_COMMAND as H,KEY_ARROW_UP_COMMAND as w,KEY_ARROW_DOWN_COMMAND as D,MOVE_TO_START as L,MOVE_TO_END as k,$createPoint as B,$setSelectionFromCaretRange as F,$getCaretRangeInDirection as M,$getCaretRange as J,$getTextPointCaret as z,$normalizeCaret as E}from"lexical";import{getPeerDependencyFromEditor as K,effect as I,namedSignals as R}from"@lexical/extension";function W(t,...e){const n=new URL("https://lexical.dev/docs/error"),r=new URLSearchParams;r.append("code",t);for(const t of e)r.append("v",t);throw n.search=r.toString(),Error(`Minified Lexical error #${t}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}function j(t,e){let r=t;for(let o=i(t,e);o&&(xt(o.origin)||n(o.origin));o=o.getAdjacentCaret())r=o.origin;return r}function U(t){return j(t,"previous")}function $(t){return j(t,"next")}function q(n){const r=U(n),i=$(n);let o=r;for(;null!==o;){if(xt(o)){const e=t(o.getTextContent());if(null!==e)return e}if(o===i)break;o=o.getNextSibling()}const s=r.getParent();if(e(s)){const t=s.getDirection();if("ltr"===t||"rtl"===t)return t}return null}function G(t,e){let i=null,o=null,s=t,l=e,u=t.getTextContent();for(;;){if(0===l){if(s=s.getPreviousSibling(),null===s)break;if(xt(s)||n(s)||r(s)||W(167),r(s)){i={node:s,offset:1};break}l=Math.max(0,s.getTextContentSize()-1),u=s.getTextContent()}else l--;const t=u[l];xt(s)&&" "!==t&&(o={node:s,offset:l})}if(null!==o)return o;let g=null;if(e<t.getTextContentSize())xt(t)&&(g=t.getTextContent()[e]);else{const e=t.getNextSibling();xt(e)&&(g=e.getTextContent()[0])}if(null!==g&&" "!==g)return i;{const n=function(t,e){let n=t,i=e,o=t.getTextContent(),s=t.getTextContentSize();for(;;){if(!xt(n)||i===s){if(n=n.getNextSibling(),null===n||r(n))return null;xt(n)&&(i=0,o=n.getTextContent(),s=n.getTextContentSize())}if(xt(n)){if(" "!==o[i])return{node:n,offset:i};i++}}}(t,e);return null!==n?n:i}}function Q(t){const e=$(t);return r(e)&&W(168),e}function V(t,e,n){if(!Number.isInteger(e)||e<=0)return!1;const r=t.getTextContent(),i=/^ +/.exec(r);if(!i)return!1;const o=Math.min(e,i[0].length),s=t.getKey(),l=n.anchor.key===s&&"text"===n.anchor.type?n.anchor.offset:null,u=n.focus.key===s&&"text"===n.focus.type?n.focus.offset:null;return t.spliceText(0,o,""),null!==l&&n.anchor.set(s,Math.max(0,l-o),"text"),null!==u&&n.focus.set(s,Math.max(0,u-o),"text"),!0}const X="javascript",Y=()=>X;function Z(t,e){for(const n of t.childNodes){if(p(n)&&n.tagName===e)return!0;Z(n,e)}return!1}const tt="data-language",et="data-highlight-language",nt="data-theme",rt=()=>{};class it extends s{__language;__theme;__isSyntaxHighlightSupported;static getType(){return"code"}static clone(t){return new it(t.__language,t.__key)}constructor(t,e){super(e),this.__language=t||void 0,this.__isSyntaxHighlightSupported=!1,this.__theme=void 0}afterCloneFrom(t){super.afterCloneFrom(t),this.__language=t.__language,this.__theme=t.__theme,this.__isSyntaxHighlightSupported=t.__isSyntaxHighlightSupported}createDOM(t){const e=document.createElement("code");l(e,t.theme.code),e.setAttribute("spellcheck","false");const n=this.getLanguage();n&&(e.setAttribute(tt,n),this.getIsSyntaxHighlightSupported()&&e.setAttribute(et,n));const r=this.getTheme();r&&e.setAttribute(nt,r);const i=this.getStyle();return i&&u(e.style,i),e}updateDOM(t,e,n){const r=this.__language,i=t.__language;r?r!==i&&e.setAttribute(tt,r):i&&e.removeAttribute(tt);const o=this.__isSyntaxHighlightSupported;t.__isSyntaxHighlightSupported&&i?o&&r?r!==i&&e.setAttribute(et,r):e.removeAttribute(et):o&&r&&e.setAttribute(et,r);const s=this.__theme,l=t.__theme;s?s!==l&&e.setAttribute(nt,s):l&&e.removeAttribute(nt);const g=this.__style,c=t.__style;return g!==c&&u(e.style,g,c),!1}exportDOM(t){const e=document.createElement("pre");l(e,t._config.theme.code),e.setAttribute("spellcheck","false");const n=this.getLanguage();n&&(e.setAttribute(tt,n),this.getIsSyntaxHighlightSupported()&&e.setAttribute(et,n));const r=this.getTheme();r&&e.setAttribute(nt,r);const i=this.getStyle();return i&&u(e.style,i),{element:e}}static importDOM(){return{code:t=>null!=t.textContent&&(/\r?\n/.test(t.textContent)||Z(t,"BR"))?{conversion:lt,priority:1}:null,div:()=>({conversion:ut,priority:1}),pre:()=>({conversion:lt,priority:0}),table:t=>ht(t)?{conversion:gt,priority:3}:null,td:t=>{const e=t,n=e.closest("table");return e.classList.contains("js-file-line")||n&&ht(n)?{conversion:ct,priority:3}:null},tr:t=>{const e=t.closest("table");return e&&ht(e)?{conversion:ct,priority:3}:null}}}static importJSON(t){return ot().updateFromJSON(t)}updateFromJSON(t){return super.updateFromJSON(t).setLanguage(t.language).setTheme(t.theme)}exportJSON(){return{...super.exportJSON(),language:this.getLanguage(),theme:this.getTheme()}}insertNewAfter(t,e=!0){if(!K(g(),"@lexical/code")){rt();const e=ft(t);if(e)return e}const{anchor:r,focus:i}=t,o=(r.isBefore(i)?r:i).getNode();if(c(o)){let t=U(o);const e=[];for(;;)if(n(t))e.push(a()),t=t.getNextSibling();else{if(!xt(t))break;{let n=0;const r=t.getTextContent(),i=t.getTextContentSize();for(;n<i&&" "===r[n];)n++;if(0!==n&&e.push(mt(" ".repeat(n))),n!==i)break;t=t.getNextSibling()}}const i=o.splitText(r.offset)[0],s=0===r.offset?0:1,l=i.getIndexWithinParent()+s,u=o.getParentOrThrow(),g=[h(),...e];u.splice(l,0,g);const c=e[e.length-1];c?c.select():0===r.offset?i.selectPrevious():i.getNextSibling().selectNext(0,0)}if(st(o)){const{offset:e}=t.anchor;o.splice(e,0,[h()]),o.select(e+1,e+1)}return null}canIndent(){return!1}collapseAtStart(){const t=f();return this.getChildren().forEach(e=>t.append(e)),this.replace(t),!0}setLanguage(t){const e=this.getWritable();return e.__language=t||void 0,e}getLanguage(){return this.getLatest().__language}setIsSyntaxHighlightSupported(t){const e=this.getWritable();return e.__isSyntaxHighlightSupported=t,e}getIsSyntaxHighlightSupported(){return this.getLatest().__isSyntaxHighlightSupported}setTheme(t){const e=this.getWritable();return e.__theme=t||void 0,e}getTheme(){return this.getLatest().__theme}}function ot(t,e){return o(it).setLanguage(t).setTheme(e)}function st(t){return t instanceof it}function lt(t){return{node:ot(t.getAttribute(tt))}}function ut(t){const e=t,n=at(e);return n||function(t){let e=t.parentElement;for(;null!==e;){if(at(e))return!0;e=e.parentElement}return!1}(e)?{node:n?ot():null}:{node:null}}function gt(){return{node:ot()}}function ct(){return{node:null}}function at(t){return null!==t.style.fontFamily.match("monospace")}function ht(t){return t.classList.contains("js-file-line-container")}function ft(t){const{anchor:e}=t;if(t.isCollapsed()&&"element"===e.type){const t=e.getNode();if(st(t)){const n=t.getChildrenSize();if(n>=2&&e.offset===n){const e=t.getLastChild();if(r(e)&&r(e.getPreviousSibling())){const e=f();return t.splice(n-2,2,[]).insertAfter(e,!1),e.select(),e}}}}return null}class pt extends m{__highlightType;constructor(t="",e,n){super(t,n),this.__highlightType=e}static getType(){return"code-highlight"}static clone(t){return new pt(t.__text,t.__highlightType||void 0,t.__key)}afterCloneFrom(t){super.afterCloneFrom(t),this.__highlightType=t.__highlightType}getHighlightType(){return this.getLatest().__highlightType}setHighlightType(t){const e=this.getWritable();return e.__highlightType=t||void 0,e}canHaveFormat(){return!1}createDOM(t){const e=super.createDOM(t),n=dt(t.theme,this.__highlightType);return l(e,n),e}updateDOM(t,e,n){const r=super.updateDOM(t,e,n),i=dt(n.theme,t.__highlightType),o=dt(n.theme,this.__highlightType);return i!==o&&(i&&x(e,i),o&&l(e,o)),r}static importJSON(t){return mt().updateFromJSON(t)}updateFromJSON(t){return super.updateFromJSON(t).setHighlightType(t.highlightType)}exportJSON(){return{...super.exportJSON(),highlightType:this.getHighlightType()}}setFormat(t){return this}isParentRequired(){return!0}createParentElementNode(){return ot()}}function dt(t,e){return e&&t&&t.codeHighlight&&t.codeHighlight[e]}function mt(t="",e){return d(new pt(t,e))}function xt(t){return t instanceof pt}const _t=_({name:"@lexical/code",nodes:()=>[it,pt],register:t=>t.registerCommand(S,t=>{const e=y();return!(!b(e)||!ft(e))&&(t.preventDefault(),!0)},T)});function St(t){if(!b(t))return!1;const e=t.anchor.getNode(),n=st(e)?e:e.getParent(),r=t.focus.getNode(),i=st(r)?r:r.getParent();return st(n)&&n.is(i)}function yt(t){const e=t.getNodes(),i=[];if(1===e.length&&st(e[0]))return i;let o=[];for(let t=0;t<e.length;t++){const s=e[t];xt(s)||n(s)||r(s)||W(169),r(s)?o.length>0&&(i.push(o),o=[]):o.push(s)}if(o.length>0){const e=t.isBackward()?t.anchor:t.focus,n=B(o[0].getKey(),0,"text");e.is(n)||i.push(o)}return i}function bt(t,e){const r=y();if(!b(r)||!St(r))return!1;const o=yt(r),s=o.length;if(0===s&&r.isCollapsed())return t===A&&r.insertNodes([a()]),!0;if(0===s&&t===A&&"\n"===r.getTextContent()){const t=a(),e=h(),n=r.isBackward()?"previous":"next";return r.insertNodes([t,e]),F(M(J(z(t,"next",0),E(i(e,"next"))),n)),!0}for(let i=0;i<s;i++){const s=o[i];if(s.length>0){let o=s[0];if(0===i&&(o=U(o)),t===A){const t=a();if(o.insertBefore(t),0===i){const e=r.isBackward()?"focus":"anchor",n=B(o.getKey(),0,"text");r[e].is(n)&&r[e].set(t.getKey(),0,"text")}}else n(o)?o.remove():void 0!==e&&xt(o)&&V(o,e,r)}}return!0}function Tt(t,e){const i=y();if(!b(i))return!1;const{anchor:o,focus:s}=i,l=o.offset,u=s.offset,g=o.getNode(),c=s.getNode(),a=t===w;if(!St(i)||!xt(g)&&!n(g)||!xt(c)&&!n(c))return!1;if(!e.altKey){if(i.isCollapsed()){const t=g.getParentOrThrow();if(a&&0===l&&null===g.getPreviousSibling()){if(null===t.getPreviousSibling())return t.selectPrevious(),e.preventDefault(),!0}else if(!a&&l===g.getTextContentSize()&&null===g.getNextSibling()){if(null===t.getNextSibling())return t.selectNext(),e.preventDefault(),!0}}return!1}let h,f;if(g.isBefore(c)?(h=U(g),f=$(c)):(h=U(c),f=$(g)),null==h||null==f)return!1;const p=h.getNodesBetween(f);for(let t=0;t<p.length;t++){const e=p[t];if(!xt(e)&&!n(e)&&!r(e))return!1}e.preventDefault(),e.stopPropagation();const d=a?h.getPreviousSibling():f.getNextSibling();if(!r(d))return!0;const m=a?d.getPreviousSibling():d.getNextSibling();if(null==m)return!0;const x=xt(m)||n(m)||r(m)?a?U(m):$(m):null;let _=null!=x?x:m;return d.remove(),p.forEach(t=>t.remove()),t===w?(p.forEach(t=>_.insertBefore(t)),_.insertBefore(d)):(_.insertAfter(d),_=d,p.forEach(t=>{_.insertAfter(t),_=t})),i.setTextNodeRange(g,l,c,u),!0}function vt(t,e){const i=y();if(!b(i))return!1;const{anchor:o,focus:s}=i,l=o.getNode(),u=s.getNode(),g=t===L;if(!St(i)||!xt(l)&&!n(l)||!xt(u)&&!n(u))return!1;const c=u;if("rtl"===q(c)?!g:g){const t=G(c,s.offset);if(null!==t){const{node:e,offset:n}=t;r(e)?e.selectNext(0,0):i.setTextNodeRange(e,n,e,n)}else c.getParentOrThrow().selectStart()}else{Q(c).select()}return e.preventDefault(),e.stopPropagation(),!0}function Nt(t,e){return N(t.registerCommand(C,e=>{const n=function(t){const e=y();if(!b(e)||!St(e))return null;const n=t?H:A,r=t?H:O,i=e.anchor,o=e.focus;if(i.is(o))return r;const s=yt(e);if(1!==s.length)return n;const l=s[0];let u,g;0===l.length&&W(285),e.isBackward()?(u=o,g=i):(u=i,g=o);const c=U(l[0]),a=$(l[0]),h=B(c.getKey(),0,"text"),f=B(a.getKey(),a.getTextContentSize(),"text");return u.isBefore(h)||f.isBefore(g)?n:h.isBefore(u)||g.isBefore(f)?r:n}(e.shiftKey);return null!==n&&(e.preventDefault(),t.dispatchCommand(n,void 0),!0)},T),t.registerCommand(O,()=>!!St(y())&&(P([a()]),!0),T),t.registerCommand(A,()=>bt(A),T),t.registerCommand(H,()=>bt(H,e),T),t.registerCommand(w,t=>{const e=y();if(!b(e))return!1;const{anchor:n}=e,r=n.getNode();return!!St(e)&&(e.isCollapsed()&&0===n.offset&&null===r.getPreviousSibling()&&st(r.getParentOrThrow())?(t.preventDefault(),!0):Tt(w,t))},T),t.registerCommand(D,t=>{const e=y();if(!b(e))return!1;const{anchor:n}=e,r=n.getNode();return!!St(e)&&(e.isCollapsed()&&n.offset===r.getTextContentSize()&&null===r.getNextSibling()&&st(r.getParentOrThrow())?(t.preventDefault(),!0):Tt(D,t))},T),t.registerCommand(L,t=>vt(L,t),T),t.registerCommand(k,t=>vt(k,t),T))}const Ct=_({build:(t,e)=>R(e),config:v({disabled:!1,tabSize:void 0}),dependencies:[_t],name:"@lexical/code-indent",register:(t,e,n)=>{const r=n.getOutput();return I(()=>{if(!r.disabled.value)return Nt(t,r.tabSize.value)})}});export{mt as $createCodeHighlightNode,ot as $createCodeNode,q as $getCodeLineDirection,Q as $getEndOfCodeInLine,U as $getFirstCodeNodeOfLine,$ as $getLastCodeNodeOfLine,G as $getStartOfCodeInLine,xt as $isCodeHighlightNode,st as $isCodeNode,V as $outdentLeadingSpaces,_t as CodeExtension,pt as CodeHighlightNode,Ct as CodeIndentExtension,it as CodeNode,X as DEFAULT_CODE_LANGUAGE,Y as getDefaultCodeLanguage,Nt as registerCodeIndentation};
package/index.d.ts CHANGED
@@ -7,6 +7,7 @@
7
7
  */
8
8
  export { CodeExtension } from './CodeExtension';
9
9
  export { $createCodeHighlightNode, $isCodeHighlightNode, CodeHighlightNode, } from './CodeHighlightNode';
10
+ export { type CodeIndentConfig, CodeIndentExtension, registerCodeIndentation, } from './CodeIndentation';
10
11
  export type { SerializedCodeNode } from './CodeNode';
11
12
  export { $createCodeNode, $isCodeNode, CodeNode, DEFAULT_CODE_LANGUAGE, getDefaultCodeLanguage, } from './CodeNode';
12
- export { $getCodeLineDirection, $getEndOfCodeInLine, $getFirstCodeNodeOfLine, $getLastCodeNodeOfLine, $getStartOfCodeInLine, } from './FlatStructureUtils';
13
+ export { $getCodeLineDirection, $getEndOfCodeInLine, $getFirstCodeNodeOfLine, $getLastCodeNodeOfLine, $getStartOfCodeInLine, $outdentLeadingSpaces, } from './FlatStructureUtils';
package/package.json CHANGED
@@ -8,12 +8,12 @@
8
8
  "code"
9
9
  ],
10
10
  "license": "MIT",
11
- "version": "0.44.1-nightly.20260430.0",
11
+ "version": "0.44.1-nightly.20260504.0",
12
12
  "main": "LexicalCodeCore.js",
13
13
  "types": "index.d.ts",
14
14
  "dependencies": {
15
- "@lexical/extension": "0.44.1-nightly.20260430.0",
16
- "lexical": "0.44.1-nightly.20260430.0"
15
+ "@lexical/extension": "0.44.1-nightly.20260504.0",
16
+ "lexical": "0.44.1-nightly.20260504.0"
17
17
  },
18
18
  "repository": {
19
19
  "type": "git",