@lobehub/editor 4.23.0 → 4.23.2

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.
Files changed (52) hide show
  1. package/es/editor-kernel/index.js +1 -0
  2. package/es/editor-kernel/kernel.js +47 -40
  3. package/es/editor-kernel/react/useLexicalNodeSelection.js +4 -2
  4. package/es/headless.js +201 -175
  5. package/es/index.js +5 -2
  6. package/es/plugins/auto-complete/plugin/index.js +4 -2
  7. package/es/plugins/block/react/ReactBlockPlugin.js +5 -4
  8. package/es/plugins/codeblock/command/index.js +8 -6
  9. package/es/plugins/codeblock/plugin/index.js +4 -2
  10. package/es/plugins/codeblock/react/ReactCodeblockPlugin.js +2 -1
  11. package/es/plugins/codemirror-block/command/index.js +8 -6
  12. package/es/plugins/common/data-source/json-data-source.js +5 -4
  13. package/es/plugins/common/plugin/index.js +1 -3
  14. package/es/plugins/common/plugin/paste-handler.js +2 -1
  15. package/es/plugins/hr/command/index.js +2 -1
  16. package/es/plugins/image/react/components/BrokenImage.js +2 -1
  17. package/es/plugins/image/react/components/Image.js +18 -15
  18. package/es/plugins/image/react/components/ImageEditPopover.js +42 -41
  19. package/es/plugins/image/react/components/ResizeHandle.js +1 -3
  20. package/es/plugins/link/node/LinkNode.js +4 -2
  21. package/es/plugins/link/plugin/registry.js +17 -12
  22. package/es/plugins/link/react/components/LinkEdit.js +9 -8
  23. package/es/plugins/list/plugin/index.js +1 -4
  24. package/es/plugins/list/utils/index.js +4 -2
  25. package/es/plugins/litexml/command/index.js +69 -62
  26. package/es/plugins/litexml/node/DiffNode.js +14 -10
  27. package/es/plugins/litexml/plugin/index.js +6 -12
  28. package/es/plugins/markdown/data-source/markdown-data-source.js +5 -4
  29. package/es/plugins/markdown/plugin/index.js +2 -1
  30. package/es/plugins/markdown/service/transformers.js +4 -2
  31. package/es/plugins/markdown/utils/index.js +10 -8
  32. package/es/plugins/math/command/index.js +4 -2
  33. package/es/plugins/math/react/components/MathInline.js +14 -13
  34. package/es/plugins/math/react/style.js +2 -2
  35. package/es/plugins/slash/plugin/index.js +13 -4
  36. package/es/plugins/slash/react/components/DefaultSlashMenu.js +2 -1
  37. package/es/plugins/slash/react/components/menuLoading.js +4 -0
  38. package/es/plugins/slash/service/i-slash-service.d.ts +6 -1
  39. package/es/plugins/slash/service/i-slash-service.js +16 -2
  40. package/es/plugins/slash/utils/utils.js +1 -1
  41. package/es/plugins/table/react/TableActionMenu/ActionMenu.js +69 -67
  42. package/es/plugins/table/react/TableColController.js +29 -26
  43. package/es/plugins/table/react/TableHoverActions/index.js +0 -2
  44. package/es/plugins/table/react/TableRowController.js +29 -26
  45. package/es/plugins/table/utils/index.js +4 -2
  46. package/es/react/Editor/Editor.js +24 -23
  47. package/es/react/hooks/useEditorState/index.js +28 -21
  48. package/es/react/hooks/useEditorState/utils.js +2 -1
  49. package/es/renderer/engine/render-builtin-node.js +26 -18
  50. package/es/renderer/engine/render-tree.js +4 -1
  51. package/es/renderer/renderers/mermaid.js +2 -1
  52. package/package.json +1 -1
@@ -70,12 +70,9 @@ const LitexmlPlugin = class extends KernelPlugin {
70
70
  nodeToXML(diffNode.getChildAtIndex(0), lines, indent);
71
71
  break;
72
72
  case "remove": break;
73
- case "listItemModify":
74
- diffNode.getChildAtIndex(1).getChildren().forEach((child) => {
75
- nodeToXML(child, lines, indent);
76
- });
77
- break;
78
- default: break;
73
+ case "listItemModify": diffNode.getChildAtIndex(1).getChildren().forEach((child) => {
74
+ nodeToXML(child, lines, indent);
75
+ });
79
76
  }
80
77
  return { lines };
81
78
  });
