@xcelsior/ui-spreadsheets 1.0.13 → 1.0.15

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
@@ -40,7 +40,7 @@ __export(index_exports, {
40
40
  module.exports = __toCommonJS(index_exports);
41
41
 
42
42
  // src/components/Spreadsheet.tsx
43
- var import_react15 = require("react");
43
+ var import_react16 = require("react");
44
44
 
45
45
  // ../../../node_modules/.pnpm/react-icons@4.12.0_react@18.3.1/node_modules/react-icons/lib/esm/iconBase.js
46
46
  var import_react2 = __toESM(require("react"));
@@ -164,12 +164,6 @@ function HiZoomIn(props) {
164
164
  function HiZoomOut(props) {
165
165
  return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 20 20", "fill": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "fillRule": "evenodd", "d": "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z", "clipRule": "evenodd" } }, { "tag": "path", "attr": { "fillRule": "evenodd", "d": "M5 8a1 1 0 011-1h4a1 1 0 110 2H6a1 1 0 01-1-1z", "clipRule": "evenodd" } }] })(props);
166
166
  }
167
- function HiOutlineClipboardCheck(props) {
168
- return GenIcon({ "tag": "svg", "attr": { "fill": "none", "viewBox": "0 0 24 24", "strokeWidth": "2", "stroke": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "strokeLinecap": "round", "strokeLinejoin": "round", "d": "M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" } }] })(props);
169
- }
170
- function HiOutlineClipboardCopy(props) {
171
- return GenIcon({ "tag": "svg", "attr": { "fill": "none", "viewBox": "0 0 24 24", "strokeWidth": "2", "stroke": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "strokeLinecap": "round", "strokeLinejoin": "round", "d": "M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" } }] })(props);
172
- }
173
167
  function HiOutlineQuestionMarkCircle(props) {
174
168
  return GenIcon({ "tag": "svg", "attr": { "fill": "none", "viewBox": "0 0 24 24", "strokeWidth": "2", "stroke": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "strokeLinecap": "round", "strokeLinejoin": "round", "d": "M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" } }] })(props);
175
169
  }
