@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.
@@ -1,5 +1,5 @@
1
1
  import React, { useMemo } from 'react';
2
- import { UnorderedList, OrderedList, Paragraph, AddRowAtBottom, AddRowAtTop, AddColumnAtLeft, AddColumnAtRight, Unpinned, Pinned, TableRemoveTitle, TableSetColumnTitle, TableSetRowTitle, AlignLeft, AlignCenter, AlignRight, Trash } from '@quadrats/icons';
2
+ import { UnorderedList, OrderedList, Paragraph, AddRowAtBottom, AddRowAtTop, AddColumnAtLeft, AddColumnAtRight, Unpinned, Pinned, TableRemoveTitle, TableSetColumnTitle, TableSetRowTitle, AlignLeft, AlignCenter, AlignRight, TableMoveDown, TableMoveUp, TableMoveRight, TableMoveLeft, Trash } from '@quadrats/icons';
3
3
  import { Element, PARAGRAPH_TYPE } from '@quadrats/core';
4
4
  import { LIST_TYPES } from '@quadrats/common/list';
5
5
  import { useTableActionsContext } from './useTableActionsContext.js';
@@ -15,10 +15,10 @@ import { useSlateStatic } from 'slate-react';
15
15
  function useTableCellToolbarActions({ element, cellPosition, isHeader, transformCellContent, }) {
16
16
  const editor = useSlateStatic();
17
17
  const { tableSelectedOn, setTableSelectedOn } = useTableStateContext();
18
- const { tableElement, isReachMaximumColumns, isReachMinimumNormalColumns, isReachMinimumBodyRows, isColumnPinned, isRowPinned, } = useTableMetadata();
18
+ const { tableElement, columnCount, rowCount, isReachMaximumColumns, isReachMinimumNormalColumns, isReachMinimumBodyRows, isColumnPinned, isRowPinned, } = useTableMetadata();
19
19
  const setAlign = useTableCellAlign(tableElement, editor);
20
20
  const getAlign = useTableCellAlignStatus(tableElement, editor);
21
- const { deleteRow, deleteColumn, addRow, addColumn, moveRowToBody, moveRowToHeader, unsetColumnAsTitle, setColumnAsTitle, pinColumn, unpinColumn, pinRow, unpinRow, } = useTableActionsContext();
21
+ const { deleteRow, deleteColumn, addRow, addColumn, moveRowToBody, moveRowToHeader, unsetColumnAsTitle, setColumnAsTitle, pinColumn, unpinColumn, pinRow, unpinRow, moveOrSwapRow, moveOrSwapColumn, } = useTableActionsContext();
22
22
  // 獲取當前 cell 內容的類型(用於顯示對應的 icon)
23
23
  const getCurrentContentType = useMemo(() => {
24
24
  // 檢查 cell 的第一個 child 的類型
@@ -359,6 +359,116 @@ function useTableCellToolbarActions({ element, cellPosition, isHeader, transform
359
359
  },
360
360
  ].filter((i) => i !== undefined);
361
361
  }, [tableSelectedOn, addColumn, setTableSelectedOn, isReachMaximumColumns]);
362
+ // Row move actions
363
+ const rowMoveActions = useMemo(() => {
364
+ if ((tableSelectedOn === null || tableSelectedOn === void 0 ? void 0 : tableSelectedOn.region) !== 'row' || typeof tableSelectedOn.index !== 'number') {
365
+ return null;
366
+ }
367
+ const rowIndex = tableSelectedOn.index;
368
+ // 從 tableElement 獲取 header 和 body 的資訊
369
+ const tableMainElement = tableElement.children.find((child) => Element.isElement(child) && child.type.includes('table_main'));
370
+ if (!tableMainElement || !Element.isElement(tableMainElement)) {
371
+ return null;
372
+ }
373
+ const tableHeaderElement = tableMainElement.children.find((child) => Element.isElement(child) && child.type.includes('table_header'));
374
+ const headerRowCount = tableHeaderElement && Element.isElement(tableHeaderElement) ? tableHeaderElement.children.length : 0;
375
+ // 判斷當前列是在 header 還是 body 中
376
+ const currentInHeader = rowIndex < headerRowCount;
377
+ // 判斷上方和下方是否可以互換
378
+ const canMoveUp = rowIndex > 0; // 不在第一列
379
+ const canMoveDown = rowIndex < rowCount - 1; // 不在最後一列
380
+ // 檢查目標位置是否在相同的容器中(header 或 body)
381
+ const targetUpInHeader = rowIndex - 1 < headerRowCount;
382
+ const targetDownInHeader = rowIndex + 1 < headerRowCount;
383
+ // 標題列只能與標題列互換,一般列只能與一般列互換
384
+ const canSwapUp = canMoveUp && currentInHeader === targetUpInHeader;
385
+ const canSwapDown = canMoveDown && currentInHeader === targetDownInHeader;
386
+ return [
387
+ canSwapDown
388
+ ? {
389
+ icon: TableMoveDown,
390
+ onClick: () => {
391
+ if (typeof tableSelectedOn.index === 'number') {
392
+ moveOrSwapRow(tableSelectedOn.index, tableSelectedOn.index + 1, 'swap');
393
+ setTableSelectedOn(undefined);
394
+ }
395
+ },
396
+ }
397
+ : undefined,
398
+ canSwapUp
399
+ ? {
400
+ icon: TableMoveUp,
401
+ onClick: () => {
402
+ if (typeof tableSelectedOn.index === 'number') {
403
+ moveOrSwapRow(tableSelectedOn.index, tableSelectedOn.index - 1, 'swap');
404
+ setTableSelectedOn(undefined);
405
+ }
406
+ },
407
+ }
408
+ : undefined,
409
+ ].filter((i) => i !== undefined);
410
+ }, [tableSelectedOn, setTableSelectedOn, rowCount, tableElement, moveOrSwapRow]);
411
+ // Column move actions
412
+ const columnMoveActions = useMemo(() => {
413
+ if ((tableSelectedOn === null || tableSelectedOn === void 0 ? void 0 : tableSelectedOn.region) !== 'column' || typeof tableSelectedOn.index !== 'number') {
414
+ return null;
415
+ }
416
+ const columnIndex = tableSelectedOn.index;
417
+ // 檢查當前行是否為標題行
418
+ const currentIsTitle = !!element.treatAsTitle;
419
+ // 判斷左右是否可以移動
420
+ const canMoveLeft = columnIndex > 0;
421
+ const canMoveRight = columnIndex < columnCount - 1;
422
+ // 檢查相鄰行是否為相同類型(都是標題行或都不是標題行)
423
+ const checkAdjacentColumnType = (adjacentIndex) => {
424
+ // 從 tableElement 中獲取相鄰行的 treatAsTitle 狀態
425
+ const tableMainElement = tableElement.children.find((child) => Element.isElement(child) && child.type.includes('table_main'));
426
+ if (!tableMainElement || !Element.isElement(tableMainElement)) {
427
+ return false;
428
+ }
429
+ const tableBodyElement = tableMainElement.children.find((child) => Element.isElement(child) && child.type.includes('table_body'));
430
+ if (!tableBodyElement || !Element.isElement(tableBodyElement)) {
431
+ return false;
432
+ }
433
+ // 從 body 的第一列獲取相鄰 cell 的 treatAsTitle 屬性
434
+ const firstRow = tableBodyElement.children[0];
435
+ if (!Element.isElement(firstRow)) {
436
+ return false;
437
+ }
438
+ const adjacentCell = firstRow.children[adjacentIndex];
439
+ if (!Element.isElement(adjacentCell)) {
440
+ return false;
441
+ }
442
+ return !!adjacentCell.treatAsTitle;
443
+ };
444
+ // 標題行只能與標題行互換,一般行只能與一般行互換
445
+ const canSwapLeft = canMoveLeft && currentIsTitle === checkAdjacentColumnType(columnIndex - 1);
446
+ const canSwapRight = canMoveRight && currentIsTitle === checkAdjacentColumnType(columnIndex + 1);
447
+ return [
448
+ canSwapRight
449
+ ? {
450
+ icon: TableMoveRight,
451
+ onClick: () => {
452
+ if (typeof tableSelectedOn.index === 'number') {
453
+ moveOrSwapColumn(tableSelectedOn.index, tableSelectedOn.index + 1, 'swap');
454
+ setTableSelectedOn(undefined);
455
+ }
456
+ },
457
+ }
458
+ : undefined,
459
+ canSwapLeft
460
+ ? {
461
+ icon: TableMoveLeft,
462
+ onClick: () => {
463
+ if (typeof tableSelectedOn.index === 'number') {
464
+ moveOrSwapColumn(tableSelectedOn.index, tableSelectedOn.index - 1, 'swap');
465
+ setTableSelectedOn(undefined);
466
+ }
467
+ },
468
+ }
469
+ : undefined,
470
+ ].filter((i) => i !== undefined);
471
+ }, [tableSelectedOn, setTableSelectedOn, columnCount, element.treatAsTitle, tableElement, moveOrSwapColumn]);
362
472
  // Delete actions
363
473
  const deleteActions = useMemo(() => {
364
474
  return [
@@ -390,11 +500,23 @@ function useTableCellToolbarActions({ element, cellPosition, isHeader, transform
390
500
  {
391
501
  icons: rowAddActions || columnAddActions || [],
392
502
  },
503
+ {
504
+ icons: rowMoveActions || columnMoveActions || [],
505
+ },
393
506
  {
394
507
  icons: deleteActions,
395
508
  },
396
509
  ];
397
- }, [rowActions, columnActions, columnAlignActions, rowAddActions, columnAddActions, deleteActions]);
510
+ }, [
511
+ rowActions,
512
+ columnActions,
513
+ columnAlignActions,
514
+ rowAddActions,
515
+ columnAddActions,
516
+ rowMoveActions,
517
+ columnMoveActions,
518
+ deleteActions,
519
+ ]);
398
520
  return {
399
521
  focusToolbarIconGroups,
400
522
  inlineToolbarIconGroups,