@@ -91,12 +88,9 @@ const LitexmlPlugin = class extends KernelPlugin {
91
88
  ctx.processChild(ctx, diffNode.getChildAtIndex(0));
92
89
  break;
93
90
  case "remove": break;
94
- case "listItemModify":
95
- diffNode.getChildAtIndex(1).getChildren().forEach((child) => {
96
- ctx.processChild(ctx, child);
97
- });
98
- break;
99
- default: break;
91
+ case "listItemModify": diffNode.getChildAtIndex(1).getChildren().forEach((child) => {
92
+ ctx.processChild(ctx, child);
93
+ });
100
94
  }
101
95
  return true;
102
96
  });
@@ -83,10 +83,11 @@ var MarkdownDataSource = class extends DataSource {
83
83
  ...node.exportJSON(),
84
84
  $key: node.getKey()
85
85
  };
86
- if (node === firstNode) if (node === lastNode) {
87
- if (anchor.type !== "element" || focus.type !== "element" || focus.offset === anchor.offset) sNode.text = anchorOffset < focusOffset ? sNode.text.slice(anchorOffset, focusOffset) : sNode.text.slice(focusOffset, anchorOffset);
88
- } else sNode.text = isBefore ? sNode.text.slice(anchorOffset) : sNode.text.slice(focusOffset);
89
- else if (node === lastNode) sNode.text = isBefore ? sNode.text.slice(0, focusOffset) : sNode.text.slice(0, anchorOffset);
86
+ if (node === firstNode) {
87
+ if (node === lastNode) {
88
+ if (anchor.type !== "element" || focus.type !== "element" || focus.offset === anchor.offset) sNode.text = anchorOffset < focusOffset ? sNode.text.slice(anchorOffset, focusOffset) : sNode.text.slice(focusOffset, anchorOffset);
89
+ } else sNode.text = isBefore ? sNode.text.slice(anchorOffset) : sNode.text.slice(focusOffset);
90
+ } else if (node === lastNode) sNode.text = isBefore ? sNode.text.slice(0, focusOffset) : sNode.text.slice(0, anchorOffset);
90
91
  let hasPush = false;
