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/compound.js CHANGED
@@ -2646,9 +2646,123 @@ function formatDefaultCellValue(value) {
2646
2646
  return String(value);
2647
2647
  }
2648
2648
 
2649
+ // src/components/ui/table/features/cell-selection/pasteData.ts
2650
+ function countLeadingEmptyCells(cells) {
2651
+ let depth = 0;
2652
+ while (depth < cells.length && cells[depth] === "") {
2653
+ depth += 1;
2654
+ }
2655
+ return depth;
2656
+ }
2657
+ function looksLikeSubtreeIndentation(leadingEmptyCounts) {
2658
+ if (leadingEmptyCounts.length === 0) return false;
2659
+ const firstDepth = leadingEmptyCounts[0] ?? 0;
2660
+ if (firstDepth !== 0) return false;
2661
+ return leadingEmptyCounts.some((depth) => depth > 0);
2662
+ }
2663
+ function parseClipboardTSVWithDepths(text) {
2664
+ if (!text) return { values: [], depths: [] };
2665
+ const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
2666
+ const withoutTrailing = normalized.replace(/\n+$/, "");
2667
+ if (!withoutTrailing) return { values: [], depths: [] };
2668
+ const rows = withoutTrailing.split("\n").map((line) => line.split(" "));
2669
+ const leadingEmptyCounts = rows.map(countLeadingEmptyCells);
2670
+ const treatAsDepth = looksLikeSubtreeIndentation(leadingEmptyCounts);
2671
+ const values = [];
2672
+ const depths = [];
2673
+ for (let index = 0; index < rows.length; index += 1) {
2674
+ const cells = rows[index] ?? [];
2675
+ const depth = leadingEmptyCounts[index] ?? 0;
2676
+ if (treatAsDepth) {
2677
+ values.push(cells.slice(depth));
2678
+ depths.push(depth);
2679
+ } else {
2680
+ values.push(cells);
2681
+ depths.push(0);
2682
+ }
2683
+ }
2684
+ return { values, depths };
2685
+ }
2686
+ function resolvePasteColumnIds(rows, startCol, width) {
2687
+ if (width <= 0) return [];
2688
+ const cells = rows[0]?.getVisibleCells() ?? [];
2689
+ const columnIds = [];
2690
+ for (let offset = 0; offset < width; offset += 1) {
2691
+ const cell = cells[startCol + offset];
2692
+ if (!cell) break;
2693
+ columnIds.push(cell.column.id);
2694
+ }
2695
+ return columnIds;
2696
+ }
2697
+ function buildRowsPastePayload(rows, startRow, startCol, text, mode, endRow = startRow) {
2698
+ const { values, depths } = parseClipboardTSVWithDepths(text);
2699
+ if (values.length === 0) return null;
2700
+ const width = Math.max(...values.map((row) => row.length), 0);
2701
+ if (width === 0) return null;
2702
+ const columnIds = resolvePasteColumnIds(rows, startCol, width);
2703
+ if (columnIds.length === 0) return null;
2704
+ const rowIds = [];
2705
+ for (let offset = 0; offset < values.length; offset += 1) {
2706
+ const row = rows[startRow + offset];
2707
+ if (!row) break;
2708
+ rowIds.push(row.id);
2709
+ }
2710
+ const anchorRow = rows[endRow] ?? rows[startRow];
2711
+ return {
2712
+ mode,
2713
+ startRow,
2714
+ startCol,
2715
+ endRow,
2716
+ rowIds,
2717
+ anchorRowId: anchorRow?.id ?? "",
2718
+ columnIds,
2719
+ values,
2720
+ depths
2721
+ };
2722
+ }
2723
+ function isEditablePasteTarget(target) {
2724
+ if (!(target instanceof HTMLElement)) return false;
2725
+ const tag = target.tagName;
2726
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
2727
+ return Boolean(target.isContentEditable);
2728
+ }
2729
+
2649
2730
  // src/components/ui/table/features/cell-selection/useCellSelection.ts
2650
2731
  import { useCallback as useCallback3, useEffect as useEffect5, useRef as useRef5, useState as useState3 } from "react";
2651
2732
 
