@xcelsior/ui-spreadsheets 1.2.2 → 1.3.1

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.
Files changed (99) hide show
  1. package/.omc/state/agent-replay-0cead415-b3bd-40fd-b199-47371946c4db.jsonl +27 -0
  2. package/.omc/state/idle-notif-cooldown.json +3 -0
  3. package/.omc/state/last-tool-error.json +7 -0
  4. package/.omc/state/mission-state.json +189 -0
  5. package/.omc/state/subagent-tracking.json +125 -0
  6. package/.turbo/turbo-build.log +28 -28
  7. package/.turbo/turbo-lint.log +140 -0
  8. package/dist/index.d.mts +94 -4
  9. package/dist/index.d.ts +94 -4
  10. package/dist/index.js +2134 -1157
  11. package/dist/index.js.map +1 -1
  12. package/dist/index.mjs +2024 -1049
  13. package/dist/index.mjs.map +1 -1
  14. package/dist/styles/globals.css +156 -16
  15. package/dist/styles/globals.css.map +1 -1
  16. package/package.json +1 -1
  17. package/plans/20260330-1230-spreadsheet-features/phase-01-types-and-duplicates-hook.md +73 -0
  18. package/plans/20260330-1230-spreadsheet-features/phase-02-filter-dropdown-portal.md +90 -0
  19. package/plans/20260330-1230-spreadsheet-features/phase-03-header-overflow-menu.md +101 -0
  20. package/plans/20260330-1230-spreadsheet-features/phase-04-integration.md +193 -0
  21. package/plans/20260330-1230-spreadsheet-features/plan.md +59 -0
  22. package/src/components/ColorPickerPopover.tsx +77 -32
  23. package/src/components/ColumnHeaderActions.tsx +241 -1
  24. package/src/components/RowIndexColumnHeader.tsx +13 -17
  25. package/src/components/SelectionSummaryBar.tsx +103 -0
  26. package/src/components/Spreadsheet.stories.tsx +254 -0
  27. package/src/components/Spreadsheet.tsx +235 -190
  28. package/src/components/SpreadsheetCell.tsx +280 -42
  29. package/src/components/SpreadsheetFilterDropdown.tsx +178 -13
  30. package/src/components/SpreadsheetHeader.tsx +79 -24
  31. package/src/components/SpreadsheetSettingsModal.tsx +4 -0
  32. package/src/hooks/useSpreadsheetColumnResize.ts +143 -0
  33. package/src/hooks/useSpreadsheetDuplicates.ts +149 -0
  34. package/src/hooks/useSpreadsheetFiltering.ts +18 -1
  35. package/src/hooks/useSpreadsheetHighlighting.ts +23 -3
  36. package/src/hooks/useSpreadsheetKeyboardShortcuts.ts +16 -0
  37. package/src/hooks/useSpreadsheetPinning.ts +148 -134
  38. package/src/hooks/useSpreadsheetSelection.ts +10 -22
  39. package/src/hooks/useSpreadsheetSummary.ts +68 -0
  40. package/src/index.ts +4 -1
  41. package/src/styles/globals.css +51 -0
  42. package/src/types.ts +50 -2
  43. package/storybook-static/assets/Color-YHDXOIA2-CtQurLnT.js +1 -0
  44. package/storybook-static/assets/DocsRenderer-CFRXHY34-oxrW8Hvo.js +575 -0
  45. package/storybook-static/assets/Spreadsheet.stories-DvhhzuK4.js +1357 -0
  46. package/storybook-static/assets/chunk-XP5HYGXS-BpfKkqn7.js +1 -0
  47. package/storybook-static/assets/entry-preview-CkBGHCAN.js +2 -0
  48. package/storybook-static/assets/entry-preview-docs-ugJb6pa8.js +46 -0
  49. package/storybook-static/assets/iframe-CPp2u3vg.js +211 -0
  50. package/storybook-static/assets/index-BB9bPxRC.js +24 -0
  51. package/storybook-static/assets/index-BQFlzFLk.js +9 -0
  52. package/storybook-static/assets/index-CtvPRVHf.js +9 -0
  53. package/storybook-static/assets/index-DgH-xKnr.js +11 -0
  54. package/storybook-static/assets/index-DrFu-skq.js +6 -0
  55. package/storybook-static/assets/index-DrdPSA1J.js +240 -0
  56. package/storybook-static/assets/index-DzFBShOR.js +20 -0
  57. package/storybook-static/assets/index-v-1boR4t.js +1 -0
  58. package/storybook-static/assets/preview-B8lJiyuQ.js +34 -0
  59. package/storybook-static/assets/preview-BBWR9nbA.js +1 -0
  60. package/storybook-static/assets/preview-BWzBA1C2.js +396 -0
  61. package/storybook-static/assets/preview-Bm0S-uxO.css +1 -0
  62. package/storybook-static/assets/preview-CvbIS5ZJ.js +1 -0
  63. package/storybook-static/assets/preview-DD_OYowb.js +1 -0
  64. package/storybook-static/assets/preview-DGUiP6tS.js +7 -0
  65. package/storybook-static/assets/preview-DHQbi4pV.js +1 -0
  66. package/storybook-static/assets/preview-DwI0w3cI.js +1 -0
  67. package/storybook-static/assets/preview-DyR7iiFG.js +1 -0
  68. package/storybook-static/assets/preview-zxZ6Be2V.js +2 -0
  69. package/storybook-static/assets/react-18-Pj8skaX9.js +1 -0
  70. package/storybook-static/assets/test-utils-quxJ1Z79.js +9 -0
  71. package/storybook-static/favicon.svg +1 -0
  72. package/storybook-static/iframe.html +666 -0
  73. package/storybook-static/index.html +177 -0
  74. package/storybook-static/index.json +1 -0
  75. package/storybook-static/nunito-sans-bold-italic.woff2 +0 -0
  76. package/storybook-static/nunito-sans-bold.woff2 +0 -0
  77. package/storybook-static/nunito-sans-italic.woff2 +0 -0
  78. package/storybook-static/nunito-sans-regular.woff2 +0 -0
  79. package/storybook-static/project.json +1 -0
  80. package/storybook-static/sb-addons/essentials-actions-3/manager-bundle.js +3 -0
  81. package/storybook-static/sb-addons/essentials-backgrounds-5/manager-bundle.js +12 -0
  82. package/storybook-static/sb-addons/essentials-controls-2/manager-bundle.js +405 -0
  83. package/storybook-static/sb-addons/essentials-docs-4/manager-bundle.js +245 -0
  84. package/storybook-static/sb-addons/essentials-measure-8/manager-bundle.js +3 -0
  85. package/storybook-static/sb-addons/essentials-outline-9/manager-bundle.js +3 -0
  86. package/storybook-static/sb-addons/essentials-toolbars-7/manager-bundle.js +3 -0
  87. package/storybook-static/sb-addons/essentials-viewport-6/manager-bundle.js +3 -0
  88. package/storybook-static/sb-addons/interactions-10/manager-bundle.js +222 -0
  89. package/storybook-static/sb-addons/links-1/manager-bundle.js +3 -0
  90. package/storybook-static/sb-addons/storybook-core-core-server-presets-0/common-manager-bundle.js +3 -0
  91. package/storybook-static/sb-common-assets/favicon.svg +1 -0
  92. package/storybook-static/sb-common-assets/nunito-sans-bold-italic.woff2 +0 -0
  93. package/storybook-static/sb-common-assets/nunito-sans-bold.woff2 +0 -0
  94. package/storybook-static/sb-common-assets/nunito-sans-italic.woff2 +0 -0
  95. package/storybook-static/sb-common-assets/nunito-sans-regular.woff2 +0 -0
  96. package/storybook-static/sb-manager/globals-module-info.js +1052 -0
  97. package/storybook-static/sb-manager/globals-runtime.js +42127 -0
  98. package/storybook-static/sb-manager/globals.js +48 -0
  99. package/storybook-static/sb-manager/runtime.js +12048 -0
package/dist/index.js CHANGED
@@ -31,18 +31,20 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  ActiveFiltersDisplay: () => ActiveFiltersDisplay,
34
+ MemoizedSpreadsheetHeader: () => MemoizedSpreadsheetHeader,
34
35
  RowContextMenu: () => RowContextMenu,
35
36
  Spreadsheet: () => Spreadsheet,
36
37
  SpreadsheetCell: () => MemoizedSpreadsheetCell,
37
38
  SpreadsheetFilterDropdown: () => SpreadsheetFilterDropdown,
38
39
  SpreadsheetHeader: () => SpreadsheetHeader,
39
40
  SpreadsheetSettingsModal: () => SpreadsheetSettingsModal,
40
- SpreadsheetToolbar: () => SpreadsheetToolbar
41
+ SpreadsheetToolbar: () => SpreadsheetToolbar,
42
+ useSpreadsheetDuplicates: () => useSpreadsheetDuplicates
41
43
  });
42
44
  module.exports = __toCommonJS(index_exports);
43
45
 
44
46
  // src/components/Spreadsheet.tsx
45
- var import_react17 = require("react");
47
+ var import_react23 = require("react");
46
48
 
47
49
  // ../../../node_modules/.pnpm/react-icons@4.12.0_react@18.3.1/node_modules/react-icons/lib/esm/iconBase.js
48
50
  var import_react2 = __toESM(require("react"));