91
92
  for (let i = lastElement.length - 1; i >= 0; i--) if ($getNodeByKey(lastElement[i].$key)?.isParentOf(node)) {
92
93
  lastElement[i].children.push(sNode);
@@ -212,7 +212,8 @@ const MarkdownPlugin = class extends KernelPlugin {
212
212
  const markdown = this.markdownDataSource.write(editor, { selection: true });
213
213
  if (!markdown) return false;
214
214
  event.preventDefault();
215
- setLexicalClipboardDataTransfer(clipboardData, $getClipboardDataFromSelection(selection));
215
+ const data = $getClipboardDataFromSelection(selection);
216
+ setLexicalClipboardDataTransfer(clipboardData, data);
216
217
  clipboardData.setData("text/plain", markdown);
217
218
  clipboardData.setData("text/markdown", markdown);
218
219
  this.logger.debug("copy with text/markdown:", { markdownLength: markdown.length });
@@ -2,7 +2,8 @@ import { PUNCTUATION_OR_SPACE, getOpenTagStartIndex, isEqualSubString } from "..
2
2
  import { $createRangeSelection, $getNodeByKey, $getSelection, $isLineBreakNode, $isRangeSelection, $isRootOrShadowRoot, $isTextNode, $setSelection } from "lexical";
3
3
  //#region src/plugins/markdown/service/transformers.ts
4
4
  function testElementTransformers(parentNode, anchorNode, anchorOffset, elementTransformers, fromTrigger) {
5
- if (!$isRootOrShadowRoot(parentNode.getParent()) || parentNode.getFirstChild() !== anchorNode) return false;
5
+ const grandParentNode = parentNode.getParent();
6
+ if (!$isRootOrShadowRoot(grandParentNode) || parentNode.getFirstChild() !== anchorNode) return false;
6
7
  const textContent = anchorNode.getTextContent();
7
8
  if (fromTrigger !== "enter" && textContent[anchorOffset - 1] !== " ") return false;
8
9
  for (const { regExp, trigger } of elementTransformers) {
@@ -12,7 +13,8 @@ function testElementTransformers(parentNode, anchorNode, anchorOffset, elementTr
12
13
  return false;
13
14
  }
14
15
  function runElementTransformers(parentNode, anchorNode, anchorOffset, elementTransformers, fromTrigger) {
15
- if (!$isRootOrShadowRoot(parentNode.getParent()) || parentNode.getFirstChild() !== anchorNode) return false;
16
+ const grandParentNode = parentNode.getParent();
17
+ if (!$isRootOrShadowRoot(grandParentNode) || parentNode.getFirstChild() !== anchorNode) return false;
16
18
  const textContent = anchorNode.getTextContent();
17
19
  if (fromTrigger !== "enter" && textContent[anchorOffset - 1] !== " ") return false;
18
20
  for (const { regExp, replace, trigger } of elementTransformers) {
@@ -34,7 +34,7 @@ function indexBy(list, callback) {
34
34
  }
35
35
  let Punctuation = /[!"#$%&'()*+,./:;<=>?@[\\\]^_`{|}~\u00A1\u2010-\u2027-]/;
36
36
  try {
37
- Punctuation = /* @__PURE__ */ new RegExp("[\\p{Pc}|\\p{Pd}|\\p{Pe}|\\p{Pf}|\\p{Pi}|\\p{Po}|\\p{Ps}]", "u");
37
+ Punctuation = new RegExp("[\\p{Pc}|\\p{Pd}|\\p{Pe}|\\p{Pf}|\\p{Pi}|\\p{Po}|\\p{Ps}]", "u");
38
38
  } catch {}
39
39
  /**
40
40
  * Checks if a character is a punctuation character.
@@ -49,13 +49,15 @@ function $updateSelectionOnInsert(selection) {
49
49
  const anchor = selection.anchor;
50
50
  let nodeToInspect = null;
51
51
  const anchorCaret = $caretFromPoint(anchor, "previous");
52
- if (anchorCaret) if ($isTextPointCaret(anchorCaret)) nodeToInspect = anchorCaret.origin;
53
- else {
54
- const range = $getCaretRange(anchorCaret, $getChildCaret($getRoot(), "next").getFlipped());
55
- for (const caret of range) if ($isTextNode(caret.origin)) {
56
- nodeToInspect = caret.origin;
57
- break;
58
- } else if ($isElementNode(caret.origin) && !caret.origin.isInline()) break;
52
+ if (anchorCaret) {
53
+ if ($isTextPointCaret(anchorCaret)) nodeToInspect = anchorCaret.origin;
54
+ else {
55
+ const range = $getCaretRange(anchorCaret, $getChildCaret($getRoot(), "next").getFlipped());
56
+ for (const caret of range) if ($isTextNode(caret.origin)) {
57
+ nodeToInspect = caret.origin;
58
+ break;
59
+ } else if ($isElementNode(caret.origin) && !caret.origin.isInline()) break;
60
+ }
59
61
  }
60
62
  if (nodeToInspect && $isTextNode(nodeToInspect)) {
61
63
  const newFormat = nodeToInspect.getFormat();
@@ -28,8 +28,10 @@ function registerMathCommand(editor) {
28
28
  const { key, prev } = payload;
29
29
  editor.update(() => {
30
30
  const mathNode = $getNodeByKey(key);
31
- if (mathNode) if (prev) mathNode.selectPrevious();
32
- else mathNode.selectNext();
31
+ if (mathNode) {
32
+ if (prev) mathNode.selectPrevious();
33
+ else mathNode.selectNext();
34
+ }
33
35
  });
34
36
  return true;
35
37
  }, COMMAND_PRIORITY_HIGH));
@@ -63,21 +63,22 @@ const MathInline = ({ editor, node, className }) => {
63
63
  });
64
64
  });
65
65
  }, [node]);
66
+ const handleClick = useCallback((e) => {
67
+ logger.debug("📊 Math click event:", e.target === ref.current);
68
+ e.preventDefault();
69
+ e.stopPropagation();
70
+ if (isSelected) clearSelected();
71
+ setIsSelected(true);
72
+ editor.focus();
73
+ }, [
74
+ clearSelected,
75
+ editor,
76
+ isSelected,
77
+ setIsSelected
78
+ ]);
66
79
  return /* @__PURE__ */ jsx("span", {
67
80
  className,
68
- onClick: useCallback((e) => {
69
- logger.debug("📊 Math click event:", e.target === ref.current);
70
- e.preventDefault();
71
- e.stopPropagation();
72
- if (isSelected) clearSelected();
73
- setIsSelected(true);
74
- editor.focus();
75
- }, [
76
- clearSelected,
77
- editor,
78
- isSelected,
79
- setIsSelected
80
- ]),
81
+ onClick: handleClick,
81
82
  ref,
82
83
  children: node.code ? node.code : /* @__PURE__ */ jsx(Placeholder, { mathBlock: isMathBlock })
83
84
  });
@@ -14,12 +14,12 @@ const styles = createStaticStyles(({ css, cssVar }) => {
14
14
  overflow: auto hidden;
15
15
  padding: 3px;
16
16
 
17
- .base {
17
+ .katex-base {
18
18
  margin-block: 0;
19
19
  margin-inline: auto;
20
20
  }
21
21
 
22
- .tag {
22
+ .katex-tag {
23
23
  position: relative !important;
24
24
  display: inline-block;
25
25
  padding-inline-start: 0.5rem;
@@ -109,7 +109,7 @@ const SlashPlugin = class extends KernelPlugin {
109
109
  const range = editorWindow.document.createRange();
110
110
  const selection = $getSelection();
111
111
  const text = getQueryTextForSearch(editor);
112
- if (!$isRangeSelection(selection) || !selection.isCollapsed() || selection.hasFormat("code") || text === null || range === null || this.currentSlashTrigger === null && text.length > 1 && text.at(-2) !== " ") {
112
+ if (!$isRangeSelection(selection) || !selection.isCollapsed() || selection.hasFormat("code") || text === null || range === null) {
113
113
  this.triggerClose();
114
114
  return;
115
115
  }
@@ -119,8 +119,13 @@ const SlashPlugin = class extends KernelPlugin {
119
119
  }
120
120
  let triggerText = this.currentSlashTrigger;
121
121
  if (triggerText === null) {
122
- triggerText = text.slice(-1);
123
- this.currentSlashTriggerIndex = text.length - 1;
122
+ const resolution = this.service?.resolveTrigger(text);
123
+ if (!resolution) {
124
+ this.triggerClose();
125
+ return;
126
+ }
127
+ triggerText = resolution.trigger;
128
+ this.currentSlashTriggerIndex = resolution.leadOffset;
124
129
  }
125
130
  const lastIndex = text.lastIndexOf(triggerText);
126
131
  if (lastIndex < this.currentSlashTriggerIndex) {
@@ -137,11 +142,15 @@ const SlashPlugin = class extends KernelPlugin {
137
142
  const fuse = this.service?.getSlashFuse(triggerText);
138
143
  const isRangePositioned = tryToPositionRange(this.currentSlashTriggerIndex, range, editorWindow);
139
144
  const match = triggerFn?.(text.slice(this.currentSlashTriggerIndex));
145
+ if (!match) {
146
+ this.triggerClose();
147
+ return;
148
+ }
140
149
  if (text.slice(this.currentSlashTriggerIndex).includes(" ") && !slashOptions?.allowWhitespace) {
141
150
  this.triggerClose();
142
151
  return;
143
152
  }
144
- const finalItems = fuse && match && match.matchingString.length > 0 ? fuse.search(match.matchingString).map((result) => result.item) : slashOptions.items;
153
+ const finalItems = fuse && match.matchingString.length > 0 ? fuse.search(match.matchingString).map((result) => result.item) : slashOptions.items;
145
154
  if (isRangePositioned !== null && finalItems.length > 0) {
146
155
  this.currentSlashTrigger = triggerText;
147
156
  this.config?.triggerOpen({
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { flattenSlashOptions, isSlashDividerOption, isSlashSectionOption } from "../../service/i-slash-service.js";
3
+ import { shouldShowLoadingPlaceholder } from "./menuLoading.js";
3
4
  import { Icon, LOBE_THEME_APP_ID, menuSharedStyles } from "@lobehub/ui";
4
5
  import { useEffect, useLayoutEffect, useMemo, useRef } from "react";
5
6
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -121,7 +122,7 @@ const renderMenuItem = (item, activeKey, onSelect) => {
121
122
  }, String(item.key));
122
123
  };
123
124
  const renderItems = (options, activeKey, loading, onSelect) => {
124
- if (loading) return /* @__PURE__ */ jsx("div", {
125
+ if (shouldShowLoadingPlaceholder(loading, options)) return /* @__PURE__ */ jsx("div", {
125
126
  className: menuSharedStyles.empty,
126
127
  children: "Loading..."
127
128
  });
@@ -0,0 +1,4 @@
1
+ //#region src/plugins/slash/react/components/menuLoading.ts
2
+ const shouldShowLoadingPlaceholder = (loading, options) => Boolean(loading && options.length === 0);
3
+ //#endregion
4
+ export { shouldShowLoadingPlaceholder };
@@ -53,10 +53,15 @@ interface SlashOptions {
53
53
  searchKeys?: string[];
54
54
  trigger: string;
55
55
  }
56
+ interface SlashTriggerResolution {
57
+ leadOffset: number;
58
+ trigger: string;
59
+ }
56
60
  interface ISlashService {
57
61
  registerSlash(options: SlashOptions): void;
62
+ resolveTrigger(text: string): SlashTriggerResolution | null;
58
63
  updateOptions(options: SlashOptions[]): void;
59
64
  }
60
65
  declare const ISlashService: IServiceID<ISlashService>;
61
66
  //#endregion
62
- export { ISlashDividerOption, ISlashMenuOption, ISlashOption, ISlashSectionOption, ISlashService, SlashOptions, flattenSlashOptions, isSlashDividerOption, isSlashMenuOption, isSlashSectionOption };
67
+ export { ISlashDividerOption, ISlashMenuOption, ISlashOption, ISlashSectionOption, ISlashService, SlashOptions, SlashTriggerResolution, flattenSlashOptions, isSlashDividerOption, isSlashMenuOption, isSlashSectionOption };
@@ -23,8 +23,10 @@ var SlashService = class {
23
23
  this.logger = createDebugLogger("service", "slash");
24
24
  }
25
25
  registerSlash(options, update = false) {
26
- if (this.triggerMap.has(options.trigger) && !update) if (this.kernel.isHotReloadMode()) this.logger.warn(`🔄 Overriding slash trigger "${options.trigger}"`);
27
- else throw new Error(`Slash trigger "${options.trigger}" is already registered.`);
26
+ if (this.triggerMap.has(options.trigger) && !update) {
27
+ if (this.kernel.isHotReloadMode()) this.logger.warn(`🔄 Overriding slash trigger "${options.trigger}"`);
28
+ else throw new Error(`Slash trigger "${options.trigger}" is already registered.`);
29
+ }
28
30
  this.triggerMap.set(options.trigger, options);
29
31
  this.triggerFnMap.set(options.trigger, getBasicTypeaheadTriggerMatch(options.trigger, {
30
32
  allowWhitespace: options.allowWhitespace,
@@ -44,6 +46,18 @@ var SlashService = class {
44
46
  this.triggerFnMap.clear();
45
47
  this.triggerFuseMap.clear();
46
48
  }
49
+ resolveTrigger(text) {
50
+ let resolution = null;
51
+ for (const [trigger, triggerFn] of this.triggerFnMap) {
52
+ const match = triggerFn(text);
53
+ if (!match || resolution && match.leadOffset <= resolution.leadOffset) continue;
54
+ resolution = {
55
+ leadOffset: match.leadOffset,
56
+ trigger
57
+ };
58
+ }
59
+ return resolution;
60
+ }
47
61
  updateOptions(options) {
48
62
  this.removeAllOptions();
49
63
  options.forEach((option) => {
@@ -44,7 +44,7 @@ function tryToPositionRange(leadOffset, range, editorWindow) {
44
44
  }
45
45
  return true;
46
46
  }
47
- const PUNCTUATION = "\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%'\"~=<>_:;";
47
+ const PUNCTUATION = "\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\!%'\"~=<>_:;";
48
48
  function getBasicTypeaheadTriggerMatch(trigger, { minLength = 0, maxLength = 75, punctuation = PUNCTUATION, allowWhitespace = false }) {
49
49
  return (text) => {
50
50
  const validCharsSuffix = allowWhitespace ? "" : "\\s";
@@ -134,7 +134,8 @@ const TableActionMenu = memo(({ editor, tableCellNode: _tableCellNode, cellMerge
134
134
  editor.update(() => {
135
135
  const selection = $getSelection();
136
136
  if (!$isTableSelection(selection)) return;
137
- const targetCell = $mergeCells(selection.getNodes().filter($isTableCellNode));
137
+ const tableCells = selection.getNodes().filter($isTableCellNode);
138
+ const targetCell = $mergeCells(tableCells);
138
139
  if (targetCell) $selectLastDescendant(targetCell);
139
140
  });
140
141
  };
@@ -221,73 +222,74 @@ const TableActionMenu = memo(({ editor, tableCellNode: _tableCellNode, cellMerge
221
222
  tableCellNode,
222
223
  clearTableSelection
223
224
  ]);
225
+ const menuItems = useMemo(() => {
226
+ return [
227
+ {
228
+ icon: PanelTopCloseIcon,
229
+ key: "table-insert-row-above",
230
+ label: t(`table.insertRowAbove`, { count: selectionCounts.rows }),
231
+ onClick: () => insertTableRowAtSelection(false)
232
+ },
233
+ {
234
+ icon: PanelBottomCloseIcon,
235
+ key: "table-insert-row-below",
236
+ label: t(`table.insertRowBelow`, { count: selectionCounts.rows }),
237
+ onClick: () => insertTableRowAtSelection(true)
238
+ },
239
+ { type: "divider" },
240
+ {
241
+ icon: PanelLeftCloseIcon,
242
+ key: "table-insert-column-before",
243
+ label: t(`table.insertColumnLeft`, { count: selectionCounts.columns }),
244
+ onClick: () => insertTableColumnAtSelection(false)
245
+ },
246
+ {
247
+ icon: PanelRightCloseIcon,
248
+ key: "table-insert-column-after",
249
+ label: t(`table.insertColumnRight`, { count: selectionCounts.columns }),
250
+ onClick: () => insertTableColumnAtSelection(true)
251
+ },
252
+ { type: "divider" },
253
+ {
254
+ icon: TableColumnsSplitIcon,
255
+ key: "table-delete-columns",
256
+ label: renderDeleteMenuLabel(t(`table.deleteColumn`), "columns"),
257
+ onClick: () => deleteTableColumnAtSelection()
258
+ },
259
+ {
260
+ icon: TableRowsSplitIcon,
261
+ key: "table-delete-rows",
262
+ label: renderDeleteMenuLabel(t(`table.deleteRow`), "rows"),
263
+ onClick: () => deleteTableRowAtSelection()
264
+ },
265
+ { type: "divider" },
266
+ {
267
+ icon: Grid2X2XIcon,
268
+ key: "table-delete",
269
+ label: renderDeleteMenuLabel(t(`table.delete`), "table"),
270
+ onClick: () => deleteTableAtSelection()
271
+ }
272
+ ];
273
+ }, [
274
+ cellMerge,
275
+ canMergeCells,
276
+ canUnmergeCell,
277
+ selectionCounts.rows,
278
+ selectionCounts.columns,
279
+ tableCellNode.__headerState,
280
+ mergeTableCellsAtSelection,
281
+ unmergeTableCellsAtSelection,
282
+ insertTableRowAtSelection,
283
+ insertTableColumnAtSelection,
284
+ deleteTableColumnAtSelection,
285
+ deleteTableRowAtSelection,
286
+ deleteTableAtSelection,
287
+ renderDeleteMenuLabel,
288
+ toggleTableRowIsHeader,
289
+ toggleTableColumnIsHeader
290
+ ]);
224
291
  return /* @__PURE__ */ jsx(DropdownMenu, {
225
- items: useMemo(() => {
226
- return [
227
- {
228
- icon: PanelTopCloseIcon,
229
- key: "table-insert-row-above",
230
- label: t(`table.insertRowAbove`, { count: selectionCounts.rows }),
231
- onClick: () => insertTableRowAtSelection(false)
232
- },
233
- {
234
- icon: PanelBottomCloseIcon,
235
- key: "table-insert-row-below",
236
- label: t(`table.insertRowBelow`, { count: selectionCounts.rows }),
237
- onClick: () => insertTableRowAtSelection(true)
238
- },
239
- { type: "divider" },
240
- {
241
- icon: PanelLeftCloseIcon,
242
- key: "table-insert-column-before",
243
- label: t(`table.insertColumnLeft`, { count: selectionCounts.columns }),
244
- onClick: () => insertTableColumnAtSelection(false)
245
- },
246
- {
247
- icon: PanelRightCloseIcon,
248
- key: "table-insert-column-after",
249
- label: t(`table.insertColumnRight`, { count: selectionCounts.columns }),
250
- onClick: () => insertTableColumnAtSelection(true)
251
- },
252
- { type: "divider" },
253
- {
254
- icon: TableColumnsSplitIcon,
255
- key: "table-delete-columns",
256
- label: renderDeleteMenuLabel(t(`table.deleteColumn`), "columns"),
257
- onClick: () => deleteTableColumnAtSelection()
258
- },
259
- {
260
- icon: TableRowsSplitIcon,
261
- key: "table-delete-rows",
262
- label: renderDeleteMenuLabel(t(`table.deleteRow`), "rows"),
263
- onClick: () => deleteTableRowAtSelection()
264
- },
265
- { type: "divider" },
266
- {
267
- icon: Grid2X2XIcon,
268
- key: "table-delete",
269
- label: renderDeleteMenuLabel(t(`table.delete`), "table"),
270
- onClick: () => deleteTableAtSelection()
271
- }
272
- ];
273
- }, [
274
- cellMerge,
275
- canMergeCells,
276
- canUnmergeCell,
277
- selectionCounts.rows,
278
- selectionCounts.columns,
279
- tableCellNode.__headerState,
280
- mergeTableCellsAtSelection,
281
- unmergeTableCellsAtSelection,
282
- insertTableRowAtSelection,
283
- insertTableColumnAtSelection,
284
- deleteTableColumnAtSelection,
285
- deleteTableRowAtSelection,
286
- deleteTableAtSelection,
287
- renderDeleteMenuLabel,
288
- toggleTableRowIsHeader,
289
- toggleTableColumnIsHeader
290
- ]),
292
+ items: menuItems,
291
293
  children
292
294
  });
293
295
  });
@@ -47,7 +47,8 @@ const TableColController = memo((props) => {
47
47
  clearDeletePreview();
48
48
  if (!showMenu) return;
49
49
  editor.getEditorState().read(() => {
50
- const [tableMap] = $computeTableMapSkipCellCheck(node.getLatest(), null, null);
50
+ const tableNode = node.getLatest();
51
+ const [tableMap] = $computeTableMapSkipCellCheck(tableNode, null, null);
51
52
  const previewCellKeys = /* @__PURE__ */ new Set();
52
53
  for (const row of tableMap) for (const columnIndex of selectedColumns) {
53
54
  const cell = row[columnIndex]?.cell;
@@ -110,32 +111,34 @@ const TableColController = memo((props) => {
110
111
  }
111
112
  }, INSERT_BUTTON_HIDE_DELAY);
112
113
  };
113
- const { clearDragState, dragTarget, finishDrag: finishColumnDrag, isDragging, startDrag } = useTableAxisDrag({
114
- getDropTarget: useCallback((event) => {
115
- const controller = colTopRef.current;
116
- if (!controller) return null;
117
- const columns = Array.from(controller.querySelectorAll(".col"));
118
- if (columns.length === 0) return null;
119
- for (const [index, column] of columns.entries()) {
120
- const rect = column.getBoundingClientRect();
121
- if (event.clientX <= rect.left + rect.width / 2) return {
122
- index,
123
- insertAfter: false
124
- };
125
- }
126
- return {
127
- index: columns.length - 1,
128
- insertAfter: true
114
+ const getColumnDropTarget = useCallback((event) => {
115
+ const controller = colTopRef.current;
116
+ if (!controller) return null;
117
+ const columns = Array.from(controller.querySelectorAll(".col"));
118
+ if (columns.length === 0) return null;
119
+ for (const [index, column] of columns.entries()) {
120
+ const rect = column.getBoundingClientRect();
121
+ if (event.clientX <= rect.left + rect.width / 2) return {
122
+ index,
123
+ insertAfter: false
129
124
  };
130
- }, []),
131
- onMove: useCallback((target, selectedIndexes) => {
132
- editor.dispatchCommand(MOVE_TABLE_COLUMN_COMMAND, {
133
- columnIndex: target.index,
134
- insertAfter: target.insertAfter,
135
- selectedColumns: selectedIndexes,
136
- table: node.getKey()
137
- });
138
- }, [editor, node])
125
+ }
126
+ return {
127
+ index: columns.length - 1,
128
+ insertAfter: true
129
+ };
130
+ }, []);
131
+ const moveColumns = useCallback((target, selectedIndexes) => {
132
+ editor.dispatchCommand(MOVE_TABLE_COLUMN_COMMAND, {
133
+ columnIndex: target.index,
134
+ insertAfter: target.insertAfter,
135
+ selectedColumns: selectedIndexes,
136
+ table: node.getKey()
137
+ });
138
+ }, [editor, node]);
139
+ const { clearDragState, dragTarget, finishDrag: finishColumnDrag, isDragging, startDrag } = useTableAxisDrag({
140
+ getDropTarget: getColumnDropTarget,
141
+ onMove: moveColumns
139
142
  });
140
143
  const startColumnDrag = useCallback((event, selectedIndexes) => {
141
144
  event.dataTransfer.effectAllowed = "move";
@@ -98,8 +98,6 @@ const TableHoverActions = memo(({ editor }) => {
98
98
  case "destroyed":
99
99
  tableSetRef.current.delete(key);
100
100
  resetObserver = true;
101
- break;
102
- default: break;
103
101
  }
104
102
  if (resetObserver) {
105
103
  tableResizeObserver.disconnect();
@@ -47,7 +47,8 @@ const TableRowController = memo((props) => {
47
47
  clearDeletePreview();
48
48
  if (!showMenu) return;
49
49
  editor.getEditorState().read(() => {
50
- const [tableMap] = $computeTableMapSkipCellCheck(node.getLatest(), null, null);
50
+ const tableNode = node.getLatest();
51
+ const [tableMap] = $computeTableMapSkipCellCheck(tableNode, null, null);
51
52
  const previewCellKeys = /* @__PURE__ */ new Set();
52
53
  for (const rowIndex of selectedRows) {
53
54
  const row = tableMap[rowIndex];
@@ -108,32 +109,34 @@ const TableRowController = memo((props) => {
108
109
  if (!insertButtonHoveredRef.current) setInsertTarget(null);
109
110
  }, INSERT_BUTTON_HIDE_DELAY);
110
111
  };
111
- const { clearDragState, dragTarget, finishDrag: finishRowDrag, isDragging, startDrag } = useTableAxisDrag({
112
- getDropTarget: useCallback((event) => {
113
- const controller = rowLeftRef.current;
114
- if (!controller) return null;
115
- const rows = Array.from(controller.querySelectorAll(".row"));
116
- if (rows.length === 0) return null;
117
- for (const [index, row] of rows.entries()) {
118
- const rect = row.getBoundingClientRect();
119
- if (event.clientY <= rect.top + rect.height / 2) return {
120
- index,
121
- insertAfter: false
122
- };
123
- }
124
- return {
125
- index: rows.length - 1,
126
- insertAfter: true
112
+ const getRowDropTarget = useCallback((event) => {
113
+ const controller = rowLeftRef.current;
114
+ if (!controller) return null;
115
+ const rows = Array.from(controller.querySelectorAll(".row"));
116
+ if (rows.length === 0) return null;
117
+ for (const [index, row] of rows.entries()) {
118
+ const rect = row.getBoundingClientRect();
119
+ if (event.clientY <= rect.top + rect.height / 2) return {
120
+ index,
121
+ insertAfter: false
127
122
  };
128
- }, []),
129
- onMove: useCallback((target, selectedIndexes) => {
130
- editor.dispatchCommand(MOVE_TABLE_ROW_COMMAND, {
131
- insertAfter: target.insertAfter,
132
- rowIndex: target.index,
133
- selectedRows: selectedIndexes,
134
- table: node.getKey()
135
- });
136
- }, [editor, node])
123
+ }
124
+ return {
125
+ index: rows.length - 1,
126
+ insertAfter: true
127
+ };
128
+ }, []);
129
+ const moveRows = useCallback((target, selectedIndexes) => {
130
+ editor.dispatchCommand(MOVE_TABLE_ROW_COMMAND, {
131
+ insertAfter: target.insertAfter,
132
+ rowIndex: target.index,
133
+ selectedRows: selectedIndexes,
134
+ table: node.getKey()
135
+ });
136
+ }, [editor, node]);
137
+ const { clearDragState, dragTarget, finishDrag: finishRowDrag, isDragging, startDrag } = useTableAxisDrag({
138
+ getDropTarget: getRowDropTarget,
139
+ onMove: moveRows
137
140
  });
138
141
  const startRowDrag = useCallback((event, selectedIndexes) => {
139
142
  event.dataTransfer.effectAllowed = "move";
@@ -63,12 +63,14 @@ function $forEachTableCell(grid, cb) {
63
63
  function $addHighlightToDOM(editor, cell) {
64
64
  const element = cell.elem;
65
65
  const editorThemeClasses = editor._config.theme;
66
- assert($isTableCellNode($getNearestNodeFromDOMNode(element)), "Expected to find LexicalNode from Table Cell DOMNode");
66
+ const node = $getNearestNodeFromDOMNode(element);
67
+ assert($isTableCellNode(node), "Expected to find LexicalNode from Table Cell DOMNode");
67
68
  addClassNamesToElement(element, editorThemeClasses.tableCellSelected);
68
69
  }
69
70
  function $removeHighlightFromDOM(editor, cell) {
70
71
  const element = cell.elem;
71
- assert($isTableCellNode($getNearestNodeFromDOMNode(element)), "Expected to find LexicalNode from Table Cell DOMNode");
72
+ const node = $getNearestNodeFromDOMNode(element);
73
+ assert($isTableCellNode(node), "Expected to find LexicalNode from Table Cell DOMNode");
72
74
  const editorThemeClasses = editor._config.theme;
73
75
  removeClassNamesFromElement(element, editorThemeClasses.tableCellSelected);
74
76
  }