react-glide-table 2.3.0 → 2.3.2

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
@@ -441,9 +441,126 @@ function withCellUpdate(context, commitValue) {
441
441
  };
442
442
  }
443
443
 
444
+ // src/components/ui/table/features/cell-selection/pasteData.ts
445
+ function countLeadingEmptyCells(cells) {
446
+ let depth = 0;
447
+ while (depth < cells.length && cells[depth] === "") {
448
+ depth += 1;
449
+ }
450
+ return depth;
451
+ }
452
+ function looksLikeSubtreeIndentation(leadingEmptyCounts) {
453
+ if (leadingEmptyCounts.length === 0) return false;
454
+ const firstDepth = leadingEmptyCounts[0] ?? 0;
455
+ if (firstDepth !== 0) return false;
456
+ return leadingEmptyCounts.some((depth) => depth > 0);
457
+ }
458
+ function parseClipboardTSV(text) {
459
+ return parseClipboardTSVWithDepths(text).values;
460
+ }
461
+ function parseClipboardTSVWithDepths(text) {
462
+ if (!text) return { values: [], depths: [] };
463
+ const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
464
+ const withoutTrailing = normalized.replace(/\n+$/, "");
465
+ if (!withoutTrailing) return { values: [], depths: [] };
466
+ const rows = withoutTrailing.split("\n").map((line) => line.split(" "));
467
+ const leadingEmptyCounts = rows.map(countLeadingEmptyCells);
468
+ const treatAsDepth = looksLikeSubtreeIndentation(leadingEmptyCounts);
469
+ const values = [];
470
+ const depths = [];
471
+ for (let index = 0; index < rows.length; index += 1) {
472
+ const cells = rows[index] ?? [];
473
+ const depth = leadingEmptyCounts[index] ?? 0;
474
+ if (treatAsDepth) {
475
+ values.push(cells.slice(depth));
476
+ depths.push(depth);
477
+ } else {
478
+ values.push(cells);
479
+ depths.push(0);
480
+ }
481
+ }
482
+ return { values, depths };
483
+ }
484
+ function resolvePasteColumnIds(rows, startCol, width) {
485
+ if (width <= 0) return [];
486
+ const cells = rows[0]?.getVisibleCells() ?? [];
487
+ const columnIds = [];
488
+ for (let offset = 0; offset < width; offset += 1) {
489
+ const cell = cells[startCol + offset];
490
+ if (!cell) break;
491
+ columnIds.push(cell.column.id);
492
+ }
493
+ return columnIds;
494
+ }
495
+ function buildRowsPastePayload(rows, startRow, startCol, text, mode, endRow = startRow) {
496
+ const { values, depths } = parseClipboardTSVWithDepths(text);
497
+ if (values.length === 0) return null;
498
+ const width = Math.max(...values.map((row) => row.length), 0);
499
+ if (width === 0) return null;
500
+ const columnIds = resolvePasteColumnIds(rows, startCol, width);
501
+ if (columnIds.length === 0) return null;
502
+ const rowIds = [];
503
+ for (let offset = 0; offset < values.length; offset += 1) {
504
+ const row = rows[startRow + offset];
505
+ if (!row) break;
506
+ rowIds.push(row.id);
507
+ }
508
+ const anchorRow = rows[endRow] ?? rows[startRow];
509
+ return {
510
+ mode,
511
+ startRow,
512
+ startCol,
513
+ endRow,
514
+ rowIds,
515
+ anchorRowId: anchorRow?.id ?? "",
516
+ columnIds,
517
+ values,
518
+ depths
519
+ };
520
+ }
521
+ function isEditablePasteTarget(target) {
522
+ if (!(target instanceof HTMLElement)) return false;
523
+ const tag = target.tagName;
524
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
525
+ return Boolean(target.isContentEditable);
526
+ }
527
+
444
528
  // src/components/ui/table/features/cell-selection/useCellSelection.ts
445
529
  import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
446
530
 
