react-glide-table 1.1.5 → 1.1.6

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/compound.js CHANGED
@@ -443,15 +443,16 @@ var useConvertTreeData = ({
443
443
  children: [],
444
444
  processed: false
445
445
  }));
446
- const itemMap = /* @__PURE__ */ new Map();
447
- dataWithLevels.forEach((item) => {
448
- const key = getFieldValue(item, toggleField);
449
- if (typeof key !== "string" || !key) return;
450
- if (!itemMap.has(key)) {
451
- itemMap.set(key, []);
446
+ const findNearestPrecedingParent = (index, parentKey) => {
447
+ for (let i = index - 1; i >= 0; i -= 1) {
448
+ const candidate = dataWithLevels[i];
449
+ if (!candidate) continue;
450
+ if (getFieldValue(candidate, toggleField) === parentKey) {
451
+ return candidate;
452
+ }
452
453
  }
453
- itemMap.get(key)?.push(item);
454
- });
454
+ return void 0;
455
+ };
455
456
  const rootItems = [];
456
457
  dataWithLevels.forEach((item) => {
457
458
  if (!getFieldValue(item, childField)) {
@@ -459,29 +460,18 @@ var useConvertTreeData = ({
459
460
  item.processed = true;
460
461
  }
461
462
  });
462
- dataWithLevels.forEach((item) => {
463
+ dataWithLevels.forEach((item, index) => {
463
464
  const parentKey = getFieldValue(item, childField);
464
465
  if (!parentKey || item.processed) return;
465
- const parentItems = dataWithLevels.filter(
466
- (parent) => getFieldValue(parent, toggleField) === parentKey && !getFieldValue(parent, childField)
467
- );
468
- if (parentItems.length > 0) {
469
- const parent = parentItems[0];
466
+ const parent = findNearestPrecedingParent(index, parentKey);
467
+ if (parent) {
470
468
  item.level = parent.level + 1;
471
469
  parent.children.push(item);
472
470
  item.processed = true;
473
- } else {
474
- const otherParents = itemMap.get(String(parentKey)) || [];
475
- if (otherParents.length > 0) {
476
- const parent = otherParents[0];
477
- item.level = parent.level + 1;
478
- parent.children.push(item);
479
- item.processed = true;
480
- } else {
481
- rootItems.push(item);
482
- item.processed = true;
483
- }
471
+ return;
484
472
  }
473
+ rootItems.push(item);
474
+ item.processed = true;
485
475
  });
486
476
  return rootItems;
487
477
  }, [enabled, data, toggleField, childField, flattenField]);
@@ -506,16 +496,23 @@ var useConvertTreeData = ({
506
496
  return result;
507
497
  };
508
498
  const flattenedData = flatten(processedData, [], 0);
509
- flattenedData.forEach((item) => {
510
- if (getFieldValue(item, childField)) {
511
- const parentItem = flattenedData.find(
512
- (parent) => getFieldValue(parent, toggleField) === getFieldValue(item, childField)
513
- );
514
- const parentAmount = parentItem ? Number(getFieldValue(parentItem, qtyField) ?? 1) : 1;
515
- item.parentCount = parentAmount || 1;
516
- } else {
499
+ flattenedData.forEach((item, index) => {
500
+ const parentKey = getFieldValue(item, childField);
501
+ if (!parentKey) {
517
502
  item.parentCount = 1;
503
+ return;
504
+ }
505
+ let parentItem;
506
+ for (let i = index - 1; i >= 0; i -= 1) {
507
+ const candidate = flattenedData[i];
508
+ if (!candidate) continue;
509
+ if (getFieldValue(candidate, toggleField) === parentKey) {
510
+ parentItem = candidate;
511
+ break;
512
+ }
518
513
  }
514
+ const parentAmount = parentItem ? Number(getFieldValue(parentItem, qtyField) ?? 1) : 1;
515
+ item.parentCount = parentAmount || 1;
519
516
  });
520
517
  return flattenedData;
521
518
  }, [
@@ -787,11 +784,10 @@ function DataTableRow({
787
784
  const { classNames, rowSpan, selection, cellSelection, cellEdit, expand } = useDataTableRowContext();
788
785
  const {
789
786
  enableRowSpan,
790
- primaryRowSpanKey,
787
+ primaryRowSpanColumnId,
791
788
  columnRowSpanMap,
792
789
  hoveredRowIndex,
793
- hoveredGroupKey,
794
- selectedGroupKeys,
790
+ selectedRowIndices,
795
791
  onRowHover
796
792
  } = rowSpan;
797
793
  const { rowSelectionMode, selectOnRowClick, onRowClick, getRowClassName } = selection;
@@ -824,9 +820,11 @@ function DataTableRow({
824
820
  const rowData = row.original;
825
821
  const isRowHovered = hoveredRowIndex === rowIndex;
826
822
  const isRowSelected = row.getIsSelected();
827
- const rowGroupKey = primaryRowSpanKey !== void 0 && rowData[primaryRowSpanKey] !== null && rowData[primaryRowSpanKey] !== void 0 ? String(rowData[primaryRowSpanKey]) : null;
828
- const isGroupHovered = enableRowSpan && hoveredGroupKey !== null && rowGroupKey === hoveredGroupKey;
829
- const isGroupSelected = enableRowSpan && rowGroupKey !== null && selectedGroupKeys.has(rowGroupKey);
823
+ const { startRow: primaryGroupStart, rowSpan: primaryGroupSpan } = resolveRowSpanAt(
824
+ primaryRowSpanColumnId ? columnRowSpanMap.get(primaryRowSpanColumnId) : void 0,
825
+ rowIndex
826
+ );
827
+ const isGroupHovered = enableRowSpan && hoveredRowIndex !== null && hoveredRowIndex >= primaryGroupStart && hoveredRowIndex <= primaryGroupStart + primaryGroupSpan - 1;
830
828
  const visibleCells = row.getVisibleCells();
831
829
  const columnIdsByIndex = visibleCells.map((cell) => cell.column.id);
832
830
  const isVisuallySelectedAt = activeSelectionBounds ? (targetRow, targetCol) => {
@@ -903,7 +901,16 @@ function DataTableRow({
903
901
  const cellRowSpan = rowSpanInfo?.rowSpan ?? 1;
904
902
  const isMergedCellHovered = hoveredRowIndex !== null && hoveredRowIndex >= rowIndex && hoveredRowIndex <= rowIndex + cellRowSpan - 1;
905
903
  const showCellHover = isRowSpanColumn ? isMergedCellHovered : isRowHovered;
906
- const showCellSelected = isRowSpanColumn ? isGroupSelected : isRowSelected;
904
+ let isMergedCellSelected = false;
905
+ if (isRowSpanColumn) {
906
+ for (let r = rowIndex; r < rowIndex + cellRowSpan; r += 1) {
907
+ if (selectedRowIndices.has(r)) {
908
+ isMergedCellSelected = true;
909
+ break;
910
+ }
911
+ }
912
+ }
913
+ const showCellSelected = isRowSpanColumn ? isMergedCellSelected : isRowSelected;
907
914
  const isMerged = cellRowSpan > 1;
908
915
  const showMergedRightEdge = isMerged && (cellIndex === visibleCells.length - 1 || resolveRowSpanAt(
909
916
  columnRowSpanMap.get(columnIdsByIndex[cellIndex + 1]),
@@ -1153,7 +1160,7 @@ import {
1153
1160
  useCallback as useCallback3,
1154
1161
  useEffect as useEffect5,
1155
1162
  useMemo as useMemo2,
1156
- useRef as useRef4,
1163
+ useRef as useRef5,
1157
1164
  useState as useState3
1158
1165
  } from "react";
1159
1166
 
@@ -1237,7 +1244,109 @@ function useCellEdit({
1237
1244
  }
1238
1245
 
1239
1246
  // src/components/ui/table/features/cell-selection/useCellSelection.ts
1240
- import { useCallback as useCallback2, useEffect as useEffect4, useState as useState2 } from "react";
1247
+ import { useCallback as useCallback2, useEffect as useEffect4, useRef as useRef4, useState as useState2 } from "react";
1248
+
1249
+ // src/components/ui/table/features/cell-selection/copyData.ts
1250
+ function formatCellValue(value) {
1251
+ if (value === null || value === void 0) return "";
1252
+ return String(value);
1253
+ }
1254
+ function getNestedValue(row, path) {
1255
+ if (!path.includes(".")) return row[path];
1256
+ return path.split(".").reduce((current, key) => {
1257
+ if (current === null || current === void 0 || typeof current !== "object") {
1258
+ return void 0;
1259
+ }
1260
+ return current[key];
1261
+ }, row);
1262
+ }
1263
+ function readRowColumnValue(rowData, columnDef) {
1264
+ if ("accessorFn" in columnDef && typeof columnDef.accessorFn === "function") {
1265
+ return columnDef.accessorFn(rowData, 0);
1266
+ }
1267
+ if ("accessorKey" in columnDef && columnDef.accessorKey != null && columnDef.accessorKey !== "") {
1268
+ return getNestedValue(rowData, String(columnDef.accessorKey));
1269
+ }
1270
+ return void 0;
1271
+ }
1272
+ function hasSubtree(row) {
1273
+ const children = row.children;
1274
+ return Array.isArray(children) && children.length > 0;
1275
+ }
1276
+ function getOriginalRowId(original) {
1277
+ return String(original.id ?? original.uniqueId ?? "");
1278
+ }
1279
+ function getRowDepth(original) {
1280
+ return typeof original.level === "number" ? original.level : 0;
1281
+ }
1282
+ function collectCopyRowEntries(visibleRows, bounds, mode = "visible") {
1283
+ const { startRow, endRow } = bounds;
1284
+ const result = [];
1285
+ const includedOriginalIds = /* @__PURE__ */ new Set();
1286
+ const appendSubtree = (node, depth) => {
1287
+ const children = node.children;
1288
+ if (!Array.isArray(children) || children.length === 0) return;
1289
+ for (const child of children) {
1290
+ const childId = getOriginalRowId(child);
1291
+ if (!(childId && includedOriginalIds.has(childId))) {
1292
+ result.push({ row: child, depth });
1293
+ if (childId) includedOriginalIds.add(childId);
1294
+ }
1295
+ appendSubtree(child, depth + 1);
1296
+ }
1297
+ };
1298
+ for (let rowIndex = startRow; rowIndex <= endRow; rowIndex += 1) {
1299
+ const row = visibleRows[rowIndex];
1300
+ if (!row) continue;
1301
+ const originalId = getOriginalRowId(row.original);
1302
+ if (originalId && includedOriginalIds.has(originalId)) continue;
1303
+ const depth = getRowDepth(row.original);
1304
+ result.push({ row: row.original, depth });
1305
+ if (originalId) includedOriginalIds.add(originalId);
1306
+ if (mode !== "subtree" || !hasSubtree(row.original)) continue;
1307
+ appendSubtree(row.original, depth + 1);
1308
+ }
1309
+ return result;
1310
+ }
1311
+ function serializeCopyRowsToTSV(copyRows, visibleRows, bounds, depths) {
1312
+ if (copyRows.length === 0) return "";
1313
+ const { startCol, endCol } = bounds;
1314
+ const columnCells = visibleRows[0]?.getVisibleCells().slice(startCol, endCol + 1) ?? [];
1315
+ if (columnCells.length === 0) return "";
1316
+ const resolvedDepths = depths && depths.length === copyRows.length ? depths : copyRows.map((row) => getRowDepth(row));
1317
+ const minDepth = Math.min(...resolvedDepths);
1318
+ return copyRows.map((rowData, index) => {
1319
+ const relativeDepth = Math.max(0, (resolvedDepths[index] ?? 0) - minDepth);
1320
+ const line = columnCells.map(
1321
+ (cell) => formatCellValue(
1322
+ readRowColumnValue(
1323
+ rowData,
1324
+ cell.column.columnDef
1325
+ )
1326
+ )
1327
+ ).join(" ");
1328
+ return `${" ".repeat(relativeDepth)}${line}`;
1329
+ }).join("\n");
1330
+ }
1331
+ function serializeSelectionToTSV(visibleRows, bounds, mode = "visible") {
1332
+ const entries = collectCopyRowEntries(visibleRows, bounds, mode);
1333
+ return serializeCopyRowsToTSV(
1334
+ entries.map((entry) => entry.row),
1335
+ visibleRows,
1336
+ bounds,
1337
+ entries.map((entry) => entry.depth)
1338
+ );
1339
+ }
1340
+ async function writeSelectionToClipboard(visibleRows, bounds, mode = "visible") {
1341
+ const text = serializeSelectionToTSV(visibleRows, bounds, mode);
1342
+ if (!text) return false;
1343
+ try {
1344
+ await navigator.clipboard.writeText(text);
1345
+ } catch {
1346
+ return false;
1347
+ }
1348
+ return true;
1349
+ }
1241
1350
 
1242
1351
  // src/components/ui/table/features/cell-selection/fillData.ts
1243
1352
  function getColumnAccessorKey2(columnDef) {
@@ -1295,15 +1404,100 @@ function hasFillExtension(sourceBounds, fillBounds) {
1295
1404
  return fillBounds.startRow < sourceBounds.startRow || fillBounds.endRow > sourceBounds.endRow || fillBounds.startCol < sourceBounds.startCol || fillBounds.endCol > sourceBounds.endCol;
1296
1405
  }
1297
1406
 
1407
+ // src/components/ui/table/features/cell-selection/pasteData.ts
1408
+ function countLeadingEmptyCells(cells) {
1409
+ let depth = 0;
1410
+ while (depth < cells.length && cells[depth] === "") {
1411
+ depth += 1;
1412
+ }
1413
+ return depth;
1414
+ }
1415
+ function looksLikeSubtreeIndentation(leadingEmptyCounts) {
1416
+ if (leadingEmptyCounts.length === 0) return false;
1417
+ const firstDepth = leadingEmptyCounts[0] ?? 0;
1418
+ if (firstDepth !== 0) return false;
1419
+ return leadingEmptyCounts.some((depth) => depth > 0);
1420
+ }
1421
+ function parseClipboardTSVWithDepths(text) {
1422
+ if (!text) return { values: [], depths: [] };
1423
+ const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
1424
+ const withoutTrailing = normalized.replace(/\n+$/, "");
1425
+ if (!withoutTrailing) return { values: [], depths: [] };
1426
+ const rows = withoutTrailing.split("\n").map((line) => line.split(" "));
1427
+ const leadingEmptyCounts = rows.map(countLeadingEmptyCells);
1428
+ const treatAsDepth = looksLikeSubtreeIndentation(leadingEmptyCounts);
1429
+ const values = [];
1430
+ const depths = [];
1431
+ for (let index = 0; index < rows.length; index += 1) {
1432
+ const cells = rows[index] ?? [];
1433
+ const depth = leadingEmptyCounts[index] ?? 0;
1434
+ if (treatAsDepth) {
1435
+ values.push(cells.slice(depth));
1436
+ depths.push(depth);
1437
+ } else {
1438
+ values.push(cells);
1439
+ depths.push(0);
1440
+ }
1441
+ }
1442
+ return { values, depths };
1443
+ }
1444
+ function resolvePasteColumnIds(rows, startCol, width) {
1445
+ if (width <= 0) return [];
1446
+ const cells = rows[0]?.getVisibleCells() ?? [];
1447
+ const columnIds = [];
1448
+ for (let offset = 0; offset < width; offset += 1) {
1449
+ const cell = cells[startCol + offset];
1450
+ if (!cell) break;
1451
+ columnIds.push(cell.column.id);
1452
+ }
1453
+ return columnIds;
1454
+ }
1455
+ function buildRowsPastePayload(rows, startRow, startCol, text, mode, endRow = startRow) {
1456
+ const { values, depths } = parseClipboardTSVWithDepths(text);
1457
+ if (values.length === 0) return null;
1458
+ const width = Math.max(...values.map((row) => row.length), 0);
1459
+ if (width === 0) return null;
1460
+ const columnIds = resolvePasteColumnIds(rows, startCol, width);
1461
+ if (columnIds.length === 0) return null;
1462
+ const rowIds = [];
1463
+ for (let offset = 0; offset < values.length; offset += 1) {
1464
+ const row = rows[startRow + offset];
1465
+ if (!row) break;
1466
+ rowIds.push(row.id);
1467
+ }
1468
+ const anchorRow = rows[endRow] ?? rows[startRow];
1469
+ return {
1470
+ mode,
1471
+ startRow,
1472
+ startCol,
1473
+ endRow,
1474
+ rowIds,
1475
+ anchorRowId: anchorRow?.id ?? "",
1476
+ columnIds,
1477
+ values,
1478
+ depths
1479
+ };
1480
+ }
1481
+ function isEditablePasteTarget(target) {
1482
+ if (!(target instanceof HTMLElement)) return false;
1483
+ const tag = target.tagName;
1484
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
1485
+ return Boolean(target.isContentEditable);
1486
+ }
1487
+
1298
1488
  // src/components/ui/table/features/cell-selection/useCellSelection.ts
1299
1489
  function useCellSelection({
1300
1490
  data,
1301
1491
  rows,
1302
1492
  enabled = true,
1493
+ enableSubtreeCopy = false,
1494
+ enableInsertPaste = true,
1303
1495
  onDataChange,
1304
- onBatchChange
1496
+ onBatchChange,
1497
+ onRowsPaste
1305
1498
  }) {
1306
1499
  const [dragState, setDragState] = useState2(INITIAL_DRAG_STATE);
1500
+ const pendingPasteModeRef = useRef4(null);
1307
1501
  const cellSelectionBounds = getCellSelectionBounds(dragState.start, dragState.end);
1308
1502
  const activeSelectionBounds = enabled ? getActiveSelectionBounds(dragState, cellSelectionBounds) : null;
1309
1503
  const handleCellMouseDown = useCallback2(
@@ -1357,21 +1551,113 @@ function useCellSelection({
1357
1551
  setDragState(INITIAL_DRAG_STATE);
1358
1552
  }
1359
1553
  }, [enabled]);
1554
+ const copySelection = useCallback2(
1555
+ async (options) => {
1556
+ if (!enabled || !activeSelectionBounds) return false;
1557
+ const mode = options?.includeDescendants && enableSubtreeCopy ? "subtree" : "visible";
1558
+ return writeSelectionToClipboard(rows, activeSelectionBounds, mode);
1559
+ },
1560
+ [activeSelectionBounds, enableSubtreeCopy, enabled, rows]
1561
+ );
1360
1562
  useEffect4(() => {
1361
1563
  if (!enabled) return;
1362
1564
  const handleKeyDown = (e) => {
1363
- if ((e.ctrlKey || e.metaKey) && e.key === "c" && activeSelectionBounds) {
1364
- const { startRow, endRow, startCol, endCol } = activeSelectionBounds;
1365
- const selectedData = rows.slice(startRow, endRow + 1).map((row) => {
1366
- const cells = row.getVisibleCells();
1367
- return cells.slice(startCol, endCol + 1).map((cell) => cell.getValue()).join(" ");
1368
- }).join("\n");
1369
- navigator.clipboard.writeText(selectedData);
1370
- }
1565
+ if (!activeSelectionBounds) return;
1566
+ if (!(e.ctrlKey || e.metaKey)) return;
1567
+ const isSubtreeShortcut = enableSubtreeCopy && e.shiftKey && e.key.toLowerCase() === "c";
1568
+ const isVisibleCopyShortcut = !e.shiftKey && e.key.toLowerCase() === "c";
1569
+ if (!isSubtreeShortcut && !isVisibleCopyShortcut) return;
1570
+ e.preventDefault();
1571
+ void copySelection({ includeDescendants: isSubtreeShortcut });
1371
1572
  };
1372
1573
  window.addEventListener("keydown", handleKeyDown);
1373
1574
  return () => window.removeEventListener("keydown", handleKeyDown);
1374
- }, [activeSelectionBounds, enabled, rows]);
1575
+ }, [activeSelectionBounds, copySelection, enableSubtreeCopy, enabled]);
1576
+ const emitRowsPaste = useCallback2(
1577
+ (text, mode) => {
1578
+ if (!onRowsPaste || !activeSelectionBounds) return false;
1579
+ const payload = buildRowsPastePayload(
1580
+ rows,
1581
+ activeSelectionBounds.startRow,
1582
+ activeSelectionBounds.startCol,
1583
+ text,
1584
+ mode,
1585
+ activeSelectionBounds.endRow
1586
+ );
1587
+ if (!payload) return false;
1588
+ onRowsPaste(payload);
1589
+ return true;
1590
+ },
1591
+ [activeSelectionBounds, onRowsPaste, rows]
1592
+ );
1593
+ useEffect4(() => {
1594
+ if (!enabled || !onRowsPaste) return;
1595
+ const pasteHandledRef = { current: false };
1596
+ const ignoreNextPasteRef = { current: false };
1597
+ const handleKeyDown = (e) => {
1598
+ if (!activeSelectionBounds) return;
1599
+ if (!(e.ctrlKey || e.metaKey)) return;
1600
+ if (e.key.toLowerCase() !== "v") return;
1601
+ if (isEditablePasteTarget(e.target) || isEditablePasteTarget(document.activeElement)) {
1602
+ return;
1603
+ }
1604
+ if (e.shiftKey && !enableInsertPaste) {
1605
+ ignoreNextPasteRef.current = true;
1606
+ pendingPasteModeRef.current = null;
1607
+ return;
1608
+ }
1609
+ const mode = e.shiftKey ? "insert" : "overwrite";
1610
+ pasteHandledRef.current = false;
1611
+ ignoreNextPasteRef.current = false;
1612
+ pendingPasteModeRef.current = mode;
1613
+ void (async () => {
1614
+ try {
1615
+ const text = await navigator.clipboard.readText();
1616
+ if (pasteHandledRef.current) return;
1617
+ if (pendingPasteModeRef.current !== mode) return;
1618
+ if (!text) return;
1619
+ pasteHandledRef.current = true;
1620
+ emitRowsPaste(text, mode);
1621
+ pendingPasteModeRef.current = null;
1622
+ } catch {
1623
+ }
1624
+ })();
1625
+ };
1626
+ const handlePaste = (e) => {
1627
+ if (!activeSelectionBounds) return;
1628
+ if (isEditablePasteTarget(e.target) || isEditablePasteTarget(document.activeElement)) {
1629
+ return;
1630
+ }
1631
+ if (ignoreNextPasteRef.current) {
1632
+ ignoreNextPasteRef.current = false;
1633
+ pendingPasteModeRef.current = null;
1634
+ return;
1635
+ }
1636
+ const mode = pendingPasteModeRef.current ?? "overwrite";
1637
+ if (pasteHandledRef.current) {
1638
+ e.preventDefault();
1639
+ return;
1640
+ }
1641
+ const text = e.clipboardData?.getData("text/plain");
1642
+ if (text == null || text === "") return;
1643
+ pasteHandledRef.current = true;
1644
+ e.preventDefault();
1645
+ emitRowsPaste(text, mode);
1646
+ pendingPasteModeRef.current = null;
1647
+ };
1648
+ window.addEventListener("keydown", handleKeyDown);
1649
+ window.addEventListener("paste", handlePaste);
1650
+ return () => {
1651
+ window.removeEventListener("keydown", handleKeyDown);
1652
+ window.removeEventListener("paste", handlePaste);
1653
+ };
1654
+ }, [
1655
+ activeSelectionBounds,
1656
+ emitRowsPaste,
1657
+ enableInsertPaste,
1658
+ enabled,
1659
+ onRowsPaste
1660
+ ]);
1375
1661
  useEffect4(() => {
1376
1662
  if (!enabled) return;
1377
1663
  const handleMouseUp = () => {
@@ -1417,7 +1703,8 @@ function useCellSelection({
1417
1703
  activeSelectionBounds,
1418
1704
  handleCellMouseDown,
1419
1705
  handleCellMouseEnter,
1420
- handleFillHandleMouseDown
1706
+ handleFillHandleMouseDown,
1707
+ copySelection
1421
1708
  };
1422
1709
  }
1423
1710
 
@@ -1481,6 +1768,10 @@ function useGlideTable(options) {
1481
1768
  expandedRows: controlledExpandedRows,
1482
1769
  onExpandedRowsChange,
1483
1770
  preventExpand = false,
1771
+ enableSubtreeCopy,
1772
+ onCopyActionsReady,
1773
+ onRowsPaste,
1774
+ enableInsertPaste,
1484
1775
  enableVirtualization = true,
1485
1776
  estimateRowHeight = DATA_TABLE_ROW_HEIGHT,
1486
1777
  virtualOverscan = DATA_TABLE_VIRTUAL_OVERSCAN
@@ -1495,13 +1786,13 @@ function useGlideTable(options) {
1495
1786
  };
1496
1787
  }, [labelsProp, emptyText, loadingText, selectionLabel]);
1497
1788
  const enableExpand = Boolean(toggleField);
1789
+ const resolvedEnableSubtreeCopy = enableSubtreeCopy ?? enableExpand;
1498
1790
  const [internalRowSelection, setInternalRowSelection] = useState3({});
1499
1791
  const [internalExpandedRows, setInternalExpandedRows] = useState3(
1500
1792
  () => /* @__PURE__ */ new Set()
1501
1793
  );
1502
1794
  const [hoveredRowIndex, setHoveredRowIndex] = useState3(null);
1503
- const [hoveredGroupKey, setHoveredGroupKey] = useState3(null);
1504
- const scrollRef = useRef4(null);
1795
+ const scrollRef = useRef5(null);
1505
1796
  const shouldVirtualize = enableVirtualization && !enableRowSpan;
1506
1797
  useEffect5(() => {
1507
1798
  if (enableVirtualization && enableRowSpan) {
@@ -1564,6 +1855,7 @@ function useGlideTable(options) {
1564
1855
  return collectRowSpanColumns(columns);
1565
1856
  }, [enableRowSpan, columns]);
1566
1857
  const primaryRowSpanKey = rowSpanColumnKeys[0]?.rowSpanKey;
1858
+ const primaryRowSpanColumnId = rowSpanColumnKeys[0]?.columnId;
1567
1859
  const columnRowSpanMap = useMemo2(
1568
1860
  () => buildColumnRowSpanMap(tableData, rowSpanColumnKeys),
1569
1861
  [tableData, rowSpanColumnKeys]
@@ -1582,27 +1874,29 @@ function useGlideTable(options) {
1582
1874
  const totalSize = rowVirtualizer.getTotalSize();
1583
1875
  const paddingTop = virtualRows.length > 0 ? virtualRows[0]?.start ?? 0 : 0;
1584
1876
  const paddingBottom = virtualRows.length > 0 ? totalSize - (virtualRows[virtualRows.length - 1]?.end ?? 0) : 0;
1585
- const selectedGroupKeys = useMemo2(() => {
1586
- if (!enableRowSpan || !primaryRowSpanKey) return /* @__PURE__ */ new Set();
1587
- const keys = /* @__PURE__ */ new Set();
1877
+ const selectedRowIndices = useMemo2(() => {
1878
+ const indices = /* @__PURE__ */ new Set();
1588
1879
  for (const selectedRow of selectedRows) {
1589
- const value = selectedRow.original[primaryRowSpanKey];
1590
- if (value !== null && value !== void 0) keys.add(String(value));
1880
+ indices.add(selectedRow.index);
1591
1881
  }
1592
- return keys;
1593
- }, [enableRowSpan, primaryRowSpanKey, selectedRows]);
1882
+ return indices;
1883
+ }, [selectedRows]);
1594
1884
  const {
1595
1885
  dragState,
1596
1886
  activeSelectionBounds,
1597
1887
  handleCellMouseDown,
1598
1888
  handleCellMouseEnter,
1599
- handleFillHandleMouseDown
1889
+ handleFillHandleMouseDown,
1890
+ copySelection
1600
1891
  } = useCellSelection({
1601
1892
  data: tableData,
1602
1893
  rows,
1603
1894
  enabled: enableCellSelection,
1895
+ enableSubtreeCopy: resolvedEnableSubtreeCopy,
1896
+ enableInsertPaste: enableInsertPaste ?? true,
1604
1897
  onDataChange,
1605
- onBatchChange
1898
+ onBatchChange,
1899
+ onRowsPaste
1606
1900
  });
1607
1901
  const {
1608
1902
  editingCell,
@@ -1624,22 +1918,10 @@ function useGlideTable(options) {
1624
1918
  );
1625
1919
  const clearHover = useCallback3(() => {
1626
1920
  setHoveredRowIndex(null);
1627
- setHoveredGroupKey(null);
1628
1921
  }, []);
1629
- const handleRowHover = useCallback3(
1630
- (rowIndex, rowData) => {
1631
- setHoveredRowIndex(rowIndex);
1632
- if (!primaryRowSpanKey) {
1633
- setHoveredGroupKey(null);
1634
- return;
1635
- }
1636
- const groupValue = rowData[primaryRowSpanKey];
1637
- setHoveredGroupKey(
1638
- groupValue === null || groupValue === void 0 ? null : String(groupValue)
1639
- );
1640
- },
1641
- [primaryRowSpanKey]
1642
- );
1922
+ const handleRowHover = useCallback3((rowIndex, _rowData) => {
1923
+ setHoveredRowIndex(rowIndex);
1924
+ }, []);
1643
1925
  const handleToggleSelect = useCallback3(
1644
1926
  (row) => {
1645
1927
  if (!row.getCanSelect()) return;
@@ -1662,10 +1944,10 @@ function useGlideTable(options) {
1662
1944
  rowSpan: {
1663
1945
  enableRowSpan,
1664
1946
  primaryRowSpanKey,
1947
+ primaryRowSpanColumnId,
1665
1948
  columnRowSpanMap,
1666
1949
  hoveredRowIndex,
1667
- hoveredGroupKey,
1668
- selectedGroupKeys,
1950
+ selectedRowIndices,
1669
1951
  onRowHover: handleRowHover
1670
1952
  },
1671
1953
  selection: {
@@ -1703,10 +1985,10 @@ function useGlideTable(options) {
1703
1985
  }, [
1704
1986
  enableRowSpan,
1705
1987
  primaryRowSpanKey,
1988
+ primaryRowSpanColumnId,
1706
1989
  columnRowSpanMap,
1707
1990
  hoveredRowIndex,
1708
- hoveredGroupKey,
1709
- selectedGroupKeys,
1991
+ selectedRowIndices,
1710
1992
  handleRowHover,
1711
1993
  rowSelectionMode,
1712
1994
  selectOnRowClick,
@@ -1732,6 +2014,14 @@ function useGlideTable(options) {
1732
2014
  labels.expandRow,
1733
2015
  labels.collapseRow
1734
2016
  ]);
2017
+ const copySelectionRef = useRef5(copySelection);
2018
+ useEffect5(() => {
2019
+ copySelectionRef.current = copySelection;
2020
+ }, [copySelection]);
2021
+ const stableCopySelection = useCallback3((options2) => copySelectionRef.current(options2), []);
2022
+ useEffect5(() => {
2023
+ onCopyActionsReady?.({ copySelection: stableCopySelection });
2024
+ }, [onCopyActionsReady, stableCopySelection]);
1735
2025
  return {
1736
2026
  table,
1737
2027
  tableData,
@@ -1751,7 +2041,8 @@ function useGlideTable(options) {
1751
2041
  paddingBottom,
1752
2042
  rowContextValue,
1753
2043
  handleToggleSelect,
1754
- clearHover
2044
+ clearHover,
2045
+ copySelection: stableCopySelection
1755
2046
  };
1756
2047
  }
1757
2048