@quadrats/react 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) 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/embed/renderers/base/components/BaseEmbedElement.js +51 -43
  8. package/embed/renderers/base/index.cjs.js +50 -42
  9. package/image/components/Image.js +34 -26
  10. package/image/createReactImage.js +1 -1
  11. package/image/index.cjs.js +34 -26
  12. package/package.json +4 -4
  13. package/table/components/Table.d.ts +9 -0
  14. package/table/components/Table.js +231 -0
  15. package/table/components/TableBody.d.ts +5 -0
  16. package/table/components/TableBody.js +8 -0
  17. package/table/components/TableCell.d.ts +5 -0
  18. package/table/components/TableCell.js +191 -0
  19. package/table/components/TableHeader.d.ts +5 -0
  20. package/table/components/TableHeader.js +13 -0
  21. package/table/components/TableMain.d.ts +5 -0
  22. package/table/components/TableMain.js +225 -0
  23. package/table/components/TableRow.d.ts +5 -0
  24. package/table/components/TableRow.js +8 -0
  25. package/table/components/TableTitle.d.ts +5 -0
  26. package/table/components/TableTitle.js +18 -0
  27. package/table/contexts/TableActionsContext.d.ts +3 -0
  28. package/table/contexts/TableActionsContext.js +5 -0
  29. package/table/contexts/TableHeaderContext.d.ts +2 -0
  30. package/table/contexts/TableHeaderContext.js +7 -0
  31. package/table/contexts/TableMetadataContext.d.ts +3 -0
  32. package/table/contexts/TableMetadataContext.js +5 -0
  33. package/table/contexts/TableScrollContext.d.ts +2 -0
  34. package/table/contexts/TableScrollContext.js +9 -0
  35. package/table/contexts/TableStateContext.d.ts +3 -0
  36. package/table/contexts/TableStateContext.js +5 -0
  37. package/table/createReactTable.d.ts +4 -0
  38. package/table/createReactTable.js +297 -0
  39. package/table/defaultRenderTableElements.d.ts +2 -0
  40. package/table/defaultRenderTableElements.js +20 -0
  41. package/table/hooks/useColumnResize.d.ts +12 -0
  42. package/table/hooks/useColumnResize.js +139 -0
  43. package/table/hooks/useTableActions.d.ts +25 -0
  44. package/table/hooks/useTableActions.js +886 -0
  45. package/table/hooks/useTableActionsContext.d.ts +1 -0
  46. package/table/hooks/useTableActionsContext.js +12 -0
  47. package/table/hooks/useTableCell.d.ts +16 -0
  48. package/table/hooks/useTableCell.js +166 -0
  49. package/table/hooks/useTableCellToolbarActions.d.ts +34 -0
  50. package/table/hooks/useTableCellToolbarActions.js +404 -0
  51. package/table/hooks/useTableMetadata.d.ts +1 -0
  52. package/table/hooks/useTableMetadata.js +12 -0
  53. package/table/hooks/useTableStateContext.d.ts +1 -0
  54. package/table/hooks/useTableStateContext.js +12 -0
  55. package/table/hooks/useTableStates.d.ts +18 -0
  56. package/table/hooks/useTableStates.js +14 -0
  57. package/table/index.cjs.js +3254 -0
  58. package/table/index.d.ts +16 -0
  59. package/table/index.js +24 -0
  60. package/table/jsx-serializer/components/Table.d.ts +3 -0
  61. package/table/jsx-serializer/components/Table.js +7 -0
  62. package/table/jsx-serializer/components/TableBody.d.ts +3 -0
  63. package/table/jsx-serializer/components/TableBody.js +7 -0
  64. package/table/jsx-serializer/components/TableCell.d.ts +5 -0
  65. package/table/jsx-serializer/components/TableCell.js +33 -0
  66. package/table/jsx-serializer/components/TableHeader.d.ts +3 -0
  67. package/table/jsx-serializer/components/TableHeader.js +10 -0
  68. package/table/jsx-serializer/components/TableMain.d.ts +6 -0
  69. package/table/jsx-serializer/components/TableMain.js +18 -0
  70. package/table/jsx-serializer/components/TableRow.d.ts +3 -0
  71. package/table/jsx-serializer/components/TableRow.js +7 -0
  72. package/table/jsx-serializer/components/TableTitle.d.ts +3 -0
  73. package/table/jsx-serializer/components/TableTitle.js +7 -0
  74. package/table/jsx-serializer/contexts/TableHeaderContext.d.ts +1 -0
  75. package/table/jsx-serializer/contexts/TableHeaderContext.js +5 -0
  76. package/table/jsx-serializer/contexts/TableScrollContext.d.ts +2 -0
  77. package/table/jsx-serializer/contexts/TableScrollContext.js +7 -0
  78. package/table/jsx-serializer/createJsxSerializeTable.d.ts +5 -0
  79. package/table/jsx-serializer/createJsxSerializeTable.js +113 -0
  80. package/table/jsx-serializer/defaultRenderTableElements.d.ts +2 -0
  81. package/table/jsx-serializer/defaultRenderTableElements.js +20 -0
  82. package/table/jsx-serializer/index.cjs.js +195 -0
  83. package/table/jsx-serializer/index.d.ts +3 -0
  84. package/table/jsx-serializer/index.js +2 -0
  85. package/table/jsx-serializer/package.json +7 -0
  86. package/table/jsx-serializer/typings.d.ts +12 -0
  87. package/table/package.json +10 -0
  88. package/table/table.css +1 -0
  89. package/table/table.scss +393 -0
  90. package/table/toolbar/TableToolbarIcon.d.ts +8 -0
  91. package/table/toolbar/TableToolbarIcon.js +12 -0
  92. package/table/toolbar/index.cjs.js +24 -0
  93. package/table/toolbar/index.d.ts +2 -0
  94. package/table/toolbar/index.js +2 -0
  95. package/table/toolbar/package.json +7 -0
  96. package/table/toolbar/useTableTool.d.ts +4 -0
  97. package/table/toolbar/useTableTool.js +13 -0
  98. package/table/typings.d.ts +66 -0
  99. package/table/utils/helper.d.ts +160 -0
  100. package/table/utils/helper.js +693 -0
  101. package/toolbar/components/InlineToolbar.d.ts +12 -11
  102. package/toolbar/components/InlineToolbar.js +23 -19
  103. package/toolbar/components/Toolbar.js +2 -2
  104. package/toolbar/index.cjs.js +24 -21
  105. package/toolbar/toolbar.css +1 -1
  106. package/toolbar/toolbar.scss +4 -1
  107. package/utils/index.cjs.js +7 -1
  108. package/utils/removePreviousElement.js +7 -1