2733
+ // src/components/ui/table/features/cell-selection/activeCellSelectionOwner.ts
2734
+ var activeOwner = null;
2735
+ var clearByOwner = /* @__PURE__ */ new Map();
2736
+ function createCellSelectionOwner() {
2737
+ return /* @__PURE__ */ Symbol("cell-selection-owner");
2738
+ }
2739
+ function registerCellSelectionOwner(owner, clearSelection) {
2740
+ clearByOwner.set(owner, clearSelection);
2741
+ return () => {
2742
+ clearByOwner.delete(owner);
2743
+ if (activeOwner === owner) {
2744
+ activeOwner = null;
2745
+ }
2746
+ };
2747
+ }
2748
+ function claimCellSelectionOwner(owner) {
2749
+ if (activeOwner === owner) return;
2750
+ activeOwner = owner;
2751
+ for (const [id, clearSelection] of clearByOwner) {
2752
+ if (id !== owner) {
2753
+ clearSelection();
2754
+ }
2755
+ }
2756
+ }
2757
+ function isActiveCellSelectionOwner(owner) {
2758
+ return activeOwner === owner;
2759
+ }
2760
+ function releaseCellSelectionOwner(owner) {
2761
+ if (activeOwner === owner) {
2762
+ activeOwner = null;
2763
+ }
2764
+ }
2765
+
2652
2766
  // src/components/ui/table/features/cell-selection/copyData.ts
2653
2767
  import { isValidElement } from "react";
