react-glide-table 1.1.5 → 1.1.7

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/dist/index.js CHANGED
@@ -31,7 +31,7 @@ import {
31
31
  useCallback as useCallback3,
32
32
  useEffect as useEffect4,
33
33
  useMemo as useMemo2,
34
- useRef as useRef3,
34
+ useRef as useRef4,
35
35
  useState as useState3
36
36
  } from "react";
37
37
 
@@ -175,7 +175,7 @@ function useCellEdit({
175
175
  }
176
176
 
177
177
  // src/components/ui/table/features/cell-selection/useCellSelection.ts
178
- import { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
178
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
179
179
 
180
180
  // src/components/ui/table/features/cell-selection/cellSelection.ts
181
181
  var INITIAL_DRAG_STATE = {
@@ -452,6 +452,127 @@ function hasCellSelectionEdges(style) {
452
452
  );
453
453
  }
454
454
 
455
+ // src/components/ui/table/features/cell-selection/copyData.ts
456
+ function formatCellValue(value) {
457
+ if (value === null || value === void 0) return "";
458
+ return String(value);
459
+ }
460
+ function getNestedValue(row, path) {
461
+ if (!path.includes(".")) return row[path];
462
+ return path.split(".").reduce((current, key) => {
463
+ if (current === null || current === void 0 || typeof current !== "object") {
464
+ return void 0;
465
+ }
466
+ return current[key];
467
+ }, row);
468
+ }
469
+ function readRowColumnValue(rowData, columnDef) {
470
+ if ("accessorFn" in columnDef && typeof columnDef.accessorFn === "function") {
471
+ return columnDef.accessorFn(rowData, 0);
472
+ }
473
+ if ("accessorKey" in columnDef && columnDef.accessorKey != null && columnDef.accessorKey !== "") {
474
+ return getNestedValue(rowData, String(columnDef.accessorKey));
475
+ }
476
+ return void 0;
477
+ }
478
+ function flattenSubtreeRows(row) {
479
+ const children = row.children;
480
+ if (!Array.isArray(children) || children.length === 0) return [];
481
+ const result = [];
482
+ const walk = (nodes) => {
483
+ for (const node of nodes) {
484
+ result.push(node);
485
+ const nested = node.children;
486
+ if (Array.isArray(nested) && nested.length > 0) {
487
+ walk(nested);
488
+ }
489
+ }
490
+ };
491
+ walk(children);
492
+ return result;
493
+ }
494
+ function hasSubtree(row) {
495
+ const children = row.children;
496
+ return Array.isArray(children) && children.length > 0;
497
+ }
498
+ function getOriginalRowId(original) {
499
+ return String(original.id ?? original.uniqueId ?? "");
500
+ }
501
+ function getRowDepth(original) {
502
+ return typeof original.level === "number" ? original.level : 0;
503
+ }
504
+ function collectCopyRowEntries(visibleRows, bounds, mode = "visible") {
505
+ const { startRow, endRow } = bounds;
506
+ const result = [];
507
+ const includedOriginalIds = /* @__PURE__ */ new Set();
508
+ const appendSubtree = (node, depth) => {
509
+ const children = node.children;
510
+ if (!Array.isArray(children) || children.length === 0) return;
511
+ for (const child of children) {
512
+ const childId = getOriginalRowId(child);
513
+ if (!(childId && includedOriginalIds.has(childId))) {
514
+ result.push({ row: child, depth });
515
+ if (childId) includedOriginalIds.add(childId);
516
+ }
517
+ appendSubtree(child, depth + 1);
518
+ }
519
+ };
520
+ for (let rowIndex = startRow; rowIndex <= endRow; rowIndex += 1) {
521
+ const row = visibleRows[rowIndex];
522
+ if (!row) continue;
523
+ const originalId = getOriginalRowId(row.original);
524
+ if (originalId && includedOriginalIds.has(originalId)) continue;
525
+ const depth = getRowDepth(row.original);
526
+ result.push({ row: row.original, depth });
527
+ if (originalId) includedOriginalIds.add(originalId);
528
+ if (mode !== "subtree" || !hasSubtree(row.original)) continue;
529
+ appendSubtree(row.original, depth + 1);
530
+ }
531
+ return result;
532
+ }
533
+ function collectCopyRows(visibleRows, bounds, mode = "visible") {
534
+ return collectCopyRowEntries(visibleRows, bounds, mode).map((entry) => entry.row);
535
+ }
536
+ function serializeCopyRowsToTSV(copyRows, visibleRows, bounds, depths) {
537
+ if (copyRows.length === 0) return "";
538
+ const { startCol, endCol } = bounds;
539
+ const columnCells = visibleRows[0]?.getVisibleCells().slice(startCol, endCol + 1) ?? [];
540
+ if (columnCells.length === 0) return "";
541
+ const resolvedDepths = depths && depths.length === copyRows.length ? depths : copyRows.map((row) => getRowDepth(row));
542
+ const minDepth = Math.min(...resolvedDepths);
543
+ return copyRows.map((rowData, index) => {
544
+ const relativeDepth = Math.max(0, (resolvedDepths[index] ?? 0) - minDepth);
545
+ const line = columnCells.map(
546
+ (cell) => formatCellValue(
547
+ readRowColumnValue(
548
+ rowData,
549
+ cell.column.columnDef
550
+ )
551
+ )
552
+ ).join(" ");
553
+ return `${" ".repeat(relativeDepth)}${line}`;
554
+ }).join("\n");
555
+ }
556
+ function serializeSelectionToTSV(visibleRows, bounds, mode = "visible") {
557
+ const entries = collectCopyRowEntries(visibleRows, bounds, mode);
558
+ return serializeCopyRowsToTSV(
559
+ entries.map((entry) => entry.row),
560
+ visibleRows,
561
+ bounds,
562
+ entries.map((entry) => entry.depth)
563
+ );
564
+ }
565
+ async function writeSelectionToClipboard(visibleRows, bounds, mode = "visible") {
566
+ const text = serializeSelectionToTSV(visibleRows, bounds, mode);
567
+ if (!text) return false;
568
+ try {
569
+ await navigator.clipboard.writeText(text);
570
+ } catch {
571
+ return false;
572
+ }
573
+ return true;
574
+ }
575
+
455
576
  // src/components/ui/table/features/cell-selection/fillData.ts
456
577
  function getColumnAccessorKey2(columnDef) {
457
578
  if ("accessorKey" in columnDef && columnDef.accessorKey) {
@@ -508,15 +629,103 @@ function hasFillExtension(sourceBounds, fillBounds) {
508
629
  return fillBounds.startRow < sourceBounds.startRow || fillBounds.endRow > sourceBounds.endRow || fillBounds.startCol < sourceBounds.startCol || fillBounds.endCol > sourceBounds.endCol;
509
630
  }
510
631
 
632
+ // src/components/ui/table/features/cell-selection/pasteData.ts
633
+ function countLeadingEmptyCells(cells) {
634
+ let depth = 0;
635
+ while (depth < cells.length && cells[depth] === "") {
636
+ depth += 1;
637
+ }
638
+ return depth;
639
+ }
640
+ function looksLikeSubtreeIndentation(leadingEmptyCounts) {
641
+ if (leadingEmptyCounts.length === 0) return false;
642
+ const firstDepth = leadingEmptyCounts[0] ?? 0;
643
+ if (firstDepth !== 0) return false;
644
+ return leadingEmptyCounts.some((depth) => depth > 0);
645
+ }
646
+ function parseClipboardTSV(text) {
647
+ return parseClipboardTSVWithDepths(text).values;
648
+ }
649
+ function parseClipboardTSVWithDepths(text) {
650
+ if (!text) return { values: [], depths: [] };
651
+ const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
652
+ const withoutTrailing = normalized.replace(/\n+$/, "");
653
+ if (!withoutTrailing) return { values: [], depths: [] };
654
+ const rows = withoutTrailing.split("\n").map((line) => line.split(" "));
655
+ const leadingEmptyCounts = rows.map(countLeadingEmptyCells);
656
+ const treatAsDepth = looksLikeSubtreeIndentation(leadingEmptyCounts);
657
+ const values = [];
658
+ const depths = [];
659
+ for (let index = 0; index < rows.length; index += 1) {
660
+ const cells = rows[index] ?? [];
661
+ const depth = leadingEmptyCounts[index] ?? 0;
662
+ if (treatAsDepth) {
663
+ values.push(cells.slice(depth));
664
+ depths.push(depth);
665
+ } else {
666
+ values.push(cells);
667
+ depths.push(0);
668
+ }
669
+ }
670
+ return { values, depths };
671
+ }
672
+ function resolvePasteColumnIds(rows, startCol, width) {
673
+ if (width <= 0) return [];
674
+ const cells = rows[0]?.getVisibleCells() ?? [];
675
+ const columnIds = [];
676
+ for (let offset = 0; offset < width; offset += 1) {
677
+ const cell = cells[startCol + offset];
678
+ if (!cell) break;
679
+ columnIds.push(cell.column.id);
680
+ }
681
+ return columnIds;
682
+ }
683
+ function buildRowsPastePayload(rows, startRow, startCol, text, mode, endRow = startRow) {
684
+ const { values, depths } = parseClipboardTSVWithDepths(text);
685
+ if (values.length === 0) return null;
686
+ const width = Math.max(...values.map((row) => row.length), 0);
687
+ if (width === 0) return null;
688
+ const columnIds = resolvePasteColumnIds(rows, startCol, width);
689
+ if (columnIds.length === 0) return null;
690
+ const rowIds = [];
691
+ for (let offset = 0; offset < values.length; offset += 1) {
692
+ const row = rows[startRow + offset];
693
+ if (!row) break;
694
+ rowIds.push(row.id);
695
+ }
696
+ const anchorRow = rows[endRow] ?? rows[startRow];
697
+ return {
698
+ mode,
699
+ startRow,
700
+ startCol,
701
+ endRow,
702
+ rowIds,
703
+ anchorRowId: anchorRow?.id ?? "",
704
+ columnIds,
705
+ values,
706
+ depths
707
+ };
708
+ }
709
+ function isEditablePasteTarget(target) {
710
+ if (!(target instanceof HTMLElement)) return false;
711
+ const tag = target.tagName;
712
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
713
+ return Boolean(target.isContentEditable);
714
+ }
715
+
511
716
  // src/components/ui/table/features/cell-selection/useCellSelection.ts
512
717
  function useCellSelection({
513
718
  data,
514
719
  rows,
515
720
  enabled = true,
721
+ enableSubtreeCopy = false,
722
+ enableInsertPaste = true,
516
723
  onDataChange,
517
- onBatchChange
724
+ onBatchChange,
725
+ onRowsPaste
518
726
  }) {
519
727
  const [dragState, setDragState] = useState2(INITIAL_DRAG_STATE);
728
+ const pendingPasteModeRef = useRef2(null);
520
729
  const cellSelectionBounds = getCellSelectionBounds(dragState.start, dragState.end);
521
730
  const activeSelectionBounds = enabled ? getActiveSelectionBounds(dragState, cellSelectionBounds) : null;
522
731
  const handleCellMouseDown = useCallback2(
@@ -570,21 +779,113 @@ function useCellSelection({
570
779
  setDragState(INITIAL_DRAG_STATE);
571
780
  }
572
781
  }, [enabled]);
782
+ const copySelection = useCallback2(
783
+ async (options) => {
784
+ if (!enabled || !activeSelectionBounds) return false;
785
+ const mode = options?.includeDescendants && enableSubtreeCopy ? "subtree" : "visible";
786
+ return writeSelectionToClipboard(rows, activeSelectionBounds, mode);
787
+ },
788
+ [activeSelectionBounds, enableSubtreeCopy, enabled, rows]
789
+ );
573
790
  useEffect2(() => {
574
791
  if (!enabled) return;
575
792
  const handleKeyDown = (e) => {
576
- if ((e.ctrlKey || e.metaKey) && e.key === "c" && activeSelectionBounds) {
577
- const { startRow, endRow, startCol, endCol } = activeSelectionBounds;
578
- const selectedData = rows.slice(startRow, endRow + 1).map((row) => {
579
- const cells = row.getVisibleCells();
580
- return cells.slice(startCol, endCol + 1).map((cell) => cell.getValue()).join(" ");
581
- }).join("\n");
582
- navigator.clipboard.writeText(selectedData);
583
- }
793
+ if (!activeSelectionBounds) return;
794
+ if (!(e.ctrlKey || e.metaKey)) return;
795
+ const isSubtreeShortcut = enableSubtreeCopy && e.shiftKey && e.key.toLowerCase() === "c";
796
+ const isVisibleCopyShortcut = !e.shiftKey && e.key.toLowerCase() === "c";
797
+ if (!isSubtreeShortcut && !isVisibleCopyShortcut) return;
798
+ e.preventDefault();
799
+ void copySelection({ includeDescendants: isSubtreeShortcut });
584
800
  };
585
801
  window.addEventListener("keydown", handleKeyDown);
586
802
  return () => window.removeEventListener("keydown", handleKeyDown);
587
- }, [activeSelectionBounds, enabled, rows]);
803
+ }, [activeSelectionBounds, copySelection, enableSubtreeCopy, enabled]);
804
+ const emitRowsPaste = useCallback2(
805
+ (text, mode) => {
806
+ if (!onRowsPaste || !activeSelectionBounds) return false;
807
+ const payload = buildRowsPastePayload(
808
+ rows,
809
+ activeSelectionBounds.startRow,
810
+ activeSelectionBounds.startCol,
811
+ text,
812
+ mode,
813
+ activeSelectionBounds.endRow
814
+ );
815
+ if (!payload) return false;
816
+ onRowsPaste(payload);
817
+ return true;
818
+ },
819
+ [activeSelectionBounds, onRowsPaste, rows]
820
+ );
821
+ useEffect2(() => {
822
+ if (!enabled || !onRowsPaste) return;
823
+ const pasteHandledRef = { current: false };
824
+ const ignoreNextPasteRef = { current: false };
825
+ const handleKeyDown = (e) => {
826
+ if (!activeSelectionBounds) return;
827
+ if (!(e.ctrlKey || e.metaKey)) return;
828
+ if (e.key.toLowerCase() !== "v") return;
829
+ if (isEditablePasteTarget(e.target) || isEditablePasteTarget(document.activeElement)) {
830
+ return;
831
+ }
832
+ if (e.shiftKey && !enableInsertPaste) {
833
+ ignoreNextPasteRef.current = true;
834
+ pendingPasteModeRef.current = null;
835
+ return;
836
+ }
837
+ const mode = e.shiftKey ? "insert" : "overwrite";
838
+ pasteHandledRef.current = false;
839
+ ignoreNextPasteRef.current = false;
840
+ pendingPasteModeRef.current = mode;
841
+ void (async () => {
842
+ try {
843
+ const text = await navigator.clipboard.readText();
844
+ if (pasteHandledRef.current) return;
845
+ if (pendingPasteModeRef.current !== mode) return;
846
+ if (!text) return;
847
+ pasteHandledRef.current = true;
848
+ emitRowsPaste(text, mode);
849
+ pendingPasteModeRef.current = null;
850
+ } catch {
851
+ }
852
+ })();
853
+ };
854
+ const handlePaste = (e) => {
855
+ if (!activeSelectionBounds) return;
856
+ if (isEditablePasteTarget(e.target) || isEditablePasteTarget(document.activeElement)) {
857
+ return;
858
+ }
859
+ if (ignoreNextPasteRef.current) {
860
+ ignoreNextPasteRef.current = false;
861
+ pendingPasteModeRef.current = null;
862
+ return;
863
+ }
864
+ const mode = pendingPasteModeRef.current ?? "overwrite";
865
+ if (pasteHandledRef.current) {
866
+ e.preventDefault();
867
+ return;
868
+ }
869
+ const text = e.clipboardData?.getData("text/plain");
870
+ if (text == null || text === "") return;
871
+ pasteHandledRef.current = true;
872
+ e.preventDefault();
873
+ emitRowsPaste(text, mode);
874
+ pendingPasteModeRef.current = null;
875
+ };
876
+ window.addEventListener("keydown", handleKeyDown);
877
+ window.addEventListener("paste", handlePaste);
878
+ return () => {
879
+ window.removeEventListener("keydown", handleKeyDown);
880
+ window.removeEventListener("paste", handlePaste);
881
+ };
882
+ }, [
883
+ activeSelectionBounds,
884
+ emitRowsPaste,
885
+ enableInsertPaste,
886
+ enabled,
887
+ onRowsPaste
888
+ ]);
588
889
  useEffect2(() => {
589
890
  if (!enabled) return;
590
891
  const handleMouseUp = () => {
@@ -630,12 +931,13 @@ function useCellSelection({
630
931
  activeSelectionBounds,
631
932
  handleCellMouseDown,
632
933
  handleCellMouseEnter,
633
- handleFillHandleMouseDown
934
+ handleFillHandleMouseDown,
935
+ copySelection
634
936
  };
635
937
  }
636
938
 
637
939
  // src/components/ui/table/features/row-expand/row-expand.ts
638
- import { useEffect as useEffect3, useMemo, useRef as useRef2 } from "react";
940
+ import { useEffect as useEffect3, useMemo, useRef as useRef3 } from "react";
639
941
  function getFieldValue(row, key) {
640
942
  return row[key];
641
943
  }
@@ -665,8 +967,8 @@ var useConvertTreeData = ({
665
967
  expandedRows,
666
968
  onExpandedRowsChange
667
969
  }) => {
668
- const onExpandedRowsChangeRef = useRef2(onExpandedRowsChange);
669
- const hasInitializedRef = useRef2(false);
970
+ const onExpandedRowsChangeRef = useRef3(onExpandedRowsChange);
971
+ const hasInitializedRef = useRef3(false);
670
972
  useEffect3(() => {
671
973
  onExpandedRowsChangeRef.current = onExpandedRowsChange;
672
974
  }, [onExpandedRowsChange]);
@@ -712,15 +1014,16 @@ var useConvertTreeData = ({
712
1014
  children: [],
713
1015
  processed: false
714
1016
  }));
715
- const itemMap = /* @__PURE__ */ new Map();
716
- dataWithLevels.forEach((item) => {
717
- const key = getFieldValue(item, toggleField);
718
- if (typeof key !== "string" || !key) return;
719
- if (!itemMap.has(key)) {
720
- itemMap.set(key, []);
1017
+ const findNearestPrecedingParent = (index, parentKey) => {
1018
+ for (let i = index - 1; i >= 0; i -= 1) {
1019
+ const candidate = dataWithLevels[i];
1020
+ if (!candidate) continue;
1021
+ if (getFieldValue(candidate, toggleField) === parentKey) {
1022
+ return candidate;
1023
+ }
721
1024
  }
722
- itemMap.get(key)?.push(item);
723
- });
1025
+ return void 0;
1026
+ };
724
1027
  const rootItems = [];
725
1028
  dataWithLevels.forEach((item) => {
726
1029
  if (!getFieldValue(item, childField)) {
@@ -728,29 +1031,18 @@ var useConvertTreeData = ({
728
1031
  item.processed = true;
729
1032
  }
730
1033
  });
731
- dataWithLevels.forEach((item) => {
1034
+ dataWithLevels.forEach((item, index) => {
732
1035
  const parentKey = getFieldValue(item, childField);
733
1036
  if (!parentKey || item.processed) return;
734
- const parentItems = dataWithLevels.filter(
735
- (parent) => getFieldValue(parent, toggleField) === parentKey && !getFieldValue(parent, childField)
736
- );
737
- if (parentItems.length > 0) {
738
- const parent = parentItems[0];
1037
+ const parent = findNearestPrecedingParent(index, parentKey);
1038
+ if (parent) {
739
1039
  item.level = parent.level + 1;
740
1040
  parent.children.push(item);
741
1041
  item.processed = true;
742
- } else {
743
- const otherParents = itemMap.get(String(parentKey)) || [];
744
- if (otherParents.length > 0) {
745
- const parent = otherParents[0];
746
- item.level = parent.level + 1;
747
- parent.children.push(item);
748
- item.processed = true;
749
- } else {
750
- rootItems.push(item);
751
- item.processed = true;
752
- }
1042
+ return;
753
1043
  }
1044
+ rootItems.push(item);
1045
+ item.processed = true;
754
1046
  });
755
1047
  return rootItems;
756
1048
  }, [enabled, data, toggleField, childField, flattenField]);
@@ -775,16 +1067,23 @@ var useConvertTreeData = ({
775
1067
  return result;
776
1068
  };
777
1069
  const flattenedData = flatten(processedData, [], 0);
778
- flattenedData.forEach((item) => {
779
- if (getFieldValue(item, childField)) {
780
- const parentItem = flattenedData.find(
781
- (parent) => getFieldValue(parent, toggleField) === getFieldValue(item, childField)
782
- );
783
- const parentAmount = parentItem ? Number(getFieldValue(parentItem, qtyField) ?? 1) : 1;
784
- item.parentCount = parentAmount || 1;
785
- } else {
1070
+ flattenedData.forEach((item, index) => {
1071
+ const parentKey = getFieldValue(item, childField);
1072
+ if (!parentKey) {
786
1073
  item.parentCount = 1;
1074
+ return;
787
1075
  }
1076
+ let parentItem;
1077
+ for (let i = index - 1; i >= 0; i -= 1) {
1078
+ const candidate = flattenedData[i];
1079
+ if (!candidate) continue;
1080
+ if (getFieldValue(candidate, toggleField) === parentKey) {
1081
+ parentItem = candidate;
1082
+ break;
1083
+ }
1084
+ }
1085
+ const parentAmount = parentItem ? Number(getFieldValue(parentItem, qtyField) ?? 1) : 1;
1086
+ item.parentCount = parentAmount || 1;
788
1087
  });
789
1088
  return flattenedData;
790
1089
  }, [
@@ -931,6 +1230,10 @@ function useGlideTable(options) {
931
1230
  expandedRows: controlledExpandedRows,
932
1231
  onExpandedRowsChange,
933
1232
  preventExpand = false,
1233
+ enableSubtreeCopy,
1234
+ onCopyActionsReady,
1235
+ onRowsPaste,
1236
+ enableInsertPaste,
934
1237
  enableVirtualization = true,
935
1238
  estimateRowHeight = DATA_TABLE_ROW_HEIGHT,
936
1239
  virtualOverscan = DATA_TABLE_VIRTUAL_OVERSCAN
@@ -945,13 +1248,13 @@ function useGlideTable(options) {
945
1248
  };
946
1249
  }, [labelsProp, emptyText, loadingText, selectionLabel]);
947
1250
  const enableExpand = Boolean(toggleField);
1251
+ const resolvedEnableSubtreeCopy = enableSubtreeCopy ?? enableExpand;
948
1252
  const [internalRowSelection, setInternalRowSelection] = useState3({});
949
1253
  const [internalExpandedRows, setInternalExpandedRows] = useState3(
950
1254
  () => /* @__PURE__ */ new Set()
951
1255
  );
952
1256
  const [hoveredRowIndex, setHoveredRowIndex] = useState3(null);
953
- const [hoveredGroupKey, setHoveredGroupKey] = useState3(null);
954
- const scrollRef = useRef3(null);
1257
+ const scrollRef = useRef4(null);
955
1258
  const shouldVirtualize = enableVirtualization && !enableRowSpan;
956
1259
  useEffect4(() => {
957
1260
  if (enableVirtualization && enableRowSpan) {
@@ -1014,6 +1317,7 @@ function useGlideTable(options) {
1014
1317
  return collectRowSpanColumns(columns);
1015
1318
  }, [enableRowSpan, columns]);
1016
1319
  const primaryRowSpanKey = rowSpanColumnKeys[0]?.rowSpanKey;
1320
+ const primaryRowSpanColumnId = rowSpanColumnKeys[0]?.columnId;
1017
1321
  const columnRowSpanMap = useMemo2(
1018
1322
  () => buildColumnRowSpanMap(tableData, rowSpanColumnKeys),
1019
1323
  [tableData, rowSpanColumnKeys]
@@ -1032,27 +1336,29 @@ function useGlideTable(options) {
1032
1336
  const totalSize = rowVirtualizer.getTotalSize();
1033
1337
  const paddingTop = virtualRows.length > 0 ? virtualRows[0]?.start ?? 0 : 0;
1034
1338
  const paddingBottom = virtualRows.length > 0 ? totalSize - (virtualRows[virtualRows.length - 1]?.end ?? 0) : 0;
1035
- const selectedGroupKeys = useMemo2(() => {
1036
- if (!enableRowSpan || !primaryRowSpanKey) return /* @__PURE__ */ new Set();
1037
- const keys = /* @__PURE__ */ new Set();
1339
+ const selectedRowIndices = useMemo2(() => {
1340
+ const indices = /* @__PURE__ */ new Set();
1038
1341
  for (const selectedRow of selectedRows) {
1039
- const value = selectedRow.original[primaryRowSpanKey];
1040
- if (value !== null && value !== void 0) keys.add(String(value));
1342
+ indices.add(selectedRow.index);
1041
1343
  }
1042
- return keys;
1043
- }, [enableRowSpan, primaryRowSpanKey, selectedRows]);
1344
+ return indices;
1345
+ }, [selectedRows]);
1044
1346
  const {
1045
1347
  dragState,
1046
1348
  activeSelectionBounds,
1047
1349
  handleCellMouseDown,
1048
1350
  handleCellMouseEnter,
1049
- handleFillHandleMouseDown
1351
+ handleFillHandleMouseDown,
1352
+ copySelection
1050
1353
  } = useCellSelection({
1051
1354
  data: tableData,
1052
1355
  rows,
1053
1356
  enabled: enableCellSelection,
1357
+ enableSubtreeCopy: resolvedEnableSubtreeCopy,
1358
+ enableInsertPaste: enableInsertPaste ?? true,
1054
1359
  onDataChange,
1055
- onBatchChange
1360
+ onBatchChange,
1361
+ onRowsPaste
1056
1362
  });
1057
1363
  const {
1058
1364
  editingCell,
@@ -1074,22 +1380,10 @@ function useGlideTable(options) {
1074
1380
  );
1075
1381
  const clearHover = useCallback3(() => {
1076
1382
  setHoveredRowIndex(null);
1077
- setHoveredGroupKey(null);
1078
1383
  }, []);
1079
- const handleRowHover = useCallback3(
1080
- (rowIndex, rowData) => {
1081
- setHoveredRowIndex(rowIndex);
1082
- if (!primaryRowSpanKey) {
1083
- setHoveredGroupKey(null);
1084
- return;
1085
- }
1086
- const groupValue = rowData[primaryRowSpanKey];
1087
- setHoveredGroupKey(
1088
- groupValue === null || groupValue === void 0 ? null : String(groupValue)
1089
- );
1090
- },
1091
- [primaryRowSpanKey]
1092
- );
1384
+ const handleRowHover = useCallback3((rowIndex, _rowData) => {
1385
+ setHoveredRowIndex(rowIndex);
1386
+ }, []);
1093
1387
  const handleToggleSelect = useCallback3(
1094
1388
  (row) => {
1095
1389
  if (!row.getCanSelect()) return;
@@ -1112,10 +1406,10 @@ function useGlideTable(options) {
1112
1406
  rowSpan: {
1113
1407
  enableRowSpan,
1114
1408
  primaryRowSpanKey,
1409
+ primaryRowSpanColumnId,
1115
1410
  columnRowSpanMap,
1116
1411
  hoveredRowIndex,
1117
- hoveredGroupKey,
1118
- selectedGroupKeys,
1412
+ selectedRowIndices,
1119
1413
  onRowHover: handleRowHover
1120
1414
  },
1121
1415
  selection: {
@@ -1153,10 +1447,10 @@ function useGlideTable(options) {
1153
1447
  }, [
1154
1448
  enableRowSpan,
1155
1449
  primaryRowSpanKey,
1450
+ primaryRowSpanColumnId,
1156
1451
  columnRowSpanMap,
1157
1452
  hoveredRowIndex,
1158
- hoveredGroupKey,
1159
- selectedGroupKeys,
1453
+ selectedRowIndices,
1160
1454
  handleRowHover,
1161
1455
  rowSelectionMode,
1162
1456
  selectOnRowClick,
@@ -1182,6 +1476,14 @@ function useGlideTable(options) {
1182
1476
  labels.expandRow,
1183
1477
  labels.collapseRow
1184
1478
  ]);
1479
+ const copySelectionRef = useRef4(copySelection);
1480
+ useEffect4(() => {
1481
+ copySelectionRef.current = copySelection;
1482
+ }, [copySelection]);
1483
+ const stableCopySelection = useCallback3((options2) => copySelectionRef.current(options2), []);
1484
+ useEffect4(() => {
1485
+ onCopyActionsReady?.({ copySelection: stableCopySelection });
1486
+ }, [onCopyActionsReady, stableCopySelection]);
1185
1487
  return {
1186
1488
  table,
1187
1489
  tableData,
@@ -1201,7 +1503,8 @@ function useGlideTable(options) {
1201
1503
  paddingBottom,
1202
1504
  rowContextValue,
1203
1505
  handleToggleSelect,
1204
- clearHover
1506
+ clearHover,
1507
+ copySelection: stableCopySelection
1205
1508
  };
1206
1509
  }
1207
1510
 
@@ -1211,7 +1514,7 @@ import { useMemo as useMemo3 } from "react";
1211
1514
 
1212
1515
  // src/components/ui/table/components/DataTable/DataTableRow.tsx
1213
1516
  import { flexRender } from "@tanstack/react-table";
1214
- import { useEffect as useEffect5, useRef as useRef4 } from "react";
1517
+ import { useEffect as useEffect5, useRef as useRef5 } from "react";
1215
1518
 
1216
1519
  // src/components/ui/table/DataTableContext.tsx
1217
1520
  import { createContext, use } from "react";
@@ -1401,11 +1704,10 @@ function DataTableRow({
1401
1704
  const { classNames, rowSpan, selection, cellSelection, cellEdit, expand } = useDataTableRowContext();
1402
1705
  const {
1403
1706
  enableRowSpan,
1404
- primaryRowSpanKey,
1707
+ primaryRowSpanColumnId,
1405
1708
  columnRowSpanMap,
1406
1709
  hoveredRowIndex,
1407
- hoveredGroupKey,
1408
- selectedGroupKeys,
1710
+ selectedRowIndices,
1409
1711
  onRowHover
1410
1712
  } = rowSpan;
1411
1713
  const { rowSelectionMode, selectOnRowClick, onRowClick, getRowClassName } = selection;
@@ -1438,9 +1740,11 @@ function DataTableRow({
1438
1740
  const rowData = row.original;
1439
1741
  const isRowHovered = hoveredRowIndex === rowIndex;
1440
1742
  const isRowSelected = row.getIsSelected();
1441
- const rowGroupKey = primaryRowSpanKey !== void 0 && rowData[primaryRowSpanKey] !== null && rowData[primaryRowSpanKey] !== void 0 ? String(rowData[primaryRowSpanKey]) : null;
1442
- const isGroupHovered = enableRowSpan && hoveredGroupKey !== null && rowGroupKey === hoveredGroupKey;
1443
- const isGroupSelected = enableRowSpan && rowGroupKey !== null && selectedGroupKeys.has(rowGroupKey);
1743
+ const { startRow: primaryGroupStart, rowSpan: primaryGroupSpan } = resolveRowSpanAt(
1744
+ primaryRowSpanColumnId ? columnRowSpanMap.get(primaryRowSpanColumnId) : void 0,
1745
+ rowIndex
1746
+ );
1747
+ const isGroupHovered = enableRowSpan && hoveredRowIndex !== null && hoveredRowIndex >= primaryGroupStart && hoveredRowIndex <= primaryGroupStart + primaryGroupSpan - 1;
1444
1748
  const visibleCells = row.getVisibleCells();
1445
1749
  const columnIdsByIndex = visibleCells.map((cell) => cell.column.id);
1446
1750
  const isVisuallySelectedAt = activeSelectionBounds ? (targetRow, targetCol) => {
@@ -1464,7 +1768,7 @@ function DataTableRow({
1464
1768
  const expandKey = toggleField && rowData[toggleField] !== null && rowData[toggleField] !== void 0 ? String(rowData[toggleField]) : null;
1465
1769
  const isExpanded = expandKey !== null && Boolean(expandedRows?.has(expandKey));
1466
1770
  const rowLevel = typeof rowData.level === "number" ? rowData.level : 0;
1467
- const editInputRef = useRef4(null);
1771
+ const editInputRef = useRef5(null);
1468
1772
  const isRowEditing = editingCell?.rowIndex === rowIndex;
1469
1773
  useEffect5(() => {
1470
1774
  if (!isRowEditing) return;
@@ -1517,7 +1821,16 @@ function DataTableRow({
1517
1821
  const cellRowSpan = rowSpanInfo?.rowSpan ?? 1;
1518
1822
  const isMergedCellHovered = hoveredRowIndex !== null && hoveredRowIndex >= rowIndex && hoveredRowIndex <= rowIndex + cellRowSpan - 1;
1519
1823
  const showCellHover = isRowSpanColumn ? isMergedCellHovered : isRowHovered;
1520
- const showCellSelected = isRowSpanColumn ? isGroupSelected : isRowSelected;
1824
+ let isMergedCellSelected = false;
1825
+ if (isRowSpanColumn) {
1826
+ for (let r = rowIndex; r < rowIndex + cellRowSpan; r += 1) {
1827
+ if (selectedRowIndices.has(r)) {
1828
+ isMergedCellSelected = true;
1829
+ break;
1830
+ }
1831
+ }
1832
+ }
1833
+ const showCellSelected = isRowSpanColumn ? isMergedCellSelected : isRowSelected;
1521
1834
  const isMerged = cellRowSpan > 1;
1522
1835
  const showMergedRightEdge = isMerged && (cellIndex === visibleCells.length - 1 || resolveRowSpanAt(
1523
1836
  columnRowSpanMap.get(columnIdsByIndex[cellIndex + 1]),
@@ -1877,7 +2190,12 @@ function DataTable({
1877
2190
  return /* @__PURE__ */ jsx5(
1878
2191
  "th",
1879
2192
  {
1880
- style: { width: header.getSize() !== 150 ? header.getSize() : void 0 },
2193
+ style: {
2194
+ width: header.getSize() !== 150 ? header.getSize() : void 0,
2195
+ // Column sizing follows TanStack's `size`, but
2196
+ // ensure the column keeps its min width when the container shrinks.
2197
+ minWidth: header.getSize() !== 150 ? header.getSize() : void 0
2198
+ },
1881
2199
  className: cn(
1882
2200
  "data-table-head-cell",
1883
2201
  CELL_ALIGN_CLASS[align],
@@ -2319,10 +2637,14 @@ export {
2319
2637
  applyFillData,
2320
2638
  applySelectionUpdater,
2321
2639
  buildColumnRowSpanMap,
2640
+ buildRowsPastePayload,
2322
2641
  canExpandRow,
2642
+ collectCopyRowEntries,
2643
+ collectCopyRows,
2323
2644
  collectFillChanges,
2324
2645
  collectRowSpanColumns,
2325
2646
  createTable,
2647
+ flattenSubtreeRows,
2326
2648
  getCellEditDraftValue,
2327
2649
  getCellSelectionEdgeStyle,
2328
2650
  getColumnEditType,
@@ -2330,15 +2652,22 @@ export {
2330
2652
  hasCellSelectionEdges,
2331
2653
  isCellInSelection,
2332
2654
  isColumnEditable,
2655
+ isEditablePasteTarget,
2333
2656
  measureMergedSpanRowHeights,
2334
2657
  parseCellEditValue,
2658
+ parseClipboardTSV,
2659
+ parseClipboardTSVWithDepths,
2335
2660
  resolveDataTableLabels,
2661
+ resolvePasteColumnIds,
2336
2662
  resolveRowSelection,
2337
2663
  resolveRowSpanAt,
2338
2664
  rowRangeToHeightRatios,
2665
+ serializeCopyRowsToTSV,
2666
+ serializeSelectionToTSV,
2339
2667
  toggleExpandedRowId,
2340
2668
  useCellEdit,
2341
2669
  useCellSelection,
2342
2670
  useConvertTreeData,
2343
- useGlideTable
2671
+ useGlideTable,
2672
+ writeSelectionToClipboard
2344
2673
  };