@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.
Files changed (119) hide show
  1. package/card/components/Card.js +83 -75
  2. package/card/index.cjs.js +82 -74
  3. package/carousel/components/Carousel.js +32 -28
  4. package/carousel/index.cjs.js +32 -28
  5. package/components/Tooltip/index.js +5 -2
  6. package/components/index.cjs.js +5 -2
  7. package/core/components/Quadrats.js +5 -2
  8. package/core/contexts/modal/CarouselModal/CarouselModal.js +14 -17
  9. package/embed/renderers/base/components/BaseEmbedElement.js +51 -43
  10. package/embed/renderers/base/index.cjs.js +50 -42
  11. package/image/components/Image.js +34 -26
  12. package/image/createReactImage.js +1 -1
  13. package/image/index.cjs.js +34 -26
  14. package/index.cjs.js +19 -19
  15. package/package.json +4 -4
  16. package/table/components/ColumnDragButton.d.ts +10 -0
  17. package/table/components/ColumnDragButton.js +41 -0
  18. package/table/components/RowDragButton.d.ts +10 -0
  19. package/table/components/RowDragButton.js +42 -0
  20. package/table/components/Table.d.ts +9 -0
  21. package/table/components/Table.js +236 -0
  22. package/table/components/TableBody.d.ts +5 -0
  23. package/table/components/TableBody.js +8 -0
  24. package/table/components/TableCell.d.ts +5 -0
  25. package/table/components/TableCell.js +297 -0
  26. package/table/components/TableDragLayer.d.ts +6 -0
  27. package/table/components/TableDragLayer.js +89 -0
  28. package/table/components/TableHeader.d.ts +5 -0
  29. package/table/components/TableHeader.js +13 -0
  30. package/table/components/TableMain.d.ts +5 -0
  31. package/table/components/TableMain.js +233 -0
  32. package/table/components/TableRow.d.ts +5 -0
  33. package/table/components/TableRow.js +8 -0
  34. package/table/components/TableTitle.d.ts +5 -0
  35. package/table/components/TableTitle.js +18 -0
  36. package/table/contexts/TableActionsContext.d.ts +3 -0
  37. package/table/contexts/TableActionsContext.js +5 -0
  38. package/table/contexts/TableDragContext.d.ts +26 -0
  39. package/table/contexts/TableDragContext.js +26 -0
  40. package/table/contexts/TableHeaderContext.d.ts +2 -0
  41. package/table/contexts/TableHeaderContext.js +7 -0
  42. package/table/contexts/TableMetadataContext.d.ts +3 -0
  43. package/table/contexts/TableMetadataContext.js +5 -0
  44. package/table/contexts/TableScrollContext.d.ts +2 -0
  45. package/table/contexts/TableScrollContext.js +9 -0
  46. package/table/contexts/TableStateContext.d.ts +3 -0
  47. package/table/contexts/TableStateContext.js +5 -0
  48. package/table/createReactTable.d.ts +4 -0
  49. package/table/createReactTable.js +297 -0
  50. package/table/defaultRenderTableElements.d.ts +2 -0
  51. package/table/defaultRenderTableElements.js +20 -0
  52. package/table/hooks/useColumnResize.d.ts +12 -0
  53. package/table/hooks/useColumnResize.js +168 -0
  54. package/table/hooks/useTableActions.d.ts +27 -0
  55. package/table/hooks/useTableActions.js +1092 -0
  56. package/table/hooks/useTableActionsContext.d.ts +1 -0
  57. package/table/hooks/useTableActionsContext.js +12 -0
  58. package/table/hooks/useTableCell.d.ts +16 -0
  59. package/table/hooks/useTableCell.js +166 -0
  60. package/table/hooks/useTableCellToolbarActions.d.ts +34 -0
  61. package/table/hooks/useTableCellToolbarActions.js +526 -0
  62. package/table/hooks/useTableMetadata.d.ts +1 -0
  63. package/table/hooks/useTableMetadata.js +12 -0
  64. package/table/hooks/useTableStateContext.d.ts +1 -0
  65. package/table/hooks/useTableStateContext.js +12 -0
  66. package/table/hooks/useTableStates.d.ts +18 -0
  67. package/table/hooks/useTableStates.js +14 -0
  68. package/table/index.cjs.js +4002 -0
  69. package/table/index.d.ts +16 -0
  70. package/table/index.js +27 -0
  71. package/table/jsx-serializer/components/Table.d.ts +3 -0
  72. package/table/jsx-serializer/components/Table.js +7 -0
  73. package/table/jsx-serializer/components/TableBody.d.ts +3 -0
  74. package/table/jsx-serializer/components/TableBody.js +7 -0
  75. package/table/jsx-serializer/components/TableCell.d.ts +5 -0
  76. package/table/jsx-serializer/components/TableCell.js +33 -0
  77. package/table/jsx-serializer/components/TableHeader.d.ts +3 -0
  78. package/table/jsx-serializer/components/TableHeader.js +10 -0
  79. package/table/jsx-serializer/components/TableMain.d.ts +6 -0
  80. package/table/jsx-serializer/components/TableMain.js +18 -0
  81. package/table/jsx-serializer/components/TableRow.d.ts +3 -0
  82. package/table/jsx-serializer/components/TableRow.js +7 -0
  83. package/table/jsx-serializer/components/TableTitle.d.ts +3 -0
  84. package/table/jsx-serializer/components/TableTitle.js +7 -0
  85. package/table/jsx-serializer/contexts/TableHeaderContext.d.ts +1 -0
  86. package/table/jsx-serializer/contexts/TableHeaderContext.js +5 -0
  87. package/table/jsx-serializer/contexts/TableScrollContext.d.ts +2 -0
  88. package/table/jsx-serializer/contexts/TableScrollContext.js +7 -0
  89. package/table/jsx-serializer/createJsxSerializeTable.d.ts +5 -0
  90. package/table/jsx-serializer/createJsxSerializeTable.js +113 -0
  91. package/table/jsx-serializer/defaultRenderTableElements.d.ts +2 -0
  92. package/table/jsx-serializer/defaultRenderTableElements.js +20 -0
  93. package/table/jsx-serializer/index.cjs.js +195 -0
  94. package/table/jsx-serializer/index.d.ts +3 -0
  95. package/table/jsx-serializer/index.js +2 -0
  96. package/table/jsx-serializer/package.json +7 -0
  97. package/table/jsx-serializer/typings.d.ts +12 -0
  98. package/table/package.json +10 -0
  99. package/table/table.css +1 -0
  100. package/table/table.scss +428 -0
  101. package/table/toolbar/TableToolbarIcon.d.ts +8 -0
  102. package/table/toolbar/TableToolbarIcon.js +12 -0
  103. package/table/toolbar/index.cjs.js +24 -0
  104. package/table/toolbar/index.d.ts +2 -0
  105. package/table/toolbar/index.js +2 -0
  106. package/table/toolbar/package.json +7 -0
  107. package/table/toolbar/useTableTool.d.ts +4 -0
  108. package/table/toolbar/useTableTool.js +13 -0
  109. package/table/typings.d.ts +68 -0
  110. package/table/utils/helper.d.ts +186 -0
  111. package/table/utils/helper.js +799 -0
  112. package/toolbar/components/InlineToolbar.d.ts +12 -11
  113. package/toolbar/components/InlineToolbar.js +23 -19
  114. package/toolbar/components/Toolbar.js +2 -2
  115. package/toolbar/index.cjs.js +24 -21
  116. package/toolbar/toolbar.css +1 -1
  117. package/toolbar/toolbar.scss +4 -1
  118. package/utils/index.cjs.js +7 -1
  119. package/utils/removePreviousElement.js +7 -1
