@lobehub/editor 4.15.2 → 4.16.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.
Files changed (52) hide show
  1. package/es/editor-kernel/react/useDecorators.js +14 -8
  2. package/es/headless.d.ts +2 -0
  3. package/es/headless.js +710 -46
  4. package/es/index.d.ts +4 -3
  5. package/es/index.js +4 -3
  6. package/es/locale/index.d.ts +2 -0
  7. package/es/locale/index.js +5 -1
  8. package/es/plugins/auto-complete/plugin/index.js +3 -3
  9. package/es/plugins/block/index.d.ts +1 -1
  10. package/es/plugins/block/plugin/index.js +78 -2
  11. package/es/plugins/block/react/ReactBlockPlugin.js +172 -16
  12. package/es/plugins/block/react/drag/drag-utils.js +37 -10
  13. package/es/plugins/block/service/i-block-menu-service.d.ts +18 -1
  14. package/es/plugins/block/service/i-block-menu-service.js +24 -0
  15. package/es/plugins/block/service/index.d.ts +1 -1
  16. package/es/plugins/codeblock/plugin/index.js +25 -1
  17. package/es/plugins/common/plugin/register.js +2 -2
  18. package/es/plugins/litexml/plugin/index.js +8 -2
  19. package/es/plugins/slash/plugin/index.js +1 -1
  20. package/es/plugins/slash/react/ReactSlashPlugin.js +4 -4
  21. package/es/plugins/table/command/index.d.ts +13 -1
  22. package/es/plugins/table/command/index.js +220 -39
  23. package/es/plugins/table/index.d.ts +3 -2
  24. package/es/plugins/table/node/index.d.ts +2 -0
  25. package/es/plugins/table/node/index.js +130 -2
  26. package/es/plugins/table/plugin/index.d.ts +6 -0
  27. package/es/plugins/table/plugin/index.js +193 -4
  28. package/es/plugins/table/react/TableActionMenu/ActionMenu.js +82 -6
  29. package/es/plugins/table/react/TableActionMenu/index.js +9 -4
  30. package/es/plugins/table/react/TableColController.js +354 -0
  31. package/es/plugins/table/react/TableController/hooks.js +201 -0
  32. package/es/plugins/table/react/TableController/style.js +264 -0
  33. package/es/plugins/table/react/TableController/utils.js +25 -0
  34. package/es/plugins/table/react/TableControllerButton.js +81 -0
  35. package/es/plugins/table/react/TableControllerMenu.js +123 -0
  36. package/es/plugins/table/react/TableInsertButton.js +25 -0
  37. package/es/plugins/table/react/TableResize/index.js +153 -78
  38. package/es/plugins/table/react/TableRowController.js +349 -0
  39. package/es/plugins/table/react/hooks.js +77 -0
  40. package/es/plugins/table/react/index.js +139 -16
  41. package/es/plugins/table/react/style.js +89 -8
  42. package/es/plugins/table/react/type.d.ts +2 -0
  43. package/es/plugins/table/react/useAutoFitPastedTable.js +189 -0
  44. package/es/plugins/table/service/i-table-controller-menu-service.d.ts +44 -0
  45. package/es/plugins/table/service/i-table-controller-menu-service.js +31 -0
  46. package/es/plugins/table/service/index.d.ts +1 -0
  47. package/es/plugins/table/utils/autoFitColumnWidth.js +87 -0
  48. package/es/plugins/table/utils/distributeColumnWidth.js +37 -0
  49. package/es/plugins/table/utils/index.js +102 -2
  50. package/es/react/EditorProvider/index.d.ts +2 -2
  51. package/es/renderer/LexicalDiff.d.ts +2 -2
  52. package/package.json +1 -1
