@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/.storybook/preview.tsx +1 -0
- package/.turbo/turbo-build.log +28 -0
- package/.turbo/turbo-lint.log +147 -12
- package/CHANGELOG.md +9 -0
- package/dist/index.d.mts +41 -10
- package/dist/index.d.ts +41 -10
- package/dist/index.js +670 -164
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +668 -162
- package/dist/index.mjs.map +1 -1
- package/dist/styles/globals.css +1402 -0
- package/dist/styles/globals.css.map +1 -0
- package/dist/styles/globals.d.mts +2 -0
- package/dist/styles/globals.d.ts +2 -0
- package/package.json +8 -5
- package/src/components/Spreadsheet.stories.tsx +33 -8
- package/src/components/Spreadsheet.tsx +168 -114
- package/src/components/SpreadsheetCell.tsx +95 -87
- package/src/components/SpreadsheetToolbar.tsx +5 -3
- package/src/hooks/useSpreadsheetKeyboardShortcuts.ts +58 -6
- package/src/hooks/useSpreadsheetSelection.ts +678 -0
- package/src/index.ts +3 -0
- package/src/styles/globals.css +3 -0
- package/src/types.ts +42 -8
- package/tsup.config.ts +1 -1
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
|
|
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-
|
|
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
|
-
|
|
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,
|
|
2999
|
-
const [lastSelectedRow, setLastSelectedRow] = (0,
|
|
3000
|
-
const [
|
|
3001
|
-
const [
|
|
3002
|
-
const [
|
|
3003
|
-
const [
|
|
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,
|
|
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,
|
|
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,
|
|
3030
|
-
const [spreadsheetSettings, setSpreadsheetSettings] = (0,
|
|
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,
|
|
3500
|
+
(0, import_react16.useEffect)(() => {
|
|
3042
3501
|
setSpreadsheetSettings((prev) => ({
|
|
3043
3502
|
...prev,
|
|
3044
3503
|
defaultSort: sortConfig
|
|
3045
3504
|
}));
|
|
3046
3505
|
}, [sortConfig]);
|
|
3047
|
-
const
|
|
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
|
-
|
|
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
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
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
|
-
[
|
|
3615
|
+
[navigateCell, focusedCell]
|
|
3141
3616
|
);
|
|
3142
|
-
const handleEnterEditMode = (0,
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
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
|
-
}, [
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
3722
|
+
const handleCellClick = (0, import_react16.useCallback)(
|
|
3221
3723
|
(rowId, columnId, event) => {
|
|
3222
3724
|
event.stopPropagation();
|
|
3223
|
-
|
|
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
|
-
[
|
|
3727
|
+
[handleCellMouseDown]
|
|
3233
3728
|
);
|
|
3234
|
-
const handleCellChange = (0,
|
|
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
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3739
|
+
edit: {
|
|
3740
|
+
rowId,
|
|
3741
|
+
columnId,
|
|
3742
|
+
previousValue,
|
|
3743
|
+
nextValue: newValue
|
|
3744
|
+
}
|
|
3248
3745
|
});
|
|
3249
3746
|
}
|
|
3250
|
-
|
|
3747
|
+
onCellsEdit?.([{ rowId, columnId, value: newValue }]);
|
|
3251
3748
|
markAsChanged();
|
|
3252
3749
|
},
|
|
3253
|
-
[data, getRowId, enableUndoRedo,
|
|
3750
|
+
[data, getRowId, enableUndoRedo, onCellsEdit, pushToUndoStack, markAsChanged]
|
|
3254
3751
|
);
|
|
3255
|
-
const handleConfirmEdit = (0,
|
|
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,
|
|
3761
|
+
const handleCancelEdit = (0, import_react16.useCallback)(() => {
|
|
3265
3762
|
setEditingCell(null);
|
|
3266
|
-
}, []);
|
|
3267
|
-
const handleRowClone = (0,
|
|
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,
|
|
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,
|
|
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),
|