@quadrats/react 1.1.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/core/components/Quadrats.js +5 -2
- package/core/contexts/modal/CarouselModal/CarouselModal.js +14 -17
- package/index.cjs.js +19 -19
- package/package.json +3 -3
- 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.js +22 -17
- package/table/components/TableCell.js +129 -23
- package/table/components/TableDragLayer.d.ts +6 -0
- package/table/components/TableDragLayer.js +89 -0
- package/table/components/TableMain.js +14 -6
- package/table/contexts/TableActionsContext.d.ts +1 -1
- package/table/contexts/TableDragContext.d.ts +26 -0
- package/table/contexts/TableDragContext.js +26 -0
- package/table/hooks/useColumnResize.js +29 -0
- package/table/hooks/useTableActions.d.ts +2 -0
- package/table/hooks/useTableActions.js +217 -11
- package/table/hooks/useTableCellToolbarActions.js +126 -4
- package/table/index.cjs.js +873 -125
- package/table/index.js +3 -0
- package/table/table.css +1 -1
- package/table/table.scss +35 -0
- package/table/typings.d.ts +2 -0
- package/table/utils/helper.d.ts +34 -8
- package/table/utils/helper.js +178 -72
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Slate } from 'slate-react';
|
|
3
|
+
import { DndProvider } from 'react-dnd';
|
|
4
|
+
import { HTML5Backend } from 'react-dnd-html5-backend';
|
|
3
5
|
import { ConfigsProvider } from '@quadrats/react/configs';
|
|
4
6
|
import { ModalProvider } from '../contexts/modal/ModalProvider.js';
|
|
5
7
|
import { MessageProvider } from '../contexts/message/MessageProvider.js';
|
|
@@ -11,8 +13,9 @@ function Quadrats(props) {
|
|
|
11
13
|
const { children, editor, locale, onChange, theme, value, needConfirmModal, setNeedConfirmModal } = props;
|
|
12
14
|
return (React.createElement(ConfigsProvider, { theme: theme, locale: locale },
|
|
13
15
|
React.createElement(Slate, { editor: editor, onChange: onChange, initialValue: value },
|
|
14
|
-
React.createElement(
|
|
15
|
-
React.createElement(
|
|
16
|
+
React.createElement(DndProvider, { backend: HTML5Backend },
|
|
17
|
+
React.createElement(MessageProvider, null,
|
|
18
|
+
React.createElement(ModalProvider, { needConfirmModal: needConfirmModal, setNeedConfirmModal: setNeedConfirmModal }, children))))));
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
export { Quadrats as default };
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { __awaiter } from 'tslib';
|
|
2
2
|
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
|
3
3
|
import clsx from 'clsx';
|
|
4
|
-
import { HTML5Backend } from 'react-dnd-html5-backend';
|
|
5
|
-
import { DndProvider } from 'react-dnd';
|
|
6
4
|
import { Plus, Upload } from '@quadrats/icons';
|
|
7
5
|
import { usePreviousValue, readFileAsBase64, upload } from '@quadrats/react/utils';
|
|
8
6
|
import { useSlateStatic } from 'slate-react';
|
|
@@ -154,21 +152,20 @@ const CarouselModal = ({ isOpen, close, controller, initialValue = [], onConfirm
|
|
|
154
152
|
}, 0);
|
|
155
153
|
}
|
|
156
154
|
} },
|
|
157
|
-
React.createElement(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
React.createElement("div", { className: "qdr-carousel-
|
|
166
|
-
React.createElement(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
: `僅能上傳 ${acceptText};檔案大小不可超過 ${controller === null || controller === void 0 ? void 0 : controller.limitSize}MB`))))))));
|
|
155
|
+
React.createElement(FilesDropZone, { isDragging: isDragging, setIsDragging: setIsDragging, isOverMaxLength: isOverMaxLength, controller: controller, uploadFiles: uploadFiles }, items.length > 0 ? (React.createElement("div", { className: clsx('qdr-carousel-modal__grid', {
|
|
156
|
+
'qdr-carousel-modal__grid--isDragging': isDragging,
|
|
157
|
+
}) }, items.map((item, index) => (React.createElement(CarouselItem, { key: `${item.url}-${item.caption || ''}-${index}`, url: item.url, preview: item.preview, progress: item.progress, caption: item.caption, index: index, ratio: controller === null || controller === void 0 ? void 0 : controller.ratio, isError: item.isError, onChange: (value) => {
|
|
158
|
+
change(index, { url: item.url, caption: value });
|
|
159
|
+
}, onRemove: () => {
|
|
160
|
+
remove(index);
|
|
161
|
+
}, swap: swap }))))) : (React.createElement("div", { className: "qdr-carousel-modal__placeholder" },
|
|
162
|
+
React.createElement("div", { className: "qdr-carousel-modal__placeholder__block" },
|
|
163
|
+
React.createElement("div", { className: "qdr-carousel-modal__placeholder__icon" },
|
|
164
|
+
React.createElement(Icon, { icon: Upload, width: 32, height: 32 })),
|
|
165
|
+
React.createElement("div", { className: "qdr-carousel-modal__placeholder__title" }, "\u62D6\u66F3\u6A94\u6848\u5230\u6B64\u4E0A\u50B3"),
|
|
166
|
+
React.createElement("div", { className: "qdr-carousel-modal__placeholder__hint" }, (controller === null || controller === void 0 ? void 0 : controller.ratio)
|
|
167
|
+
? `僅能上傳 ${acceptText};建議比例為 ${controller.ratio[0]}:${controller.ratio[1]} 且寬度至少達 2000px 以上;檔案大小不可超過 ${controller === null || controller === void 0 ? void 0 : controller.limitSize}MB`
|
|
168
|
+
: `僅能上傳 ${acceptText};檔案大小不可超過 ${controller === null || controller === void 0 ? void 0 : controller.limitSize}MB`)))))));
|
|
172
169
|
};
|
|
173
170
|
|
|
174
171
|
export { CarouselModal };
|
package/index.cjs.js
CHANGED
|
@@ -7,9 +7,9 @@ var tslib = require('tslib');
|
|
|
7
7
|
var React = require('react');
|
|
8
8
|
var slate = require('slate');
|
|
9
9
|
var clsx = require('clsx');
|
|
10
|
-
var components = require('@quadrats/react/components');
|
|
11
|
-
var reactDndHtml5Backend = require('react-dnd-html5-backend');
|
|
12
10
|
var reactDnd = require('react-dnd');
|
|
11
|
+
var reactDndHtml5Backend = require('react-dnd-html5-backend');
|
|
12
|
+
var components = require('@quadrats/react/components');
|
|
13
13
|
var icons = require('@quadrats/icons');
|
|
14
14
|
var utils = require('@quadrats/react/utils');
|
|
15
15
|
var _internal = require('@quadrats/react/_internal');
|
|
@@ -366,21 +366,20 @@ const CarouselModal = ({ isOpen, close, controller, initialValue = [], onConfirm
|
|
|
366
366
|
}, 0);
|
|
367
367
|
}
|
|
368
368
|
} },
|
|
369
|
-
React.createElement(
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
React.createElement("div", { className: "qdr-carousel-
|
|
378
|
-
React.createElement(
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
: `僅能上傳 ${acceptText};檔案大小不可超過 ${controller === null || controller === void 0 ? void 0 : controller.limitSize}MB`))))))));
|
|
369
|
+
React.createElement(FilesDropZone, { isDragging: isDragging, setIsDragging: setIsDragging, isOverMaxLength: isOverMaxLength, controller: controller, uploadFiles: uploadFiles }, items.length > 0 ? (React.createElement("div", { className: clsx('qdr-carousel-modal__grid', {
|
|
370
|
+
'qdr-carousel-modal__grid--isDragging': isDragging,
|
|
371
|
+
}) }, items.map((item, index) => (React.createElement(CarouselItem, { key: `${item.url}-${item.caption || ''}-${index}`, url: item.url, preview: item.preview, progress: item.progress, caption: item.caption, index: index, ratio: controller === null || controller === void 0 ? void 0 : controller.ratio, isError: item.isError, onChange: (value) => {
|
|
372
|
+
change(index, { url: item.url, caption: value });
|
|
373
|
+
}, onRemove: () => {
|
|
374
|
+
remove(index);
|
|
375
|
+
}, swap: swap }))))) : (React.createElement("div", { className: "qdr-carousel-modal__placeholder" },
|
|
376
|
+
React.createElement("div", { className: "qdr-carousel-modal__placeholder__block" },
|
|
377
|
+
React.createElement("div", { className: "qdr-carousel-modal__placeholder__icon" },
|
|
378
|
+
React.createElement(components.Icon, { icon: icons.Upload, width: 32, height: 32 })),
|
|
379
|
+
React.createElement("div", { className: "qdr-carousel-modal__placeholder__title" }, "\u62D6\u66F3\u6A94\u6848\u5230\u6B64\u4E0A\u50B3"),
|
|
380
|
+
React.createElement("div", { className: "qdr-carousel-modal__placeholder__hint" }, (controller === null || controller === void 0 ? void 0 : controller.ratio)
|
|
381
|
+
? `僅能上傳 ${acceptText};建議比例為 ${controller.ratio[0]}:${controller.ratio[1]} 且寬度至少達 2000px 以上;檔案大小不可超過 ${controller === null || controller === void 0 ? void 0 : controller.limitSize}MB`
|
|
382
|
+
: `僅能上傳 ${acceptText};檔案大小不可超過 ${controller === null || controller === void 0 ? void 0 : controller.limitSize}MB`)))))));
|
|
384
383
|
};
|
|
385
384
|
|
|
386
385
|
const options = [
|
|
@@ -601,8 +600,9 @@ function Quadrats(props) {
|
|
|
601
600
|
const { children, editor, locale, onChange, theme, value, needConfirmModal, setNeedConfirmModal } = props;
|
|
602
601
|
return (React.createElement(configs.ConfigsProvider, { theme: theme, locale: locale },
|
|
603
602
|
React.createElement(slateReact.Slate, { editor: editor, onChange: onChange, initialValue: value },
|
|
604
|
-
React.createElement(
|
|
605
|
-
React.createElement(
|
|
603
|
+
React.createElement(reactDnd.DndProvider, { backend: reactDndHtml5Backend.HTML5Backend },
|
|
604
|
+
React.createElement(MessageProvider, null,
|
|
605
|
+
React.createElement(ModalProvider, { needConfirmModal: needConfirmModal, setNeedConfirmModal: setNeedConfirmModal }, children))))));
|
|
606
606
|
}
|
|
607
607
|
|
|
608
608
|
function createEventHandler(editor, handlers) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quadrats/react",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"author": "Rytass",
|
|
6
6
|
"homepage": "https://github.com/Quadrats/quadrats#readme",
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"url": "https://github.com/Quadrats/quadrats/issues"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@quadrats/common": "^1.1.
|
|
23
|
+
"@quadrats/common": "^1.1.1",
|
|
24
24
|
"@quadrats/core": "^1.1.0",
|
|
25
|
-
"@quadrats/icons": "^1.1.
|
|
25
|
+
"@quadrats/icons": "^1.1.1",
|
|
26
26
|
"@quadrats/locales": "^1.0.0",
|
|
27
27
|
"@quadrats/theme": "^1.0.0",
|
|
28
28
|
"@quadrats/utils": "^1.0.0",
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface ColumnDragButtonProps {
|
|
3
|
+
columnIndex: number;
|
|
4
|
+
style?: React.CSSProperties;
|
|
5
|
+
onClick: (e: React.MouseEvent) => void;
|
|
6
|
+
checkIsTitleColumn: (columnIndex: number) => boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare const COLUMN_DRAG_TYPE = "TABLE_COLUMN";
|
|
9
|
+
export declare const ColumnDragButton: React.FC<ColumnDragButtonProps>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React, { useRef, useEffect } from 'react';
|
|
2
|
+
import { useDrag } from 'react-dnd';
|
|
3
|
+
import { getEmptyImage } from 'react-dnd-html5-backend';
|
|
4
|
+
import clsx from 'clsx';
|
|
5
|
+
import { Icon } from '@quadrats/react/components';
|
|
6
|
+
import { Drag } from '@quadrats/icons';
|
|
7
|
+
import { useTableDragContext } from '../contexts/TableDragContext.js';
|
|
8
|
+
|
|
9
|
+
const COLUMN_DRAG_TYPE = 'TABLE_COLUMN';
|
|
10
|
+
const ColumnDragButton = ({ columnIndex, style, onClick, checkIsTitleColumn, }) => {
|
|
11
|
+
const buttonRef = useRef(null);
|
|
12
|
+
const { setDragState, setDropTargetIndex, setDragDirection } = useTableDragContext();
|
|
13
|
+
const isTitle = checkIsTitleColumn(columnIndex);
|
|
14
|
+
const [{ isDragging }, drag, preview] = useDrag(() => ({
|
|
15
|
+
type: COLUMN_DRAG_TYPE,
|
|
16
|
+
item: () => {
|
|
17
|
+
const dragItem = { columnIndex, isTitle };
|
|
18
|
+
setDragState({ type: 'column', columnIndex, isTitle });
|
|
19
|
+
return dragItem;
|
|
20
|
+
},
|
|
21
|
+
collect: (monitor) => ({
|
|
22
|
+
isDragging: monitor.isDragging(),
|
|
23
|
+
}),
|
|
24
|
+
end: () => {
|
|
25
|
+
setDragState(null);
|
|
26
|
+
setDropTargetIndex(null);
|
|
27
|
+
setDragDirection(null);
|
|
28
|
+
},
|
|
29
|
+
}), [columnIndex, isTitle, setDragState, setDropTargetIndex, setDragDirection]);
|
|
30
|
+
// 使用空白圖片作為 drag preview,避免顯示預設的拖曳圖像
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
preview(getEmptyImage(), { captureDraggingState: true });
|
|
33
|
+
}, [preview]);
|
|
34
|
+
drag(buttonRef);
|
|
35
|
+
return (React.createElement("button", { ref: buttonRef, type: "button", contentEditable: false, style: style, onClick: onClick, title: "\u9EDE\u64CA\u958B\u555F\u9078\u55AE\uFF0C\u62D6\u66F3\u4EE5\u79FB\u52D5", className: clsx('qdr-table__cell-column-action', {
|
|
36
|
+
'qdr-table__cell-column-action--dragging': isDragging,
|
|
37
|
+
}) },
|
|
38
|
+
React.createElement(Icon, { icon: Drag, width: 20, height: 20 })));
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export { COLUMN_DRAG_TYPE, ColumnDragButton };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface RowDragButtonProps {
|
|
3
|
+
rowIndex: number;
|
|
4
|
+
headerRowCount: number;
|
|
5
|
+
style?: React.CSSProperties;
|
|
6
|
+
onClick: (e: React.MouseEvent) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare const ROW_DRAG_TYPE = "TABLE_ROW";
|
|
9
|
+
export declare const RowDragButton: React.FC<RowDragButtonProps>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React, { useRef, useEffect } from 'react';
|
|
2
|
+
import { useDrag } from 'react-dnd';
|
|
3
|
+
import { getEmptyImage } from 'react-dnd-html5-backend';
|
|
4
|
+
import clsx from 'clsx';
|
|
5
|
+
import { Icon } from '@quadrats/react/components';
|
|
6
|
+
import { Drag } from '@quadrats/icons';
|
|
7
|
+
import { useTableDragContext } from '../contexts/TableDragContext.js';
|
|
8
|
+
|
|
9
|
+
const ROW_DRAG_TYPE = 'TABLE_ROW';
|
|
10
|
+
const RowDragButton = ({ rowIndex, headerRowCount, style, onClick }) => {
|
|
11
|
+
const buttonRef = useRef(null);
|
|
12
|
+
const { setDragState, setDropTargetIndex, setDragDirection } = useTableDragContext();
|
|
13
|
+
// 判斷當前 row 是否在 header 中
|
|
14
|
+
const isInHeader = rowIndex < headerRowCount;
|
|
15
|
+
const [{ isDragging }, drag, preview] = useDrag(() => ({
|
|
16
|
+
type: ROW_DRAG_TYPE,
|
|
17
|
+
item: () => {
|
|
18
|
+
const dragItem = { rowIndex, isInHeader };
|
|
19
|
+
setDragState({ type: 'row', rowIndex, isInHeader });
|
|
20
|
+
return dragItem;
|
|
21
|
+
},
|
|
22
|
+
collect: (monitor) => ({
|
|
23
|
+
isDragging: monitor.isDragging(),
|
|
24
|
+
}),
|
|
25
|
+
end: () => {
|
|
26
|
+
setDragState(null);
|
|
27
|
+
setDropTargetIndex(null);
|
|
28
|
+
setDragDirection(null);
|
|
29
|
+
},
|
|
30
|
+
}), [rowIndex, isInHeader, setDragState, setDropTargetIndex, setDragDirection]);
|
|
31
|
+
// 使用空白圖片作為 drag preview,避免顯示預設的拖曳圖像
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
preview(getEmptyImage(), { captureDraggingState: true });
|
|
34
|
+
}, [preview]);
|
|
35
|
+
drag(buttonRef);
|
|
36
|
+
return (React.createElement("button", { ref: buttonRef, type: "button", contentEditable: false, style: style, onClick: onClick, title: "\u9EDE\u64CA\u958B\u555F\u9078\u55AE\uFF0C\u62D6\u66F3\u4EE5\u79FB\u52D5", className: clsx('qdr-table__cell-row-action', {
|
|
37
|
+
'qdr-table__cell-row-action--dragging': isDragging,
|
|
38
|
+
}) },
|
|
39
|
+
React.createElement(Icon, { icon: Drag, width: 20, height: 20 })));
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export { ROW_DRAG_TYPE, RowDragButton };
|
|
@@ -3,6 +3,7 @@ import { Element } from '@quadrats/core';
|
|
|
3
3
|
import { TableActionsContext } from '../contexts/TableActionsContext.js';
|
|
4
4
|
import { TableMetadataContext } from '../contexts/TableMetadataContext.js';
|
|
5
5
|
import { TableStateContext } from '../contexts/TableStateContext.js';
|
|
6
|
+
import { TableDragProvider } from '../contexts/TableDragContext.js';
|
|
6
7
|
import { TABLE_ROW_TYPE, TABLE_CELL_TYPE, TABLE_DEFAULT_MAX_COLUMNS, TABLE_DEFAULT_MAX_ROWS } from '@quadrats/common/table';
|
|
7
8
|
import { useTableActions } from '../hooks/useTableActions.js';
|
|
8
9
|
import { Icon } from '@quadrats/react/components';
|
|
@@ -11,10 +12,9 @@ import { useTableStates } from '../hooks/useTableStates.js';
|
|
|
11
12
|
import { getTableElements } from '../utils/helper.js';
|
|
12
13
|
|
|
13
14
|
function Table({ attributes, children, element, }) {
|
|
14
|
-
const { addColumn, addRow, addColumnAndRow, deleteRow, deleteColumn, moveRowToBody, moveRowToHeader, unsetColumnAsTitle, setColumnAsTitle, pinColumn, pinRow, unpinColumn, unpinRow, } = useTableActions(element);
|
|
15
|
+
const { addColumn, addRow, addColumnAndRow, deleteRow, deleteColumn, moveRowToBody, moveRowToHeader, unsetColumnAsTitle, setColumnAsTitle, pinColumn, pinRow, unpinColumn, unpinRow, moveOrSwapRow, moveOrSwapColumn, } = useTableActions(element);
|
|
15
16
|
const { tableSelectedOn, setTableSelectedOn, tableHoveredOn, setTableHoveredOn } = useTableStates();
|
|
16
17
|
const portalContainerRef = useRef(null);
|
|
17
|
-
// 優化表格結構計算 - 使用 getTableElements 重用邏輯
|
|
18
18
|
const { columnCount, rowCount, normalCols, bodyCount, tableElements } = useMemo(() => {
|
|
19
19
|
const elements = getTableElements(element);
|
|
20
20
|
if (!elements.tableMainElement) {
|
|
@@ -161,6 +161,8 @@ function Table({ attributes, children, element, }) {
|
|
|
161
161
|
pinRow,
|
|
162
162
|
unpinColumn,
|
|
163
163
|
unpinRow,
|
|
164
|
+
moveOrSwapRow,
|
|
165
|
+
moveOrSwapColumn,
|
|
164
166
|
}), [
|
|
165
167
|
addColumn,
|
|
166
168
|
addRow,
|
|
@@ -175,6 +177,8 @@ function Table({ attributes, children, element, }) {
|
|
|
175
177
|
pinRow,
|
|
176
178
|
unpinColumn,
|
|
177
179
|
unpinRow,
|
|
180
|
+
moveOrSwapRow,
|
|
181
|
+
moveOrSwapColumn,
|
|
178
182
|
]);
|
|
179
183
|
const metadataValue = useMemo(() => ({
|
|
180
184
|
tableElement: element,
|
|
@@ -211,21 +215,22 @@ function Table({ attributes, children, element, }) {
|
|
|
211
215
|
tableHoveredOn,
|
|
212
216
|
setTableHoveredOn,
|
|
213
217
|
}), [tableSelectedOn, setTableSelectedOn, tableHoveredOn, setTableHoveredOn]);
|
|
214
|
-
return (React.createElement(
|
|
215
|
-
React.createElement(
|
|
216
|
-
React.createElement(
|
|
217
|
-
React.createElement(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
evt
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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 })))))));
|
|
229
234
|
}
|
|
230
235
|
|
|
231
236
|
export { Table as default };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import React, { useContext, useMemo, useRef, useState, useEffect } from 'react';
|
|
1
|
+
import React, { useContext, useCallback, useMemo, useRef, useState, useEffect } from 'react';
|
|
2
2
|
import clsx from 'clsx';
|
|
3
|
-
import { Transforms } from '@quadrats/core';
|
|
3
|
+
import { Element, Transforms } from '@quadrats/core';
|
|
4
4
|
import { useSlateStatic } from 'slate-react';
|
|
5
|
+
import { useDrop } from 'react-dnd';
|
|
5
6
|
import { TableHeaderContext } from '../contexts/TableHeaderContext.js';
|
|
6
|
-
import { Portal
|
|
7
|
-
import { Drag } from '@quadrats/icons';
|
|
7
|
+
import { Portal } from '@quadrats/react/components';
|
|
8
8
|
import { useTableMetadata } from '../hooks/useTableMetadata.js';
|
|
9
9
|
import { useTableStateContext } from '../hooks/useTableStateContext.js';
|
|
10
10
|
import { InlineToolbar } from '@quadrats/react/toolbar';
|
|
@@ -12,12 +12,19 @@ import { useTableCellFocused, useTableCellPosition, useTableCellTransformContent
|
|
|
12
12
|
import { useTableCellToolbarActions } from '../hooks/useTableCellToolbarActions.js';
|
|
13
13
|
import { TableScrollContext } from '../contexts/TableScrollContext.js';
|
|
14
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';
|
|
15
20
|
|
|
16
21
|
function TableCell(props) {
|
|
17
|
-
var _a;
|
|
22
|
+
var _a, _b;
|
|
18
23
|
const { attributes, children, element } = props;
|
|
19
24
|
const { tableSelectedOn, setTableSelectedOn, tableHoveredOn, setTableHoveredOn } = useTableStateContext();
|
|
20
25
|
const { columnCount, rowCount, portalContainerRef, isColumnPinned, tableElement } = useTableMetadata();
|
|
26
|
+
const { moveOrSwapRow, moveOrSwapColumn } = useTableActionsContext();
|
|
27
|
+
const { dragState, dropTargetIndex, setDropTargetIndex, dragDirection, setDragDirection } = useTableDragContext();
|
|
21
28
|
// Component context
|
|
22
29
|
const { isHeader } = useContext(TableHeaderContext);
|
|
23
30
|
const { scrollTop, scrollLeft, scrollRef } = useContext(TableScrollContext);
|
|
@@ -26,6 +33,20 @@ function TableCell(props) {
|
|
|
26
33
|
const focused = useTableCellFocused(element, editor);
|
|
27
34
|
const cellPosition = useTableCellPosition(element);
|
|
28
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]);
|
|
29
50
|
// Toolbar actions
|
|
30
51
|
const { focusToolbarIconGroups, inlineToolbarIconGroups } = useTableCellToolbarActions({
|
|
31
52
|
element,
|
|
@@ -35,10 +56,13 @@ function TableCell(props) {
|
|
|
35
56
|
});
|
|
36
57
|
const isSelectedInSameRow = (tableSelectedOn === null || tableSelectedOn === void 0 ? void 0 : tableSelectedOn.region) === 'row' && (tableSelectedOn === null || tableSelectedOn === void 0 ? void 0 : tableSelectedOn.index) === cellPosition.rowIndex;
|
|
37
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;
|
|
38
62
|
const isSelectionTriggerByMe = (isSelectedInSameRow && cellPosition.columnIndex === 0) || (isSelectedInSameColumn && cellPosition.rowIndex === 0);
|
|
39
|
-
const
|
|
63
|
+
const showColumnActionButton = useMemo(() => cellPosition.rowIndex === 0 &&
|
|
40
64
|
(isSelectedInSameColumn || ((tableHoveredOn === null || tableHoveredOn === void 0 ? void 0 : tableHoveredOn.columnIndex) === cellPosition.columnIndex && !tableSelectedOn)), [cellPosition, isSelectedInSameColumn, tableHoveredOn, tableSelectedOn]);
|
|
41
|
-
const
|
|
65
|
+
const showRowActionButton = useMemo(() => cellPosition.columnIndex === 0 &&
|
|
42
66
|
(isSelectedInSameRow || ((tableHoveredOn === null || tableHoveredOn === void 0 ? void 0 : tableHoveredOn.rowIndex) === cellPosition.rowIndex && !tableSelectedOn)), [cellPosition, isSelectedInSameRow, tableHoveredOn, tableSelectedOn]);
|
|
43
67
|
// 用於定位 InlineToolbar 的 ref
|
|
44
68
|
const cellRef = useRef(null);
|
|
@@ -46,6 +70,64 @@ function TableCell(props) {
|
|
|
46
70
|
const [rowButtonPosition, setRowButtonPosition] = useState(null);
|
|
47
71
|
const [columnButtonPosition, setColumnButtonPosition] = useState(null);
|
|
48
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));
|
|
49
131
|
// Column resize
|
|
50
132
|
const { isResizing, handleResizeStart } = useColumnResize({
|
|
51
133
|
tableElement,
|
|
@@ -125,12 +207,40 @@ function TableCell(props) {
|
|
|
125
207
|
}, className: clsx('qdr-table__cell', {
|
|
126
208
|
'qdr-table__cell--header': isHeader || element.treatAsTitle,
|
|
127
209
|
'qdr-table__cell--pinned': myColumnIsPinned,
|
|
128
|
-
'qdr-table__cell--top-active': isSelectedInSameRow ||
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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),
|
|
132
226
|
'qdr-table__cell--is-selection-trigger-by-me': isSelectionTriggerByMe,
|
|
133
|
-
|
|
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
|
|
134
244
|
? {
|
|
135
245
|
left: cellStuckAtLeft,
|
|
136
246
|
}
|
|
@@ -144,14 +254,13 @@ function TableCell(props) {
|
|
|
144
254
|
top: toolbarPosition === null || toolbarPosition === void 0 ? void 0 : toolbarPosition.top,
|
|
145
255
|
left: toolbarPosition === null || toolbarPosition === void 0 ? void 0 : toolbarPosition.left,
|
|
146
256
|
}, iconGroups: focusToolbarIconGroups }))),
|
|
147
|
-
|
|
148
|
-
React.createElement(
|
|
257
|
+
showRowActionButton && (React.createElement(Portal, { getContainer: () => portalContainerRef.current || document.body },
|
|
258
|
+
React.createElement(RowDragButton, { rowIndex: cellPosition.rowIndex, headerRowCount: headerRowCount, style: {
|
|
149
259
|
top: rowButtonPosition === null || rowButtonPosition === void 0 ? void 0 : rowButtonPosition.top,
|
|
150
260
|
left: rowButtonPosition === null || rowButtonPosition === void 0 ? void 0 : rowButtonPosition.left,
|
|
151
261
|
}, onClick: (e) => {
|
|
152
262
|
e.preventDefault();
|
|
153
263
|
e.stopPropagation();
|
|
154
|
-
// Clear focus by removing selection
|
|
155
264
|
Transforms.deselect(editor);
|
|
156
265
|
setTableSelectedOn((prev) => {
|
|
157
266
|
if ((prev === null || prev === void 0 ? void 0 : prev.region) === 'row' && prev.index === cellPosition.rowIndex) {
|
|
@@ -159,17 +268,15 @@ function TableCell(props) {
|
|
|
159
268
|
}
|
|
160
269
|
return { region: 'row', index: cellPosition.rowIndex };
|
|
161
270
|
});
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
React.createElement("button", { type: "button", contentEditable: false, style: {
|
|
271
|
+
} }))),
|
|
272
|
+
showColumnActionButton && (React.createElement(Portal, { getContainer: () => portalContainerRef.current || document.body },
|
|
273
|
+
React.createElement(ColumnDragButton, { columnIndex: cellPosition.columnIndex, style: {
|
|
166
274
|
// pinned 時因為 sticky 所以要扣掉 scrollTop
|
|
167
|
-
top: ((
|
|
275
|
+
top: ((_b = columnButtonPosition === null || columnButtonPosition === void 0 ? void 0 : columnButtonPosition.top) !== null && _b !== void 0 ? _b : 0) - (element.pinned ? scrollTop : 0),
|
|
168
276
|
left: columnButtonPosition === null || columnButtonPosition === void 0 ? void 0 : columnButtonPosition.left,
|
|
169
277
|
}, onClick: (e) => {
|
|
170
278
|
e.preventDefault();
|
|
171
279
|
e.stopPropagation();
|
|
172
|
-
// Clear focus by removing selection
|
|
173
280
|
Transforms.deselect(editor);
|
|
174
281
|
setTableSelectedOn((prev) => {
|
|
175
282
|
if ((prev === null || prev === void 0 ? void 0 : prev.region) === 'column' && prev.index === cellPosition.columnIndex) {
|
|
@@ -177,8 +284,7 @@ function TableCell(props) {
|
|
|
177
284
|
}
|
|
178
285
|
return { region: 'column', index: cellPosition.columnIndex };
|
|
179
286
|
});
|
|
180
|
-
},
|
|
181
|
-
React.createElement(Icon, { icon: Drag, width: 20, height: 20 })))),
|
|
287
|
+
}, checkIsTitleColumn: checkIsTitleColumn }))),
|
|
182
288
|
isSelectionTriggerByMe && (cellPosition.columnIndex === 0 || cellPosition.rowIndex === 0) ? (React.createElement(Portal, { getContainer: () => portalContainerRef.current || document.body },
|
|
183
289
|
React.createElement(InlineToolbar, { className: "qdr-table__cell__inline-table-toolbar", style: {
|
|
184
290
|
top: toolbarPosition === null || toolbarPosition === void 0 ? void 0 : toolbarPosition.top,
|