531
+ // src/components/ui/table/features/cell-selection/activeCellSelectionOwner.ts
532
+ var activeOwner = null;
533
+ var clearByOwner = /* @__PURE__ */ new Map();
534
+ function createCellSelectionOwner() {
535
+ return /* @__PURE__ */ Symbol("cell-selection-owner");
536
+ }
537
+ function registerCellSelectionOwner(owner, clearSelection) {
538
+ clearByOwner.set(owner, clearSelection);
539
+ return () => {
540
+ clearByOwner.delete(owner);
541
+ if (activeOwner === owner) {
542
+ activeOwner = null;
543
+ }
544
+ };
545
+ }
546
+ function claimCellSelectionOwner(owner) {
547
+ if (activeOwner === owner) return;
548
+ activeOwner = owner;
549
+ for (const [id, clearSelection] of clearByOwner) {
550
+ if (id !== owner) {
551
+ clearSelection();
552
+ }
553
+ }
554
+ }
555
+ function isActiveCellSelectionOwner(owner) {
556
+ return activeOwner === owner;
557
+ }
558
+ function releaseCellSelectionOwner(owner) {
559
+ if (activeOwner === owner) {
560
+ activeOwner = null;
561
+ }
562
+ }
563
+
447
564
  // src/components/ui/table/features/cell-selection/cellSelection.ts