@@ -136,6 +138,9 @@ function HiCog(props) {
136
138
  function HiDotsVertical(props) {
137
139
  return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 20 20", "fill": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "d": "M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z" } }] })(props);
138
140
  }
141
+ function HiExclamation(props) {
142
+ return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 20 20", "fill": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "fillRule": "evenodd", "d": "M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z", "clipRule": "evenodd" } }] })(props);
143
+ }
139
144
  function HiEye(props) {
140
145
  return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 20 20", "fill": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "d": "M10 12a2 2 0 100-4 2 2 0 000 4z" } }, { "tag": "path", "attr": { "fillRule": "evenodd", "d": "M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z", "clipRule": "evenodd" } }] })(props);
141
146
  }
@@ -148,6 +153,9 @@ function HiReply(props) {
148
153
  function HiSortAscending(props) {
149
154
  return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 20 20", "fill": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "d": "M3 3a1 1 0 000 2h11a1 1 0 100-2H3zM3 7a1 1 0 000 2h5a1 1 0 000-2H3zM3 11a1 1 0 100 2h4a1 1 0 100-2H3zM13 16a1 1 0 102 0v-5.586l1.293 1.293a1 1 0 001.414-1.414l-3-3a1 1 0 00-1.414 0l-3 3a1 1 0 101.414 1.414L13 10.414V16z" } }] })(props);
150
155
  }
156
+ function HiSortDescending(props) {
157
+ return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 20 20", "fill": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "d": "M3 3a1 1 0 000 2h11a1 1 0 100-2H3zM3 7a1 1 0 000 2h7a1 1 0 100-2H3zM3 11a1 1 0 100 2h4a1 1 0 100-2H3zM15 8a1 1 0 10-2 0v5.586l-1.293-1.293a1 1 0 00-1.414 1.414l3 3a1 1 0 001.414 0l3-3a1 1 0 00-1.414-1.414L15 13.586V8z" } }] })(props);
158
+ }
151
159
  function HiViewBoards(props) {
152
160
  return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 20 20", "fill": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "d": "M2 4a1 1 0 011-1h2a1 1 0 011 1v12a1 1 0 01-1 1H3a1 1 0 01-1-1V4zM8 4a1 1 0 011-1h2a1 1 0 011 1v12a1 1 0 01-1 1H9a1 1 0 01-1-1V4zM15 3a1 1 0 00-1 1v12a1 1 0 001 1h2a1 1 0 001-1V4a1 1 0 00-1-1h-2z" } }] })(props);
153
161
  }
@@ -188,165 +196,186 @@ function cn(...inputs) {
188
196
  }
189
197
 
190
198
  // src/components/SpreadsheetCell.tsx
191
- var import_react4 = require("react");
192
-
193
- // src/hooks/useSpreadsheetPinning.ts
194
199
  var import_react3 = require("react");
195
- var ROW_INDEX_COLUMN_ID = "__row_index__";
196
- var ROW_INDEX_COLUMN_WIDTH = 80;
197
- var MIN_PINNED_COLUMN_WIDTH = 150;
198
- function useSpreadsheetPinning({
199
- columns,
200
- columnGroups,
201
- showRowIndex = true,
202
- defaultPinnedColumns = [],
203
- defaultPinnedRightColumns = []
204
- }) {
205
- const [pinnedColumns, setPinnedColumns] = (0, import_react3.useState)(() => {
206
- const map = /* @__PURE__ */ new Map();
207
- defaultPinnedColumns.forEach((col) => {
208
- map.set(col, "left");
209
- });
210
- defaultPinnedRightColumns.forEach((col) => {
211
- map.set(col, "right");
212
- });
213
- return map;
214
- });
215
- const [collapsedGroups, setCollapsedGroups] = (0, import_react3.useState)(/* @__PURE__ */ new Set());
216
- const isRowIndexPinned = pinnedColumns.has(ROW_INDEX_COLUMN_ID);
217
- const handleTogglePin = (0, import_react3.useCallback)((columnId) => {
218
- setPinnedColumns((prev) => {
219
- const newMap = new Map(prev);
220
- if (newMap.has(columnId)) {
221
- newMap.delete(columnId);
222
- } else {
223
- newMap.set(columnId, "left");
224
- }
225
- return newMap;
226
- });
227
- }, []);
228
- const setPinnedColumnsFromIds = (0, import_react3.useCallback)((columnIds) => {
229
- const map = /* @__PURE__ */ new Map();
230
- columnIds.forEach((col) => {
231
- map.set(col, "left");
232
- });
233
- setPinnedColumns(map);
234
- }, []);
235
- const handleToggleGroupCollapse = (0, import_react3.useCallback)((groupId) => {
236
- setCollapsedGroups((prev) => {
237
- const newSet = new Set(prev);
238
- if (newSet.has(groupId)) {
239
- newSet.delete(groupId);
240
- } else {
241
- newSet.add(groupId);
242
- }
243
- return newSet;
244
- });
245
- }, []);
246
- const visibleColumns = (0, import_react3.useMemo)(() => {
247
- if (!columns || !Array.isArray(columns) || columns.length === 0) {
248
- return [];
249
- }
250
- let result = [...columns];
251
- if (columnGroups && Array.isArray(columnGroups)) {
252
- result = result.filter((column) => {
253
- const group = columnGroups.find((g) => g.columns.includes(column.id));
254
- if (!group) return true;
255
- if (!collapsedGroups.has(group.id)) return true;
256
- return pinnedColumns.has(column.id);
200
+ var import_react_dom = require("react-dom");
201
+ var import_jsx_runtime = require("react/jsx-runtime");
202
+ var cellPaddingCompact = "px-1.5 py-0.5";
203
+ var cellPaddingNormal = "px-2.5 py-1.5";
204
+ var AutocompleteEditor = ({ value, column, compactMode, onConfirm, onCancel }) => {
205
+ const getLabel = (0, import_react3.useCallback)(
206
+ (val) => {
207
+ if (val === null || val === void 0 || val === "") return "";
208
+ if (column.getOptionLabel) return column.getOptionLabel(val);
209
+ const match = column.autocompleteOptions?.find((o) => o.value === val);
210
+ return match ? match.label : String(val);
211
+ },
212
+ [column]
213
+ );
214
+ const [searchText, setSearchText] = (0, import_react3.useState)(() => getLabel(value));
215
+ const [filteredOptions, setFilteredOptions] = (0, import_react3.useState)(
216
+ column.autocompleteOptions ?? []
217
+ );
218
+ const [focusedIndex, setFocusedIndex] = (0, import_react3.useState)(-1);
219
+ const [isOpen, setIsOpen] = (0, import_react3.useState)(true);
220
+ const [dropdownPos, setDropdownPos] = (0, import_react3.useState)(null);
221
+ const inputRef = (0, import_react3.useRef)(null);
222
+ const dropdownRef = (0, import_react3.useRef)(null);
223
+ const debounceRef = (0, import_react3.useRef)(null);
224
+ const updateDropdownPos = (0, import_react3.useCallback)(() => {
225
+ if (inputRef.current) {
226
+ const rect = inputRef.current.getBoundingClientRect();
227
+ setDropdownPos({
228
+ top: rect.bottom + 2,
229
+ left: rect.left,
230
+ width: rect.width
257
231
  });
258
232
  }
259
- const nonRowIndexPinned = Array.from(pinnedColumns.keys()).filter(
260
- (id) => id !== ROW_INDEX_COLUMN_ID
261
- );
262
- if (nonRowIndexPinned.length === 0) {
263
- return result;
264
- }
265
- const leftPinned = [];
266
- const unpinned = [];
267
- const rightPinned = [];
268
- const pinnedLeftIds = Array.from(pinnedColumns.entries()).filter(([id, side]) => side === "left" && id !== ROW_INDEX_COLUMN_ID).map(([id]) => id);
269
- const pinnedRightIds = Array.from(pinnedColumns.entries()).filter(([id, side]) => side === "right" && id !== ROW_INDEX_COLUMN_ID).map(([id]) => id);
270
- for (const column of result) {
271
- const pinSide = pinnedColumns.get(column.id);
272
- if (pinSide === "left") {
273
- leftPinned.push(column);
274
- } else if (pinSide === "right") {
275
- rightPinned.push(column);
276
- } else {
277
- unpinned.push(column);
278
- }
279
- }
280
- leftPinned.sort((a, b) => pinnedLeftIds.indexOf(a.id) - pinnedLeftIds.indexOf(b.id));
281
- rightPinned.sort((a, b) => pinnedRightIds.indexOf(a.id) - pinnedRightIds.indexOf(b.id));
282
- return [...leftPinned, ...unpinned, ...rightPinned];
283
- }, [columns, columnGroups, collapsedGroups, pinnedColumns]);
284
- const getColumnLeftOffset = (0, import_react3.useCallback)(
285
- (columnId) => {
286
- if (columnId === ROW_INDEX_COLUMN_ID) {
287
- return 0;
288
- }
289
- const pinnedLeft = Array.from(pinnedColumns.entries()).filter(([id, side]) => side === "left" && id !== ROW_INDEX_COLUMN_ID).map(([id]) => id);
290
- const index = pinnedLeft.indexOf(columnId);
291
- const isRowIndexPinnedNow = pinnedColumns.has(ROW_INDEX_COLUMN_ID);
292
- const baseOffset = showRowIndex && isRowIndexPinnedNow ? ROW_INDEX_COLUMN_WIDTH : 0;
293
- if (index === -1) return baseOffset;
294
- let offset = baseOffset;
295
- for (let i = 0; i < index; i++) {
296
- const col = columns.find((c) => c.id === pinnedLeft[i]);
297
- const configuredWidth = col?.minWidth || col?.width || MIN_PINNED_COLUMN_WIDTH;
298
- offset += Math.max(configuredWidth, MIN_PINNED_COLUMN_WIDTH);
299
- }
300
- return offset;
233
+ }, []);
234
+ (0, import_react3.useEffect)(() => {
235
+ inputRef.current?.focus();
236
+ inputRef.current?.select();
237
+ updateDropdownPos();
238
+ }, [updateDropdownPos]);
239
+ const filterClientSide = (0, import_react3.useCallback)(
240
+ (term) => {
241
+ const opts = column.autocompleteOptions ?? [];
242
+ if (!term.trim()) return opts;
243
+ const lower = term.toLowerCase();
244
+ return opts.filter((o) => o.label.toLowerCase().includes(lower));
301
245
  },
302
- [pinnedColumns, columns, showRowIndex]
246
+ [column.autocompleteOptions]
303
247
  );
304
- const isColumnPinned = (0, import_react3.useCallback)(
305
- (columnId) => {
306
- return pinnedColumns.has(columnId);
248
+ const handleSearchChange = (0, import_react3.useCallback)(
249
+ (e) => {
250
+ const term = e.target.value;
251
+ setSearchText(term);
252
+ setFocusedIndex(-1);
253
+ setIsOpen(true);
254
+ updateDropdownPos();
255
+ if (debounceRef.current) clearTimeout(debounceRef.current);
256
+ debounceRef.current = setTimeout(async () => {
257
+ if (column.onAutocompleteSearch) {
258
+ try {
259
+ const results = await column.onAutocompleteSearch(term);
260
+ setFilteredOptions(results);
261
+ } catch {
262
+ setFilteredOptions([]);
263
+ }
264
+ } else {
265
+ setFilteredOptions(filterClientSide(term));
266
+ }
267
+ }, 300);
307
268
  },
308
- [pinnedColumns]
269
+ [column, filterClientSide, updateDropdownPos]
309
270
  );
310
- const getColumnPinSide = (0, import_react3.useCallback)(
311
- (columnId) => {
312
- return pinnedColumns.get(columnId);
271
+ (0, import_react3.useEffect)(() => {
272
+ return () => {
273
+ if (debounceRef.current) clearTimeout(debounceRef.current);
274
+ };
275
+ }, []);
276
+ const selectOption = (0, import_react3.useCallback)(
277
+ (option) => {
278
+ setSearchText(option.label);
279
+ setIsOpen(false);
280
+ onConfirm?.(option.value);
313
281
  },
314
- [pinnedColumns]
282
+ [onConfirm]
315
283
  );
316
- const getColumnRightOffset = (0, import_react3.useCallback)(
317
- (columnId) => {
318
- const pinnedRight = Array.from(pinnedColumns.entries()).filter(([, side]) => side === "right").map(([id]) => id);
319
- const index = pinnedRight.indexOf(columnId);
320
- if (index === -1) return 0;
321
- let offset = 0;
322
- for (let i = pinnedRight.length - 1; i > index; i--) {
323
- const col = columns.find((c) => c.id === pinnedRight[i]);
324
- const configuredWidth = col?.minWidth || col?.width || MIN_PINNED_COLUMN_WIDTH;
325
- offset += Math.max(configuredWidth, MIN_PINNED_COLUMN_WIDTH);
284
+ const handleKeyDown = (0, import_react3.useCallback)(
285
+ (e) => {
286
+ const visibleOptions2 = filteredOptions.slice(0, 8);
287
+ if (e.key === "ArrowDown") {
288
+ e.preventDefault();
289
+ setFocusedIndex((prev) => Math.min(prev + 1, visibleOptions2.length - 1));
290
+ } else if (e.key === "ArrowUp") {
291
+ e.preventDefault();
292
+ setFocusedIndex((prev) => Math.max(prev - 1, -1));
293
+ } else if (e.key === "Enter") {
294
+ e.preventDefault();
295
+ if (focusedIndex >= 0 && visibleOptions2[focusedIndex]) {
296
+ selectOption(visibleOptions2[focusedIndex]);
297
+ } else {
298
+ onConfirm?.(value);
299
+ }
300
+ } else if (e.key === "Escape") {
301
+ e.preventDefault();
302
+ e.stopPropagation();
303
+ setIsOpen(false);
304
+ onCancel?.();
326
305
  }
327
- return offset;
328
306
  },
329
- [pinnedColumns, columns]
307
+ [filteredOptions, focusedIndex, selectOption, onConfirm, onCancel, value]
330
308
  );
331
- return {
332
- pinnedColumns,
333
- isRowIndexPinned,
334
- collapsedGroups,
335
- visibleColumns,
336
- handleTogglePin,
337
- handleToggleGroupCollapse,
338
- setPinnedColumnsFromIds,
339
- getColumnLeftOffset,
340
- getColumnRightOffset,
341
- isColumnPinned,
342
- getColumnPinSide
343
- };
344
- }
345
-
346
- // src/components/SpreadsheetCell.tsx
347
- var import_jsx_runtime = require("react/jsx-runtime");
348
- var cellPaddingCompact = "px-1 py-px";
349
- var cellPaddingNormal = "px-2 py-1";
309
+ const handleBlur = (0, import_react3.useCallback)(
310
+ (e) => {
311
+ setTimeout(() => {
312
+ if (dropdownRef.current && dropdownRef.current.contains(document.activeElement)) {
313
+ return;
314
+ }
315
+ setIsOpen(false);
316
+ onConfirm?.(value);
317
+ }, 150);
318
+ },
319
+ [onConfirm, value]
320
+ );
321
+ const visibleOptions = filteredOptions.slice(0, 8);
322
+ const dropdown = isOpen && visibleOptions.length > 0 && dropdownPos ? (0, import_react_dom.createPortal)(
323
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
324
+ "div",
325
+ {
326
+ ref: dropdownRef,
327
+ style: {
328
+ position: "fixed",
329
+ top: dropdownPos.top,
330
+ left: dropdownPos.left,
331
+ width: Math.max(dropdownPos.width, 180),
332
+ zIndex: 50
333
+ },
334
+ className: "bg-white border border-gray-200 rounded-md shadow-lg max-h-48 overflow-y-auto",
335
+ onMouseDown: (e) => e.preventDefault(),
336
+ children: visibleOptions.map((option, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
337
+ "div",
338
+ {
339
+ onMouseDown: (e) => {
340
+ e.preventDefault();
341
+ selectOption(option);
342
+ },
343
+ className: cn(
344
+ "px-3 py-1.5 cursor-pointer",
345
+ compactMode ? "text-xs" : "text-sm",
346
+ index === focusedIndex ? "bg-blue-100" : "hover:bg-blue-50"
347
+ ),
348
+ children: option.label
349
+ },
350
+ `${option.value}-${index}`
351
+ ))
352
+ }
353
+ ),
354
+ document.body
355
+ ) : null;
356
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
357
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
358
+ "input",
359
+ {
360
+ ref: inputRef,
361
+ type: "text",
362
+ value: searchText,
363
+ onChange: handleSearchChange,
364
+ onKeyDown: handleKeyDown,
365
+ onBlur: handleBlur,
366
+ autoComplete: "off",
367
+ autoCorrect: "off",
368
+ autoCapitalize: "off",
369
+ spellCheck: false,
370
+ className: cn(
371
+ "w-full border-0 bg-blue-50 focus:outline-none focus:ring-1 focus:ring-blue-500 rounded-sm",
372
+ compactMode ? "text-xs" : "text-sm"
373
+ )
374
+ }
375
+ ),
376
+ dropdown
377
+ ] });
378
+ };
350
379
  var SpreadsheetCell = ({
351
380
  value,
352
381
  column,
@@ -361,6 +390,7 @@ var SpreadsheetCell = ({
361
390
  isRowSelected = false,
362
391
  isRowHovered = false,
363
392
  highlightColor,
393
+ isDuplicate = false,
364
394
  hasComments = false,
365
395
  unresolvedCommentCount = 0,
366
396
  isCopied = false,
@@ -369,7 +399,11 @@ var SpreadsheetCell = ({
369
399
  pinSide,
370
400
  leftOffset = 0,
371
401
  rightOffset = 0,
402
+ isOddRow = false,
403
+ resolvedWidth,
404
+ pinnedZIndex,
372
405
  onClick,
406
+ onDoubleClick,
373
407
  onMouseDown,
374
408
  onMouseEnter,
375
409
  onChange,
@@ -380,17 +414,17 @@ var SpreadsheetCell = ({
380
414
  onViewComments,
381
415
  className
382
416
  }) => {
383
- const [localValue, setLocalValue] = (0, import_react4.useState)(value);
384
- const inputRef = (0, import_react4.useRef)(null);
385
- const selectRef = (0, import_react4.useRef)(null);
386
- (0, import_react4.useEffect)(() => {
417
+ const [localValue, setLocalValue] = (0, import_react3.useState)(value);
418
+ const inputRef = (0, import_react3.useRef)(null);
419
+ const selectRef = (0, import_react3.useRef)(null);
420
+ (0, import_react3.useEffect)(() => {
387
421
  setLocalValue(value);
388
422
  }, [value]);
389
- (0, import_react4.useEffect)(() => {
423
+ (0, import_react3.useEffect)(() => {
390
424
  if (isEditing) {
391
425
  if (column.type === "select") {
392
426
  selectRef.current?.focus();
393
- } else {
427
+ } else if (column.type !== "autocomplete") {
394
428
  inputRef.current?.focus();
395
429
  inputRef.current?.select();
396
430
  }
@@ -409,8 +443,10 @@ var SpreadsheetCell = ({
409
443
  };
410
444
  const getBackgroundColor = () => {
411
445
  if (highlightColor) return highlightColor;
446
+ if (isDuplicate) return "rgb(254 202 202)";
412
447
  if (isRowSelected) return "rgb(219 234 254)";
413
448
  if (isRowHovered) return "rgb(243 244 246)";
449
+ if (isOddRow) return "rgb(249 250 251)";
414
450
  return "white";
415
451
  };
416
452
  const handleCheckboxChange = (e) => {
@@ -447,12 +483,29 @@ var SpreadsheetCell = ({
447
483
  if (column.type === "number") {
448
484
  return typeof value === "number" ? value.toLocaleString() : value;
449
485
  }
486
+ if (column.type === "autocomplete") {
487
+ if (column.getOptionLabel) return column.getOptionLabel(value, row);
488
+ const match = column.autocompleteOptions?.find((o) => o.value === value);
489
+ return match ? match.label : String(value);
490
+ }
450
491
  return String(value);
451
492
  };
452
493
  const renderEditInput = () => {
453
494
  if (column.type === "checkbox") {
454
495
  return renderContent();
455
496
  }
497
+ if (column.type === "autocomplete") {
498
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
499
+ AutocompleteEditor,
500
+ {
501
+ value: localValue,
502
+ column,
503
+ compactMode,
504
+ onConfirm,
505
+ onCancel
506
+ }
507
+ );
508
+ }
456
509
  if (column.type === "select" && column.options) {
457
510
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
458
511
  "select",
@@ -465,10 +518,11 @@ var SpreadsheetCell = ({
465
518
  onConfirm?.(newValue);
466
519
  },
467
520
  onKeyDown: handleKeyDown,
468
- onBlur: () => onConfirm?.(localValue),
521
+ onClick: (e) => e.stopPropagation(),
522
+ onMouseDown: (e) => e.stopPropagation(),
469
523
  className: cn(
470
524
  "w-full border-0 bg-blue-50 focus:outline-none focus:ring-1 focus:ring-blue-500 rounded-sm",
471
- compactMode ? "text-[10px]" : "text-xs"
525
+ compactMode ? "text-xs" : "text-sm"
472
526
  ),
473
527
  children: column.options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: option, children: option }, option))
474
528
  }
@@ -493,7 +547,7 @@ var SpreadsheetCell = ({
493
547
  spellCheck: false,
494
548
  className: cn(
495
549
  "w-full border-0 bg-blue-50 focus:outline-none focus:ring-1 focus:ring-blue-500 rounded-sm",
496
- compactMode ? "text-[10px]" : "text-xs"
550
+ compactMode ? "text-xs" : "text-sm"
497
551
  )
498
552
  }
499
553
  );
@@ -517,60 +571,50 @@ var SpreadsheetCell = ({
517
571
  }
518
572
  const selectionBorderStyles = {};
519
573
  if (isInSelection && selectionEdge) {
520
- const borderColor = "rgb(59 130 246)";
521
- const borderWidth = "2px";
522
- if (selectionEdge.top) {
523
- selectionBorderStyles.borderTopColor = borderColor;
524
- selectionBorderStyles.borderTopWidth = borderWidth;
525
- }
526
- if (selectionEdge.right) {
527
- selectionBorderStyles.borderRightColor = borderColor;
528
- selectionBorderStyles.borderRightWidth = borderWidth;
529
- }
530
- if (selectionEdge.bottom) {
531
- selectionBorderStyles.borderBottomColor = borderColor;
532
- selectionBorderStyles.borderBottomWidth = borderWidth;
533
- }
534
- if (selectionEdge.left) {
535
- selectionBorderStyles.borderLeftColor = borderColor;
536
- selectionBorderStyles.borderLeftWidth = borderWidth;
574
+ const color = "rgb(59 130 246)";
575
+ const w = "2px";
576
+ const shadows = [];
577
+ if (selectionEdge.top) shadows.push(`inset 0 ${w} 0 0 ${color}`);
578
+ if (selectionEdge.bottom) shadows.push(`inset 0 -${w} 0 0 ${color}`);
579
+ if (selectionEdge.left) shadows.push(`inset ${w} 0 0 0 ${color}`);
580
+ if (selectionEdge.right) shadows.push(`inset -${w} 0 0 0 ${color}`);
581
+ if (shadows.length > 0) {
582
+ selectionBorderStyles.boxShadow = shadows.join(", ");
537
583
  }
538
584
  }
539
585
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
540
586
  "td",
541
587
  {
542
588
  onClick,
589
+ onDoubleClick,
543
590
  onMouseDown,
544
591
  onMouseEnter,
545
592
  onKeyDown: handleCellKeyDown,
546
593
  "data-cell-id": `${rowId}-${column.id}`,
594
+ "data-column-id": column.id,
547
595
  className: cn(
548
- "border border-gray-200 group cursor-pointer transition-colors select-none",
549
- compactMode ? "text-[10px]" : "text-xs",
596
+ "border border-gray-200 group cursor-pointer transition-colors select-none relative",
597
+ compactMode ? "text-xs" : "text-sm",
550
598
  cellPadding,
551
599
  column.align === "right" && "text-right",
552
600
  column.align === "center" && "text-center",
553
601
  isCopied && "animate-pulse",
554
602
  isFocused && !isInSelection && "ring-2 ring-blue-500 ring-inset",
555
603
  isInSelection && "bg-blue-50",
556
- isPinned ? "z-20" : "z-0",
604
+ !isPinned && "z-0",
557
605
  className
558
606
  ),
559
607
  style: {
560
608
  backgroundColor: isInSelection ? "rgb(239 246 255)" : getBackgroundColor(),
561
- minWidth: column.minWidth || column.width,
562
- // Pinned columns must have a fixed width so sticky offsets stay correct.
563
- // Enforce MIN_PINNED_COLUMN_WIDTH so header actions always fit.
564
- ...isPinned && {
565
- width: Math.max(
566
- column.minWidth || column.width || MIN_PINNED_COLUMN_WIDTH,
567
- MIN_PINNED_COLUMN_WIDTH
568
- ),
569
- maxWidth: Math.max(
570
- column.minWidth || column.width || MIN_PINNED_COLUMN_WIDTH,
571
- MIN_PINNED_COLUMN_WIDTH
572
- )
609
+ // Always set explicit width to prevent layout shift on selection/re-render
610
+ ...resolvedWidth ? {
611
+ width: resolvedWidth,
612
+ minWidth: resolvedWidth
613
+ } : {
614
+ width: column.width || column.minWidth,
615
+ minWidth: column.minWidth || column.width
573
616
  },
617
+ ...isPinned && pinnedZIndex !== void 0 && { zIndex: pinnedZIndex },
574
618
  ...positionStyles,
575
619
  ...selectionBorderStyles
576
620
  },
@@ -580,12 +624,13 @@ var SpreadsheetCell = ({
580
624
  {
581
625
  className: cn(
582
626
  "flex-1 truncate",
583
- isEditable && "cursor-text bg-blue-50/50 rounded"
627
+ isEditable && "cursor-text bg-blue-50 rounded px-1"
584
628
  ),
585
629
  title: String(value ?? ""),
586
630
  children: renderContent()
587
631
  }
588
632
  ),
633
+ isEditable && (column.type === "select" || column.type === "autocomplete") && !isEditing && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HiChevronDown, { className: "h-3 w-3 shrink-0 text-gray-400" }),
589
634
  !isInSelection && !isFocused && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-0.5 shrink-0", children: [
590
635
  column.highlightable !== false && onHighlight && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
591
636
  "button",
@@ -642,7 +687,7 @@ var SpreadsheetCell = ({
642
687
  );
643
688
  };
644
689
  SpreadsheetCell.displayName = "SpreadsheetCell";
645
- var MemoizedSpreadsheetCell = (0, import_react4.memo)(SpreadsheetCell, (prevProps, nextProps) => {
690
+ var MemoizedSpreadsheetCell = (0, import_react3.memo)(SpreadsheetCell, (prevProps, nextProps) => {
646
691
  if (prevProps.isEditing !== nextProps.isEditing) return false;
647
692
  if (prevProps.isFocused !== nextProps.isFocused) return false;
648
693
  if (prevProps.value !== nextProps.value) return false;
@@ -650,6 +695,7 @@ var MemoizedSpreadsheetCell = (0, import_react4.memo)(SpreadsheetCell, (prevProp
650
695
  if (prevProps.isRowSelected !== nextProps.isRowSelected) return false;
651
696
  if (prevProps.isRowHovered !== nextProps.isRowHovered) return false;
652
697
  if (prevProps.highlightColor !== nextProps.highlightColor) return false;
698
+ if (prevProps.isDuplicate !== nextProps.isDuplicate) return false;
653
699
  if (prevProps.hasComments !== nextProps.hasComments) return false;
654
700
  if (prevProps.unresolvedCommentCount !== nextProps.unresolvedCommentCount) return false;
655
701
  if (prevProps.isCopied !== nextProps.isCopied) return false;
@@ -657,6 +703,7 @@ var MemoizedSpreadsheetCell = (0, import_react4.memo)(SpreadsheetCell, (prevProp
657
703
  if (prevProps.leftOffset !== nextProps.leftOffset) return false;
658
704
  if (prevProps.rightOffset !== nextProps.rightOffset) return false;
659
705
  if (prevProps.compactMode !== nextProps.compactMode) return false;
706
+ if (prevProps.isOddRow !== nextProps.isOddRow) return false;
660
707
  const prevEdge = prevProps.selectionEdge;
661
708
  const nextEdge = nextProps.selectionEdge;
662
709
  if (prevEdge !== nextEdge) {
@@ -671,7 +718,8 @@ var MemoizedSpreadsheetCell = (0, import_react4.memo)(SpreadsheetCell, (prevProp
671
718
  MemoizedSpreadsheetCell.displayName = "MemoizedSpreadsheetCell";
672
719
 
673
720
  // src/components/SpreadsheetFilterDropdown.tsx
674
- var import_react5 = require("react");
721
+ var import_react4 = require("react");
722
+ var import_react_dom2 = require("react-dom");
675
723
  var import_jsx_runtime2 = require("react/jsx-runtime");
676
724
  var TEXT_OPERATORS = [
677
725
  { value: "contains", label: "Contains" },
@@ -710,59 +758,121 @@ var DATE_OPERATORS = [
710
758
  { value: "isEmpty", label: "Is empty" },
711
759
  { value: "isNotEmpty", label: "Is not empty" }
712
760
  ];
761
+ var DROPDOWN_WIDTH = 256;
762
+ var DROPDOWN_HEIGHT = 340;
763
+ var ARROW_SIZE = 6;
713
764
  var SpreadsheetFilterDropdown = ({
714
765
  column,
715
766
  filter,
716
767
  onFilterChange,
717
768
  onClose,
718
- className
769
+ className,
770
+ triggerRef,
771
+ hasDuplicateCheck = false
719
772
  }) => {
720
- const [textOperator, setTextOperator] = (0, import_react5.useState)(
773
+ const [showDuplicatesOnly, setShowDuplicatesOnly] = (0, import_react4.useState)(
774
+ filter?.showDuplicatesOnly ?? false
775
+ );
776
+ const [textOperator, setTextOperator] = (0, import_react4.useState)(
721
777
  filter?.textCondition?.operator || "contains"
722
778
  );
723
- const [textValue, setTextValue] = (0, import_react5.useState)(filter?.textCondition?.value || "");
724
- const [numberOperator, setNumberOperator] = (0, import_react5.useState)(
779
+ const [textValue, setTextValue] = (0, import_react4.useState)(filter?.textCondition?.value || "");
780
+ const [numberOperator, setNumberOperator] = (0, import_react4.useState)(
725
781
  filter?.numberCondition?.operator || "equals"
726
782
  );
727
- const [numberValue, setNumberValue] = (0, import_react5.useState)(
783
+ const [numberValue, setNumberValue] = (0, import_react4.useState)(
728
784
  filter?.numberCondition?.value?.toString() || ""
729
785
  );
730
- const [numberValueTo, setNumberValueTo] = (0, import_react5.useState)(
786
+ const [numberValueTo, setNumberValueTo] = (0, import_react4.useState)(
731
787
  filter?.numberCondition?.valueTo?.toString() || ""
732
788
  );
733
- const [dateOperator, setDateOperator] = (0, import_react5.useState)(
789
+ const [dateOperator, setDateOperator] = (0, import_react4.useState)(
734
790
  filter?.dateCondition?.operator || "equals"
735
791
  );
736
- const [dateValue, setDateValue] = (0, import_react5.useState)(filter?.dateCondition?.value || "");
737
- const [dateValueTo, setDateValueTo] = (0, import_react5.useState)(filter?.dateCondition?.valueTo || "");
738
- const dropdownRef = (0, import_react5.useRef)(null);
792
+ const [dateValue, setDateValue] = (0, import_react4.useState)(filter?.dateCondition?.value || "");
793
+ const [dateValueTo, setDateValueTo] = (0, import_react4.useState)(filter?.dateCondition?.valueTo || "");
794
+ const dropdownRef = (0, import_react4.useRef)(null);
795
+ const [position, setPosition] = (0, import_react4.useState)({ top: 0, left: 0, arrowLeft: DROPDOWN_WIDTH / 2, flippedUp: false });
739
796
  const isNumeric = column.type === "number";
740
797
  const isDate = column.type === "date";
741
- (0, import_react5.useEffect)(() => {
798
+ const updatePosition = (0, import_react4.useCallback)(() => {
799
+ if (!triggerRef?.current) return;
800
+ const rect = triggerRef.current.getBoundingClientRect();
801
+ let top = rect.bottom + 4;
802
+ let flippedUp = false;
803
+ if (top + DROPDOWN_HEIGHT > window.innerHeight) {
804
+ top = rect.top - DROPDOWN_HEIGHT - 4;
805
+ flippedUp = true;
806
+ }
807
+ let left = rect.left;
808
+ if (left + DROPDOWN_WIDTH > window.innerWidth) {
809
+ left = window.innerWidth - DROPDOWN_WIDTH - 8;
810
+ }
811
+ if (left < 8) {
812
+ left = 8;
813
+ }
814
+ const anchorCenter = rect.left + rect.width / 2;
815
+ const arrowLeft = Math.min(
816
+ Math.max(anchorCenter - left, ARROW_SIZE + 4),
817
+ DROPDOWN_WIDTH - ARROW_SIZE - 4
818
+ );
819
+ setPosition({ top, left, arrowLeft, flippedUp });
820
+ }, [triggerRef]);
821
+ (0, import_react4.useEffect)(() => {
822
+ if (!triggerRef?.current) return;
823
+ updatePosition();
824
+ window.addEventListener("scroll", updatePosition, true);
825
+ window.addEventListener("resize", updatePosition);
826
+ return () => {
827
+ window.removeEventListener("scroll", updatePosition, true);
828
+ window.removeEventListener("resize", updatePosition);
829
+ };
830
+ }, [updatePosition, triggerRef]);
831
+ (0, import_react4.useEffect)(() => {
742
832
  const handleClickOutside = (event) => {
743
- if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
833
+ const target = event.target;
834
+ if (dropdownRef.current && !dropdownRef.current.contains(target)) {
835
+ if (triggerRef?.current?.contains(target)) return;
744
836
  onClose();
745
837
  }
746
838
  };
747
- document.addEventListener("mousedown", handleClickOutside);
748
- return () => document.removeEventListener("mousedown", handleClickOutside);
839
+ const rafId = requestAnimationFrame(() => {
840
+ document.addEventListener("mousedown", handleClickOutside);
841
+ });
842
+ return () => {
843
+ cancelAnimationFrame(rafId);
844
+ document.removeEventListener("mousedown", handleClickOutside);
845
+ };
846
+ }, [onClose, triggerRef]);
847
+ (0, import_react4.useEffect)(() => {
848
+ const handleKeyDown = (event) => {
849
+ if (event.key === "Escape") {
850
+ onClose();
851
+ }
852
+ };
853
+ document.addEventListener("keydown", handleKeyDown);
854
+ return () => document.removeEventListener("keydown", handleKeyDown);
749
855
  }, [onClose]);
750
856
  const handleApplyFilter = () => {
751
857
  let newFilter;
752
858
  if (isNumeric) {
753
859
  const needsValue = !["isEmpty", "isNotEmpty"].includes(numberOperator);
754
- if (needsValue && !numberValue) {
860
+ if (needsValue && !numberValue && !showDuplicatesOnly) {
755
861
  onFilterChange(void 0);
756
862
  onClose();
757
863
  return;
758
864
  }
759
865
  newFilter = {
760
- numberCondition: {
866
+ numberCondition: needsValue || !numberValue ? {
761
867
  operator: numberOperator,
762
868
  value: numberValue ? parseFloat(numberValue) : void 0,
763
869
  valueTo: numberValueTo ? parseFloat(numberValueTo) : void 0
764
- }
870
+ } : void 0,
871
+ ...showDuplicatesOnly && { showDuplicatesOnly: true }
765
872
  };
873
+ if (!newFilter.numberCondition && !newFilter.showDuplicatesOnly) {
874
+ newFilter = void 0;
875
+ }
766
876
  } else if (isDate) {
767
877
  const needsValue = ![
768
878
  "isEmpty",
@@ -775,7 +885,7 @@ var SpreadsheetFilterDropdown = ({
775
885
  "lastMonth",
776
886
  "thisYear"
777
887
  ].includes(dateOperator);
778
- if (needsValue && !dateValue) {
888
+ if (needsValue && !dateValue && !showDuplicatesOnly) {
779
889
  onFilterChange(void 0);
780
890
  onClose();
781
891
  return;
@@ -785,26 +895,32 @@ var SpreadsheetFilterDropdown = ({
785
895
  operator: dateOperator,
786
896
  value: dateValue || void 0,
787
897
  valueTo: dateValueTo || void 0
788
- }
898
+ },
899
+ ...showDuplicatesOnly && { showDuplicatesOnly: true }
789
900
  };
790
901
  } else {
791
902
  const needsValue = !["isEmpty", "isNotEmpty"].includes(textOperator);
792
- if (needsValue && !textValue) {
903
+ if (needsValue && !textValue && !showDuplicatesOnly) {
793
904
  onFilterChange(void 0);
794
905
  onClose();
795
906
  return;
796
907
  }
797
908
  newFilter = {
798
- textCondition: {
909
+ textCondition: needsValue || !textValue ? {
799
910
  operator: textOperator,
800
911
  value: textValue || void 0
801
- }
912
+ } : void 0,
913
+ ...showDuplicatesOnly && { showDuplicatesOnly: true }
802
914
  };
915
+ if (!newFilter.textCondition && !newFilter.showDuplicatesOnly) {
916
+ newFilter = void 0;
917
+ }
803
918
  }
804
919
  onFilterChange(newFilter);
805
920
  onClose();
806
921
  };
807
922
  const handleClearFilter = () => {
923
+ setShowDuplicatesOnly(false);
808
924
  setTextValue("");
809
925
  setNumberValue("");
810
926
  setNumberValueTo("");
@@ -813,6 +929,12 @@ var SpreadsheetFilterDropdown = ({
813
929
  onFilterChange(void 0);
814
930
  onClose();
815
931
  };
932
+ const handleFilterKeyDown = (e) => {
933
+ if (e.key === "Enter") {
934
+ e.preventDefault();
935
+ handleApplyFilter();
936
+ }
937
+ };
816
938
  const textNeedsValue = !["isEmpty", "isNotEmpty"].includes(textOperator);
817
939
  const numberNeedsValue = !["isEmpty", "isNotEmpty"].includes(numberOperator);
818
940
  const dateNeedsValue = ![
@@ -826,20 +948,57 @@ var SpreadsheetFilterDropdown = ({
826
948
  "lastMonth",
827
949
  "thisYear"
828
950
  ].includes(dateOperator);
829
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
951
+ const dropdownContent = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
830
952
  "div",
831
953
  {
832
954
  ref: dropdownRef,
833
955
  className: cn(
834
- "absolute top-full left-0 mt-1 bg-white border border-gray-200 shadow-lg rounded-lg w-64 overflow-hidden flex flex-col z-[100]",
956
+ "bg-white border border-gray-200 shadow-lg rounded-lg w-64 overflow-hidden flex flex-col",
957
+ !triggerRef && "absolute top-full left-0 mt-1 z-[100]",
835
958
  className
836
959
  ),
960
+ style: triggerRef ? { position: "fixed", top: position.top, left: position.left, zIndex: 9999 } : void 0,
837
961
  onClick: (e) => e.stopPropagation(),
838
962
  children: [
963
+ triggerRef && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
964
+ "div",
965
+ {
966
+ className: "absolute pointer-events-none",
967
+ style: position.flippedUp ? {
968
+ bottom: -ARROW_SIZE,
969
+ left: position.arrowLeft - ARROW_SIZE,
970
+ width: 0,
971
+ height: 0,
972
+ borderLeft: `${ARROW_SIZE}px solid transparent`,
973
+ borderRight: `${ARROW_SIZE}px solid transparent`,
974
+ borderTop: `${ARROW_SIZE}px solid #e5e7eb`
975
+ } : {
976
+ top: -ARROW_SIZE,
977
+ left: position.arrowLeft - ARROW_SIZE,
978
+ width: 0,
979
+ height: 0,
980
+ borderLeft: `${ARROW_SIZE}px solid transparent`,
981
+ borderRight: `${ARROW_SIZE}px solid transparent`,
982
+ borderBottom: `${ARROW_SIZE}px solid #e5e7eb`
983
+ }
984
+ }
985
+ ),
839
986
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "px-3 py-2 border-b border-gray-200 bg-gray-50", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "text-xs font-medium text-gray-700", children: [
840
987
  "Filter: ",
841
988
  column.label
842
989
  ] }) }),
990
+ hasDuplicateCheck && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "px-3 pt-2 pb-2 border-b border-gray-200", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer select-none", children: [
991
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
992
+ "input",
993
+ {
994
+ type: "checkbox",
995
+ checked: showDuplicatesOnly,
996
+ onChange: (e) => setShowDuplicatesOnly(e.target.checked),
997
+ className: "h-3 w-3 rounded border-gray-300 text-red-600 focus:ring-red-500"
998
+ }
999
+ ),
1000
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-xs text-gray-700", children: "Show only duplicates" })
1001
+ ] }) }),
843
1002
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "p-3 space-y-3", children: isNumeric ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
844
1003
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
845
1004
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { className: "text-xs text-gray-500 mb-1 block", children: "Condition" }),
@@ -862,6 +1021,7 @@ var SpreadsheetFilterDropdown = ({
862
1021
  placeholder: "Enter value",
863
1022
  value: numberValue,
864
1023
  onChange: (e) => setNumberValue(e.target.value),
1024
+ onKeyDown: handleFilterKeyDown,
865
1025
  className: "w-full px-2 py-1.5 text-xs border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-blue-500"
866
1026
  }
867
1027
  )
@@ -875,6 +1035,7 @@ var SpreadsheetFilterDropdown = ({
875
1035
  placeholder: "Enter end value",
876
1036
  value: numberValueTo,
877
1037
  onChange: (e) => setNumberValueTo(e.target.value),
1038
+ onKeyDown: handleFilterKeyDown,
878
1039
  className: "w-full px-2 py-1.5 text-xs border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-blue-500"
879
1040
  }
880
1041
  )
@@ -900,6 +1061,7 @@ var SpreadsheetFilterDropdown = ({
900
1061
  type: "date",
901
1062
  value: dateValue,
902
1063
  onChange: (e) => setDateValue(e.target.value),
1064
+ onKeyDown: handleFilterKeyDown,
903
1065
  className: "w-full px-2 py-1.5 text-xs border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-blue-500"
904
1066
  }
905
1067
  )
@@ -912,6 +1074,7 @@ var SpreadsheetFilterDropdown = ({
912
1074
  type: "date",
913
1075
  value: dateValueTo,
914
1076
  onChange: (e) => setDateValueTo(e.target.value),
1077
+ onKeyDown: handleFilterKeyDown,
915
1078
  className: "w-full px-2 py-1.5 text-xs border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-blue-500"
916
1079
  }
917
1080
  )
@@ -938,6 +1101,7 @@ var SpreadsheetFilterDropdown = ({
938
1101
  placeholder: "Enter text",
939
1102
  value: textValue,
940
1103
  onChange: (e) => setTextValue(e.target.value),
1104
+ onKeyDown: handleFilterKeyDown,
941
1105
  className: "w-full px-2 py-1.5 text-xs border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-blue-500"
942
1106
  }
943
1107
  )
@@ -972,11 +1136,15 @@ var SpreadsheetFilterDropdown = ({
972
1136
  ]
973
1137
  }
974
1138
  );
1139
+ if (triggerRef) {
1140
+ return (0, import_react_dom2.createPortal)(dropdownContent, document.body);
1141
+ }
1142
+ return dropdownContent;
975
1143
  };
976
1144
  SpreadsheetFilterDropdown.displayName = "SpreadsheetFilterDropdown";
977
1145
 
978
1146
  // src/components/SpreadsheetToolbar.tsx
979
- var import_react6 = __toESM(require("react"));
1147
+ var import_react5 = __toESM(require("react"));
980
1148
 
981
1149
  // src/components/ActiveFiltersDisplay.tsx
982
1150
  var import_jsx_runtime3 = require("react/jsx-runtime");
@@ -1199,9 +1367,9 @@ var SpreadsheetToolbar = ({
1199
1367
  onToggleFiltersPanel,
1200
1368
  className
1201
1369
  }) => {
1202
- const [showMoreMenu, setShowMoreMenu] = import_react6.default.useState(false);
1203
- const menuRef = import_react6.default.useRef(null);
1204
- import_react6.default.useEffect(() => {
1370
+ const [showMoreMenu, setShowMoreMenu] = import_react5.default.useState(false);
1371
+ const menuRef = import_react5.default.useRef(null);
1372
+ import_react5.default.useEffect(() => {
1205
1373
  const handleClickOutside = (event) => {
1206
1374
  if (menuRef.current && !menuRef.current.contains(event.target)) {
1207
1375
  setShowMoreMenu(false);
@@ -1501,6 +1669,13 @@ var SpreadsheetToolbar = ({
1501
1669
  };
1502
1670
  SpreadsheetToolbar.displayName = "SpreadsheetToolbar";
1503
1671
 
1672
+ // src/components/SpreadsheetHeader.tsx
1673
+ var import_react7 = __toESM(require("react"));
1674
+
1675
+ // src/components/ColumnHeaderActions.tsx
1676
+ var import_react6 = require("react");
1677
+ var import_react_dom3 = require("react-dom");
1678
+
1504
1679
  // ../../../node_modules/.pnpm/react-icons@4.12.0_react@18.3.1/node_modules/react-icons/md/index.esm.js
1505
1680
  function MdPushPin(props) {
1506
1681
  return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 24 24" }, "child": [{ "tag": "path", "attr": { "fill": "none", "d": "M0 0h24v24H0z" } }, { "tag": "path", "attr": { "fillRule": "evenodd", "d": "M16 9V4h1c.55 0 1-.45 1-1s-.45-1-1-1H7c-.55 0-1 .45-1 1s.45 1 1 1h1v5c0 1.66-1.34 3-3 3v2h5.97v7l1 1 1-1v-7H19v-2c-1.66 0-3-1.34-3-3z" } }] })(props);
@@ -1518,6 +1693,9 @@ function ColumnHeaderActions({
1518
1693
  hasActiveFilter = false,
1519
1694
  hasActiveHighlight = false,
1520
1695
  isPinned = false,
1696
+ enableSorting = false,
1697
+ sortDirection = null,
1698
+ onSortClick,
1521
1699
  onFilterClick,
1522
1700
  onHighlightClick,
1523
1701
  onPinClick,
@@ -1525,82 +1703,281 @@ function ColumnHeaderActions({
1525
1703
  highlightTitle = "Highlight column",
1526
1704
  pinnedTitle = "Unpin column",
1527
1705
  unpinnedTitle = "Pin column",
1528
- className
1706
+ className,
1707
+ resolvedWidth,
1708
+ enableDuplicateCheck = false,
1709
+ hasDuplicateCheck = false,
1710
+ onDuplicateCheckClick
1529
1711
  }) {
1530
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: cn("flex items-center gap-0.5", className), children: [
1531
- enableFiltering && onFilterClick && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1532
- "button",
1533
- {
1534
- type: "button",
1535
- onClick: (e) => {
1536
- e.stopPropagation();
1537
- onFilterClick();
1538
- },
1539
- className: cn(
1540
- "p-0.5 hover:bg-gray-200 rounded transition-opacity",
1541
- hasActiveFilter ? "text-blue-600 opacity-100" : "text-gray-400 opacity-0 group-hover:opacity-100"
1542
- ),
1543
- title: filterTitle,
1544
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(HiFilter, { className: "h-3 w-3" })
1545
- }
1546
- ),
1547
- enableHighlighting && onHighlightClick && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1548
- "button",
1549
- {
1550
- type: "button",
1551
- onClick: (e) => {
1552
- e.stopPropagation();
1553
- onHighlightClick();
1554
- },
1555
- className: cn(
1556
- "p-0.5 hover:bg-gray-200 rounded transition-opacity",
1557
- hasActiveHighlight ? "text-amber-500 opacity-100" : "text-gray-400 opacity-0 group-hover:opacity-100"
1558
- ),
1559
- title: highlightTitle,
1560
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(AiFillHighlight, { className: "h-3 w-3" })
1561
- }
1562
- ),
1563
- enablePinning && onPinClick && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1564
- "button",
1565
- {
1566
- type: "button",
1567
- onClick: (e) => {
1568
- e.stopPropagation();
1569
- onPinClick();
1570
- },
1571
- className: cn(
1572
- "p-0.5 hover:bg-gray-200 rounded transition-opacity",
1573
- isPinned ? "text-blue-600 opacity-100" : "text-gray-400 opacity-0 group-hover:opacity-100"
1574
- ),
1575
- title: isPinned ? pinnedTitle : unpinnedTitle,
1576
- children: isPinned ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MdPushPin, { className: "h-3 w-3" }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MdOutlinePushPin, { className: "h-3 w-3" })
1712
+ const [overflowOpen, setOverflowOpen] = (0, import_react6.useState)(false);
1713
+ const overflowRef = (0, import_react6.useRef)(null);
1714
+ const triggerRef = (0, import_react6.useRef)(null);
1715
+ const [dropdownPos, setDropdownPos] = (0, import_react6.useState)(null);
1716
+ const isCompact = resolvedWidth !== void 0 && resolvedWidth < 80;
1717
+ (0, import_react6.useEffect)(() => {
1718
+ if (!overflowOpen) return;
1719
+ const handler = (e) => {
1720
+ if (overflowRef.current && !overflowRef.current.contains(e.target) && triggerRef.current && !triggerRef.current.contains(e.target)) {
1721
+ setOverflowOpen(false);
1577
1722
  }
1578
- )
1579
- ] });
1580
- }
1581
- ColumnHeaderActions.displayName = "ColumnHeaderActions";
1582
-
1583
- // src/components/SpreadsheetHeader.tsx
1584
- var import_jsx_runtime6 = require("react/jsx-runtime");
1585
- var cellPaddingCompact2 = "px-1 py-0.5";
1586
- var cellPaddingNormal2 = "px-2 py-1.5";
1587
- var SpreadsheetHeader = ({
1588
- column,
1589
- sortConfig,
1590
- hasActiveFilter = false,
1591
- isPinned = false,
1592
- pinSide,
1593
- leftOffset = 0,
1594
- rightOffset = 0,
1595
- highlightColor,
1596
- compactMode = false,
1597
- onClick,
1598
- onFilterClick,
1599
- onPinClick,
1600
- onHighlightClick,
1601
- className,
1602
- children
1723
+ };
1724
+ document.addEventListener("mousedown", handler);
1725
+ return () => document.removeEventListener("mousedown", handler);
1726
+ }, [overflowOpen]);
1727
+ const openOverflow = (e) => {
1728
+ e.stopPropagation();
1729
+ if (!overflowOpen && triggerRef.current) {
1730
+ const rect = triggerRef.current.getBoundingClientRect();
1731
+ setDropdownPos({
1732
+ top: rect.bottom + window.scrollY + 4,
1733
+ right: window.innerWidth - rect.right - window.scrollX
1734
+ });
1735
+ }
1736
+ setOverflowOpen((prev) => !prev);
1737
+ };
1738
+ if (isCompact) {
1739
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: cn("flex items-center", className), children: [
1740
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1741
+ "button",
1742
+ {
1743
+ ref: triggerRef,
1744
+ type: "button",
1745
+ onClick: openOverflow,
1746
+ className: "p-0.5 hover:bg-gray-200 rounded text-gray-400 opacity-0 group-hover:opacity-100 transition-opacity",
1747
+ title: "Column actions",
1748
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(HiDotsVertical, { className: "h-3 w-3" })
1749
+ }
1750
+ ),
1751
+ overflowOpen && dropdownPos && (0, import_react_dom3.createPortal)(
1752
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1753
+ "div",
1754
+ {
1755
+ ref: overflowRef,
1756
+ style: {
1757
+ position: "absolute",
1758
+ top: dropdownPos.top,
1759
+ right: dropdownPos.right
1760
+ },
1761
+ className: "bg-white border border-gray-200 shadow-lg rounded-md py-1 w-44 z-[9999]",
1762
+ children: [
1763
+ enableSorting && onSortClick && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1764
+ "button",
1765
+ {
1766
+ type: "button",
1767
+ onClick: (e) => {
1768
+ e.stopPropagation();
1769
+ onSortClick();
1770
+ setOverflowOpen(false);
1771
+ },
1772
+ className: cn(
1773
+ "flex items-center gap-2 w-full px-3 py-1.5 text-xs hover:bg-gray-100 text-left",
1774
+ sortDirection ? "text-blue-600" : "text-gray-700"
1775
+ ),
1776
+ children: [
1777
+ sortDirection === "desc" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(HiSortDescending, { className: "h-3.5 w-3.5 shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(HiSortAscending, { className: "h-3.5 w-3.5 shrink-0" }),
1778
+ sortDirection === "asc" ? "Sorted ascending" : sortDirection === "desc" ? "Sorted descending" : "Sort"
1779
+ ]
1780
+ }
1781
+ ),
1782
+ enableFiltering && onFilterClick && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1783
+ "button",
1784
+ {
1785
+ type: "button",
1786
+ onClick: (e) => {
1787
+ e.stopPropagation();
1788
+ onFilterClick();
1789
+ setOverflowOpen(false);
1790
+ },
1791
+ className: cn(
1792
+ "flex items-center gap-2 w-full px-3 py-1.5 text-xs hover:bg-gray-100 text-left",
1793
+ hasActiveFilter ? "text-blue-600" : "text-gray-700"
1794
+ ),
1795
+ children: [
1796
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(HiFilter, { className: "h-3.5 w-3.5 shrink-0" }),
1797
+ "Filter",
1798
+ hasActiveFilter && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "ml-auto h-1.5 w-1.5 rounded-full bg-blue-500 shrink-0" })
1799
+ ]
1800
+ }
1801
+ ),
1802
+ enableHighlighting && onHighlightClick && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1803
+ "button",
1804
+ {
1805
+ type: "button",
1806
+ onClick: (e) => {
1807
+ e.stopPropagation();
1808
+ onHighlightClick();
1809
+ setOverflowOpen(false);
1810
+ },
1811
+ className: cn(
1812
+ "flex items-center gap-2 w-full px-3 py-1.5 text-xs hover:bg-gray-100 text-left",
1813
+ hasActiveHighlight ? "text-amber-500" : "text-gray-700"
1814
+ ),
1815
+ children: [
1816
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(AiFillHighlight, { className: "h-3.5 w-3.5 shrink-0" }),
1817
+ "Highlight"
1818
+ ]
1819
+ }
1820
+ ),
1821
+ enablePinning && onPinClick && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1822
+ "button",
1823
+ {
1824
+ type: "button",
1825
+ onClick: (e) => {
1826
+ e.stopPropagation();
1827
+ onPinClick();
1828
+ setOverflowOpen(false);
1829
+ },
1830
+ className: cn(
1831
+ "flex items-center gap-2 w-full px-3 py-1.5 text-xs hover:bg-gray-100 text-left",
1832
+ isPinned ? "text-blue-600" : "text-gray-700"
1833
+ ),
1834
+ children: [
1835
+ isPinned ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MdPushPin, { className: "h-3.5 w-3.5 shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MdOutlinePushPin, { className: "h-3.5 w-3.5 shrink-0" }),
1836
+ isPinned ? "Unpin" : "Pin"
1837
+ ]
1838
+ }
1839
+ ),
1840
+ enableDuplicateCheck && onDuplicateCheckClick && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1841
+ "button",
1842
+ {
1843
+ type: "button",
1844
+ onClick: (e) => {
1845
+ e.stopPropagation();
1846
+ onDuplicateCheckClick();
1847
+ setOverflowOpen(false);
1848
+ },
1849
+ className: cn(
1850
+ "flex items-center gap-2 w-full px-3 py-1.5 text-xs hover:bg-gray-100 text-left",
1851
+ hasDuplicateCheck ? "text-purple-600" : "text-gray-700"
1852
+ ),
1853
+ children: [
1854
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(HiExclamation, { className: "h-3.5 w-3.5 shrink-0" }),
1855
+ "Check Duplicates"
1856
+ ]
1857
+ }
1858
+ )
1859
+ ]
1860
+ }
1861
+ ),
1862
+ document.body
1863
+ )
1864
+ ] });
1865
+ }
1866
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: cn("flex items-center gap-0.5", className), children: [
1867
+ enableSorting && onSortClick && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1868
+ "button",
1869
+ {
1870
+ type: "button",
1871
+ onClick: (e) => {
1872
+ e.stopPropagation();
1873
+ onSortClick();
1874
+ },
1875
+ className: cn(
1876
+ "p-0.5 hover:bg-gray-200 rounded transition-opacity",
1877
+ sortDirection ? "text-blue-600 opacity-100" : "text-gray-400 opacity-0 group-hover:opacity-100"
1878
+ ),
1879
+ title: sortDirection === "asc" ? "Sorted ascending" : sortDirection === "desc" ? "Sorted descending" : "Sort column",
1880
+ children: sortDirection === "desc" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(HiSortDescending, { className: "h-3 w-3" }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(HiSortAscending, { className: "h-3 w-3" })
1881
+ }
1882
+ ),
1883
+ enableFiltering && onFilterClick && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1884
+ "button",
1885
+ {
1886
+ type: "button",
1887
+ onClick: (e) => {
1888
+ e.stopPropagation();
1889
+ onFilterClick();
1890
+ },
1891
+ className: cn(
1892
+ "p-0.5 hover:bg-gray-200 rounded transition-opacity",
1893
+ hasActiveFilter ? "text-blue-600 opacity-100" : "text-gray-400 opacity-0 group-hover:opacity-100"
1894
+ ),
1895
+ title: filterTitle,
1896
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(HiFilter, { className: "h-3 w-3" })
1897
+ }
1898
+ ),
1899
+ enableHighlighting && onHighlightClick && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1900
+ "button",
1901
+ {
1902
+ type: "button",
1903
+ onClick: (e) => {
1904
+ e.stopPropagation();
1905
+ onHighlightClick();
1906
+ },
1907
+ className: cn(
1908
+ "p-0.5 hover:bg-gray-200 rounded transition-opacity",
1909
+ hasActiveHighlight ? "text-amber-500 opacity-100" : "text-gray-400 opacity-0 group-hover:opacity-100"
1910
+ ),
1911
+ title: highlightTitle,
1912
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(AiFillHighlight, { className: "h-3 w-3" })
1913
+ }
1914
+ ),
1915
+ enablePinning && onPinClick && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1916
+ "button",
1917
+ {
1918
+ type: "button",
1919
+ onClick: (e) => {
1920
+ e.stopPropagation();
1921
+ onPinClick();
1922
+ },
1923
+ className: cn(
1924
+ "p-0.5 hover:bg-gray-200 rounded transition-opacity",
1925
+ isPinned ? "text-blue-600 opacity-100" : "text-gray-400 opacity-0 group-hover:opacity-100"
1926
+ ),
1927
+ title: isPinned ? pinnedTitle : unpinnedTitle,
1928
+ children: isPinned ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MdPushPin, { className: "h-3 w-3" }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MdOutlinePushPin, { className: "h-3 w-3" })
1929
+ }
1930
+ ),
1931
+ enableDuplicateCheck && onDuplicateCheckClick && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1932
+ "button",
1933
+ {
1934
+ type: "button",
1935
+ onClick: (e) => {
1936
+ e.stopPropagation();
1937
+ onDuplicateCheckClick();
1938
+ },
1939
+ className: cn(
1940
+ "p-0.5 hover:bg-gray-200 rounded transition-opacity",
1941
+ hasDuplicateCheck ? "text-purple-600 opacity-100" : "text-gray-400 opacity-0 group-hover:opacity-100"
1942
+ ),
1943
+ title: "Check duplicates",
1944
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(HiExclamation, { className: "h-3 w-3" })
1945
+ }
1946
+ )
1947
+ ] });
1948
+ }
1949
+ ColumnHeaderActions.displayName = "ColumnHeaderActions";
1950
+
1951
+ // src/components/SpreadsheetHeader.tsx
1952
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1953
+ var cellPaddingCompact2 = "px-1.5 py-1";
1954
+ var cellPaddingNormal2 = "px-2 py-2";
1955
+ var SpreadsheetHeader = ({
1956
+ column,
1957
+ sortConfig,
1958
+ hasActiveFilter = false,
1959
+ isPinned = false,
1960
+ pinSide,
1961
+ leftOffset = 0,
1962
+ rightOffset = 0,
1963
+ highlightColor,
1964
+ compactMode = false,
1965
+ onClick,
1966
+ pinnedZIndex,
1967
+ onSortClick,
1968
+ onFilterClick,
1969
+ onPinClick,
1970
+ onHighlightClick,
1971
+ hasDuplicateCheck = false,
1972
+ onDuplicateCheckClick,
1973
+ duplicateCount,
1974
+ resizeHandleProps,
1975
+ resolvedWidth,
1976
+ topOffset = 0,
1977
+ className,
1978
+ children
1603
1979
  }) => {
1980
+ const thRef = (0, import_react7.useRef)(null);
1604
1981
  const isSorted = sortConfig?.columnId === column.id;
1605
1982
  const sortDirection = isSorted ? sortConfig.direction : null;
1606
1983
  const cellPadding = compactMode ? cellPaddingCompact2 : cellPaddingNormal2;
@@ -1616,46 +1993,55 @@ var SpreadsheetHeader = ({
1616
1993
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1617
1994
  "th",
1618
1995
  {
1619
- onClick: column.sortable ? onClick : void 0,
1996
+ ref: thRef,
1997
+ "data-column-id": column.id,
1998
+ onClick,
1620
1999
  className: cn(
1621
- "border border-gray-200 font-semibold text-gray-700 sticky group",
1622
- compactMode ? "text-[10px]" : "text-xs",
2000
+ "border border-gray-200 font-semibold text-gray-700 group relative cursor-pointer",
2001
+ isPinned && "sticky",
2002
+ compactMode ? "text-xs" : "text-sm",
1623
2003
  cellPadding,
1624
2004
  column.align === "right" && "text-right",
1625
2005
  column.align === "center" && "text-center",
1626
- column.sortable && "cursor-pointer hover:bg-gray-100",
1627
- isPinned ? "z-30" : "z-20",
2006
+ "hover:bg-gray-100",
2007
+ !isPinned && "z-20",
1628
2008
  className
1629
2009
  ),
1630
2010
  style: {
1631
2011
  backgroundColor: highlightColor || "rgb(243 244 246)",
1632
2012
  // gray-100
1633
- minWidth: column.minWidth || column.width,
1634
- // Pinned columns must have a fixed width so sticky offsets stay correct.
1635
- // Enforce MIN_PINNED_COLUMN_WIDTH so header actions (pin/filter/highlight) always fit.
1636
- ...isPinned && {
1637
- width: Math.max(
1638
- column.minWidth || column.width || MIN_PINNED_COLUMN_WIDTH,
1639
- MIN_PINNED_COLUMN_WIDTH
1640
- ),
1641
- maxWidth: Math.max(
1642
- column.minWidth || column.width || MIN_PINNED_COLUMN_WIDTH,
1643
- MIN_PINNED_COLUMN_WIDTH
1644
- )
2013
+ // Always set explicit width to prevent layout shift on selection/re-render
2014
+ ...resolvedWidth ? {
2015
+ width: resolvedWidth,
2016
+ minWidth: resolvedWidth
2017
+ } : {
2018
+ width: column.width || column.minWidth,
2019
+ minWidth: column.minWidth || column.width
1645
2020
  },
1646
- top: 0,
1647
- // For sticky header
2021
+ ...isPinned && pinnedZIndex !== void 0 && { zIndex: pinnedZIndex + 10 },
2022
+ // +10 so headers are above cells
1648
2023
  ...positionStyles
1649
2024
  },
1650
2025
  children: [
1651
2026
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex items-center justify-between gap-1", children: [
1652
2027
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "flex-1 flex items-center gap-1", children: [
1653
2028
  column.label,
1654
- isSorted && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-blue-600", children: sortDirection === "asc" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(HiChevronUp, { className: "h-3 w-3" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(HiChevronDown, { className: "h-3 w-3" }) })
2029
+ isSorted && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-blue-600", children: sortDirection === "asc" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(HiChevronUp, { className: "h-3 w-3" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(HiChevronDown, { className: "h-3 w-3" }) }),
2030
+ hasDuplicateCheck && duplicateCount != null && duplicateCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2031
+ "span",
2032
+ {
2033
+ className: "inline-flex items-center px-1 py-0.5 rounded-full bg-red-100 text-red-600 text-[10px] font-medium leading-none",
2034
+ title: `${duplicateCount} duplicate values found`,
2035
+ children: duplicateCount
2036
+ }
2037
+ )
1655
2038
  ] }),
1656
2039
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1657
2040
  ColumnHeaderActions,
1658
2041
  {
2042
+ enableSorting: column.sortable,
2043
+ sortDirection: isSorted ? sortDirection : null,
2044
+ onSortClick,
1659
2045
  enableFiltering: column.filterable,
1660
2046
  enableHighlighting: column.highlightable !== false && !!onHighlightClick,
1661
2047
  enablePinning: column.pinnable !== false,
@@ -1664,21 +2050,249 @@ var SpreadsheetHeader = ({
1664
2050
  isPinned,
1665
2051
  onFilterClick,
1666
2052
  onHighlightClick,
1667
- onPinClick
2053
+ onPinClick,
2054
+ resolvedWidth,
2055
+ enableDuplicateCheck: true,
2056
+ hasDuplicateCheck,
2057
+ onDuplicateCheckClick
1668
2058
  }
1669
2059
  )
1670
2060
  ] }),
1671
- children
2061
+ children && import_react7.default.Children.map(
2062
+ children,
2063
+ (child) => import_react7.default.isValidElement(child) ? import_react7.default.cloneElement(child, { triggerRef: thRef }) : child
2064
+ ),
2065
+ resizeHandleProps && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2066
+ "div",
2067
+ {
2068
+ onMouseDown: resizeHandleProps.onMouseDown,
2069
+ style: resizeHandleProps.style,
2070
+ className: resizeHandleProps.className
2071
+ }
2072
+ )
1672
2073
  ]
1673
2074
  }
1674
2075
  );
1675
2076
  };
1676
2077
  SpreadsheetHeader.displayName = "SpreadsheetHeader";
2078
+ var MemoizedSpreadsheetHeader = (0, import_react7.memo)(SpreadsheetHeader, (prevProps, nextProps) => {
2079
+ if (prevProps.column.id !== nextProps.column.id) return false;
2080
+ if (prevProps.sortConfig?.columnId !== nextProps.sortConfig?.columnId) return false;
2081
+ if (prevProps.sortConfig?.direction !== nextProps.sortConfig?.direction) return false;
2082
+ if (prevProps.hasActiveFilter !== nextProps.hasActiveFilter) return false;
2083
+ if (prevProps.isPinned !== nextProps.isPinned) return false;
2084
+ if (prevProps.pinSide !== nextProps.pinSide) return false;
2085
+ if (prevProps.leftOffset !== nextProps.leftOffset) return false;
2086
+ if (prevProps.rightOffset !== nextProps.rightOffset) return false;
2087
+ if (prevProps.highlightColor !== nextProps.highlightColor) return false;
2088
+ if (prevProps.compactMode !== nextProps.compactMode) return false;
2089
+ if (prevProps.pinnedZIndex !== nextProps.pinnedZIndex) return false;
2090
+ if (prevProps.resolvedWidth !== nextProps.resolvedWidth) return false;
2091
+ if (prevProps.topOffset !== nextProps.topOffset) return false;
2092
+ if (prevProps.hasDuplicateCheck !== nextProps.hasDuplicateCheck) return false;
2093
+ if (prevProps.duplicateCount !== nextProps.duplicateCount) return false;
2094
+ if (prevProps.children !== nextProps.children) return false;
2095
+ return true;
2096
+ });
2097
+ MemoizedSpreadsheetHeader.displayName = "MemoizedSpreadsheetHeader";
2098
+
2099
+ // src/hooks/useSpreadsheetPinning.ts
2100
+ var import_react8 = require("react");
2101
+ var ROW_INDEX_COLUMN_ID = "__row_index__";
2102
+ var ROW_INDEX_COLUMN_WIDTH = 90;
2103
+ function useSpreadsheetPinning({
2104
+ columns,
2105
+ columnGroups,
2106
+ showRowIndex = true,
2107
+ defaultPinnedColumns = [],
2108
+ defaultPinnedRightColumns = [],
2109
+ getColumnWidth
2110
+ }) {
2111
+ const [pinnedColumns, setPinnedColumns] = (0, import_react8.useState)(() => {
2112
+ const map = /* @__PURE__ */ new Map();
2113
+ if (showRowIndex) {
2114
+ map.set(ROW_INDEX_COLUMN_ID, "left");
2115
+ }
2116
+ const midpoint = Math.floor(columns.length / 2);
2117
+ defaultPinnedColumns.forEach((col) => {
2118
+ if (col === ROW_INDEX_COLUMN_ID) return;
2119
+ const colIndex = columns.findIndex((c) => c.id === col);
2120
+ map.set(col, colIndex >= 0 && colIndex <= midpoint ? "left" : "right");
2121
+ });
2122
+ defaultPinnedRightColumns.forEach((col) => map.set(col, "right"));
2123
+ return map;
2124
+ });
2125
+ const [collapsedGroups, setCollapsedGroups] = (0, import_react8.useState)(/* @__PURE__ */ new Set());
2126
+ const measuredWidthsRef = (0, import_react8.useRef)(/* @__PURE__ */ new Map());
2127
+ const tableElRef = (0, import_react8.useRef)(null);
2128
+ const [measureVersion, setMeasureVersion] = (0, import_react8.useState)(0);
2129
+ const measureColumnWidths = (0, import_react8.useCallback)(() => {
2130
+ const el = tableElRef.current;
2131
+ if (!el) return;
2132
+ const newMap = /* @__PURE__ */ new Map();
2133
+ const headers = el.querySelectorAll("th[data-column-id]");
2134
+ headers.forEach((th) => {
2135
+ const colId = th.getAttribute("data-column-id");
2136
+ if (colId) {
2137
+ newMap.set(colId, th.offsetWidth);
2138
+ }
2139
+ });
2140
+ const rowIndexTd = el.querySelector('td[data-column-id="__row_index__"]');
2141
+ if (rowIndexTd) {
2142
+ newMap.set(ROW_INDEX_COLUMN_ID, rowIndexTd.offsetWidth);
2143
+ }
2144
+ measuredWidthsRef.current = newMap;
2145
+ setMeasureVersion((v) => v + 1);
2146
+ }, []);
2147
+ const measureRef = (0, import_react8.useCallback)((el) => {
2148
+ tableElRef.current = el;
2149
+ }, []);
2150
+ const isRowIndexPinned = pinnedColumns.has(ROW_INDEX_COLUMN_ID);
2151
+ const handleTogglePin = (0, import_react8.useCallback)(
2152
+ (columnId) => {
2153
+ setPinnedColumns((prev) => {
2154
+ const newMap = new Map(prev);
2155
+ if (newMap.has(columnId)) {
2156
+ newMap.delete(columnId);
2157
+ } else {
2158
+ if (columnId === ROW_INDEX_COLUMN_ID) {
2159
+ newMap.set(columnId, "left");
2160
+ } else {
2161
+ const colIndex = columns.findIndex((c) => c.id === columnId);
2162
+ const midpoint = Math.floor(columns.length / 2);
2163
+ newMap.set(columnId, colIndex <= midpoint ? "left" : "right");
2164
+ }
2165
+ }
2166
+ return newMap;
2167
+ });
2168
+ },
2169
+ [columns]
2170
+ );
2171
+ const setPinnedColumnsFromIds = (0, import_react8.useCallback)((columnIds) => {
2172
+ const map = /* @__PURE__ */ new Map();
2173
+ if (showRowIndex) {
2174
+ map.set(ROW_INDEX_COLUMN_ID, "left");
2175
+ }
2176
+ const midpoint = Math.floor(columns.length / 2);
2177
+ columnIds.forEach((col) => {
2178
+ if (col !== ROW_INDEX_COLUMN_ID) {
2179
+ const colIndex = columns.findIndex((c) => c.id === col);
2180
+ map.set(col, colIndex >= 0 && colIndex <= midpoint ? "left" : "right");
2181
+ }
2182
+ });
2183
+ setPinnedColumns(map);
2184
+ }, [showRowIndex, columns]);
2185
+ const handleToggleGroupCollapse = (0, import_react8.useCallback)((groupId) => {
2186
+ setCollapsedGroups((prev) => {
2187
+ const newSet = new Set(prev);
2188
+ if (newSet.has(groupId)) {
2189
+ newSet.delete(groupId);
2190
+ } else {
2191
+ newSet.add(groupId);
2192
+ }
2193
+ return newSet;
2194
+ });
2195
+ }, []);
2196
+ const visibleColumns = (0, import_react8.useMemo)(() => {
2197
+ if (!columns || !Array.isArray(columns) || columns.length === 0) return [];
2198
+ let result = [...columns];
2199
+ if (columnGroups && Array.isArray(columnGroups)) {
2200
+ result = result.filter((column) => {
2201
+ const group = columnGroups.find((g) => g.columns.includes(column.id));
2202
+ if (!group) return true;
2203
+ if (!collapsedGroups.has(group.id)) return true;
2204
+ return pinnedColumns.has(column.id);
2205
+ });
2206
+ }
2207
+ return result;
2208
+ }, [columns, columnGroups, collapsedGroups, pinnedColumns]);
2209
+ (0, import_react8.useEffect)(() => {
2210
+ requestAnimationFrame(() => measureColumnWidths());
2211
+ }, [measureColumnWidths, visibleColumns, pinnedColumns]);
2212
+ const widthMap = (0, import_react8.useMemo)(() => {
2213
+ void measureVersion;
2214
+ const map = /* @__PURE__ */ new Map();
2215
+ for (const col of columns) {
2216
+ const measured = measuredWidthsRef.current.get(col.id);
2217
+ if (measured) {
2218
+ map.set(col.id, measured);
2219
+ continue;
2220
+ }
2221
+ const resized = getColumnWidth?.(col.id);
2222
+ if (resized) {
2223
+ map.set(col.id, resized);
2224
+ continue;
2225
+ }
2226
+ map.set(col.id, col.width || col.minWidth || 100);
2227
+ }
2228
+ const measuredRI = measuredWidthsRef.current.get(ROW_INDEX_COLUMN_ID);
2229
+ map.set(ROW_INDEX_COLUMN_ID, measuredRI || ROW_INDEX_COLUMN_WIDTH);
2230
+ return map;
2231
+ }, [columns, getColumnWidth, measureVersion]);
2232
+ const { leftOffsets, rightOffsets, zIndices } = (0, import_react8.useMemo)(() => {
2233
+ const leftOffsets2 = /* @__PURE__ */ new Map();
2234
+ const rightOffsets2 = /* @__PURE__ */ new Map();
2235
+ const zIndices2 = /* @__PURE__ */ new Map();
2236
+ leftOffsets2.set(ROW_INDEX_COLUMN_ID, 0);
2237
+ zIndices2.set(ROW_INDEX_COLUMN_ID, 40);
2238
+ const leftPinned = visibleColumns.filter((c) => pinnedColumns.get(c.id) === "left");
2239
+ const rightPinned = visibleColumns.filter((c) => pinnedColumns.get(c.id) === "right");
2240
+ const isRowIndexPinnedNow = pinnedColumns.has(ROW_INDEX_COLUMN_ID);
2241
+ let leftOffset = showRowIndex && isRowIndexPinnedNow ? widthMap.get(ROW_INDEX_COLUMN_ID) || ROW_INDEX_COLUMN_WIDTH : 0;
2242
+ for (let i = 0; i < leftPinned.length; i++) {
2243
+ leftOffsets2.set(leftPinned[i].id, leftOffset);
2244
+ zIndices2.set(leftPinned[i].id, 20 + (leftPinned.length - i));
2245
+ leftOffset += widthMap.get(leftPinned[i].id) || 100;
2246
+ }
2247
+ let rightOffset = 0;
2248
+ for (let i = rightPinned.length - 1; i >= 0; i--) {
2249
+ rightOffsets2.set(rightPinned[i].id, rightOffset);
2250
+ zIndices2.set(rightPinned[i].id, 20 + (rightPinned.length - i));
2251
+ rightOffset += widthMap.get(rightPinned[i].id) || 100;
2252
+ }
2253
+ return { leftOffsets: leftOffsets2, rightOffsets: rightOffsets2, zIndices: zIndices2 };
2254
+ }, [pinnedColumns, visibleColumns, showRowIndex, widthMap]);
2255
+ const getColumnLeftOffset = (0, import_react8.useCallback)(
2256
+ (columnId) => leftOffsets.get(columnId) ?? 0,
2257
+ [leftOffsets]
2258
+ );
2259
+ const getColumnRightOffset = (0, import_react8.useCallback)(
2260
+ (columnId) => rightOffsets.get(columnId) ?? 0,
2261
+ [rightOffsets]
2262
+ );
2263
+ const isColumnPinned = (0, import_react8.useCallback)(
2264
+ (columnId) => pinnedColumns.has(columnId),
2265
+ [pinnedColumns]
2266
+ );
2267
+ const getColumnPinSide = (0, import_react8.useCallback)(
2268
+ (columnId) => pinnedColumns.get(columnId),
2269
+ [pinnedColumns]
2270
+ );
2271
+ const getPinnedZIndex = (0, import_react8.useCallback)(
2272
+ (columnId) => zIndices.get(columnId) ?? 0,
2273
+ [zIndices]
2274
+ );
2275
+ return {
2276
+ pinnedColumns,
2277
+ isRowIndexPinned,
2278
+ collapsedGroups,
2279
+ visibleColumns,
2280
+ handleTogglePin,
2281
+ handleToggleGroupCollapse,
2282
+ setPinnedColumnsFromIds,
2283
+ getColumnLeftOffset,
2284
+ getColumnRightOffset,
2285
+ isColumnPinned,
2286
+ getColumnPinSide,
2287
+ getPinnedZIndex,
2288
+ measureRef
2289
+ };
2290
+ }
1677
2291
 
1678
2292
  // src/components/RowIndexColumnHeader.tsx
1679
2293
  var import_jsx_runtime7 = require("react/jsx-runtime");
1680
- var cellPaddingCompact3 = "px-1 py-0.5";
1681
- var cellPaddingNormal3 = "px-2 py-1.5";
2294
+ var cellPaddingCompact3 = "px-1.5 py-1";
2295
+ var cellPaddingNormal3 = "px-2 py-2";
1682
2296
  function RowIndexColumnHeader({
1683
2297
  enableHighlighting = false,
1684
2298
  highlightColor,
@@ -1695,18 +2309,16 @@ function RowIndexColumnHeader({
1695
2309
  "th",
1696
2310
  {
1697
2311
  className: cn(
1698
- "border border-gray-200 text-center font-bold text-gray-700",
1699
- compactMode ? "text-[10px]" : "text-xs",
2312
+ "border border-gray-200 text-center font-bold text-gray-700 sticky",
2313
+ compactMode ? "text-xs" : "text-sm",
1700
2314
  cellPadding,
1701
- isPinned ? "z-30" : "z-20",
1702
2315
  className
1703
2316
  ),
1704
2317
  style: {
1705
2318
  minWidth: `${ROW_INDEX_COLUMN_WIDTH}px`,
1706
2319
  width: `${ROW_INDEX_COLUMN_WIDTH}px`,
1707
- // Only sticky horizontally if pinned, NOT vertically
1708
- position: isPinned ? "sticky" : void 0,
1709
- left: isPinned ? 0 : void 0,
2320
+ left: 0,
2321
+ zIndex: 50,
1710
2322
  backgroundColor: highlightColor || "rgb(243 244 246)"
1711
2323
  }
1712
2324
  }
@@ -1716,18 +2328,16 @@ function RowIndexColumnHeader({
1716
2328
  "th",
1717
2329
  {
1718
2330
  className: cn(
1719
- "border border-gray-200 text-center font-bold text-gray-700 group",
1720
- compactMode ? "text-[10px]" : "text-xs",
2331
+ "border border-gray-200 text-center font-bold text-gray-700 group sticky",
2332
+ compactMode ? "text-xs" : "text-sm",
1721
2333
  cellPadding,
1722
- isPinned ? "z-30" : "z-20",
1723
2334
  className
1724
2335
  ),
1725
2336
  style: {
1726
2337
  minWidth: `${ROW_INDEX_COLUMN_WIDTH}px`,
1727
2338
  width: `${ROW_INDEX_COLUMN_WIDTH}px`,
1728
- position: "sticky",
1729
- top: 0,
1730
- left: isPinned ? 0 : void 0,
2339
+ left: 0,
2340
+ zIndex: 50,
1731
2341
  backgroundColor: highlightColor || "rgb(243 244 246)"
1732
2342
  },
1733
2343
  children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center justify-center gap-1", children: [
@@ -1752,231 +2362,104 @@ function RowIndexColumnHeader({
1752
2362
  }
1753
2363
  RowIndexColumnHeader.displayName = "RowIndexColumnHeader";
1754
2364
 
1755
- // src/hooks/useSpreadsheetHighlighting.ts
1756
- var import_react7 = require("react");
1757
- var HIGHLIGHT_COLORS = {
1758
- // Darker colors for rows (more visible)
1759
- row: [
1760
- "#fef08a",
1761
- // yellow
1762
- "#bbf7d0",
1763
- // green
1764
- "#bfdbfe",
1765
- // blue
1766
- "#fecaca",
1767
- // red
1768
- "#e9d5ff",
1769
- // purple
1770
- "#fed7aa",
1771
- // orange
1772
- "#a5f3fc",
1773
- // cyan
1774
- "#fce7f3",
1775
- // pink
1776
- "#d1d5db"
1777
- // gray
1778
- ],
1779
- // Lighter colors for columns/headers (subtle background)
1780
- column: [
1781
- "#fef9c3",
1782
- // yellow-100
1783
- "#dcfce7",
1784
- // green-100
1785
- "#dbeafe",
1786
- // blue-100
1787
- "#fee2e2",
1788
- // red-100
1789
- "#f3e8ff",
1790
- // purple-100
1791
- "#ffedd5",
1792
- // orange-100
1793
- "#cffafe",
1794
- // cyan-100
1795
- "#fce7f3",
1796
- // pink-100
1797
- "#e5e7eb"
1798
- // gray-200
1799
- ]
1800
- };
1801
- function useSpreadsheetHighlighting({
1802
- externalRowHighlights,
1803
- onRowHighlight,
1804
- externalColumnHighlights,
1805
- onColumnHighlight,
1806
- externalCellHighlights,
1807
- onCellHighlight
1808
- } = {}) {
1809
- const [cellHighlightsInternal, setCellHighlightsInternal] = (0, import_react7.useState)([]);
1810
- const [rowHighlightsInternal, setRowHighlightsInternal] = (0, import_react7.useState)([]);
1811
- const [columnHighlightsInternal, setColumnHighlightsInternal] = (0, import_react7.useState)({});
1812
- const [highlightPickerRow, setHighlightPickerRow] = (0, import_react7.useState)(null);
1813
- const [highlightPickerColumn, setHighlightPickerColumn] = (0, import_react7.useState)(null);
1814
- const [highlightPickerCell, setHighlightPickerCell] = (0, import_react7.useState)(null);
1815
- const cellHighlights = externalCellHighlights || cellHighlightsInternal;
1816
- const rowHighlights = externalRowHighlights || rowHighlightsInternal;
1817
- const columnHighlights = externalColumnHighlights || columnHighlightsInternal;
1818
- const getCellHighlight = (0, import_react7.useCallback)(
1819
- (rowId, columnId) => {
1820
- return cellHighlights.find((h) => h.rowId === rowId && h.columnId === columnId)?.color;
1821
- },
1822
- [cellHighlights]
1823
- );
1824
- const handleCellHighlightToggle = (0, import_react7.useCallback)(
1825
- (rowId, columnId, color = "#fef08a") => {
1826
- if (onCellHighlight) {
1827
- onCellHighlight(rowId, columnId, color);
1828
- } else {
1829
- setCellHighlightsInternal((prev) => {
1830
- const existing = prev.find((h) => h.rowId === rowId && h.columnId === columnId);
1831
- if (existing) {
1832
- if (color === null) {
1833
- return prev.filter(
1834
- (h) => !(h.rowId === rowId && h.columnId === columnId)
1835
- );
1836
- }
1837
- return prev.map(
1838
- (h) => h.rowId === rowId && h.columnId === columnId ? { ...h, color } : h
1839
- );
1840
- }
1841
- if (color) {
1842
- return [...prev, { rowId, columnId, color }];
1843
- }
1844
- return prev;
1845
- });
1846
- }
1847
- setHighlightPickerCell(null);
1848
- },
1849
- [onCellHighlight]
1850
- );
1851
- const getRowHighlight = (0, import_react7.useCallback)(
1852
- (rowId) => {
1853
- return rowHighlights.find((h) => h.rowId === rowId && !h.columnId);
1854
- },
1855
- [rowHighlights]
1856
- );
1857
- const handleRowHighlightToggle = (0, import_react7.useCallback)(
1858
- (rowId, color) => {
1859
- if (onRowHighlight) {
1860
- onRowHighlight(rowId, color);
1861
- } else {
1862
- setRowHighlightsInternal((prev) => {
1863
- const existing = prev.find((h) => h.rowId === rowId && !h.columnId);
1864
- if (existing) {
1865
- if (color === null) {
1866
- return prev.filter((h) => !(h.rowId === rowId && !h.columnId));
1867
- }
1868
- return prev.map(
1869
- (h) => h.rowId === rowId && !h.columnId ? { ...h, color } : h
1870
- );
1871
- }
1872
- if (color) {
1873
- return [...prev, { rowId, color }];
1874
- }
1875
- return prev;
1876
- });
1877
- }
1878
- setHighlightPickerRow(null);
1879
- },
1880
- [onRowHighlight]
1881
- );
1882
- const getColumnHighlight = (0, import_react7.useCallback)(
1883
- (columnId) => {
1884
- return columnHighlights[columnId];
1885
- },
1886
- [columnHighlights]
1887
- );
1888
- const handleColumnHighlightToggle = (0, import_react7.useCallback)(
1889
- (columnId, color) => {
1890
- if (onColumnHighlight) {
1891
- onColumnHighlight(columnId, color);
1892
- } else {
1893
- setColumnHighlightsInternal((prev) => {
1894
- const newHighlights = { ...prev };
1895
- if (color === null) {
1896
- delete newHighlights[columnId];
1897
- } else {
1898
- newHighlights[columnId] = color;
1899
- }
1900
- return newHighlights;
1901
- });
1902
- }
1903
- setHighlightPickerColumn(null);
1904
- },
1905
- [onColumnHighlight]
1906
- );
1907
- const clearAllHighlights = (0, import_react7.useCallback)(() => {
1908
- setCellHighlightsInternal([]);
1909
- setRowHighlightsInternal([]);
1910
- setColumnHighlightsInternal({});
1911
- }, []);
1912
- return {
1913
- // Cell highlights
1914
- cellHighlights,
1915
- getCellHighlight,
1916
- handleCellHighlightToggle,
1917
- // Row highlights
1918
- rowHighlights,
1919
- getRowHighlight,
1920
- handleRowHighlightToggle,
1921
- // Column highlights
1922
- columnHighlights,
1923
- getColumnHighlight,
1924
- handleColumnHighlightToggle,
1925
- // Picker state
1926
- highlightPickerRow,
1927
- setHighlightPickerRow,
1928
- highlightPickerColumn,
1929
- setHighlightPickerColumn,
1930
- highlightPickerCell,
1931
- setHighlightPickerCell,
1932
- // Utility
1933
- clearAllHighlights
1934
- };
1935
- }
1936
-
1937
2365
  // src/components/ColorPickerPopover.tsx
2366
+ var import_react9 = require("react");
1938
2367
  var import_jsx_runtime8 = require("react/jsx-runtime");
1939
2368
  function ColorPickerPopover({
1940
2369
  title,
1941
- paletteType = "column",
1942
- colors,
2370
+ recentColors = [],
1943
2371
  onSelectColor,
1944
2372
  onClose,
1945
2373
  className
1946
2374
  }) {
1947
- const colorPalette = colors ?? [...HIGHLIGHT_COLORS[paletteType], null];
1948
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: cn("bg-white rounded-lg shadow-xl p-4 w-64", className), children: [
1949
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { className: "text-sm font-semibold mb-3", children: title }),
1950
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "grid grid-cols-5 gap-2", children: colorPalette.map((color) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1951
- "button",
1952
- {
1953
- type: "button",
1954
- onClick: () => onSelectColor(color),
1955
- className: cn(
1956
- "w-8 h-8 rounded border-2 transition-transform hover:scale-110",
1957
- color ? "border-gray-300" : "border-gray-300 bg-white flex items-center justify-center text-gray-400 text-xs"
1958
- ),
1959
- style: color ? { backgroundColor: color } : void 0,
1960
- title: color || "Clear highlight",
1961
- children: !color && "\u2715"
1962
- },
1963
- color || "clear"
1964
- )) }),
1965
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex justify-end mt-4", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1966
- "button",
1967
- {
1968
- type: "button",
1969
- onClick: onClose,
1970
- className: "px-3 py-1.5 text-sm text-gray-600 hover:bg-gray-100 rounded transition-colors",
1971
- children: "Cancel"
1972
- }
1973
- ) })
1974
- ] }) });
2375
+ const [selectedColor, setSelectedColor] = (0, import_react9.useState)("#fef08a");
2376
+ const colorInputRef = (0, import_react9.useRef)(null);
2377
+ const handleConfirm = () => {
2378
+ onSelectColor(selectedColor);
2379
+ };
2380
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2381
+ "div",
2382
+ {
2383
+ className: cn("bg-white rounded-lg shadow-xl p-4 w-72", className),
2384
+ onClick: (e) => e.stopPropagation(),
2385
+ children: [
2386
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { className: "text-sm font-semibold mb-3", children: title }),
2387
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center gap-3 mb-3", children: [
2388
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2389
+ "input",
2390
+ {
2391
+ ref: colorInputRef,
2392
+ type: "color",
2393
+ value: selectedColor,
2394
+ onChange: (e) => setSelectedColor(e.target.value),
2395
+ className: "w-10 h-10 rounded border border-gray-200 cursor-pointer p-0.5"
2396
+ }
2397
+ ),
2398
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex-1", children: [
2399
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2400
+ "div",
2401
+ {
2402
+ className: "w-full h-8 rounded border border-gray-200",
2403
+ style: { backgroundColor: selectedColor }
2404
+ }
2405
+ ),
2406
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-xs text-gray-500 mt-1 block", children: selectedColor })
2407
+ ] })
2408
+ ] }),
2409
+ recentColors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "mb-3", children: [
2410
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-xs text-gray-500 mb-1.5 block", children: "Recent" }),
2411
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex gap-1.5", children: recentColors.map((color) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2412
+ "button",
2413
+ {
2414
+ type: "button",
2415
+ onClick: () => setSelectedColor(color),
2416
+ className: cn(
2417
+ "w-7 h-7 rounded border-2 transition-transform hover:scale-110",
2418
+ selectedColor === color ? "border-blue-500" : "border-gray-200 hover:border-gray-400"
2419
+ ),
2420
+ style: { backgroundColor: color },
2421
+ title: color
2422
+ },
2423
+ color
2424
+ )) })
2425
+ ] }),
2426
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center gap-2 pt-2 border-t border-gray-100", children: [
2427
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2428
+ "button",
2429
+ {
2430
+ type: "button",
2431
+ onClick: handleConfirm,
2432
+ className: "flex-1 px-2.5 py-1.5 text-xs text-white bg-blue-600 hover:bg-blue-700 rounded transition-colors font-medium",
2433
+ children: "Apply"
2434
+ }
2435
+ ),
2436
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2437
+ "button",
2438
+ {
2439
+ type: "button",
2440
+ onClick: () => onSelectColor(null),
2441
+ className: "px-2.5 py-1.5 text-xs text-red-600 hover:bg-red-50 rounded border border-red-200 transition-colors",
2442
+ children: "Clear"
2443
+ }
2444
+ ),
2445
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2446
+ "button",
2447
+ {
2448
+ type: "button",
2449
+ onClick: onClose,
2450
+ className: "px-2.5 py-1.5 text-xs text-gray-600 hover:bg-gray-100 rounded transition-colors",
2451
+ children: "Cancel"
2452
+ }
2453
+ )
2454
+ ] })
2455
+ ]
2456
+ }
2457
+ ) });
1975
2458
  }
1976
2459
  ColorPickerPopover.displayName = "ColorPickerPopover";
1977
2460
 
1978
2461
  // src/components/SpreadsheetSettingsModal.tsx
1979
- var import_react8 = require("react");
2462
+ var import_react10 = require("react");
1980
2463
  var import_jsx_runtime9 = require("react/jsx-runtime");
1981
2464
  var DEFAULT_SETTINGS = {
1982
2465
  defaultPinnedColumns: [],
@@ -1995,9 +2478,9 @@ var SpreadsheetSettingsModal = ({
1995
2478
  title = "Spreadsheet Settings",
1996
2479
  pageSizeOptions = [25, 50, 100, 200]
1997
2480
  }) => {
1998
- const [activeTab, setActiveTab] = (0, import_react8.useState)("columns");
1999
- const [localSettings, setLocalSettings] = (0, import_react8.useState)(settings);
2000
- (0, import_react8.useEffect)(() => {
2481
+ const [activeTab, setActiveTab] = (0, import_react10.useState)("columns");
2482
+ const [localSettings, setLocalSettings] = (0, import_react10.useState)(settings);
2483
+ (0, import_react10.useEffect)(() => {
2001
2484
  setLocalSettings(settings);
2002
2485
  }, [settings]);
2003
2486
  if (!isOpen) return null;
@@ -2305,11 +2788,11 @@ var SpreadsheetSettingsModal = ({
2305
2788
  SpreadsheetSettingsModal.displayName = "SpreadsheetSettingsModal";
2306
2789
 
2307
2790
  // src/components/CommentModals.tsx
2308
- var import_react9 = require("react");
2791
+ var import_react11 = require("react");
2309
2792
  var import_jsx_runtime10 = require("react/jsx-runtime");
2310
2793
  function AddCommentModal({ isOpen, columnLabel, onAdd, onClose }) {
2311
- const [commentText, setCommentText] = (0, import_react9.useState)("");
2312
- (0, import_react9.useEffect)(() => {
2794
+ const [commentText, setCommentText] = (0, import_react11.useState)("");
2795
+ (0, import_react11.useEffect)(() => {
2313
2796
  if (!isOpen) {
2314
2797
  setCommentText("");
2315
2798
  }
@@ -2479,7 +2962,7 @@ function DeleteConfirmationModal({
2479
2962
  DeleteConfirmationModal.displayName = "DeleteConfirmationModal";
2480
2963
 
2481
2964
  // src/components/KeyboardShortcutsModal.tsx
2482
- var import_react10 = __toESM(require("react"));
2965
+ var import_react12 = __toESM(require("react"));
2483
2966
  var import_jsx_runtime11 = require("react/jsx-runtime");
2484
2967
  function KeyboardShortcutsModal({
2485
2968
  isOpen,
@@ -2521,7 +3004,7 @@ function ShortcutSection({ title, children }) {
2521
3004
  function ShortcutRow({ label, keys }) {
2522
3005
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center justify-between", children: [
2523
3006
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "text-gray-600 text-sm", children: label }),
2524
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "flex items-center gap-1", children: keys.map((key, index) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react10.default.Fragment, { children: [
3007
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "flex items-center gap-1", children: keys.map((key, index) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react12.default.Fragment, { children: [
2525
3008
  index > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "text-gray-400", children: "+" }),
2526
3009
  key.includes("Click") ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "text-gray-500 text-xs", children: key }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("kbd", { className: "px-2 py-1 bg-gray-100 text-gray-800 rounded text-xs border border-gray-200", children: key })
2527
3010
  ] }, index)) })
@@ -2530,7 +3013,7 @@ function ShortcutRow({ label, keys }) {
2530
3013
  KeyboardShortcutsModal.displayName = "KeyboardShortcutsModal";
2531
3014
 
2532
3015
  // src/components/RowContextMenu.tsx
2533
- var import_react11 = require("react");
3016
+ var import_react13 = require("react");
2534
3017
  var import_design_system = require("@xcelsior/design-system");
2535
3018
  var import_jsx_runtime12 = require("react/jsx-runtime");
2536
3019
  function RowContextMenu({
@@ -2540,7 +3023,7 @@ function RowContextMenu({
2540
3023
  compactMode = false,
2541
3024
  className
2542
3025
  }) {
2543
- const visibleItems = (0, import_react11.useMemo)(() => {
3026
+ const visibleItems = (0, import_react13.useMemo)(() => {
2544
3027
  return items.filter((item) => !item.visible || item.visible(row));
2545
3028
  }, [items, row]);
2546
3029
  if (visibleItems.length === 0) {
@@ -2596,7 +3079,7 @@ RowContextMenu.displayName = "RowContextMenu";
2596
3079
  var import_design_system2 = require("@xcelsior/design-system");
2597
3080
 
2598
3081
  // src/hooks/useSpreadsheetFiltering.ts
2599
- var import_react12 = require("react");
3082
+ var import_react14 = require("react");
2600
3083
  var import_utils11 = require("@xcelsior/utils");
2601
3084
  function useSpreadsheetFiltering({
2602
3085
  data,
@@ -2606,18 +3089,20 @@ function useSpreadsheetFiltering({
2606
3089
  serverSide = false,
2607
3090
  controlledFilters,
2608
3091
  controlledSortConfig,
2609
- defaultSortConfig
3092
+ defaultSortConfig,
3093
+ duplicateRowIds,
3094
+ getRowId
2610
3095
  }) {
2611
- const [internalFilters, setInternalFilters] = (0, import_react12.useState)(
3096
+ const [internalFilters, setInternalFilters] = (0, import_react14.useState)(
2612
3097
  {}
2613
3098
  );
2614
- const [internalSortConfig, setInternalSortConfig] = (0, import_react12.useState)(
3099
+ const [internalSortConfig, setInternalSortConfig] = (0, import_react14.useState)(
2615
3100
  defaultSortConfig ?? null
2616
3101
  );
2617
- const [activeFilterColumn, setActiveFilterColumn] = (0, import_react12.useState)(null);
3102
+ const [activeFilterColumn, setActiveFilterColumn] = (0, import_react14.useState)(null);
2618
3103
  const filters = controlledFilters ?? internalFilters;
2619
3104
  const sortConfig = controlledSortConfig !== void 0 ? controlledSortConfig : internalSortConfig;
2620
- const applyTextCondition = (0, import_react12.useCallback)(
3105
+ const applyTextCondition = (0, import_react14.useCallback)(
2621
3106
  (value, condition) => {
2622
3107
  const strValue = String(value ?? "").toLowerCase();
2623
3108
  const filterValue = (condition.value ?? "").toLowerCase();
@@ -2644,7 +3129,7 @@ function useSpreadsheetFiltering({
2644
3129
  },
2645
3130
  []
2646
3131
  );
2647
- const applyNumberCondition = (0, import_react12.useCallback)(
3132
+ const applyNumberCondition = (0, import_react14.useCallback)(
2648
3133
  (value, condition) => {
2649
3134
  if (condition.operator === "isEmpty") return isBlankValue(value);
2650
3135
  if (condition.operator === "isNotEmpty") return !isBlankValue(value);
@@ -2673,7 +3158,7 @@ function useSpreadsheetFiltering({
2673
3158
  },
2674
3159
  []
2675
3160
  );
2676
- const applyDateCondition = (0, import_react12.useCallback)(
3161
+ const applyDateCondition = (0, import_react14.useCallback)(
2677
3162
  (value, condition) => {
2678
3163
  if (condition.operator === "isEmpty") return isBlankValue(value);
2679
3164
  if (condition.operator === "isNotEmpty") return !isBlankValue(value);
@@ -2745,7 +3230,7 @@ function useSpreadsheetFiltering({
2745
3230
  },
2746
3231
  []
2747
3232
  );
2748
- const buildFilterPredicate = (0, import_react12.useCallback)(
3233
+ const buildFilterPredicate = (0, import_react14.useCallback)(
2749
3234
  (column, filter) => {
2750
3235
  return (row) => {
2751
3236
  const value = column.getValue ? column.getValue(row) : row[column.id];
@@ -2779,7 +3264,7 @@ function useSpreadsheetFiltering({
2779
3264
  },
2780
3265
  [applyTextCondition, applyNumberCondition, applyDateCondition]
2781
3266
  );
2782
- const buildSortComparator = (0, import_react12.useCallback)(
3267
+ const buildSortComparator = (0, import_react14.useCallback)(
2783
3268
  (column, direction) => {
2784
3269
  return (a, b) => {
2785
3270
  const aValue = column?.getValue ? column.getValue(a) : a[sortConfig?.columnId];
@@ -2794,7 +3279,7 @@ function useSpreadsheetFiltering({
2794
3279
  },
2795
3280
  [sortConfig?.columnId]
2796
3281
  );
2797
- const filteredData = (0, import_react12.useMemo)(() => {
3282
+ const filteredData = (0, import_react14.useMemo)(() => {
2798
3283
  if (!data || !Array.isArray(data)) return import_utils11.LazyArray.empty();
2799
3284
  if (serverSide) {
2800
3285
  return import_utils11.LazyArray.from(data);
@@ -2807,6 +3292,12 @@ function useSpreadsheetFiltering({
2807
3292
  const column = columns.find((c) => c.id === columnId);
2808
3293
  if (!column) continue;
2809
3294
  filterChain.add(buildFilterPredicate(column, filter));
3295
+ if (filter.showDuplicatesOnly && duplicateRowIds && getRowId) {
3296
+ const dupSet = duplicateRowIds.get(columnId);
3297
+ if (dupSet) {
3298
+ filterChain.add((row) => dupSet.has(getRowId(row)));
3299
+ }
3300
+ }
2810
3301
  }
2811
3302
  if (!filterChain.isEmpty) {
2812
3303
  lazyResult = filterChain.applyTo(lazyResult);
@@ -2816,8 +3307,8 @@ function useSpreadsheetFiltering({
2816
3307
  lazyResult = lazyResult.sort(buildSortComparator(column, sortConfig.direction));
2817
3308
  }
2818
3309
  return lazyResult;
2819
- }, [data, filters, sortConfig, columns, serverSide, buildFilterPredicate, buildSortComparator]);
2820
- const handleFilterChange = (0, import_react12.useCallback)(
3310
+ }, [data, filters, sortConfig, columns, serverSide, buildFilterPredicate, buildSortComparator, duplicateRowIds, getRowId]);
3311
+ const handleFilterChange = (0, import_react14.useCallback)(
2821
3312
  (columnId, filter) => {
2822
3313
  const newFilters = { ...filters };
2823
3314
  if (filter) {
@@ -2832,7 +3323,7 @@ function useSpreadsheetFiltering({
2832
3323
  },
2833
3324
  [filters, onFilterChange, controlledFilters]
2834
3325
  );
2835
- const handleSort = (0, import_react12.useCallback)(
3326
+ const handleSort = (0, import_react14.useCallback)(
2836
3327
  (columnId) => {
2837
3328
  let newSortConfig;
2838
3329
  if (sortConfig?.columnId === columnId) {
@@ -2851,13 +3342,13 @@ function useSpreadsheetFiltering({
2851
3342
  },
2852
3343
  [sortConfig, onSortChange, controlledSortConfig]
2853
3344
  );
2854
- const clearSort = (0, import_react12.useCallback)(() => {
3345
+ const clearSort = (0, import_react14.useCallback)(() => {
2855
3346
  if (controlledSortConfig === void 0) {
2856
3347
  setInternalSortConfig(null);
2857
3348
  }
2858
3349
  onSortChange?.(null);
2859
3350
  }, [onSortChange, controlledSortConfig]);
2860
- const setSortConfig = (0, import_react12.useCallback)(
3351
+ const setSortConfig = (0, import_react14.useCallback)(
2861
3352
  (config) => {
2862
3353
  if (controlledSortConfig === void 0) {
2863
3354
  setInternalSortConfig(config);
@@ -2866,7 +3357,7 @@ function useSpreadsheetFiltering({
2866
3357
  },
2867
3358
  [onSortChange, controlledSortConfig]
2868
3359
  );
2869
- const clearAllFilters = (0, import_react12.useCallback)(() => {
3360
+ const clearAllFilters = (0, import_react14.useCallback)(() => {
2870
3361
  if (controlledFilters === void 0) {
2871
3362
  setInternalFilters({});
2872
3363
  }
@@ -2888,24 +3379,274 @@ function useSpreadsheetFiltering({
2888
3379
  };
2889
3380
  }
2890
3381
 
3382
+ // src/hooks/useSpreadsheetDuplicates.ts
3383
+ var import_react15 = require("react");
3384
+ function normalizeValue(value) {
3385
+ if (value === null || value === void 0 || value === "") {
3386
+ return "__blank__";
3387
+ }
3388
+ if (typeof value === "number") {
3389
+ return String(value);
3390
+ }
3391
+ return String(value).trim().toLowerCase();
3392
+ }
3393
+ function useSpreadsheetDuplicates({
3394
+ data,
3395
+ columns,
3396
+ duplicateCheckColumns,
3397
+ getRowId
3398
+ }) {
3399
+ const [localDuplicateCheckColumns, setLocalDuplicateCheckColumns] = (0, import_react15.useState)(
3400
+ () => new Set(duplicateCheckColumns)
3401
+ );
3402
+ const duplicateCheckColumnsSet = (0, import_react15.useMemo)(() => {
3403
+ return new Set(duplicateCheckColumns);
3404
+ }, [duplicateCheckColumns]);
3405
+ const { duplicateRowIds, valueCounts } = (0, import_react15.useMemo)(() => {
3406
+ const duplicateRowIds2 = /* @__PURE__ */ new Map();
3407
+ const valueCounts2 = /* @__PURE__ */ new Map();
3408
+ const activeColumns = duplicateCheckColumnsSet.size > 0 ? duplicateCheckColumnsSet : localDuplicateCheckColumns;
3409
+ for (const columnId of activeColumns) {
3410
+ const column = columns.find((c) => c.id === columnId);
3411
+ if (!column) continue;
3412
+ const valueToRowIds = /* @__PURE__ */ new Map();
3413
+ for (const row of data) {
3414
+ const rawValue = column.getValue ? column.getValue(row) : row[columnId];
3415
+ if (rawValue === null || rawValue === void 0 || rawValue === "") continue;
3416
+ const normalized = normalizeValue(rawValue);
3417
+ const rowId = getRowId(row);
3418
+ const existing = valueToRowIds.get(normalized);
3419
+ if (existing) {
3420
+ existing.push(rowId);
3421
+ } else {
3422
+ valueToRowIds.set(normalized, [rowId]);
3423
+ }
3424
+ }
3425
+ const dupSet = /* @__PURE__ */ new Set();
3426
+ const countMap = /* @__PURE__ */ new Map();
3427
+ for (const [normalizedVal, rowIds] of valueToRowIds) {
3428
+ countMap.set(normalizedVal, rowIds.length);
3429
+ if (rowIds.length >= 2) {
3430
+ for (const rowId of rowIds) {
3431
+ dupSet.add(rowId);
3432
+ }
3433
+ }
3434
+ }
3435
+ duplicateRowIds2.set(columnId, dupSet);
3436
+ valueCounts2.set(columnId, countMap);
3437
+ }
3438
+ return { duplicateRowIds: duplicateRowIds2, valueCounts: valueCounts2 };
3439
+ }, [data, columns, duplicateCheckColumnsSet, localDuplicateCheckColumns, getRowId]);
3440
+ const isCellDuplicate = (0, import_react15.useCallback)(
3441
+ (rowId, columnId) => {
3442
+ return duplicateRowIds.get(columnId)?.has(rowId) ?? false;
3443
+ },
3444
+ [duplicateRowIds]
3445
+ );
3446
+ const getDuplicateCount = (0, import_react15.useCallback)(
3447
+ (columnId, value) => {
3448
+ const normalized = normalizeValue(value);
3449
+ return valueCounts.get(columnId)?.get(normalized) ?? 0;
3450
+ },
3451
+ [valueCounts]
3452
+ );
3453
+ const getDuplicateColumnCount = (0, import_react15.useCallback)(
3454
+ (columnId) => {
3455
+ return duplicateRowIds.get(columnId)?.size ?? 0;
3456
+ },
3457
+ [duplicateRowIds]
3458
+ );
3459
+ const toggleDuplicateCheck = (0, import_react15.useCallback)((columnId) => {
3460
+ setLocalDuplicateCheckColumns((prev) => {
3461
+ const next = new Set(prev);
3462
+ if (next.has(columnId)) {
3463
+ next.delete(columnId);
3464
+ } else {
3465
+ next.add(columnId);
3466
+ }
3467
+ return next;
3468
+ });
3469
+ }, []);
3470
+ const effectiveDuplicateCheckColumns = duplicateCheckColumnsSet.size > 0 ? duplicateCheckColumnsSet : localDuplicateCheckColumns;
3471
+ return {
3472
+ isCellDuplicate,
3473
+ getDuplicateCount,
3474
+ getDuplicateColumnCount,
3475
+ duplicateRowIds,
3476
+ duplicateCheckColumns: effectiveDuplicateCheckColumns,
3477
+ toggleDuplicateCheck
3478
+ };
3479
+ }
3480
+
3481
+ // src/hooks/useSpreadsheetHighlighting.ts
3482
+ var import_react16 = require("react");
3483
+ function useSpreadsheetHighlighting({
3484
+ externalRowHighlights,
3485
+ onRowHighlight,
3486
+ externalColumnHighlights,
3487
+ onColumnHighlight,
3488
+ externalCellHighlights,
3489
+ onCellHighlight
3490
+ } = {}) {
3491
+ const [cellHighlightsInternal, setCellHighlightsInternal] = (0, import_react16.useState)([]);
3492
+ const [rowHighlightsInternal, setRowHighlightsInternal] = (0, import_react16.useState)([]);
3493
+ const [columnHighlightsInternal, setColumnHighlightsInternal] = (0, import_react16.useState)({});
3494
+ const [recentColors, setRecentColors] = (0, import_react16.useState)([]);
3495
+ const addRecentColor = (0, import_react16.useCallback)((color) => {
3496
+ if (!color) return;
3497
+ setRecentColors((prev) => {
3498
+ const filtered = prev.filter((c) => c !== color);
3499
+ return [color, ...filtered].slice(0, 8);
3500
+ });
3501
+ }, []);
3502
+ const [highlightPickerRow, setHighlightPickerRow] = (0, import_react16.useState)(null);
3503
+ const [highlightPickerColumn, setHighlightPickerColumn] = (0, import_react16.useState)(null);
3504
+ const [highlightPickerCell, setHighlightPickerCell] = (0, import_react16.useState)(null);
3505
+ const cellHighlights = externalCellHighlights || cellHighlightsInternal;
3506
+ const rowHighlights = externalRowHighlights || rowHighlightsInternal;
3507
+ const columnHighlights = externalColumnHighlights || columnHighlightsInternal;
3508
+ const getCellHighlight = (0, import_react16.useCallback)(
3509
+ (rowId, columnId) => {
3510
+ return cellHighlights.find((h) => h.rowId === rowId && h.columnId === columnId)?.color;
3511
+ },
3512
+ [cellHighlights]
3513
+ );
3514
+ const handleCellHighlightToggle = (0, import_react16.useCallback)(
3515
+ (rowId, columnId, color = "#fef08a") => {
3516
+ if (onCellHighlight) {
3517
+ onCellHighlight(rowId, columnId, color);
3518
+ } else {
3519
+ setCellHighlightsInternal((prev) => {
3520
+ const existing = prev.find((h) => h.rowId === rowId && h.columnId === columnId);
3521
+ if (existing) {
3522
+ if (color === null) {
3523
+ return prev.filter(
3524
+ (h) => !(h.rowId === rowId && h.columnId === columnId)
3525
+ );
3526
+ }
3527
+ return prev.map(
3528
+ (h) => h.rowId === rowId && h.columnId === columnId ? { ...h, color } : h
3529
+ );
3530
+ }
3531
+ if (color) {
3532
+ return [...prev, { rowId, columnId, color }];
3533
+ }
3534
+ return prev;
3535
+ });
3536
+ }
3537
+ addRecentColor(color);
3538
+ setHighlightPickerCell(null);
3539
+ },
3540
+ [onCellHighlight, addRecentColor]
3541
+ );
3542
+ const getRowHighlight = (0, import_react16.useCallback)(
3543
+ (rowId) => {
3544
+ return rowHighlights.find((h) => h.rowId === rowId && !h.columnId);
3545
+ },
3546
+ [rowHighlights]
3547
+ );
3548
+ const handleRowHighlightToggle = (0, import_react16.useCallback)(
3549
+ (rowId, color) => {
3550
+ if (onRowHighlight) {
3551
+ onRowHighlight(rowId, color);
3552
+ } else {
3553
+ setRowHighlightsInternal((prev) => {
3554
+ const existing = prev.find((h) => h.rowId === rowId && !h.columnId);
3555
+ if (existing) {
3556
+ if (color === null) {
3557
+ return prev.filter((h) => !(h.rowId === rowId && !h.columnId));
3558
+ }
3559
+ return prev.map(
3560
+ (h) => h.rowId === rowId && !h.columnId ? { ...h, color } : h
3561
+ );
3562
+ }
3563
+ if (color) {
3564
+ return [...prev, { rowId, color }];
3565
+ }
3566
+ return prev;
3567
+ });
3568
+ }
3569
+ addRecentColor(color);
3570
+ setHighlightPickerRow(null);
3571
+ },
3572
+ [onRowHighlight, addRecentColor]
3573
+ );
3574
+ const getColumnHighlight = (0, import_react16.useCallback)(
3575
+ (columnId) => {
3576
+ return columnHighlights[columnId];
3577
+ },
3578
+ [columnHighlights]
3579
+ );
3580
+ const handleColumnHighlightToggle = (0, import_react16.useCallback)(
3581
+ (columnId, color) => {
3582
+ if (onColumnHighlight) {
3583
+ onColumnHighlight(columnId, color);
3584
+ } else {
3585
+ setColumnHighlightsInternal((prev) => {
3586
+ const newHighlights = { ...prev };
3587
+ if (color === null) {
3588
+ delete newHighlights[columnId];
3589
+ } else {
3590
+ newHighlights[columnId] = color;
3591
+ }
3592
+ return newHighlights;
3593
+ });
3594
+ }
3595
+ addRecentColor(color);
3596
+ setHighlightPickerColumn(null);
3597
+ },
3598
+ [onColumnHighlight, addRecentColor]
3599
+ );
3600
+ const clearAllHighlights = (0, import_react16.useCallback)(() => {
3601
+ setCellHighlightsInternal([]);
3602
+ setRowHighlightsInternal([]);
3603
+ setColumnHighlightsInternal({});
3604
+ }, []);
3605
+ return {
3606
+ // Cell highlights
3607
+ cellHighlights,
3608
+ getCellHighlight,
3609
+ handleCellHighlightToggle,
3610
+ // Row highlights
3611
+ rowHighlights,
3612
+ getRowHighlight,
3613
+ handleRowHighlightToggle,
3614
+ // Column highlights
3615
+ columnHighlights,
3616
+ getColumnHighlight,
3617
+ handleColumnHighlightToggle,
3618
+ // Recent colors
3619
+ recentColors,
3620
+ // Picker state
3621
+ highlightPickerRow,
3622
+ setHighlightPickerRow,
3623
+ highlightPickerColumn,
3624
+ setHighlightPickerColumn,
3625
+ highlightPickerCell,
3626
+ setHighlightPickerCell,
3627
+ // Utility
3628
+ clearAllHighlights
3629
+ };
3630
+ }
3631
+
2891
3632
  // src/hooks/useSpreadsheetComments.ts
2892
- var import_react13 = require("react");
3633
+ var import_react17 = require("react");
2893
3634
  function useSpreadsheetComments({
2894
3635
  externalCellComments,
2895
3636
  onAddCellComment,
2896
3637
  onToggleCommentResolved
2897
3638
  } = {}) {
2898
- const [cellCommentsInternal, setCellCommentsInternal] = (0, import_react13.useState)([]);
2899
- const [commentModalCell, setCommentModalCell] = (0, import_react13.useState)(null);
2900
- const [viewCommentsCell, setViewCommentsCell] = (0, import_react13.useState)(null);
3639
+ const [cellCommentsInternal, setCellCommentsInternal] = (0, import_react17.useState)([]);
3640
+ const [commentModalCell, setCommentModalCell] = (0, import_react17.useState)(null);
3641
+ const [viewCommentsCell, setViewCommentsCell] = (0, import_react17.useState)(null);
2901
3642
  const cellComments = externalCellComments || cellCommentsInternal;
2902
- const getCellComments = (0, import_react13.useCallback)(
3643
+ const getCellComments = (0, import_react17.useCallback)(
2903
3644
  (rowId, columnId) => {
2904
3645
  return cellComments.filter((c) => c.rowId === rowId && c.columnId === columnId);
2905
3646
  },
2906
3647
  [cellComments]
2907
3648
  );
2908
- const getCellUnresolvedCommentCount = (0, import_react13.useCallback)(
3649
+ const getCellUnresolvedCommentCount = (0, import_react17.useCallback)(
2909
3650
  (rowId, columnId) => {
2910
3651
  return cellComments.filter(
2911
3652
  (c) => c.rowId === rowId && c.columnId === columnId && !c.resolved
@@ -2913,13 +3654,13 @@ function useSpreadsheetComments({
2913
3654
  },
2914
3655
  [cellComments]
2915
3656
  );
2916
- const cellHasComments = (0, import_react13.useCallback)(
3657
+ const cellHasComments = (0, import_react17.useCallback)(
2917
3658
  (rowId, columnId) => {
2918
3659
  return cellComments.some((c) => c.rowId === rowId && c.columnId === columnId);
2919
3660
  },
2920
3661
  [cellComments]
2921
3662
  );
2922
- const handleAddCellComment = (0, import_react13.useCallback)(
3663
+ const handleAddCellComment = (0, import_react17.useCallback)(
2923
3664
  (rowId, columnId, text) => {
2924
3665
  if (!text.trim()) return;
2925
3666
  if (onAddCellComment) {
@@ -2941,7 +3682,7 @@ function useSpreadsheetComments({
2941
3682
  },
2942
3683
  [onAddCellComment]
2943
3684
  );
2944
- const handleToggleCommentResolved = (0, import_react13.useCallback)(
3685
+ const handleToggleCommentResolved = (0, import_react17.useCallback)(
2945
3686
  (commentId) => {
2946
3687
  if (onToggleCommentResolved) {
2947
3688
  onToggleCommentResolved(commentId);
@@ -2973,20 +3714,20 @@ function useSpreadsheetComments({
2973
3714
  }
2974
3715
 
2975
3716
  // src/hooks/useSpreadsheetUndoRedo.ts
2976
- var import_react14 = require("react");
3717
+ var import_react18 = require("react");
2977
3718
  function useSpreadsheetUndoRedo({
2978
3719
  enabled = true,
2979
3720
  maxStackSize = 50,
2980
3721
  autoSave = true,
2981
3722
  onSave
2982
3723
  }) {
2983
- const [undoStack, setUndoStack] = (0, import_react14.useState)([]);
2984
- const [redoStack, setRedoStack] = (0, import_react14.useState)([]);
2985
- const [hasUnsavedChanges, setHasUnsavedChanges] = (0, import_react14.useState)(false);
2986
- const [saveStatus, setSaveStatus] = (0, import_react14.useState)("saved");
2987
- const onSaveRef = (0, import_react14.useRef)(onSave);
3724
+ const [undoStack, setUndoStack] = (0, import_react18.useState)([]);
3725
+ const [redoStack, setRedoStack] = (0, import_react18.useState)([]);
3726
+ const [hasUnsavedChanges, setHasUnsavedChanges] = (0, import_react18.useState)(false);
3727
+ const [saveStatus, setSaveStatus] = (0, import_react18.useState)("saved");
3728
+ const onSaveRef = (0, import_react18.useRef)(onSave);
2988
3729
  onSaveRef.current = onSave;
2989
- const pushToUndoStack = (0, import_react14.useCallback)(
3730
+ const pushToUndoStack = (0, import_react18.useCallback)(
2990
3731
  (snapshot) => {
2991
3732
  if (!enabled) return;
2992
3733
  setUndoStack((prev) => {
@@ -3000,7 +3741,7 @@ function useSpreadsheetUndoRedo({
3000
3741
  },
3001
3742
  [enabled, maxStackSize]
3002
3743
  );
3003
- const handleUndo = (0, import_react14.useCallback)(() => {
3744
+ const handleUndo = (0, import_react18.useCallback)(() => {
3004
3745
  if (!enabled || undoStack.length === 0) return null;
3005
3746
  const previousSnapshot = undoStack[undoStack.length - 1];
3006
3747
  setUndoStack((prev) => prev.slice(0, -1));
@@ -3013,7 +3754,7 @@ function useSpreadsheetUndoRedo({
3013
3754
  });
3014
3755
  return previousSnapshot;
3015
3756
  }, [enabled, undoStack, maxStackSize]);
3016
- const handleRedo = (0, import_react14.useCallback)(() => {
3757
+ const handleRedo = (0, import_react18.useCallback)(() => {
3017
3758
  if (!enabled || redoStack.length === 0) return null;
3018
3759
  const nextSnapshot = redoStack[redoStack.length - 1];
3019
3760
  setRedoStack((prev) => prev.slice(0, -1));
@@ -3026,7 +3767,7 @@ function useSpreadsheetUndoRedo({
3026
3767
  });
3027
3768
  return nextSnapshot;
3028
3769
  }, [enabled, redoStack, maxStackSize]);
3029
- const handleSave = (0, import_react14.useCallback)(async () => {
3770
+ const handleSave = (0, import_react18.useCallback)(async () => {
3030
3771
  if (!hasUnsavedChanges && !autoSave) return;
3031
3772
  setSaveStatus("saving");
3032
3773
  try {
@@ -3039,7 +3780,7 @@ function useSpreadsheetUndoRedo({
3039
3780
  setSaveStatus("error");
3040
3781
  }
3041
3782
  }, [hasUnsavedChanges, autoSave]);
3042
- const markAsChanged = (0, import_react14.useCallback)(() => {
3783
+ const markAsChanged = (0, import_react18.useCallback)(() => {
3043
3784
  setHasUnsavedChanges(true);
3044
3785
  if (autoSave) {
3045
3786
  setSaveStatus("saving");
@@ -3060,11 +3801,11 @@ function useSpreadsheetUndoRedo({
3060
3801
  setSaveStatus("unsaved");
3061
3802
  }
3062
3803
  }, [autoSave]);
3063
- const markAsSaved = (0, import_react14.useCallback)(() => {
3804
+ const markAsSaved = (0, import_react18.useCallback)(() => {
3064
3805
  setHasUnsavedChanges(false);
3065
3806
  setSaveStatus("saved");
3066
3807
  }, []);
3067
- const clearStacks = (0, import_react14.useCallback)(() => {
3808
+ const clearStacks = (0, import_react18.useCallback)(() => {
3068
3809
  setUndoStack([]);
3069
3810
  setRedoStack([]);
3070
3811
  }, []);
@@ -3092,7 +3833,7 @@ function useSpreadsheetUndoRedo({
3092
3833
  }
3093
3834
 
3094
3835
  // src/hooks/useSpreadsheetKeyboardShortcuts.ts
3095
- var import_react15 = require("react");
3836
+ var import_react19 = require("react");
3096
3837
  function useSpreadsheetKeyboardShortcuts({
3097
3838
  onUndo,
3098
3839
  onRedo,
@@ -3102,15 +3843,16 @@ function useSpreadsheetKeyboardShortcuts({
3102
3843
  onTabNavigation,
3103
3844
  onCopy,
3104
3845
  onPaste,
3846
+ onSelectAll,
3105
3847
  hasFocusedCell = false,
3106
3848
  isEditing = false,
3107
3849
  customShortcuts = [],
3108
3850
  enabled = true
3109
3851
  } = {}) {
3110
- const [showKeyboardShortcuts, setShowKeyboardShortcuts] = (0, import_react15.useState)(false);
3852
+ const [showKeyboardShortcuts, setShowKeyboardShortcuts] = (0, import_react19.useState)(false);
3111
3853
  const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPod|iPad/.test(navigator.platform);
3112
3854
  const modifierKey = isMac ? "\u2318" : "Ctrl";
3113
- (0, import_react15.useEffect)(() => {
3855
+ (0, import_react19.useEffect)(() => {
3114
3856
  if (!enabled) return;
3115
3857
  const handleKeyDown = (event) => {
3116
3858
  if (event.key === "Escape") {
@@ -3136,6 +3878,11 @@ function useSpreadsheetKeyboardShortcuts({
3136
3878
  onRedo?.();
3137
3879
  return;
3138
3880
  }
3881
+ if ((event.metaKey || event.ctrlKey) && (event.key === "a" || event.key === "A") && !isEditing) {
3882
+ event.preventDefault();
3883
+ onSelectAll?.();
3884
+ return;
3885
+ }
3139
3886
  if ((event.metaKey || event.ctrlKey) && event.key === "c" && !isEditing && hasFocusedCell) {
3140
3887
  event.preventDefault();
3141
3888
  onCopy?.();
@@ -3200,6 +3947,7 @@ function useSpreadsheetKeyboardShortcuts({
3200
3947
  onTabNavigation,
3201
3948
  onCopy,
3202
3949
  onPaste,
3950
+ onSelectAll,
3203
3951
  hasFocusedCell,
3204
3952
  isEditing,
3205
3953
  customShortcuts
@@ -3217,6 +3965,7 @@ function useSpreadsheetKeyboardShortcuts({
3217
3965
  editing: [
3218
3966
  { label: "Undo", keys: [modifierKey, "Z"] },
3219
3967
  { label: "Redo", keys: [modifierKey, "Shift", "Z"] },
3968
+ { label: "Select all", keys: [modifierKey, "A"] },
3220
3969
  { label: "Copy cells", keys: [modifierKey, "C"] },
3221
3970
  { label: "Paste cells", keys: [modifierKey, "V"] },
3222
3971
  { label: "Confirm cell edit", keys: ["Enter"] },
@@ -3251,7 +4000,7 @@ function useSpreadsheetKeyboardShortcuts({
3251
4000
  }
3252
4001
 
3253
4002
  // src/hooks/useSpreadsheetSelection.ts
3254
- var import_react16 = require("react");
4003
+ var import_react20 = require("react");
3255
4004
  function useSpreadsheetSelection({
3256
4005
  data,
3257
4006
  columns,
@@ -3259,34 +4008,34 @@ function useSpreadsheetSelection({
3259
4008
  onCellRangeSelectionChange,
3260
4009
  enableCellEditing = true
3261
4010
  }) {
3262
- const [focusedCell, setFocusedCell] = (0, import_react16.useState)(null);
3263
- const [editingCell, setEditingCell] = (0, import_react16.useState)(null);
3264
- const [selectedCellRange, setSelectedCellRangeState] = (0, import_react16.useState)(null);
3265
- const [isDragging, setIsDragging] = (0, import_react16.useState)(false);
3266
- const [clipboardData, setClipboardData] = (0, import_react16.useState)(null);
3267
- const anchorCell = (0, import_react16.useRef)(null);
3268
- const rowIndexMap = (0, import_react16.useMemo)(() => {
4011
+ const [focusedCell, setFocusedCell] = (0, import_react20.useState)(null);
4012
+ const [editingCell, setEditingCell] = (0, import_react20.useState)(null);
4013
+ const [selectedCellRange, setSelectedCellRangeState] = (0, import_react20.useState)(null);
4014
+ const [isDragging, setIsDragging] = (0, import_react20.useState)(false);
4015
+ const [clipboardData, setClipboardData] = (0, import_react20.useState)(null);
4016
+ const anchorCell = (0, import_react20.useRef)(null);
4017
+ const rowIndexMap = (0, import_react20.useMemo)(() => {
3269
4018
  const map = /* @__PURE__ */ new Map();
3270
4019
  data.forEach((row, index) => {
3271
4020
  map.set(getRowId(row), index);
3272
4021
  });
3273
4022
  return map;
3274
4023
  }, [data, getRowId]);
3275
- const columnIndexMap = (0, import_react16.useMemo)(() => {
4024
+ const columnIndexMap = (0, import_react20.useMemo)(() => {
3276
4025
  const map = /* @__PURE__ */ new Map();
3277
4026
  columns.forEach((col, index) => {
3278
4027
  map.set(col.id, index);
3279
4028
  });
3280
4029
  return map;
3281
4030
  }, [columns]);
3282
- const setSelectedCellRange = (0, import_react16.useCallback)(
4031
+ const setSelectedCellRange = (0, import_react20.useCallback)(
3283
4032
  (range) => {
3284
4033
  setSelectedCellRangeState(range);
3285
4034
  onCellRangeSelectionChange?.(range);
3286
4035
  },
3287
4036
  [onCellRangeSelectionChange]
3288
4037
  );
3289
- const getNormalizedRange = (0, import_react16.useCallback)(
4038
+ const getNormalizedRange = (0, import_react20.useCallback)(
3290
4039
  (range) => {
3291
4040
  if (!range) return null;
3292
4041
  const startRowIdx = rowIndexMap.get(range.start.rowId);
@@ -3305,7 +4054,7 @@ function useSpreadsheetSelection({
3305
4054
  },
3306
4055
  [rowIndexMap, columnIndexMap]
3307
4056
  );
3308
- const isCellInSelection = (0, import_react16.useCallback)(
4057
+ const isCellInSelection = (0, import_react20.useCallback)(
3309
4058
  (rowId, columnId) => {
3310
4059
  const normalizedRange = getNormalizedRange(selectedCellRange);
3311
4060
  if (!normalizedRange) return false;
@@ -3316,7 +4065,7 @@ function useSpreadsheetSelection({
3316
4065
  },
3317
4066
  [selectedCellRange, rowIndexMap, columnIndexMap, getNormalizedRange]
3318
4067
  );
3319
- const getCellSelectionEdge = (0, import_react16.useCallback)(
4068
+ const getCellSelectionEdge = (0, import_react20.useCallback)(
3320
4069
  (rowId, columnId) => {
3321
4070
  if (!isCellInSelection(rowId, columnId)) return void 0;
3322
4071
  const normalizedRange = getNormalizedRange(selectedCellRange);
@@ -3333,7 +4082,7 @@ function useSpreadsheetSelection({
3333
4082
  },
3334
4083
  [isCellInSelection, selectedCellRange, rowIndexMap, columnIndexMap, getNormalizedRange]
3335
4084
  );
3336
- const getSelectedCells = (0, import_react16.useCallback)(() => {
4085
+ const getSelectedCells = (0, import_react20.useCallback)(() => {
3337
4086
  const normalizedRange = getNormalizedRange(selectedCellRange);
3338
4087
  if (!normalizedRange) {
3339
4088
  return focusedCell ? [focusedCell] : [];
@@ -3351,7 +4100,7 @@ function useSpreadsheetSelection({
3351
4100
  }
3352
4101
  return cells;
3353
4102
  }, [selectedCellRange, focusedCell, data, columns, getRowId, getNormalizedRange]);
3354
- const getSelectedCellValues = (0, import_react16.useCallback)(() => {
4103
+ const getSelectedCellValues = (0, import_react20.useCallback)(() => {
3355
4104
  const cells = getSelectedCells();
3356
4105
  return cells.map((cell) => {
3357
4106
  const row = data.find((r) => getRowId(r) === cell.rowId);
@@ -3360,7 +4109,7 @@ function useSpreadsheetSelection({
3360
4109
  return { position: cell, value };
3361
4110
  });
3362
4111
  }, [getSelectedCells, data, columns, getRowId]);
3363
- const handleCellClick = (0, import_react16.useCallback)(
4112
+ const handleCellClick = (0, import_react20.useCallback)(
3364
4113
  (rowId, columnId, event) => {
3365
4114
  event.stopPropagation();
3366
4115
  const newCell = { rowId, columnId };
@@ -3374,15 +4123,12 @@ function useSpreadsheetSelection({
3374
4123
  anchorCell.current = newCell;
3375
4124
  setFocusedCell(newCell);
3376
4125
  setSelectedCellRange(null);
3377
- const column = columns.find((c) => c.id === columnId);
3378
- if (column?.editable && enableCellEditing) {
3379
- setEditingCell(newCell);
3380
- }
4126
+ setEditingCell(null);
3381
4127
  }
3382
4128
  },
3383
- [columns, enableCellEditing, setSelectedCellRange]
4129
+ [setSelectedCellRange]
3384
4130
  );
3385
- const handleCellMouseDown = (0, import_react16.useCallback)(
4131
+ const handleCellMouseDown = (0, import_react20.useCallback)(
3386
4132
  (rowId, columnId, event) => {
3387
4133
  if (event.button !== 0) return;
3388
4134
  const newCell = { rowId, columnId };
@@ -3398,28 +4144,23 @@ function useSpreadsheetSelection({
3398
4144
  anchorCell.current = newCell;
3399
4145
  setFocusedCell(newCell);
3400
4146
  setSelectedCellRange(null);
3401
- const column = columns.find((c) => c.id === columnId);
3402
- if (column?.editable && enableCellEditing) {
3403
- setEditingCell(newCell);
3404
- } else {
3405
- setEditingCell(null);
3406
- }
4147
+ setEditingCell(null);
3407
4148
  }
3408
4149
  },
3409
- [columns, enableCellEditing, setSelectedCellRange]
4150
+ [setSelectedCellRange]
3410
4151
  );
3411
- const handleCellMouseEnter = (0, import_react16.useCallback)((_rowId, _columnId) => {
4152
+ const handleCellMouseEnter = (0, import_react20.useCallback)((_rowId, _columnId) => {
3412
4153
  }, []);
3413
- const handleMouseUp = (0, import_react16.useCallback)(() => {
4154
+ const handleMouseUp = (0, import_react20.useCallback)(() => {
3414
4155
  setIsDragging(false);
3415
4156
  }, []);
3416
- const clearSelection = (0, import_react16.useCallback)(() => {
4157
+ const clearSelection = (0, import_react20.useCallback)(() => {
3417
4158
  setFocusedCell(null);
3418
4159
  setEditingCell(null);
3419
4160
  setSelectedCellRange(null);
3420
4161
  anchorCell.current = null;
3421
4162
  }, [setSelectedCellRange]);
3422
- const navigateCell = (0, import_react16.useCallback)(
4163
+ const navigateCell = (0, import_react20.useCallback)(
3423
4164
  (direction, extendSelection = false) => {
3424
4165
  const currentCell = focusedCell;
3425
4166
  if (!currentCell) return;
@@ -3466,7 +4207,7 @@ function useSpreadsheetSelection({
3466
4207
  },
3467
4208
  [focusedCell, data, columns, getRowId, rowIndexMap, columnIndexMap, setSelectedCellRange]
3468
4209
  );
3469
- const handleTabNavigation = (0, import_react16.useCallback)(
4210
+ const handleTabNavigation = (0, import_react20.useCallback)(
3470
4211
  (shiftKey) => {
3471
4212
  const currentCell = focusedCell;
3472
4213
  if (!currentCell) return;
@@ -3507,17 +4248,17 @@ function useSpreadsheetSelection({
3507
4248
  },
3508
4249
  [focusedCell, data, columns, getRowId, rowIndexMap, columnIndexMap, setSelectedCellRange]
3509
4250
  );
3510
- const enterEditMode = (0, import_react16.useCallback)(() => {
4251
+ const enterEditMode = (0, import_react20.useCallback)(() => {
3511
4252
  if (!focusedCell || !enableCellEditing) return;
3512
4253
  const column = columns.find((c) => c.id === focusedCell.columnId);
3513
4254
  if (column?.editable) {
3514
4255
  setEditingCell(focusedCell);
3515
4256
  }
3516
4257
  }, [focusedCell, columns, enableCellEditing]);
3517
- const exitEditMode = (0, import_react16.useCallback)(() => {
4258
+ const exitEditMode = (0, import_react20.useCallback)(() => {
3518
4259
  setEditingCell(null);
3519
4260
  }, []);
3520
- const copySelectedCells = (0, import_react16.useCallback)(() => {
4261
+ const copySelectedCells = (0, import_react20.useCallback)(() => {
3521
4262
  const normalizedRange = getNormalizedRange(selectedCellRange);
3522
4263
  if (!normalizedRange && !focusedCell) return;
3523
4264
  const startRowIdx = normalizedRange?.startRowIdx ?? rowIndexMap.get(focusedCell.rowId) ?? 0;
@@ -3566,14 +4307,14 @@ function useSpreadsheetSelection({
3566
4307
  rowIndexMap,
3567
4308
  columnIndexMap
3568
4309
  ]);
3569
- const parseClipboardText = (0, import_react16.useCallback)((text) => {
4310
+ const parseClipboardText = (0, import_react20.useCallback)((text) => {
3570
4311
  const lines = text.split(/\r?\n/);
3571
4312
  if (lines.length > 0 && lines[lines.length - 1] === "") {
3572
4313
  lines.pop();
3573
4314
  }
3574
4315
  return lines.map((line) => line.split(" "));
3575
4316
  }, []);
3576
- const createEditsFromValues = (0, import_react16.useCallback)(
4317
+ const createEditsFromValues = (0, import_react20.useCallback)(
3577
4318
  (values) => {
3578
4319
  if (!focusedCell) return [];
3579
4320
  const edits = [];
@@ -3633,53 +4374,238 @@ function useSpreadsheetSelection({
3633
4374
  getNormalizedRange
3634
4375
  ]
3635
4376
  );
3636
- const pasteClipboard = (0, import_react16.useCallback)(() => {
4377
+ const pasteClipboard = (0, import_react20.useCallback)(() => {
3637
4378
  if (!clipboardData?.values) return [];
3638
4379
  return createEditsFromValues(clipboardData.values);
3639
4380
  }, [clipboardData, createEditsFromValues]);
3640
- const pasteFromSystemClipboard = (0, import_react16.useCallback)(async () => {
4381
+ const pasteFromSystemClipboard = (0, import_react20.useCallback)(async () => {
3641
4382
  if (!focusedCell) return [];
3642
4383
  try {
3643
4384
  const text = await navigator.clipboard.readText();
3644
4385
  if (!text) {
3645
4386
  return pasteClipboard();
3646
4387
  }
3647
- const values = parseClipboardText(text);
3648
- return createEditsFromValues(values);
3649
- } catch {
3650
- return pasteClipboard();
4388
+ const values = parseClipboardText(text);
4389
+ return createEditsFromValues(values);
4390
+ } catch {
4391
+ return pasteClipboard();
4392
+ }
4393
+ }, [focusedCell, pasteClipboard, parseClipboardText, createEditsFromValues]);
4394
+ return {
4395
+ focusedCell,
4396
+ setFocusedCell,
4397
+ editingCell,
4398
+ setEditingCell,
4399
+ selectedCellRange,
4400
+ setSelectedCellRange,
4401
+ isDragging,
4402
+ handleCellClick,
4403
+ handleCellMouseDown,
4404
+ handleCellMouseEnter,
4405
+ handleMouseUp,
4406
+ isCellInSelection,
4407
+ getCellSelectionEdge,
4408
+ getSelectedCells,
4409
+ getSelectedCellValues,
4410
+ clearSelection,
4411
+ navigateCell,
4412
+ handleTabNavigation,
4413
+ enterEditMode,
4414
+ exitEditMode,
4415
+ clipboardData,
4416
+ copySelectedCells,
4417
+ pasteClipboard,
4418
+ pasteFromSystemClipboard
4419
+ };
4420
+ }
4421
+
4422
+ // src/hooks/useSpreadsheetSummary.ts
4423
+ var import_react21 = require("react");
4424
+ function useSpreadsheetSummary({
4425
+ selectedCellValues,
4426
+ columns
4427
+ }) {
4428
+ const summary = (0, import_react21.useMemo)(() => {
4429
+ if (selectedCellValues.length === 0) return null;
4430
+ const numericValues = [];
4431
+ for (const { position, value } of selectedCellValues) {
4432
+ const column = columns.find((c) => c.id === position.columnId);
4433
+ if (column?.type === "number" || typeof value === "number") {
4434
+ const num = typeof value === "number" ? value : parseFloat(value);
4435
+ if (!isNaN(num)) {
4436
+ numericValues.push(num);
4437
+ }
4438
+ }
4439
+ }
4440
+ if (numericValues.length === 0) return null;
4441
+ const sum = numericValues.reduce((a, b) => a + b, 0);
4442
+ return {
4443
+ sum: Math.round(sum * 100) / 100,
4444
+ avg: Math.round(sum / numericValues.length * 100) / 100,
4445
+ count: selectedCellValues.length,
4446
+ numericCount: numericValues.length,
4447
+ min: Math.round(Math.min(...numericValues) * 100) / 100,
4448
+ max: Math.round(Math.max(...numericValues) * 100) / 100
4449
+ };
4450
+ }, [selectedCellValues, columns]);
4451
+ return {
4452
+ summary,
4453
+ hasNumericValues: summary !== null
4454
+ };
4455
+ }
4456
+
4457
+ // src/hooks/useSpreadsheetColumnResize.ts
4458
+ var import_react22 = require("react");
4459
+ var DEFAULT_MIN_WIDTH = 40;
4460
+ function useSpreadsheetColumnResize({
4461
+ minWidth = DEFAULT_MIN_WIDTH,
4462
+ initialColumnWidths,
4463
+ onColumnResize
4464
+ } = {}) {
4465
+ const [columnWidths, setColumnWidths] = (0, import_react22.useState)(() => {
4466
+ if (initialColumnWidths) {
4467
+ return new Map(Object.entries(initialColumnWidths));
4468
+ }
4469
+ return /* @__PURE__ */ new Map();
4470
+ });
4471
+ const [resizingColumnId, setResizingColumnId] = (0, import_react22.useState)(null);
4472
+ const startXRef = (0, import_react22.useRef)(0);
4473
+ const startWidthRef = (0, import_react22.useRef)(0);
4474
+ const rafRef = (0, import_react22.useRef)(null);
4475
+ const getColumnWidth = (0, import_react22.useCallback)(
4476
+ (columnId, defaultWidth) => {
4477
+ return columnWidths.get(columnId) ?? defaultWidth;
4478
+ },
4479
+ [columnWidths]
4480
+ );
4481
+ const updateColumnDom = (0, import_react22.useCallback)((columnId, width) => {
4482
+ const widthPx = `${width}px`;
4483
+ const cells = document.querySelectorAll(`[data-column-id="${columnId}"]`);
4484
+ for (let i = 0; i < cells.length; i++) {
4485
+ const el = cells[i];
4486
+ el.style.width = widthPx;
4487
+ el.style.minWidth = widthPx;
4488
+ }
4489
+ }, []);
4490
+ const getResizeHandleProps = (0, import_react22.useCallback)(
4491
+ (columnId, currentWidth) => {
4492
+ return {
4493
+ onMouseDown: (e) => {
4494
+ e.preventDefault();
4495
+ e.stopPropagation();
4496
+ startXRef.current = e.clientX;
4497
+ startWidthRef.current = columnWidths.get(columnId) ?? currentWidth;
4498
+ setResizingColumnId(columnId);
4499
+ document.body.style.cursor = "col-resize";
4500
+ document.body.style.userSelect = "none";
4501
+ let latestWidth = startWidthRef.current;
4502
+ const moveHandler = (ev) => {
4503
+ const diff = ev.clientX - startXRef.current;
4504
+ latestWidth = Math.max(minWidth, startWidthRef.current + diff);
4505
+ if (rafRef.current === null) {
4506
+ rafRef.current = requestAnimationFrame(() => {
4507
+ rafRef.current = null;
4508
+ updateColumnDom(columnId, latestWidth);
4509
+ });
4510
+ }
4511
+ };
4512
+ const upHandler = () => {
4513
+ document.removeEventListener("mousemove", moveHandler);
4514
+ document.removeEventListener("mouseup", upHandler);
4515
+ if (rafRef.current !== null) {
4516
+ cancelAnimationFrame(rafRef.current);
4517
+ rafRef.current = null;
4518
+ }
4519
+ setColumnWidths((prev) => {
4520
+ const next = new Map(prev);
4521
+ next.set(columnId, latestWidth);
4522
+ return next;
4523
+ });
4524
+ onColumnResize?.(columnId, latestWidth);
4525
+ setResizingColumnId(null);
4526
+ document.body.style.cursor = "";
4527
+ document.body.style.userSelect = "";
4528
+ };
4529
+ document.addEventListener("mousemove", moveHandler);
4530
+ document.addEventListener("mouseup", upHandler);
4531
+ },
4532
+ style: {
4533
+ cursor: "col-resize"
4534
+ },
4535
+ className: "absolute top-0 right-0 w-1 h-full hover:bg-blue-400 transition-colors z-10"
4536
+ };
4537
+ },
4538
+ [columnWidths, minWidth, onColumnResize, updateColumnDom]
4539
+ );
4540
+ return {
4541
+ columnWidths,
4542
+ getColumnWidth,
4543
+ getResizeHandleProps,
4544
+ isResizing: resizingColumnId !== null,
4545
+ resizingColumnId
4546
+ };
4547
+ }
4548
+
4549
+ // src/components/SelectionSummaryBar.tsx
4550
+ var import_jsx_runtime13 = require("react/jsx-runtime");
4551
+ function SelectionSummaryBar({
4552
+ summary,
4553
+ focusedCell,
4554
+ columns,
4555
+ data,
4556
+ getRowId,
4557
+ currentPage,
4558
+ pageSize
4559
+ }) {
4560
+ let addressDisplay = null;
4561
+ let valueDisplay = null;
4562
+ if (focusedCell) {
4563
+ const column = columns.find((c) => c.id === focusedCell.columnId);
4564
+ const rowIndex = data.findIndex((r) => getRowId(r) === focusedCell.rowId);
4565
+ const row = rowIndex !== -1 ? data[rowIndex] : null;
4566
+ if (column && row) {
4567
+ const displayRowIndex = rowIndex + 1 + (currentPage - 1) * pageSize;
4568
+ addressDisplay = `Row ${displayRowIndex} / ${column.label}`;
4569
+ const value = column.getValue ? column.getValue(row) : row[focusedCell.columnId];
4570
+ if (value !== null && value !== void 0 && value !== "") {
4571
+ valueDisplay = String(value);
4572
+ }
3651
4573
  }
3652
- }, [focusedCell, pasteClipboard, parseClipboardText, createEditsFromValues]);
3653
- return {
3654
- focusedCell,
3655
- setFocusedCell,
3656
- editingCell,
3657
- setEditingCell,
3658
- selectedCellRange,
3659
- setSelectedCellRange,
3660
- isDragging,
3661
- handleCellClick,
3662
- handleCellMouseDown,
3663
- handleCellMouseEnter,
3664
- handleMouseUp,
3665
- isCellInSelection,
3666
- getCellSelectionEdge,
3667
- getSelectedCells,
3668
- getSelectedCellValues,
3669
- clearSelection,
3670
- navigateCell,
3671
- handleTabNavigation,
3672
- enterEditMode,
3673
- exitEditMode,
3674
- clipboardData,
3675
- copySelectedCells,
3676
- pasteClipboard,
3677
- pasteFromSystemClipboard
3678
- };
4574
+ }
4575
+ if (!addressDisplay && !summary) return null;
4576
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center justify-between px-3 py-1.5 bg-gray-50 border-t border-gray-200 text-xs text-gray-600", children: [
4577
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "flex items-center gap-2 min-w-0", children: addressDisplay && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
4578
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "font-medium text-gray-500 bg-white px-2 py-0.5 rounded border border-gray-200 shrink-0", children: addressDisplay }),
4579
+ valueDisplay && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-gray-700 truncate", title: valueDisplay, children: valueDisplay })
4580
+ ] }) }),
4581
+ summary && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-4 shrink-0", children: [
4582
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { children: [
4583
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-gray-400 mr-1", children: "Count:" }),
4584
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "font-medium text-gray-700", children: summary.numericCount })
4585
+ ] }),
4586
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { children: [
4587
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-gray-400 mr-1", children: "Sum:" }),
4588
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "font-medium text-gray-700", children: summary.sum.toLocaleString() })
4589
+ ] }),
4590
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { children: [
4591
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-gray-400 mr-1", children: "Avg:" }),
4592
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "font-medium text-gray-700", children: summary.avg.toLocaleString() })
4593
+ ] }),
4594
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { children: [
4595
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-gray-400 mr-1", children: "Min:" }),
4596
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "font-medium text-gray-700", children: summary.min.toLocaleString() })
4597
+ ] }),
4598
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { children: [
4599
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-gray-400 mr-1", children: "Max:" }),
4600
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "font-medium text-gray-700", children: summary.max.toLocaleString() })
4601
+ ] })
4602
+ ] })
4603
+ ] });
3679
4604
  }
4605
+ SelectionSummaryBar.displayName = "SelectionSummaryBar";
3680
4606
 
3681
4607
  // src/components/Spreadsheet.tsx
3682
- var import_jsx_runtime13 = require("react/jsx-runtime");
4608
+ var import_jsx_runtime14 = require("react/jsx-runtime");
3683
4609
  function Spreadsheet({
3684
4610
  data,
3685
4611
  columns,
@@ -3725,9 +4651,11 @@ function Spreadsheet({
3725
4651
  pageSize: controlledPageSize,
3726
4652
  sortConfig: controlledSortConfig,
3727
4653
  onPageChange,
3728
- filters: controlledFilters
4654
+ filters: controlledFilters,
4655
+ duplicateCheckColumns: propDuplicateCheckColumns,
4656
+ onDuplicateCheckChange
3729
4657
  }) {
3730
- const [spreadsheetSettings, setSpreadsheetSettings] = (0, import_react17.useState)({
4658
+ const [spreadsheetSettings, setSpreadsheetSettings] = (0, import_react23.useState)({
3731
4659
  defaultPinnedColumns: initialSettings?.defaultPinnedColumns ?? [],
3732
4660
  defaultSort: initialSettings?.defaultSort ?? null,
3733
4661
  defaultPageSize: initialSettings?.defaultPageSize ?? 25,
@@ -3735,6 +4663,32 @@ function Spreadsheet({
3735
4663
  autoSave: initialSettings?.autoSave ?? true,
3736
4664
  compactView: initialSettings?.compactView ?? false
3737
4665
  });
4666
+ const {
4667
+ isCellDuplicate,
4668
+ toggleDuplicateCheck,
4669
+ duplicateCheckColumns,
4670
+ duplicateRowIds,
4671
+ getDuplicateColumnCount
4672
+ } = useSpreadsheetDuplicates({
4673
+ data,
4674
+ columns,
4675
+ duplicateCheckColumns: propDuplicateCheckColumns ?? [],
4676
+ getRowId
4677
+ });
4678
+ const handleDuplicateCheckToggle = (0, import_react23.useCallback)(
4679
+ (columnId) => {
4680
+ setIsProcessing(true);
4681
+ (0, import_react23.startTransition)(() => {
4682
+ toggleDuplicateCheck(columnId);
4683
+ const currentCols = Array.from(duplicateCheckColumns);
4684
+ const next = currentCols.includes(columnId) ? currentCols.filter((id) => id !== columnId) : [...currentCols, columnId];
4685
+ onDuplicateCheckChange?.(next);
4686
+ setIsProcessing(false);
4687
+ });
4688
+ },
4689
+ [toggleDuplicateCheck, duplicateCheckColumns, onDuplicateCheckChange]
4690
+ );
4691
+ const [isProcessing, setIsProcessing] = (0, import_react23.useState)(false);
3738
4692
  const {
3739
4693
  filters,
3740
4694
  sortConfig,
@@ -3754,7 +4708,9 @@ function Spreadsheet({
3754
4708
  serverSide,
3755
4709
  controlledFilters,
3756
4710
  controlledSortConfig,
3757
- defaultSortConfig: spreadsheetSettings.defaultSort
4711
+ defaultSortConfig: spreadsheetSettings.defaultSort,
4712
+ duplicateRowIds,
4713
+ getRowId
3758
4714
  });
3759
4715
  const {
3760
4716
  getCellHighlight,
@@ -3768,7 +4724,8 @@ function Spreadsheet({
3768
4724
  highlightPickerColumn,
3769
4725
  setHighlightPickerColumn,
3770
4726
  highlightPickerCell,
3771
- setHighlightPickerCell
4727
+ setHighlightPickerCell,
4728
+ recentColors
3772
4729
  } = useSpreadsheetHighlighting({
3773
4730
  externalRowHighlights,
3774
4731
  onRowHighlight,
@@ -3777,6 +4734,15 @@ function Spreadsheet({
3777
4734
  externalCellHighlights,
3778
4735
  onCellHighlight
3779
4736
  });
4737
+ const { getColumnWidth, getResizeHandleProps, isResizing, columnWidths } = useSpreadsheetColumnResize({
4738
+ initialColumnWidths: initialSettings?.columnWidths,
4739
+ onColumnResize: (columnId, width) => {
4740
+ setSpreadsheetSettings((prev) => ({
4741
+ ...prev,
4742
+ columnWidths: { ...prev.columnWidths, [columnId]: width }
4743
+ }));
4744
+ }
4745
+ });
3780
4746
  const {
3781
4747
  pinnedColumns,
3782
4748
  isRowIndexPinned,
@@ -3788,12 +4754,15 @@ function Spreadsheet({
3788
4754
  getColumnLeftOffset,
3789
4755
  getColumnRightOffset,
3790
4756
  isColumnPinned,
3791
- getColumnPinSide
4757
+ getColumnPinSide,
4758
+ getPinnedZIndex,
4759
+ measureRef
3792
4760
  } = useSpreadsheetPinning({
3793
4761
  columns,
3794
4762
  columnGroups,
3795
4763
  defaultPinnedColumns: initialSettings?.defaultPinnedColumns,
3796
- defaultPinnedRightColumns: initialSettings?.defaultPinnedRightColumns
4764
+ defaultPinnedRightColumns: initialSettings?.defaultPinnedRightColumns,
4765
+ getColumnWidth
3797
4766
  });
3798
4767
  const {
3799
4768
  getCellComments,
@@ -3810,8 +4779,8 @@ function Spreadsheet({
3810
4779
  onAddCellComment,
3811
4780
  onToggleCommentResolved
3812
4781
  });
3813
- const [showSettingsModal, setShowSettingsModal] = (0, import_react17.useState)(false);
3814
- const [showFiltersPanel, setShowFiltersPanel] = (0, import_react17.useState)(false);
4782
+ const [showSettingsModal, setShowSettingsModal] = (0, import_react23.useState)(false);
4783
+ const [showFiltersPanel, setShowFiltersPanel] = (0, import_react23.useState)(false);
3815
4784
  const {
3816
4785
  canUndo,
3817
4786
  canRedo,
@@ -3829,15 +4798,15 @@ function Spreadsheet({
3829
4798
  autoSave: spreadsheetSettings.autoSave,
3830
4799
  onSave
3831
4800
  });
3832
- const [selectedRows, setSelectedRows] = (0, import_react17.useState)(/* @__PURE__ */ new Set());
3833
- const [lastSelectedRow, setLastSelectedRow] = (0, import_react17.useState)(null);
3834
- const [hoveredRow, setHoveredRow] = (0, import_react17.useState)(null);
3835
- const [internalCurrentPage, setInternalCurrentPage] = (0, import_react17.useState)(1);
3836
- const [internalPageSize, setInternalPageSize] = (0, import_react17.useState)(spreadsheetSettings.defaultPageSize);
3837
- const [zoom, setZoom] = (0, import_react17.useState)(spreadsheetSettings.defaultZoom);
4801
+ const [selectedRows, setSelectedRows] = (0, import_react23.useState)(/* @__PURE__ */ new Set());
4802
+ const [lastSelectedRow, setLastSelectedRow] = (0, import_react23.useState)(null);
4803
+ const hoveredRowRef = (0, import_react23.useRef)(null);
4804
+ const [internalCurrentPage, setInternalCurrentPage] = (0, import_react23.useState)(1);
4805
+ const [internalPageSize, setInternalPageSize] = (0, import_react23.useState)(spreadsheetSettings.defaultPageSize);
4806
+ const [zoom, setZoom] = (0, import_react23.useState)(spreadsheetSettings.defaultZoom);
3838
4807
  const currentPage = controlledCurrentPage ?? internalCurrentPage;
3839
4808
  const pageSize = controlledPageSize ?? internalPageSize;
3840
- const handlePageChange = (0, import_react17.useCallback)(
4809
+ const handlePageChange = (0, import_react23.useCallback)(
3841
4810
  (newPage) => {
3842
4811
  if (controlledCurrentPage === void 0) {
3843
4812
  setInternalCurrentPage(newPage);
@@ -3846,7 +4815,7 @@ function Spreadsheet({
3846
4815
  },
3847
4816
  [controlledCurrentPage, onPageChange, pageSize]
3848
4817
  );
3849
- const handlePageSizeChange = (0, import_react17.useCallback)(
4818
+ const handlePageSizeChange = (0, import_react23.useCallback)(
3850
4819
  (newPageSize) => {
3851
4820
  if (controlledPageSize === void 0) {
3852
4821
  setInternalPageSize(newPageSize);
@@ -3858,30 +4827,43 @@ function Spreadsheet({
3858
4827
  },
3859
4828
  [controlledPageSize, controlledCurrentPage, onPageChange]
3860
4829
  );
3861
- const resetPaginationToFirstPage = (0, import_react17.useCallback)(() => {
4830
+ const resetPaginationToFirstPage = (0, import_react23.useCallback)(() => {
3862
4831
  if (controlledCurrentPage === void 0) {
3863
4832
  setInternalCurrentPage(1);
3864
4833
  }
3865
4834
  onPageChange?.(1, pageSize);
3866
4835
  }, [controlledCurrentPage, onPageChange, pageSize]);
3867
- const handleFilterChangeWithReset = (0, import_react17.useCallback)(
4836
+ const handleFilterChangeWithReset = (0, import_react23.useCallback)(
3868
4837
  (columnId, filter) => {
3869
4838
  handleFilterChange(columnId, filter);
3870
4839
  resetPaginationToFirstPage();
3871
4840
  },
3872
4841
  [handleFilterChange, resetPaginationToFirstPage]
3873
4842
  );
3874
- const clearAllFiltersWithReset = (0, import_react17.useCallback)(() => {
4843
+ const clearAllFiltersWithReset = (0, import_react23.useCallback)(() => {
3875
4844
  clearAllFilters();
3876
4845
  resetPaginationToFirstPage();
3877
4846
  }, [clearAllFilters, resetPaginationToFirstPage]);
3878
- (0, import_react17.useEffect)(() => {
4847
+ (0, import_react23.useEffect)(() => {
3879
4848
  setSpreadsheetSettings((prev) => ({
3880
4849
  ...prev,
3881
4850
  defaultSort: sortConfig
3882
4851
  }));
3883
4852
  }, [sortConfig]);
3884
- (0, import_react17.useEffect)(() => {
4853
+ const hasSyncedPinnedFromSettings = (0, import_react23.useRef)(false);
4854
+ (0, import_react23.useEffect)(() => {
4855
+ if (hasSyncedPinnedFromSettings.current) return;
4856
+ const settingsPinned = initialSettings?.defaultPinnedColumns;
4857
+ if (settingsPinned && settingsPinned.length > 0) {
4858
+ const currentIds = Array.from(pinnedColumns.keys());
4859
+ const hasAllSettingsPinned = settingsPinned.every((id) => pinnedColumns.has(id));
4860
+ if (!hasAllSettingsPinned) {
4861
+ setPinnedColumnsFromIds(settingsPinned);
4862
+ }
4863
+ hasSyncedPinnedFromSettings.current = true;
4864
+ }
4865
+ }, [initialSettings?.defaultPinnedColumns, pinnedColumns, setPinnedColumnsFromIds]);
4866
+ (0, import_react23.useEffect)(() => {
3885
4867
  const pinnedColumnIds = Array.from(pinnedColumns.keys());
3886
4868
  setSpreadsheetSettings((prev) => {
3887
4869
  const prevIds = prev.defaultPinnedColumns;
@@ -3894,15 +4876,15 @@ function Spreadsheet({
3894
4876
  };
3895
4877
  });
3896
4878
  }, [pinnedColumns]);
3897
- const isInitialMount = (0, import_react17.useRef)(true);
3898
- (0, import_react17.useEffect)(() => {
4879
+ const isInitialMount = (0, import_react23.useRef)(true);
4880
+ (0, import_react23.useEffect)(() => {
3899
4881
  if (isInitialMount.current) {
3900
4882
  isInitialMount.current = false;
3901
4883
  return;
3902
4884
  }
3903
4885
  onSettingsChange?.(spreadsheetSettings);
3904
4886
  }, [spreadsheetSettings, onSettingsChange]);
3905
- const applyUndo = (0, import_react17.useCallback)(() => {
4887
+ const applyUndo = (0, import_react23.useCallback)(() => {
3906
4888
  const entry = popUndoEntry();
3907
4889
  if (!entry || !onCellsEdit) return;
3908
4890
  if (entry.type === "cell-edit") {
@@ -3920,7 +4902,7 @@ function Spreadsheet({
3920
4902
  }
3921
4903
  markAsChanged();
3922
4904
  }, [popUndoEntry, onCellsEdit, markAsChanged]);
3923
- const applyRedo = (0, import_react17.useCallback)(() => {
4905
+ const applyRedo = (0, import_react23.useCallback)(() => {
3924
4906
  const entry = popRedoEntry();
3925
4907
  if (!entry || !onCellsEdit) return;
3926
4908
  if (entry.type === "cell-edit") {
@@ -3936,18 +4918,23 @@ function Spreadsheet({
3936
4918
  }
3937
4919
  markAsChanged();
3938
4920
  }, [popRedoEntry, onCellsEdit, markAsChanged]);
3939
- const paginatedData = (0, import_react17.useMemo)(() => {
4921
+ const paginatedData = (0, import_react23.useMemo)(() => {
3940
4922
  if (serverSide) {
3941
- return filteredData;
4923
+ return filteredData.toArray();
3942
4924
  }
3943
4925
  const startIndex = (currentPage - 1) * pageSize;
3944
4926
  return filteredData.slice(startIndex, startIndex + pageSize);
3945
4927
  }, [filteredData, currentPage, pageSize, serverSide]);
3946
4928
  const {
3947
4929
  focusedCell,
4930
+ setFocusedCell,
3948
4931
  editingCell,
3949
4932
  setEditingCell,
4933
+ selectedCellRange,
4934
+ setSelectedCellRange,
3950
4935
  handleCellMouseDown,
4936
+ handleCellMouseEnter,
4937
+ handleMouseUp,
3951
4938
  isCellInSelection,
3952
4939
  getCellSelectionEdge,
3953
4940
  clearSelection,
@@ -3955,14 +4942,20 @@ function Spreadsheet({
3955
4942
  handleTabNavigation,
3956
4943
  enterEditMode,
3957
4944
  copySelectedCells,
3958
- pasteFromSystemClipboard
4945
+ pasteFromSystemClipboard,
4946
+ getSelectedCellValues
3959
4947
  } = useSpreadsheetSelection({
3960
4948
  data: paginatedData,
3961
4949
  columns: visibleColumns,
3962
4950
  getRowId,
3963
4951
  enableCellEditing
3964
4952
  });
3965
- const handleEscapeCallback = (0, import_react17.useCallback)(() => {
4953
+ const selectedCellValues = (0, import_react23.useMemo)(() => getSelectedCellValues(), [getSelectedCellValues]);
4954
+ const { summary: selectionSummary, hasNumericValues } = useSpreadsheetSummary({
4955
+ selectedCellValues,
4956
+ columns: visibleColumns
4957
+ });
4958
+ const handleEscapeCallback = (0, import_react23.useCallback)(() => {
3966
4959
  if (commentModalCell !== null) {
3967
4960
  setCommentModalCell(null);
3968
4961
  } else if (viewCommentsCell !== null) {
@@ -3991,7 +4984,7 @@ function Spreadsheet({
3991
4984
  setHighlightPickerCell,
3992
4985
  clearSelection
3993
4986
  ]);
3994
- const handleNavigate = (0, import_react17.useCallback)(
4987
+ const handleNavigate = (0, import_react23.useCallback)(
3995
4988
  (direction, event) => {
3996
4989
  const extendSelection = event?.shiftKey ?? false;
3997
4990
  navigateCell(direction, extendSelection);
@@ -4012,10 +5005,10 @@ function Spreadsheet({
4012
5005
  },
4013
5006
  [navigateCell, focusedCell]
4014
5007
  );
4015
- const handleEnterEditMode = (0, import_react17.useCallback)(() => {
5008
+ const handleEnterEditMode = (0, import_react23.useCallback)(() => {
4016
5009
  enterEditMode();
4017
5010
  }, [enterEditMode]);
4018
- const handlePaste = (0, import_react17.useCallback)(async () => {
5011
+ const handlePaste = (0, import_react23.useCallback)(async () => {
4019
5012
  const edits = await pasteFromSystemClipboard();
4020
5013
  if (edits.length > 0 && onCellsEdit) {
4021
5014
  if (enableUndoRedo) {
@@ -4055,26 +5048,39 @@ function Spreadsheet({
4055
5048
  onTabNavigation: handleTabNavigation,
4056
5049
  onCopy: copySelectedCells,
4057
5050
  onPaste: handlePaste,
5051
+ onSelectAll: () => {
5052
+ if (paginatedData.length > 0 && visibleColumns.length > 0) {
5053
+ const firstRow = paginatedData[0];
5054
+ const lastRow = paginatedData[paginatedData.length - 1];
5055
+ const firstCol = visibleColumns[0];
5056
+ const lastCol = visibleColumns[visibleColumns.length - 1];
5057
+ setSelectedCellRange({
5058
+ start: { rowId: getRowId(firstRow), columnId: firstCol.id },
5059
+ end: { rowId: getRowId(lastRow), columnId: lastCol.id }
5060
+ });
5061
+ setFocusedCell({ rowId: getRowId(firstRow), columnId: firstCol.id });
5062
+ }
5063
+ },
4058
5064
  hasFocusedCell: focusedCell !== null,
4059
5065
  isEditing: editingCell !== null,
4060
5066
  enabled: true
4061
5067
  });
4062
5068
  const effectiveCompactMode = spreadsheetSettings.compactView ?? false;
4063
5069
  const rowIndexHighlightColor = getColumnHighlight(ROW_INDEX_COLUMN_ID);
4064
- const tableRef = (0, import_react17.useRef)(null);
5070
+ const tableRef = (0, import_react23.useRef)(null);
4065
5071
  const effectiveTotalItems = serverSide ? totalItems ?? data.length : filteredData.length;
4066
5072
  const totalPages = Math.max(1, Math.ceil(effectiveTotalItems / pageSize));
4067
- (0, import_react17.useEffect)(() => {
5073
+ (0, import_react23.useEffect)(() => {
4068
5074
  if (!serverSide && currentPage > totalPages) {
4069
5075
  setInternalCurrentPage(1);
4070
5076
  }
4071
5077
  }, [totalPages, currentPage, serverSide]);
4072
- const afterFilteredRef = (0, import_react17.useRef)(afterFiltered);
5078
+ const afterFilteredRef = (0, import_react23.useRef)(afterFiltered);
4073
5079
  afterFilteredRef.current = afterFiltered;
4074
- (0, import_react17.useEffect)(() => {
5080
+ (0, import_react23.useEffect)(() => {
4075
5081
  afterFilteredRef.current?.(filteredData.toArray());
4076
5082
  }, [filteredData]);
4077
- const handleRowSelect = (0, import_react17.useCallback)(
5083
+ const handleRowSelect = (0, import_react23.useCallback)(
4078
5084
  (rowId, event) => {
4079
5085
  if (!enableRowSelection) return;
4080
5086
  event.stopPropagation();
@@ -4123,14 +5129,23 @@ function Spreadsheet({
4123
5129
  onSelectionChange
4124
5130
  ]
4125
5131
  );
4126
- const handleCellClick = (0, import_react17.useCallback)(
5132
+ const handleCellClick = (0, import_react23.useCallback)(
4127
5133
  (rowId, columnId, event) => {
4128
5134
  event.stopPropagation();
4129
5135
  handleCellMouseDown(rowId, columnId, event);
4130
5136
  },
4131
5137
  [handleCellMouseDown]
4132
5138
  );
4133
- const handleCellChange = (0, import_react17.useCallback)(
5139
+ const handleCellDoubleClick = (0, import_react23.useCallback)(
5140
+ (rowId, columnId) => {
5141
+ const column = columns.find((c) => c.id === columnId);
5142
+ if (column?.editable && enableCellEditing) {
5143
+ setEditingCell({ rowId, columnId });
5144
+ }
5145
+ },
5146
+ [columns, enableCellEditing, setEditingCell]
5147
+ );
5148
+ const handleCellChange = (0, import_react23.useCallback)(
4134
5149
  (rowId, columnId, newValue) => {
4135
5150
  const row = data.find((r) => getRowId(r) === rowId);
4136
5151
  const previousValue = row ? row[columnId] : void 0;
@@ -4153,7 +5168,7 @@ function Spreadsheet({
4153
5168
  },
4154
5169
  [data, getRowId, enableUndoRedo, onCellsEdit, pushToUndoStack, markAsChanged]
4155
5170
  );
4156
- const handleConfirmEdit = (0, import_react17.useCallback)(
5171
+ const handleConfirmEdit = (0, import_react23.useCallback)(
4157
5172
  (finalValue) => {
4158
5173
  if (editingCell && finalValue !== void 0) {
4159
5174
  handleCellChange(editingCell.rowId, editingCell.columnId, finalValue);
@@ -4162,105 +5177,51 @@ function Spreadsheet({
4162
5177
  },
4163
5178
  [editingCell, handleCellChange, setEditingCell]
4164
5179
  );
4165
- const handleCancelEdit = (0, import_react17.useCallback)(() => {
5180
+ const handleCancelEdit = (0, import_react23.useCallback)(() => {
4166
5181
  setEditingCell(null);
4167
5182
  }, [setEditingCell]);
4168
- const handleRowIndexHighlightClick = (0, import_react17.useCallback)(() => {
5183
+ const handleRowIndexHighlightClick = (0, import_react23.useCallback)(() => {
4169
5184
  setHighlightPickerColumn(ROW_INDEX_COLUMN_ID);
4170
5185
  }, [setHighlightPickerColumn]);
4171
- const columnRenderItems = (0, import_react17.useMemo)(() => {
5186
+ const columnRenderItems = (0, import_react23.useMemo)(() => {
4172
5187
  if (!columnGroups || columnGroups.length === 0) {
4173
5188
  return visibleColumns.map((col) => ({
4174
5189
  type: "column",
4175
5190
  column: col
4176
5191
  }));
4177
5192
  }
4178
- const leftPinnedItems = [];
4179
- const middleItems = [];
4180
- const rightPinnedItems = [];
5193
+ const items = [];
5194
+ const allGroupedIds = new Set(columnGroups.flatMap((g) => g.columns));
5195
+ for (const col of visibleColumns) {
5196
+ if (!allGroupedIds.has(col.id)) {
5197
+ items.push({ type: "column", column: col });
5198
+ }
5199
+ }
4181
5200
  for (const group of columnGroups) {
4182
5201
  const isCollapsed = collapsedGroups.has(group.id);
5202
+ const groupVisibleCols = visibleColumns.filter((c) => group.columns.includes(c.id));
4183
5203
  if (isCollapsed) {
4184
- middleItems.push({
5204
+ items.push({
4185
5205
  type: "collapsed-placeholder",
4186
5206
  groupId: group.id,
4187
5207
  headerColor: group.headerColor
4188
5208
  });
4189
5209
  }
4190
- const groupVisibleCols = (columns || []).filter((c) => {
4191
- if (!group.columns.includes(c.id)) return false;
4192
- if (isCollapsed) return pinnedColumns.has(c.id);
4193
- return true;
4194
- });
4195
5210
  for (const col of groupVisibleCols) {
4196
- const pinSide = pinnedColumns.get(col.id);
4197
- if (pinSide === "left") {
4198
- leftPinnedItems.push({ type: "column", column: col });
4199
- } else if (pinSide === "right") {
4200
- rightPinnedItems.push({ type: "column", column: col });
4201
- } else {
4202
- middleItems.push({ type: "column", column: col });
4203
- }
4204
- }
4205
- }
4206
- const allGroupedIds = new Set(columnGroups.flatMap((g) => g.columns));
4207
- for (const col of visibleColumns) {
4208
- if (!allGroupedIds.has(col.id)) {
4209
- const pinSide = pinnedColumns.get(col.id);
4210
- if (pinSide === "left") {
4211
- leftPinnedItems.push({ type: "column", column: col });
4212
- } else if (pinSide === "right") {
4213
- rightPinnedItems.push({ type: "column", column: col });
4214
- } else {
4215
- middleItems.push({ type: "column", column: col });
4216
- }
5211
+ items.push({ type: "column", column: col });
4217
5212
  }
4218
5213
  }
4219
- const pinnedLeftOrder = Array.from(pinnedColumns.entries()).filter(([id, side]) => side === "left" && id !== ROW_INDEX_COLUMN_ID).map(([id]) => id);
4220
- const pinnedRightOrder = Array.from(pinnedColumns.entries()).filter(([, side]) => side === "right").map(([id]) => id);
4221
- leftPinnedItems.sort(
4222
- (a, b) => pinnedLeftOrder.indexOf(a.column.id) - pinnedLeftOrder.indexOf(b.column.id)
4223
- );
4224
- rightPinnedItems.sort(
4225
- (a, b) => pinnedRightOrder.indexOf(a.column.id) - pinnedRightOrder.indexOf(b.column.id)
4226
- );
4227
- return [...leftPinnedItems, ...middleItems, ...rightPinnedItems];
4228
- }, [columnGroups, collapsedGroups, columns, pinnedColumns, visibleColumns]);
4229
- const groupHeaderItems = (0, import_react17.useMemo)(() => {
5214
+ return items;
5215
+ }, [columnGroups, collapsedGroups, visibleColumns]);
5216
+ const groupHeaderItems = (0, import_react23.useMemo)(() => {
4230
5217
  if (!columnGroups || columnGroups.length === 0) return null;
4231
- const leftPinned = [];
4232
- const groups = [];
4233
- const rightPinned = [];
5218
+ const items = [];
4234
5219
  for (const group of columnGroups) {
4235
5220
  const isCollapsed = collapsedGroups.has(group.id);
4236
- const groupColumns = (columns || []).filter((c) => group.columns.includes(c.id));
4237
- const visibleGroupColumns = isCollapsed ? groupColumns.filter((c) => pinnedColumns.has(c.id)) : groupColumns;
4238
- let movedLeftCount = 0;
4239
- let movedRightCount = 0;
4240
- for (const col of visibleGroupColumns) {
4241
- const pinSide = pinnedColumns.get(col.id);
4242
- if (pinSide === "left") {
4243
- movedLeftCount++;
4244
- leftPinned.push({
4245
- type: "pinned-column",
4246
- columnId: col.id,
4247
- headerColor: group.headerColor,
4248
- pinSide: "left"
4249
- });
4250
- } else if (pinSide === "right") {
4251
- movedRightCount++;
4252
- rightPinned.push({
4253
- type: "pinned-column",
4254
- columnId: col.id,
4255
- headerColor: group.headerColor,
4256
- pinSide: "right"
4257
- });
4258
- }
4259
- }
4260
- const remainingCols = visibleGroupColumns.length - movedLeftCount - movedRightCount;
4261
- const colSpan = remainingCols + (isCollapsed ? 1 : 0);
5221
+ const visibleCount = visibleColumns.filter((c) => group.columns.includes(c.id)).length;
5222
+ const colSpan = visibleCount + (isCollapsed ? 1 : 0);
4262
5223
  if (colSpan > 0) {
4263
- groups.push({
5224
+ items.push({
4264
5225
  type: "group",
4265
5226
  group,
4266
5227
  colSpan,
@@ -4268,18 +5229,10 @@ function Spreadsheet({
4268
5229
  });
4269
5230
  }
4270
5231
  }
4271
- const pinnedLeftOrder = Array.from(pinnedColumns.entries()).filter(([id, side]) => side === "left" && id !== ROW_INDEX_COLUMN_ID).map(([id]) => id);
4272
- const pinnedRightOrder = Array.from(pinnedColumns.entries()).filter(([, side]) => side === "right").map(([id]) => id);
4273
- leftPinned.sort(
4274
- (a, b) => pinnedLeftOrder.indexOf(a.columnId) - pinnedLeftOrder.indexOf(b.columnId)
4275
- );
4276
- rightPinned.sort(
4277
- (a, b) => pinnedRightOrder.indexOf(a.columnId) - pinnedRightOrder.indexOf(b.columnId)
4278
- );
4279
- return [...leftPinned, ...groups, ...rightPinned];
4280
- }, [columnGroups, collapsedGroups, columns, pinnedColumns]);
4281
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: cn("flex flex-col h-full bg-white", className), children: [
4282
- showToolbar && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5232
+ return items;
5233
+ }, [columnGroups, collapsedGroups, visibleColumns]);
5234
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: cn("flex flex-col h-full bg-white", className), children: [
5235
+ showToolbar && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
4283
5236
  SpreadsheetToolbar,
4284
5237
  {
4285
5238
  zoom,
@@ -4314,386 +5267,405 @@ function Spreadsheet({
4314
5267
  menuItems: toolbarMenuItems
4315
5268
  }
4316
5269
  ),
4317
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { ref: tableRef, className: "flex-1 overflow-auto border border-gray-200 rounded", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4318
- "div",
4319
- {
4320
- style: {
4321
- zoom: zoom / 100
4322
- },
4323
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("table", { className: "border-separate border-spacing-0 text-xs select-none", children: [
4324
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("thead", { children: [
4325
- columnGroups && groupHeaderItems && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("tr", { children: [
4326
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4327
- RowIndexColumnHeader,
5270
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { ref: tableRef, className: cn("flex-1 overflow-auto border border-gray-200 rounded spreadsheet-scroll-container relative", isResizing && "select-none"), onMouseUp: handleMouseUp, children: [
5271
+ isProcessing && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "spreadsheet-processing-overlay", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center gap-2 text-gray-500", children: [
5272
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "w-4 h-4 border-2 border-blue-600 border-t-transparent rounded-full animate-spin" }),
5273
+ "Processing..."
5274
+ ] }) }),
5275
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("table", { ref: measureRef, className: "border-separate border-spacing-0 text-sm select-none", style: zoom !== 100 ? { zoom: zoom / 100 } : void 0, children: [
5276
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("thead", { className: "sticky top-0", style: { zIndex: 50 }, children: [
5277
+ columnGroups && groupHeaderItems && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("tr", { children: [
5278
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5279
+ RowIndexColumnHeader,
5280
+ {
5281
+ highlightColor: rowIndexHighlightColor,
5282
+ isPinned: isRowIndexPinned,
5283
+ isSecondRow: true,
5284
+ compactMode: effectiveCompactMode
5285
+ }
5286
+ ),
5287
+ groupHeaderItems.map((item) => {
5288
+ const { group, colSpan, isCollapsed } = item;
5289
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5290
+ "th",
4328
5291
  {
4329
- highlightColor: rowIndexHighlightColor,
4330
- isPinned: isRowIndexPinned,
4331
- isSecondRow: true,
4332
- compactMode: effectiveCompactMode
4333
- }
4334
- ),
4335
- groupHeaderItems.map((item) => {
4336
- if (item.type === "pinned-column") {
4337
- const col = columns.find((c) => c.id === item.columnId);
4338
- const isPinnedLeft = item.pinSide === "left";
4339
- const pinnedWidth = Math.max(
4340
- col?.minWidth || col?.width || MIN_PINNED_COLUMN_WIDTH,
4341
- MIN_PINNED_COLUMN_WIDTH
4342
- );
4343
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4344
- "th",
4345
- {
4346
- className: cn(
4347
- "border border-gray-200 px-2 py-1.5 text-center font-bold text-gray-700",
4348
- "z-30"
4349
- ),
4350
- style: {
4351
- backgroundColor: item.headerColor || "rgb(243 244 246)",
4352
- position: "sticky",
4353
- left: isPinnedLeft ? `${getColumnLeftOffset(item.columnId)}px` : void 0,
4354
- right: !isPinnedLeft ? `${getColumnRightOffset(item.columnId)}px` : void 0,
4355
- minWidth: pinnedWidth
4356
- }
4357
- },
4358
- `pinned-group-${item.columnId}`
4359
- );
4360
- }
4361
- const { group, colSpan, isCollapsed } = item;
4362
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5292
+ colSpan,
5293
+ className: cn(
5294
+ "border border-gray-200 px-2 py-1.5 text-center font-bold text-gray-700",
5295
+ group.collapsible && "cursor-pointer hover:bg-gray-100"
5296
+ ),
5297
+ style: {
5298
+ backgroundColor: group.headerColor || "rgb(243 244 246)"
5299
+ },
5300
+ onClick: () => group.collapsible && handleToggleGroupCollapse(group.id),
5301
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center justify-center gap-1", children: [
5302
+ group.collapsible && (isCollapsed ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(HiChevronRight, { className: "h-3 w-3" }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(HiChevronDown, { className: "h-3 w-3" })),
5303
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { children: group.label })
5304
+ ] })
5305
+ },
5306
+ group.id
5307
+ );
5308
+ })
5309
+ ] }),
5310
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("tr", { children: [
5311
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5312
+ RowIndexColumnHeader,
5313
+ {
5314
+ enableHighlighting,
5315
+ highlightColor: rowIndexHighlightColor,
5316
+ isPinned: isRowIndexPinned,
5317
+ onHighlightClick: handleRowIndexHighlightClick,
5318
+ onPinClick: () => handleTogglePin(ROW_INDEX_COLUMN_ID),
5319
+ compactMode: effectiveCompactMode
5320
+ }
5321
+ ),
5322
+ columnRenderItems.map((item) => {
5323
+ if (item.type === "collapsed-placeholder") {
5324
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
4363
5325
  "th",
4364
5326
  {
4365
- colSpan,
4366
- className: cn(
4367
- "border border-gray-200 px-2 py-1.5 text-center font-bold text-gray-700",
4368
- group.collapsible && "cursor-pointer hover:bg-gray-100"
4369
- ),
5327
+ className: "border border-gray-200 px-2 py-1 text-center text-gray-400",
4370
5328
  style: {
4371
- backgroundColor: group.headerColor || "rgb(243 244 246)"
5329
+ backgroundColor: item.headerColor || "rgb(243 244 246)",
5330
+ minWidth: "30px"
4372
5331
  },
4373
- onClick: () => group.collapsible && handleToggleGroupCollapse(group.id),
4374
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center justify-center gap-1", children: [
4375
- group.collapsible && (isCollapsed ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(HiChevronRight, { className: "h-3 w-3" }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(HiChevronDown, { className: "h-3 w-3" })),
4376
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: group.label })
4377
- ] })
5332
+ children: "..."
4378
5333
  },
4379
- group.id
5334
+ `${item.groupId}-placeholder`
4380
5335
  );
4381
- })
4382
- ] }),
4383
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("tr", { children: [
4384
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4385
- RowIndexColumnHeader,
5336
+ }
5337
+ const column = item.column;
5338
+ const isPinnedLeft = isColumnPinned(column.id) && getColumnPinSide(column.id) === "left";
5339
+ const isPinnedRight = isColumnPinned(column.id) && getColumnPinSide(column.id) === "right";
5340
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5341
+ MemoizedSpreadsheetHeader,
4386
5342
  {
4387
- enableHighlighting,
4388
- highlightColor: rowIndexHighlightColor,
4389
- isPinned: isRowIndexPinned,
4390
- onHighlightClick: handleRowIndexHighlightClick,
4391
- onPinClick: () => handleTogglePin(ROW_INDEX_COLUMN_ID),
4392
- compactMode: effectiveCompactMode
4393
- }
4394
- ),
4395
- columnRenderItems.map((item) => {
4396
- if (item.type === "collapsed-placeholder") {
4397
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4398
- "th",
4399
- {
4400
- className: "border border-gray-200 px-2 py-1 text-center text-gray-400 sticky z-20",
4401
- style: {
4402
- backgroundColor: item.headerColor || "rgb(243 244 246)",
4403
- minWidth: "30px",
4404
- top: 0
4405
- },
4406
- children: "..."
4407
- },
4408
- `${item.groupId}-placeholder`
4409
- );
4410
- }
4411
- const column = item.column;
4412
- const isPinnedLeft = isColumnPinned(column.id) && getColumnPinSide(column.id) === "left";
4413
- const isPinnedRight = isColumnPinned(column.id) && getColumnPinSide(column.id) === "right";
4414
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4415
- SpreadsheetHeader,
4416
- {
4417
- column,
4418
- sortConfig,
4419
- hasActiveFilter: !!filters[column.id],
4420
- isPinned: isColumnPinned(column.id),
4421
- pinSide: getColumnPinSide(column.id),
4422
- leftOffset: isPinnedLeft ? getColumnLeftOffset(column.id) : 0,
4423
- rightOffset: isPinnedRight ? getColumnRightOffset(column.id) : 0,
4424
- highlightColor: getColumnHighlight(column.id),
4425
- compactMode: effectiveCompactMode,
4426
- onClick: () => handleSort(column.id),
4427
- onFilterClick: () => setActiveFilterColumn(
4428
- activeFilterColumn === column.id ? null : column.id
4429
- ),
4430
- onPinClick: () => handleTogglePin(column.id),
4431
- onHighlightClick: enableHighlighting ? () => setHighlightPickerColumn(column.id) : void 0,
4432
- children: activeFilterColumn === column.id && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4433
- SpreadsheetFilterDropdown,
4434
- {
4435
- column,
4436
- filter: filters[column.id],
4437
- onFilterChange: (filter) => handleFilterChangeWithReset(
4438
- column.id,
4439
- filter
4440
- ),
4441
- onClose: () => setActiveFilterColumn(null)
5343
+ column,
5344
+ sortConfig,
5345
+ hasActiveFilter: !!filters[column.id],
5346
+ isPinned: isColumnPinned(column.id),
5347
+ pinSide: getColumnPinSide(column.id),
5348
+ pinnedZIndex: isColumnPinned(column.id) ? getPinnedZIndex(column.id) : void 0,
5349
+ leftOffset: isPinnedLeft ? getColumnLeftOffset(column.id) : 0,
5350
+ rightOffset: isPinnedRight ? getColumnRightOffset(column.id) : 0,
5351
+ highlightColor: getColumnHighlight(column.id),
5352
+ compactMode: effectiveCompactMode,
5353
+ onClick: () => {
5354
+ if (paginatedData.length > 0) {
5355
+ const firstRowId = getRowId(paginatedData[0]);
5356
+ const lastRowId = getRowId(paginatedData[paginatedData.length - 1]);
5357
+ const isAlreadySelected = selectedCellRange?.start.columnId === column.id && selectedCellRange?.end.columnId === column.id && selectedCellRange?.start.rowId === firstRowId && selectedCellRange?.end.rowId === lastRowId;
5358
+ if (isAlreadySelected) {
5359
+ setSelectedCellRange(null);
5360
+ setFocusedCell(null);
5361
+ } else {
5362
+ setSelectedCellRange({
5363
+ start: { rowId: firstRowId, columnId: column.id },
5364
+ end: { rowId: lastRowId, columnId: column.id }
5365
+ });
5366
+ setFocusedCell({ rowId: firstRowId, columnId: column.id });
4442
5367
  }
4443
- )
5368
+ }
4444
5369
  },
4445
- column.id
4446
- );
4447
- })
4448
- ] })
4449
- ] }),
4450
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tbody", { children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4451
- "td",
4452
- {
4453
- colSpan: columnRenderItems.length + 1,
4454
- className: "text-center py-8 text-gray-500",
4455
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center justify-center gap-2", children: [
4456
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "w-4 h-4 border-2 border-blue-600 border-t-transparent rounded-full animate-spin" }),
4457
- "Loading..."
4458
- ] })
4459
- }
4460
- ) }) : paginatedData.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4461
- "td",
4462
- {
4463
- colSpan: columnRenderItems.length + 1,
4464
- className: "text-center py-8 text-gray-500",
4465
- children: emptyMessage
4466
- }
4467
- ) }) : paginatedData.map((row, rowIndex) => {
4468
- const rowId = getRowId(row);
4469
- const isRowSelected = selectedRows.has(rowId);
4470
- const isRowHovered = hoveredRow === rowId;
4471
- const rowHighlight = getRowHighlight(rowId);
4472
- const displayIndex = rowIndex + 1 + (currentPage - 1) * pageSize;
4473
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
4474
- "tr",
4475
- {
4476
- onMouseEnter: () => setHoveredRow(rowId),
4477
- onMouseLeave: () => setHoveredRow(null),
4478
- onClick: () => {
4479
- onRowClick?.(row, rowIndex);
4480
- },
4481
- onDoubleClick: () => onRowDoubleClick?.(row, rowIndex),
4482
- children: [
4483
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4484
- "td",
5370
+ onSortClick: () => handleSort(column.id),
5371
+ onFilterClick: () => setActiveFilterColumn(
5372
+ activeFilterColumn === column.id ? null : column.id
5373
+ ),
5374
+ onPinClick: () => handleTogglePin(column.id),
5375
+ onHighlightClick: enableHighlighting ? () => setHighlightPickerColumn(column.id) : void 0,
5376
+ resizeHandleProps: getResizeHandleProps(
5377
+ column.id,
5378
+ column.width || column.minWidth || 100
5379
+ ),
5380
+ resolvedWidth: getColumnWidth(column.id),
5381
+ hasDuplicateCheck: duplicateCheckColumns.has(column.id),
5382
+ onDuplicateCheckClick: () => handleDuplicateCheckToggle(column.id),
5383
+ duplicateCount: getDuplicateColumnCount(column.id),
5384
+ children: activeFilterColumn === column.id && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5385
+ SpreadsheetFilterDropdown,
4485
5386
  {
4486
- onClick: (e) => handleRowSelect(rowId, e),
4487
- className: cn(
4488
- "border border-gray-200 text-center font-semibold cursor-pointer group",
4489
- effectiveCompactMode ? "text-[10px] px-1 py-px" : "text-xs px-2 py-1",
4490
- isRowIndexPinned ? "z-20" : "z-0",
4491
- isRowSelected && "bg-blue-100",
4492
- !isRowSelected && rowHighlight && "",
4493
- isRowHovered && !isRowSelected && !rowHighlight && "bg-gray-50"
5387
+ column,
5388
+ filter: filters[column.id],
5389
+ onFilterChange: (filter) => handleFilterChangeWithReset(
5390
+ column.id,
5391
+ filter
4494
5392
  ),
4495
- style: {
4496
- backgroundColor: rowHighlight?.color || (isRowSelected ? "#dbeafe" : isRowHovered ? "#f9fafb" : rowIndexHighlightColor || "white"),
4497
- minWidth: `${ROW_INDEX_COLUMN_WIDTH}px`,
4498
- width: `${ROW_INDEX_COLUMN_WIDTH}px`,
4499
- ...isRowIndexPinned && {
4500
- position: "sticky",
4501
- left: 0
4502
- }
4503
- },
4504
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "relative flex items-center justify-center w-full h-full", children: [
4505
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: displayIndex }),
4506
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "absolute inset-0 flex items-center justify-between", children: [
4507
- rowContextMenuItems && rowContextMenuItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4508
- RowContextMenu,
4509
- {
4510
- row,
4511
- rowId,
4512
- items: rowContextMenuItems,
4513
- compactMode: effectiveCompactMode
4514
- }
4515
- ),
4516
- enableHighlighting && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4517
- "button",
4518
- {
4519
- type: "button",
4520
- onClick: (e) => {
4521
- e.stopPropagation();
4522
- setHighlightPickerRow(rowId);
4523
- },
4524
- className: "opacity-0 group-hover:opacity-100 transition-opacity p-0.5 bg-gray-100 hover:bg-gray-200 rounded",
4525
- title: "Highlight row",
4526
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4527
- AiFillHighlight,
4528
- {
4529
- className: cn(
4530
- "h-2.5 w-2.5",
4531
- rowHighlight ? "text-amber-500" : "text-gray-500"
4532
- )
4533
- }
4534
- )
4535
- }
4536
- ),
4537
- enableComments && (cellHasComments(
5393
+ onClose: () => setActiveFilterColumn(null),
5394
+ hasDuplicateCheck: duplicateCheckColumns.has(column.id)
5395
+ }
5396
+ )
5397
+ },
5398
+ column.id
5399
+ );
5400
+ })
5401
+ ] })
5402
+ ] }),
5403
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("tbody", { children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5404
+ "td",
5405
+ {
5406
+ colSpan: columnRenderItems.length + 1,
5407
+ className: "text-center py-8 text-gray-500",
5408
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center justify-center gap-2", children: [
5409
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "w-4 h-4 border-2 border-blue-600 border-t-transparent rounded-full animate-spin" }),
5410
+ "Loading..."
5411
+ ] })
5412
+ }
5413
+ ) }) : paginatedData.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5414
+ "td",
5415
+ {
5416
+ colSpan: columnRenderItems.length + 1,
5417
+ className: "text-center py-8 text-gray-500",
5418
+ children: emptyMessage
5419
+ }
5420
+ ) }) : paginatedData.map((row, rowIndex) => {
5421
+ const rowId = getRowId(row);
5422
+ const isRowSelected = selectedRows.has(rowId);
5423
+ const rowHighlight = getRowHighlight(rowId);
5424
+ const displayIndex = rowIndex + 1 + (currentPage - 1) * pageSize;
5425
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
5426
+ "tr",
5427
+ {
5428
+ onMouseEnter: () => {
5429
+ hoveredRowRef.current = rowId;
5430
+ },
5431
+ onMouseLeave: () => {
5432
+ hoveredRowRef.current = null;
5433
+ },
5434
+ onClick: () => {
5435
+ onRowClick?.(row, rowIndex);
5436
+ },
5437
+ onDoubleClick: () => onRowDoubleClick?.(row, rowIndex),
5438
+ children: [
5439
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5440
+ "td",
5441
+ {
5442
+ "data-column-id": "__row_index__",
5443
+ onClick: (e) => handleRowSelect(rowId, e),
5444
+ className: cn(
5445
+ "border border-gray-200 text-center font-semibold cursor-pointer group",
5446
+ effectiveCompactMode ? "text-xs px-1.5 py-0.5" : "text-sm px-2.5 py-1.5",
5447
+ "sticky",
5448
+ isRowSelected && "bg-blue-100",
5449
+ !isRowSelected && rowHighlight && ""
5450
+ ),
5451
+ style: {
5452
+ backgroundColor: rowHighlight?.color || (isRowSelected ? "#dbeafe" : rowIndexHighlightColor || (rowIndex % 2 !== 0 ? "#f9fafb" : "white")),
5453
+ minWidth: `${ROW_INDEX_COLUMN_WIDTH}px`,
5454
+ width: `${ROW_INDEX_COLUMN_WIDTH}px`,
5455
+ position: "sticky",
5456
+ left: 0,
5457
+ zIndex: 40
5458
+ },
5459
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "relative flex items-center w-full h-full", children: [
5460
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "pl-1", children: displayIndex }),
5461
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "absolute inset-y-0 right-0 flex items-center gap-0.5 pr-0.5", children: [
5462
+ rowContextMenuItems && rowContextMenuItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5463
+ RowContextMenu,
5464
+ {
5465
+ row,
4538
5466
  rowId,
4539
- ROW_INDEX_COLUMN_ID
4540
- ) ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
4541
- "button",
4542
- {
4543
- type: "button",
4544
- onClick: (e) => {
4545
- e.stopPropagation();
4546
- setViewCommentsCell({
4547
- rowId,
4548
- columnId: ROW_INDEX_COLUMN_ID
4549
- });
4550
- },
4551
- className: "p-0.5 bg-amber-100 hover:bg-amber-200 rounded transition-colors flex items-center gap-0.5",
4552
- title: `${getCellUnresolvedCommentCount(rowId, ROW_INDEX_COLUMN_ID)} comment(s) - click to view`,
4553
- children: [
4554
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(FaComment, { className: "h-2.5 w-2.5 text-amber-500" }),
4555
- getCellUnresolvedCommentCount(
4556
- rowId,
4557
- ROW_INDEX_COLUMN_ID
4558
- ) > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-[9px] font-medium text-amber-600", children: getCellUnresolvedCommentCount(
4559
- rowId,
4560
- ROW_INDEX_COLUMN_ID
4561
- ) > 99 ? "99+" : getCellUnresolvedCommentCount(
4562
- rowId,
4563
- ROW_INDEX_COLUMN_ID
4564
- ) })
4565
- ]
4566
- }
4567
- ) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5467
+ items: rowContextMenuItems,
5468
+ compactMode: effectiveCompactMode
5469
+ }
5470
+ ),
5471
+ enableHighlighting && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5472
+ "button",
5473
+ {
5474
+ type: "button",
5475
+ onClick: (e) => {
5476
+ e.stopPropagation();
5477
+ setHighlightPickerRow(rowId);
5478
+ },
5479
+ className: "opacity-0 group-hover:opacity-100 transition-opacity p-0.5 bg-gray-100 hover:bg-gray-200 rounded",
5480
+ title: "Highlight row",
5481
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5482
+ AiFillHighlight,
5483
+ {
5484
+ className: cn(
5485
+ "h-2.5 w-2.5",
5486
+ rowHighlight ? "text-amber-500" : "text-gray-500"
5487
+ )
5488
+ }
5489
+ )
5490
+ }
5491
+ ),
5492
+ enableComments && (cellHasComments(
5493
+ rowId,
5494
+ ROW_INDEX_COLUMN_ID
5495
+ ) ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
5496
+ "button",
5497
+ {
5498
+ type: "button",
5499
+ onClick: (e) => {
5500
+ e.stopPropagation();
5501
+ setViewCommentsCell({
5502
+ rowId,
5503
+ columnId: ROW_INDEX_COLUMN_ID
5504
+ });
5505
+ },
5506
+ className: "p-0.5 bg-amber-100 hover:bg-amber-200 rounded transition-colors flex items-center gap-0.5",
5507
+ title: `${getCellUnresolvedCommentCount(rowId, ROW_INDEX_COLUMN_ID)} comment(s) - click to view`,
5508
+ children: [
5509
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(FaComment, { className: "h-2.5 w-2.5 text-amber-500" }),
5510
+ getCellUnresolvedCommentCount(
5511
+ rowId,
5512
+ ROW_INDEX_COLUMN_ID
5513
+ ) > 0 && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-[9px] font-medium text-amber-600", children: getCellUnresolvedCommentCount(
5514
+ rowId,
5515
+ ROW_INDEX_COLUMN_ID
5516
+ ) > 99 ? "99+" : getCellUnresolvedCommentCount(
5517
+ rowId,
5518
+ ROW_INDEX_COLUMN_ID
5519
+ ) })
5520
+ ]
5521
+ }
5522
+ ) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5523
+ "button",
5524
+ {
5525
+ type: "button",
5526
+ onClick: (e) => {
5527
+ e.stopPropagation();
5528
+ setCommentModalCell({
5529
+ rowId,
5530
+ columnId: ROW_INDEX_COLUMN_ID
5531
+ });
5532
+ },
5533
+ className: "opacity-0 group-hover:opacity-100 transition-opacity p-0.5 bg-gray-100 hover:bg-gray-200 rounded",
5534
+ title: "Add comment",
5535
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(FaRegComment, { className: "h-2.5 w-2.5 text-gray-500" })
5536
+ }
5537
+ )),
5538
+ rowActions?.map((action) => {
5539
+ if (action.visible && !action.visible(row))
5540
+ return null;
5541
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
4568
5542
  "button",
4569
5543
  {
4570
5544
  type: "button",
4571
5545
  onClick: (e) => {
4572
5546
  e.stopPropagation();
4573
- setCommentModalCell({
4574
- rowId,
4575
- columnId: ROW_INDEX_COLUMN_ID
4576
- });
5547
+ action.onClick(row, rowId);
4577
5548
  },
4578
- className: "opacity-0 group-hover:opacity-100 transition-opacity p-0.5 bg-gray-100 hover:bg-gray-200 rounded",
4579
- title: "Add comment",
4580
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(FaRegComment, { className: "h-2.5 w-2.5 text-gray-500" })
4581
- }
4582
- )),
4583
- rowActions?.map((action) => {
4584
- if (action.visible && !action.visible(row))
4585
- return null;
4586
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4587
- "button",
4588
- {
4589
- type: "button",
4590
- onClick: (e) => {
4591
- e.stopPropagation();
4592
- action.onClick(row, rowId);
4593
- },
4594
- className: cn(
4595
- "opacity-0 group-hover:opacity-100 transition-opacity p-0.5 hover:bg-gray-200 rounded",
4596
- action.className
4597
- ),
4598
- title: action.tooltip,
4599
- children: action.icon
4600
- },
4601
- action.id
4602
- );
4603
- })
4604
- ] })
5549
+ className: cn(
5550
+ "opacity-0 group-hover:opacity-100 transition-opacity p-0.5 hover:bg-gray-200 rounded",
5551
+ action.className
5552
+ ),
5553
+ title: action.tooltip,
5554
+ children: action.icon
5555
+ },
5556
+ action.id
5557
+ );
5558
+ })
4605
5559
  ] })
4606
- }
4607
- ),
4608
- columnRenderItems.map((item) => {
4609
- if (item.type === "collapsed-placeholder") {
4610
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4611
- "td",
4612
- {
4613
- className: "border border-gray-200 px-2 py-1 text-center text-gray-300",
4614
- style: {
4615
- backgroundColor: item.headerColor || "rgb(243 244 246)"
4616
- }
4617
- },
4618
- `${item.groupId}-placeholder`
4619
- );
4620
- }
4621
- const column = item.column;
4622
- const value = column.getValue ? column.getValue(row) : row[column.id];
4623
- const isEditing = editingCell?.rowId === rowId && editingCell?.columnId === column.id;
4624
- const isFocused = focusedCell?.rowId === rowId && focusedCell?.columnId === column.id;
4625
- const isInSelection = isCellInSelection(
4626
- rowId,
4627
- column.id
5560
+ ] })
5561
+ }
5562
+ ),
5563
+ columnRenderItems.map((item) => {
5564
+ if (item.type === "collapsed-placeholder") {
5565
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5566
+ "td",
5567
+ {
5568
+ className: "border border-gray-200 px-2 py-1 text-center text-gray-300",
5569
+ style: {
5570
+ backgroundColor: item.headerColor || "rgb(243 244 246)"
5571
+ }
5572
+ },
5573
+ `${item.groupId}-placeholder`
4628
5574
  );
4629
- const selectionEdge = getCellSelectionEdge(
5575
+ }
5576
+ const column = item.column;
5577
+ const value = column.getValue ? column.getValue(row) : row[column.id];
5578
+ const isEditing = editingCell?.rowId === rowId && editingCell?.columnId === column.id;
5579
+ const isFocused = focusedCell?.rowId === rowId && focusedCell?.columnId === column.id;
5580
+ const isInSelection = isCellInSelection(
5581
+ rowId,
5582
+ column.id
5583
+ );
5584
+ const selectionEdge = getCellSelectionEdge(
5585
+ rowId,
5586
+ column.id
5587
+ );
5588
+ const cellOrRowOrColumnHighlight = getCellHighlight(rowId, column.id) || rowHighlight?.color || getColumnHighlight(column.id);
5589
+ const isColPinned = isColumnPinned(column.id);
5590
+ const colPinSide = getColumnPinSide(column.id);
5591
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5592
+ MemoizedSpreadsheetCell,
5593
+ {
5594
+ value,
5595
+ column,
5596
+ row,
5597
+ rowIndex,
4630
5598
  rowId,
4631
- column.id
4632
- );
4633
- const cellOrRowOrColumnHighlight = getCellHighlight(rowId, column.id) || rowHighlight?.color || getColumnHighlight(column.id);
4634
- const isColPinned = isColumnPinned(column.id);
4635
- const colPinSide = getColumnPinSide(column.id);
4636
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4637
- MemoizedSpreadsheetCell,
4638
- {
4639
- value,
4640
- column,
4641
- row,
4642
- rowIndex,
4643
- rowId,
4644
- isEditable: column.editable && enableCellEditing,
4645
- isEditing,
4646
- isFocused,
4647
- isInSelection,
4648
- selectionEdge,
4649
- isRowSelected,
4650
- isRowHovered,
4651
- highlightColor: cellOrRowOrColumnHighlight,
4652
- compactMode: effectiveCompactMode,
4653
- isPinned: isColPinned,
4654
- pinSide: colPinSide,
4655
- leftOffset: getColumnLeftOffset(column.id),
4656
- rightOffset: getColumnRightOffset(
4657
- column.id
4658
- ),
4659
- onClick: (e) => handleCellClick(rowId, column.id, e),
4660
- onConfirm: handleConfirmEdit,
4661
- onCancel: handleCancelEdit,
4662
- onHighlight: enableHighlighting ? () => {
4663
- setHighlightPickerCell({
4664
- rowId,
4665
- columnId: column.id
4666
- });
4667
- } : void 0,
4668
- hasComments: cellHasComments(
4669
- rowId,
4670
- column.id
4671
- ),
4672
- unresolvedCommentCount: getCellUnresolvedCommentCount(
4673
- rowId,
4674
- column.id
4675
- ),
4676
- onAddComment: enableComments ? () => setCommentModalCell({
4677
- rowId,
4678
- columnId: column.id
4679
- }) : void 0,
4680
- onViewComments: enableComments && cellHasComments(rowId, column.id) ? () => setViewCommentsCell({
5599
+ isEditable: column.editable && enableCellEditing,
5600
+ isEditing,
5601
+ isFocused,
5602
+ isInSelection,
5603
+ selectionEdge,
5604
+ isRowSelected,
5605
+ isRowHovered: false,
5606
+ highlightColor: cellOrRowOrColumnHighlight,
5607
+ isDuplicate: isCellDuplicate(rowId, column.id),
5608
+ compactMode: effectiveCompactMode,
5609
+ isOddRow: rowIndex % 2 !== 0,
5610
+ resolvedWidth: getColumnWidth(column.id),
5611
+ isPinned: isColPinned,
5612
+ pinSide: colPinSide,
5613
+ pinnedZIndex: isColPinned ? getPinnedZIndex(column.id) : void 0,
5614
+ leftOffset: getColumnLeftOffset(column.id),
5615
+ rightOffset: getColumnRightOffset(
5616
+ column.id
5617
+ ),
5618
+ onClick: (e) => handleCellClick(rowId, column.id, e),
5619
+ onDoubleClick: () => handleCellDoubleClick(rowId, column.id),
5620
+ onMouseEnter: () => handleCellMouseEnter(rowId, column.id),
5621
+ onConfirm: handleConfirmEdit,
5622
+ onCancel: handleCancelEdit,
5623
+ onHighlight: enableHighlighting ? () => {
5624
+ setHighlightPickerCell({
4681
5625
  rowId,
4682
5626
  columnId: column.id
4683
- }) : void 0
4684
- },
4685
- column.id
4686
- );
4687
- })
4688
- ]
4689
- },
4690
- rowId
4691
- );
4692
- }) })
4693
- ] })
5627
+ });
5628
+ } : void 0,
5629
+ hasComments: cellHasComments(
5630
+ rowId,
5631
+ column.id
5632
+ ),
5633
+ unresolvedCommentCount: getCellUnresolvedCommentCount(
5634
+ rowId,
5635
+ column.id
5636
+ ),
5637
+ onAddComment: enableComments ? () => setCommentModalCell({
5638
+ rowId,
5639
+ columnId: column.id
5640
+ }) : void 0,
5641
+ onViewComments: enableComments && cellHasComments(rowId, column.id) ? () => setViewCommentsCell({
5642
+ rowId,
5643
+ columnId: column.id
5644
+ }) : void 0
5645
+ },
5646
+ column.id
5647
+ );
5648
+ })
5649
+ ]
5650
+ },
5651
+ rowId
5652
+ );
5653
+ }) })
5654
+ ] })
5655
+ ] }),
5656
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
5657
+ SelectionSummaryBar,
5658
+ {
5659
+ summary: selectionSummary,
5660
+ focusedCell,
5661
+ columns: visibleColumns,
5662
+ data: paginatedData,
5663
+ getRowId,
5664
+ currentPage,
5665
+ pageSize
4694
5666
  }
4695
- ) }),
4696
- showPagination && effectiveTotalItems > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5667
+ ),
5668
+ showPagination && effectiveTotalItems > 0 && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
4697
5669
  import_design_system2.Pagination,
4698
5670
  {
4699
5671
  currentPage,
@@ -4708,7 +5680,7 @@ function Spreadsheet({
4708
5680
  onPageSizeChange: handlePageSizeChange
4709
5681
  }
4710
5682
  ),
4711
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5683
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
4712
5684
  AddCommentModal,
4713
5685
  {
4714
5686
  isOpen: commentModalCell !== null,
@@ -4719,7 +5691,7 @@ function Spreadsheet({
4719
5691
  }
4720
5692
  }
4721
5693
  ),
4722
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5694
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
4723
5695
  ViewCommentsModal,
4724
5696
  {
4725
5697
  isOpen: viewCommentsCell !== null,
@@ -4734,29 +5706,32 @@ function Spreadsheet({
4734
5706
  onClose: () => setViewCommentsCell(null)
4735
5707
  }
4736
5708
  ),
4737
- highlightPickerRow !== null && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5709
+ highlightPickerRow !== null && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
4738
5710
  ColorPickerPopover,
4739
5711
  {
4740
5712
  title: "Highlight Row",
4741
5713
  paletteType: "row",
5714
+ recentColors,
4742
5715
  onSelectColor: (color) => handleRowHighlightToggle(highlightPickerRow, color),
4743
5716
  onClose: () => setHighlightPickerRow(null)
4744
5717
  }
4745
5718
  ),
4746
- highlightPickerColumn !== null && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5719
+ highlightPickerColumn !== null && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
4747
5720
  ColorPickerPopover,
4748
5721
  {
4749
5722
  title: highlightPickerColumn === ROW_INDEX_COLUMN_ID ? "Highlight Row Index Column" : `Highlight Column: ${columns.find((c) => c.id === highlightPickerColumn)?.label || ""}`,
4750
5723
  paletteType: "column",
5724
+ recentColors,
4751
5725
  onSelectColor: (color) => handleColumnHighlightToggle(highlightPickerColumn, color),
4752
5726
  onClose: () => setHighlightPickerColumn(null)
4753
5727
  }
4754
5728
  ),
4755
- highlightPickerCell !== null && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5729
+ highlightPickerCell !== null && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
4756
5730
  ColorPickerPopover,
4757
5731
  {
4758
5732
  title: "Highlight Cell",
4759
5733
  paletteType: "row",
5734
+ recentColors,
4760
5735
  onSelectColor: (color) => handleCellHighlightToggle(
4761
5736
  highlightPickerCell.rowId,
4762
5737
  highlightPickerCell.columnId,
@@ -4765,7 +5740,7 @@ function Spreadsheet({
4765
5740
  onClose: () => setHighlightPickerCell(null)
4766
5741
  }
4767
5742
  ),
4768
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5743
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
4769
5744
  KeyboardShortcutsModal,
4770
5745
  {
4771
5746
  isOpen: showKeyboardShortcuts,
@@ -4773,7 +5748,7 @@ function Spreadsheet({
4773
5748
  shortcuts
4774
5749
  }
4775
5750
  ),
4776
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
5751
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
4777
5752
  SpreadsheetSettingsModal,
4778
5753
  {
4779
5754
  isOpen: showSettingsModal,
@@ -4802,12 +5777,14 @@ Spreadsheet.displayName = "Spreadsheet";
4802
5777
  // Annotate the CommonJS export names for ESM import in node:
4803
5778
  0 && (module.exports = {
4804
5779
  ActiveFiltersDisplay,
5780
+ MemoizedSpreadsheetHeader,
4805
5781
  RowContextMenu,
4806
5782
  Spreadsheet,
4807
5783
  SpreadsheetCell,
4808
5784
  SpreadsheetFilterDropdown,
4809
5785
  SpreadsheetHeader,
4810
5786
  SpreadsheetSettingsModal,
4811
- SpreadsheetToolbar
5787
+ SpreadsheetToolbar,
5788
+ useSpreadsheetDuplicates
4812
5789
  });
4813
5790
  //# sourceMappingURL=index.js.map