@@ -0,0 +1,354 @@
1
+ import LexicalPortalContainer from "../../../editor-kernel/react/PortalContainer.js";
2
+ import { INSERT_TABLE_COLUMN_COMMAND, MOVE_TABLE_COLUMN_COMMAND, SELECT_TABLE_COMMAND } from "../command/index.js";
3
+ import { sum, useTableAxisDrag, useTableColumnMetrics } from "./TableController/hooks.js";
4
+ import { styles } from "./TableController/style.js";
5
+ import { createTableDragImage } from "./TableController/utils.js";
6
+ import TableControllerMenu from "./TableControllerMenu.js";
7
+ import TableInsertButton from "./TableInsertButton.js";
8
+ import { useTableControllerSelection } from "./hooks.js";
9
+ import { memo, useCallback, useEffect, useRef, useState } from "react";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ import { cx } from "antd-style";
12
+ import { $computeTableMapSkipCellCheck } from "@lexical/table";
13
+ //#region src/plugins/table/react/TableColController.tsx
14
+ const INSERT_BUTTON_HIDE_DELAY = 160;
15
+ const TABLE_DELETE_PREVIEW_CLASS = "lobe-editor-table-delete-preview";
16
+ const DOTS = Array.from({ length: 6 }, (_, index) => index);
17
+ const TableColController = memo((props) => {
18
+ const { blockMenuService, editor, menuService, node, onColumnMetricsChange, onInsertPreviewChange } = props;
19
+ const { colWidths, refreshColumnMetrics, tableHeight } = useTableColumnMetrics(editor, node);
20
+ const { isTableFocused, isTableSelected, selectedColumns } = useTableControllerSelection(editor, node);
21
+ const anchorColumnIndexRef = useRef(null);
22
+ const colTopRef = useRef(null);
23
+ const renderColWidths = Array.from({ length: colWidths.length }, (_, index) => colWidths[index] || 92);
24
+ const deletePreviewElementsRef = useRef([]);
25
+ const insertButtonHoveredRef = useRef(false);
26
+ const insertButtonHideTimerRef = useRef(null);
27
+ const showMenu = selectedColumns.length > 0 && !isTableSelected;
28
+ const [insertTarget, setInsertTarget] = useState(null);
29
+ const [menuAnchorElement, setMenuAnchorElement] = useState(null);
30
+ const [, setMenuVersion] = useState(0);
31
+ const pendingDragColumnsRef = useRef(null);
32
+ const [isControllerHovered, setControllerHovered] = useState(false);
33
+ const [isInsertButtonHovered, setInsertButtonHovered] = useState(false);
34
+ const clearInsertButtonHideTimer = () => {
35
+ if (insertButtonHideTimerRef.current) {
36
+ clearTimeout(insertButtonHideTimerRef.current);
37
+ insertButtonHideTimerRef.current = null;
38
+ }
39
+ };
40
+ const clearDeletePreview = useCallback(() => {
41
+ deletePreviewElementsRef.current.forEach((element) => {
42
+ element.classList.remove(TABLE_DELETE_PREVIEW_CLASS);
43
+ });
44
+ deletePreviewElementsRef.current = [];
45
+ }, []);
46
+ const showDeletePreview = useCallback(() => {
47
+ clearDeletePreview();
48
+ if (!showMenu) return;
49
+ editor.getEditorState().read(() => {
50
+ const [tableMap] = $computeTableMapSkipCellCheck(node.getLatest(), null, null);
51
+ const previewCellKeys = /* @__PURE__ */ new Set();
52
+ for (const row of tableMap) for (const columnIndex of selectedColumns) {
53
+ const cell = row[columnIndex]?.cell;
54
+ if (cell) previewCellKeys.add(cell.getKey());
55
+ }
56
+ for (const cellKey of previewCellKeys) {
57
+ const element = editor.getElementByKey(cellKey);
58
+ if (element instanceof HTMLElement) {
59
+ element.classList.add(TABLE_DELETE_PREVIEW_CLASS);
60
+ deletePreviewElementsRef.current.push(element);
61
+ }
62
+ }
63
+ });
64
+ }, [
65
+ clearDeletePreview,
66
+ editor,
67
+ node,
68
+ selectedColumns,
69
+ showMenu
70
+ ]);
71
+ const closeMenu = useCallback(() => {
72
+ setMenuAnchorElement(null);
73
+ onInsertPreviewChange?.(null);
74
+ clearDeletePreview();
75
+ }, [clearDeletePreview, onInsertPreviewChange]);
76
+ const menuContext = {
77
+ axis: "column",
78
+ editor,
79
+ node,
80
+ selectedIndexes: selectedColumns
81
+ };
82
+ const menuItems = menuService?.getItems(menuContext).map((item) => {
83
+ if (item.type === "separator") return {
84
+ key: item.key,
85
+ type: "separator"
86
+ };
87
+ return {
88
+ danger: item.danger,
89
+ key: item.key,
90
+ label: typeof item.label === "function" ? item.label(menuContext) : item.label,
91
+ onClick: () => {
92
+ item.onClick(menuContext);
93
+ },
94
+ onMouseEnter: item.preview === "delete" ? showDeletePreview : item.preview === "insert-before" ? () => {
95
+ onInsertPreviewChange?.("left");
96
+ } : item.preview === "insert-after" ? () => {
97
+ onInsertPreviewChange?.("right");
98
+ } : void 0,
99
+ onMouseLeave: item.preview === "delete" ? clearDeletePreview : item.preview === "insert-before" || item.preview === "insert-after" ? () => {
100
+ onInsertPreviewChange?.(null);
101
+ } : void 0
102
+ };
103
+ }) ?? [];
104
+ const scheduleHideInsertButton = () => {
105
+ clearInsertButtonHideTimer();
106
+ insertButtonHideTimerRef.current = setTimeout(() => {
107
+ if (!insertButtonHoveredRef.current) {
108
+ setInsertTarget(null);
109
+ setInsertButtonHovered(false);
110
+ }
111
+ }, INSERT_BUTTON_HIDE_DELAY);
112
+ };
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
129
+ };
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])
139
+ });
140
+ const startColumnDrag = useCallback((event, selectedIndexes) => {
141
+ event.dataTransfer.effectAllowed = "move";
142
+ event.dataTransfer.dropEffect = "move";
143
+ event.dataTransfer.setData("text/plain", "");
144
+ const dragImage = createTableDragImage(`拖拽 ${selectedIndexes.length} 列`);
145
+ event.dataTransfer.setDragImage(dragImage, dragImage.offsetWidth / 2, dragImage.offsetHeight / 2);
146
+ setInsertTarget(null);
147
+ closeMenu();
148
+ startDrag(event.nativeEvent, selectedIndexes);
149
+ }, [closeMenu, startDrag]);
150
+ const insertColumnOffset = insertTarget ? sum(renderColWidths.slice(0, insertTarget.index)) + (insertTarget.insertAfter ? renderColWidths[insertTarget.index] || 0 : 0) : 0;
151
+ const dragColumnOffset = dragTarget ? sum(renderColWidths.slice(0, dragTarget.index)) + (dragTarget.insertAfter ? renderColWidths[dragTarget.index] || 0 : 0) : 0;
152
+ const controllerWidth = sum(renderColWidths);
153
+ const showInsertButton = !isDragging && (Boolean(insertTarget) || isInsertButtonHovered);
154
+ useEffect(() => {
155
+ if (selectedColumns.length > 0) anchorColumnIndexRef.current = selectedColumns[0];
156
+ }, [selectedColumns]);
157
+ useEffect(() => {
158
+ onColumnMetricsChange?.();
159
+ }, [
160
+ colWidths,
161
+ onColumnMetricsChange,
162
+ tableHeight
163
+ ]);
164
+ useEffect(() => {
165
+ return menuService?.subscribe(() => {
166
+ setMenuVersion((version) => version + 1);
167
+ });
168
+ }, [menuService]);
169
+ useEffect(() => {
170
+ return () => {
171
+ clearInsertButtonHideTimer();
172
+ clearDeletePreview();
173
+ onInsertPreviewChange?.(null);
174
+ setMenuAnchorElement(null);
175
+ pendingDragColumnsRef.current = null;
176
+ clearDragState();
177
+ };
178
+ }, [
179
+ clearDeletePreview,
180
+ clearDragState,
181
+ onInsertPreviewChange
182
+ ]);
183
+ const shouldShowController = isTableFocused || isControllerHovered;
184
+ useEffect(() => {
185
+ if (!shouldShowController) {
186
+ clearInsertButtonHideTimer();
187
+ clearDeletePreview();
188
+ setInsertTarget(null);
189
+ setInsertButtonHovered(false);
190
+ onInsertPreviewChange?.(null);
191
+ setMenuAnchorElement(null);
192
+ pendingDragColumnsRef.current = null;
193
+ clearDragState();
194
+ }
195
+ }, [
196
+ clearDeletePreview,
197
+ clearDragState,
198
+ onInsertPreviewChange,
199
+ shouldShowController
200
+ ]);
201
+ useEffect(() => {
202
+ if (!showMenu) closeMenu();
203
+ }, [closeMenu, showMenu]);
204
+ if (!shouldShowController) return null;
205
+ return /* @__PURE__ */ jsx(LexicalPortalContainer, {
206
+ editor,
207
+ node,
208
+ children: /* @__PURE__ */ jsx("div", {
209
+ className: "table-controller-col",
210
+ contentEditable: false,
211
+ style: { inlineSize: controllerWidth },
212
+ children: /* @__PURE__ */ jsxs("div", {
213
+ className: cx("top", styles.colTop),
214
+ onMouseEnter: () => {
215
+ setControllerHovered(true);
216
+ },
217
+ onMouseLeave: () => {
218
+ setControllerHovered(false);
219
+ scheduleHideInsertButton();
220
+ },
221
+ ref: colTopRef,
222
+ style: { inlineSize: controllerWidth },
223
+ children: [
224
+ /* @__PURE__ */ jsx(TableControllerMenu, {
225
+ anchorElement: menuAnchorElement,
226
+ blockMenuService,
227
+ items: menuItems,
228
+ onOpenChange: (open) => {
229
+ if (!open) closeMenu();
230
+ },
231
+ open: Boolean(menuAnchorElement) && showMenu && menuItems.length > 0,
232
+ position: "top"
233
+ }),
234
+ renderColWidths.map((width, index) => {
235
+ const isLastCol = index + 1 === renderColWidths.length;
236
+ const isSelected = selectedColumns.includes(index);
237
+ const isSelectedController = isSelected && !isTableSelected;
238
+ const showSelectionDots = isSelected && !isTableSelected;
239
+ return /* @__PURE__ */ jsx("div", {
240
+ className: cx("col", styles.col, isLastCol && styles.colLast, isSelected && styles.selected),
241
+ draggable: true,
242
+ onClickCapture: (event) => {
243
+ if (isSelectedController) {
244
+ event.preventDefault();
245
+ event.stopPropagation();
246
+ clearInsertButtonHideTimer();
247
+ setInsertTarget(null);
248
+ setInsertButtonHovered(false);
249
+ setMenuAnchorElement(event.currentTarget);
250
+ }
251
+ },
252
+ onDragEnd: () => {
253
+ pendingDragColumnsRef.current = null;
254
+ finishColumnDrag();
255
+ },
256
+ onDragStart: (event) => {
257
+ event.stopPropagation();
258
+ startColumnDrag(event, pendingDragColumnsRef.current || (isSelectedController ? selectedColumns : [index]));
259
+ },
260
+ onMouseDown: (event) => {
261
+ event.stopPropagation();
262
+ if (isSelectedController) {
263
+ event.preventDefault();
264
+ pendingDragColumnsRef.current = selectedColumns;
265
+ return;
266
+ }
267
+ closeMenu();
268
+ pendingDragColumnsRef.current = [index];
269
+ const anchorIndex = event.shiftKey ? selectedColumns[0] ?? anchorColumnIndexRef.current : index;
270
+ editor.dispatchCommand(SELECT_TABLE_COMMAND, {
271
+ anchorIndex,
272
+ columnIndex: index,
273
+ extend: event.shiftKey,
274
+ table: node.getKey()
275
+ });
276
+ if (!event.shiftKey) anchorColumnIndexRef.current = index;
277
+ },
278
+ onMouseDownCapture: (event) => {
279
+ if (isSelectedController) {
280
+ event.stopPropagation();
281
+ pendingDragColumnsRef.current = selectedColumns;
282
+ }
283
+ },
284
+ onMouseMove: (event) => {
285
+ if (isDragging || isSelectedController) {
286
+ setInsertTarget(null);
287
+ return;
288
+ }
289
+ const rect = event.currentTarget.getBoundingClientRect();
290
+ setInsertTarget({
291
+ index,
292
+ insertAfter: event.clientX - rect.left > rect.width / 2
293
+ });
294
+ },
295
+ onMouseUpCapture: (event) => {
296
+ if (isSelectedController) {
297
+ event.preventDefault();
298
+ event.stopPropagation();
299
+ }
300
+ },
301
+ onPointerDownCapture: (event) => {
302
+ if (isSelectedController) {
303
+ event.stopPropagation();
304
+ pendingDragColumnsRef.current = selectedColumns;
305
+ }
306
+ },
307
+ style: { width },
308
+ children: /* @__PURE__ */ jsx("span", {
309
+ className: cx(styles.selectionDots, styles.colSelectionDots, showSelectionDots && styles.selectionDotsVisible),
310
+ children: DOTS.map((dot) => /* @__PURE__ */ jsx("span", {}, dot))
311
+ })
312
+ }, index);
313
+ }),
314
+ /* @__PURE__ */ jsx("span", {
315
+ className: cx(styles.dragIndicator, styles.colDragIndicator, Boolean(dragTarget) && isDragging && styles.dragIndicatorVisible),
316
+ style: {
317
+ blockSize: tableHeight,
318
+ left: dragColumnOffset
319
+ }
320
+ }),
321
+ /* @__PURE__ */ jsx(TableInsertButton, {
322
+ ariaLabel: "Insert column",
323
+ offset: insertColumnOffset,
324
+ onInsert: () => {
325
+ if (!insertTarget) return;
326
+ editor.dispatchCommand(INSERT_TABLE_COLUMN_COMMAND, {
327
+ columnIndex: insertTarget.index,
328
+ insertAfter: insertTarget.insertAfter,
329
+ table: node.getKey()
330
+ });
331
+ refreshColumnMetrics();
332
+ setInsertTarget(null);
333
+ },
334
+ onMouseEnter: () => {
335
+ insertButtonHoveredRef.current = true;
336
+ clearInsertButtonHideTimer();
337
+ setInsertButtonHovered(true);
338
+ },
339
+ onMouseLeave: () => {
340
+ insertButtonHoveredRef.current = false;
341
+ scheduleHideInsertButton();
342
+ },
343
+ position: "top",
344
+ reference: colTopRef.current,
345
+ visible: showInsertButton
346
+ })
347
+ ]
348
+ })
349
+ })
350
+ });
351
+ });
352
+ TableColController.displayName = "TableColController";
353
+ //#endregion
354
+ export { TableColController as default };
@@ -0,0 +1,201 @@
1
+ import { syncTableWidthDOM } from "../../utils/index.js";
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+ //#region src/plugins/table/react/TableController/hooks.ts
4
+ const CONTROL_SIZE = 14;
5
+ const ROW_BORDER_HEIGHT = .5;
6
+ const sum = (values) => {
7
+ return values.reduce((total, value) => total + value, 0);
8
+ };
9
+ const getTableElement = (element) => {
10
+ if (element instanceof HTMLTableElement) return element;
11
+ return element?.querySelector("table.editor_table, table") || null;
12
+ };
13
+ const readNodeColWidths = (editor, node) => {
14
+ return editor.getEditorState().read(() => {
15
+ const latestNode = node.getLatest();
16
+ const columnCount = latestNode.getColumnCount();
17
+ const colWidths = latestNode.getColWidths();
18
+ return Array.from({ length: columnCount }, (_, index) => colWidths?.[index] || 92);
19
+ });
20
+ };
21
+ const normalizeRowControllerHeight = (height, isLastRow = false) => {
22
+ return Math.max(CONTROL_SIZE, Math.round(height) - (isLastRow ? 0 : ROW_BORDER_HEIGHT));
23
+ };
24
+ const getControlledRowHeights = (rowHeights, tableHeight) => {
25
+ if (rowHeights.length === 0 || tableHeight <= 0) return rowHeights;
26
+ const lastRowIndex = rowHeights.length - 1;
27
+ const previousRowsHeight = sum(rowHeights.slice(0, lastRowIndex));
28
+ const lastRowHeight = Math.max(CONTROL_SIZE, tableHeight - previousRowsHeight);
29
+ return rowHeights.map((height, index) => index === lastRowIndex ? lastRowHeight : height);
30
+ };
31
+ const useTableColumnMetrics = (editor, node) => {
32
+ const [colWidths, setColWidths] = useState(() => readNodeColWidths(editor, node));
33
+ const [tableHeight, setTableHeight] = useState(0);
34
+ const refreshColumnMetrics = useCallback(() => {
35
+ const nodeColWidths = readNodeColWidths(editor, node);
36
+ const tableElement = getTableElement(editor.getElementByKey(node.getKey()));
37
+ setColWidths((currentWidths) => {
38
+ if (currentWidths.length === nodeColWidths.length && currentWidths.every((width, index) => width === nodeColWidths[index])) return currentWidths;
39
+ return nodeColWidths;
40
+ });
41
+ if (tableElement) {
42
+ syncTableWidthDOM(editor, node.getKey(), nodeColWidths);
43
+ setTableHeight(tableElement.getBoundingClientRect().height);
44
+ }
45
+ }, [editor, node]);
46
+ useEffect(() => {
47
+ const tableElement = getTableElement(editor.getElementByKey(node.getKey()));
48
+ let frame = null;
49
+ const scheduleMeasure = () => {
50
+ if (frame !== null) cancelAnimationFrame(frame);
51
+ frame = requestAnimationFrame(() => {
52
+ frame = null;
53
+ refreshColumnMetrics();
54
+ });
55
+ };
56
+ scheduleMeasure();
57
+ if (!tableElement) {
58
+ const unregisterUpdate = editor.registerUpdateListener(scheduleMeasure);
59
+ return () => {
60
+ if (frame !== null) cancelAnimationFrame(frame);
61
+ unregisterUpdate();
62
+ };
63
+ }
64
+ const resizeObserver = new ResizeObserver(refreshColumnMetrics);
65
+ resizeObserver.observe(tableElement);
66
+ const mutationObserver = new MutationObserver(scheduleMeasure);
67
+ mutationObserver.observe(tableElement, {
68
+ childList: true,
69
+ subtree: true
70
+ });
71
+ const unregisterUpdate = editor.registerUpdateListener(scheduleMeasure);
72
+ return () => {
73
+ if (frame !== null) cancelAnimationFrame(frame);
74
+ resizeObserver.disconnect();
75
+ mutationObserver.disconnect();
76
+ unregisterUpdate();
77
+ };
78
+ }, [
79
+ editor,
80
+ node,
81
+ refreshColumnMetrics
82
+ ]);
83
+ return {
84
+ colWidths,
85
+ refreshColumnMetrics,
86
+ tableHeight
87
+ };
88
+ };
89
+ const useTableRowMetrics = (editor, node) => {
90
+ const [rowHeights, setRowHeights] = useState([]);
91
+ const [tableWidth, setTableWidth] = useState(0);
92
+ useEffect(() => {
93
+ const tableElement = editor.getElementByKey(node.getKey());
94
+ const table = tableElement?.querySelector("table.editor_table, table") ?? tableElement;
95
+ if (!(tableElement instanceof HTMLElement)) return;
96
+ let frame = null;
97
+ const measure = () => {
98
+ const rows = tableElement.querySelectorAll("tr");
99
+ if (rows.length === 0) return;
100
+ const tableRect = table instanceof HTMLElement ? table.getBoundingClientRect() : null;
101
+ const nextHeights = getControlledRowHeights(Array.from(rows, (row, index) => {
102
+ return normalizeRowControllerHeight(row.getBoundingClientRect().height, index + 1 === rows.length);
103
+ }), tableRect?.height || 0);
104
+ setRowHeights((currentHeights) => {
105
+ if (currentHeights.length === nextHeights.length && currentHeights.every((height, index) => height === nextHeights[index])) return currentHeights;
106
+ return nextHeights;
107
+ });
108
+ setTableWidth(tableRect?.width || 0);
109
+ };
110
+ const scheduleMeasure = () => {
111
+ if (frame !== null) cancelAnimationFrame(frame);
112
+ frame = requestAnimationFrame(() => {
113
+ frame = null;
114
+ measure();
115
+ });
116
+ };
117
+ scheduleMeasure();
118
+ const resizeObserver = new ResizeObserver(measure);
119
+ resizeObserver.observe(tableElement);
120
+ const mutationObserver = new MutationObserver(scheduleMeasure);
121
+ mutationObserver.observe(tableElement, {
122
+ childList: true,
123
+ subtree: true
124
+ });
125
+ const unregisterUpdate = editor.registerUpdateListener(scheduleMeasure);
126
+ return () => {
127
+ if (frame !== null) cancelAnimationFrame(frame);
128
+ resizeObserver.disconnect();
129
+ mutationObserver.disconnect();
130
+ unregisterUpdate();
131
+ };
132
+ }, [editor, node]);
133
+ return {
134
+ rowHeights,
135
+ tableWidth
136
+ };
137
+ };
138
+ const useTableAxisDrag = ({ getDropTarget, onMove }) => {
139
+ const [dragTarget, setDragTarget] = useState(null);
140
+ const [isDragging, setDragging] = useState(false);
141
+ const selectedIndexesRef = useRef(null);
142
+ const dragTargetRef = useRef(null);
143
+ const cleanupRef = useRef(null);
144
+ const clearDragState = useCallback(() => {
145
+ cleanupRef.current?.();
146
+ cleanupRef.current = null;
147
+ selectedIndexesRef.current = null;
148
+ dragTargetRef.current = null;
149
+ setDragging(false);
150
+ setDragTarget(null);
151
+ }, []);
152
+ const updateDragTarget = useCallback((event) => {
153
+ const target = getDropTarget(event);
154
+ dragTargetRef.current = target;
155
+ setDragTarget(target);
156
+ }, [getDropTarget]);
157
+ const finishDrag = useCallback(() => {
158
+ const selectedIndexes = selectedIndexesRef.current;
159
+ const target = dragTargetRef.current;
160
+ if (selectedIndexes && target) onMove(target, selectedIndexes);
161
+ clearDragState();
162
+ }, [clearDragState, onMove]);
163
+ const startDrag = useCallback((event, selectedIndexes) => {
164
+ selectedIndexesRef.current = selectedIndexes;
165
+ setDragging(true);
166
+ updateDragTarget(event);
167
+ const body = document.body;
168
+ const handleDragOver = (dragEvent) => {
169
+ dragEvent.preventDefault();
170
+ dragEvent.stopPropagation();
171
+ if (dragEvent.dataTransfer) dragEvent.dataTransfer.dropEffect = "move";
172
+ updateDragTarget(dragEvent);
173
+ };
174
+ const handleDragEnd = (dragEvent) => {
175
+ dragEvent.preventDefault();
176
+ dragEvent.stopPropagation();
177
+ finishDrag();
178
+ };
179
+ cleanupRef.current?.();
180
+ cleanupRef.current = () => {
181
+ body.removeEventListener("dragover", handleDragOver);
182
+ body.removeEventListener("drop", handleDragEnd);
183
+ body.removeEventListener("dragend", handleDragEnd);
184
+ };
185
+ body.addEventListener("dragover", handleDragOver);
186
+ body.addEventListener("drop", handleDragEnd);
187
+ body.addEventListener("dragend", handleDragEnd);
188
+ }, [finishDrag, updateDragTarget]);
189
+ useEffect(() => {
190
+ return clearDragState;
191
+ }, [clearDragState]);
192
+ return {
193
+ clearDragState,
194
+ dragTarget,
195
+ finishDrag,
196
+ isDragging,
197
+ startDrag
198
+ };
199
+ };
200
+ //#endregion
201
+ export { sum, useTableAxisDrag, useTableColumnMetrics, useTableRowMetrics };