@@ -211,6 +205,8 @@ var SpreadsheetCell = ({
211
205
  isEditable = false,
212
206
  isEditing = false,
213
207
  isFocused = false,
208
+ isInSelection = false,
209
+ selectionEdge,
214
210
  isRowSelected = false,
215
211
  isRowHovered = false,
216
212
  highlightColor,
@@ -223,15 +219,14 @@ var SpreadsheetCell = ({
223
219
  leftOffset = 0,
224
220
  rightOffset = 0,
225
221
  onClick,
222
+ onMouseDown,
223
+ onMouseEnter,
226
224
  onChange,
227
225
  onConfirm,
228
226
  onCancel,
229
- onCopyDown,
230
- onCopyToSelected,
231
227
  onHighlight,
232
228
  onAddComment,
233
229
  onViewComments,
234
- hasSelectedRows = false,
235
230
  className
236
231
  }) => {
237
232
  const [localValue, setLocalValue] = (0, import_react3.useState)(value);
@@ -345,26 +340,51 @@ var SpreadsheetCell = ({
345
340
  positionStyles.position = "sticky";
346
341
  }
347
342
  }
343
+ const selectionBorderStyles = {};
344
+ if (isInSelection && selectionEdge) {
345
+ const borderColor = "rgb(59 130 246)";
346
+ const borderWidth = "2px";
347
+ if (selectionEdge.top) {
348
+ selectionBorderStyles.borderTopColor = borderColor;
349
+ selectionBorderStyles.borderTopWidth = borderWidth;
350
+ }
351
+ if (selectionEdge.right) {
352
+ selectionBorderStyles.borderRightColor = borderColor;
353
+ selectionBorderStyles.borderRightWidth = borderWidth;
354
+ }
355
+ if (selectionEdge.bottom) {
356
+ selectionBorderStyles.borderBottomColor = borderColor;
357
+ selectionBorderStyles.borderBottomWidth = borderWidth;
358
+ }
359
+ if (selectionEdge.left) {
360
+ selectionBorderStyles.borderLeftColor = borderColor;
361
+ selectionBorderStyles.borderLeftWidth = borderWidth;
362
+ }
363
+ }
348
364
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
349
365
  "td",
350
366
  {
351
367
  onClick,
368
+ onMouseDown,
369
+ onMouseEnter,
352
370
  onKeyDown: handleCellKeyDown,
353
371
  "data-cell-id": `${rowId}-${column.id}`,
354
372
  className: cn(
355
- "border border-gray-200 text-xs group cursor-pointer transition-colors",
373
+ "border border-gray-200 text-xs group cursor-pointer transition-colors select-none",
356
374
  cellPadding,
357
375
  column.align === "right" && "text-right",
358
376
  column.align === "center" && "text-center",
359
377
  isCopied && "animate-pulse",
360
- isFocused && "ring-2 ring-blue-500 ring-inset",
378
+ isFocused && !isInSelection && "ring-2 ring-blue-500 ring-inset",
379
+ isInSelection && "bg-blue-50",
361
380
  isPinned ? "z-20" : "z-0",
362
381
  className
363
382
  ),
364
383
  style: {
365
- backgroundColor: getBackgroundColor(),
384
+ backgroundColor: isInSelection ? "rgb(239 246 255)" : getBackgroundColor(),
366
385
  minWidth: column.minWidth || column.width,
367
- ...positionStyles
386
+ ...positionStyles,
387
+ ...selectionBorderStyles
368
388
  },
369
389
  children: isEditing ? renderEditInput() : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-1 relative", children: [
370
390
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -372,39 +392,13 @@ var SpreadsheetCell = ({
372
392
  {
373
393
  className: cn(
374
394
  "flex-1 truncate",
375
- isEditable && "cursor-text hover:bg-gray-50 px-0.5 rounded min-h-[18px] flex items-center bg-yellow-50/50"
395
+ isEditable && "cursor-text hover:bg-gray-50 px-0.5 rounded min-h-[18px] flex items-center bg-blue-50/50"
376
396
  ),
377
397
  title: String(value ?? ""),
378
398
  children: renderContent()
379
399
  }
380
400
  ),
381
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-0.5 shrink-0", children: [
382
- value !== null && value !== void 0 && value !== "" && onCopyDown && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
383
- "button",
384
- {
385
- type: "button",
386
- onClick: (e) => {
387
- e.stopPropagation();
388
- onCopyDown();
389
- },
390
- className: "opacity-0 group-hover:opacity-100 transition-opacity p-0.5 bg-gray-100 hover:bg-gray-200 rounded",
391
- title: "Copy value down to rows below",
392
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HiOutlineClipboardCopy, { className: "h-2.5 w-2.5 text-gray-500" })
393
- }
394
- ),
395
- hasSelectedRows && value !== null && value !== void 0 && value !== "" && onCopyToSelected && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
396
- "button",
397
- {
398
- type: "button",
399
- onClick: (e) => {
400
- e.stopPropagation();
401
- onCopyToSelected();
402
- },
403
- className: "opacity-0 group-hover:opacity-100 transition-opacity p-0.5 bg-green-100 hover:bg-green-200 rounded",
404
- title: "Copy to selected rows",
405
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HiOutlineClipboardCheck, { className: "h-2.5 w-2.5 text-green-600" })
406
- }
407
- ),
401
+ !isInSelection && !isFocused && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-0.5 shrink-0", children: [
408
402
  onHighlight && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
409
403
  "button",
410
404
  {
@@ -464,6 +458,7 @@ var MemoizedSpreadsheetCell = (0, import_react3.memo)(SpreadsheetCell, (prevProp
464
458
  if (prevProps.isEditing !== nextProps.isEditing) return false;
465
459
  if (prevProps.isFocused !== nextProps.isFocused) return false;
466
460
  if (prevProps.value !== nextProps.value) return false;
461
+ if (prevProps.isInSelection !== nextProps.isInSelection) return false;
467
462
  if (prevProps.isRowSelected !== nextProps.isRowSelected) return false;
468
463
  if (prevProps.isRowHovered !== nextProps.isRowHovered) return false;
469
464
  if (prevProps.highlightColor !== nextProps.highlightColor) return false;
@@ -473,6 +468,15 @@ var MemoizedSpreadsheetCell = (0, import_react3.memo)(SpreadsheetCell, (prevProp
473
468
  if (prevProps.isPinned !== nextProps.isPinned) return false;
474
469
  if (prevProps.leftOffset !== nextProps.leftOffset) return false;
475
470
  if (prevProps.rightOffset !== nextProps.rightOffset) return false;
471
+ const prevEdge = prevProps.selectionEdge;
472
+ const nextEdge = nextProps.selectionEdge;
473
+ if (prevEdge !== nextEdge) {
474
+ if (!prevEdge || !nextEdge) return false;
475
+ if (prevEdge.top !== nextEdge.top) return false;
476
+ if (prevEdge.right !== nextEdge.right) return false;
477
+ if (prevEdge.bottom !== nextEdge.bottom) return false;
478
+ if (prevEdge.left !== nextEdge.left) return false;
479
+ }
476
480
  return true;
477
481
  });
478
482
  MemoizedSpreadsheetCell.displayName = "MemoizedSpreadsheetCell";
@@ -2744,6 +2748,9 @@ function useSpreadsheetKeyboardShortcuts({
2744
2748
  onEscape,
2745
2749
  onNavigate,
2746
2750
  onEnterEditMode,
2751
+ onTabNavigation,
2752
+ onCopy,
2753
+ onPaste,
2747
2754
  hasFocusedCell = false,
2748
2755
  isEditing = false,
2749
2756
  customShortcuts = [],
@@ -2778,25 +2785,40 @@ function useSpreadsheetKeyboardShortcuts({
2778
2785
  onRedo?.();
2779
2786
  return;
2780
2787
  }
2788
+ if ((event.metaKey || event.ctrlKey) && event.key === "c" && !isEditing && hasFocusedCell) {
2789
+ event.preventDefault();
2790
+ onCopy?.();
2791
+ return;
2792
+ }
2793
+ if ((event.metaKey || event.ctrlKey) && event.key === "v" && !isEditing && hasFocusedCell) {
2794
+ event.preventDefault();
2795
+ onPaste?.();
2796
+ return;
2797
+ }
2798
+ if (event.key === "Tab" && !isEditing && hasFocusedCell) {
2799
+ event.preventDefault();
2800
+ onTabNavigation?.(event.shiftKey);
2801
+ return;
2802
+ }
2781
2803
  if (hasFocusedCell && !isEditing && !event.metaKey && !event.ctrlKey) {
2782
2804
  if (event.key === "ArrowUp") {
2783
2805
  event.preventDefault();
2784
- onNavigate?.("up");
2806
+ onNavigate?.("up", event);
2785
2807
  return;
2786
2808
  }
2787
2809
  if (event.key === "ArrowDown") {
2788
2810
  event.preventDefault();
2789
- onNavigate?.("down");
2811
+ onNavigate?.("down", event);
2790
2812
  return;
2791
2813
  }
2792
2814
  if (event.key === "ArrowLeft") {
2793
2815
  event.preventDefault();
2794
- onNavigate?.("left");
2816
+ onNavigate?.("left", event);
2795
2817
  return;
2796
2818
  }
2797
2819
  if (event.key === "ArrowRight") {
2798
2820
  event.preventDefault();
2799
- onNavigate?.("right");
2821
+ onNavigate?.("right", event);
2800
2822
  return;
2801
2823
  }
2802
2824
  if (event.key === "F2") {
@@ -2824,6 +2846,9 @@ function useSpreadsheetKeyboardShortcuts({
2824
2846
  onEscape,
2825
2847
  onNavigate,
2826
2848
  onEnterEditMode,
2849
+ onTabNavigation,
2850
+ onCopy,
2851
+ onPaste,
2827
2852
  hasFocusedCell,
2828
2853
  isEditing,
2829
2854
  customShortcuts
@@ -2841,6 +2866,8 @@ function useSpreadsheetKeyboardShortcuts({
2841
2866
  editing: [
2842
2867
  { label: "Undo", keys: [modifierKey, "Z"] },
2843
2868
  { label: "Redo", keys: [modifierKey, "Shift", "Z"] },
2869
+ { label: "Copy cells", keys: [modifierKey, "C"] },
2870
+ { label: "Paste cells", keys: [modifierKey, "V"] },
2844
2871
  { label: "Confirm cell edit", keys: ["Enter"] },
2845
2872
  { label: "Cancel cell edit", keys: ["Escape"] }
2846
2873
  ],
@@ -2849,6 +2876,12 @@ function useSpreadsheetKeyboardShortcuts({
2849
2876
  { label: "Navigate down", keys: ["\u2193"] },
2850
2877
  { label: "Navigate left", keys: ["\u2190"] },
2851
2878
  { label: "Navigate right", keys: ["\u2192"] },
2879
+ { label: "Extend selection up", keys: ["Shift", "\u2191"] },
2880
+ { label: "Extend selection down", keys: ["Shift", "\u2193"] },
2881
+ { label: "Extend selection left", keys: ["Shift", "\u2190"] },
2882
+ { label: "Extend selection right", keys: ["Shift", "\u2192"] },
2883
+ { label: "Next cell", keys: ["Tab"] },
2884
+ { label: "Previous cell", keys: ["Shift", "Tab"] },
2852
2885
  { label: "Edit cell", keys: ["F2"] }
2853
2886
  ],
2854
2887
  rowActions: [
@@ -2866,6 +2899,434 @@ function useSpreadsheetKeyboardShortcuts({
2866
2899
  };
2867
2900
  }
2868
2901
 
2902
+ // src/hooks/useSpreadsheetSelection.ts
2903
+ var import_react15 = require("react");
2904
+ function useSpreadsheetSelection({
2905
+ data,
2906
+ columns,
2907
+ getRowId,
2908
+ onCellRangeSelectionChange,
2909
+ enableCellEditing = true
2910
+ }) {
2911
+ const [focusedCell, setFocusedCell] = (0, import_react15.useState)(null);
2912
+ const [editingCell, setEditingCell] = (0, import_react15.useState)(null);
2913
+ const [selectedCellRange, setSelectedCellRangeState] = (0, import_react15.useState)(null);
2914
+ const [isDragging, setIsDragging] = (0, import_react15.useState)(false);
2915
+ const [clipboardData, setClipboardData] = (0, import_react15.useState)(null);
2916
+ const anchorCell = (0, import_react15.useRef)(null);
2917
+ const rowIndexMap = (0, import_react15.useMemo)(() => {
2918
+ const map = /* @__PURE__ */ new Map();
2919
+ data.forEach((row, index) => {
2920
+ map.set(getRowId(row), index);
2921
+ });
2922
+ return map;
2923
+ }, [data, getRowId]);
2924
+ const columnIndexMap = (0, import_react15.useMemo)(() => {
2925
+ const map = /* @__PURE__ */ new Map();
2926
+ columns.forEach((col, index) => {
2927
+ map.set(col.id, index);
2928
+ });
2929
+ return map;
2930
+ }, [columns]);
2931
+ const setSelectedCellRange = (0, import_react15.useCallback)(
2932
+ (range) => {
2933
+ setSelectedCellRangeState(range);
2934
+ onCellRangeSelectionChange?.(range);
2935
+ },
2936
+ [onCellRangeSelectionChange]
2937
+ );
2938
+ const getNormalizedRange = (0, import_react15.useCallback)(
2939
+ (range) => {
2940
+ if (!range) return null;
2941
+ const startRowIdx = rowIndexMap.get(range.start.rowId);
2942
+ const endRowIdx = rowIndexMap.get(range.end.rowId);
2943
+ const startColIdx = columnIndexMap.get(range.start.columnId);
2944
+ const endColIdx = columnIndexMap.get(range.end.columnId);
2945
+ if (startRowIdx === void 0 || endRowIdx === void 0 || startColIdx === void 0 || endColIdx === void 0) {
2946
+ return null;
2947
+ }
2948
+ return {
2949
+ startRowIdx: Math.min(startRowIdx, endRowIdx),
2950
+ endRowIdx: Math.max(startRowIdx, endRowIdx),
2951
+ startColIdx: Math.min(startColIdx, endColIdx),
2952
+ endColIdx: Math.max(startColIdx, endColIdx)
2953
+ };
2954
+ },
2955
+ [rowIndexMap, columnIndexMap]
2956
+ );
2957
+ const isCellInSelection = (0, import_react15.useCallback)(
2958
+ (rowId, columnId) => {
2959
+ const normalizedRange = getNormalizedRange(selectedCellRange);
2960
+ if (!normalizedRange) return false;
2961
+ const rowIdx = rowIndexMap.get(rowId);
2962
+ const colIdx = columnIndexMap.get(columnId);
2963
+ if (rowIdx === void 0 || colIdx === void 0) return false;
2964
+ return rowIdx >= normalizedRange.startRowIdx && rowIdx <= normalizedRange.endRowIdx && colIdx >= normalizedRange.startColIdx && colIdx <= normalizedRange.endColIdx;
2965
+ },
2966
+ [selectedCellRange, rowIndexMap, columnIndexMap, getNormalizedRange]
2967
+ );
2968
+ const getCellSelectionEdge = (0, import_react15.useCallback)(
2969
+ (rowId, columnId) => {
2970
+ if (!isCellInSelection(rowId, columnId)) return void 0;
2971
+ const normalizedRange = getNormalizedRange(selectedCellRange);
2972
+ if (!normalizedRange) return void 0;
2973
+ const rowIdx = rowIndexMap.get(rowId);
2974
+ const colIdx = columnIndexMap.get(columnId);
2975
+ if (rowIdx === void 0 || colIdx === void 0) return void 0;
2976
+ return {
2977
+ top: rowIdx === normalizedRange.startRowIdx,
2978
+ bottom: rowIdx === normalizedRange.endRowIdx,
2979
+ left: colIdx === normalizedRange.startColIdx,
2980
+ right: colIdx === normalizedRange.endColIdx
2981
+ };
2982
+ },
2983
+ [isCellInSelection, selectedCellRange, rowIndexMap, columnIndexMap, getNormalizedRange]
2984
+ );
2985
+ const getSelectedCells = (0, import_react15.useCallback)(() => {
2986
+ const normalizedRange = getNormalizedRange(selectedCellRange);
2987
+ if (!normalizedRange) {
2988
+ return focusedCell ? [focusedCell] : [];
2989
+ }
2990
+ const cells = [];
2991
+ for (let rowIdx = normalizedRange.startRowIdx; rowIdx <= normalizedRange.endRowIdx; rowIdx++) {
2992
+ const row = data[rowIdx];
2993
+ if (!row) continue;
2994
+ const rowId = getRowId(row);
2995
+ for (let colIdx = normalizedRange.startColIdx; colIdx <= normalizedRange.endColIdx; colIdx++) {
2996
+ const column = columns[colIdx];
2997
+ if (!column) continue;
2998
+ cells.push({ rowId, columnId: column.id });
2999
+ }
3000
+ }
3001
+ return cells;
3002
+ }, [selectedCellRange, focusedCell, data, columns, getRowId, getNormalizedRange]);
3003
+ const getSelectedCellValues = (0, import_react15.useCallback)(() => {
3004
+ const cells = getSelectedCells();
3005
+ return cells.map((cell) => {
3006
+ const row = data.find((r) => getRowId(r) === cell.rowId);
3007
+ const column = columns.find((c) => c.id === cell.columnId);
3008
+ const value = row && column ? column.getValue ? column.getValue(row) : row[cell.columnId] : void 0;
3009
+ return { position: cell, value };
3010
+ });
3011
+ }, [getSelectedCells, data, columns, getRowId]);
3012
+ const handleCellClick = (0, import_react15.useCallback)(
3013
+ (rowId, columnId, event) => {
3014
+ event.stopPropagation();
3015
+ const newCell = { rowId, columnId };
3016
+ if (event.shiftKey && anchorCell.current) {
3017
+ setSelectedCellRange({
3018
+ start: anchorCell.current,
3019
+ end: newCell
3020
+ });
3021
+ setFocusedCell(newCell);
3022
+ } else {
3023
+ anchorCell.current = newCell;
3024
+ setFocusedCell(newCell);
3025
+ setSelectedCellRange(null);
3026
+ const column = columns.find((c) => c.id === columnId);
3027
+ if (column?.editable && enableCellEditing) {
3028
+ setEditingCell(newCell);
3029
+ }
3030
+ }
3031
+ },
3032
+ [columns, enableCellEditing, setSelectedCellRange]
3033
+ );
3034
+ const handleCellMouseDown = (0, import_react15.useCallback)(
3035
+ (rowId, columnId, event) => {
3036
+ if (event.button !== 0) return;
3037
+ const newCell = { rowId, columnId };
3038
+ if (event.shiftKey && anchorCell.current) {
3039
+ event.preventDefault();
3040
+ setSelectedCellRange({
3041
+ start: anchorCell.current,
3042
+ end: newCell
3043
+ });
3044
+ setFocusedCell(newCell);
3045
+ setEditingCell(null);
3046
+ } else {
3047
+ anchorCell.current = newCell;
3048
+ setFocusedCell(newCell);
3049
+ setSelectedCellRange(null);
3050
+ const column = columns.find((c) => c.id === columnId);
3051
+ if (column?.editable && enableCellEditing) {
3052
+ setEditingCell(newCell);
3053
+ } else {
3054
+ setEditingCell(null);
3055
+ }
3056
+ }
3057
+ },
3058
+ [columns, enableCellEditing, setSelectedCellRange]
3059
+ );
3060
+ const handleCellMouseEnter = (0, import_react15.useCallback)((_rowId, _columnId) => {
3061
+ }, []);
3062
+ const handleMouseUp = (0, import_react15.useCallback)(() => {
3063
+ setIsDragging(false);
3064
+ }, []);
3065
+ const clearSelection = (0, import_react15.useCallback)(() => {
3066
+ setFocusedCell(null);
3067
+ setEditingCell(null);
3068
+ setSelectedCellRange(null);
3069
+ anchorCell.current = null;
3070
+ }, [setSelectedCellRange]);
3071
+ const navigateCell = (0, import_react15.useCallback)(
3072
+ (direction, extendSelection = false) => {
3073
+ const currentCell = focusedCell;
3074
+ if (!currentCell) return;
3075
+ const currentRowIdx = rowIndexMap.get(currentCell.rowId);
3076
+ const currentColIdx = columnIndexMap.get(currentCell.columnId);
3077
+ if (currentRowIdx === void 0 || currentColIdx === void 0) return;
3078
+ let newRowIdx = currentRowIdx;
3079
+ let newColIdx = currentColIdx;
3080
+ switch (direction) {
3081
+ case "up":
3082
+ newRowIdx = Math.max(0, currentRowIdx - 1);
3083
+ break;
3084
+ case "down":
3085
+ newRowIdx = Math.min(data.length - 1, currentRowIdx + 1);
3086
+ break;
3087
+ case "left":
3088
+ newColIdx = Math.max(0, currentColIdx - 1);
3089
+ break;
3090
+ case "right":
3091
+ newColIdx = Math.min(columns.length - 1, currentColIdx + 1);
3092
+ break;
3093
+ }
3094
+ const newRow = data[newRowIdx];
3095
+ const newColumn = columns[newColIdx];
3096
+ if (!newRow || !newColumn) return;
3097
+ const newCell = {
3098
+ rowId: getRowId(newRow),
3099
+ columnId: newColumn.id
3100
+ };
3101
+ setFocusedCell(newCell);
3102
+ setEditingCell(null);
3103
+ if (extendSelection) {
3104
+ if (!anchorCell.current) {
3105
+ anchorCell.current = currentCell;
3106
+ }
3107
+ setSelectedCellRange({
3108
+ start: anchorCell.current,
3109
+ end: newCell
3110
+ });
3111
+ } else {
3112
+ anchorCell.current = newCell;
3113
+ setSelectedCellRange(null);
3114
+ }
3115
+ },
3116
+ [focusedCell, data, columns, getRowId, rowIndexMap, columnIndexMap, setSelectedCellRange]
3117
+ );
3118
+ const handleTabNavigation = (0, import_react15.useCallback)(
3119
+ (shiftKey) => {
3120
+ const currentCell = focusedCell;
3121
+ if (!currentCell) return;
3122
+ const currentRowIdx = rowIndexMap.get(currentCell.rowId);
3123
+ const currentColIdx = columnIndexMap.get(currentCell.columnId);
3124
+ if (currentRowIdx === void 0 || currentColIdx === void 0) return;
3125
+ let newRowIdx = currentRowIdx;
3126
+ let newColIdx = currentColIdx;
3127
+ if (shiftKey) {
3128
+ newColIdx--;
3129
+ if (newColIdx < 0) {
3130
+ newColIdx = columns.length - 1;
3131
+ newRowIdx--;
3132
+ }
3133
+ } else {
3134
+ newColIdx++;
3135
+ if (newColIdx >= columns.length) {
3136
+ newColIdx = 0;
3137
+ newRowIdx++;
3138
+ }
3139
+ }
3140
+ if (newRowIdx < 0) {
3141
+ newRowIdx = data.length - 1;
3142
+ } else if (newRowIdx >= data.length) {
3143
+ newRowIdx = 0;
3144
+ }
3145
+ const newRow = data[newRowIdx];
3146
+ const newColumn = columns[newColIdx];
3147
+ if (!newRow || !newColumn) return;
3148
+ const newCell = {
3149
+ rowId: getRowId(newRow),
3150
+ columnId: newColumn.id
3151
+ };
3152
+ setFocusedCell(newCell);
3153
+ setEditingCell(null);
3154
+ setSelectedCellRange(null);
3155
+ anchorCell.current = newCell;
3156
+ },
3157
+ [focusedCell, data, columns, getRowId, rowIndexMap, columnIndexMap, setSelectedCellRange]
3158
+ );
3159
+ const enterEditMode = (0, import_react15.useCallback)(() => {
3160
+ if (!focusedCell || !enableCellEditing) return;
3161
+ const column = columns.find((c) => c.id === focusedCell.columnId);
3162
+ if (column?.editable) {
3163
+ setEditingCell(focusedCell);
3164
+ }
3165
+ }, [focusedCell, columns, enableCellEditing]);
3166
+ const exitEditMode = (0, import_react15.useCallback)(() => {
3167
+ setEditingCell(null);
3168
+ }, []);
3169
+ const copySelectedCells = (0, import_react15.useCallback)(() => {
3170
+ const normalizedRange = getNormalizedRange(selectedCellRange);
3171
+ if (!normalizedRange && !focusedCell) return;
3172
+ const startRowIdx = normalizedRange?.startRowIdx ?? rowIndexMap.get(focusedCell.rowId) ?? 0;
3173
+ const endRowIdx = normalizedRange?.endRowIdx ?? startRowIdx;
3174
+ const startColIdx = normalizedRange?.startColIdx ?? columnIndexMap.get(focusedCell.columnId) ?? 0;
3175
+ const endColIdx = normalizedRange?.endColIdx ?? startColIdx;
3176
+ const values = [];
3177
+ const textRows = [];
3178
+ for (let rowIdx = startRowIdx; rowIdx <= endRowIdx; rowIdx++) {
3179
+ const row = data[rowIdx];
3180
+ if (!row) continue;
3181
+ const rowValues = [];
3182
+ const textCells = [];
3183
+ for (let colIdx = startColIdx; colIdx <= endColIdx; colIdx++) {
3184
+ const column = columns[colIdx];
3185
+ if (!column) continue;
3186
+ const value = column.getValue ? column.getValue(row) : row[column.id];
3187
+ rowValues.push(value);
3188
+ textCells.push(value != null ? String(value) : "");
3189
+ }
3190
+ values.push(rowValues);
3191
+ textRows.push(textCells.join(" "));
3192
+ }
3193
+ const startRow = data[startRowIdx];
3194
+ const startColumn = columns[startColIdx];
3195
+ if (startRow && startColumn) {
3196
+ setClipboardData({
3197
+ values,
3198
+ startCell: {
3199
+ rowId: getRowId(startRow),
3200
+ columnId: startColumn.id
3201
+ }
3202
+ });
3203
+ }
3204
+ const text = textRows.join("\n");
3205
+ navigator.clipboard.writeText(text).catch(() => {
3206
+ console.warn("Could not copy to system clipboard");
3207
+ });
3208
+ }, [
3209
+ selectedCellRange,
3210
+ focusedCell,
3211
+ data,
3212
+ columns,
3213
+ getRowId,
3214
+ getNormalizedRange,
3215
+ rowIndexMap,
3216
+ columnIndexMap
3217
+ ]);
3218
+ const parseClipboardText = (0, import_react15.useCallback)((text) => {
3219
+ const lines = text.split(/\r?\n/);
3220
+ if (lines.length > 0 && lines[lines.length - 1] === "") {
3221
+ lines.pop();
3222
+ }
3223
+ return lines.map((line) => line.split(" "));
3224
+ }, []);
3225
+ const createEditsFromValues = (0, import_react15.useCallback)(
3226
+ (values) => {
3227
+ if (!focusedCell) return [];
3228
+ const edits = [];
3229
+ const normalizedRange = getNormalizedRange(selectedCellRange);
3230
+ let startRowIdx;
3231
+ let endRowIdx;
3232
+ let startColIdx;
3233
+ let endColIdx;
3234
+ if (normalizedRange) {
3235
+ startRowIdx = normalizedRange.startRowIdx;
3236
+ endRowIdx = normalizedRange.endRowIdx;
3237
+ startColIdx = normalizedRange.startColIdx;
3238
+ endColIdx = normalizedRange.endColIdx;
3239
+ } else {
3240
+ const focusRowIdx = rowIndexMap.get(focusedCell.rowId);
3241
+ const focusColIdx = columnIndexMap.get(focusedCell.columnId);
3242
+ if (focusRowIdx === void 0 || focusColIdx === void 0) return [];
3243
+ startRowIdx = focusRowIdx;
3244
+ endRowIdx = Math.min(focusRowIdx + values.length - 1, data.length - 1);
3245
+ startColIdx = focusColIdx;
3246
+ endColIdx = Math.min(
3247
+ focusColIdx + (values[0]?.length || 1) - 1,
3248
+ columns.length - 1
3249
+ );
3250
+ }
3251
+ for (let rowIdx = startRowIdx; rowIdx <= endRowIdx; rowIdx++) {
3252
+ const row = data[rowIdx];
3253
+ if (!row) continue;
3254
+ const rowId = getRowId(row);
3255
+ const valueRowIdx = (rowIdx - startRowIdx) % values.length;
3256
+ for (let colIdx = startColIdx; colIdx <= endColIdx; colIdx++) {
3257
+ const column = columns[colIdx];
3258
+ if (!column || !column.editable) continue;
3259
+ const valueColIdx = (colIdx - startColIdx) % (values[valueRowIdx]?.length || 1);
3260
+ let cellValue = values[valueRowIdx]?.[valueColIdx];
3261
+ if (column.type === "number" && typeof cellValue === "string") {
3262
+ const parsed = parseFloat(cellValue);
3263
+ cellValue = isNaN(parsed) ? cellValue : parsed;
3264
+ }
3265
+ edits.push({
3266
+ rowId,
3267
+ columnId: column.id,
3268
+ value: cellValue
3269
+ });
3270
+ }
3271
+ }
3272
+ return edits;
3273
+ },
3274
+ [
3275
+ focusedCell,
3276
+ selectedCellRange,
3277
+ data,
3278
+ columns,
3279
+ getRowId,
3280
+ rowIndexMap,
3281
+ columnIndexMap,
3282
+ getNormalizedRange
3283
+ ]
3284
+ );
3285
+ const pasteClipboard = (0, import_react15.useCallback)(() => {
3286
+ if (!clipboardData?.values) return [];
3287
+ return createEditsFromValues(clipboardData.values);
3288
+ }, [clipboardData, createEditsFromValues]);
3289
+ const pasteFromSystemClipboard = (0, import_react15.useCallback)(async () => {
3290
+ if (!focusedCell) return [];
3291
+ try {
3292
+ const text = await navigator.clipboard.readText();
3293
+ if (!text) {
3294
+ return pasteClipboard();
3295
+ }
3296
+ const values = parseClipboardText(text);
3297
+ return createEditsFromValues(values);
3298
+ } catch {
3299
+ return pasteClipboard();
3300
+ }
3301
+ }, [focusedCell, pasteClipboard, parseClipboardText, createEditsFromValues]);
3302
+ return {
3303
+ focusedCell,
3304
+ setFocusedCell,
3305
+ editingCell,
3306
+ setEditingCell,
3307
+ selectedCellRange,
3308
+ setSelectedCellRange,
3309
+ isDragging,
3310
+ handleCellClick,
3311
+ handleCellMouseDown,
3312
+ handleCellMouseEnter,
3313
+ handleMouseUp,
3314
+ isCellInSelection,
3315
+ getCellSelectionEdge,
3316
+ getSelectedCells,
3317
+ getSelectedCellValues,
3318
+ clearSelection,
3319
+ navigateCell,
3320
+ handleTabNavigation,
3321
+ enterEditMode,
3322
+ exitEditMode,
3323
+ clipboardData,
3324
+ copySelectedCells,
3325
+ pasteClipboard,
3326
+ pasteFromSystemClipboard
3327
+ };
3328
+ }
3329
+
2869
3330
  // src/components/Spreadsheet.tsx
2870
3331
  var import_jsx_runtime11 = require("react/jsx-runtime");
2871
3332
  function Spreadsheet({
@@ -2873,7 +3334,7 @@ function Spreadsheet({
2873
3334
  columns,
2874
3335
  columnGroups,
2875
3336
  getRowId,
2876
- onCellEdit,
3337
+ onCellsEdit,
2877
3338
  onSelectionChange,
2878
3339
  onSortChange,
2879
3340
  onFilterChange,
@@ -2995,17 +3456,15 @@ function Spreadsheet({
2995
3456
  enabled: enableUndoRedo,
2996
3457
  autoSave
2997
3458
  });
2998
- const [selectedRows, setSelectedRows] = (0, import_react15.useState)(/* @__PURE__ */ new Set());
2999
- const [lastSelectedRow, setLastSelectedRow] = (0, import_react15.useState)(null);
3000
- const [focusedCell, setFocusedCell] = (0, import_react15.useState)(null);
3001
- const [editingCell, setEditingCell] = (0, import_react15.useState)(null);
3002
- const [hoveredRow, setHoveredRow] = (0, import_react15.useState)(null);
3003
- const [internalCurrentPage, setInternalCurrentPage] = (0, import_react15.useState)(1);
3004
- const [internalPageSize, setInternalPageSize] = (0, import_react15.useState)(defaultPageSize);
3005
- const [zoom, setZoom] = (0, import_react15.useState)(defaultZoom);
3459
+ const [selectedRows, setSelectedRows] = (0, import_react16.useState)(/* @__PURE__ */ new Set());
3460
+ const [lastSelectedRow, setLastSelectedRow] = (0, import_react16.useState)(null);
3461
+ const [hoveredRow, setHoveredRow] = (0, import_react16.useState)(null);
3462
+ const [internalCurrentPage, setInternalCurrentPage] = (0, import_react16.useState)(1);
3463
+ const [internalPageSize, setInternalPageSize] = (0, import_react16.useState)(defaultPageSize);
3464
+ const [zoom, setZoom] = (0, import_react16.useState)(defaultZoom);
3006
3465
  const currentPage = controlledCurrentPage ?? internalCurrentPage;
3007
3466
  const pageSize = controlledPageSize ?? internalPageSize;
3008
- const handlePageChange = (0, import_react15.useCallback)(
3467
+ const handlePageChange = (0, import_react16.useCallback)(
3009
3468
  (newPage) => {
3010
3469
  if (controlledCurrentPage === void 0) {
3011
3470
  setInternalCurrentPage(newPage);
@@ -3014,7 +3473,7 @@ function Spreadsheet({
3014
3473
  },
3015
3474
  [controlledCurrentPage, onPageChange, pageSize]
3016
3475
  );
3017
- const handlePageSizeChange = (0, import_react15.useCallback)(
3476
+ const handlePageSizeChange = (0, import_react16.useCallback)(
3018
3477
  (newPageSize) => {
3019
3478
  if (controlledPageSize === void 0) {
3020
3479
  setInternalPageSize(newPageSize);
@@ -3026,8 +3485,8 @@ function Spreadsheet({
3026
3485
  },
3027
3486
  [controlledPageSize, controlledCurrentPage, onPageChange]
3028
3487
  );
3029
- const [showSettingsModal, setShowSettingsModal] = (0, import_react15.useState)(false);
3030
- const [spreadsheetSettings, setSpreadsheetSettings] = (0, import_react15.useState)({
3488
+ const [showSettingsModal, setShowSettingsModal] = (0, import_react16.useState)(false);
3489
+ const [spreadsheetSettings, setSpreadsheetSettings] = (0, import_react16.useState)({
3031
3490
  defaultPinnedColumns: [],
3032
3491
  defaultSort: null,
3033
3492
  defaultPageSize,
@@ -3038,13 +3497,74 @@ function Spreadsheet({
3038
3497
  pinRowIndex: false,
3039
3498
  rowIndexHighlightColor: void 0
3040
3499
  });
3041
- (0, import_react15.useEffect)(() => {
3500
+ (0, import_react16.useEffect)(() => {
3042
3501
  setSpreadsheetSettings((prev) => ({
3043
3502
  ...prev,
3044
3503
  defaultSort: sortConfig
3045
3504
  }));
3046
3505
  }, [sortConfig]);
3047
- const handleEscapeCallback = (0, import_react15.useCallback)(() => {
3506
+ const applyUndo = (0, import_react16.useCallback)(() => {
3507
+ const entry = popUndoEntry();
3508
+ if (!entry || !onCellsEdit) return;
3509
+ if (entry.type === "cell-edit") {
3510
+ const { edit } = entry;
3511
+ onCellsEdit([
3512
+ { rowId: edit.rowId, columnId: edit.columnId, value: edit.previousValue }
3513
+ ]);
3514
+ } else if (entry.type === "batch-edit") {
3515
+ const edits = entry.edits.map((edit) => ({
3516
+ rowId: edit.rowId,
3517
+ columnId: edit.columnId,
3518
+ value: edit.previousValue
3519
+ }));
3520
+ onCellsEdit(edits);
3521
+ }
3522
+ markAsChanged();
3523
+ }, [popUndoEntry, onCellsEdit, markAsChanged]);
3524
+ const applyRedo = (0, import_react16.useCallback)(() => {
3525
+ const entry = popRedoEntry();
3526
+ if (!entry || !onCellsEdit) return;
3527
+ if (entry.type === "cell-edit") {
3528
+ const { edit } = entry;
3529
+ onCellsEdit([{ rowId: edit.rowId, columnId: edit.columnId, value: edit.nextValue }]);
3530
+ } else if (entry.type === "batch-edit") {
3531
+ const edits = entry.edits.map((edit) => ({
3532
+ rowId: edit.rowId,
3533
+ columnId: edit.columnId,
3534
+ value: edit.nextValue
3535
+ }));
3536
+ onCellsEdit(edits);
3537
+ }
3538
+ markAsChanged();
3539
+ }, [popRedoEntry, onCellsEdit, markAsChanged]);
3540
+ const paginatedData = (0, import_react16.useMemo)(() => {
3541
+ if (serverSide) {
3542
+ return filteredData;
3543
+ }
3544
+ const startIndex = (currentPage - 1) * pageSize;
3545
+ return filteredData.slice(startIndex, startIndex + pageSize);
3546
+ }, [filteredData, currentPage, pageSize, serverSide]);
3547
+ const {
3548
+ focusedCell,
3549
+ setFocusedCell,
3550
+ editingCell,
3551
+ setEditingCell,
3552
+ handleCellMouseDown,
3553
+ isCellInSelection,
3554
+ getCellSelectionEdge,
3555
+ clearSelection,
3556
+ navigateCell,
3557
+ handleTabNavigation,
3558
+ enterEditMode,
3559
+ copySelectedCells,
3560
+ pasteFromSystemClipboard
3561
+ } = useSpreadsheetSelection({
3562
+ data: paginatedData,
3563
+ columns: visibleColumns,
3564
+ getRowId,
3565
+ enableCellEditing
3566
+ });
3567
+ const handleEscapeCallback = (0, import_react16.useCallback)(() => {
3048
3568
  if (commentModalCell !== null) {
3049
3569
  setCommentModalCell(null);
3050
3570
  } else if (viewCommentsCell !== null) {
@@ -3058,8 +3578,7 @@ function Spreadsheet({
3058
3578
  } else {
3059
3579
  setSelectedRows(/* @__PURE__ */ new Set());
3060
3580
  setLastSelectedRow(null);
3061
- setFocusedCell(null);
3062
- setEditingCell(null);
3581
+ clearSelection();
3063
3582
  }
3064
3583
  }, [
3065
3584
  commentModalCell,
@@ -3071,105 +3590,88 @@ function Spreadsheet({
3071
3590
  highlightPickerColumn,
3072
3591
  setHighlightPickerColumn,
3073
3592
  highlightPickerCell,
3074
- setHighlightPickerCell
3593
+ setHighlightPickerCell,
3594
+ clearSelection
3075
3595
  ]);
3076
- const applyUndo = (0, import_react15.useCallback)(() => {
3077
- const entry = popUndoEntry();
3078
- if (!entry || !onCellEdit) return;
3079
- if (entry.type === "cell-edit") {
3080
- onCellEdit(entry.rowId, entry.columnId, entry.previousValue);
3081
- markAsChanged();
3082
- }
3083
- }, [popUndoEntry, onCellEdit, markAsChanged]);
3084
- const applyRedo = (0, import_react15.useCallback)(() => {
3085
- const entry = popRedoEntry();
3086
- if (!entry || !onCellEdit) return;
3087
- if (entry.type === "cell-edit") {
3088
- onCellEdit(entry.rowId, entry.columnId, entry.nextValue);
3089
- markAsChanged();
3090
- }
3091
- }, [popRedoEntry, onCellEdit, markAsChanged]);
3092
- const paginatedData = (0, import_react15.useMemo)(() => {
3093
- if (serverSide) {
3094
- return filteredData;
3095
- }
3096
- const startIndex = (currentPage - 1) * pageSize;
3097
- return filteredData.slice(startIndex, startIndex + pageSize);
3098
- }, [filteredData, currentPage, pageSize, serverSide]);
3099
- const handleNavigate = (0, import_react15.useCallback)(
3100
- (direction) => {
3101
- if (!focusedCell) return;
3102
- const currentRowIndex = paginatedData.findIndex(
3103
- (r) => getRowId(r) === focusedCell.rowId
3104
- );
3105
- const currentColIndex = visibleColumns.findIndex((c) => c.id === focusedCell.columnId);
3106
- if (currentRowIndex === -1 || currentColIndex === -1) return;
3107
- let newRowIndex = currentRowIndex;
3108
- let newColIndex = currentColIndex;
3109
- switch (direction) {
3110
- case "up":
3111
- newRowIndex = Math.max(0, currentRowIndex - 1);
3112
- break;
3113
- case "down":
3114
- newRowIndex = Math.min(paginatedData.length - 1, currentRowIndex + 1);
3115
- break;
3116
- case "left":
3117
- newColIndex = Math.max(0, currentColIndex - 1);
3118
- break;
3119
- case "right":
3120
- newColIndex = Math.min(visibleColumns.length - 1, currentColIndex + 1);
3121
- break;
3596
+ const handleNavigate = (0, import_react16.useCallback)(
3597
+ (direction, event) => {
3598
+ const extendSelection = event?.shiftKey ?? false;
3599
+ navigateCell(direction, extendSelection);
3600
+ if (focusedCell) {
3601
+ requestAnimationFrame(() => {
3602
+ const cellElement = tableRef.current?.querySelector(
3603
+ `[data-cell-id="${focusedCell.rowId}-${focusedCell.columnId}"]`
3604
+ );
3605
+ if (cellElement) {
3606
+ cellElement.scrollIntoView({
3607
+ behavior: "smooth",
3608
+ block: "nearest",
3609
+ inline: "nearest"
3610
+ });
3611
+ }
3612
+ });
3122
3613
  }
3123
- const newRowId = getRowId(paginatedData[newRowIndex]);
3124
- const newColumnId = visibleColumns[newColIndex].id;
3125
- setFocusedCell({ rowId: newRowId, columnId: newColumnId });
3126
- setEditingCell(null);
3127
- requestAnimationFrame(() => {
3128
- const cellElement = tableRef.current?.querySelector(
3129
- `[data-cell-id="${newRowId}-${newColumnId}"]`
3130
- );
3131
- if (cellElement) {
3132
- cellElement.scrollIntoView({
3133
- behavior: "smooth",
3134
- block: "nearest",
3135
- inline: "nearest"
3136
- });
3137
- }
3138
- });
3139
3614
  },
3140
- [focusedCell, paginatedData, visibleColumns, getRowId]
3615
+ [navigateCell, focusedCell]
3141
3616
  );
3142
- const handleEnterEditMode = (0, import_react15.useCallback)(() => {
3143
- if (!focusedCell) return;
3144
- const column = (columns || []).find((c) => c.id === focusedCell.columnId);
3145
- if (column?.editable && enableCellEditing) {
3146
- const row = (data || []).find((r) => getRowId(r) === focusedCell.rowId);
3147
- if (row) {
3148
- setEditingCell({ rowId: focusedCell.rowId, columnId: focusedCell.columnId });
3617
+ const handleEnterEditMode = (0, import_react16.useCallback)(() => {
3618
+ enterEditMode();
3619
+ }, [enterEditMode]);
3620
+ const handlePaste = (0, import_react16.useCallback)(async () => {
3621
+ const edits = await pasteFromSystemClipboard();
3622
+ if (edits.length > 0 && onCellsEdit) {
3623
+ if (enableUndoRedo) {
3624
+ const undoEdits = edits.map((edit) => {
3625
+ const row = data.find((r) => getRowId(r) === edit.rowId);
3626
+ const previousValue = row ? row[edit.columnId] : void 0;
3627
+ return {
3628
+ rowId: edit.rowId,
3629
+ columnId: edit.columnId,
3630
+ previousValue,
3631
+ nextValue: edit.value
3632
+ };
3633
+ });
3634
+ pushToUndoStack({
3635
+ type: "batch-edit",
3636
+ edits: undoEdits
3637
+ });
3149
3638
  }
3639
+ onCellsEdit(edits);
3640
+ markAsChanged();
3150
3641
  }
3151
- }, [focusedCell, columns, data, getRowId, enableCellEditing]);
3642
+ }, [
3643
+ pasteFromSystemClipboard,
3644
+ onCellsEdit,
3645
+ data,
3646
+ getRowId,
3647
+ enableUndoRedo,
3648
+ pushToUndoStack,
3649
+ markAsChanged
3650
+ ]);
3152
3651
  const { showKeyboardShortcuts, setShowKeyboardShortcuts, shortcuts } = useSpreadsheetKeyboardShortcuts({
3153
3652
  onUndo: applyUndo,
3154
3653
  onRedo: applyRedo,
3155
3654
  onEscape: handleEscapeCallback,
3156
3655
  onNavigate: handleNavigate,
3157
3656
  onEnterEditMode: handleEnterEditMode,
3657
+ onTabNavigation: handleTabNavigation,
3658
+ onCopy: copySelectedCells,
3659
+ onPaste: handlePaste,
3158
3660
  hasFocusedCell: focusedCell !== null,
3159
3661
  isEditing: editingCell !== null,
3160
3662
  enabled: true
3161
3663
  });
3162
3664
  const effectiveShowRowIndex = spreadsheetSettings.showRowIndex !== false;
3163
3665
  const rowIndexHighlightColor = getColumnHighlight(ROW_INDEX_COLUMN_ID);
3164
- const tableRef = (0, import_react15.useRef)(null);
3666
+ const tableRef = (0, import_react16.useRef)(null);
3165
3667
  const effectiveTotalItems = serverSide ? totalItems ?? data.length : filteredData.length;
3166
3668
  const totalPages = Math.max(1, Math.ceil(effectiveTotalItems / pageSize));
3167
- (0, import_react15.useEffect)(() => {
3669
+ (0, import_react16.useEffect)(() => {
3168
3670
  if (!serverSide && currentPage > totalPages) {
3169
3671
  setInternalCurrentPage(1);
3170
3672
  }
3171
3673
  }, [totalPages, currentPage, serverSide]);
3172
- const handleRowSelect = (0, import_react15.useCallback)(
3674
+ const handleRowSelect = (0, import_react16.useCallback)(
3173
3675
  (rowId, event) => {
3174
3676
  if (!enableRowSelection) return;
3175
3677
  event.stopPropagation();
@@ -3217,21 +3719,14 @@ function Spreadsheet({
3217
3719
  onSelectionChange
3218
3720
  ]
3219
3721
  );
3220
- const handleCellClick = (0, import_react15.useCallback)(
3722
+ const handleCellClick = (0, import_react16.useCallback)(
3221
3723
  (rowId, columnId, event) => {
3222
3724
  event.stopPropagation();
3223
- setFocusedCell({ rowId, columnId });
3224
- const column = (columns || []).find((c) => c.id === columnId);
3225
- if (column?.editable && enableCellEditing) {
3226
- const row = (data || []).find((r) => getRowId(r) === rowId);
3227
- if (row) {
3228
- setEditingCell({ rowId, columnId });
3229
- }
3230
- }
3725
+ handleCellMouseDown(rowId, columnId, event);
3231
3726
  },
3232
- [columns, data, getRowId, enableCellEditing]
3727
+ [handleCellMouseDown]
3233
3728
  );
3234
- const handleCellChange = (0, import_react15.useCallback)(
3729
+ const handleCellChange = (0, import_react16.useCallback)(
3235
3730
  (rowId, columnId, newValue) => {
3236
3731
  const row = data.find((r) => getRowId(r) === rowId);
3237
3732
  const previousValue = row ? row[columnId] : void 0;
@@ -3241,42 +3736,44 @@ function Spreadsheet({
3241
3736
  if (row && enableUndoRedo) {
3242
3737
  pushToUndoStack({
3243
3738
  type: "cell-edit",
3244
- rowId,
3245
- columnId,
3246
- previousValue,
3247
- nextValue: newValue
3739
+ edit: {
3740
+ rowId,
3741
+ columnId,
3742
+ previousValue,
3743
+ nextValue: newValue
3744
+ }
3248
3745
  });
3249
3746
  }
3250
- onCellEdit?.(rowId, columnId, newValue);
3747
+ onCellsEdit?.([{ rowId, columnId, value: newValue }]);
3251
3748
  markAsChanged();
3252
3749
  },
3253
- [data, getRowId, enableUndoRedo, onCellEdit, pushToUndoStack, markAsChanged]
3750
+ [data, getRowId, enableUndoRedo, onCellsEdit, pushToUndoStack, markAsChanged]
3254
3751
  );
3255
- const handleConfirmEdit = (0, import_react15.useCallback)(
3752
+ const handleConfirmEdit = (0, import_react16.useCallback)(
3256
3753
  (finalValue) => {
3257
3754
  if (editingCell && finalValue !== void 0) {
3258
3755
  handleCellChange(editingCell.rowId, editingCell.columnId, finalValue);
3259
3756
  setEditingCell(null);
3260
3757
  }
3261
3758
  },
3262
- [editingCell, handleCellChange]
3759
+ [editingCell, handleCellChange, setEditingCell]
3263
3760
  );
3264
- const handleCancelEdit = (0, import_react15.useCallback)(() => {
3761
+ const handleCancelEdit = (0, import_react16.useCallback)(() => {
3265
3762
  setEditingCell(null);
3266
- }, []);
3267
- const handleRowClone = (0, import_react15.useCallback)(
3763
+ }, [setEditingCell]);
3764
+ const handleRowClone = (0, import_react16.useCallback)(
3268
3765
  (row, rowId) => {
3269
3766
  onRowClone?.(row, rowId);
3270
3767
  },
3271
3768
  [onRowClone]
3272
3769
  );
3273
- const handleRowDelete = (0, import_react15.useCallback)(
3770
+ const handleRowDelete = (0, import_react16.useCallback)(
3274
3771
  (row, rowId) => {
3275
3772
  onRowDelete?.(row, rowId);
3276
3773
  },
3277
3774
  [onRowDelete]
3278
3775
  );
3279
- const handleRowIndexHighlightClick = (0, import_react15.useCallback)(() => {
3776
+ const handleRowIndexHighlightClick = (0, import_react16.useCallback)(() => {
3280
3777
  setHighlightPickerColumn(ROW_INDEX_COLUMN_ID);
3281
3778
  }, [setHighlightPickerColumn]);
3282
3779
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: cn("flex flex-col h-full bg-white", className), children: [
@@ -3318,7 +3815,7 @@ function Spreadsheet({
3318
3815
  transformOrigin: "top left",
3319
3816
  width: `${100 / (zoom / 100)}%`
3320
3817
  },
3321
- children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("table", { className: "w-full border-separate border-spacing-0 text-xs", children: [
3818
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("table", { className: "w-full border-separate border-spacing-0 text-xs select-none", children: [
3322
3819
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("thead", { children: [
3323
3820
  columnGroups && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
3324
3821
  effectiveShowRowIndex && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
@@ -3593,6 +4090,14 @@ function Spreadsheet({
3593
4090
  const value = column.getValue ? column.getValue(row) : row[column.id];
3594
4091
  const isEditing = editingCell?.rowId === rowId && editingCell?.columnId === column.id;
3595
4092
  const isFocused = focusedCell?.rowId === rowId && focusedCell?.columnId === column.id;
4093
+ const isInSelection = isCellInSelection(
4094
+ rowId,
4095
+ column.id
4096
+ );
4097
+ const selectionEdge = getCellSelectionEdge(
4098
+ rowId,
4099
+ column.id
4100
+ );
3596
4101
  const cellOrRowOrColumnHighlight = getCellHighlight(rowId, column.id) || rowHighlight?.color || getColumnHighlight(column.id);
3597
4102
  const isColPinned = isColumnPinned(column.id);
3598
4103
  const colPinSide = getColumnPinSide(column.id);
@@ -3607,11 +4112,12 @@ function Spreadsheet({
3607
4112
  isEditable: column.editable && enableCellEditing,
3608
4113
  isEditing,
3609
4114
  isFocused,
4115
+ isInSelection,
4116
+ selectionEdge,
3610
4117
  isRowSelected,
3611
4118
  isRowHovered,
3612
4119
  highlightColor: cellOrRowOrColumnHighlight,
3613
4120
  compactMode,
3614
- hasSelectedRows: selectedRows.size > 1,
3615
4121
  isPinned: isColPinned,
3616
4122
  pinSide: colPinSide,
3617
4123
  leftOffset: getColumnLeftOffset(column.id),