448
565
  var INITIAL_DRAG_STATE = {
449
566
  isSelecting: false,
@@ -921,19 +1038,31 @@ function extractRenderedCopyText(node, value, cellPosition, root) {
921
1038
  function formatCopyCellText(rowData, columnDef, columnId, visibleRow, fallbackIndex, sourceCell, cellPosition, options) {
922
1039
  const meta = columnDef.meta;
923
1040
  const value = sourceCell ? sourceCell.getValue() : readRowColumnValue(rowData, columnDef);
1041
+ const row = visibleRow ?? createCopyRenderRow(rowData, fallbackIndex);
1042
+ const ctx = {
1043
+ value,
1044
+ row,
1045
+ index: row.index,
1046
+ columnId,
1047
+ cellProps: meta?.cellProps,
1048
+ update: () => {
1049
+ }
1050
+ };
1051
+ const copyValue = meta?.copyValue;
1052
+ if (typeof copyValue === "function") {
1053
+ try {
1054
+ return sanitizeClipboardCell(copyValue(ctx));
1055
+ } catch {
1056
+ return formatCellValue(value);
1057
+ }
1058
+ }
1059
+ if (copyValue === "value") {
1060
+ return formatCellValue(value);
1061
+ }
924
1062
  const cellRender = meta?.cellRender;
925
1063
  if (typeof cellRender === "function") {
926
1064
  try {
927
- const row = visibleRow ?? createCopyRenderRow(rowData, fallbackIndex);
928
- const node = cellRender({
929
- value,
930
- row,
931
- index: row.index,
932
- columnId,
933
- cellProps: meta?.cellProps,
934
- update: () => {
935
- }
936
- });
1065
+ const node = cellRender(ctx);
937
1066
  return extractRenderedCopyText(node, value, cellPosition, options?.root);
938
1067
  } catch {
939
1068
  return formatCellValue(value);
@@ -941,16 +1070,6 @@ function formatCopyCellText(rowData, columnDef, columnId, visibleRow, fallbackIn
941
1070
  }
942
1071
  if (options?.registry && meta?.kind && isPrimitiveCopyValue(value)) {
943
1072
  try {
944
- const row = visibleRow ?? createCopyRenderRow(rowData, fallbackIndex);
945
- const ctx = {
946
- value,
947
- row,
948
- index: row.index,
949
- columnId,
950
- cellProps: meta.cellProps,
951
- update: () => {
952
- }
953
- };
954
1073
  const renderer = resolveCellRenderer(options.registry, meta.kind, ctx);
955
1074
  if (renderer) {
956
1075
  const node = renderer.render(ctx);
@@ -1076,11 +1195,16 @@ function serializeCopyRowsToTSV(copyRows, visibleRows, bounds, depths, options)
1076
1195
  const resolvedDepths = depths && depths.length === copyRows.length ? depths : copyRows.map((row) => getRowDepth(row));
1077
1196
  const minDepth = Math.min(...resolvedDepths);
1078
1197
  const visibleRowByOriginal = buildVisibleRowLookup(visibleRows);
1198
+ const copyableColumns = columnCells.flatMap((templateCell, colOffset) => {
1199
+ const meta = templateCell.column.columnDef.meta;
1200
+ if (meta?.copyValue === "omit") return [];
1201
+ return [{ templateCell, colOffset }];
1202
+ });
1079
1203
  return copyRows.map((rowData, index) => {
1080
1204
  const relativeDepth = Math.max(0, (resolvedDepths[index] ?? 0) - minDepth);
1081
1205
  const visibleRow = visibleRowByOriginal.get(rowData);
1082
1206
  const matchingCells = visibleRow?.getVisibleCells().slice(startCol, endCol + 1);
1083
- const line = columnCells.map((templateCell, colOffset) => {
1207
+ const line = copyableColumns.map(({ templateCell, colOffset }) => {
1084
1208
  const sourceCell = matchingCells?.[colOffset];
1085
1209
  const column = sourceCell?.column ?? templateCell.column;
1086
1210
  return formatCopyCellText(
@@ -1174,90 +1298,6 @@ function hasFillExtension(sourceBounds, fillBounds) {
1174
1298
  return fillBounds.startRow < sourceBounds.startRow || fillBounds.endRow > sourceBounds.endRow || fillBounds.startCol < sourceBounds.startCol || fillBounds.endCol > sourceBounds.endCol;
1175
1299
  }
1176
1300
 
1177
- // src/components/ui/table/features/cell-selection/pasteData.ts
1178
- function countLeadingEmptyCells(cells) {
1179
- let depth = 0;
1180
- while (depth < cells.length && cells[depth] === "") {
1181
- depth += 1;
1182
- }
1183
- return depth;
1184
- }
1185
- function looksLikeSubtreeIndentation(leadingEmptyCounts) {
1186
- if (leadingEmptyCounts.length === 0) return false;
1187
- const firstDepth = leadingEmptyCounts[0] ?? 0;
1188
- if (firstDepth !== 0) return false;
1189
- return leadingEmptyCounts.some((depth) => depth > 0);
1190
- }
1191
- function parseClipboardTSV(text) {
1192
- return parseClipboardTSVWithDepths(text).values;
1193
- }
1194
- function parseClipboardTSVWithDepths(text) {
1195
- if (!text) return { values: [], depths: [] };
1196
- const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
1197
- const withoutTrailing = normalized.replace(/\n+$/, "");
1198
- if (!withoutTrailing) return { values: [], depths: [] };
1199
- const rows = withoutTrailing.split("\n").map((line) => line.split(" "));
1200
- const leadingEmptyCounts = rows.map(countLeadingEmptyCells);
1201
- const treatAsDepth = looksLikeSubtreeIndentation(leadingEmptyCounts);
1202
- const values = [];
1203
- const depths = [];
1204
- for (let index = 0; index < rows.length; index += 1) {
1205
- const cells = rows[index] ?? [];
1206
- const depth = leadingEmptyCounts[index] ?? 0;
1207
- if (treatAsDepth) {
1208
- values.push(cells.slice(depth));
1209
- depths.push(depth);
1210
- } else {
1211
- values.push(cells);
1212
- depths.push(0);
1213
- }
1214
- }
1215
- return { values, depths };
1216
- }
1217
- function resolvePasteColumnIds(rows, startCol, width) {
1218
- if (width <= 0) return [];
1219
- const cells = rows[0]?.getVisibleCells() ?? [];
1220
- const columnIds = [];
1221
- for (let offset = 0; offset < width; offset += 1) {
1222
- const cell = cells[startCol + offset];
1223
- if (!cell) break;
1224
- columnIds.push(cell.column.id);
1225
- }
1226
- return columnIds;
1227
- }
1228
- function buildRowsPastePayload(rows, startRow, startCol, text, mode, endRow = startRow) {
1229
- const { values, depths } = parseClipboardTSVWithDepths(text);
1230
- if (values.length === 0) return null;
1231
- const width = Math.max(...values.map((row) => row.length), 0);
1232
- if (width === 0) return null;
1233
- const columnIds = resolvePasteColumnIds(rows, startCol, width);
1234
- if (columnIds.length === 0) return null;
1235
- const rowIds = [];
1236
- for (let offset = 0; offset < values.length; offset += 1) {
1237
- const row = rows[startRow + offset];
1238
- if (!row) break;
1239
- rowIds.push(row.id);
1240
- }
1241
- const anchorRow = rows[endRow] ?? rows[startRow];
1242
- return {
1243
- mode,
1244
- startRow,
1245
- startCol,
1246
- endRow,
1247
- rowIds,
1248
- anchorRowId: anchorRow?.id ?? "",
1249
- columnIds,
1250
- values,
1251
- depths
1252
- };
1253
- }
1254
- function isEditablePasteTarget(target) {
1255
- if (!(target instanceof HTMLElement)) return false;
1256
- const tag = target.tagName;
1257
- if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
1258
- return Boolean(target.isContentEditable);
1259
- }
1260
-
1261
1301
  // src/components/ui/table/features/cell-selection/useCellSelection.ts
1262
1302
  function useCellSelection({
1263
1303
  data,
@@ -1273,6 +1313,7 @@ function useCellSelection({
1273
1313
  cellRendererRegistry,
1274
1314
  rootRef
1275
1315
  }) {
1316
+ const ownerRef = useRef2(createCellSelectionOwner());
1276
1317
  const [dragState, setDragState] = useState2(INITIAL_DRAG_STATE);
1277
1318
  const pendingPasteModeRef = useRef2(null);
1278
1319
  const dragStateRef = useRef2(dragState);
@@ -1284,6 +1325,7 @@ function useCellSelection({
1284
1325
  const handleCellMouseDown = useCallback2(
1285
1326
  (rowIndex, colIndex, options) => {
1286
1327
  if (!enabled) return;
1328
+ claimCellSelectionOwner(ownerRef.current);
1287
1329
  setDragState((prev) => {
1288
1330
  if (options?.shiftKey && prev.start) {
1289
1331
  return {
@@ -1325,6 +1367,7 @@ function useCellSelection({
1325
1367
  const handleFillHandleMouseDown = useCallback2(
1326
1368
  (rowIndex, colIndex) => {
1327
1369
  if (!enabled) return;
1370
+ claimCellSelectionOwner(ownerRef.current);
1328
1371
  setDragState((prev) => {
1329
1372
  const bounds = getCellSelectionBounds(prev.start, prev.end);
1330
1373
  if (!bounds) return prev;
@@ -1339,14 +1382,27 @@ function useCellSelection({
1339
1382
  },
1340
1383
  [enabled]
1341
1384
  );
1385
+ const clearSelection = useCallback2(() => {
1386
+ const prev = dragStateRef.current;
1387
+ if (prev.start === null && prev.end === null && !prev.isSelecting && !prev.isFillDragging) {
1388
+ return;
1389
+ }
1390
+ releaseCellSelectionOwner(ownerRef.current);
1391
+ dragStateRef.current = INITIAL_DRAG_STATE;
1392
+ setDragState(INITIAL_DRAG_STATE);
1393
+ }, []);
1342
1394
  useEffect2(() => {
1343
1395
  if (!enabled) {
1344
- setDragState(INITIAL_DRAG_STATE);
1396
+ clearSelection();
1345
1397
  }
1346
- }, [enabled]);
1398
+ }, [clearSelection, enabled]);
1399
+ useEffect2(() => {
1400
+ return registerCellSelectionOwner(ownerRef.current, clearSelection);
1401
+ }, [clearSelection]);
1347
1402
  useEffect2(() => {
1348
1403
  if (!enabled) return;
1349
1404
  const handleKeyDown = (e) => {
1405
+ if (!isActiveCellSelectionOwner(ownerRef.current)) return;
1350
1406
  if (e.ctrlKey || e.metaKey || e.altKey) return;
1351
1407
  if (isEditablePasteTarget(e.target) || isEditablePasteTarget(document.activeElement)) {
1352
1408
  return;
@@ -1412,6 +1468,7 @@ function useCellSelection({
1412
1468
  useEffect2(() => {
1413
1469
  if (!enabled) return;
1414
1470
  const handleKeyDown = (e) => {
1471
+ if (!isActiveCellSelectionOwner(ownerRef.current)) return;
1415
1472
  if (!activeSelectionBounds) return;
1416
1473
  if (!(e.ctrlKey || e.metaKey)) return;
1417
1474
  if (isEditablePasteTarget(e.target) || isEditablePasteTarget(document.activeElement)) {
@@ -1448,6 +1505,7 @@ function useCellSelection({
1448
1505
  const pasteHandledRef = { current: false };
1449
1506
  const ignoreNextPasteRef = { current: false };
1450
1507
  const handleKeyDown = (e) => {
1508
+ if (!isActiveCellSelectionOwner(ownerRef.current)) return;
1451
1509
  if (!activeSelectionBounds) return;
1452
1510
  if (!(e.ctrlKey || e.metaKey)) return;
1453
1511
  if (e.key.toLowerCase() !== "v") return;
@@ -1468,6 +1526,7 @@ function useCellSelection({
1468
1526
  const text = await navigator.clipboard.readText();
1469
1527
  if (pasteHandledRef.current) return;
1470
1528
  if (pendingPasteModeRef.current !== mode) return;
1529
+ if (!isActiveCellSelectionOwner(ownerRef.current)) return;
1471
1530
  if (!text) return;
1472
1531
  pasteHandledRef.current = true;
1473
1532
  emitRowsPaste(text, mode);
@@ -1477,6 +1536,7 @@ function useCellSelection({
1477
1536
  })();
1478
1537
  };
1479
1538
  const handlePaste = (e) => {
1539
+ if (!isActiveCellSelectionOwner(ownerRef.current)) return;
1480
1540
  if (!activeSelectionBounds) return;
1481
1541
  if (isEditablePasteTarget(e.target) || isEditablePasteTarget(document.activeElement)) {
1482
1542
  return;
@@ -1557,10 +1617,30 @@ function useCellSelection({
1557
1617
  handleCellMouseDown,
1558
1618
  handleCellMouseEnter,
1559
1619
  handleFillHandleMouseDown,
1620
+ clearSelection,
1560
1621
  copySelection
1561
1622
  };
1562
1623
  }
1563
1624
 
1625
+ // src/components/ui/table/features/selection-dismiss/isOutsideDismissTarget.ts
1626
+ var OVERLAY_DISMISS_IGNORE_SELECTOR = [
1627
+ '[role="dialog"]',
1628
+ '[role="alertdialog"]',
1629
+ '[role="menu"]',
1630
+ '[role="listbox"]',
1631
+ '[role="tooltip"]',
1632
+ '[aria-modal="true"]',
1633
+ "[data-radix-portal]",
1634
+ "[data-radix-popper-content-wrapper]",
1635
+ "[data-floating-ui-portal]",
1636
+ "[data-table-ignore-outside-dismiss]"
1637
+ ].join(",");
1638
+ function isOverlayDismissIgnoreTarget(target) {
1639
+ const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
1640
+ if (!element) return false;
1641
+ return element.closest(OVERLAY_DISMISS_IGNORE_SELECTOR) !== null;
1642
+ }
1643
+
1564
1644
  // src/components/ui/table/features/column-reorder/columnReorder.ts
1565
1645
  function getColumnDefId(column) {
1566
1646
  if (column.id != null && column.id !== "") return column.id;
@@ -2847,6 +2927,7 @@ function useGlideTable(options) {
2847
2927
  handleCellMouseDown,
2848
2928
  handleCellMouseEnter,
2849
2929
  handleFillHandleMouseDown,
2930
+ clearSelection: clearCellSelection,
2850
2931
  copySelection
2851
2932
  } = useCellSelection({
2852
2933
  data: tableData,
@@ -2862,6 +2943,37 @@ function useGlideTable(options) {
2862
2943
  cellRendererRegistry,
2863
2944
  rootRef
2864
2945
  });
2946
+ const clearRowSelection = useCallback4(() => {
2947
+ if (rowSelectionMode === "none") return;
2948
+ const hasSelection = Object.values(rowSelection).some(Boolean);
2949
+ if (!hasSelection) return;
2950
+ if (onRowSelectionChange) {
2951
+ onRowSelectionChange(() => ({}));
2952
+ return;
2953
+ }
2954
+ setInternalRowSelection({});
2955
+ }, [onRowSelectionChange, rowSelection, rowSelectionMode]);
2956
+ useEffect5(() => {
2957
+ const clearAllSelections = () => {
2958
+ clearCellSelection();
2959
+ clearRowSelection();
2960
+ };
2961
+ const handleKeyDown = (event) => {
2962
+ if (event.key !== "Escape") return;
2963
+ if (event.defaultPrevented) return;
2964
+ if (isEditablePasteTarget(event.target) || isEditablePasteTarget(document.activeElement)) {
2965
+ return;
2966
+ }
2967
+ if (isOverlayDismissIgnoreTarget(event.target) || isOverlayDismissIgnoreTarget(document.activeElement)) {
2968
+ return;
2969
+ }
2970
+ clearAllSelections();
2971
+ };
2972
+ window.addEventListener("keydown", handleKeyDown);
2973
+ return () => {
2974
+ window.removeEventListener("keydown", handleKeyDown);
2975
+ };
2976
+ }, [clearCellSelection, clearRowSelection]);
2865
2977
  const {
2866
2978
  editingCell,
2867
2979
  draftValue,
@@ -4956,7 +5068,8 @@ function buildColumnDef(props, sort, onSort) {
4956
5068
  cellProps,
4957
5069
  className,
4958
5070
  headerClassName,
4959
- render
5071
+ render,
5072
+ copyValue
4960
5073
  } = props;
4961
5074
  return {
4962
5075
  id: field,
@@ -4990,6 +5103,7 @@ function buildColumnDef(props, sort, onSort) {
4990
5103
  kind,
4991
5104
  cellProps,
4992
5105
  cellRender: render,
5106
+ copyValue,
4993
5107
  frozen,
4994
5108
  reorderable,
4995
5109
  width,
@@ -177,6 +177,14 @@ declare function resolveDataTableLabels(partial?: Partial<DataTableLabels>): Dat
177
177
  type RowSelectionMode = "none" | "single" | "multi";
178
178
  type CellEditType = "text" | "number";
179
179
  type DataTableEditInputProps = Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "value" | "defaultValue">;
180
+ /**
181
+ * Per-column clipboard serialization.
182
+ * - `"display"`: text extracted from the rendered cell
183
+ * - `"value"`: raw accessor / field value
184
+ * - `"omit"`: exclude this column from clipboard TSV entirely
185
+ * - function: custom clipboard string
186
+ */
187
+ type ColumnCopyValue<T extends Record<string, unknown> = Record<string, unknown>, V = unknown> = "display" | "value" | "omit" | ((ctx: CellRenderContext<T, V>) => string);
180
188
 
181
189
  declare module "@tanstack/react-table" {
182
190
  interface ColumnMeta<TData, TValue> {
@@ -209,6 +217,14 @@ declare module "@tanstack/react-table" {
209
217
  cellProps?: Record<string, unknown>;
210
218
  /** Compound `Column.render` stored for `ResolvedTableCell` */
211
219
  cellRender?: CellRenderFn<Record<string, unknown>>;
220
+ /**
221
+ * Clipboard serialization for this column.
222
+ * - `"display"`: rendered cell text (default when `render` / `kind` is set)
223
+ * - `"value"`: raw accessor / field value
224
+ * - `"omit"`: exclude this column from clipboard TSV entirely
225
+ * - function: custom string for the clipboard
226
+ */
227
+ copyValue?: ColumnCopyValue;
212
228
  /**
213
229
  * Freeze (sticky) this column without reordering.
214
230
  * `true` / `"left"` stick to the scrollport left; `"right"` to the right.
@@ -623,6 +639,14 @@ type TableColumnProps<T extends Record<string, unknown>, K extends string = keyo
623
639
  * Use `update` to commit via `onCellChange` / `onDataChange`.
624
640
  */
625
641
  render?: CellRenderFn<T, K extends keyof T ? T[K] : unknown>;
642
+ /**
643
+ * Clipboard serialization for this column.
644
+ * - `"display"`: rendered cell text (default when `render` / `kind` is set)
645
+ * - `"value"`: raw accessor / field value (useful for button cells that still wrap real data)
646
+ * - `"omit"`: exclude this column from clipboard TSV (shifts neighbors; prefer empty string for in-table paste)
647
+ * - function: custom string for the clipboard
648
+ */
649
+ copyValue?: ColumnCopyValue<T, K extends keyof T ? T[K] : unknown>;
626
650
  };
627
651
  /** Declares a multi-row header group wrapping leaf `Table.Column`s. */
628
652
  type TableColumnGroupProps = {
@@ -638,4 +662,4 @@ type TableProps<T extends Record<string, unknown>> = Omit<DataTableProps<T>, "co
638
662
  children: ReactNode;
639
663
  };
640
664
 
641
- export { collectSearchMatchesInRange as A, type BuiltinCellKind as B, type CellKind as C, DEFAULT_DATA_TABLE_LABELS as D, createSearchRegex as E, escapeSearchRegex as F, formatSearchResultLabel as G, getColumnFreezeEdgeAttr as H, INLINE_SEARCH_MAX_RESULTS as I, getColumnFreezeStyle as J, mapSearchResultToVisibleItem as K, mapSearchResultsToVisibleKeys as L, nextSearchIndex as M, nextSearchStride as N, previousSearchIndex as O, type PasteMode as P, resolveColumnFreezeSide as Q, type RowSelectionMode as R, type SearchCorpusRow as S, type TableColumnGroupProps as T, resolveDataTableLabels as U, resolveHeaderFreezeOffset as V, type CellEditType as W, type CellRenderContext as a, type CellRenderFn as b, type CellRenderer as c, type ColumnFreezeColumnInput as d, type ColumnFreezeEdgeSide as e, type ColumnFreezeMeta as f, type ColumnFreezeOffset as g, type ColumnFreezeSide as h, type DataTableClassNames as i, type DataTableCopyActions as j, type DataTableLabels as k, type DataTableProps as l, type DataTableScrollSlotProps as m, type DataTableSlots as n, type RowsPastePayload as o, type SearchResultItem as p, type SearchStatus as q, type TableColumnProps as r, type TableProps as s, buildColumnFreezeOffsets as t, buildFlatSearchCorpus as u, buildSearchMatchKey as v, buildSearchMatchKeys as w, buildTreeSearchCorpus as x, cellValueToSearchText as y, collectAncestorKeysToExpand as z };
665
+ export { collectAncestorKeysToExpand as A, type BuiltinCellKind as B, type CellKind as C, DEFAULT_DATA_TABLE_LABELS as D, collectSearchMatchesInRange as E, createSearchRegex as F, escapeSearchRegex as G, formatSearchResultLabel as H, INLINE_SEARCH_MAX_RESULTS as I, getColumnFreezeEdgeAttr as J, getColumnFreezeStyle as K, mapSearchResultToVisibleItem as L, mapSearchResultsToVisibleKeys as M, nextSearchIndex as N, nextSearchStride as O, type PasteMode as P, previousSearchIndex as Q, type RowSelectionMode as R, type SearchCorpusRow as S, type TableColumnGroupProps as T, resolveColumnFreezeSide as U, resolveDataTableLabels as V, resolveHeaderFreezeOffset as W, type CellEditType as X, type CellRenderContext as a, type CellRenderFn as b, type CellRenderer as c, type ColumnCopyValue as d, type ColumnFreezeColumnInput as e, type ColumnFreezeEdgeSide as f, type ColumnFreezeMeta as g, type ColumnFreezeOffset as h, type ColumnFreezeSide as i, type DataTableClassNames as j, type DataTableCopyActions as k, type DataTableLabels as l, type DataTableProps as m, type DataTableScrollSlotProps as n, type DataTableSlots as o, type RowsPastePayload as p, type SearchResultItem as q, type SearchStatus as r, type TableColumnProps as s, type TableProps as t, buildColumnFreezeOffsets as u, buildFlatSearchCorpus as v, buildSearchMatchKey as w, buildSearchMatchKeys as x, buildTreeSearchCorpus as y, cellValueToSearchText as z };
@@ -177,6 +177,14 @@ declare function resolveDataTableLabels(partial?: Partial<DataTableLabels>): Dat
177
177
  type RowSelectionMode = "none" | "single" | "multi";
178
178
  type CellEditType = "text" | "number";
179
179
  type DataTableEditInputProps = Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "value" | "defaultValue">;
180
+ /**
181
+ * Per-column clipboard serialization.
182
+ * - `"display"`: text extracted from the rendered cell
183
+ * - `"value"`: raw accessor / field value
184
+ * - `"omit"`: exclude this column from clipboard TSV entirely
185
+ * - function: custom clipboard string
186
+ */
187
+ type ColumnCopyValue<T extends Record<string, unknown> = Record<string, unknown>, V = unknown> = "display" | "value" | "omit" | ((ctx: CellRenderContext<T, V>) => string);
180
188
 
181
189
  declare module "@tanstack/react-table" {
182
190
  interface ColumnMeta<TData, TValue> {
@@ -209,6 +217,14 @@ declare module "@tanstack/react-table" {
209
217
  cellProps?: Record<string, unknown>;
210
218
  /** Compound `Column.render` stored for `ResolvedTableCell` */
211
219
  cellRender?: CellRenderFn<Record<string, unknown>>;
220
+ /**
221
+ * Clipboard serialization for this column.
222
+ * - `"display"`: rendered cell text (default when `render` / `kind` is set)
223
+ * - `"value"`: raw accessor / field value
224
+ * - `"omit"`: exclude this column from clipboard TSV entirely
225
+ * - function: custom string for the clipboard
226
+ */
227
+ copyValue?: ColumnCopyValue;
212
228
  /**
213
229
  * Freeze (sticky) this column without reordering.
214
230
  * `true` / `"left"` stick to the scrollport left; `"right"` to the right.
@@ -623,6 +639,14 @@ type TableColumnProps<T extends Record<string, unknown>, K extends string = keyo
623
639
  * Use `update` to commit via `onCellChange` / `onDataChange`.
624
640
  */
625
641
  render?: CellRenderFn<T, K extends keyof T ? T[K] : unknown>;
642
+ /**
643
+ * Clipboard serialization for this column.
644
+ * - `"display"`: rendered cell text (default when `render` / `kind` is set)
645
+ * - `"value"`: raw accessor / field value (useful for button cells that still wrap real data)
646
+ * - `"omit"`: exclude this column from clipboard TSV (shifts neighbors; prefer empty string for in-table paste)
647
+ * - function: custom string for the clipboard
648
+ */
649
+ copyValue?: ColumnCopyValue<T, K extends keyof T ? T[K] : unknown>;
626
650
  };
627
651
  /** Declares a multi-row header group wrapping leaf `Table.Column`s. */
628
652
  type TableColumnGroupProps = {
@@ -638,4 +662,4 @@ type TableProps<T extends Record<string, unknown>> = Omit<DataTableProps<T>, "co
638
662
  children: ReactNode;
639
663
  };
640
664
 
641
- export { collectSearchMatchesInRange as A, type BuiltinCellKind as B, type CellKind as C, DEFAULT_DATA_TABLE_LABELS as D, createSearchRegex as E, escapeSearchRegex as F, formatSearchResultLabel as G, getColumnFreezeEdgeAttr as H, INLINE_SEARCH_MAX_RESULTS as I, getColumnFreezeStyle as J, mapSearchResultToVisibleItem as K, mapSearchResultsToVisibleKeys as L, nextSearchIndex as M, nextSearchStride as N, previousSearchIndex as O, type PasteMode as P, resolveColumnFreezeSide as Q, type RowSelectionMode as R, type SearchCorpusRow as S, type TableColumnGroupProps as T, resolveDataTableLabels as U, resolveHeaderFreezeOffset as V, type CellEditType as W, type CellRenderContext as a, type CellRenderFn as b, type CellRenderer as c, type ColumnFreezeColumnInput as d, type ColumnFreezeEdgeSide as e, type ColumnFreezeMeta as f, type ColumnFreezeOffset as g, type ColumnFreezeSide as h, type DataTableClassNames as i, type DataTableCopyActions as j, type DataTableLabels as k, type DataTableProps as l, type DataTableScrollSlotProps as m, type DataTableSlots as n, type RowsPastePayload as o, type SearchResultItem as p, type SearchStatus as q, type TableColumnProps as r, type TableProps as s, buildColumnFreezeOffsets as t, buildFlatSearchCorpus as u, buildSearchMatchKey as v, buildSearchMatchKeys as w, buildTreeSearchCorpus as x, cellValueToSearchText as y, collectAncestorKeysToExpand as z };
665
+ export { collectAncestorKeysToExpand as A, type BuiltinCellKind as B, type CellKind as C, DEFAULT_DATA_TABLE_LABELS as D, collectSearchMatchesInRange as E, createSearchRegex as F, escapeSearchRegex as G, formatSearchResultLabel as H, INLINE_SEARCH_MAX_RESULTS as I, getColumnFreezeEdgeAttr as J, getColumnFreezeStyle as K, mapSearchResultToVisibleItem as L, mapSearchResultsToVisibleKeys as M, nextSearchIndex as N, nextSearchStride as O, type PasteMode as P, previousSearchIndex as Q, type RowSelectionMode as R, type SearchCorpusRow as S, type TableColumnGroupProps as T, resolveColumnFreezeSide as U, resolveDataTableLabels as V, resolveHeaderFreezeOffset as W, type CellEditType as X, type CellRenderContext as a, type CellRenderFn as b, type CellRenderer as c, type ColumnCopyValue as d, type ColumnFreezeColumnInput as e, type ColumnFreezeEdgeSide as f, type ColumnFreezeMeta as g, type ColumnFreezeOffset as h, type ColumnFreezeSide as i, type DataTableClassNames as j, type DataTableCopyActions as k, type DataTableLabels as l, type DataTableProps as m, type DataTableScrollSlotProps as n, type DataTableSlots as o, type RowsPastePayload as p, type SearchResultItem as q, type SearchStatus as r, type TableColumnProps as s, type TableProps as t, buildColumnFreezeOffsets as u, buildFlatSearchCorpus as v, buildSearchMatchKey as w, buildSearchMatchKeys as x, buildTreeSearchCorpus as y, cellValueToSearchText as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-glide-table",
3
- "version": "2.3.0",
3
+ "version": "2.3.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/zpxlffjrm/react-glide-table.git"