@@ -0,0 +1,191 @@
1
+ import React, { useContext, useMemo, useRef, useState, useEffect } from 'react';
2
+ import clsx from 'clsx';
3
+ import { Transforms } from '@quadrats/core';
4
+ import { useSlateStatic } from 'slate-react';
5
+ import { TableHeaderContext } from '../contexts/TableHeaderContext.js';
6
+ import { Portal, Icon } from '@quadrats/react/components';
7
+ import { Drag } from '@quadrats/icons';
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
+
16
+ function TableCell(props) {
17
+ var _a;
18
+ const { attributes, children, element } = props;
19
+ const { tableSelectedOn, setTableSelectedOn, tableHoveredOn, setTableHoveredOn } = useTableStateContext();
20
+ const { columnCount, rowCount, portalContainerRef, isColumnPinned, tableElement } = useTableMetadata();
21
+ // Component context
22
+ const { isHeader } = useContext(TableHeaderContext);
23
+ const { scrollTop, scrollLeft, scrollRef } = useContext(TableScrollContext);
24
+ const editor = useSlateStatic();
25
+ // Cell-specific hooks
26
+ const focused = useTableCellFocused(element, editor);
27
+ const cellPosition = useTableCellPosition(element);
28
+ const transformCellContent = useTableCellTransformContent(element, editor);
29
+ // Toolbar actions
30
+ const { focusToolbarIconGroups, inlineToolbarIconGroups } = useTableCellToolbarActions({
31
+ element,
32
+ cellPosition,
33
+ isHeader,
34
+ transformCellContent,
35
+ });
36
+ const isSelectedInSameRow = (tableSelectedOn === null || tableSelectedOn === void 0 ? void 0 : tableSelectedOn.region) === 'row' && (tableSelectedOn === null || tableSelectedOn === void 0 ? void 0 : tableSelectedOn.index) === cellPosition.rowIndex;
37
+ const isSelectedInSameColumn = (tableSelectedOn === null || tableSelectedOn === void 0 ? void 0 : tableSelectedOn.region) === 'column' && (tableSelectedOn === null || tableSelectedOn === void 0 ? void 0 : tableSelectedOn.index) === cellPosition.columnIndex;
38
+ const isSelectionTriggerByMe = (isSelectedInSameRow && cellPosition.columnIndex === 0) || (isSelectedInSameColumn && cellPosition.rowIndex === 0);
39
+ const showRowActionButton = useMemo(() => cellPosition.rowIndex === 0 &&
40
+ (isSelectedInSameColumn || ((tableHoveredOn === null || tableHoveredOn === void 0 ? void 0 : tableHoveredOn.columnIndex) === cellPosition.columnIndex && !tableSelectedOn)), [cellPosition, isSelectedInSameColumn, tableHoveredOn, tableSelectedOn]);
41
+ const showColumnActionButton = useMemo(() => cellPosition.columnIndex === 0 &&
42
+ (isSelectedInSameRow || ((tableHoveredOn === null || tableHoveredOn === void 0 ? void 0 : tableHoveredOn.rowIndex) === cellPosition.rowIndex && !tableSelectedOn)), [cellPosition, isSelectedInSameRow, tableHoveredOn, tableSelectedOn]);
43
+ // 用於定位 InlineToolbar 的 ref
44
+ const cellRef = useRef(null);
45
+ const [toolbarPosition, setToolbarPosition] = useState(null);
46
+ const [rowButtonPosition, setRowButtonPosition] = useState(null);
47
+ const [columnButtonPosition, setColumnButtonPosition] = useState(null);
48
+ const [cellStuckAtLeft, setCellStuckAtLeft] = useState(undefined);
49
+ // Column resize
50
+ const { isResizing, handleResizeStart } = useColumnResize({
51
+ tableElement,
52
+ columnIndex: cellPosition.columnIndex,
53
+ cellRef,
54
+ });
55
+ useEffect(() => {
56
+ const { current: cell } = cellRef;
57
+ const { current: portalContainer } = portalContainerRef;
58
+ if (!cell || !portalContainer) {
59
+ setToolbarPosition(null);
60
+ setRowButtonPosition(null);
61
+ setColumnButtonPosition(null);
62
+ return;
63
+ }
64
+ const rafId = requestAnimationFrame(() => {
65
+ const cellRect = cell.getBoundingClientRect();
66
+ const portalContainerRect = portalContainer.getBoundingClientRect();
67
+ // 工具列位置 (針對 focused 狀態)
68
+ if (focused || isSelectionTriggerByMe) {
69
+ setToolbarPosition({
70
+ top: cellRect.top - portalContainerRect.top - 4, // -4px offset
71
+ left: cellRect.left - portalContainerRect.left,
72
+ });
73
+ }
74
+ else {
75
+ setToolbarPosition(null);
76
+ }
77
+ // 行按鈕位置 (顯示在第一列)
78
+ if (cellPosition.columnIndex === 0) {
79
+ setRowButtonPosition({
80
+ top: Math.min(cellRect.top - portalContainerRect.top + cellRect.height / 2 - 10, 0), // 置中,按鈕高度約 20px
81
+ left: cellRect.left - portalContainerRect.left - 10, // 向左偏移 10px
82
+ });
83
+ }
84
+ else {
85
+ setRowButtonPosition(null);
86
+ }
87
+ // 列按鈕位置 (顯示在第一行)
88
+ if (cellPosition.rowIndex === 0) {
89
+ setColumnButtonPosition({
90
+ top: cellRect.top - portalContainerRect.top - 10 + scrollTop, // 向上偏移 10px
91
+ left: cellRect.left - portalContainerRect.left + cellRect.width / 2 - 10, // 置中,按鈕寬度約 20px
92
+ });
93
+ }
94
+ else {
95
+ setColumnButtonPosition(null);
96
+ }
97
+ });
98
+ return () => {
99
+ cancelAnimationFrame(rafId);
100
+ };
101
+ }, [
102
+ focused,
103
+ isSelectionTriggerByMe,
104
+ cellPosition.columnIndex,
105
+ cellPosition.rowIndex,
106
+ portalContainerRef,
107
+ scrollTop,
108
+ scrollLeft,
109
+ ]);
110
+ useEffect(() => {
111
+ const { current: cell } = cellRef;
112
+ const { current: scrollContainer } = scrollRef;
113
+ if (scrollContainer && cell) {
114
+ const cellRect = cell.getBoundingClientRect();
115
+ const containerRect = scrollContainer.getBoundingClientRect();
116
+ setCellStuckAtLeft(Math.max(Math.round(cellRect.left - containerRect.left), 0));
117
+ }
118
+ }, [scrollRef]);
119
+ const TagName = isHeader ? 'th' : 'td';
120
+ const myColumnIsPinned = isColumnPinned(cellPosition.columnIndex) && element.treatAsTitle;
121
+ return (React.createElement(TagName, Object.assign({}, attributes, { ref: cellRef, onMouseEnter: () => {
122
+ setTableHoveredOn({ columnIndex: cellPosition.columnIndex, rowIndex: cellPosition.rowIndex });
123
+ }, onMouseLeave: () => {
124
+ setTableHoveredOn(undefined);
125
+ }, className: clsx('qdr-table__cell', {
126
+ 'qdr-table__cell--header': isHeader || element.treatAsTitle,
127
+ 'qdr-table__cell--pinned': myColumnIsPinned,
128
+ 'qdr-table__cell--top-active': isSelectedInSameRow || (isSelectedInSameColumn && cellPosition.rowIndex === 0),
129
+ 'qdr-table__cell--right-active': isSelectedInSameColumn || (isSelectedInSameRow && cellPosition.columnIndex === columnCount - 1),
130
+ 'qdr-table__cell--bottom-active': isSelectedInSameRow || (isSelectedInSameColumn && cellPosition.rowIndex === rowCount - 1),
131
+ 'qdr-table__cell--left-active': isSelectedInSameColumn || (isSelectedInSameRow && cellPosition.columnIndex === 0),
132
+ 'qdr-table__cell--is-selection-trigger-by-me': isSelectionTriggerByMe,
133
+ }), style: myColumnIsPinned
134
+ ? {
135
+ left: cellStuckAtLeft,
136
+ }
137
+ : undefined }),
138
+ children,
139
+ React.createElement("div", { contentEditable: false, "data-slate-editor": false, className: clsx('qdr-table__cell__resize-handle', {
140
+ 'qdr-table__cell__resize-handle--active': isResizing,
141
+ }), onMouseDown: handleResizeStart }),
142
+ focused && (React.createElement(Portal, { getContainer: () => portalContainerRef.current || document.body },
143
+ React.createElement(InlineToolbar, { className: 'qdr-table__cell__focus-toolbar', style: {
144
+ top: toolbarPosition === null || toolbarPosition === void 0 ? void 0 : toolbarPosition.top,
145
+ left: toolbarPosition === null || toolbarPosition === void 0 ? void 0 : toolbarPosition.left,
146
+ }, iconGroups: focusToolbarIconGroups }))),
147
+ showColumnActionButton && (React.createElement(Portal, { getContainer: () => portalContainerRef.current || document.body },
148
+ React.createElement("button", { type: "button", contentEditable: false, style: {
149
+ top: rowButtonPosition === null || rowButtonPosition === void 0 ? void 0 : rowButtonPosition.top,
150
+ left: rowButtonPosition === null || rowButtonPosition === void 0 ? void 0 : rowButtonPosition.left,
151
+ }, onClick: (e) => {
152
+ e.preventDefault();
153
+ e.stopPropagation();
154
+ // Clear focus by removing selection
155
+ Transforms.deselect(editor);
156
+ setTableSelectedOn((prev) => {
157
+ if ((prev === null || prev === void 0 ? void 0 : prev.region) === 'row' && prev.index === cellPosition.rowIndex) {
158
+ return undefined;
159
+ }
160
+ return { region: 'row', index: cellPosition.rowIndex };
161
+ });
162
+ }, className: "qdr-table__cell-row-action" },
163
+ React.createElement(Icon, { icon: Drag, width: 20, height: 20 })))),
164
+ showRowActionButton && (React.createElement(Portal, { getContainer: () => portalContainerRef.current || document.body },
165
+ React.createElement("button", { type: "button", contentEditable: false, style: {
166
+ // pinned 時因為 sticky 所以要扣掉 scrollTop
167
+ top: ((_a = columnButtonPosition === null || columnButtonPosition === void 0 ? void 0 : columnButtonPosition.top) !== null && _a !== void 0 ? _a : 0) - (element.pinned ? scrollTop : 0),
168
+ left: columnButtonPosition === null || columnButtonPosition === void 0 ? void 0 : columnButtonPosition.left,
169
+ }, onClick: (e) => {
170
+ e.preventDefault();
171
+ e.stopPropagation();
172
+ // Clear focus by removing selection
173
+ Transforms.deselect(editor);
174
+ setTableSelectedOn((prev) => {
175
+ if ((prev === null || prev === void 0 ? void 0 : prev.region) === 'column' && prev.index === cellPosition.columnIndex) {
176
+ return undefined;
177
+ }
178
+ return { region: 'column', index: cellPosition.columnIndex };
179
+ });
180
+ }, className: "qdr-table__cell-column-action" },
181
+ React.createElement(Icon, { icon: Drag, width: 20, height: 20 })))),
182
+ isSelectionTriggerByMe && (cellPosition.columnIndex === 0 || cellPosition.rowIndex === 0) ? (React.createElement(Portal, { getContainer: () => portalContainerRef.current || document.body },
183
+ React.createElement(InlineToolbar, { className: "qdr-table__cell__inline-table-toolbar", style: {
184
+ top: toolbarPosition === null || toolbarPosition === void 0 ? void 0 : toolbarPosition.top,
185
+ left: toolbarPosition === null || toolbarPosition === void 0 ? void 0 : toolbarPosition.left,
186
+ }, iconGroups: inlineToolbarIconGroups, onClickAway: () => {
187
+ setTableSelectedOn(undefined);
188
+ } }))) : null));
189
+ }
190
+
191
+ export { TableCell 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 TableHeader(props: RenderElementProps<TableElement>): React.JSX.Element;
5
+ export default TableHeader;
@@ -0,0 +1,13 @@
1
+ import React, { useMemo } from 'react';
2
+ import clsx from 'clsx';
3
+ import { TableHeaderContext } from '../contexts/TableHeaderContext.js';
4
+
5
+ function TableHeader(props) {
6
+ const { attributes, children, element } = props;
7
+ const tableHeaderContextValue = useMemo(() => ({ isHeader: true }), []);
8
+ const hasAnyRowPinned = element.children.some((child) => child.children.every((cell) => cell.pinned));
9
+ return (React.createElement(TableHeaderContext.Provider, { value: tableHeaderContextValue },
10
+ React.createElement("thead", Object.assign({}, attributes, { className: clsx('qdr-table__header', { 'qdr-table__header--pinned': hasAnyRowPinned }) }), children)));
11
+ }
12
+
13
+ export { TableHeader 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 TableMain(props: RenderElementProps<TableElement>): React.JSX.Element;
5
+ export default TableMain;
@@ -0,0 +1,225 @@
1
+ import React, { useRef, useState, useMemo, useEffect, useCallback } from 'react';
2
+ import clsx from 'clsx';
3
+ import { useModal, useSlateStatic, ReactEditor } from '@quadrats/react';
4
+ import { Icon } from '@quadrats/react/components';
5
+ import { Copy, Trash, AlignLeft, AlignCenter, AlignRight, Plus } from '@quadrats/icons';
6
+ import { useTableActionsContext } from '../hooks/useTableActionsContext.js';
7
+ import { useTableMetadata } from '../hooks/useTableMetadata.js';
8
+ import { useTableStateContext } from '../hooks/useTableStateContext.js';
9
+ import { InlineToolbar, ToolbarGroupIcon, ToolbarIcon } from '@quadrats/react/toolbar';
10
+ import { Transforms } from 'slate';
11
+ import { calculateTableMinWidth, columnWidthToCSS } from '@quadrats/common/table';
12
+ import { TableScrollContext } from '../contexts/TableScrollContext.js';
13
+ import { useTableCellAlign, useTableCellAlignStatus } from '../hooks/useTableCell.js';
14
+ import { getTableElements, getColumnWidths } from '../utils/helper.js';
15
+
16
+ function TableMain(props) {
17
+ const { attributes, children } = props;
18
+ const { setConfirmModalConfig } = useModal();
19
+ const editor = useSlateStatic();
20
+ const { addColumn, addRow, addColumnAndRow } = useTableActionsContext();
21
+ const { isReachMaximumColumns, isReachMaximumRows, tableElement } = useTableMetadata();
22
+ const { tableSelectedOn, setTableSelectedOn } = useTableStateContext();
23
+ // Table align functions
24
+ const setAlign = useTableCellAlign(tableElement, editor);
25
+ const getAlign = useTableCellAlignStatus(tableElement, editor);
26
+ const tablePath = ReactEditor.findPath(editor, tableElement);
27
+ const scrollRef = useRef(null);
28
+ const tableRef = useRef(null);
29
+ const [scrollTop, setScrollTop] = useState(0);
30
+ const [scrollLeft, setScrollLeft] = useState(0);
31
+ const [tableWidth, setTableWidth] = useState(0);
32
+ const scrollUpdateTimerRef = useRef(null);
33
+ const isUpdatingScrollRef = useRef(false); // 標記是否正在更新滾動位置
34
+ const previousColumnWidthsRef = useRef(''); // 追蹤 columnWidths 的變化
35
+ // sizing
36
+ const { tableBodyElement } = getTableElements(tableElement);
37
+ const firstRowCells = tableBodyElement === null || tableBodyElement === void 0 ? void 0 : tableBodyElement.children[0].children;
38
+ // 獲取欄位寬度(傳入 tableWidth 以支援混合模式)
39
+ const columnWidths = useMemo(() => getColumnWidths(tableElement, tableWidth), [tableElement, tableWidth]);
40
+ // 計算 table 的最小寬度
41
+ const tableMinWidth = useMemo(() => calculateTableMinWidth(columnWidths), [columnWidths]);
42
+ // 監聽 table 寬度變化
43
+ useEffect(() => {
44
+ const { current: table } = tableRef;
45
+ if (!table)
46
+ return;
47
+ const resizeObserver = new ResizeObserver((entries) => {
48
+ for (const entry of entries) {
49
+ setTableWidth(entry.contentRect.width);
50
+ }
51
+ });
52
+ resizeObserver.observe(table);
53
+ // 初始化寬度
54
+ setTableWidth(table.getBoundingClientRect().width);
55
+ return () => {
56
+ resizeObserver.disconnect();
57
+ };
58
+ }, []);
59
+ useEffect(() => {
60
+ const { current: scrollContainer } = scrollRef;
61
+ if (!scrollContainer)
62
+ return;
63
+ const handleScroll = () => {
64
+ setScrollTop(scrollContainer.scrollTop);
65
+ setScrollLeft(scrollContainer.scrollLeft);
66
+ // 如果正在程式化更新滾動位置,不要觸發 Slate 更新
67
+ if (isUpdatingScrollRef.current) {
68
+ return;
69
+ }
70
+ // 使用 debounce 來減少 Slate 更新頻率
71
+ if (scrollUpdateTimerRef.current) {
72
+ clearTimeout(scrollUpdateTimerRef.current);
73
+ }
74
+ scrollUpdateTimerRef.current = setTimeout(() => {
75
+ // 更新 tableElement 的 scrollPosition
76
+ const tablePath = ReactEditor.findPath(editor, tableElement);
77
+ Transforms.setNodes(editor, {
78
+ scrollPosition: {
79
+ scrollLeft: scrollContainer.scrollLeft,
80
+ scrollTop: scrollContainer.scrollTop,
81
+ },
82
+ }, { at: tablePath });
83
+ }, 300); // 300ms debounce
84
+ };
85
+ scrollContainer.addEventListener('scroll', handleScroll, false);
86
+ return () => {
87
+ scrollContainer.removeEventListener('scroll', handleScroll, false);
88
+ if (scrollUpdateTimerRef.current) {
89
+ clearTimeout(scrollUpdateTimerRef.current);
90
+ }
91
+ };
92
+ }, [editor, tableElement]);
93
+ // 只在 columnWidths 改變時恢復滾動位置
94
+ useEffect(() => {
95
+ const { current: scrollContainer } = scrollRef;
96
+ if (!scrollContainer || !tableElement.scrollPosition)
97
+ return;
98
+ // 檢查 columnWidths 是否真的改變了
99
+ const currentColumnWidthsStr = JSON.stringify(columnWidths);
100
+ if (previousColumnWidthsRef.current !== currentColumnWidthsStr) {
101
+ previousColumnWidthsRef.current = currentColumnWidthsStr;
102
+ // 標記正在更新,避免觸發 handleScroll
103
+ isUpdatingScrollRef.current = true;
104
+ // 使用 requestAnimationFrame 確保 DOM 已更新
105
+ requestAnimationFrame(() => {
106
+ var _a, _b, _c, _d;
107
+ scrollContainer.scrollLeft = (_b = (_a = tableElement.scrollPosition) === null || _a === void 0 ? void 0 : _a.scrollLeft) !== null && _b !== void 0 ? _b : 0;
108
+ scrollContainer.scrollTop = (_d = (_c = tableElement.scrollPosition) === null || _c === void 0 ? void 0 : _c.scrollTop) !== null && _d !== void 0 ? _d : 0;
109
+ // 重置標記
110
+ setTimeout(() => {
111
+ isUpdatingScrollRef.current = false;
112
+ }, 100);
113
+ });
114
+ }
115
+ }, [columnWidths, tableElement.scrollPosition]);
116
+ const scrollContextValue = useMemo(() => ({ scrollTop, scrollLeft, scrollRef }), [scrollTop, scrollLeft, scrollRef]);
117
+ // 複製 Table 功能
118
+ const copyTable = useCallback(() => {
119
+ try {
120
+ const clonedTable = JSON.parse(JSON.stringify(tableElement));
121
+ // 找到當前 table 的父節點路徑
122
+ const tableParentPath = tablePath.slice(0, -1);
123
+ const tableIndex = tablePath[tablePath.length - 1];
124
+ // 在當前 table 之後插入複製的 table
125
+ Transforms.insertNodes(editor, clonedTable, {
126
+ at: [...tableParentPath, tableIndex + 1],
127
+ });
128
+ }
129
+ catch (error) {
130
+ console.error('Failed to copy table:', error);
131
+ }
132
+ }, [editor, tableElement, tablePath]);
133
+ // 獲取當前 table 的 align 狀態
134
+ const currentTableAlign = getAlign('table');
135
+ // 根據當前 table align 狀態選擇對應的 icon
136
+ const getCurrentTableAlignIcon = () => {
137
+ switch (currentTableAlign) {
138
+ case 'left':
139
+ return AlignLeft;
140
+ case 'center':
141
+ return AlignCenter;
142
+ case 'right':
143
+ return AlignRight;
144
+ default:
145
+ return AlignLeft;
146
+ }
147
+ };
148
+ return (React.createElement("div", { className: clsx('qdr-table__mainWrapper', {
149
+ 'qdr-table__mainWrapper--selected': (tableSelectedOn === null || tableSelectedOn === void 0 ? void 0 : tableSelectedOn.region) === 'table',
150
+ }) },
151
+ React.createElement(InlineToolbar, { className: "qdr-table__table-toolbar", onClickAway: (tableSelectedOn === null || tableSelectedOn === void 0 ? void 0 : tableSelectedOn.region) === 'table' ? () => setTableSelectedOn(undefined) : undefined, iconGroups: [
152
+ {
153
+ icons: [
154
+ {
155
+ icon: Copy,
156
+ onClick: () => {
157
+ copyTable();
158
+ },
159
+ },
160
+ React.createElement(ToolbarGroupIcon, { key: "table-align-change", icon: getCurrentTableAlignIcon() },
161
+ React.createElement(ToolbarIcon, { icon: AlignLeft, onClick: () => {
162
+ setAlign('left', 'table');
163
+ } }),
164
+ React.createElement(ToolbarIcon, { icon: AlignCenter, onClick: () => {
165
+ setAlign('center', 'table');
166
+ } }),
167
+ React.createElement(ToolbarIcon, { icon: AlignRight, onClick: () => {
168
+ setAlign('right', 'table');
169
+ } })),
170
+ ],
171
+ },
172
+ {
173
+ icons: [
174
+ {
175
+ icon: Trash,
176
+ className: 'qdr-table__delete',
177
+ onClick: () => {
178
+ setConfirmModalConfig({
179
+ title: '刪除表格',
180
+ content: '是否確認刪除此表格?刪除後將立即移除,且此操作無法復原。',
181
+ confirmText: '刪除表格',
182
+ onConfirm: () => {
183
+ Transforms.removeNodes(editor, { at: tablePath });
184
+ },
185
+ });
186
+ },
187
+ },
188
+ ],
189
+ },
190
+ ] }),
191
+ React.createElement("div", { ref: scrollRef, className: "qdr-table__scrollContainer" },
192
+ React.createElement(TableScrollContext.Provider, { value: scrollContextValue },
193
+ React.createElement("table", Object.assign({}, attributes, { ref: (node) => {
194
+ // 合併兩個 refs
195
+ tableRef.current = node;
196
+ if (typeof attributes.ref === 'function') {
197
+ attributes.ref(node);
198
+ }
199
+ else if (attributes.ref) {
200
+ attributes.ref.current = node;
201
+ }
202
+ }, className: "qdr-table__main", style: {
203
+ minWidth: tableMinWidth,
204
+ } }),
205
+ React.createElement("colgroup", null, columnWidths.map((width, index) => (React.createElement("col", { key: index, style: {
206
+ width: columnWidthToCSS(width),
207
+ minWidth: columnWidthToCSS(width),
208
+ } })))),
209
+ children))),
210
+ React.createElement("div", { className: "qdr-table__size-indicators" }, firstRowCells === null || firstRowCells === void 0 ? void 0 : firstRowCells.map((cell, colIndex) => (React.createElement("div", { key: colIndex, className: "qdr-table__size-indicator", style: {
211
+ width: columnWidthToCSS(columnWidths[colIndex]),
212
+ minWidth: columnWidthToCSS(columnWidths[colIndex]),
213
+ transform: cell.pinned ? 'none' : `translateX(-${scrollLeft}px)`,
214
+ zIndex: cell.pinned ? 2 : 1,
215
+ } },
216
+ React.createElement("div", { className: "qdr-table__size" }, columnWidthToCSS(columnWidths[colIndex])))))),
217
+ isReachMaximumColumns ? null : (React.createElement("button", { type: "button", onClick: () => addColumn(), title: "Add Column", className: "qdr-table__add-column" },
218
+ React.createElement(Icon, { icon: Plus, width: 20, height: 20, className: "qdr-table__btn-icon" }))),
219
+ isReachMaximumRows ? null : (React.createElement("button", { type: "button", onClick: () => addRow(), title: "Add Row", className: "qdr-table__add-row" },
220
+ React.createElement(Icon, { icon: Plus, width: 20, height: 20, className: "qdr-table__btn-icon" }))),
221
+ isReachMaximumColumns || isReachMaximumRows ? null : (React.createElement("button", { type: "button", onClick: addColumnAndRow, title: "Add Column and Row", className: "qdr-table__add-both" },
222
+ React.createElement(Icon, { icon: Plus, width: 20, height: 20, className: "qdr-table__btn-icon" })))));
223
+ }
224
+
225
+ export { TableMain 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 TableRow(props: RenderElementProps<TableElement>): React.JSX.Element;
5
+ export default TableRow;
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+
3
+ function TableRow(props) {
4
+ const { attributes, children } = props;
5
+ return React.createElement("tr", Object.assign({}, attributes), children);
6
+ }
7
+
8
+ export { TableRow 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 TableTitle(props: RenderElementProps<TableElement>): React.JSX.Element;
5
+ export default TableTitle;
@@ -0,0 +1,18 @@
1
+ import React, { useMemo } from 'react';
2
+ import { Editor, Path } from '@quadrats/core';
3
+ import { useComposition, useQuadrats, ReactEditor } from '@quadrats/react';
4
+
5
+ function TableTitle(props) {
6
+ const { compositionPath } = useComposition();
7
+ const { attributes, children, element } = props;
8
+ const editor = useQuadrats();
9
+ const path = ReactEditor.findPath(editor, element);
10
+ const text = Editor.string(editor, path);
11
+ const isEmpty = !text;
12
+ const composing = useMemo(() => Path.equals(compositionPath, path), [compositionPath, path]);
13
+ return (React.createElement("h3", Object.assign({}, attributes, { className: "qdr-table__title" }),
14
+ children,
15
+ isEmpty && !composing && (React.createElement("span", { className: "qdr-table__title__placeholder", contentEditable: false }, "\u8ACB\u8F38\u5165\u8868\u683C\u6A19\u984C"))));
16
+ }
17
+
18
+ export { TableTitle as default };
@@ -0,0 +1,3 @@
1
+ import { TableContextType } from '../typings';
2
+ export type TableActionsContextType = Pick<TableContextType, 'addColumn' | 'addRow' | 'addColumnAndRow' | 'deleteRow' | 'deleteColumn' | 'moveRowToBody' | 'moveRowToHeader' | 'unsetColumnAsTitle' | 'setColumnAsTitle' | 'pinColumn' | 'unpinColumn' | 'pinRow' | 'unpinRow'>;
3
+ export declare const TableActionsContext: import("react").Context<TableActionsContextType | undefined>;
@@ -0,0 +1,5 @@
1
+ import { createContext } from 'react';
2
+
3
+ const TableActionsContext = createContext(undefined);
4
+
5
+ export { TableActionsContext };
@@ -0,0 +1,2 @@
1
+ import { TableHeaderContextType } from '../typings';
2
+ export declare const TableHeaderContext: import("react").Context<TableHeaderContextType>;
@@ -0,0 +1,7 @@
1
+ import { createContext } from 'react';
2
+
3
+ const TableHeaderContext = createContext({
4
+ isHeader: false,
5
+ });
6
+
7
+ export { TableHeaderContext };
@@ -0,0 +1,3 @@
1
+ import { TableContextType } from '../typings';
2
+ export type TableMetadataContextType = Pick<TableContextType, 'tableElement' | 'columnCount' | 'rowCount' | 'portalContainerRef' | 'isReachMaximumColumns' | 'isReachMaximumRows' | 'isReachMinimumNormalColumns' | 'isReachMinimumBodyRows' | 'pinnedColumns' | 'pinnedRows' | 'cellPositions' | 'isColumnPinned' | 'isRowPinned'>;
3
+ export declare const TableMetadataContext: import("react").Context<TableMetadataContextType | undefined>;
@@ -0,0 +1,5 @@
1
+ import { createContext } from 'react';
2
+
3
+ const TableMetadataContext = createContext(undefined);
4
+
5
+ export { TableMetadataContext };
@@ -0,0 +1,2 @@
1
+ import { TableScrollContextType } from '../typings';
2
+ export declare const TableScrollContext: import("react").Context<TableScrollContextType>;
@@ -0,0 +1,9 @@
1
+ import { createContext } from 'react';
2
+
3
+ const TableScrollContext = createContext({
4
+ scrollRef: { current: null },
5
+ scrollTop: 0,
6
+ scrollLeft: 0,
7
+ });
8
+
9
+ export { TableScrollContext };
@@ -0,0 +1,3 @@
1
+ import { TableContextType } from '../typings';
2
+ export type TableStateContextType = Pick<TableContextType, 'tableSelectedOn' | 'setTableSelectedOn' | 'tableHoveredOn' | 'setTableHoveredOn'>;
3
+ export declare const TableStateContext: import("react").Context<TableStateContextType | undefined>;
@@ -0,0 +1,5 @@
1
+ import { createContext } from 'react';
2
+
3
+ const TableStateContext = createContext(undefined);
4
+
5
+ export { TableStateContext };
@@ -0,0 +1,4 @@
1
+ import { CreateTableOptions } from '@quadrats/common/table';
2
+ import { ReactTable } from './typings';
3
+ export type CreateReactTableOptions = CreateTableOptions;
4
+ export declare function createReactTable(options?: CreateReactTableOptions): ReactTable;