2654
2768
  function isReactNodeIterable(node) {
@@ -2821,19 +2935,31 @@ function extractRenderedCopyText(node, value, cellPosition, root) {
2821
2935
  function formatCopyCellText(rowData, columnDef, columnId, visibleRow, fallbackIndex, sourceCell, cellPosition, options) {
2822
2936
  const meta = columnDef.meta;
2823
2937
  const value = sourceCell ? sourceCell.getValue() : readRowColumnValue(rowData, columnDef);
2938
+ const row = visibleRow ?? createCopyRenderRow(rowData, fallbackIndex);
2939
+ const ctx = {
2940
+ value,
2941
+ row,
2942
+ index: row.index,
2943
+ columnId,
2944
+ cellProps: meta?.cellProps,
2945
+ update: () => {
2946
+ }
2947
+ };
2948
+ const copyValue = meta?.copyValue;
2949
+ if (typeof copyValue === "function") {
2950
+ try {
2951
+ return sanitizeClipboardCell(copyValue(ctx));
2952
+ } catch {
2953
+ return formatCellValue(value);
2954
+ }
2955
+ }
2956
+ if (copyValue === "value") {
2957
+ return formatCellValue(value);
2958
+ }
2824
2959
  const cellRender = meta?.cellRender;
2825
2960
  if (typeof cellRender === "function") {
2826
2961
  try {
2827
- const row = visibleRow ?? createCopyRenderRow(rowData, fallbackIndex);
2828
- const node = cellRender({
2829
- value,
2830
- row,
2831
- index: row.index,
2832
- columnId,
2833
- cellProps: meta?.cellProps,
2834
- update: () => {
2835
- }
2836
- });
2962
+ const node = cellRender(ctx);
2837
2963
  return extractRenderedCopyText(node, value, cellPosition, options?.root);
2838
2964
  } catch {
2839
2965
  return formatCellValue(value);
@@ -2841,16 +2967,6 @@ function formatCopyCellText(rowData, columnDef, columnId, visibleRow, fallbackIn
2841
2967
  }
2842
2968
  if (options?.registry && meta?.kind && isPrimitiveCopyValue(value)) {
2843
2969
  try {
2844
- const row = visibleRow ?? createCopyRenderRow(rowData, fallbackIndex);
2845
- const ctx = {
2846
- value,
2847
- row,
2848
- index: row.index,
2849
- columnId,
2850
- cellProps: meta.cellProps,
2851
- update: () => {
2852
- }
2853
- };
2854
2970
  const renderer = resolveCellRenderer(options.registry, meta.kind, ctx);
2855
2971
  if (renderer) {
2856
2972
  const node = renderer.render(ctx);
@@ -2957,11 +3073,16 @@ function serializeCopyRowsToTSV(copyRows, visibleRows, bounds, depths, options)
2957
3073
  const resolvedDepths = depths && depths.length === copyRows.length ? depths : copyRows.map((row) => getRowDepth(row));
2958
3074
  const minDepth = Math.min(...resolvedDepths);
2959
3075
  const visibleRowByOriginal = buildVisibleRowLookup(visibleRows);
3076
+ const copyableColumns = columnCells.flatMap((templateCell, colOffset) => {
3077
+ const meta = templateCell.column.columnDef.meta;
3078
+ if (meta?.copyValue === "omit") return [];
3079
+ return [{ templateCell, colOffset }];
3080
+ });
2960
3081
  return copyRows.map((rowData, index) => {
2961
3082
  const relativeDepth = Math.max(0, (resolvedDepths[index] ?? 0) - minDepth);
2962
3083
  const visibleRow = visibleRowByOriginal.get(rowData);
2963
3084
  const matchingCells = visibleRow?.getVisibleCells().slice(startCol, endCol + 1);
2964
- const line = columnCells.map((templateCell, colOffset) => {
3085
+ const line = copyableColumns.map(({ templateCell, colOffset }) => {
2965
3086
  const sourceCell = matchingCells?.[colOffset];
2966
3087
  const column = sourceCell?.column ?? templateCell.column;
2967
3088
  return formatCopyCellText(
@@ -3055,87 +3176,6 @@ function hasFillExtension(sourceBounds, fillBounds) {
3055
3176
  return fillBounds.startRow < sourceBounds.startRow || fillBounds.endRow > sourceBounds.endRow || fillBounds.startCol < sourceBounds.startCol || fillBounds.endCol > sourceBounds.endCol;
3056
3177
  }
3057
3178
 
3058
- // src/components/ui/table/features/cell-selection/pasteData.ts
3059
- function countLeadingEmptyCells(cells) {
3060
- let depth = 0;
3061
- while (depth < cells.length && cells[depth] === "") {
3062
- depth += 1;
3063
- }
3064
- return depth;
3065
- }
3066
- function looksLikeSubtreeIndentation(leadingEmptyCounts) {
3067
- if (leadingEmptyCounts.length === 0) return false;
3068
- const firstDepth = leadingEmptyCounts[0] ?? 0;
3069
- if (firstDepth !== 0) return false;
3070
- return leadingEmptyCounts.some((depth) => depth > 0);
3071
- }
3072
- function parseClipboardTSVWithDepths(text) {
3073
- if (!text) return { values: [], depths: [] };
3074
- const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
3075
- const withoutTrailing = normalized.replace(/\n+$/, "");
3076
- if (!withoutTrailing) return { values: [], depths: [] };
3077
- const rows = withoutTrailing.split("\n").map((line) => line.split(" "));
3078
- const leadingEmptyCounts = rows.map(countLeadingEmptyCells);
3079
- const treatAsDepth = looksLikeSubtreeIndentation(leadingEmptyCounts);
3080
- const values = [];
3081
- const depths = [];
3082
- for (let index = 0; index < rows.length; index += 1) {
3083
- const cells = rows[index] ?? [];
3084
- const depth = leadingEmptyCounts[index] ?? 0;
3085
- if (treatAsDepth) {
3086
- values.push(cells.slice(depth));
3087
- depths.push(depth);
3088
- } else {
3089
- values.push(cells);
3090
- depths.push(0);
3091
- }
3092
- }
3093
- return { values, depths };
3094
- }
3095
- function resolvePasteColumnIds(rows, startCol, width) {
3096
- if (width <= 0) return [];
3097
- const cells = rows[0]?.getVisibleCells() ?? [];
3098
- const columnIds = [];
3099
- for (let offset = 0; offset < width; offset += 1) {
3100
- const cell = cells[startCol + offset];
3101
- if (!cell) break;
3102
- columnIds.push(cell.column.id);
3103
- }
3104
- return columnIds;
3105
- }
3106
- function buildRowsPastePayload(rows, startRow, startCol, text, mode, endRow = startRow) {
3107
- const { values, depths } = parseClipboardTSVWithDepths(text);
3108
- if (values.length === 0) return null;
3109
- const width = Math.max(...values.map((row) => row.length), 0);
3110
- if (width === 0) return null;
3111
- const columnIds = resolvePasteColumnIds(rows, startCol, width);
3112
- if (columnIds.length === 0) return null;
3113
- const rowIds = [];
3114
- for (let offset = 0; offset < values.length; offset += 1) {
3115
- const row = rows[startRow + offset];
3116
- if (!row) break;
3117
- rowIds.push(row.id);
3118
- }
3119
- const anchorRow = rows[endRow] ?? rows[startRow];
3120
- return {
3121
- mode,
3122
- startRow,
3123
- startCol,
3124
- endRow,
3125
- rowIds,
3126
- anchorRowId: anchorRow?.id ?? "",
3127
- columnIds,
3128
- values,
3129
- depths
3130
- };
3131
- }
3132
- function isEditablePasteTarget(target) {
3133
- if (!(target instanceof HTMLElement)) return false;
3134
- const tag = target.tagName;
3135
- if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
3136
- return Boolean(target.isContentEditable);
3137
- }
3138
-
3139
3179
  // src/components/ui/table/features/cell-selection/useCellSelection.ts
3140
3180
  function useCellSelection({
3141
3181
  data,
@@ -3151,6 +3191,7 @@ function useCellSelection({
3151
3191
  cellRendererRegistry,
3152
3192
  rootRef
3153
3193
  }) {
3194
+ const ownerRef = useRef5(createCellSelectionOwner());
3154
3195
  const [dragState, setDragState] = useState3(INITIAL_DRAG_STATE);
3155
3196
  const pendingPasteModeRef = useRef5(null);
3156
3197
  const dragStateRef = useRef5(dragState);
@@ -3162,6 +3203,7 @@ function useCellSelection({
3162
3203
  const handleCellMouseDown = useCallback3(
3163
3204
  (rowIndex, colIndex, options) => {
3164
3205
  if (!enabled) return;
3206
+ claimCellSelectionOwner(ownerRef.current);
3165
3207
  setDragState((prev) => {
3166
3208
  if (options?.shiftKey && prev.start) {
3167
3209
  return {
@@ -3203,6 +3245,7 @@ function useCellSelection({
3203
3245
  const handleFillHandleMouseDown = useCallback3(
3204
3246
  (rowIndex, colIndex) => {
3205
3247
  if (!enabled) return;
3248
+ claimCellSelectionOwner(ownerRef.current);
3206
3249
  setDragState((prev) => {
3207
3250
  const bounds = getCellSelectionBounds(prev.start, prev.end);
3208
3251
  if (!bounds) return prev;
@@ -3217,14 +3260,27 @@ function useCellSelection({
3217
3260
  },
3218
3261
  [enabled]
3219
3262
  );
3263
+ const clearSelection = useCallback3(() => {
3264
+ const prev = dragStateRef.current;
3265
+ if (prev.start === null && prev.end === null && !prev.isSelecting && !prev.isFillDragging) {
3266
+ return;
3267
+ }
3268
+ releaseCellSelectionOwner(ownerRef.current);
3269
+ dragStateRef.current = INITIAL_DRAG_STATE;
3270
+ setDragState(INITIAL_DRAG_STATE);
3271
+ }, []);
3220
3272
  useEffect5(() => {
3221
3273
  if (!enabled) {
3222
- setDragState(INITIAL_DRAG_STATE);
3274
+ clearSelection();
3223
3275
  }
3224
- }, [enabled]);
3276
+ }, [clearSelection, enabled]);
3277
+ useEffect5(() => {
3278
+ return registerCellSelectionOwner(ownerRef.current, clearSelection);
3279
+ }, [clearSelection]);
3225
3280
  useEffect5(() => {
3226
3281
  if (!enabled) return;
3227
3282
  const handleKeyDown = (e) => {
3283
+ if (!isActiveCellSelectionOwner(ownerRef.current)) return;
3228
3284
  if (e.ctrlKey || e.metaKey || e.altKey) return;
3229
3285
  if (isEditablePasteTarget(e.target) || isEditablePasteTarget(document.activeElement)) {
3230
3286
  return;
@@ -3290,6 +3346,7 @@ function useCellSelection({
3290
3346
  useEffect5(() => {
3291
3347
  if (!enabled) return;
3292
3348
  const handleKeyDown = (e) => {
3349
+ if (!isActiveCellSelectionOwner(ownerRef.current)) return;
3293
3350
  if (!activeSelectionBounds) return;
3294
3351
  if (!(e.ctrlKey || e.metaKey)) return;
3295
3352
  if (isEditablePasteTarget(e.target) || isEditablePasteTarget(document.activeElement)) {
@@ -3326,6 +3383,7 @@ function useCellSelection({
3326
3383
  const pasteHandledRef = { current: false };
3327
3384
  const ignoreNextPasteRef = { current: false };
3328
3385
  const handleKeyDown = (e) => {
3386
+ if (!isActiveCellSelectionOwner(ownerRef.current)) return;
3329
3387
  if (!activeSelectionBounds) return;
3330
3388
  if (!(e.ctrlKey || e.metaKey)) return;
3331
3389
  if (e.key.toLowerCase() !== "v") return;
@@ -3346,6 +3404,7 @@ function useCellSelection({
3346
3404
  const text = await navigator.clipboard.readText();
3347
3405
  if (pasteHandledRef.current) return;
3348
3406
  if (pendingPasteModeRef.current !== mode) return;
3407
+ if (!isActiveCellSelectionOwner(ownerRef.current)) return;
3349
3408
  if (!text) return;
3350
3409
  pasteHandledRef.current = true;
3351
3410
  emitRowsPaste(text, mode);
@@ -3355,6 +3414,7 @@ function useCellSelection({
3355
3414
  })();
3356
3415
  };
3357
3416
  const handlePaste = (e) => {
3417
+ if (!isActiveCellSelectionOwner(ownerRef.current)) return;
3358
3418
  if (!activeSelectionBounds) return;
3359
3419
  if (isEditablePasteTarget(e.target) || isEditablePasteTarget(document.activeElement)) {
3360
3420
  return;
@@ -3435,10 +3495,30 @@ function useCellSelection({
3435
3495
  handleCellMouseDown,
3436
3496
  handleCellMouseEnter,
3437
3497
  handleFillHandleMouseDown,
3498
+ clearSelection,
3438
3499
  copySelection
3439
3500
  };
3440
3501
  }
3441
3502
 
3503
+ // src/components/ui/table/features/selection-dismiss/isOutsideDismissTarget.ts
3504
+ var OVERLAY_DISMISS_IGNORE_SELECTOR = [
3505
+ '[role="dialog"]',
3506
+ '[role="alertdialog"]',
3507
+ '[role="menu"]',
3508
+ '[role="listbox"]',
3509
+ '[role="tooltip"]',
3510
+ '[aria-modal="true"]',
3511
+ "[data-radix-portal]",
3512
+ "[data-radix-popper-content-wrapper]",
3513
+ "[data-floating-ui-portal]",
3514
+ "[data-table-ignore-outside-dismiss]"
3515
+ ].join(",");
3516
+ function isOverlayDismissIgnoreTarget(target) {
3517
+ const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
3518
+ if (!element) return false;
3519
+ return element.closest(OVERLAY_DISMISS_IGNORE_SELECTOR) !== null;
3520
+ }
3521
+
3442
3522
  // src/components/ui/table/features/inline-search/useInlineSearch.ts
3443
3523
  import {
3444
3524
  useCallback as useCallback4,
@@ -4033,6 +4113,7 @@ function useGlideTable(options) {
4033
4113
  handleCellMouseDown,
4034
4114
  handleCellMouseEnter,
4035
4115
  handleFillHandleMouseDown,
4116
+ clearSelection: clearCellSelection,
4036
4117
  copySelection
4037
4118
  } = useCellSelection({
4038
4119
  data: tableData,
@@ -4048,6 +4129,37 @@ function useGlideTable(options) {
4048
4129
  cellRendererRegistry,
4049
4130
  rootRef
4050
4131
  });
4132
+ const clearRowSelection = useCallback5(() => {
4133
+ if (rowSelectionMode === "none") return;
4134
+ const hasSelection = Object.values(rowSelection).some(Boolean);
4135
+ if (!hasSelection) return;
4136
+ if (onRowSelectionChange) {
4137
+ onRowSelectionChange(() => ({}));
4138
+ return;
4139
+ }
4140
+ setInternalRowSelection({});
4141
+ }, [onRowSelectionChange, rowSelection, rowSelectionMode]);
4142
+ useEffect7(() => {
4143
+ const clearAllSelections = () => {
4144
+ clearCellSelection();
4145
+ clearRowSelection();
4146
+ };
4147
+ const handleKeyDown = (event) => {
4148
+ if (event.key !== "Escape") return;
4149
+ if (event.defaultPrevented) return;
4150
+ if (isEditablePasteTarget(event.target) || isEditablePasteTarget(document.activeElement)) {
4151
+ return;
4152
+ }
4153
+ if (isOverlayDismissIgnoreTarget(event.target) || isOverlayDismissIgnoreTarget(document.activeElement)) {
4154
+ return;
4155
+ }
4156
+ clearAllSelections();
4157
+ };
4158
+ window.addEventListener("keydown", handleKeyDown);
4159
+ return () => {
4160
+ window.removeEventListener("keydown", handleKeyDown);
4161
+ };
4162
+ }, [clearCellSelection, clearRowSelection]);
4051
4163
  const {
4052
4164
  editingCell,
4053
4165
  draftValue,
@@ -4920,7 +5032,8 @@ function buildColumnDef(props, sort, onSort) {
4920
5032
  cellProps,
4921
5033
  className,
4922
5034
  headerClassName,
4923
- render
5035
+ render,
5036
+ copyValue
4924
5037
  } = props;
4925
5038
  return {
4926
5039
  id: field,
@@ -4954,6 +5067,7 @@ function buildColumnDef(props, sort, onSort) {
4954
5067
  kind,
4955
5068
  cellProps,
4956
5069
  cellRender: render,
5070
+ copyValue,
4957
5071
  frozen,
4958
5072
  reorderable,
4959
5073
  width,