@quadrats/react 1.0.0 → 1.1.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/card/components/Card.js +83 -75
- package/card/index.cjs.js +82 -74
- package/carousel/components/Carousel.js +32 -28
- package/carousel/index.cjs.js +32 -28
- package/components/Tooltip/index.js +5 -2
- package/components/index.cjs.js +5 -2
- package/core/components/Quadrats.js +5 -2
- package/core/contexts/modal/CarouselModal/CarouselModal.js +14 -17
- package/embed/renderers/base/components/BaseEmbedElement.js +51 -43
- package/embed/renderers/base/index.cjs.js +50 -42
- package/image/components/Image.js +34 -26
- package/image/createReactImage.js +1 -1
- package/image/index.cjs.js +34 -26
- package/index.cjs.js +19 -19
- package/package.json +4 -4
- package/table/components/ColumnDragButton.d.ts +10 -0
- package/table/components/ColumnDragButton.js +41 -0
- package/table/components/RowDragButton.d.ts +10 -0
- package/table/components/RowDragButton.js +42 -0
- package/table/components/Table.d.ts +9 -0
- package/table/components/Table.js +236 -0
- package/table/components/TableBody.d.ts +5 -0
- package/table/components/TableBody.js +8 -0
- package/table/components/TableCell.d.ts +5 -0
- package/table/components/TableCell.js +297 -0
- package/table/components/TableDragLayer.d.ts +6 -0
- package/table/components/TableDragLayer.js +89 -0
- package/table/components/TableHeader.d.ts +5 -0
- package/table/components/TableHeader.js +13 -0
- package/table/components/TableMain.d.ts +5 -0
- package/table/components/TableMain.js +233 -0
- package/table/components/TableRow.d.ts +5 -0
- package/table/components/TableRow.js +8 -0
- package/table/components/TableTitle.d.ts +5 -0
- package/table/components/TableTitle.js +18 -0
- package/table/contexts/TableActionsContext.d.ts +3 -0
- package/table/contexts/TableActionsContext.js +5 -0
- package/table/contexts/TableDragContext.d.ts +26 -0
- package/table/contexts/TableDragContext.js +26 -0
- package/table/contexts/TableHeaderContext.d.ts +2 -0
- package/table/contexts/TableHeaderContext.js +7 -0
- package/table/contexts/TableMetadataContext.d.ts +3 -0
- package/table/contexts/TableMetadataContext.js +5 -0
- package/table/contexts/TableScrollContext.d.ts +2 -0
- package/table/contexts/TableScrollContext.js +9 -0
- package/table/contexts/TableStateContext.d.ts +3 -0
- package/table/contexts/TableStateContext.js +5 -0
- package/table/createReactTable.d.ts +4 -0
- package/table/createReactTable.js +297 -0
- package/table/defaultRenderTableElements.d.ts +2 -0
- package/table/defaultRenderTableElements.js +20 -0
- package/table/hooks/useColumnResize.d.ts +12 -0
- package/table/hooks/useColumnResize.js +168 -0
- package/table/hooks/useTableActions.d.ts +27 -0
- package/table/hooks/useTableActions.js +1092 -0
- package/table/hooks/useTableActionsContext.d.ts +1 -0
- package/table/hooks/useTableActionsContext.js +12 -0
- package/table/hooks/useTableCell.d.ts +16 -0
- package/table/hooks/useTableCell.js +166 -0
- package/table/hooks/useTableCellToolbarActions.d.ts +34 -0
- package/table/hooks/useTableCellToolbarActions.js +526 -0
- package/table/hooks/useTableMetadata.d.ts +1 -0
- package/table/hooks/useTableMetadata.js +12 -0
- package/table/hooks/useTableStateContext.d.ts +1 -0
- package/table/hooks/useTableStateContext.js +12 -0
- package/table/hooks/useTableStates.d.ts +18 -0
- package/table/hooks/useTableStates.js +14 -0
- package/table/index.cjs.js +4002 -0
- package/table/index.d.ts +16 -0
- package/table/index.js +27 -0
- package/table/jsx-serializer/components/Table.d.ts +3 -0
- package/table/jsx-serializer/components/Table.js +7 -0
- package/table/jsx-serializer/components/TableBody.d.ts +3 -0
- package/table/jsx-serializer/components/TableBody.js +7 -0
- package/table/jsx-serializer/components/TableCell.d.ts +5 -0
- package/table/jsx-serializer/components/TableCell.js +33 -0
- package/table/jsx-serializer/components/TableHeader.d.ts +3 -0
- package/table/jsx-serializer/components/TableHeader.js +10 -0
- package/table/jsx-serializer/components/TableMain.d.ts +6 -0
- package/table/jsx-serializer/components/TableMain.js +18 -0
- package/table/jsx-serializer/components/TableRow.d.ts +3 -0
- package/table/jsx-serializer/components/TableRow.js +7 -0
- package/table/jsx-serializer/components/TableTitle.d.ts +3 -0
- package/table/jsx-serializer/components/TableTitle.js +7 -0
- package/table/jsx-serializer/contexts/TableHeaderContext.d.ts +1 -0
- package/table/jsx-serializer/contexts/TableHeaderContext.js +5 -0
- package/table/jsx-serializer/contexts/TableScrollContext.d.ts +2 -0
- package/table/jsx-serializer/contexts/TableScrollContext.js +7 -0
- package/table/jsx-serializer/createJsxSerializeTable.d.ts +5 -0
- package/table/jsx-serializer/createJsxSerializeTable.js +113 -0
- package/table/jsx-serializer/defaultRenderTableElements.d.ts +2 -0
- package/table/jsx-serializer/defaultRenderTableElements.js +20 -0
- package/table/jsx-serializer/index.cjs.js +195 -0
- package/table/jsx-serializer/index.d.ts +3 -0
- package/table/jsx-serializer/index.js +2 -0
- package/table/jsx-serializer/package.json +7 -0
- package/table/jsx-serializer/typings.d.ts +12 -0
- package/table/package.json +10 -0
- package/table/table.css +1 -0
- package/table/table.scss +428 -0
- package/table/toolbar/TableToolbarIcon.d.ts +8 -0
- package/table/toolbar/TableToolbarIcon.js +12 -0
- package/table/toolbar/index.cjs.js +24 -0
- package/table/toolbar/index.d.ts +2 -0
- package/table/toolbar/index.js +2 -0
- package/table/toolbar/package.json +7 -0
- package/table/toolbar/useTableTool.d.ts +4 -0
- package/table/toolbar/useTableTool.js +13 -0
- package/table/typings.d.ts +68 -0
- package/table/utils/helper.d.ts +186 -0
- package/table/utils/helper.js +799 -0
- package/toolbar/components/InlineToolbar.d.ts +12 -11
- package/toolbar/components/InlineToolbar.js +23 -19
- package/toolbar/components/Toolbar.js +2 -2
- package/toolbar/index.cjs.js +24 -21
- package/toolbar/toolbar.css +1 -1
- package/toolbar/toolbar.scss +4 -1
- package/utils/index.cjs.js +7 -1
- package/utils/removePreviousElement.js +7 -1
|
@@ -0,0 +1,1092 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { Element, Editor, Transforms } from '@quadrats/core';
|
|
3
|
+
import { ReactEditor } from 'slate-react';
|
|
4
|
+
import { TABLE_ROW_TYPE, TABLE_CELL_TYPE, TABLE_DEFAULT_MAX_COLUMNS, TABLE_HEADER_TYPE } from '@quadrats/common/table';
|
|
5
|
+
import { useQuadrats } from '@quadrats/react';
|
|
6
|
+
import { getTableStructure, createTableCell, getColumnWidths, getPinnedColumnsInfo, calculateColumnWidthsAfterAdd, setColumnWidths, getReferenceRowFromHeaderOrBody, calculateColumnWidthsAfterDelete, hasAnyPinnedRows, moveOrSwapColumnWidth, convertToMixedWidthMode, hasAnyPinnedColumns, convertToPercentageMode } from '../utils/helper.js';
|
|
7
|
+
|
|
8
|
+
function useTableActions(element) {
|
|
9
|
+
const editor = useQuadrats();
|
|
10
|
+
const isColumnPinned = useCallback((columnIndex) => {
|
|
11
|
+
try {
|
|
12
|
+
const tableStructure = getTableStructure(editor, element);
|
|
13
|
+
if (!tableStructure)
|
|
14
|
+
return false;
|
|
15
|
+
const { tableMainElement } = tableStructure;
|
|
16
|
+
if (!tableMainElement)
|
|
17
|
+
return false;
|
|
18
|
+
for (const container of tableMainElement.children) {
|
|
19
|
+
if (!Element.isElement(container))
|
|
20
|
+
continue;
|
|
21
|
+
for (const row of container.children) {
|
|
22
|
+
if (Element.isElement(row) && row.type.includes(TABLE_ROW_TYPE)) {
|
|
23
|
+
const cell = row.children[columnIndex];
|
|
24
|
+
if (Element.isElement(cell) && cell.type.includes(TABLE_CELL_TYPE)) {
|
|
25
|
+
// 如果有任何一個 cell 沒有 pinned 屬性,則整個 column 不算 pinned
|
|
26
|
+
if (!cell.pinned) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}, [element, editor]);
|
|
39
|
+
const isRowPinned = useCallback((rowIndex) => {
|
|
40
|
+
try {
|
|
41
|
+
const tableStructure = getTableStructure(editor, element);
|
|
42
|
+
if (!tableStructure)
|
|
43
|
+
return false;
|
|
44
|
+
const { tableHeaderElement, tableBodyElement } = tableStructure;
|
|
45
|
+
const headerRowCount = tableHeaderElement && Element.isElement(tableHeaderElement) ? tableHeaderElement.children.length : 0;
|
|
46
|
+
let targetRow;
|
|
47
|
+
if (rowIndex < headerRowCount && tableHeaderElement && Element.isElement(tableHeaderElement)) {
|
|
48
|
+
// 在 Header 中
|
|
49
|
+
const rowElement = tableHeaderElement.children[rowIndex];
|
|
50
|
+
if (Element.isElement(rowElement)) {
|
|
51
|
+
targetRow = rowElement;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else if (tableBodyElement && Element.isElement(tableBodyElement)) {
|
|
55
|
+
// 在 Body 中
|
|
56
|
+
const bodyRowIndex = rowIndex - headerRowCount;
|
|
57
|
+
const rowElement = tableBodyElement.children[bodyRowIndex];
|
|
58
|
+
if (Element.isElement(rowElement)) {
|
|
59
|
+
targetRow = rowElement;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (!Element.isElement(targetRow) || !targetRow.type.includes(TABLE_ROW_TYPE)) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
// 檢查所有 cell 是否都有 pinned 屬性
|
|
66
|
+
for (const cell of targetRow.children) {
|
|
67
|
+
if (Element.isElement(cell) && cell.type.includes(TABLE_CELL_TYPE)) {
|
|
68
|
+
if (!cell.pinned) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}, [element, editor]);
|
|
79
|
+
const addColumn = useCallback((options = {}) => {
|
|
80
|
+
const { position = 'right', columnIndex } = options;
|
|
81
|
+
try {
|
|
82
|
+
const tableStructure = getTableStructure(editor, element);
|
|
83
|
+
if (!tableStructure)
|
|
84
|
+
return;
|
|
85
|
+
const { tableHeaderElement, tableBodyElement, tableHeaderPath, tableBodyPath, columnCount } = tableStructure;
|
|
86
|
+
if (columnCount >= TABLE_DEFAULT_MAX_COLUMNS) {
|
|
87
|
+
console.warn(`Maximum columns limit (${TABLE_DEFAULT_MAX_COLUMNS}) reached`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
// 計算插入位置
|
|
91
|
+
let insertIndex;
|
|
92
|
+
if (typeof columnIndex === 'number') {
|
|
93
|
+
insertIndex = position === 'left' ? Math.max(0, columnIndex) : Math.min(columnCount, columnIndex + 1);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
insertIndex = columnCount;
|
|
97
|
+
}
|
|
98
|
+
// 使用 Editor.withoutNormalizing 來批次執行所有操作
|
|
99
|
+
Editor.withoutNormalizing(editor, () => {
|
|
100
|
+
// 在 Header 中加入 cell
|
|
101
|
+
if (tableHeaderElement && tableHeaderPath) {
|
|
102
|
+
tableHeaderElement.children.forEach((row, rowIndex) => {
|
|
103
|
+
if (Element.isElement(row) && row.type.includes(TABLE_ROW_TYPE)) {
|
|
104
|
+
const referenceCell = row.children[insertIndex - (position === 'left' ? 0 : 1)];
|
|
105
|
+
const newCell = createTableCell(referenceCell);
|
|
106
|
+
const cellPath = [...tableHeaderPath, rowIndex, insertIndex];
|
|
107
|
+
Transforms.insertNodes(editor, newCell, { at: cellPath });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
// 在 Body 中加入 cell
|
|
112
|
+
tableBodyElement.children.forEach((row, rowIndex) => {
|
|
113
|
+
if (Element.isElement(row) && row.type.includes(TABLE_ROW_TYPE)) {
|
|
114
|
+
const referenceCell = row.children[insertIndex - (position === 'left' ? 0 : 1)];
|
|
115
|
+
const newCell = createTableCell(referenceCell);
|
|
116
|
+
const cellPath = [...tableBodyPath, rowIndex, insertIndex];
|
|
117
|
+
Transforms.insertNodes(editor, newCell, { at: cellPath });
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
// 調整欄位寬度
|
|
121
|
+
const currentWidths = getColumnWidths(element);
|
|
122
|
+
if (currentWidths.length > 0) {
|
|
123
|
+
// 獲取當前的 pinned columns 資訊
|
|
124
|
+
const { pinnedColumnIndices } = getPinnedColumnsInfo(element);
|
|
125
|
+
const newWidths = calculateColumnWidthsAfterAdd(currentWidths, insertIndex, pinnedColumnIndices, columnIndex);
|
|
126
|
+
setColumnWidths(editor, element, newWidths);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
console.warn('Failed to add column:', error);
|
|
132
|
+
}
|
|
133
|
+
}, [editor, element]);
|
|
134
|
+
const addRow = useCallback((options = {}) => {
|
|
135
|
+
const { position = 'bottom', rowIndex } = options;
|
|
136
|
+
try {
|
|
137
|
+
const tableStructure = getTableStructure(editor, element);
|
|
138
|
+
if (!tableStructure)
|
|
139
|
+
return;
|
|
140
|
+
const { tableHeaderElement, tableBodyElement, tableHeaderPath, tableBodyPath, headerRowCount, columnCount } = tableStructure;
|
|
141
|
+
// 計算插入位置和參考行
|
|
142
|
+
let insertIndex;
|
|
143
|
+
let referenceRowElement;
|
|
144
|
+
let targetPath;
|
|
145
|
+
if (typeof rowIndex === 'number') {
|
|
146
|
+
// 檢查是在 Header / Body 之中
|
|
147
|
+
if (tableHeaderElement && rowIndex < headerRowCount) {
|
|
148
|
+
targetPath = tableHeaderPath;
|
|
149
|
+
if (position === 'top') {
|
|
150
|
+
insertIndex = Math.max(0, rowIndex);
|
|
151
|
+
referenceRowElement = getReferenceRowFromHeaderOrBody(tableHeaderElement, insertIndex);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
insertIndex = Math.min(headerRowCount, rowIndex + 1);
|
|
155
|
+
referenceRowElement = getReferenceRowFromHeaderOrBody(tableHeaderElement, rowIndex);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
targetPath = tableBodyPath;
|
|
160
|
+
const bodyRowIndex = rowIndex - headerRowCount;
|
|
161
|
+
if (position === 'top') {
|
|
162
|
+
insertIndex = Math.max(0, bodyRowIndex);
|
|
163
|
+
referenceRowElement = getReferenceRowFromHeaderOrBody(tableBodyElement, insertIndex);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
insertIndex = Math.min(tableBodyElement.children.length, bodyRowIndex + 1);
|
|
167
|
+
referenceRowElement = getReferenceRowFromHeaderOrBody(tableBodyElement, bodyRowIndex);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
// 預設:在 Body 尾端加入列
|
|
173
|
+
targetPath = tableBodyPath;
|
|
174
|
+
insertIndex = tableBodyElement.children.length;
|
|
175
|
+
referenceRowElement = getReferenceRowFromHeaderOrBody(tableBodyElement, insertIndex - 1);
|
|
176
|
+
}
|
|
177
|
+
// 創建新行
|
|
178
|
+
const newRow = {
|
|
179
|
+
type: TABLE_ROW_TYPE,
|
|
180
|
+
children: Array.from({ length: columnCount }, (_, cellIndex) => {
|
|
181
|
+
let referenceCell;
|
|
182
|
+
if (referenceRowElement && referenceRowElement.children[cellIndex]) {
|
|
183
|
+
const cell = referenceRowElement.children[cellIndex];
|
|
184
|
+
if (Element.isElement(cell) && cell.type.includes(TABLE_CELL_TYPE)) {
|
|
185
|
+
referenceCell = cell;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return createTableCell(referenceCell);
|
|
189
|
+
}),
|
|
190
|
+
};
|
|
191
|
+
// 插入新行
|
|
192
|
+
const newRowPath = [...targetPath, insertIndex];
|
|
193
|
+
Transforms.insertNodes(editor, newRow, { at: newRowPath });
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
console.warn('Failed to add row:', error);
|
|
197
|
+
}
|
|
198
|
+
}, [editor, element]);
|
|
199
|
+
const addColumnAndRow = useCallback(() => {
|
|
200
|
+
try {
|
|
201
|
+
const tableStructure = getTableStructure(editor, element);
|
|
202
|
+
if (!tableStructure)
|
|
203
|
+
return;
|
|
204
|
+
const { tableHeaderElement, tableBodyElement, tableHeaderPath, tableBodyPath, columnCount } = tableStructure;
|
|
205
|
+
if (columnCount >= TABLE_DEFAULT_MAX_COLUMNS) {
|
|
206
|
+
console.warn(`Maximum columns limit (${TABLE_DEFAULT_MAX_COLUMNS}) reached`);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
Editor.withoutNormalizing(editor, () => {
|
|
210
|
+
// 在 Header 中加入新列
|
|
211
|
+
if (tableHeaderElement && tableHeaderPath) {
|
|
212
|
+
tableHeaderElement.children.forEach((row, rowIndex) => {
|
|
213
|
+
if (Element.isElement(row) && row.type.includes(TABLE_ROW_TYPE)) {
|
|
214
|
+
const lastCell = row.children[row.children.length - 1];
|
|
215
|
+
const newHeaderCell = createTableCell(lastCell);
|
|
216
|
+
const cellPath = [...tableHeaderPath, rowIndex, row.children.length];
|
|
217
|
+
Transforms.insertNodes(editor, newHeaderCell, { at: cellPath });
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
// 在 Body 中加入新列
|
|
222
|
+
tableBodyElement.children.forEach((row, rowIndex) => {
|
|
223
|
+
if (Element.isElement(row) && row.type.includes(TABLE_ROW_TYPE)) {
|
|
224
|
+
const lastCell = row.children[row.children.length - 1];
|
|
225
|
+
const newCell = createTableCell(lastCell);
|
|
226
|
+
const cellPath = [...tableBodyPath, rowIndex, row.children.length];
|
|
227
|
+
Transforms.insertNodes(editor, newCell, { at: cellPath });
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
// 加入新行
|
|
231
|
+
const newColumnCount = columnCount + 1;
|
|
232
|
+
const lastRow = getReferenceRowFromHeaderOrBody(tableBodyElement, tableBodyElement.children.length - 1);
|
|
233
|
+
const newRow = {
|
|
234
|
+
type: TABLE_ROW_TYPE,
|
|
235
|
+
children: Array.from({ length: newColumnCount }, (_, cellIndex) => {
|
|
236
|
+
let referenceCell;
|
|
237
|
+
if (cellIndex < newColumnCount - 1 && Element.isElement(lastRow) && lastRow.type.includes(TABLE_ROW_TYPE)) {
|
|
238
|
+
const cell = lastRow.children[cellIndex];
|
|
239
|
+
if (Element.isElement(cell) && cell.type.includes(TABLE_CELL_TYPE)) {
|
|
240
|
+
referenceCell = cell;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
if (Element.isElement(lastRow) && lastRow.type.includes(TABLE_ROW_TYPE)) {
|
|
245
|
+
const cell = lastRow.children[lastRow.children.length - 1];
|
|
246
|
+
if (Element.isElement(cell) && cell.type.includes(TABLE_CELL_TYPE)) {
|
|
247
|
+
referenceCell = cell;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return createTableCell(referenceCell);
|
|
252
|
+
}),
|
|
253
|
+
};
|
|
254
|
+
const newRowPath = [...tableBodyPath, tableBodyElement.children.length];
|
|
255
|
+
Transforms.insertNodes(editor, newRow, { at: newRowPath });
|
|
256
|
+
// 調整欄位寬度(新增欄位在最後)
|
|
257
|
+
const currentWidths = getColumnWidths(element);
|
|
258
|
+
if (currentWidths.length > 0) {
|
|
259
|
+
// 新欄位插入在最後(columnCount 位置)
|
|
260
|
+
const insertIndex = columnCount;
|
|
261
|
+
// 獲取當前的 pinned columns 資訊
|
|
262
|
+
const { pinnedColumnIndices } = getPinnedColumnsInfo(element);
|
|
263
|
+
const newWidths = calculateColumnWidthsAfterAdd(currentWidths, insertIndex, pinnedColumnIndices);
|
|
264
|
+
setColumnWidths(editor, element, newWidths);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
console.warn('Failed to add column and row:', error);
|
|
270
|
+
}
|
|
271
|
+
}, [editor, element]);
|
|
272
|
+
const deleteRow = useCallback((rowIndex) => {
|
|
273
|
+
try {
|
|
274
|
+
const tableStructure = getTableStructure(editor, element);
|
|
275
|
+
if (!tableStructure)
|
|
276
|
+
return;
|
|
277
|
+
const { tableHeaderElement, tableBodyElement, tableHeaderPath, tableBodyPath, headerRowCount } = tableStructure;
|
|
278
|
+
// 檢查是否刪除 Header 行
|
|
279
|
+
if (rowIndex < headerRowCount) {
|
|
280
|
+
if (!tableHeaderElement || !tableHeaderPath)
|
|
281
|
+
return;
|
|
282
|
+
const headerRowPath = [...tableHeaderPath, rowIndex];
|
|
283
|
+
Transforms.removeNodes(editor, { at: headerRowPath });
|
|
284
|
+
// 如果是最後一個 header 行,移除整個 header 元素
|
|
285
|
+
if (headerRowCount <= 1) {
|
|
286
|
+
Transforms.removeNodes(editor, { at: tableHeaderPath });
|
|
287
|
+
}
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
// 刪除 Body 行
|
|
291
|
+
const bodyRowIndex = rowIndex - headerRowCount;
|
|
292
|
+
if (bodyRowIndex < 0 || bodyRowIndex >= tableBodyElement.children.length) {
|
|
293
|
+
console.warn('Invalid row index for deletion');
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (tableBodyElement.children.length <= 1) {
|
|
297
|
+
console.warn('Cannot delete the last row');
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const rowPath = [...tableBodyPath, bodyRowIndex];
|
|
301
|
+
Transforms.removeNodes(editor, { at: rowPath });
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
console.warn('Failed to delete row:', error);
|
|
305
|
+
}
|
|
306
|
+
}, [editor, element]);
|
|
307
|
+
const deleteColumn = useCallback((columnIndex) => {
|
|
308
|
+
try {
|
|
309
|
+
const tableStructure = getTableStructure(editor, element);
|
|
310
|
+
if (!tableStructure)
|
|
311
|
+
return;
|
|
312
|
+
const { tableHeaderElement, tableBodyElement, tableHeaderPath, tableBodyPath, columnCount } = tableStructure;
|
|
313
|
+
// 檢查是否有足夠的列(不允許刪除最後一列)
|
|
314
|
+
if (columnCount <= 1) {
|
|
315
|
+
console.warn('Cannot delete the last column');
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
// 檢查 columnIndex 是否有效
|
|
319
|
+
if (columnIndex < 0 || columnIndex >= columnCount) {
|
|
320
|
+
console.warn('Invalid column index for deletion');
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
editor.withoutNormalizing(() => {
|
|
324
|
+
// 從 Header 中刪除列
|
|
325
|
+
if (tableHeaderElement && tableHeaderPath) {
|
|
326
|
+
// 以反向順序刪除
|
|
327
|
+
for (let rowIndex = tableHeaderElement.children.length - 1; rowIndex >= 0; rowIndex--) {
|
|
328
|
+
const headerRow = tableHeaderElement.children[rowIndex];
|
|
329
|
+
if (Element.isElement(headerRow) && headerRow.type.includes(TABLE_ROW_TYPE)) {
|
|
330
|
+
const headerCellPath = [...tableHeaderPath, rowIndex, columnIndex];
|
|
331
|
+
Transforms.removeNodes(editor, { at: headerCellPath });
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// 從 Body 中刪除列
|
|
336
|
+
for (let rowIndex = tableBodyElement.children.length - 1; rowIndex >= 0; rowIndex--) {
|
|
337
|
+
const row = tableBodyElement.children[rowIndex];
|
|
338
|
+
if (Element.isElement(row) && row.type.includes(TABLE_ROW_TYPE)) {
|
|
339
|
+
const cellPath = [...tableBodyPath, rowIndex, columnIndex];
|
|
340
|
+
Transforms.removeNodes(editor, { at: cellPath });
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// 調整欄位寬度
|
|
344
|
+
const currentWidths = getColumnWidths(element);
|
|
345
|
+
if (currentWidths.length > 0) {
|
|
346
|
+
const newWidths = calculateColumnWidthsAfterDelete(currentWidths, columnIndex);
|
|
347
|
+
setColumnWidths(editor, element, newWidths);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
catch (error) {
|
|
352
|
+
console.warn('Failed to delete column:', error);
|
|
353
|
+
}
|
|
354
|
+
}, [editor, element]);
|
|
355
|
+
const moveRowToBody = useCallback((rowIndex) => {
|
|
356
|
+
try {
|
|
357
|
+
const tableStructure = getTableStructure(editor, element);
|
|
358
|
+
if (!tableStructure)
|
|
359
|
+
return;
|
|
360
|
+
const { tableHeaderElement, tableHeaderPath, tableBodyPath } = tableStructure;
|
|
361
|
+
if (!tableHeaderElement || !tableHeaderPath)
|
|
362
|
+
return;
|
|
363
|
+
// 檢查行是否存在於 header 中
|
|
364
|
+
if (rowIndex >= tableHeaderElement.children.length) {
|
|
365
|
+
console.warn('Invalid header row index:', rowIndex);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const rowToMove = tableHeaderElement.children[rowIndex];
|
|
369
|
+
if (!Element.isElement(rowToMove) || !rowToMove.type.includes(TABLE_ROW_TYPE))
|
|
370
|
+
return;
|
|
371
|
+
const rowPath = [...tableHeaderPath, rowIndex];
|
|
372
|
+
// 移動前移除所有 cell 的 pinned 屬性
|
|
373
|
+
rowToMove.children.forEach((cell, columnIndex) => {
|
|
374
|
+
if (Element.isElement(cell) && cell.type.includes(TABLE_CELL_TYPE) && cell.pinned) {
|
|
375
|
+
if (cell.pinned && isColumnPinned(columnIndex)) {
|
|
376
|
+
const cellPath = [...rowPath, columnIndex];
|
|
377
|
+
Transforms.unsetNodes(editor, 'pinned', { at: cellPath });
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
// 移動行到 body 的開始位置
|
|
382
|
+
const bodyTargetPath = [...tableBodyPath, 0];
|
|
383
|
+
Transforms.moveNodes(editor, {
|
|
384
|
+
at: rowPath,
|
|
385
|
+
to: bodyTargetPath,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
console.warn('Failed to move row to body:', error);
|
|
390
|
+
}
|
|
391
|
+
}, [editor, element, isColumnPinned]);
|
|
392
|
+
const moveRowToHeader = useCallback((rowIndex, customProps) => {
|
|
393
|
+
try {
|
|
394
|
+
const tableStructure = getTableStructure(editor, element);
|
|
395
|
+
if (!tableStructure)
|
|
396
|
+
return;
|
|
397
|
+
const { tableHeaderElement, tableBodyElement, tableMainPath, tableHeaderPath, tableBodyPath, headerRowCount } = tableStructure;
|
|
398
|
+
// 計算正確的 body 行索引
|
|
399
|
+
const bodyRowIndex = rowIndex - headerRowCount;
|
|
400
|
+
// 檢查 body 行索引是否有效
|
|
401
|
+
if (bodyRowIndex < 0 || bodyRowIndex >= tableBodyElement.children.length) {
|
|
402
|
+
console.warn('Invalid body row index:', bodyRowIndex);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
// 檢查行是否存在
|
|
406
|
+
const rowToMove = tableBodyElement.children[bodyRowIndex];
|
|
407
|
+
if (!Element.isElement(rowToMove) || !rowToMove.type.includes(TABLE_ROW_TYPE))
|
|
408
|
+
return;
|
|
409
|
+
// 檢查 header 中是否已有 pinned rows(一致性規則檢查)
|
|
410
|
+
const hasExistingPinnedRows = tableStructure ? hasAnyPinnedRows(tableStructure) : false;
|
|
411
|
+
// 如果有現有的 pinned rows 且沒有提供自定義屬性,自動設置 pinned 以保持一致性
|
|
412
|
+
const finalProps = customProps || (hasExistingPinnedRows ? { pinned: true } : undefined);
|
|
413
|
+
// 如果提供了 finalProps,則應用到 cells
|
|
414
|
+
const processedRow = finalProps
|
|
415
|
+
? Object.assign(Object.assign({}, rowToMove), { children: rowToMove.children.map((cell) => {
|
|
416
|
+
if (Element.isElement(cell) && cell.type.includes(TABLE_CELL_TYPE)) {
|
|
417
|
+
return Object.assign(Object.assign({}, cell), finalProps);
|
|
418
|
+
}
|
|
419
|
+
return cell;
|
|
420
|
+
}) }) : rowToMove;
|
|
421
|
+
const rowPath = [...tableBodyPath, bodyRowIndex];
|
|
422
|
+
// 如果 header 不存在,先創建它
|
|
423
|
+
if (!tableHeaderElement) {
|
|
424
|
+
const newHeader = {
|
|
425
|
+
type: TABLE_HEADER_TYPE,
|
|
426
|
+
children: [processedRow],
|
|
427
|
+
};
|
|
428
|
+
const headerInsertPath = [...tableMainPath, 0];
|
|
429
|
+
Editor.withoutNormalizing(editor, () => {
|
|
430
|
+
Transforms.removeNodes(editor, { at: rowPath });
|
|
431
|
+
Transforms.insertNodes(editor, newHeader, { at: headerInsertPath });
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
// 如果這是 pinned row,找到正確的插入位置(pinned rows 在頂部)
|
|
436
|
+
let headerTargetPath;
|
|
437
|
+
if (finalProps === null || finalProps === void 0 ? void 0 : finalProps.pinned) {
|
|
438
|
+
let insertIndex = 0;
|
|
439
|
+
for (const [index, headerRow] of tableHeaderElement.children.entries()) {
|
|
440
|
+
if (Element.isElement(headerRow)) {
|
|
441
|
+
const hasNonPinnedCell = headerRow.children.some((cell) => Element.isElement(cell) && !cell.pinned);
|
|
442
|
+
if (hasNonPinnedCell) {
|
|
443
|
+
break;
|
|
444
|
+
}
|
|
445
|
+
insertIndex = index + 1;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
headerTargetPath = [...tableHeaderPath, insertIndex];
|
|
449
|
+
Editor.withoutNormalizing(editor, () => {
|
|
450
|
+
Transforms.removeNodes(editor, { at: rowPath });
|
|
451
|
+
Transforms.insertNodes(editor, processedRow, { at: headerTargetPath });
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
// 移動行到現有 header 的末尾
|
|
456
|
+
headerTargetPath = [...tableHeaderPath, tableHeaderElement.children.length];
|
|
457
|
+
Transforms.moveNodes(editor, {
|
|
458
|
+
at: rowPath,
|
|
459
|
+
to: headerTargetPath,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
catch (error) {
|
|
465
|
+
console.warn('Failed to move row to header:', error);
|
|
466
|
+
}
|
|
467
|
+
}, [editor, element]);
|
|
468
|
+
const unsetColumnAsTitle = useCallback((columnIndex) => {
|
|
469
|
+
try {
|
|
470
|
+
const tableStructure = getTableStructure(editor, element);
|
|
471
|
+
if (!tableStructure)
|
|
472
|
+
return;
|
|
473
|
+
const { tableHeaderElement, tableBodyElement, tableMainElement } = tableStructure;
|
|
474
|
+
// 獲取 table 的實際寬度(用於轉換為混合模式)
|
|
475
|
+
let tableWidth = 0;
|
|
476
|
+
if (tableMainElement) {
|
|
477
|
+
const tableDOMElement = ReactEditor.toDOMNode(editor, tableMainElement);
|
|
478
|
+
if (tableDOMElement instanceof HTMLElement) {
|
|
479
|
+
tableWidth = tableDOMElement.getBoundingClientRect().width;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
const processContainer = (containerElement) => {
|
|
483
|
+
if (!Element.isElement(containerElement))
|
|
484
|
+
return;
|
|
485
|
+
const containerPath = ReactEditor.findPath(editor, containerElement);
|
|
486
|
+
const firstRow = containerElement.children[0];
|
|
487
|
+
// 找到 column 標題列的尾端
|
|
488
|
+
let targetColumnIndex = 0;
|
|
489
|
+
if (Element.isElement(firstRow) && firstRow.type.includes(TABLE_ROW_TYPE)) {
|
|
490
|
+
for (let i = 0; i < firstRow.children.length; i++) {
|
|
491
|
+
const cell = firstRow.children[i];
|
|
492
|
+
if (Element.isElement(cell) &&
|
|
493
|
+
cell.type.includes(TABLE_CELL_TYPE) &&
|
|
494
|
+
cell.treatAsTitle &&
|
|
495
|
+
i !== columnIndex) {
|
|
496
|
+
targetColumnIndex = i + 1;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
containerElement.children.forEach((row, rowIndex) => {
|
|
501
|
+
if (Element.isElement(row) && row.type.includes(TABLE_ROW_TYPE)) {
|
|
502
|
+
const cell = row.children[columnIndex];
|
|
503
|
+
if (Element.isElement(cell) && cell.type.includes(TABLE_CELL_TYPE)) {
|
|
504
|
+
const cellPath = [...containerPath, rowIndex, columnIndex];
|
|
505
|
+
Transforms.unsetNodes(editor, 'treatAsTitle', { at: cellPath });
|
|
506
|
+
if (cell.pinned && !isRowPinned(rowIndex)) {
|
|
507
|
+
Transforms.unsetNodes(editor, 'pinned', { at: cellPath });
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
if (columnIndex < targetColumnIndex) {
|
|
513
|
+
const actualTargetIndex = targetColumnIndex - 1;
|
|
514
|
+
for (let rowIndex = containerElement.children.length - 1; rowIndex >= 0; rowIndex--) {
|
|
515
|
+
const row = containerElement.children[rowIndex];
|
|
516
|
+
if (Element.isElement(row) && row.type.includes(TABLE_ROW_TYPE)) {
|
|
517
|
+
const fromPath = [...containerPath, rowIndex, columnIndex];
|
|
518
|
+
const toPath = [...containerPath, rowIndex, actualTargetIndex];
|
|
519
|
+
Transforms.moveNodes(editor, {
|
|
520
|
+
at: fromPath,
|
|
521
|
+
to: toPath,
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
// 調整 columnWidths:將 columnIndex 的寬度移動到 actualTargetIndex
|
|
526
|
+
const currentWidths = getColumnWidths(element);
|
|
527
|
+
if (currentWidths.length > 0) {
|
|
528
|
+
const movedWidths = moveOrSwapColumnWidth(currentWidths, columnIndex, actualTargetIndex, 'move');
|
|
529
|
+
// 檢查移動後是否還有 pinned columns
|
|
530
|
+
const { pinnedColumnIndices } = getPinnedColumnsInfo(element);
|
|
531
|
+
// 更新釘選欄位索引(移除當前欄位,並調整其他欄位的索引)
|
|
532
|
+
const updatedPinnedIndices = pinnedColumnIndices
|
|
533
|
+
.filter((idx) => idx !== columnIndex)
|
|
534
|
+
.map((idx) => {
|
|
535
|
+
if (idx > columnIndex && idx <= actualTargetIndex)
|
|
536
|
+
return idx - 1;
|
|
537
|
+
return idx;
|
|
538
|
+
})
|
|
539
|
+
.sort((a, b) => a - b);
|
|
540
|
+
// 如果還有 pinned columns,轉換為混合模式;否則可能轉回全 percentage 模式
|
|
541
|
+
if (updatedPinnedIndices.length > 0 && tableWidth > 0) {
|
|
542
|
+
const mixedWidths = convertToMixedWidthMode(movedWidths, updatedPinnedIndices, tableWidth);
|
|
543
|
+
setColumnWidths(editor, element, mixedWidths);
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
// 沒有 pinned columns 了,使用原本的寬度
|
|
547
|
+
setColumnWidths(editor, element, movedWidths);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
// 即使沒有移動位置,也需要檢查是否需要更新寬度模式
|
|
553
|
+
const currentWidths = getColumnWidths(element);
|
|
554
|
+
if (currentWidths.length > 0) {
|
|
555
|
+
const { pinnedColumnIndices } = getPinnedColumnsInfo(element);
|
|
556
|
+
// 移除當前欄位
|
|
557
|
+
const updatedPinnedIndices = pinnedColumnIndices
|
|
558
|
+
.filter((idx) => idx !== columnIndex)
|
|
559
|
+
.sort((a, b) => a - b);
|
|
560
|
+
// 如果還有 pinned columns,轉換為混合模式;否則可能轉回全 percentage 模式
|
|
561
|
+
if (updatedPinnedIndices.length > 0 && tableWidth > 0) {
|
|
562
|
+
const mixedWidths = convertToMixedWidthMode(currentWidths, updatedPinnedIndices, tableWidth);
|
|
563
|
+
setColumnWidths(editor, element, mixedWidths);
|
|
564
|
+
}
|
|
565
|
+
// 如果沒有 pinned columns,保持原樣(可能已經是全 percentage 了)
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
if (tableHeaderElement) {
|
|
570
|
+
processContainer(tableHeaderElement);
|
|
571
|
+
}
|
|
572
|
+
if (tableBodyElement) {
|
|
573
|
+
processContainer(tableBodyElement);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
catch (error) {
|
|
577
|
+
console.warn('Failed to unset column as title:', error);
|
|
578
|
+
}
|
|
579
|
+
}, [editor, element, isRowPinned]);
|
|
580
|
+
const setColumnAsTitle = useCallback((columnIndex, customProps) => {
|
|
581
|
+
try {
|
|
582
|
+
const tableStructure = getTableStructure(editor, element);
|
|
583
|
+
if (!tableStructure)
|
|
584
|
+
return;
|
|
585
|
+
const { tableHeaderElement, tableBodyElement, tableMainElement } = tableStructure;
|
|
586
|
+
// 檢查是否已有 pinned columns
|
|
587
|
+
const hasExistingPinnedColumns = hasAnyPinnedColumns(tableStructure);
|
|
588
|
+
// 如果有現有的 pinned columns 且沒有提供自定義屬性,自動設置 pinned 以保持一致性
|
|
589
|
+
const finalProps = customProps || (hasExistingPinnedColumns ? { pinned: true } : undefined);
|
|
590
|
+
// 獲取 table 的實際寬度(用於轉換為混合模式)
|
|
591
|
+
let tableWidth = 0;
|
|
592
|
+
if (tableMainElement) {
|
|
593
|
+
const tableDOMElement = ReactEditor.toDOMNode(editor, tableMainElement);
|
|
594
|
+
if (tableDOMElement instanceof HTMLElement) {
|
|
595
|
+
tableWidth = tableDOMElement.getBoundingClientRect().width;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
const processContainer = (containerElement) => {
|
|
599
|
+
if (!Element.isElement(containerElement))
|
|
600
|
+
return;
|
|
601
|
+
const containerPath = ReactEditor.findPath(editor, containerElement);
|
|
602
|
+
const firstRow = containerElement.children[0];
|
|
603
|
+
// 先找到 column 標題列的尾端
|
|
604
|
+
let targetColumnIndex = 0;
|
|
605
|
+
if (Element.isElement(firstRow) && firstRow.type.includes(TABLE_ROW_TYPE)) {
|
|
606
|
+
for (let i = 0; i < firstRow.children.length; i++) {
|
|
607
|
+
const cell = firstRow.children[i];
|
|
608
|
+
if (Element.isElement(cell) && cell.type.includes(TABLE_CELL_TYPE) && cell.treatAsTitle) {
|
|
609
|
+
targetColumnIndex = i + 1;
|
|
610
|
+
}
|
|
611
|
+
else {
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
containerElement.children.forEach((row, rowIndex) => {
|
|
617
|
+
if (Element.isElement(row) && row.type.includes(TABLE_ROW_TYPE)) {
|
|
618
|
+
row.children.forEach((cell, childColIndex) => {
|
|
619
|
+
const cellPath = [...containerPath, rowIndex, childColIndex];
|
|
620
|
+
if (childColIndex === columnIndex) {
|
|
621
|
+
const nodeProps = finalProps ? Object.assign({ treatAsTitle: true }, finalProps) : { treatAsTitle: true };
|
|
622
|
+
Transforms.setNodes(editor, nodeProps, { at: cellPath });
|
|
623
|
+
}
|
|
624
|
+
else if (finalProps === null || finalProps === void 0 ? void 0 : finalProps.pinned) {
|
|
625
|
+
// 確保其他 title column 也有 pinned 屬性以保持一致性
|
|
626
|
+
if (Element.isElement(cell) && cell.treatAsTitle) {
|
|
627
|
+
Transforms.setNodes(editor, { pinned: true }, { at: cellPath });
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
// 檢查是否需要移動位置
|
|
634
|
+
const needsMove = columnIndex >= targetColumnIndex && columnIndex !== targetColumnIndex;
|
|
635
|
+
if (needsMove) {
|
|
636
|
+
for (let rowIndex = containerElement.children.length - 1; rowIndex >= 0; rowIndex--) {
|
|
637
|
+
const row = containerElement.children[rowIndex];
|
|
638
|
+
if (Element.isElement(row) && row.type.includes(TABLE_ROW_TYPE)) {
|
|
639
|
+
const fromPath = [...containerPath, rowIndex, columnIndex];
|
|
640
|
+
const toPath = [...containerPath, rowIndex, targetColumnIndex];
|
|
641
|
+
Transforms.moveNodes(editor, {
|
|
642
|
+
at: fromPath,
|
|
643
|
+
to: toPath,
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
// 調整 columnWidths:將 columnIndex 的寬度移動到 targetColumnIndex
|
|
648
|
+
const currentWidths = getColumnWidths(element);
|
|
649
|
+
if (currentWidths.length > 0) {
|
|
650
|
+
const movedWidths = moveOrSwapColumnWidth(currentWidths, columnIndex, targetColumnIndex, 'move');
|
|
651
|
+
// 如果設定了 pinned,需要轉換為混合模式
|
|
652
|
+
if ((finalProps === null || finalProps === void 0 ? void 0 : finalProps.pinned) && tableWidth > 0) {
|
|
653
|
+
const { pinnedColumnIndices } = getPinnedColumnsInfo(element);
|
|
654
|
+
// 更新釘選欄位索引
|
|
655
|
+
const updatedPinnedIndices = pinnedColumnIndices
|
|
656
|
+
.map((idx) => {
|
|
657
|
+
if (idx === columnIndex)
|
|
658
|
+
return targetColumnIndex;
|
|
659
|
+
if (idx >= targetColumnIndex && idx < columnIndex)
|
|
660
|
+
return idx + 1;
|
|
661
|
+
return idx;
|
|
662
|
+
})
|
|
663
|
+
.concat(targetColumnIndex)
|
|
664
|
+
.filter((idx, i, arr) => arr.indexOf(idx) === i)
|
|
665
|
+
.sort((a, b) => a - b);
|
|
666
|
+
const mixedWidths = convertToMixedWidthMode(movedWidths, updatedPinnedIndices, tableWidth);
|
|
667
|
+
setColumnWidths(editor, element, mixedWidths);
|
|
668
|
+
}
|
|
669
|
+
else {
|
|
670
|
+
setColumnWidths(editor, element, movedWidths);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
else if ((finalProps === null || finalProps === void 0 ? void 0 : finalProps.pinned) && tableWidth > 0) {
|
|
675
|
+
// 即使沒有移動位置,如果設定了 pinned,也需要轉換為混合模式
|
|
676
|
+
const currentWidths = getColumnWidths(element);
|
|
677
|
+
if (currentWidths.length > 0) {
|
|
678
|
+
const { pinnedColumnIndices } = getPinnedColumnsInfo(element);
|
|
679
|
+
// 找出所有已經是 title 的 columns
|
|
680
|
+
const titleColumnIndices = new Set();
|
|
681
|
+
const firstRow = containerElement.children[0];
|
|
682
|
+
if (Element.isElement(firstRow) && firstRow.type.includes(TABLE_ROW_TYPE)) {
|
|
683
|
+
firstRow.children.forEach((cell, colIndex) => {
|
|
684
|
+
if (Element.isElement(cell) &&
|
|
685
|
+
cell.type.includes(TABLE_CELL_TYPE) &&
|
|
686
|
+
cell.treatAsTitle) {
|
|
687
|
+
titleColumnIndices.add(colIndex);
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
// 將當前 column 加入 title columns
|
|
692
|
+
titleColumnIndices.add(columnIndex);
|
|
693
|
+
// 合併所有 pinned columns 和 title columns
|
|
694
|
+
const allPinnedIndices = new Set([...pinnedColumnIndices, ...Array.from(titleColumnIndices)]);
|
|
695
|
+
const updatedPinnedIndices = Array.from(allPinnedIndices).sort((a, b) => a - b);
|
|
696
|
+
const mixedWidths = convertToMixedWidthMode(currentWidths, updatedPinnedIndices, tableWidth);
|
|
697
|
+
setColumnWidths(editor, element, mixedWidths);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
if (tableHeaderElement) {
|
|
702
|
+
processContainer(tableHeaderElement);
|
|
703
|
+
}
|
|
704
|
+
if (tableBodyElement) {
|
|
705
|
+
processContainer(tableBodyElement);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
catch (error) {
|
|
709
|
+
console.warn('Failed to set column as title:', error);
|
|
710
|
+
}
|
|
711
|
+
}, [editor, element]);
|
|
712
|
+
const pinColumn = useCallback((columnIndex) => {
|
|
713
|
+
try {
|
|
714
|
+
setColumnAsTitle(columnIndex, { pinned: true });
|
|
715
|
+
}
|
|
716
|
+
catch (error) {
|
|
717
|
+
console.warn('Failed to pin column:', error);
|
|
718
|
+
}
|
|
719
|
+
}, [setColumnAsTitle]);
|
|
720
|
+
const unpinColumn = useCallback(() => {
|
|
721
|
+
try {
|
|
722
|
+
const tableStructure = getTableStructure(editor, element);
|
|
723
|
+
if (!tableStructure)
|
|
724
|
+
return;
|
|
725
|
+
const { tableHeaderElement, tableBodyElement } = tableStructure;
|
|
726
|
+
// 檢查 column 與 row 之間是否有交叉 pinned 狀態的關係
|
|
727
|
+
const shouldRowRemainPinned = (rowElement, excludeColumns) => {
|
|
728
|
+
let hasNonExcludedCells = false;
|
|
729
|
+
for (let colIndex = 0; colIndex < rowElement.children.length; colIndex++) {
|
|
730
|
+
const cell = rowElement.children[colIndex];
|
|
731
|
+
if (!Element.isElement(cell) || !cell.type.includes(TABLE_CELL_TYPE))
|
|
732
|
+
continue;
|
|
733
|
+
if (excludeColumns.has(colIndex))
|
|
734
|
+
continue;
|
|
735
|
+
hasNonExcludedCells = true;
|
|
736
|
+
if (!cell.pinned) {
|
|
737
|
+
return false;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return hasNonExcludedCells;
|
|
741
|
+
};
|
|
742
|
+
const processContainer = (containerElement) => {
|
|
743
|
+
const containerPath = ReactEditor.findPath(editor, containerElement);
|
|
744
|
+
const treatAsTitleColumns = new Set();
|
|
745
|
+
containerElement.children.forEach((row) => {
|
|
746
|
+
if (Element.isElement(row) && row.type.includes(TABLE_ROW_TYPE)) {
|
|
747
|
+
row.children.forEach((cell, colIndex) => {
|
|
748
|
+
if (Element.isElement(cell) && cell.type.includes(TABLE_CELL_TYPE) && cell.treatAsTitle) {
|
|
749
|
+
treatAsTitleColumns.add(colIndex);
|
|
750
|
+
}
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
containerElement.children.forEach((row, rowIndex) => {
|
|
755
|
+
if (Element.isElement(row) && row.type.includes(TABLE_ROW_TYPE)) {
|
|
756
|
+
const rowShouldRemainPinned = shouldRowRemainPinned(row, treatAsTitleColumns);
|
|
757
|
+
row.children.forEach((cell, colIndex) => {
|
|
758
|
+
if (treatAsTitleColumns.has(colIndex) && Element.isElement(cell) && cell.type.includes(TABLE_CELL_TYPE)) {
|
|
759
|
+
const cellPath = [...containerPath, rowIndex, colIndex];
|
|
760
|
+
Transforms.unsetNodes(editor, 'treatAsTitle', { at: cellPath });
|
|
761
|
+
if (!rowShouldRemainPinned) {
|
|
762
|
+
Transforms.unsetNodes(editor, 'pinned', { at: cellPath });
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
};
|
|
769
|
+
if (tableHeaderElement) {
|
|
770
|
+
processContainer(tableHeaderElement);
|
|
771
|
+
}
|
|
772
|
+
if (tableBodyElement) {
|
|
773
|
+
processContainer(tableBodyElement);
|
|
774
|
+
}
|
|
775
|
+
// 轉換回純百分比模式
|
|
776
|
+
const currentWidths = getColumnWidths(element);
|
|
777
|
+
const percentageWidths = convertToPercentageMode(currentWidths);
|
|
778
|
+
setColumnWidths(editor, element, percentageWidths);
|
|
779
|
+
}
|
|
780
|
+
catch (error) {
|
|
781
|
+
console.warn('Failed to unpin column:', error);
|
|
782
|
+
}
|
|
783
|
+
}, [editor, element]);
|
|
784
|
+
const setPinnedOnRowCells = useCallback((row, pinned) => {
|
|
785
|
+
try {
|
|
786
|
+
for (const [, cell] of row.children.entries()) {
|
|
787
|
+
if (Element.isElement(cell) && cell.type.includes(TABLE_CELL_TYPE)) {
|
|
788
|
+
const cellPath = ReactEditor.findPath(editor, cell);
|
|
789
|
+
if (pinned) {
|
|
790
|
+
Transforms.setNodes(editor, { pinned: true }, { at: cellPath });
|
|
791
|
+
}
|
|
792
|
+
else {
|
|
793
|
+
Transforms.unsetNodes(editor, 'pinned', { at: cellPath });
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
catch (error) {
|
|
799
|
+
console.warn('Failed to set pinned on row cells:', error);
|
|
800
|
+
}
|
|
801
|
+
}, [editor]);
|
|
802
|
+
const setPinnedOnAllHeaderRows = useCallback((headerElement, pinned) => {
|
|
803
|
+
try {
|
|
804
|
+
for (const headerRow of headerElement.children) {
|
|
805
|
+
if (Element.isElement(headerRow) && headerRow.type.includes(TABLE_ROW_TYPE)) {
|
|
806
|
+
setPinnedOnRowCells(headerRow, pinned);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
catch (error) {
|
|
811
|
+
console.warn('Failed to set pinned on all header rows:', error);
|
|
812
|
+
}
|
|
813
|
+
}, [setPinnedOnRowCells]);
|
|
814
|
+
const pinRow = useCallback((rowIndex) => {
|
|
815
|
+
try {
|
|
816
|
+
const tableStructure = getTableStructure(editor, element);
|
|
817
|
+
if (!tableStructure)
|
|
818
|
+
return;
|
|
819
|
+
const { tableHeaderElement, headerRowCount } = tableStructure;
|
|
820
|
+
// 先將目前所有的 header rows 都設為 pinned
|
|
821
|
+
if (tableHeaderElement) {
|
|
822
|
+
setPinnedOnAllHeaderRows(tableHeaderElement, true);
|
|
823
|
+
}
|
|
824
|
+
// 然後將目標 row 移動到 header 中並設為 pinned
|
|
825
|
+
if (rowIndex >= headerRowCount) {
|
|
826
|
+
moveRowToHeader(rowIndex, { pinned: true });
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
catch (error) {
|
|
830
|
+
console.warn('Failed to pin row:', error);
|
|
831
|
+
}
|
|
832
|
+
}, [editor, element, moveRowToHeader, setPinnedOnAllHeaderRows]);
|
|
833
|
+
const unpinRow = useCallback(() => {
|
|
834
|
+
try {
|
|
835
|
+
const tableStructure = getTableStructure(editor, element);
|
|
836
|
+
if (!tableStructure)
|
|
837
|
+
return;
|
|
838
|
+
const { tableHeaderElement, tableBodyElement, tableHeaderPath } = tableStructure;
|
|
839
|
+
if (!tableHeaderElement || !tableBodyElement)
|
|
840
|
+
return;
|
|
841
|
+
// 檢查 column 與 row 之間是否有交叉 pinned 狀態的關係
|
|
842
|
+
const shouldColumnRemainPinned = (columnIndex) => {
|
|
843
|
+
const containers = [tableHeaderElement, tableBodyElement];
|
|
844
|
+
for (const container of containers) {
|
|
845
|
+
if (!Element.isElement(container))
|
|
846
|
+
continue;
|
|
847
|
+
for (const row of container.children) {
|
|
848
|
+
if (Element.isElement(row) && row.type.includes(TABLE_ROW_TYPE)) {
|
|
849
|
+
const cell = row.children[columnIndex];
|
|
850
|
+
if (Element.isElement(cell) && cell.type.includes(TABLE_CELL_TYPE)) {
|
|
851
|
+
// 如果這個 cell 在 body 中且有 pinned 屬性,則 column 應該保持 pinned
|
|
852
|
+
if (container.type === tableBodyElement.type && cell.pinned) {
|
|
853
|
+
return true;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
return false;
|
|
860
|
+
};
|
|
861
|
+
tableHeaderElement.children.forEach((row, headerRowIndex) => {
|
|
862
|
+
if (Element.isElement(row) && row.type.includes(TABLE_ROW_TYPE)) {
|
|
863
|
+
row.children.forEach((cell, colIndex) => {
|
|
864
|
+
if (Element.isElement(cell) && cell.type.includes(TABLE_CELL_TYPE)) {
|
|
865
|
+
const cellPath = [...tableHeaderPath, headerRowIndex, colIndex];
|
|
866
|
+
if (!shouldColumnRemainPinned(colIndex)) {
|
|
867
|
+
Transforms.unsetNodes(editor, 'pinned', { at: cellPath });
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
const tableBodyPath = ReactEditor.findPath(editor, tableBodyElement);
|
|
874
|
+
for (let i = tableHeaderElement.children.length - 1; i >= 0; i--) {
|
|
875
|
+
const row = tableHeaderElement.children[i];
|
|
876
|
+
if (Element.isElement(row) && row.type.includes(TABLE_ROW_TYPE)) {
|
|
877
|
+
const fromPath = [...tableHeaderPath, i];
|
|
878
|
+
const toPath = [...tableBodyPath, 0];
|
|
879
|
+
Transforms.moveNodes(editor, {
|
|
880
|
+
at: fromPath,
|
|
881
|
+
to: toPath,
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
catch (error) {
|
|
887
|
+
console.warn('Failed to unpin row:', error);
|
|
888
|
+
}
|
|
889
|
+
}, [editor, element]);
|
|
890
|
+
/**
|
|
891
|
+
* 內部函數:移動或交換列的位置
|
|
892
|
+
* @param mode 'swap' 為交換相鄰位置(toolbar 按鈕),'move' 為移動到任意位置(拖曳)
|
|
893
|
+
*/
|
|
894
|
+
const moveOrSwapRow = useCallback((sourceRowIndex, targetRowIndex, mode = 'move') => {
|
|
895
|
+
try {
|
|
896
|
+
const tableStructure = getTableStructure(editor, element);
|
|
897
|
+
if (!tableStructure)
|
|
898
|
+
return;
|
|
899
|
+
const { tableHeaderElement, tableBodyElement, tableHeaderPath, tableBodyPath, headerRowCount } = tableStructure;
|
|
900
|
+
// 確定當前列和目標列所屬的容器
|
|
901
|
+
const sourceInHeader = sourceRowIndex < headerRowCount;
|
|
902
|
+
const targetInHeader = targetRowIndex < headerRowCount;
|
|
903
|
+
// 標題列只能與標題列互換/移動,一般列只能與一般列互換/移動
|
|
904
|
+
if (sourceInHeader !== targetInHeader) {
|
|
905
|
+
console.warn(`Cannot ${mode} row between header and body`);
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
// 檢查邊界
|
|
909
|
+
if (sourceRowIndex === targetRowIndex) {
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
let containerPath;
|
|
913
|
+
let sourceLocalIndex;
|
|
914
|
+
let targetLocalIndex;
|
|
915
|
+
if (sourceInHeader) {
|
|
916
|
+
// 在 header 中
|
|
917
|
+
if (!tableHeaderElement || !tableHeaderPath)
|
|
918
|
+
return;
|
|
919
|
+
containerPath = tableHeaderPath;
|
|
920
|
+
sourceLocalIndex = sourceRowIndex;
|
|
921
|
+
targetLocalIndex = targetRowIndex;
|
|
922
|
+
}
|
|
923
|
+
else {
|
|
924
|
+
// 在 body 中
|
|
925
|
+
if (!tableBodyElement)
|
|
926
|
+
return;
|
|
927
|
+
containerPath = tableBodyPath;
|
|
928
|
+
sourceLocalIndex = sourceRowIndex - headerRowCount;
|
|
929
|
+
targetLocalIndex = targetRowIndex - headerRowCount;
|
|
930
|
+
}
|
|
931
|
+
Editor.withoutNormalizing(editor, () => {
|
|
932
|
+
if (mode === 'swap') {
|
|
933
|
+
// swap 邏輯:交換兩個相鄰位置
|
|
934
|
+
if (sourceRowIndex < targetRowIndex) {
|
|
935
|
+
// 向下移動:先將源列移到目標位置之後
|
|
936
|
+
const sourcePath = [...containerPath, sourceLocalIndex];
|
|
937
|
+
const afterTargetPath = [...containerPath, targetLocalIndex];
|
|
938
|
+
Transforms.moveNodes(editor, {
|
|
939
|
+
at: sourcePath,
|
|
940
|
+
to: afterTargetPath,
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
else {
|
|
944
|
+
// 向上移動:先將目標列移到源位置之後
|
|
945
|
+
const targetPath = [...containerPath, targetLocalIndex];
|
|
946
|
+
const afterSourcePath = [...containerPath, sourceLocalIndex];
|
|
947
|
+
Transforms.moveNodes(editor, {
|
|
948
|
+
at: targetPath,
|
|
949
|
+
to: afterSourcePath,
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
else {
|
|
954
|
+
// move 邏輯:直接移動到目標位置
|
|
955
|
+
const sourcePath = [...containerPath, sourceLocalIndex];
|
|
956
|
+
const targetPath = [...containerPath, targetLocalIndex];
|
|
957
|
+
Transforms.moveNodes(editor, {
|
|
958
|
+
at: sourcePath,
|
|
959
|
+
to: targetPath,
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
catch (error) {
|
|
965
|
+
console.warn(`Failed to ${mode} row:`, error);
|
|
966
|
+
}
|
|
967
|
+
}, [editor, element]);
|
|
968
|
+
/**
|
|
969
|
+
* 內部函數:移動或交換行的位置
|
|
970
|
+
* @param mode 'swap' 為交換相鄰位置(toolbar 按鈕),'move' 為移動到任意位置(拖曳)
|
|
971
|
+
*/
|
|
972
|
+
const moveOrSwapColumn = useCallback((sourceColumnIndex, targetColumnIndex, mode = 'move') => {
|
|
973
|
+
try {
|
|
974
|
+
const tableStructure = getTableStructure(editor, element);
|
|
975
|
+
if (!tableStructure)
|
|
976
|
+
return;
|
|
977
|
+
const { tableHeaderElement, tableBodyElement, columnCount } = tableStructure;
|
|
978
|
+
// 檢查邊界
|
|
979
|
+
if (targetColumnIndex < 0 || targetColumnIndex >= columnCount) {
|
|
980
|
+
console.warn('Target column index out of bounds');
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
// 檢查是否為同一行
|
|
984
|
+
if (sourceColumnIndex === targetColumnIndex) {
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
// 檢查當前行和目標行是否都是標題行或都是一般行
|
|
988
|
+
// 透過檢查第一個 cell 的 treatAsTitle 屬性來判斷
|
|
989
|
+
const checkIsTitleColumn = (container, colIndex) => {
|
|
990
|
+
if (!Element.isElement(container))
|
|
991
|
+
return false;
|
|
992
|
+
for (const row of container.children) {
|
|
993
|
+
if (Element.isElement(row) && row.type.includes(TABLE_ROW_TYPE)) {
|
|
994
|
+
const cell = row.children[colIndex];
|
|
995
|
+
if (Element.isElement(cell) && cell.type.includes(TABLE_CELL_TYPE)) {
|
|
996
|
+
return !!cell.treatAsTitle;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
return false;
|
|
1001
|
+
};
|
|
1002
|
+
// 檢查兩個 container 中的第一列來確定是否為標題行
|
|
1003
|
+
let sourceIsTitle = false;
|
|
1004
|
+
let targetIsTitle = false;
|
|
1005
|
+
if (tableHeaderElement) {
|
|
1006
|
+
sourceIsTitle = sourceIsTitle || checkIsTitleColumn(tableHeaderElement, sourceColumnIndex);
|
|
1007
|
+
targetIsTitle = targetIsTitle || checkIsTitleColumn(tableHeaderElement, targetColumnIndex);
|
|
1008
|
+
}
|
|
1009
|
+
if (tableBodyElement) {
|
|
1010
|
+
sourceIsTitle = sourceIsTitle || checkIsTitleColumn(tableBodyElement, sourceColumnIndex);
|
|
1011
|
+
targetIsTitle = targetIsTitle || checkIsTitleColumn(tableBodyElement, targetColumnIndex);
|
|
1012
|
+
}
|
|
1013
|
+
// 標題行只能與標題行互換/移動,一般行只能與一般行互換/移動
|
|
1014
|
+
if (sourceIsTitle !== targetIsTitle) {
|
|
1015
|
+
console.warn(`Cannot ${mode} column between title and normal columns`);
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
// 根據模式選擇不同的 columnWidths 處理方式
|
|
1019
|
+
const currentWidths = getColumnWidths(element);
|
|
1020
|
+
const newWidths = moveOrSwapColumnWidth(currentWidths, sourceColumnIndex, targetColumnIndex, mode);
|
|
1021
|
+
setColumnWidths(editor, element, newWidths);
|
|
1022
|
+
// 對 header 和 body 中的所有列進行操作
|
|
1023
|
+
Editor.withoutNormalizing(editor, () => {
|
|
1024
|
+
const containers = [tableHeaderElement, tableBodyElement].filter((c) => c && Element.isElement(c));
|
|
1025
|
+
for (const container of containers) {
|
|
1026
|
+
// 對每一列進行操作
|
|
1027
|
+
for (let rowIndex = 0; rowIndex < container.children.length; rowIndex++) {
|
|
1028
|
+
const row = container.children[rowIndex];
|
|
1029
|
+
if (!Element.isElement(row) || !row.type.includes(TABLE_ROW_TYPE))
|
|
1030
|
+
continue;
|
|
1031
|
+
const containerPath = ReactEditor.findPath(editor, container);
|
|
1032
|
+
const rowPath = [...containerPath, rowIndex];
|
|
1033
|
+
if (mode === 'swap') {
|
|
1034
|
+
// swap 邏輯:交換兩個相鄰位置
|
|
1035
|
+
if (sourceColumnIndex < targetColumnIndex) {
|
|
1036
|
+
// 向右移動:將源 cell 移到目標位置之後
|
|
1037
|
+
const sourceCellPath = [...rowPath, sourceColumnIndex];
|
|
1038
|
+
const afterTargetCellPath = [...rowPath, targetColumnIndex];
|
|
1039
|
+
Transforms.moveNodes(editor, {
|
|
1040
|
+
at: sourceCellPath,
|
|
1041
|
+
to: afterTargetCellPath,
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
else {
|
|
1045
|
+
// 向左移動:將目標 cell 移到源位置之後
|
|
1046
|
+
const targetCellPath = [...rowPath, targetColumnIndex];
|
|
1047
|
+
const afterSourceCellPath = [...rowPath, sourceColumnIndex];
|
|
1048
|
+
Transforms.moveNodes(editor, {
|
|
1049
|
+
at: targetCellPath,
|
|
1050
|
+
to: afterSourceCellPath,
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
else {
|
|
1055
|
+
// move 邏輯:直接移動到目標位置
|
|
1056
|
+
const sourceCellPath = [...rowPath, sourceColumnIndex];
|
|
1057
|
+
const targetCellPath = [...rowPath, targetColumnIndex];
|
|
1058
|
+
Transforms.moveNodes(editor, {
|
|
1059
|
+
at: sourceCellPath,
|
|
1060
|
+
to: targetCellPath,
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
catch (error) {
|
|
1068
|
+
console.warn(`Failed to ${mode} column:`, error);
|
|
1069
|
+
}
|
|
1070
|
+
}, [editor, element]);
|
|
1071
|
+
return {
|
|
1072
|
+
addColumn,
|
|
1073
|
+
addRow,
|
|
1074
|
+
addColumnAndRow,
|
|
1075
|
+
deleteRow,
|
|
1076
|
+
deleteColumn,
|
|
1077
|
+
moveRowToBody,
|
|
1078
|
+
moveRowToHeader,
|
|
1079
|
+
unsetColumnAsTitle,
|
|
1080
|
+
setColumnAsTitle,
|
|
1081
|
+
pinColumn,
|
|
1082
|
+
unpinColumn,
|
|
1083
|
+
pinRow,
|
|
1084
|
+
unpinRow,
|
|
1085
|
+
isColumnPinned,
|
|
1086
|
+
isRowPinned,
|
|
1087
|
+
moveOrSwapRow,
|
|
1088
|
+
moveOrSwapColumn,
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
export { useTableActions };
|