@@ -0,0 +1,236 @@
1
+ import React, { useRef, useMemo, useCallback } from 'react';
2
+ import { Element } from '@quadrats/core';
3
+ import { TableActionsContext } from '../contexts/TableActionsContext.js';
4
+ import { TableMetadataContext } from '../contexts/TableMetadataContext.js';
5
+ import { TableStateContext } from '../contexts/TableStateContext.js';
6
+ import { TableDragProvider } from '../contexts/TableDragContext.js';
7
+ import { TABLE_ROW_TYPE, TABLE_CELL_TYPE, TABLE_DEFAULT_MAX_COLUMNS, TABLE_DEFAULT_MAX_ROWS } from '@quadrats/common/table';
8
+ import { useTableActions } from '../hooks/useTableActions.js';
9
+ import { Icon } from '@quadrats/react/components';
10
+ import { Drag } from '@quadrats/icons';
11
+ import { useTableStates } from '../hooks/useTableStates.js';
12
+ import { getTableElements } from '../utils/helper.js';
13
+
14
+ function Table({ attributes, children, element, }) {
15
+ const { addColumn, addRow, addColumnAndRow, deleteRow, deleteColumn, moveRowToBody, moveRowToHeader, unsetColumnAsTitle, setColumnAsTitle, pinColumn, pinRow, unpinColumn, unpinRow, moveOrSwapRow, moveOrSwapColumn, } = useTableActions(element);
16
+ const { tableSelectedOn, setTableSelectedOn, tableHoveredOn, setTableHoveredOn } = useTableStates();
17
+ const portalContainerRef = useRef(null);
18
+ const { columnCount, rowCount, normalCols, bodyCount, tableElements } = useMemo(() => {
19
+ const elements = getTableElements(element);
20
+ if (!elements.tableMainElement) {
21
+ return {
22
+ columnCount: 0,
23
+ rowCount: 0,
24
+ treatAsTitleCols: 0,
25
+ normalCols: 0,
26
+ bodyCount: 0,
27
+ headerCount: 0,
28
+ tableElements: elements,
29
+ };
30
+ }
31
+ const headerRowElements = elements.tableHeaderElement
32
+ ? elements.tableHeaderElement.children.filter((child) => Element.isElement(child) && child.type.includes(TABLE_ROW_TYPE))
33
+ : [];
34
+ const bodyRowElements = elements.tableBodyElement
35
+ ? elements.tableBodyElement.children.filter((child) => Element.isElement(child) && child.type.includes(TABLE_ROW_TYPE))
36
+ : [];
37
+ const cols = bodyRowElements.length > 0 && Element.isElement(bodyRowElements[0]) ? bodyRowElements[0].children.length : 0;
38
+ const treatAsTitleCols = bodyRowElements.length > 0 && Element.isElement(bodyRowElements[0])
39
+ ? bodyRowElements[0].children.filter((row) => (Element.isElement(row) ? row.treatAsTitle : false)).length
40
+ : 0;
41
+ const rows = headerRowElements.length + bodyRowElements.length;
42
+ return {
43
+ columnCount: cols,
44
+ rowCount: rows,
45
+ headerCount: headerRowElements.length,
46
+ bodyCount: bodyRowElements.length,
47
+ treatAsTitleCols,
48
+ normalCols: cols - treatAsTitleCols,
49
+ tableElements: elements,
50
+ };
51
+ }, [element]);
52
+ // 預計算所有 cell positions
53
+ const cellPositions = useMemo(() => {
54
+ const positions = new Map();
55
+ if (!tableElements.tableMainElement)
56
+ return positions;
57
+ const { tableHeaderElement, tableBodyElement } = tableElements;
58
+ let globalRowIndex = 0;
59
+ // Process header rows
60
+ if (tableHeaderElement && Element.isElement(tableHeaderElement)) {
61
+ for (const row of tableHeaderElement.children) {
62
+ if (Element.isElement(row) && row.type.includes(TABLE_ROW_TYPE)) {
63
+ for (let colIndex = 0; colIndex < row.children.length; colIndex++) {
64
+ const cell = row.children[colIndex];
65
+ if (Element.isElement(cell) && cell.type.includes(TABLE_CELL_TYPE)) {
66
+ positions.set(cell, {
67
+ columnIndex: colIndex,
68
+ rowIndex: globalRowIndex,
69
+ });
70
+ }
71
+ }
72
+ globalRowIndex++;
73
+ }
74
+ }
75
+ }
76
+ // Process body rows
77
+ if (tableBodyElement && Element.isElement(tableBodyElement)) {
78
+ for (const row of tableBodyElement.children) {
79
+ if (Element.isElement(row) && row.type.includes(TABLE_ROW_TYPE)) {
80
+ for (let colIndex = 0; colIndex < row.children.length; colIndex++) {
81
+ const cell = row.children[colIndex];
82
+ if (Element.isElement(cell) && cell.type.includes(TABLE_CELL_TYPE)) {
83
+ positions.set(cell, {
84
+ columnIndex: colIndex,
85
+ rowIndex: globalRowIndex,
86
+ });
87
+ }
88
+ }
89
+ globalRowIndex++;
90
+ }
91
+ }
92
+ }
93
+ return positions;
94
+ }, [tableElements]);
95
+ // 預計算 pinned columns 和 rows
96
+ const { pinnedColumns, pinnedRows } = useMemo(() => {
97
+ const columns = new Set();
98
+ const rows = new Set();
99
+ if (!tableElements.tableBodyElement)
100
+ return { pinnedColumns: columns, pinnedRows: rows };
101
+ const { tableHeaderElement, tableBodyElement } = tableElements;
102
+ // 檢查 pinned columns - 只需要檢查第一個 body row
103
+ if (Element.isElement(tableBodyElement) && tableBodyElement.children.length > 0) {
104
+ const firstBodyRow = tableBodyElement.children[0];
105
+ if (Element.isElement(firstBodyRow) && firstBodyRow.type.includes(TABLE_ROW_TYPE)) {
106
+ for (let colIndex = 0; colIndex < firstBodyRow.children.length; colIndex++) {
107
+ const cell = firstBodyRow.children[colIndex];
108
+ if (Element.isElement(cell) &&
109
+ cell.type.includes(TABLE_CELL_TYPE) &&
110
+ cell.treatAsTitle &&
111
+ cell.pinned) {
112
+ columns.add(colIndex);
113
+ }
114
+ }
115
+ }
116
+ }
117
+ // 檢查 pinned rows - 只需要檢查 header rows
118
+ if (tableHeaderElement && Element.isElement(tableHeaderElement)) {
119
+ for (let rowIndex = 0; rowIndex < tableHeaderElement.children.length; rowIndex++) {
120
+ const row = tableHeaderElement.children[rowIndex];
121
+ if (Element.isElement(row) && row.type.includes(TABLE_ROW_TYPE)) {
122
+ // 檢查這一行的所有 cells 是否都是 pinned
123
+ const allCellsPinned = row.children.every((cell) => Element.isElement(cell) && cell.type.includes(TABLE_CELL_TYPE) && cell.pinned);
124
+ if (allCellsPinned) {
125
+ rows.add(rowIndex);
126
+ }
127
+ }
128
+ }
129
+ }
130
+ return { pinnedColumns: columns, pinnedRows: rows };
131
+ }, [tableElements]);
132
+ const isReachMaximumColumns = useMemo(() => {
133
+ return columnCount >= TABLE_DEFAULT_MAX_COLUMNS;
134
+ }, [columnCount]);
135
+ const isReachMaximumRows = useMemo(() => {
136
+ return TABLE_DEFAULT_MAX_ROWS > 0 ? rowCount >= TABLE_DEFAULT_MAX_ROWS : false;
137
+ }, [rowCount]);
138
+ const isReachMinimumNormalColumns = useMemo(() => {
139
+ return normalCols <= 1;
140
+ }, [normalCols]);
141
+ const isReachMinimumBodyRows = useMemo(() => {
142
+ return bodyCount <= 1;
143
+ }, [bodyCount]);
144
+ const isColumnPinned = useCallback((columnIndex) => {
145
+ return pinnedColumns.has(columnIndex);
146
+ }, [pinnedColumns]);
147
+ const isRowPinned = useCallback((rowIndex) => {
148
+ return pinnedRows.has(rowIndex);
149
+ }, [pinnedRows]);
150
+ const actionsValue = useMemo(() => ({
151
+ addColumn,
152
+ addRow,
153
+ addColumnAndRow,
154
+ deleteRow,
155
+ deleteColumn,
156
+ moveRowToBody,
157
+ moveRowToHeader,
158
+ unsetColumnAsTitle,
159
+ setColumnAsTitle,
160
+ pinColumn,
161
+ pinRow,
162
+ unpinColumn,
163
+ unpinRow,
164
+ moveOrSwapRow,
165
+ moveOrSwapColumn,
166
+ }), [
167
+ addColumn,
168
+ addRow,
169
+ addColumnAndRow,
170
+ deleteRow,
171
+ deleteColumn,
172
+ moveRowToBody,
173
+ moveRowToHeader,
174
+ unsetColumnAsTitle,
175
+ setColumnAsTitle,
176
+ pinColumn,
177
+ pinRow,
178
+ unpinColumn,
179
+ unpinRow,
180
+ moveOrSwapRow,
181
+ moveOrSwapColumn,
182
+ ]);
183
+ const metadataValue = useMemo(() => ({
184
+ tableElement: element,
185
+ columnCount,
186
+ rowCount,
187
+ portalContainerRef,
188
+ isReachMaximumColumns,
189
+ isReachMaximumRows,
190
+ isReachMinimumNormalColumns,
191
+ isReachMinimumBodyRows,
192
+ pinnedColumns,
193
+ pinnedRows,
194
+ cellPositions,
195
+ isColumnPinned,
196
+ isRowPinned,
197
+ }), [
198
+ element,
199
+ columnCount,
200
+ rowCount,
201
+ portalContainerRef,
202
+ isReachMaximumColumns,
203
+ isReachMaximumRows,
204
+ isReachMinimumNormalColumns,
205
+ isReachMinimumBodyRows,
206
+ pinnedColumns,
207
+ pinnedRows,
208
+ cellPositions,
209
+ isColumnPinned,
210
+ isRowPinned,
211
+ ]);
212
+ const stateValue = useMemo(() => ({
213
+ tableSelectedOn,
214
+ setTableSelectedOn,
215
+ tableHoveredOn,
216
+ setTableHoveredOn,
217
+ }), [tableSelectedOn, setTableSelectedOn, tableHoveredOn, setTableHoveredOn]);
218
+ return (React.createElement(TableDragProvider, null,
219
+ React.createElement(TableActionsContext.Provider, { value: actionsValue },
220
+ React.createElement(TableMetadataContext.Provider, { value: metadataValue },
221
+ React.createElement(TableStateContext.Provider, { value: stateValue },
222
+ React.createElement("div", Object.assign({}, attributes, { className: "qdr-table" }),
223
+ children,
224
+ React.createElement("button", { type: "button", onClick: (evt) => {
225
+ evt.preventDefault();
226
+ evt.stopPropagation();
227
+ setTableSelectedOn((prev) => ((prev === null || prev === void 0 ? void 0 : prev.region) === 'table' ? undefined : { region: 'table' }));
228
+ }, onMouseDown: (evt) => {
229
+ evt.preventDefault();
230
+ evt.stopPropagation();
231
+ }, className: "qdr-table__selection", title: (tableSelectedOn === null || tableSelectedOn === void 0 ? void 0 : tableSelectedOn.region) === 'table' ? 'Deselect Table' : 'Select Table' },
232
+ React.createElement(Icon, { icon: Drag, width: 20, height: 20 })),
233
+ React.createElement("div", { ref: portalContainerRef, className: "qdr-table__portal-container", "data-slate-editor": false, contentEditable: false })))))));
234
+ }
235
+
236
+ export { Table as default };
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+ import { RenderElementProps } from '@quadrats/react';
3
+ import { TableElement } from '@quadrats/common/table';
4
+ declare function TableBody(props: RenderElementProps<TableElement>): React.JSX.Element;
5
+ export default TableBody;
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+
3
+ function TableBody(props) {
4
+ const { attributes, children } = props;
5
+ return (React.createElement("tbody", Object.assign({}, attributes, { className: "qdr-table__body" }), children));
6
+ }
7
+
8
+ export { TableBody as default };
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+ import { RenderElementProps } from '@quadrats/react';
3
+ import { TableElement } from '@quadrats/common/table';
4
+ declare function TableCell(props: RenderElementProps<TableElement>): React.JSX.Element;
5
+ export default TableCell;
@@ -0,0 +1,297 @@
1
+ import React, { useContext, useCallback, useMemo, useRef, useState, useEffect } from 'react';
2
+ import clsx from 'clsx';
3
+ import { Element, Transforms } from '@quadrats/core';
4
+ import { useSlateStatic } from 'slate-react';
5
+ import { useDrop } from 'react-dnd';
6
+ import { TableHeaderContext } from '../contexts/TableHeaderContext.js';
7
+ import { Portal } from '@quadrats/react/components';
8
+ import { useTableMetadata } from '../hooks/useTableMetadata.js';
9
+ import { useTableStateContext } from '../hooks/useTableStateContext.js';
10
+ import { InlineToolbar } from '@quadrats/react/toolbar';
11
+ import { useTableCellFocused, useTableCellPosition, useTableCellTransformContent } from '../hooks/useTableCell.js';
12
+ import { useTableCellToolbarActions } from '../hooks/useTableCellToolbarActions.js';
13
+ import { TableScrollContext } from '../contexts/TableScrollContext.js';
14
+ import { useColumnResize } from '../hooks/useColumnResize.js';
15
+ import { useTableActionsContext } from '../hooks/useTableActionsContext.js';
16
+ import { useTableDragContext } from '../contexts/TableDragContext.js';
17
+ import { ROW_DRAG_TYPE, RowDragButton } from './RowDragButton.js';
18
+ import { COLUMN_DRAG_TYPE, ColumnDragButton } from './ColumnDragButton.js';
19
+ import { getTableElements } from '../utils/helper.js';
20
+
21
+ function TableCell(props) {
22
+ var _a, _b;
23
+ const { attributes, children, element } = props;
24
+ const { tableSelectedOn, setTableSelectedOn, tableHoveredOn, setTableHoveredOn } = useTableStateContext();
25
+ const { columnCount, rowCount, portalContainerRef, isColumnPinned, tableElement } = useTableMetadata();
26
+ const { moveOrSwapRow, moveOrSwapColumn } = useTableActionsContext();
27
+ const { dragState, dropTargetIndex, setDropTargetIndex, dragDirection, setDragDirection } = useTableDragContext();
28
+ // Component context
29
+ const { isHeader } = useContext(TableHeaderContext);
30
+ const { scrollTop, scrollLeft, scrollRef } = useContext(TableScrollContext);
31
+ const editor = useSlateStatic();
32
+ // Cell-specific hooks
33
+ const focused = useTableCellFocused(element, editor);
34
+ const cellPosition = useTableCellPosition(element);
35
+ const transformCellContent = useTableCellTransformContent(element, editor);
36
+ // Get header row count from table structure
37
+ const tableElements = getTableElements(tableElement);
38
+ const headerRowCount = ((_a = tableElements.tableHeaderElement) === null || _a === void 0 ? void 0 : _a.children.length) || 0;
39
+ // Helper to check if column is title column
40
+ const checkIsTitleColumn = useCallback((colIndex) => {
41
+ const elements = getTableElements(tableElement);
42
+ if (!elements.tableBodyElement)
43
+ return false;
44
+ const firstRow = elements.tableBodyElement.children[0];
45
+ if (!Element.isElement(firstRow))
46
+ return false;
47
+ const cell = firstRow.children[colIndex];
48
+ return Element.isElement(cell) && !!cell.treatAsTitle;
49
+ }, [tableElement]);
50
+ // Toolbar actions
51
+ const { focusToolbarIconGroups, inlineToolbarIconGroups } = useTableCellToolbarActions({
52
+ element,
53
+ cellPosition,
54
+ isHeader,
55
+ transformCellContent,
56
+ });
57
+ const isSelectedInSameRow = (tableSelectedOn === null || tableSelectedOn === void 0 ? void 0 : tableSelectedOn.region) === 'row' && (tableSelectedOn === null || tableSelectedOn === void 0 ? void 0 : tableSelectedOn.index) === cellPosition.rowIndex;
58
+ const isSelectedInSameColumn = (tableSelectedOn === null || tableSelectedOn === void 0 ? void 0 : tableSelectedOn.region) === 'column' && (tableSelectedOn === null || tableSelectedOn === void 0 ? void 0 : tableSelectedOn.index) === cellPosition.columnIndex;
59
+ // 判斷是否為正在被拖曳的 row/column
60
+ const isDraggingThisRow = (dragState === null || dragState === void 0 ? void 0 : dragState.type) === 'row' && dragState.rowIndex === cellPosition.rowIndex;
61
+ const isDraggingThisColumn = (dragState === null || dragState === void 0 ? void 0 : dragState.type) === 'column' && dragState.columnIndex === cellPosition.columnIndex;
62
+ const isSelectionTriggerByMe = (isSelectedInSameRow && cellPosition.columnIndex === 0) || (isSelectedInSameColumn && cellPosition.rowIndex === 0);
63
+ const showColumnActionButton = useMemo(() => cellPosition.rowIndex === 0 &&
64
+ (isSelectedInSameColumn || ((tableHoveredOn === null || tableHoveredOn === void 0 ? void 0 : tableHoveredOn.columnIndex) === cellPosition.columnIndex && !tableSelectedOn)), [cellPosition, isSelectedInSameColumn, tableHoveredOn, tableSelectedOn]);
65
+ const showRowActionButton = useMemo(() => cellPosition.columnIndex === 0 &&
66
+ (isSelectedInSameRow || ((tableHoveredOn === null || tableHoveredOn === void 0 ? void 0 : tableHoveredOn.rowIndex) === cellPosition.rowIndex && !tableSelectedOn)), [cellPosition, isSelectedInSameRow, tableHoveredOn, tableSelectedOn]);
67
+ // 用於定位 InlineToolbar 的 ref
68
+ const cellRef = useRef(null);
69
+ const [toolbarPosition, setToolbarPosition] = useState(null);
70
+ const [rowButtonPosition, setRowButtonPosition] = useState(null);
71
+ const [columnButtonPosition, setColumnButtonPosition] = useState(null);
72
+ const [cellStuckAtLeft, setCellStuckAtLeft] = useState(undefined);
73
+ const [, dropRow] = useDrop(() => ({
74
+ accept: ROW_DRAG_TYPE,
75
+ canDrop: (item) => {
76
+ if (!dragState || dragState.type !== 'row')
77
+ return false;
78
+ if (item.rowIndex === cellPosition.rowIndex)
79
+ return false;
80
+ const sourceInHeader = item.isInHeader;
81
+ const targetInHeader = cellPosition.rowIndex < headerRowCount;
82
+ return sourceInHeader === targetInHeader;
83
+ },
84
+ hover: (item, monitor) => {
85
+ if (!monitor.canDrop()) {
86
+ setDropTargetIndex(null);
87
+ setDragDirection(null);
88
+ return;
89
+ }
90
+ const direction = item.rowIndex < cellPosition.rowIndex ? 'down' : 'up';
91
+ setDropTargetIndex(cellPosition.rowIndex);
92
+ setDragDirection(direction);
93
+ },
94
+ drop: (item) => {
95
+ moveOrSwapRow(item.rowIndex, cellPosition.rowIndex, 'move');
96
+ setDropTargetIndex(null);
97
+ setDragDirection(null);
98
+ },
99
+ }), [dragState, cellPosition.rowIndex, headerRowCount, moveOrSwapRow, setDropTargetIndex, setDragDirection]);
100
+ // Drop target logic for columns
101
+ const [, dropColumn] = useDrop(() => ({
102
+ accept: COLUMN_DRAG_TYPE,
103
+ canDrop: (item) => {
104
+ if (!dragState || dragState.type !== 'column')
105
+ return false;
106
+ if (item.columnIndex === cellPosition.columnIndex)
107
+ return false;
108
+ const sourceIsTitle = item.isTitle;
109
+ const targetIsTitle = checkIsTitleColumn(cellPosition.columnIndex);
110
+ return sourceIsTitle === targetIsTitle;
111
+ },
112
+ hover: (item, monitor) => {
113
+ if (!monitor.canDrop()) {
114
+ setDropTargetIndex(null);
115
+ setDragDirection(null);
116
+ return;
117
+ }
118
+ // 計算拖曳方向
119
+ const direction = item.columnIndex < cellPosition.columnIndex ? 'right' : 'left';
120
+ setDropTargetIndex(cellPosition.columnIndex);
121
+ setDragDirection(direction);
122
+ },
123
+ drop: (item) => {
124
+ moveOrSwapColumn(item.columnIndex, cellPosition.columnIndex, 'move');
125
+ setDropTargetIndex(null);
126
+ setDragDirection(null);
127
+ },
128
+ }), [dragState, cellPosition.columnIndex, checkIsTitleColumn, moveOrSwapColumn, setDropTargetIndex, setDragDirection]);
129
+ // Combine refs
130
+ dropRow(dropColumn(cellRef));
131
+ // Column resize
132
+ const { isResizing, handleResizeStart } = useColumnResize({
133
+ tableElement,
134
+ columnIndex: cellPosition.columnIndex,
135
+ cellRef,
136
+ });
137
+ useEffect(() => {
138
+ const { current: cell } = cellRef;
139
+ const { current: portalContainer } = portalContainerRef;
140
+ if (!cell || !portalContainer) {
141
+ setToolbarPosition(null);
142
+ setRowButtonPosition(null);
143
+ setColumnButtonPosition(null);
144
+ return;
145
+ }
146
+ const rafId = requestAnimationFrame(() => {
147
+ const cellRect = cell.getBoundingClientRect();
148
+ const portalContainerRect = portalContainer.getBoundingClientRect();
149
+ // 工具列位置 (針對 focused 狀態)
150
+ if (focused || isSelectionTriggerByMe) {
151
+ setToolbarPosition({
152
+ top: cellRect.top - portalContainerRect.top - 4, // -4px offset
153
+ left: cellRect.left - portalContainerRect.left,
154
+ });
155
+ }
156
+ else {
157
+ setToolbarPosition(null);
158
+ }
159
+ // 行按鈕位置 (顯示在第一列)
160
+ if (cellPosition.columnIndex === 0) {
161
+ setRowButtonPosition({
162
+ top: Math.min(cellRect.top - portalContainerRect.top + cellRect.height / 2 - 10, 0), // 置中,按鈕高度約 20px
163
+ left: cellRect.left - portalContainerRect.left - 10, // 向左偏移 10px
164
+ });
165
+ }
166
+ else {
167
+ setRowButtonPosition(null);
168
+ }
169
+ // 列按鈕位置 (顯示在第一行)
170
+ if (cellPosition.rowIndex === 0) {
171
+ setColumnButtonPosition({
172
+ top: cellRect.top - portalContainerRect.top - 10 + scrollTop, // 向上偏移 10px
173
+ left: cellRect.left - portalContainerRect.left + cellRect.width / 2 - 10, // 置中,按鈕寬度約 20px
174
+ });
175
+ }
176
+ else {
177
+ setColumnButtonPosition(null);
178
+ }
179
+ });
180
+ return () => {
181
+ cancelAnimationFrame(rafId);
182
+ };
183
+ }, [
184
+ focused,
185
+ isSelectionTriggerByMe,
186
+ cellPosition.columnIndex,
187
+ cellPosition.rowIndex,
188
+ portalContainerRef,
189
+ scrollTop,
190
+ scrollLeft,
191
+ ]);
192
+ useEffect(() => {
193
+ const { current: cell } = cellRef;
194
+ const { current: scrollContainer } = scrollRef;
195
+ if (scrollContainer && cell) {
196
+ const cellRect = cell.getBoundingClientRect();
197
+ const containerRect = scrollContainer.getBoundingClientRect();
198
+ setCellStuckAtLeft(Math.max(Math.round(cellRect.left - containerRect.left), 0));
199
+ }
200
+ }, [scrollRef]);
201
+ const TagName = isHeader ? 'th' : 'td';
202
+ const myColumnIsPinned = isColumnPinned(cellPosition.columnIndex) && element.treatAsTitle;
203
+ return (React.createElement(TagName, Object.assign({}, attributes, { ref: cellRef, onMouseEnter: () => {
204
+ setTableHoveredOn({ columnIndex: cellPosition.columnIndex, rowIndex: cellPosition.rowIndex });
205
+ }, onMouseLeave: () => {
206
+ setTableHoveredOn(undefined);
207
+ }, className: clsx('qdr-table__cell', {
208
+ 'qdr-table__cell--header': isHeader || element.treatAsTitle,
209
+ 'qdr-table__cell--pinned': myColumnIsPinned,
210
+ 'qdr-table__cell--top-active': isSelectedInSameRow ||
211
+ (isSelectedInSameColumn && cellPosition.rowIndex === 0) ||
212
+ isDraggingThisRow ||
213
+ (isDraggingThisColumn && cellPosition.rowIndex === 0),
214
+ 'qdr-table__cell--right-active': isSelectedInSameColumn ||
215
+ (isSelectedInSameRow && cellPosition.columnIndex === columnCount - 1) ||
216
+ isDraggingThisColumn ||
217
+ (isDraggingThisRow && cellPosition.columnIndex === columnCount - 1),
218
+ 'qdr-table__cell--bottom-active': isSelectedInSameRow ||
219
+ (isSelectedInSameColumn && cellPosition.rowIndex === rowCount - 1) ||
220
+ isDraggingThisRow ||
221
+ (isDraggingThisColumn && cellPosition.rowIndex === rowCount - 1),
222
+ 'qdr-table__cell--left-active': isSelectedInSameColumn ||
223
+ (isSelectedInSameRow && cellPosition.columnIndex === 0) ||
224
+ isDraggingThisColumn ||
225
+ (isDraggingThisRow && cellPosition.columnIndex === 0),
226
+ 'qdr-table__cell--is-selection-trigger-by-me': isSelectionTriggerByMe,
227
+ 'qdr-table__cell--drag-row-target-top': (dragState === null || dragState === void 0 ? void 0 : dragState.type) === 'row' &&
228
+ dropTargetIndex === cellPosition.rowIndex &&
229
+ dropTargetIndex !== dragState.rowIndex &&
230
+ dragDirection === 'up',
231
+ 'qdr-table__cell--drag-row-target-bottom': (dragState === null || dragState === void 0 ? void 0 : dragState.type) === 'row' &&
232
+ dropTargetIndex === cellPosition.rowIndex &&
233
+ dropTargetIndex !== dragState.rowIndex &&
234
+ dragDirection === 'down',
235
+ 'qdr-table__cell--drag-column-target-left': (dragState === null || dragState === void 0 ? void 0 : dragState.type) === 'column' &&
236
+ dropTargetIndex === cellPosition.columnIndex &&
237
+ dropTargetIndex !== dragState.columnIndex &&
238
+ dragDirection === 'left',
239
+ 'qdr-table__cell--drag-column-target-right': (dragState === null || dragState === void 0 ? void 0 : dragState.type) === 'column' &&
240
+ dropTargetIndex === cellPosition.columnIndex &&
241
+ dropTargetIndex !== dragState.columnIndex &&
242
+ dragDirection === 'right',
243
+ }), "data-row-index": cellPosition.rowIndex, "data-column-index": cellPosition.columnIndex, style: myColumnIsPinned
244
+ ? {
245
+ left: cellStuckAtLeft,
246
+ }
247
+ : undefined }),
248
+ children,
249
+ React.createElement("div", { contentEditable: false, "data-slate-editor": false, className: clsx('qdr-table__cell__resize-handle', {
250
+ 'qdr-table__cell__resize-handle--active': isResizing,
251
+ }), onMouseDown: handleResizeStart }),
252
+ focused && (React.createElement(Portal, { getContainer: () => portalContainerRef.current || document.body },
253
+ React.createElement(InlineToolbar, { className: 'qdr-table__cell__focus-toolbar', style: {
254
+ top: toolbarPosition === null || toolbarPosition === void 0 ? void 0 : toolbarPosition.top,
255
+ left: toolbarPosition === null || toolbarPosition === void 0 ? void 0 : toolbarPosition.left,
256
+ }, iconGroups: focusToolbarIconGroups }))),
257
+ showRowActionButton && (React.createElement(Portal, { getContainer: () => portalContainerRef.current || document.body },
258
+ React.createElement(RowDragButton, { rowIndex: cellPosition.rowIndex, headerRowCount: headerRowCount, style: {
259
+ top: rowButtonPosition === null || rowButtonPosition === void 0 ? void 0 : rowButtonPosition.top,
260
+ left: rowButtonPosition === null || rowButtonPosition === void 0 ? void 0 : rowButtonPosition.left,
261
+ }, onClick: (e) => {
262
+ e.preventDefault();
263
+ e.stopPropagation();
264
+ Transforms.deselect(editor);
265
+ setTableSelectedOn((prev) => {
266
+ if ((prev === null || prev === void 0 ? void 0 : prev.region) === 'row' && prev.index === cellPosition.rowIndex) {
267
+ return undefined;
268
+ }
269
+ return { region: 'row', index: cellPosition.rowIndex };
270
+ });
271
+ } }))),
272
+ showColumnActionButton && (React.createElement(Portal, { getContainer: () => portalContainerRef.current || document.body },
273
+ React.createElement(ColumnDragButton, { columnIndex: cellPosition.columnIndex, style: {
274
+ // pinned 時因為 sticky 所以要扣掉 scrollTop
275
+ top: ((_b = columnButtonPosition === null || columnButtonPosition === void 0 ? void 0 : columnButtonPosition.top) !== null && _b !== void 0 ? _b : 0) - (element.pinned ? scrollTop : 0),
276
+ left: columnButtonPosition === null || columnButtonPosition === void 0 ? void 0 : columnButtonPosition.left,
277
+ }, onClick: (e) => {
278
+ e.preventDefault();
279
+ e.stopPropagation();
280
+ Transforms.deselect(editor);
281
+ setTableSelectedOn((prev) => {
282
+ if ((prev === null || prev === void 0 ? void 0 : prev.region) === 'column' && prev.index === cellPosition.columnIndex) {
283
+ return undefined;
284
+ }
285
+ return { region: 'column', index: cellPosition.columnIndex };
286
+ });
287
+ }, checkIsTitleColumn: checkIsTitleColumn }))),
288
+ isSelectionTriggerByMe && (cellPosition.columnIndex === 0 || cellPosition.rowIndex === 0) ? (React.createElement(Portal, { getContainer: () => portalContainerRef.current || document.body },
289
+ React.createElement(InlineToolbar, { className: "qdr-table__cell__inline-table-toolbar", style: {
290
+ top: toolbarPosition === null || toolbarPosition === void 0 ? void 0 : toolbarPosition.top,
291
+ left: toolbarPosition === null || toolbarPosition === void 0 ? void 0 : toolbarPosition.left,
292
+ }, iconGroups: inlineToolbarIconGroups, onClickAway: () => {
293
+ setTableSelectedOn(undefined);
294
+ } }))) : null));
295
+ }
296
+
297
+ export { TableCell as default };
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ interface DragLayerProps {
3
+ scrollRef: React.RefObject<HTMLDivElement | null>;
4
+ }
5
+ export declare const TableDragLayer: React.FC<DragLayerProps>;
6
+ export {};
@@ -0,0 +1,89 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { useDragLayer } from 'react-dnd';
3
+ import { useTableDragContext } from '../contexts/TableDragContext.js';
4
+
5
+ const TableDragLayer = ({ scrollRef }) => {
6
+ const { dragState } = useTableDragContext();
7
+ const [columnWidths, setColumnWidths] = useState([]);
8
+ const [rowHeights, setRowHeights] = useState([]);
9
+ const { isDragging, currentOffset } = useDragLayer((monitor) => ({
10
+ isDragging: monitor.isDragging(),
11
+ currentOffset: monitor.getClientOffset(),
12
+ }));
13
+ // 計算所有 column 的寬度和 row 的高度
14
+ useEffect(() => {
15
+ if (!scrollRef.current || !isDragging)
16
+ return;
17
+ const tableContainer = scrollRef.current;
18
+ const cells = tableContainer.querySelectorAll('.qdr-table__cell');
19
+ if ((dragState === null || dragState === void 0 ? void 0 : dragState.type) === 'column') {
20
+ // 計算每個 column 的寬度
21
+ const widths = [];
22
+ const columnCells = Array.from(cells).filter((cell) => {
23
+ const cellElement = cell;
24
+ return cellElement.dataset.columnIndex !== undefined;
25
+ });
26
+ const columnCount = Math.max(...columnCells.map((cell) => {
27
+ const cellElement = cell;
28
+ return parseInt(cellElement.dataset.columnIndex || '0', 10);
29
+ })) + 1;
30
+ for (let i = 0; i < columnCount; i++) {
31
+ const cellsInColumn = columnCells.filter((cell) => {
32
+ const cellElement = cell;
33
+ return parseInt(cellElement.dataset.columnIndex || '0', 10) === i;
34
+ });
35
+ if (cellsInColumn.length > 0) {
36
+ const firstCell = cellsInColumn[0];
37
+ widths[i] = firstCell.getBoundingClientRect().width;
38
+ }
39
+ }
40
+ setColumnWidths(widths);
41
+ }
42
+ else if ((dragState === null || dragState === void 0 ? void 0 : dragState.type) === 'row') {
43
+ // 計算每個 row 的高度
44
+ const heights = [];
45
+ const rowCells = Array.from(cells).filter((cell) => {
46
+ const cellElement = cell;
47
+ return cellElement.dataset.rowIndex !== undefined;
48
+ });
49
+ const rowCount = Math.max(...rowCells.map((cell) => {
50
+ const cellElement = cell;
51
+ return parseInt(cellElement.dataset.rowIndex || '0', 10);
52
+ })) + 1;
53
+ for (let i = 0; i < rowCount; i++) {
54
+ const cellsInRow = rowCells.filter((cell) => {
55
+ const cellElement = cell;
56
+ return parseInt(cellElement.dataset.rowIndex || '0', 10) === i;
57
+ });
58
+ if (cellsInRow.length > 0) {
59
+ const firstCell = cellsInRow[0];
60
+ heights[i] = firstCell.getBoundingClientRect().height;
61
+ }
62
+ }
63
+ setRowHeights(heights);
64
+ }
65
+ }, [isDragging, dragState, scrollRef]);
66
+ if (!isDragging || !dragState || !currentOffset || !scrollRef.current) {
67
+ return null;
68
+ }
69
+ const tableContainer = scrollRef.current;
70
+ const tableRect = tableContainer.getBoundingClientRect();
71
+ if (dragState.type) {
72
+ const sourceIndex = dragState.type === 'column' ? dragState.columnIndex : dragState.rowIndex;
73
+ const rowHeight = dragState.type === 'column' ? tableRect.height : rowHeights[sourceIndex];
74
+ const columnWidth = dragState.type === 'column' ? columnWidths[sourceIndex] : tableRect.width;
75
+ return (React.createElement("div", { className: "qdr-table__drag-overlay", style: {
76
+ left: tableRect.left,
77
+ top: tableRect.top,
78
+ width: columnWidth,
79
+ height: rowHeight,
80
+ transform: dragState.type === 'column'
81
+ ? `translateX(${currentOffset.x - tableRect.left - columnWidth / 2}px)`
82
+ : `translateY(${currentOffset.y - tableRect.top - rowHeight / 2}px)`,
83
+ } },
84
+ React.createElement("div", { className: "qdr-table__drag-overlay-content" })));
85
+ }
86
+ return null;
87
+ };
88
+
89
+ export { TableDragLayer };
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+ import { RenderElementProps } from '@quadrats/react';
3
+ import { TableElement } from '@quadrats/common/table';
4
+ declare function TableHeader(props: RenderElementProps<TableElement>): React.JSX.Element;
5
+ export default TableHeader;