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/core.js CHANGED
@@ -432,9 +432,126 @@ function withCellUpdate(context, commitValue) {
432
432
  };
433
433
  }
434
434
 
435
+ // src/components/ui/table/features/cell-selection/pasteData.ts
436
+ function countLeadingEmptyCells(cells) {
437
+ let depth = 0;
438
+ while (depth < cells.length && cells[depth] === "") {
439
+ depth += 1;
440
+ }
441
+ return depth;
442
+ }
443
+ function looksLikeSubtreeIndentation(leadingEmptyCounts) {
444
+ if (leadingEmptyCounts.length === 0) return false;
445
+ const firstDepth = leadingEmptyCounts[0] ?? 0;
446
+ if (firstDepth !== 0) return false;
447
+ return leadingEmptyCounts.some((depth) => depth > 0);
448
+ }
449
+ function parseClipboardTSV(text) {
450
+ return parseClipboardTSVWithDepths(text).values;
451
+ }
452
+ function parseClipboardTSVWithDepths(text) {
453
+ if (!text) return { values: [], depths: [] };
454
+ const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
455
+ const withoutTrailing = normalized.replace(/\n+$/, "");
456
+ if (!withoutTrailing) return { values: [], depths: [] };
457
+ const rows = withoutTrailing.split("\n").map((line) => line.split(" "));
458
+ const leadingEmptyCounts = rows.map(countLeadingEmptyCells);
459
+ const treatAsDepth = looksLikeSubtreeIndentation(leadingEmptyCounts);
460
+ const values = [];
461
+ const depths = [];
462
+ for (let index = 0; index < rows.length; index += 1) {
463
+ const cells = rows[index] ?? [];
464
+ const depth = leadingEmptyCounts[index] ?? 0;
465
+ if (treatAsDepth) {
466
+ values.push(cells.slice(depth));
467
+ depths.push(depth);
468
+ } else {
469
+ values.push(cells);
470
+ depths.push(0);
471
+ }
472
+ }
473
+ return { values, depths };
474
+ }
475
+ function resolvePasteColumnIds(rows, startCol, width) {
476
+ if (width <= 0) return [];
477
+ const cells = rows[0]?.getVisibleCells() ?? [];
478
+ const columnIds = [];
479
+ for (let offset = 0; offset < width; offset += 1) {
480
+ const cell = cells[startCol + offset];
481
+ if (!cell) break;
482
+ columnIds.push(cell.column.id);
483
+ }
484
+ return columnIds;
485
+ }
486
+ function buildRowsPastePayload(rows, startRow, startCol, text, mode, endRow = startRow) {
487
+ const { values, depths } = parseClipboardTSVWithDepths(text);
488
+ if (values.length === 0) return null;
489
+ const width = Math.max(...values.map((row) => row.length), 0);
490
+ if (width === 0) return null;
491
+ const columnIds = resolvePasteColumnIds(rows, startCol, width);
492
+ if (columnIds.length === 0) return null;
493
+ const rowIds = [];
494
+ for (let offset = 0; offset < values.length; offset += 1) {
495
+ const row = rows[startRow + offset];
496
+ if (!row) break;
497
+ rowIds.push(row.id);
498
+ }
499
+ const anchorRow = rows[endRow] ?? rows[startRow];
500
+ return {
501
+ mode,
502
+ startRow,
503
+ startCol,
504
+ endRow,
505
+ rowIds,
506
+ anchorRowId: anchorRow?.id ?? "",
507
+ columnIds,
508
+ values,
509
+ depths
510
+ };
511
+ }
512
+ function isEditablePasteTarget(target) {
513
+ if (!(target instanceof HTMLElement)) return false;
514
+ const tag = target.tagName;
515
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
516
+ return Boolean(target.isContentEditable);
517
+ }
518
+
435
519
  // src/components/ui/table/features/cell-selection/useCellSelection.ts
436
520
  import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
437
521
 
