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.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
 
@@ -167,7 +167,7 @@ function useCellEdit({
167
167
  }
168
168
 
169
169
  // src/components/ui/table/features/cell-selection/useCellSelection.ts
170
- import { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
170
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
171
171
 
172
172
  // src/components/ui/table/features/cell-selection/cellSelection.ts
173
173
  var INITIAL_DRAG_STATE = {
@@ -444,6 +444,127 @@ function hasCellSelectionEdges(style) {
444
444
  );
445
445
  }
446
446
 
447
+ // src/components/ui/table/features/cell-selection/copyData.ts
448
+ function formatCellValue(value) {
449
+ if (value === null || value === void 0) return "";
450
+ return String(value);
451
+ }
452
+ function getNestedValue(row, path) {
453
+ if (!path.includes(".")) return row[path];
454
+ return path.split(".").reduce((current, key) => {
455
+ if (current === null || current === void 0 || typeof current !== "object") {
456
+ return void 0;
457
+ }
458
+ return current[key];
459
+ }, row);
460
+ }
461
+ function readRowColumnValue(rowData, columnDef) {
462
+ if ("accessorFn" in columnDef && typeof columnDef.accessorFn === "function") {
463
+ return columnDef.accessorFn(rowData, 0);
464
+ }
465
+ if ("accessorKey" in columnDef && columnDef.accessorKey != null && columnDef.accessorKey !== "") {
466
+ return getNestedValue(rowData, String(columnDef.accessorKey));
467
+ }
468
+ return void 0;
469
+ }
470
+ function flattenSubtreeRows(row) {
471
+ const children = row.children;
472
+ if (!Array.isArray(children) || children.length === 0) return [];
473
+ const result = [];
474
+ const walk = (nodes) => {
475
+ for (const node of nodes) {
476
+ result.push(node);
477
+ const nested = node.children;
478
+ if (Array.isArray(nested) && nested.length > 0) {
479
+ walk(nested);
480
+ }
481
+ }
482
+ };
483
+ walk(children);
484
+ return result;
485
+ }
486
+ function hasSubtree(row) {
487
+ const children = row.children;
488
+ return Array.isArray(children) && children.length > 0;
489
+ }
490
+ function getOriginalRowId(original) {
491
+ return String(original.id ?? original.uniqueId ?? "");
492
+ }
493
+ function getRowDepth(original) {
494
+ return typeof original.level === "number" ? original.level : 0;
495
+ }
496
+ function collectCopyRowEntries(visibleRows, bounds, mode = "visible") {
497
+ const { startRow, endRow } = bounds;
498
+ const result = [];
499
+ const includedOriginalIds = /* @__PURE__ */ new Set();
500
+ const appendSubtree = (node, depth) => {
501
+ const children = node.children;
502
+ if (!Array.isArray(children) || children.length === 0) return;
503
+ for (const child of children) {
504
+ const childId = getOriginalRowId(child);
505
+ if (!(childId && includedOriginalIds.has(childId))) {
506
+ result.push({ row: child, depth });
507
+ if (childId) includedOriginalIds.add(childId);
508
+ }
509
+ appendSubtree(child, depth + 1);
510
+ }
511
+ };
512
+ for (let rowIndex = startRow; rowIndex <= endRow; rowIndex += 1) {
513
+ const row = visibleRows[rowIndex];
514
+ if (!row) continue;
515
+ const originalId = getOriginalRowId(row.original);
516
+ if (originalId && includedOriginalIds.has(originalId)) continue;
517
+ const depth = getRowDepth(row.original);
518
+ result.push({ row: row.original, depth });
519
+ if (originalId) includedOriginalIds.add(originalId);
520
+ if (mode !== "subtree" || !hasSubtree(row.original)) continue;
521
+ appendSubtree(row.original, depth + 1);
522
+ }
523
+ return result;
524
+ }
525
+ function collectCopyRows(visibleRows, bounds, mode = "visible") {
526
+ return collectCopyRowEntries(visibleRows, bounds, mode).map((entry) => entry.row);
527
+ }
528
+ function serializeCopyRowsToTSV(copyRows, visibleRows, bounds, depths) {
529
+ if (copyRows.length === 0) return "";
530
+ const { startCol, endCol } = bounds;
531
+ const columnCells = visibleRows[0]?.getVisibleCells().slice(startCol, endCol + 1) ?? [];
532
+ if (columnCells.length === 0) return "";
533
+ const resolvedDepths = depths && depths.length === copyRows.length ? depths : copyRows.map((row) => getRowDepth(row));
534
+ const minDepth = Math.min(...resolvedDepths);
535
+ return copyRows.map((rowData, index) => {
536
+ const relativeDepth = Math.max(0, (resolvedDepths[index] ?? 0) - minDepth);
537
+ const line = columnCells.map(
538
+ (cell) => formatCellValue(
539
+ readRowColumnValue(
540
+ rowData,
541
+ cell.column.columnDef
542
+ )
543
+ )
544
+ ).join(" ");
545
+ return `${" ".repeat(relativeDepth)}${line}`;
546
+ }).join("\n");
547
+ }
548
+ function serializeSelectionToTSV(visibleRows, bounds, mode = "visible") {
549
+ const entries = collectCopyRowEntries(visibleRows, bounds, mode);
550
+ return serializeCopyRowsToTSV(
551
+ entries.map((entry) => entry.row),
552
+ visibleRows,
553
+ bounds,
554
+ entries.map((entry) => entry.depth)
555
+ );
556
+ }
557
+ async function writeSelectionToClipboard(visibleRows, bounds, mode = "visible") {
558
+ const text = serializeSelectionToTSV(visibleRows, bounds, mode);
559
+ if (!text) return false;
560
+ try {
561
+ await navigator.clipboard.writeText(text);
562
+ } catch {
563
+ return false;
564
+ }
565
+ return true;
566
+ }
567
+
447
568
  // src/components/ui/table/features/cell-selection/fillData.ts
448
569
  function getColumnAccessorKey2(columnDef) {
449
570
  if ("accessorKey" in columnDef && columnDef.accessorKey) {
@@ -500,15 +621,103 @@ function hasFillExtension(sourceBounds, fillBounds) {
500
621
  return fillBounds.startRow < sourceBounds.startRow || fillBounds.endRow > sourceBounds.endRow || fillBounds.startCol < sourceBounds.startCol || fillBounds.endCol > sourceBounds.endCol;
501
622
  }
502
623
 
624
+ // src/components/ui/table/features/cell-selection/pasteData.ts
625
+ function countLeadingEmptyCells(cells) {
626
+ let depth = 0;
627
+ while (depth < cells.length && cells[depth] === "") {
628
+ depth += 1;
629
+ }
630
+ return depth;
631
+ }
632
+ function looksLikeSubtreeIndentation(leadingEmptyCounts) {
633
+ if (leadingEmptyCounts.length === 0) return false;
634
+ const firstDepth = leadingEmptyCounts[0] ?? 0;
635
+ if (firstDepth !== 0) return false;
636
+ return leadingEmptyCounts.some((depth) => depth > 0);
637
+ }
638
+ function parseClipboardTSV(text) {
639
+ return parseClipboardTSVWithDepths(text).values;
640
+ }
641
+ function parseClipboardTSVWithDepths(text) {
642
+ if (!text) return { values: [], depths: [] };
643
+ const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
644
+ const withoutTrailing = normalized.replace(/\n+$/, "");
645
+ if (!withoutTrailing) return { values: [], depths: [] };
646
+ const rows = withoutTrailing.split("\n").map((line) => line.split(" "));
647
+ const leadingEmptyCounts = rows.map(countLeadingEmptyCells);
648
+ const treatAsDepth = looksLikeSubtreeIndentation(leadingEmptyCounts);
649
+ const values = [];
650
+ const depths = [];
651
+ for (let index = 0; index < rows.length; index += 1) {
652
+ const cells = rows[index] ?? [];
653
+ const depth = leadingEmptyCounts[index] ?? 0;
654
+ if (treatAsDepth) {
655
+ values.push(cells.slice(depth));
656
+ depths.push(depth);
657
+ } else {
658
+ values.push(cells);
659
+ depths.push(0);
660
+ }
661
+ }
662
+ return { values, depths };
663
+ }
664
+ function resolvePasteColumnIds(rows, startCol, width) {
665
+ if (width <= 0) return [];
666
+ const cells = rows[0]?.getVisibleCells() ?? [];
667
+ const columnIds = [];
668
+ for (let offset = 0; offset < width; offset += 1) {
669
+ const cell = cells[startCol + offset];
670
+ if (!cell) break;
671
+ columnIds.push(cell.column.id);
672
+ }
673
+ return columnIds;
674
+ }
675
+ function buildRowsPastePayload(rows, startRow, startCol, text, mode, endRow = startRow) {
676
+ const { values, depths } = parseClipboardTSVWithDepths(text);
677
+ if (values.length === 0) return null;
678
+ const width = Math.max(...values.map((row) => row.length), 0);
679
+ if (width === 0) return null;
680
+ const columnIds = resolvePasteColumnIds(rows, startCol, width);
681
+ if (columnIds.length === 0) return null;
682
+ const rowIds = [];
683
+ for (let offset = 0; offset < values.length; offset += 1) {
684
+ const row = rows[startRow + offset];
685
+ if (!row) break;
686
+ rowIds.push(row.id);
687
+ }
688
+ const anchorRow = rows[endRow] ?? rows[startRow];
689
+ return {
690
+ mode,
691
+ startRow,
692
+ startCol,
693
+ endRow,
694
+ rowIds,
695
+ anchorRowId: anchorRow?.id ?? "",
696
+ columnIds,
697
+ values,
698
+ depths
699
+ };
700
+ }
701
+ function isEditablePasteTarget(target) {
702
+ if (!(target instanceof HTMLElement)) return false;
703
+ const tag = target.tagName;
704
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
705
+ return Boolean(target.isContentEditable);
706
+ }
707
+
503
708
  // src/components/ui/table/features/cell-selection/useCellSelection.ts
504
709
  function useCellSelection({
505
710
  data,
506
711
  rows,
507
712
  enabled = true,
713
+ enableSubtreeCopy = false,
714
+ enableInsertPaste = true,
508
715
  onDataChange,
509
- onBatchChange
716
+ onBatchChange,
717
+ onRowsPaste
510
718
  }) {
511
719
  const [dragState, setDragState] = useState2(INITIAL_DRAG_STATE);
720
+ const pendingPasteModeRef = useRef2(null);
512
721
  const cellSelectionBounds = getCellSelectionBounds(dragState.start, dragState.end);
513
722
  const activeSelectionBounds = enabled ? getActiveSelectionBounds(dragState, cellSelectionBounds) : null;
514
723
  const handleCellMouseDown = useCallback2(
@@ -562,21 +771,113 @@ function useCellSelection({
562
771
  setDragState(INITIAL_DRAG_STATE);
563
772
  }
564
773
  }, [enabled]);
774
+ const copySelection = useCallback2(
775
+ async (options) => {
776
+ if (!enabled || !activeSelectionBounds) return false;
777
+ const mode = options?.includeDescendants && enableSubtreeCopy ? "subtree" : "visible";
778
+ return writeSelectionToClipboard(rows, activeSelectionBounds, mode);
779
+ },
780
+ [activeSelectionBounds, enableSubtreeCopy, enabled, rows]
781
+ );
565
782
  useEffect2(() => {
566
783
  if (!enabled) return;
567
784
  const handleKeyDown = (e) => {
568
- if ((e.ctrlKey || e.metaKey) && e.key === "c" && activeSelectionBounds) {
569
- const { startRow, endRow, startCol, endCol } = activeSelectionBounds;
570
- const selectedData = rows.slice(startRow, endRow + 1).map((row) => {
571
- const cells = row.getVisibleCells();
572
- return cells.slice(startCol, endCol + 1).map((cell) => cell.getValue()).join(" ");
573
- }).join("\n");
574
- navigator.clipboard.writeText(selectedData);
575
- }
785
+ if (!activeSelectionBounds) return;
786
+ if (!(e.ctrlKey || e.metaKey)) return;
787
+ const isSubtreeShortcut = enableSubtreeCopy && e.shiftKey && e.key.toLowerCase() === "c";
788
+ const isVisibleCopyShortcut = !e.shiftKey && e.key.toLowerCase() === "c";
789
+ if (!isSubtreeShortcut && !isVisibleCopyShortcut) return;
790
+ e.preventDefault();
791
+ void copySelection({ includeDescendants: isSubtreeShortcut });
576
792
  };
577
793
  window.addEventListener("keydown", handleKeyDown);
578
794
  return () => window.removeEventListener("keydown", handleKeyDown);
579
- }, [activeSelectionBounds, enabled, rows]);
795
+ }, [activeSelectionBounds, copySelection, enableSubtreeCopy, enabled]);
796
+ const emitRowsPaste = useCallback2(
797
+ (text, mode) => {
798
+ if (!onRowsPaste || !activeSelectionBounds) return false;
799
+ const payload = buildRowsPastePayload(
800
+ rows,
801
+ activeSelectionBounds.startRow,
802
+ activeSelectionBounds.startCol,
803
+ text,
804
+ mode,
805
+ activeSelectionBounds.endRow
806
+ );
807
+ if (!payload) return false;
808
+ onRowsPaste(payload);
809
+ return true;
810
+ },
811
+ [activeSelectionBounds, onRowsPaste, rows]
812
+ );
813
+ useEffect2(() => {
814
+ if (!enabled || !onRowsPaste) return;
815
+ const pasteHandledRef = { current: false };
816
+ const ignoreNextPasteRef = { current: false };
817
+ const handleKeyDown = (e) => {
818
+ if (!activeSelectionBounds) return;
819
+ if (!(e.ctrlKey || e.metaKey)) return;
820
+ if (e.key.toLowerCase() !== "v") return;
821
+ if (isEditablePasteTarget(e.target) || isEditablePasteTarget(document.activeElement)) {
822
+ return;
823
+ }
824
+ if (e.shiftKey && !enableInsertPaste) {
825
+ ignoreNextPasteRef.current = true;
826
+ pendingPasteModeRef.current = null;
827
+ return;
828
+ }
829
+ const mode = e.shiftKey ? "insert" : "overwrite";
830
+ pasteHandledRef.current = false;
831
+ ignoreNextPasteRef.current = false;
832
+ pendingPasteModeRef.current = mode;
833
+ void (async () => {
834
+ try {
835
+ const text = await navigator.clipboard.readText();
836
+ if (pasteHandledRef.current) return;
837
+ if (pendingPasteModeRef.current !== mode) return;
838
+ if (!text) return;
839
+ pasteHandledRef.current = true;
840
+ emitRowsPaste(text, mode);
841
+ pendingPasteModeRef.current = null;
842
+ } catch {
843
+ }
844
+ })();
845
+ };
846
+ const handlePaste = (e) => {
847
+ if (!activeSelectionBounds) return;
848
+ if (isEditablePasteTarget(e.target) || isEditablePasteTarget(document.activeElement)) {
849
+ return;
850
+ }
851
+ if (ignoreNextPasteRef.current) {
852
+ ignoreNextPasteRef.current = false;
853
+ pendingPasteModeRef.current = null;
854
+ return;
855
+ }
856
+ const mode = pendingPasteModeRef.current ?? "overwrite";
857
+ if (pasteHandledRef.current) {
858
+ e.preventDefault();
859
+ return;
860
+ }
861
+ const text = e.clipboardData?.getData("text/plain");
862
+ if (text == null || text === "") return;
863
+ pasteHandledRef.current = true;
864
+ e.preventDefault();
865
+ emitRowsPaste(text, mode);
866
+ pendingPasteModeRef.current = null;
867
+ };
868
+ window.addEventListener("keydown", handleKeyDown);
869
+ window.addEventListener("paste", handlePaste);
870
+ return () => {
871
+ window.removeEventListener("keydown", handleKeyDown);
872
+ window.removeEventListener("paste", handlePaste);
873
+ };
874
+ }, [
875
+ activeSelectionBounds,
876
+ emitRowsPaste,
877
+ enableInsertPaste,
878
+ enabled,
879
+ onRowsPaste
880
+ ]);
580
881
  useEffect2(() => {
581
882
  if (!enabled) return;
582
883
  const handleMouseUp = () => {
@@ -622,12 +923,13 @@ function useCellSelection({
622
923
  activeSelectionBounds,
623
924
  handleCellMouseDown,
624
925
  handleCellMouseEnter,
625
- handleFillHandleMouseDown
926
+ handleFillHandleMouseDown,
927
+ copySelection
626
928
  };
627
929
  }
628
930
 
629
931
  // src/components/ui/table/features/row-expand/row-expand.ts
630
- import { useEffect as useEffect3, useMemo, useRef as useRef2 } from "react";
932
+ import { useEffect as useEffect3, useMemo, useRef as useRef3 } from "react";
631
933
  function getFieldValue(row, key) {
632
934
  return row[key];
633
935
  }
@@ -657,8 +959,8 @@ var useConvertTreeData = ({
657
959
  expandedRows,
658
960
  onExpandedRowsChange
659
961
  }) => {
660
- const onExpandedRowsChangeRef = useRef2(onExpandedRowsChange);
661
- const hasInitializedRef = useRef2(false);
962
+ const onExpandedRowsChangeRef = useRef3(onExpandedRowsChange);
963
+ const hasInitializedRef = useRef3(false);
662
964
  useEffect3(() => {
663
965
  onExpandedRowsChangeRef.current = onExpandedRowsChange;
664
966
  }, [onExpandedRowsChange]);
@@ -704,15 +1006,16 @@ var useConvertTreeData = ({
704
1006
  children: [],
705
1007
  processed: false
706
1008
  }));
707
- const itemMap = /* @__PURE__ */ new Map();
708
- dataWithLevels.forEach((item) => {
709
- const key = getFieldValue(item, toggleField);
710
- if (typeof key !== "string" || !key) return;
711
- if (!itemMap.has(key)) {
712
- itemMap.set(key, []);
1009
+ const findNearestPrecedingParent = (index, parentKey) => {
1010
+ for (let i = index - 1; i >= 0; i -= 1) {
1011
+ const candidate = dataWithLevels[i];
1012
+ if (!candidate) continue;
1013
+ if (getFieldValue(candidate, toggleField) === parentKey) {
1014
+ return candidate;
1015
+ }
713
1016
  }
714
- itemMap.get(key)?.push(item);
715
- });
1017
+ return void 0;
1018
+ };
716
1019
  const rootItems = [];
717
1020
  dataWithLevels.forEach((item) => {
718
1021
  if (!getFieldValue(item, childField)) {
@@ -720,29 +1023,18 @@ var useConvertTreeData = ({
720
1023
  item.processed = true;
721
1024
  }
722
1025
  });
723
- dataWithLevels.forEach((item) => {
1026
+ dataWithLevels.forEach((item, index) => {
724
1027
  const parentKey = getFieldValue(item, childField);
725
1028
  if (!parentKey || item.processed) return;
726
- const parentItems = dataWithLevels.filter(
727
- (parent) => getFieldValue(parent, toggleField) === parentKey && !getFieldValue(parent, childField)
728
- );
729
- if (parentItems.length > 0) {
730
- const parent = parentItems[0];
1029
+ const parent = findNearestPrecedingParent(index, parentKey);
1030
+ if (parent) {
731
1031
  item.level = parent.level + 1;
732
1032
  parent.children.push(item);
733
1033
  item.processed = true;
734
- } else {
735
- const otherParents = itemMap.get(String(parentKey)) || [];
736
- if (otherParents.length > 0) {
737
- const parent = otherParents[0];
738
- item.level = parent.level + 1;
739
- parent.children.push(item);
740
- item.processed = true;
741
- } else {
742
- rootItems.push(item);
743
- item.processed = true;
744
- }
1034
+ return;
745
1035
  }
1036
+ rootItems.push(item);
1037
+ item.processed = true;
746
1038
  });
747
1039
  return rootItems;
748
1040
  }, [enabled, data, toggleField, childField, flattenField]);
@@ -767,16 +1059,23 @@ var useConvertTreeData = ({
767
1059
  return result;
768
1060
  };
769
1061
  const flattenedData = flatten(processedData, [], 0);
770
- flattenedData.forEach((item) => {
771
- if (getFieldValue(item, childField)) {
772
- const parentItem = flattenedData.find(
773
- (parent) => getFieldValue(parent, toggleField) === getFieldValue(item, childField)
774
- );
775
- const parentAmount = parentItem ? Number(getFieldValue(parentItem, qtyField) ?? 1) : 1;
776
- item.parentCount = parentAmount || 1;
777
- } else {
1062
+ flattenedData.forEach((item, index) => {
1063
+ const parentKey = getFieldValue(item, childField);
1064
+ if (!parentKey) {
778
1065
  item.parentCount = 1;
1066
+ return;
1067
+ }
1068
+ let parentItem;
1069
+ for (let i = index - 1; i >= 0; i -= 1) {
1070
+ const candidate = flattenedData[i];
1071
+ if (!candidate) continue;
1072
+ if (getFieldValue(candidate, toggleField) === parentKey) {
1073
+ parentItem = candidate;
1074
+ break;
1075
+ }
779
1076
  }
1077
+ const parentAmount = parentItem ? Number(getFieldValue(parentItem, qtyField) ?? 1) : 1;
1078
+ item.parentCount = parentAmount || 1;
780
1079
  });
781
1080
  return flattenedData;
782
1081
  }, [
@@ -923,6 +1222,10 @@ function useGlideTable(options) {
923
1222
  expandedRows: controlledExpandedRows,
924
1223
  onExpandedRowsChange,
925
1224
  preventExpand = false,
1225
+ enableSubtreeCopy,
1226
+ onCopyActionsReady,
1227
+ onRowsPaste,
1228
+ enableInsertPaste,
926
1229
  enableVirtualization = true,
927
1230
  estimateRowHeight = DATA_TABLE_ROW_HEIGHT,
928
1231
  virtualOverscan = DATA_TABLE_VIRTUAL_OVERSCAN
@@ -937,13 +1240,13 @@ function useGlideTable(options) {
937
1240
  };
938
1241
  }, [labelsProp, emptyText, loadingText, selectionLabel]);
939
1242
  const enableExpand = Boolean(toggleField);
1243
+ const resolvedEnableSubtreeCopy = enableSubtreeCopy ?? enableExpand;
940
1244
  const [internalRowSelection, setInternalRowSelection] = useState3({});
941
1245
  const [internalExpandedRows, setInternalExpandedRows] = useState3(
942
1246
  () => /* @__PURE__ */ new Set()
943
1247
  );
944
1248
  const [hoveredRowIndex, setHoveredRowIndex] = useState3(null);
945
- const [hoveredGroupKey, setHoveredGroupKey] = useState3(null);
946
- const scrollRef = useRef3(null);
1249
+ const scrollRef = useRef4(null);
947
1250
  const shouldVirtualize = enableVirtualization && !enableRowSpan;
948
1251
  useEffect4(() => {
949
1252
  if (enableVirtualization && enableRowSpan) {
@@ -1006,6 +1309,7 @@ function useGlideTable(options) {
1006
1309
  return collectRowSpanColumns(columns);
1007
1310
  }, [enableRowSpan, columns]);
1008
1311
  const primaryRowSpanKey = rowSpanColumnKeys[0]?.rowSpanKey;
1312
+ const primaryRowSpanColumnId = rowSpanColumnKeys[0]?.columnId;
1009
1313
  const columnRowSpanMap = useMemo2(
1010
1314
  () => buildColumnRowSpanMap(tableData, rowSpanColumnKeys),
1011
1315
  [tableData, rowSpanColumnKeys]
@@ -1024,27 +1328,29 @@ function useGlideTable(options) {
1024
1328
  const totalSize = rowVirtualizer.getTotalSize();
1025
1329
  const paddingTop = virtualRows.length > 0 ? virtualRows[0]?.start ?? 0 : 0;
1026
1330
  const paddingBottom = virtualRows.length > 0 ? totalSize - (virtualRows[virtualRows.length - 1]?.end ?? 0) : 0;
1027
- const selectedGroupKeys = useMemo2(() => {
1028
- if (!enableRowSpan || !primaryRowSpanKey) return /* @__PURE__ */ new Set();
1029
- const keys = /* @__PURE__ */ new Set();
1331
+ const selectedRowIndices = useMemo2(() => {
1332
+ const indices = /* @__PURE__ */ new Set();
1030
1333
  for (const selectedRow of selectedRows) {
1031
- const value = selectedRow.original[primaryRowSpanKey];
1032
- if (value !== null && value !== void 0) keys.add(String(value));
1334
+ indices.add(selectedRow.index);
1033
1335
  }
1034
- return keys;
1035
- }, [enableRowSpan, primaryRowSpanKey, selectedRows]);
1336
+ return indices;
1337
+ }, [selectedRows]);
1036
1338
  const {
1037
1339
  dragState,
1038
1340
  activeSelectionBounds,
1039
1341
  handleCellMouseDown,
1040
1342
  handleCellMouseEnter,
1041
- handleFillHandleMouseDown
1343
+ handleFillHandleMouseDown,
1344
+ copySelection
1042
1345
  } = useCellSelection({
1043
1346
  data: tableData,
1044
1347
  rows,
1045
1348
  enabled: enableCellSelection,
1349
+ enableSubtreeCopy: resolvedEnableSubtreeCopy,
1350
+ enableInsertPaste: enableInsertPaste ?? true,
1046
1351
  onDataChange,
1047
- onBatchChange
1352
+ onBatchChange,
1353
+ onRowsPaste
1048
1354
  });
1049
1355
  const {
1050
1356
  editingCell,
@@ -1066,22 +1372,10 @@ function useGlideTable(options) {
1066
1372
  );
1067
1373
  const clearHover = useCallback3(() => {
1068
1374
  setHoveredRowIndex(null);
1069
- setHoveredGroupKey(null);
1070
1375
  }, []);
1071
- const handleRowHover = useCallback3(
1072
- (rowIndex, rowData) => {
1073
- setHoveredRowIndex(rowIndex);
1074
- if (!primaryRowSpanKey) {
1075
- setHoveredGroupKey(null);
1076
- return;
1077
- }
1078
- const groupValue = rowData[primaryRowSpanKey];
1079
- setHoveredGroupKey(
1080
- groupValue === null || groupValue === void 0 ? null : String(groupValue)
1081
- );
1082
- },
1083
- [primaryRowSpanKey]
1084
- );
1376
+ const handleRowHover = useCallback3((rowIndex, _rowData) => {
1377
+ setHoveredRowIndex(rowIndex);
1378
+ }, []);
1085
1379
  const handleToggleSelect = useCallback3(
1086
1380
  (row) => {
1087
1381
  if (!row.getCanSelect()) return;
@@ -1104,10 +1398,10 @@ function useGlideTable(options) {
1104
1398
  rowSpan: {
1105
1399
  enableRowSpan,
1106
1400
  primaryRowSpanKey,
1401
+ primaryRowSpanColumnId,
1107
1402
  columnRowSpanMap,
1108
1403
  hoveredRowIndex,
1109
- hoveredGroupKey,
1110
- selectedGroupKeys,
1404
+ selectedRowIndices,
1111
1405
  onRowHover: handleRowHover
1112
1406
  },
1113
1407
  selection: {
@@ -1145,10 +1439,10 @@ function useGlideTable(options) {
1145
1439
  }, [
1146
1440
  enableRowSpan,
1147
1441
  primaryRowSpanKey,
1442
+ primaryRowSpanColumnId,
1148
1443
  columnRowSpanMap,
1149
1444
  hoveredRowIndex,
1150
- hoveredGroupKey,
1151
- selectedGroupKeys,
1445
+ selectedRowIndices,
1152
1446
  handleRowHover,
1153
1447
  rowSelectionMode,
1154
1448
  selectOnRowClick,
@@ -1174,6 +1468,14 @@ function useGlideTable(options) {
1174
1468
  labels.expandRow,
1175
1469
  labels.collapseRow
1176
1470
  ]);
1471
+ const copySelectionRef = useRef4(copySelection);
1472
+ useEffect4(() => {
1473
+ copySelectionRef.current = copySelection;
1474
+ }, [copySelection]);
1475
+ const stableCopySelection = useCallback3((options2) => copySelectionRef.current(options2), []);
1476
+ useEffect4(() => {
1477
+ onCopyActionsReady?.({ copySelection: stableCopySelection });
1478
+ }, [onCopyActionsReady, stableCopySelection]);
1177
1479
  return {
1178
1480
  table,
1179
1481
  tableData,
@@ -1193,7 +1495,8 @@ function useGlideTable(options) {
1193
1495
  paddingBottom,
1194
1496
  rowContextValue,
1195
1497
  handleToggleSelect,
1196
- clearHover
1498
+ clearHover,
1499
+ copySelection: stableCopySelection
1197
1500
  };
1198
1501
  }
1199
1502
  export {
@@ -1207,9 +1510,13 @@ export {
1207
1510
  applyFillData,
1208
1511
  applySelectionUpdater,
1209
1512
  buildColumnRowSpanMap,
1513
+ buildRowsPastePayload,
1210
1514
  canExpandRow,
1515
+ collectCopyRowEntries,
1516
+ collectCopyRows,
1211
1517
  collectFillChanges,
1212
1518
  collectRowSpanColumns,
1519
+ flattenSubtreeRows,
1213
1520
  getCellEditDraftValue,
1214
1521
  getCellSelectionEdgeStyle,
1215
1522
  getColumnEditType,
@@ -1217,15 +1524,22 @@ export {
1217
1524
  hasCellSelectionEdges,
1218
1525
  isCellInSelection,
1219
1526
  isColumnEditable,
1527
+ isEditablePasteTarget,
1220
1528
  measureMergedSpanRowHeights,
1221
1529
  parseCellEditValue,
1530
+ parseClipboardTSV,
1531
+ parseClipboardTSVWithDepths,
1222
1532
  resolveDataTableLabels,
1533
+ resolvePasteColumnIds,
1223
1534
  resolveRowSelection,
1224
1535
  resolveRowSpanAt,
1225
1536
  rowRangeToHeightRatios,
1537
+ serializeCopyRowsToTSV,
1538
+ serializeSelectionToTSV,
1226
1539
  toggleExpandedRowId,
1227
1540
  useCellEdit,
1228
1541
  useCellSelection,
1229
1542
  useConvertTreeData,
1230
- useGlideTable
1543
+ useGlideTable,
1544
+ writeSelectionToClipboard
1231
1545
  };