@quadrats/react 1.1.0 → 1.1.2

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.
@@ -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(MessageProvider, null,
15
- React.createElement(ModalProvider, { needConfirmModal: needConfirmModal, setNeedConfirmModal: setNeedConfirmModal }, children)))));
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(DndProvider, { backend: HTML5Backend },
158
- 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', {
159
- 'qdr-carousel-modal__grid--isDragging': isDragging,
160
- }) }, 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) => {
161
- change(index, { url: item.url, caption: value });
162
- }, onRemove: () => {
163
- remove(index);
164
- }, swap: swap }))))) : (React.createElement("div", { className: "qdr-carousel-modal__placeholder" },
165
- React.createElement("div", { className: "qdr-carousel-modal__placeholder__block" },
166
- React.createElement("div", { className: "qdr-carousel-modal__placeholder__icon" },
167
- React.createElement(Icon, { icon: Upload, width: 32, height: 32 })),
168
- React.createElement("div", { className: "qdr-carousel-modal__placeholder__title" }, "\u62D6\u66F3\u6A94\u6848\u5230\u6B64\u4E0A\u50B3"),
169
- React.createElement("div", { className: "qdr-carousel-modal__placeholder__hint" }, (controller === null || controller === void 0 ? void 0 : controller.ratio)
170
- ? `僅能上傳 ${acceptText};建議比例為 ${controller.ratio[0]}:${controller.ratio[1]} 且寬度至少達 2000px 以上;檔案大小不可超過 ${controller === null || controller === void 0 ? void 0 : controller.limitSize}MB`
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(reactDnd.DndProvider, { backend: reactDndHtml5Backend.HTML5Backend },
370
- 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', {
371
- 'qdr-carousel-modal__grid--isDragging': isDragging,
372
- }) }, 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) => {
373
- change(index, { url: item.url, caption: value });
374
- }, onRemove: () => {
375
- remove(index);
376
- }, swap: swap }))))) : (React.createElement("div", { className: "qdr-carousel-modal__placeholder" },
377
- React.createElement("div", { className: "qdr-carousel-modal__placeholder__block" },
378
- React.createElement("div", { className: "qdr-carousel-modal__placeholder__icon" },
379
- React.createElement(components.Icon, { icon: icons.Upload, width: 32, height: 32 })),
380
- React.createElement("div", { className: "qdr-carousel-modal__placeholder__title" }, "\u62D6\u66F3\u6A94\u6848\u5230\u6B64\u4E0A\u50B3"),
381
- React.createElement("div", { className: "qdr-carousel-modal__placeholder__hint" }, (controller === null || controller === void 0 ? void 0 : controller.ratio)
382
- ? `僅能上傳 ${acceptText};建議比例為 ${controller.ratio[0]}:${controller.ratio[1]} 且寬度至少達 2000px 以上;檔案大小不可超過 ${controller === null || controller === void 0 ? void 0 : controller.limitSize}MB`
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(MessageProvider, null,
605
- React.createElement(ModalProvider, { needConfirmModal: needConfirmModal, setNeedConfirmModal: setNeedConfirmModal }, children)))));
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.0",
3
+ "version": "1.1.2",
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.0",
23
+ "@quadrats/common": "^1.1.1",
24
24
  "@quadrats/core": "^1.1.0",
25
- "@quadrats/icons": "^1.1.0",
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",
@@ -30,8 +30,8 @@
30
30
  "@types/react-transition-group": "^4.4.9",
31
31
  "clsx": "^2.1.1",
32
32
  "is-hotkey": "^0.2.0",
33
- "react-dnd": "^16.0.1",
34
- "react-dnd-html5-backend": "^16.0.1",
33
+ "react-dnd": "^15.1.2",
34
+ "react-dnd-html5-backend": "^15.1.2",
35
35
  "react-transition-group": "^4.4.5",
36
36
  "slate-dom": "^0.112.2",
37
37
  "slate-react": "^0.112.1",
@@ -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(TableActionsContext.Provider, { value: actionsValue },
215
- React.createElement(TableMetadataContext.Provider, { value: metadataValue },
216
- React.createElement(TableStateContext.Provider, { value: stateValue },
217
- React.createElement("div", Object.assign({}, attributes, { className: "qdr-table" }),
218
- children,
219
- React.createElement("button", { type: "button", onClick: (evt) => {
220
- evt.preventDefault();
221
- evt.stopPropagation();
222
- setTableSelectedOn((prev) => ((prev === null || prev === void 0 ? void 0 : prev.region) === 'table' ? undefined : { region: 'table' }));
223
- }, onMouseDown: (evt) => {
224
- evt.preventDefault();
225
- evt.stopPropagation();
226
- }, className: "qdr-table__selection", title: (tableSelectedOn === null || tableSelectedOn === void 0 ? void 0 : tableSelectedOn.region) === 'table' ? 'Deselect Table' : 'Select Table' },
227
- React.createElement(Icon, { icon: Drag, width: 20, height: 20 })),
228
- React.createElement("div", { ref: portalContainerRef, className: "qdr-table__portal-container", "data-slate-editor": false, contentEditable: false }))))));
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, Icon } from '@quadrats/react/components';
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 showRowActionButton = useMemo(() => cellPosition.rowIndex === 0 &&
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 showColumnActionButton = useMemo(() => cellPosition.columnIndex === 0 &&
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 || (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),
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
- }), style: myColumnIsPinned
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
- showColumnActionButton && (React.createElement(Portal, { getContainer: () => portalContainerRef.current || document.body },
148
- React.createElement("button", { type: "button", contentEditable: false, style: {
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
- }, 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: {
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: ((_a = columnButtonPosition === null || columnButtonPosition === void 0 ? void 0 : columnButtonPosition.top) !== null && _a !== void 0 ? _a : 0) - (element.pinned ? scrollTop : 0),
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
- }, className: "qdr-table__cell-column-action" },
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,
@@ -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 {};