522
+ // src/components/ui/table/features/cell-selection/activeCellSelectionOwner.ts
523
+ var activeOwner = null;
524
+ var clearByOwner = /* @__PURE__ */ new Map();
525
+ function createCellSelectionOwner() {
526
+ return /* @__PURE__ */ Symbol("cell-selection-owner");
527
+ }
528
+ function registerCellSelectionOwner(owner, clearSelection) {
529
+ clearByOwner.set(owner, clearSelection);
530
+ return () => {
531
+ clearByOwner.delete(owner);
532
+ if (activeOwner === owner) {
533
+ activeOwner = null;
534
+ }
535
+ };
536
+ }
537
+ function claimCellSelectionOwner(owner) {
538
+ if (activeOwner === owner) return;
539
+ activeOwner = owner;
540
+ for (const [id, clearSelection] of clearByOwner) {
541
+ if (id !== owner) {
542
+ clearSelection();
543
+ }
544
+ }
545
+ }
546
+ function isActiveCellSelectionOwner(owner) {
547
+ return activeOwner === owner;
548
+ }
549
+ function releaseCellSelectionOwner(owner) {
550
+ if (activeOwner === owner) {
551
+ activeOwner = null;
552
+ }
553
+ }
554
+
438
555
  // src/components/ui/table/features/cell-selection/cellSelection.ts
