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/core.cjs CHANGED
@@ -30,9 +30,13 @@ __export(core_exports, {
30
30
  applyFillData: () => applyFillData,
31
31
  applySelectionUpdater: () => applySelectionUpdater,
32
32
  buildColumnRowSpanMap: () => buildColumnRowSpanMap,
33
+ buildRowsPastePayload: () => buildRowsPastePayload,
33
34
  canExpandRow: () => canExpandRow,
35
+ collectCopyRowEntries: () => collectCopyRowEntries,
36
+ collectCopyRows: () => collectCopyRows,
34
37
  collectFillChanges: () => collectFillChanges,
35
38
  collectRowSpanColumns: () => collectRowSpanColumns,
39
+ flattenSubtreeRows: () => flattenSubtreeRows,
36
40
  getCellEditDraftValue: () => getCellEditDraftValue,
37
41
  getCellSelectionEdgeStyle: () => getCellSelectionEdgeStyle,
38
42
  getColumnEditType: () => getColumnEditType,
@@ -40,17 +44,24 @@ __export(core_exports, {
40
44
  hasCellSelectionEdges: () => hasCellSelectionEdges,
41
45
  isCellInSelection: () => isCellInSelection,
42
46
  isColumnEditable: () => isColumnEditable,
47
+ isEditablePasteTarget: () => isEditablePasteTarget,
43
48
  measureMergedSpanRowHeights: () => measureMergedSpanRowHeights,
44
49
  parseCellEditValue: () => parseCellEditValue,
50
+ parseClipboardTSV: () => parseClipboardTSV,
51
+ parseClipboardTSVWithDepths: () => parseClipboardTSVWithDepths,
45
52
  resolveDataTableLabels: () => resolveDataTableLabels,
53
+ resolvePasteColumnIds: () => resolvePasteColumnIds,
46
54
  resolveRowSelection: () => resolveRowSelection,
47
55
  resolveRowSpanAt: () => resolveRowSpanAt,
48
56
  rowRangeToHeightRatios: () => rowRangeToHeightRatios,
57
+ serializeCopyRowsToTSV: () => serializeCopyRowsToTSV,
58
+ serializeSelectionToTSV: () => serializeSelectionToTSV,
49
59
  toggleExpandedRowId: () => toggleExpandedRowId,
50
60
  useCellEdit: () => useCellEdit,
51
61
  useCellSelection: () => useCellSelection,
52
62
  useConvertTreeData: () => useConvertTreeData,
53
- useGlideTable: () => useGlideTable
63
+ useGlideTable: () => useGlideTable,
64
+ writeSelectionToClipboard: () => writeSelectionToClipboard
54
65
  });
55
66
  module.exports = __toCommonJS(core_exports);
56
67
 
@@ -489,6 +500,127 @@ function hasCellSelectionEdges(style) {
489
500
  );
490
501
  }
491
502
 
503
+ // src/components/ui/table/features/cell-selection/copyData.ts
504
+ function formatCellValue(value) {
505
+ if (value === null || value === void 0) return "";
506
+ return String(value);
507
+ }
508
+ function getNestedValue(row, path) {
509
+ if (!path.includes(".")) return row[path];
510
+ return path.split(".").reduce((current, key) => {
511
+ if (current === null || current === void 0 || typeof current !== "object") {
512
+ return void 0;
513
+ }
514
+ return current[key];
515
+ }, row);
516
+ }
517
+ function readRowColumnValue(rowData, columnDef) {
518
+ if ("accessorFn" in columnDef && typeof columnDef.accessorFn === "function") {
519
+ return columnDef.accessorFn(rowData, 0);
520
+ }
521
+ if ("accessorKey" in columnDef && columnDef.accessorKey != null && columnDef.accessorKey !== "") {
522
+ return getNestedValue(rowData, String(columnDef.accessorKey));
523
+ }
524
+ return void 0;
525
+ }
526
+ function flattenSubtreeRows(row) {
527
+ const children = row.children;
528
+ if (!Array.isArray(children) || children.length === 0) return [];
529
+ const result = [];
530
+ const walk = (nodes) => {
531
+ for (const node of nodes) {
532
+ result.push(node);
533
+ const nested = node.children;
534
+ if (Array.isArray(nested) && nested.length > 0) {
535
+ walk(nested);
536
+ }
537
+ }
538
+ };
539
+ walk(children);
540
+ return result;
541
+ }
542
+ function hasSubtree(row) {
543
+ const children = row.children;
544
+ return Array.isArray(children) && children.length > 0;
545
+ }
546
+ function getOriginalRowId(original) {
547
+ return String(original.id ?? original.uniqueId ?? "");
548
+ }
549
+ function getRowDepth(original) {
550
+ return typeof original.level === "number" ? original.level : 0;
551
+ }
552
+ function collectCopyRowEntries(visibleRows, bounds, mode = "visible") {
553
+ const { startRow, endRow } = bounds;
554
+ const result = [];
555
+ const includedOriginalIds = /* @__PURE__ */ new Set();
556
+ const appendSubtree = (node, depth) => {
557
+ const children = node.children;
558
+ if (!Array.isArray(children) || children.length === 0) return;
559
+ for (const child of children) {
560
+ const childId = getOriginalRowId(child);
561
+ if (!(childId && includedOriginalIds.has(childId))) {
562
+ result.push({ row: child, depth });
563
+ if (childId) includedOriginalIds.add(childId);
564
+ }
565
+ appendSubtree(child, depth + 1);
566
+ }
567
+ };
568
+ for (let rowIndex = startRow; rowIndex <= endRow; rowIndex += 1) {
569
+ const row = visibleRows[rowIndex];
570
+ if (!row) continue;
571
+ const originalId = getOriginalRowId(row.original);
572
+ if (originalId && includedOriginalIds.has(originalId)) continue;
573
+ const depth = getRowDepth(row.original);
574
+ result.push({ row: row.original, depth });
575
+ if (originalId) includedOriginalIds.add(originalId);
576
+ if (mode !== "subtree" || !hasSubtree(row.original)) continue;
577
+ appendSubtree(row.original, depth + 1);
578
+ }
579
+ return result;
580
+ }
581
+ function collectCopyRows(visibleRows, bounds, mode = "visible") {
582
+ return collectCopyRowEntries(visibleRows, bounds, mode).map((entry) => entry.row);
583
+ }
584
+ function serializeCopyRowsToTSV(copyRows, visibleRows, bounds, depths) {
585
+ if (copyRows.length === 0) return "";
586
+ const { startCol, endCol } = bounds;
587
+ const columnCells = visibleRows[0]?.getVisibleCells().slice(startCol, endCol + 1) ?? [];
588
+ if (columnCells.length === 0) return "";
589
+ const resolvedDepths = depths && depths.length === copyRows.length ? depths : copyRows.map((row) => getRowDepth(row));
590
+ const minDepth = Math.min(...resolvedDepths);
591
+ return copyRows.map((rowData, index) => {
592
+ const relativeDepth = Math.max(0, (resolvedDepths[index] ?? 0) - minDepth);
593
+ const line = columnCells.map(
594
+ (cell) => formatCellValue(
595
+ readRowColumnValue(
596
+ rowData,
597
+ cell.column.columnDef
598
+ )
599
+ )
600
+ ).join(" ");
601
+ return `${" ".repeat(relativeDepth)}${line}`;
602
+ }).join("\n");
603
+ }
604
+ function serializeSelectionToTSV(visibleRows, bounds, mode = "visible") {
605
+ const entries = collectCopyRowEntries(visibleRows, bounds, mode);
606
+ return serializeCopyRowsToTSV(
607
+ entries.map((entry) => entry.row),
608
+ visibleRows,
609
+ bounds,
610
+ entries.map((entry) => entry.depth)
611
+ );
612
+ }
613
+ async function writeSelectionToClipboard(visibleRows, bounds, mode = "visible") {
614
+ const text = serializeSelectionToTSV(visibleRows, bounds, mode);
615
+ if (!text) return false;
616
+ try {
617
+ await navigator.clipboard.writeText(text);
618
+ } catch {
619
+ return false;
620
+ }
621
+ return true;
622
+ }
623
+
492
624
  // src/components/ui/table/features/cell-selection/fillData.ts
493
625
  function getColumnAccessorKey2(columnDef) {
494
626
  if ("accessorKey" in columnDef && columnDef.accessorKey) {
@@ -545,15 +677,103 @@ function hasFillExtension(sourceBounds, fillBounds) {
545
677
  return fillBounds.startRow < sourceBounds.startRow || fillBounds.endRow > sourceBounds.endRow || fillBounds.startCol < sourceBounds.startCol || fillBounds.endCol > sourceBounds.endCol;
546
678
  }
547
679
 
680
+ // src/components/ui/table/features/cell-selection/pasteData.ts
681
+ function countLeadingEmptyCells(cells) {
682
+ let depth = 0;
683
+ while (depth < cells.length && cells[depth] === "") {
684
+ depth += 1;
685
+ }
686
+ return depth;
687
+ }
688
+ function looksLikeSubtreeIndentation(leadingEmptyCounts) {
689
+ if (leadingEmptyCounts.length === 0) return false;
690
+ const firstDepth = leadingEmptyCounts[0] ?? 0;
691
+ if (firstDepth !== 0) return false;
692
+ return leadingEmptyCounts.some((depth) => depth > 0);
693
+ }
694
+ function parseClipboardTSV(text) {
695
+ return parseClipboardTSVWithDepths(text).values;
696
+ }
697
+ function parseClipboardTSVWithDepths(text) {
698
+ if (!text) return { values: [], depths: [] };
699
+ const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
700
+ const withoutTrailing = normalized.replace(/\n+$/, "");
701
+ if (!withoutTrailing) return { values: [], depths: [] };
702
+ const rows = withoutTrailing.split("\n").map((line) => line.split(" "));
703
+ const leadingEmptyCounts = rows.map(countLeadingEmptyCells);
704
+ const treatAsDepth = looksLikeSubtreeIndentation(leadingEmptyCounts);
705
+ const values = [];
706
+ const depths = [];
707
+ for (let index = 0; index < rows.length; index += 1) {
708
+ const cells = rows[index] ?? [];
709
+ const depth = leadingEmptyCounts[index] ?? 0;
710
+ if (treatAsDepth) {
711
+ values.push(cells.slice(depth));
712
+ depths.push(depth);
713
+ } else {
714
+ values.push(cells);
715
+ depths.push(0);
716
+ }
717
+ }
718
+ return { values, depths };
719
+ }
720
+ function resolvePasteColumnIds(rows, startCol, width) {
721
+ if (width <= 0) return [];
722
+ const cells = rows[0]?.getVisibleCells() ?? [];
723
+ const columnIds = [];
724
+ for (let offset = 0; offset < width; offset += 1) {
725
+ const cell = cells[startCol + offset];
726
+ if (!cell) break;
727
+ columnIds.push(cell.column.id);
728
+ }
729
+ return columnIds;
730
+ }
731
+ function buildRowsPastePayload(rows, startRow, startCol, text, mode, endRow = startRow) {
732
+ const { values, depths } = parseClipboardTSVWithDepths(text);
733
+ if (values.length === 0) return null;
734
+ const width = Math.max(...values.map((row) => row.length), 0);
735
+ if (width === 0) return null;
736
+ const columnIds = resolvePasteColumnIds(rows, startCol, width);
737
+ if (columnIds.length === 0) return null;
738
+ const rowIds = [];
739
+ for (let offset = 0; offset < values.length; offset += 1) {
740
+ const row = rows[startRow + offset];
741
+ if (!row) break;
742
+ rowIds.push(row.id);
743
+ }
744
+ const anchorRow = rows[endRow] ?? rows[startRow];
745
+ return {
746
+ mode,
747
+ startRow,
748
+ startCol,
749
+ endRow,
750
+ rowIds,
751
+ anchorRowId: anchorRow?.id ?? "",
752
+ columnIds,
753
+ values,
754
+ depths
755
+ };
756
+ }
757
+ function isEditablePasteTarget(target) {
758
+ if (!(target instanceof HTMLElement)) return false;
759
+ const tag = target.tagName;
760
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
761
+ return Boolean(target.isContentEditable);
762
+ }
763
+
548
764
  // src/components/ui/table/features/cell-selection/useCellSelection.ts
549
765
  function useCellSelection({
550
766
  data,
551
767
  rows,
552
768
  enabled = true,
769
+ enableSubtreeCopy = false,
770
+ enableInsertPaste = true,
553
771
  onDataChange,
554
- onBatchChange
772
+ onBatchChange,
773
+ onRowsPaste
555
774
  }) {
556
775
  const [dragState, setDragState] = (0, import_react2.useState)(INITIAL_DRAG_STATE);
776
+ const pendingPasteModeRef = (0, import_react2.useRef)(null);
557
777
  const cellSelectionBounds = getCellSelectionBounds(dragState.start, dragState.end);
558
778
  const activeSelectionBounds = enabled ? getActiveSelectionBounds(dragState, cellSelectionBounds) : null;
559
779
  const handleCellMouseDown = (0, import_react2.useCallback)(
@@ -607,21 +827,113 @@ function useCellSelection({
607
827
  setDragState(INITIAL_DRAG_STATE);
608
828
  }
609
829
  }, [enabled]);
830
+ const copySelection = (0, import_react2.useCallback)(
831
+ async (options) => {
832
+ if (!enabled || !activeSelectionBounds) return false;
833
+ const mode = options?.includeDescendants && enableSubtreeCopy ? "subtree" : "visible";
834
+ return writeSelectionToClipboard(rows, activeSelectionBounds, mode);
835
+ },
836
+ [activeSelectionBounds, enableSubtreeCopy, enabled, rows]
837
+ );
610
838
  (0, import_react2.useEffect)(() => {
611
839
  if (!enabled) return;
612
840
  const handleKeyDown = (e) => {
613
- if ((e.ctrlKey || e.metaKey) && e.key === "c" && activeSelectionBounds) {
614
- const { startRow, endRow, startCol, endCol } = activeSelectionBounds;
615
- const selectedData = rows.slice(startRow, endRow + 1).map((row) => {
616
- const cells = row.getVisibleCells();
617
- return cells.slice(startCol, endCol + 1).map((cell) => cell.getValue()).join(" ");
618
- }).join("\n");
619
- navigator.clipboard.writeText(selectedData);
620
- }
841
+ if (!activeSelectionBounds) return;
842
+ if (!(e.ctrlKey || e.metaKey)) return;
843
+ const isSubtreeShortcut = enableSubtreeCopy && e.shiftKey && e.key.toLowerCase() === "c";
844
+ const isVisibleCopyShortcut = !e.shiftKey && e.key.toLowerCase() === "c";
845
+ if (!isSubtreeShortcut && !isVisibleCopyShortcut) return;
846
+ e.preventDefault();
847
+ void copySelection({ includeDescendants: isSubtreeShortcut });
621
848
  };
622
849
  window.addEventListener("keydown", handleKeyDown);
623
850
  return () => window.removeEventListener("keydown", handleKeyDown);
624
- }, [activeSelectionBounds, enabled, rows]);
851
+ }, [activeSelectionBounds, copySelection, enableSubtreeCopy, enabled]);
852
+ const emitRowsPaste = (0, import_react2.useCallback)(
853
+ (text, mode) => {
854
+ if (!onRowsPaste || !activeSelectionBounds) return false;
855
+ const payload = buildRowsPastePayload(
856
+ rows,
857
+ activeSelectionBounds.startRow,
858
+ activeSelectionBounds.startCol,
859
+ text,
860
+ mode,
861
+ activeSelectionBounds.endRow
862
+ );
863
+ if (!payload) return false;
864
+ onRowsPaste(payload);
865
+ return true;
866
+ },
867
+ [activeSelectionBounds, onRowsPaste, rows]
868
+ );
869
+ (0, import_react2.useEffect)(() => {
870
+ if (!enabled || !onRowsPaste) return;
871
+ const pasteHandledRef = { current: false };
872
+ const ignoreNextPasteRef = { current: false };
873
+ const handleKeyDown = (e) => {
874
+ if (!activeSelectionBounds) return;
875
+ if (!(e.ctrlKey || e.metaKey)) return;
876
+ if (e.key.toLowerCase() !== "v") return;
877
+ if (isEditablePasteTarget(e.target) || isEditablePasteTarget(document.activeElement)) {
878
+ return;
879
+ }
880
+ if (e.shiftKey && !enableInsertPaste) {
881
+ ignoreNextPasteRef.current = true;
882
+ pendingPasteModeRef.current = null;
883
+ return;
884
+ }
885
+ const mode = e.shiftKey ? "insert" : "overwrite";
886
+ pasteHandledRef.current = false;
887
+ ignoreNextPasteRef.current = false;
888
+ pendingPasteModeRef.current = mode;
889
+ void (async () => {
890
+ try {
891
+ const text = await navigator.clipboard.readText();
892
+ if (pasteHandledRef.current) return;
893
+ if (pendingPasteModeRef.current !== mode) return;
894
+ if (!text) return;
895
+ pasteHandledRef.current = true;
896
+ emitRowsPaste(text, mode);
897
+ pendingPasteModeRef.current = null;
898
+ } catch {
899
+ }
900
+ })();
901
+ };
902
+ const handlePaste = (e) => {
903
+ if (!activeSelectionBounds) return;
904
+ if (isEditablePasteTarget(e.target) || isEditablePasteTarget(document.activeElement)) {
905
+ return;
906
+ }
907
+ if (ignoreNextPasteRef.current) {
908
+ ignoreNextPasteRef.current = false;
909
+ pendingPasteModeRef.current = null;
910
+ return;
911
+ }
912
+ const mode = pendingPasteModeRef.current ?? "overwrite";
913
+ if (pasteHandledRef.current) {
914
+ e.preventDefault();
915
+ return;
916
+ }
917
+ const text = e.clipboardData?.getData("text/plain");
918
+ if (text == null || text === "") return;
919
+ pasteHandledRef.current = true;
920
+ e.preventDefault();
921
+ emitRowsPaste(text, mode);
922
+ pendingPasteModeRef.current = null;
923
+ };
924
+ window.addEventListener("keydown", handleKeyDown);
925
+ window.addEventListener("paste", handlePaste);
926
+ return () => {
927
+ window.removeEventListener("keydown", handleKeyDown);
928
+ window.removeEventListener("paste", handlePaste);
929
+ };
930
+ }, [
931
+ activeSelectionBounds,
932
+ emitRowsPaste,
933
+ enableInsertPaste,
934
+ enabled,
935
+ onRowsPaste
936
+ ]);
625
937
  (0, import_react2.useEffect)(() => {
626
938
  if (!enabled) return;
627
939
  const handleMouseUp = () => {
@@ -667,7 +979,8 @@ function useCellSelection({
667
979
  activeSelectionBounds,
668
980
  handleCellMouseDown,
669
981
  handleCellMouseEnter,
670
- handleFillHandleMouseDown
982
+ handleFillHandleMouseDown,
983
+ copySelection
671
984
  };
672
985
  }
673
986
 
@@ -749,15 +1062,16 @@ var useConvertTreeData = ({
749
1062
  children: [],
750
1063
  processed: false
751
1064
  }));
752
- const itemMap = /* @__PURE__ */ new Map();
753
- dataWithLevels.forEach((item) => {
754
- const key = getFieldValue(item, toggleField);
755
- if (typeof key !== "string" || !key) return;
756
- if (!itemMap.has(key)) {
757
- itemMap.set(key, []);
1065
+ const findNearestPrecedingParent = (index, parentKey) => {
1066
+ for (let i = index - 1; i >= 0; i -= 1) {
1067
+ const candidate = dataWithLevels[i];
1068
+ if (!candidate) continue;
1069
+ if (getFieldValue(candidate, toggleField) === parentKey) {
1070
+ return candidate;
1071
+ }
758
1072
  }
759
- itemMap.get(key)?.push(item);
760
- });
1073
+ return void 0;
1074
+ };
761
1075
  const rootItems = [];
762
1076
  dataWithLevels.forEach((item) => {
763
1077
  if (!getFieldValue(item, childField)) {
@@ -765,29 +1079,18 @@ var useConvertTreeData = ({
765
1079
  item.processed = true;
766
1080
  }
767
1081
  });
768
- dataWithLevels.forEach((item) => {
1082
+ dataWithLevels.forEach((item, index) => {
769
1083
  const parentKey = getFieldValue(item, childField);
770
1084
  if (!parentKey || item.processed) return;
771
- const parentItems = dataWithLevels.filter(
772
- (parent) => getFieldValue(parent, toggleField) === parentKey && !getFieldValue(parent, childField)
773
- );
774
- if (parentItems.length > 0) {
775
- const parent = parentItems[0];
1085
+ const parent = findNearestPrecedingParent(index, parentKey);
1086
+ if (parent) {
776
1087
  item.level = parent.level + 1;
777
1088
  parent.children.push(item);
778
1089
  item.processed = true;
779
- } else {
780
- const otherParents = itemMap.get(String(parentKey)) || [];
781
- if (otherParents.length > 0) {
782
- const parent = otherParents[0];
783
- item.level = parent.level + 1;
784
- parent.children.push(item);
785
- item.processed = true;
786
- } else {
787
- rootItems.push(item);
788
- item.processed = true;
789
- }
1090
+ return;
790
1091
  }
1092
+ rootItems.push(item);
1093
+ item.processed = true;
791
1094
  });
792
1095
  return rootItems;
793
1096
  }, [enabled, data, toggleField, childField, flattenField]);
@@ -812,16 +1115,23 @@ var useConvertTreeData = ({
812
1115
  return result;
813
1116
  };
814
1117
  const flattenedData = flatten(processedData, [], 0);
815
- flattenedData.forEach((item) => {
816
- if (getFieldValue(item, childField)) {
817
- const parentItem = flattenedData.find(
818
- (parent) => getFieldValue(parent, toggleField) === getFieldValue(item, childField)
819
- );
820
- const parentAmount = parentItem ? Number(getFieldValue(parentItem, qtyField) ?? 1) : 1;
821
- item.parentCount = parentAmount || 1;
822
- } else {
1118
+ flattenedData.forEach((item, index) => {
1119
+ const parentKey = getFieldValue(item, childField);
1120
+ if (!parentKey) {
823
1121
  item.parentCount = 1;
1122
+ return;
1123
+ }
1124
+ let parentItem;
1125
+ for (let i = index - 1; i >= 0; i -= 1) {
1126
+ const candidate = flattenedData[i];
1127
+ if (!candidate) continue;
1128
+ if (getFieldValue(candidate, toggleField) === parentKey) {
1129
+ parentItem = candidate;
1130
+ break;
1131
+ }
824
1132
  }
1133
+ const parentAmount = parentItem ? Number(getFieldValue(parentItem, qtyField) ?? 1) : 1;
1134
+ item.parentCount = parentAmount || 1;
825
1135
  });
826
1136
  return flattenedData;
827
1137
  }, [
@@ -968,6 +1278,10 @@ function useGlideTable(options) {
968
1278
  expandedRows: controlledExpandedRows,
969
1279
  onExpandedRowsChange,
970
1280
  preventExpand = false,
1281
+ enableSubtreeCopy,
1282
+ onCopyActionsReady,
1283
+ onRowsPaste,
1284
+ enableInsertPaste,
971
1285
  enableVirtualization = true,
972
1286
  estimateRowHeight = DATA_TABLE_ROW_HEIGHT,
973
1287
  virtualOverscan = DATA_TABLE_VIRTUAL_OVERSCAN
@@ -982,12 +1296,12 @@ function useGlideTable(options) {
982
1296
  };
983
1297
  }, [labelsProp, emptyText, loadingText, selectionLabel]);
984
1298
  const enableExpand = Boolean(toggleField);
1299
+ const resolvedEnableSubtreeCopy = enableSubtreeCopy ?? enableExpand;
985
1300
  const [internalRowSelection, setInternalRowSelection] = (0, import_react4.useState)({});
986
1301
  const [internalExpandedRows, setInternalExpandedRows] = (0, import_react4.useState)(
987
1302
  () => /* @__PURE__ */ new Set()
988
1303
  );
989
1304
  const [hoveredRowIndex, setHoveredRowIndex] = (0, import_react4.useState)(null);
990
- const [hoveredGroupKey, setHoveredGroupKey] = (0, import_react4.useState)(null);
991
1305
  const scrollRef = (0, import_react4.useRef)(null);
992
1306
  const shouldVirtualize = enableVirtualization && !enableRowSpan;
993
1307
  (0, import_react4.useEffect)(() => {
@@ -1051,6 +1365,7 @@ function useGlideTable(options) {
1051
1365
  return collectRowSpanColumns(columns);
1052
1366
  }, [enableRowSpan, columns]);
1053
1367
  const primaryRowSpanKey = rowSpanColumnKeys[0]?.rowSpanKey;
1368
+ const primaryRowSpanColumnId = rowSpanColumnKeys[0]?.columnId;
1054
1369
  const columnRowSpanMap = (0, import_react4.useMemo)(
1055
1370
  () => buildColumnRowSpanMap(tableData, rowSpanColumnKeys),
1056
1371
  [tableData, rowSpanColumnKeys]
@@ -1069,27 +1384,29 @@ function useGlideTable(options) {
1069
1384
  const totalSize = rowVirtualizer.getTotalSize();
1070
1385
  const paddingTop = virtualRows.length > 0 ? virtualRows[0]?.start ?? 0 : 0;
1071
1386
  const paddingBottom = virtualRows.length > 0 ? totalSize - (virtualRows[virtualRows.length - 1]?.end ?? 0) : 0;
1072
- const selectedGroupKeys = (0, import_react4.useMemo)(() => {
1073
- if (!enableRowSpan || !primaryRowSpanKey) return /* @__PURE__ */ new Set();
1074
- const keys = /* @__PURE__ */ new Set();
1387
+ const selectedRowIndices = (0, import_react4.useMemo)(() => {
1388
+ const indices = /* @__PURE__ */ new Set();
1075
1389
  for (const selectedRow of selectedRows) {
1076
- const value = selectedRow.original[primaryRowSpanKey];
1077
- if (value !== null && value !== void 0) keys.add(String(value));
1390
+ indices.add(selectedRow.index);
1078
1391
  }
1079
- return keys;
1080
- }, [enableRowSpan, primaryRowSpanKey, selectedRows]);
1392
+ return indices;
1393
+ }, [selectedRows]);
1081
1394
  const {
1082
1395
  dragState,
1083
1396
  activeSelectionBounds,
1084
1397
  handleCellMouseDown,
1085
1398
  handleCellMouseEnter,
1086
- handleFillHandleMouseDown
1399
+ handleFillHandleMouseDown,
1400
+ copySelection
1087
1401
  } = useCellSelection({
1088
1402
  data: tableData,
1089
1403
  rows,
1090
1404
  enabled: enableCellSelection,
1405
+ enableSubtreeCopy: resolvedEnableSubtreeCopy,
1406
+ enableInsertPaste: enableInsertPaste ?? true,
1091
1407
  onDataChange,
1092
- onBatchChange
1408
+ onBatchChange,
1409
+ onRowsPaste
1093
1410
  });
1094
1411
  const {
1095
1412
  editingCell,
@@ -1111,22 +1428,10 @@ function useGlideTable(options) {
1111
1428
  );
1112
1429
  const clearHover = (0, import_react4.useCallback)(() => {
1113
1430
  setHoveredRowIndex(null);
1114
- setHoveredGroupKey(null);
1115
1431
  }, []);
1116
- const handleRowHover = (0, import_react4.useCallback)(
1117
- (rowIndex, rowData) => {
1118
- setHoveredRowIndex(rowIndex);
1119
- if (!primaryRowSpanKey) {
1120
- setHoveredGroupKey(null);
1121
- return;
1122
- }
1123
- const groupValue = rowData[primaryRowSpanKey];
1124
- setHoveredGroupKey(
1125
- groupValue === null || groupValue === void 0 ? null : String(groupValue)
1126
- );
1127
- },
1128
- [primaryRowSpanKey]
1129
- );
1432
+ const handleRowHover = (0, import_react4.useCallback)((rowIndex, _rowData) => {
1433
+ setHoveredRowIndex(rowIndex);
1434
+ }, []);
1130
1435
  const handleToggleSelect = (0, import_react4.useCallback)(
1131
1436
  (row) => {
1132
1437
  if (!row.getCanSelect()) return;
@@ -1149,10 +1454,10 @@ function useGlideTable(options) {
1149
1454
  rowSpan: {
1150
1455
  enableRowSpan,
1151
1456
  primaryRowSpanKey,
1457
+ primaryRowSpanColumnId,
1152
1458
  columnRowSpanMap,
1153
1459
  hoveredRowIndex,
1154
- hoveredGroupKey,
1155
- selectedGroupKeys,
1460
+ selectedRowIndices,
1156
1461
  onRowHover: handleRowHover
1157
1462
  },
1158
1463
  selection: {
@@ -1190,10 +1495,10 @@ function useGlideTable(options) {
1190
1495
  }, [
1191
1496
  enableRowSpan,
1192
1497
  primaryRowSpanKey,
1498
+ primaryRowSpanColumnId,
1193
1499
  columnRowSpanMap,
1194
1500
  hoveredRowIndex,
1195
- hoveredGroupKey,
1196
- selectedGroupKeys,
1501
+ selectedRowIndices,
1197
1502
  handleRowHover,
1198
1503
  rowSelectionMode,
1199
1504
  selectOnRowClick,
@@ -1219,6 +1524,14 @@ function useGlideTable(options) {
1219
1524
  labels.expandRow,
1220
1525
  labels.collapseRow
1221
1526
  ]);
1527
+ const copySelectionRef = (0, import_react4.useRef)(copySelection);
1528
+ (0, import_react4.useEffect)(() => {
1529
+ copySelectionRef.current = copySelection;
1530
+ }, [copySelection]);
1531
+ const stableCopySelection = (0, import_react4.useCallback)((options2) => copySelectionRef.current(options2), []);
1532
+ (0, import_react4.useEffect)(() => {
1533
+ onCopyActionsReady?.({ copySelection: stableCopySelection });
1534
+ }, [onCopyActionsReady, stableCopySelection]);
1222
1535
  return {
1223
1536
  table,
1224
1537
  tableData,
@@ -1238,7 +1551,8 @@ function useGlideTable(options) {
1238
1551
  paddingBottom,
1239
1552
  rowContextValue,
1240
1553
  handleToggleSelect,
1241
- clearHover
1554
+ clearHover,
1555
+ copySelection: stableCopySelection
1242
1556
  };
1243
1557
  }
1244
1558
  // Annotate the CommonJS export names for ESM import in node:
@@ -1253,9 +1567,13 @@ function useGlideTable(options) {
1253
1567
  applyFillData,
1254
1568
  applySelectionUpdater,
1255
1569
  buildColumnRowSpanMap,
1570
+ buildRowsPastePayload,
1256
1571
  canExpandRow,
1572
+ collectCopyRowEntries,
1573
+ collectCopyRows,
1257
1574
  collectFillChanges,
1258
1575
  collectRowSpanColumns,
1576
+ flattenSubtreeRows,
1259
1577
  getCellEditDraftValue,
1260
1578
  getCellSelectionEdgeStyle,
1261
1579
  getColumnEditType,
@@ -1263,15 +1581,22 @@ function useGlideTable(options) {
1263
1581
  hasCellSelectionEdges,
1264
1582
  isCellInSelection,
1265
1583
  isColumnEditable,
1584
+ isEditablePasteTarget,
1266
1585
  measureMergedSpanRowHeights,
1267
1586
  parseCellEditValue,
1587
+ parseClipboardTSV,
1588
+ parseClipboardTSVWithDepths,
1268
1589
  resolveDataTableLabels,
1590
+ resolvePasteColumnIds,
1269
1591
  resolveRowSelection,
1270
1592
  resolveRowSpanAt,
1271
1593
  rowRangeToHeightRatios,
1594
+ serializeCopyRowsToTSV,
1595
+ serializeSelectionToTSV,
1272
1596
  toggleExpandedRowId,
1273
1597
  useCellEdit,
1274
1598
  useCellSelection,
1275
1599
  useConvertTreeData,
1276
- useGlideTable
1600
+ useGlideTable,
1601
+ writeSelectionToClipboard
1277
1602
  });