@lobehub/editor 4.15.2 → 4.16.1
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.
- package/es/editor-kernel/react/useDecorators.js +14 -8
- package/es/headless/index.d.ts +1 -1
- package/es/headless/index.js +1 -1
- package/es/headless.d.ts +3 -18
- package/es/headless.js +711 -52
- package/es/index.d.ts +5 -5
- package/es/index.js +5 -5
- package/es/locale/index.d.ts +2 -0
- package/es/locale/index.js +5 -1
- package/es/plugins/auto-complete/plugin/index.js +3 -3
- package/es/plugins/block/index.d.ts +1 -1
- package/es/plugins/block/plugin/index.js +78 -2
- package/es/plugins/block/react/ReactBlockPlugin.js +172 -16
- package/es/plugins/block/react/drag/drag-utils.js +37 -10
- package/es/plugins/block/service/i-block-menu-service.d.ts +18 -1
- package/es/plugins/block/service/i-block-menu-service.js +24 -0
- package/es/plugins/block/service/index.d.ts +1 -1
- package/es/plugins/codeblock/plugin/index.js +25 -1
- package/es/plugins/common/plugin/register.js +2 -2
- package/es/plugins/litexml/command/diffCommand.d.ts +2 -17
- package/es/plugins/litexml/command/diffCommand.js +3 -9
- package/es/plugins/litexml/command/index.d.ts +2 -38
- package/es/plugins/litexml/command/index.js +3 -6
- package/es/plugins/litexml/command/symbols.d.ts +67 -0
- package/es/plugins/litexml/command/symbols.js +34 -0
- package/es/plugins/litexml/index.d.ts +1 -2
- package/es/plugins/litexml/plugin/index.js +8 -2
- package/es/plugins/litexml/react/DiffNodeToolbar/index.js +1 -1
- package/es/plugins/slash/plugin/index.js +1 -1
- package/es/plugins/slash/react/ReactSlashPlugin.js +4 -4
- package/es/plugins/table/command/index.d.ts +13 -1
- package/es/plugins/table/command/index.js +220 -39
- package/es/plugins/table/index.d.ts +3 -2
- package/es/plugins/table/node/index.d.ts +2 -0
- package/es/plugins/table/node/index.js +130 -2
- package/es/plugins/table/plugin/index.d.ts +6 -0
- package/es/plugins/table/plugin/index.js +193 -4
- package/es/plugins/table/react/TableActionMenu/ActionMenu.js +82 -6
- package/es/plugins/table/react/TableActionMenu/index.js +9 -4
- package/es/plugins/table/react/TableColController.js +354 -0
- package/es/plugins/table/react/TableController/hooks.js +201 -0
- package/es/plugins/table/react/TableController/style.js +264 -0
- package/es/plugins/table/react/TableController/utils.js +25 -0
- package/es/plugins/table/react/TableControllerButton.js +81 -0
- package/es/plugins/table/react/TableControllerMenu.js +123 -0
- package/es/plugins/table/react/TableInsertButton.js +25 -0
- package/es/plugins/table/react/TableResize/index.js +153 -78
- package/es/plugins/table/react/TableRowController.js +349 -0
- package/es/plugins/table/react/hooks.js +77 -0
- package/es/plugins/table/react/index.js +139 -16
- package/es/plugins/table/react/style.js +89 -8
- package/es/plugins/table/react/type.d.ts +2 -0
- package/es/plugins/table/react/useAutoFitPastedTable.js +189 -0
- package/es/plugins/table/service/i-table-controller-menu-service.d.ts +44 -0
- package/es/plugins/table/service/i-table-controller-menu-service.js +31 -0
- package/es/plugins/table/service/index.d.ts +1 -0
- package/es/plugins/table/utils/autoFitColumnWidth.js +87 -0
- package/es/plugins/table/utils/distributeColumnWidth.js +37 -0
- package/es/plugins/table/utils/index.js +102 -2
- package/es/react/EditorProvider/index.d.ts +2 -2
- package/es/renderer/LexicalDiff.d.ts +2 -2
- package/es/symbols-DEEvsKq4.d.ts +67 -0
- package/package.json +5 -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 };
|