439
556
  var INITIAL_DRAG_STATE = {
440
557
  isSelecting: false,
@@ -912,19 +1029,31 @@ function extractRenderedCopyText(node, value, cellPosition, root) {
912
1029
  function formatCopyCellText(rowData, columnDef, columnId, visibleRow, fallbackIndex, sourceCell, cellPosition, options) {
913
1030
  const meta = columnDef.meta;
914
1031
  const value = sourceCell ? sourceCell.getValue() : readRowColumnValue(rowData, columnDef);
1032
+ const row = visibleRow ?? createCopyRenderRow(rowData, fallbackIndex);
1033
+ const ctx = {
1034
+ value,
1035
+ row,
1036
+ index: row.index,
1037
+ columnId,
1038
+ cellProps: meta?.cellProps,
1039
+ update: () => {
1040
+ }
1041
+ };
1042
+ const copyValue = meta?.copyValue;
1043
+ if (typeof copyValue === "function") {
1044
+ try {
1045
+ return sanitizeClipboardCell(copyValue(ctx));
1046
+ } catch {
1047
+ return formatCellValue(value);
1048
+ }
1049
+ }
1050
+ if (copyValue === "value") {
1051
+ return formatCellValue(value);
1052
+ }
915
1053
  const cellRender = meta?.cellRender;
916
1054
  if (typeof cellRender === "function") {
917
1055
  try {
918
- const row = visibleRow ?? createCopyRenderRow(rowData, fallbackIndex);
919
- const node = cellRender({
920
- value,
921
- row,
922
- index: row.index,
923
- columnId,
924
- cellProps: meta?.cellProps,
925
- update: () => {
926
- }
927
- });
1056
+ const node = cellRender(ctx);
928
1057
  return extractRenderedCopyText(node, value, cellPosition, options?.root);
929
1058
  } catch {
930
1059
  return formatCellValue(value);
@@ -932,16 +1061,6 @@ function formatCopyCellText(rowData, columnDef, columnId, visibleRow, fallbackIn
932
1061
  }
933
1062
  if (options?.registry && meta?.kind && isPrimitiveCopyValue(value)) {
934
1063
  try {
935
- const row = visibleRow ?? createCopyRenderRow(rowData, fallbackIndex);
936
- const ctx = {
937
- value,
938
- row,
939
- index: row.index,
940
- columnId,
941
- cellProps: meta.cellProps,
942
- update: () => {
943
- }
944
- };
945
1064
  const renderer = resolveCellRenderer(options.registry, meta.kind, ctx);
946
1065
  if (renderer) {
947
1066
  const node = renderer.render(ctx);
@@ -1067,11 +1186,16 @@ function serializeCopyRowsToTSV(copyRows, visibleRows, bounds, depths, options)
1067
1186
  const resolvedDepths = depths && depths.length === copyRows.length ? depths : copyRows.map((row) => getRowDepth(row));
1068
1187
  const minDepth = Math.min(...resolvedDepths);
1069
1188
  const visibleRowByOriginal = buildVisibleRowLookup(visibleRows);
1189
+ const copyableColumns = columnCells.flatMap((templateCell, colOffset) => {
1190
+ const meta = templateCell.column.columnDef.meta;
1191
+ if (meta?.copyValue === "omit") return [];
1192
+ return [{ templateCell, colOffset }];
1193
+ });
1070
1194
  return copyRows.map((rowData, index) => {
1071
1195
  const relativeDepth = Math.max(0, (resolvedDepths[index] ?? 0) - minDepth);
1072
1196
  const visibleRow = visibleRowByOriginal.get(rowData);
1073
1197
  const matchingCells = visibleRow?.getVisibleCells().slice(startCol, endCol + 1);
1074
- const line = columnCells.map((templateCell, colOffset) => {
1198
+ const line = copyableColumns.map(({ templateCell, colOffset }) => {
1075
1199
  const sourceCell = matchingCells?.[colOffset];
1076
1200
  const column = sourceCell?.column ?? templateCell.column;
1077
1201
  return formatCopyCellText(
@@ -1165,90 +1289,6 @@ function hasFillExtension(sourceBounds, fillBounds) {
1165
1289
  return fillBounds.startRow < sourceBounds.startRow || fillBounds.endRow > sourceBounds.endRow || fillBounds.startCol < sourceBounds.startCol || fillBounds.endCol > sourceBounds.endCol;
1166
1290
  }
1167
1291
 
1168
- // src/components/ui/table/features/cell-selection/pasteData.ts
1169
- function countLeadingEmptyCells(cells) {
1170
- let depth = 0;
1171
- while (depth < cells.length && cells[depth] === "") {
1172
- depth += 1;
1173
- }
1174
- return depth;
1175
- }
1176
- function looksLikeSubtreeIndentation(leadingEmptyCounts) {
1177
- if (leadingEmptyCounts.length === 0) return false;
1178
- const firstDepth = leadingEmptyCounts[0] ?? 0;
1179
- if (firstDepth !== 0) return false;
1180
- return leadingEmptyCounts.some((depth) => depth > 0);
1181
- }
1182
- function parseClipboardTSV(text) {
1183
- return parseClipboardTSVWithDepths(text).values;
1184
- }
1185
- function parseClipboardTSVWithDepths(text) {
1186
- if (!text) return { values: [], depths: [] };
1187
- const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
1188
- const withoutTrailing = normalized.replace(/\n+$/, "");
1189
- if (!withoutTrailing) return { values: [], depths: [] };
1190
- const rows = withoutTrailing.split("\n").map((line) => line.split(" "));
1191
- const leadingEmptyCounts = rows.map(countLeadingEmptyCells);
1192
- const treatAsDepth = looksLikeSubtreeIndentation(leadingEmptyCounts);
1193
- const values = [];
1194
- const depths = [];
1195
- for (let index = 0; index < rows.length; index += 1) {
1196
- const cells = rows[index] ?? [];
1197
- const depth = leadingEmptyCounts[index] ?? 0;
1198
- if (treatAsDepth) {
1199
- values.push(cells.slice(depth));
1200
- depths.push(depth);
1201
- } else {
1202
- values.push(cells);
1203
- depths.push(0);
1204
- }
1205
- }
1206
- return { values, depths };
1207
- }
1208
- function resolvePasteColumnIds(rows, startCol, width) {
1209
- if (width <= 0) return [];
1210
- const cells = rows[0]?.getVisibleCells() ?? [];
1211
- const columnIds = [];
1212
- for (let offset = 0; offset < width; offset += 1) {
1213
- const cell = cells[startCol + offset];
1214
- if (!cell) break;
1215
- columnIds.push(cell.column.id);
1216
- }
1217
- return columnIds;
1218
- }
1219
- function buildRowsPastePayload(rows, startRow, startCol, text, mode, endRow = startRow) {
1220
- const { values, depths } = parseClipboardTSVWithDepths(text);
1221
- if (values.length === 0) return null;
1222
- const width = Math.max(...values.map((row) => row.length), 0);
1223
- if (width === 0) return null;
1224
- const columnIds = resolvePasteColumnIds(rows, startCol, width);
1225
- if (columnIds.length === 0) return null;
1226
- const rowIds = [];
1227
- for (let offset = 0; offset < values.length; offset += 1) {
1228
- const row = rows[startRow + offset];
1229
- if (!row) break;
1230
- rowIds.push(row.id);
1231
- }
1232
- const anchorRow = rows[endRow] ?? rows[startRow];
1233
- return {
1234
- mode,
1235
- startRow,
1236
- startCol,
1237
- endRow,
1238
- rowIds,
1239
- anchorRowId: anchorRow?.id ?? "",
1240
- columnIds,
1241
- values,
1242
- depths
1243
- };
1244
- }
1245
- function isEditablePasteTarget(target) {
1246
- if (!(target instanceof HTMLElement)) return false;
1247
- const tag = target.tagName;
1248
- if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
1249
- return Boolean(target.isContentEditable);
1250
- }
1251
-
1252
1292
  // src/components/ui/table/features/cell-selection/useCellSelection.ts
1253
1293
  function useCellSelection({
1254
1294
  data,
@@ -1264,6 +1304,7 @@ function useCellSelection({
1264
1304
  cellRendererRegistry,
1265
1305
  rootRef
1266
1306
  }) {
1307
+ const ownerRef = useRef2(createCellSelectionOwner());
1267
1308
  const [dragState, setDragState] = useState2(INITIAL_DRAG_STATE);
1268
1309
  const pendingPasteModeRef = useRef2(null);
1269
1310
  const dragStateRef = useRef2(dragState);
@@ -1275,6 +1316,7 @@ function useCellSelection({
1275
1316
  const handleCellMouseDown = useCallback2(
1276
1317
  (rowIndex, colIndex, options) => {
1277
1318
  if (!enabled) return;
1319
+ claimCellSelectionOwner(ownerRef.current);
1278
1320
  setDragState((prev) => {
1279
1321
  if (options?.shiftKey && prev.start) {
1280
1322
  return {
@@ -1316,6 +1358,7 @@ function useCellSelection({
1316
1358
  const handleFillHandleMouseDown = useCallback2(
1317
1359
  (rowIndex, colIndex) => {
1318
1360
  if (!enabled) return;
1361
+ claimCellSelectionOwner(ownerRef.current);
1319
1362
  setDragState((prev) => {
1320
1363
  const bounds = getCellSelectionBounds(prev.start, prev.end);
1321
1364
  if (!bounds) return prev;
@@ -1330,14 +1373,27 @@ function useCellSelection({
1330
1373
  },
1331
1374
  [enabled]
1332
1375
  );
1376
+ const clearSelection = useCallback2(() => {
1377
+ const prev = dragStateRef.current;
1378
+ if (prev.start === null && prev.end === null && !prev.isSelecting && !prev.isFillDragging) {
1379
+ return;
1380
+ }
1381
+ releaseCellSelectionOwner(ownerRef.current);
1382
+ dragStateRef.current = INITIAL_DRAG_STATE;
1383
+ setDragState(INITIAL_DRAG_STATE);
1384
+ }, []);
1333
1385
  useEffect2(() => {
1334
1386
  if (!enabled) {
1335
- setDragState(INITIAL_DRAG_STATE);
1387
+ clearSelection();
1336
1388
  }
1337
- }, [enabled]);
1389
+ }, [clearSelection, enabled]);
1390
+ useEffect2(() => {
1391
+ return registerCellSelectionOwner(ownerRef.current, clearSelection);
1392
+ }, [clearSelection]);
1338
1393
  useEffect2(() => {
1339
1394
  if (!enabled) return;
1340
1395
  const handleKeyDown = (e) => {
1396
+ if (!isActiveCellSelectionOwner(ownerRef.current)) return;
1341
1397
  if (e.ctrlKey || e.metaKey || e.altKey) return;
1342
1398
  if (isEditablePasteTarget(e.target) || isEditablePasteTarget(document.activeElement)) {
1343
1399
  return;
@@ -1403,6 +1459,7 @@ function useCellSelection({
1403
1459
  useEffect2(() => {
1404
1460
  if (!enabled) return;
1405
1461
  const handleKeyDown = (e) => {
1462
+ if (!isActiveCellSelectionOwner(ownerRef.current)) return;
1406
1463
  if (!activeSelectionBounds) return;
1407
1464
  if (!(e.ctrlKey || e.metaKey)) return;
1408
1465
  if (isEditablePasteTarget(e.target) || isEditablePasteTarget(document.activeElement)) {
@@ -1439,6 +1496,7 @@ function useCellSelection({
1439
1496
  const pasteHandledRef = { current: false };
1440
1497
  const ignoreNextPasteRef = { current: false };
1441
1498
  const handleKeyDown = (e) => {
1499
+ if (!isActiveCellSelectionOwner(ownerRef.current)) return;
1442
1500
  if (!activeSelectionBounds) return;
1443
1501
  if (!(e.ctrlKey || e.metaKey)) return;
1444
1502
  if (e.key.toLowerCase() !== "v") return;
@@ -1459,6 +1517,7 @@ function useCellSelection({
1459
1517
  const text = await navigator.clipboard.readText();
1460
1518
  if (pasteHandledRef.current) return;
1461
1519
  if (pendingPasteModeRef.current !== mode) return;
1520
+ if (!isActiveCellSelectionOwner(ownerRef.current)) return;
1462
1521
  if (!text) return;
1463
1522
  pasteHandledRef.current = true;
1464
1523
  emitRowsPaste(text, mode);
@@ -1468,6 +1527,7 @@ function useCellSelection({
1468
1527
  })();
1469
1528
  };
1470
1529
  const handlePaste = (e) => {
1530
+ if (!isActiveCellSelectionOwner(ownerRef.current)) return;
1471
1531
  if (!activeSelectionBounds) return;
1472
1532
  if (isEditablePasteTarget(e.target) || isEditablePasteTarget(document.activeElement)) {
1473
1533
  return;
@@ -1548,10 +1608,30 @@ function useCellSelection({
1548
1608
  handleCellMouseDown,
1549
1609
  handleCellMouseEnter,
1550
1610
  handleFillHandleMouseDown,
1611
+ clearSelection,
1551
1612
  copySelection
1552
1613
  };
1553
1614
  }
1554
1615
 
1616
+ // src/components/ui/table/features/selection-dismiss/isOutsideDismissTarget.ts
1617
+ var OVERLAY_DISMISS_IGNORE_SELECTOR = [
1618
+ '[role="dialog"]',
1619
+ '[role="alertdialog"]',
1620
+ '[role="menu"]',
1621
+ '[role="listbox"]',
1622
+ '[role="tooltip"]',
1623
+ '[aria-modal="true"]',
1624
+ "[data-radix-portal]",
1625
+ "[data-radix-popper-content-wrapper]",
1626
+ "[data-floating-ui-portal]",
1627
+ "[data-table-ignore-outside-dismiss]"
1628
+ ].join(",");
1629
+ function isOverlayDismissIgnoreTarget(target) {
1630
+ const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
1631
+ if (!element) return false;
1632
+ return element.closest(OVERLAY_DISMISS_IGNORE_SELECTOR) !== null;
1633
+ }
1634
+
1555
1635
  // src/components/ui/table/features/column-reorder/columnReorder.ts
1556
1636
  function getColumnDefId(column) {
1557
1637
  if (column.id != null && column.id !== "") return column.id;
@@ -2832,6 +2912,7 @@ function useGlideTable(options) {
2832
2912
  handleCellMouseDown,
2833
2913
  handleCellMouseEnter,
2834
2914
  handleFillHandleMouseDown,
2915
+ clearSelection: clearCellSelection,
2835
2916
  copySelection
2836
2917
  } = useCellSelection({
2837
2918
  data: tableData,
@@ -2847,6 +2928,37 @@ function useGlideTable(options) {
2847
2928
  cellRendererRegistry,
2848
2929
  rootRef
2849
2930
  });
2931
+ const clearRowSelection = useCallback4(() => {
2932
+ if (rowSelectionMode === "none") return;
2933
+ const hasSelection = Object.values(rowSelection).some(Boolean);
2934
+ if (!hasSelection) return;
2935
+ if (onRowSelectionChange) {
2936
+ onRowSelectionChange(() => ({}));
2937
+ return;
2938
+ }
2939
+ setInternalRowSelection({});
2940
+ }, [onRowSelectionChange, rowSelection, rowSelectionMode]);
2941
+ useEffect5(() => {
2942
+ const clearAllSelections = () => {
2943
+ clearCellSelection();
2944
+ clearRowSelection();
2945
+ };
2946
+ const handleKeyDown = (event) => {
2947
+ if (event.key !== "Escape") return;
2948
+ if (event.defaultPrevented) return;
2949
+ if (isEditablePasteTarget(event.target) || isEditablePasteTarget(document.activeElement)) {
2950
+ return;
2951
+ }
2952
+ if (isOverlayDismissIgnoreTarget(event.target) || isOverlayDismissIgnoreTarget(document.activeElement)) {
2953
+ return;
2954
+ }
2955
+ clearAllSelections();
2956
+ };
2957
+ window.addEventListener("keydown", handleKeyDown);
2958
+ return () => {
2959
+ window.removeEventListener("keydown", handleKeyDown);
2960
+ };
2961
+ }, [clearCellSelection, clearRowSelection]);
2850
2962
  const {
2851
2963
  editingCell,
2852
2964
  draftValue,