@xcelsior/ui-spreadsheets 1.0.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 (37) hide show
  1. package/.storybook/main.ts +27 -0
  2. package/.storybook/preview.tsx +28 -0
  3. package/.turbo/turbo-build.log +22 -0
  4. package/CHANGELOG.md +9 -0
  5. package/biome.json +3 -0
  6. package/dist/index.d.mts +687 -0
  7. package/dist/index.d.ts +687 -0
  8. package/dist/index.js +3459 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/index.mjs +3417 -0
  11. package/dist/index.mjs.map +1 -0
  12. package/package.json +51 -0
  13. package/postcss.config.js +5 -0
  14. package/src/components/ColorPickerPopover.tsx +73 -0
  15. package/src/components/ColumnHeaderActions.tsx +139 -0
  16. package/src/components/CommentModals.tsx +137 -0
  17. package/src/components/KeyboardShortcutsModal.tsx +119 -0
  18. package/src/components/RowIndexColumnHeader.tsx +70 -0
  19. package/src/components/Spreadsheet.stories.tsx +1146 -0
  20. package/src/components/Spreadsheet.tsx +1005 -0
  21. package/src/components/SpreadsheetCell.tsx +341 -0
  22. package/src/components/SpreadsheetFilterDropdown.tsx +341 -0
  23. package/src/components/SpreadsheetHeader.tsx +111 -0
  24. package/src/components/SpreadsheetSettingsModal.tsx +555 -0
  25. package/src/components/SpreadsheetToolbar.tsx +346 -0
  26. package/src/hooks/index.ts +40 -0
  27. package/src/hooks/useSpreadsheetComments.ts +132 -0
  28. package/src/hooks/useSpreadsheetFiltering.ts +379 -0
  29. package/src/hooks/useSpreadsheetHighlighting.ts +201 -0
  30. package/src/hooks/useSpreadsheetKeyboardShortcuts.ts +149 -0
  31. package/src/hooks/useSpreadsheetPinning.ts +203 -0
  32. package/src/hooks/useSpreadsheetUndoRedo.ts +167 -0
  33. package/src/index.ts +31 -0
  34. package/src/types.ts +612 -0
  35. package/src/utils.ts +16 -0
  36. package/tsconfig.json +30 -0
  37. package/tsup.config.ts +12 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,3417 @@
1
+ // src/components/Spreadsheet.tsx
2
+ import { useCallback as useCallback6, useEffect as useEffect5, useMemo as useMemo3, useRef as useRef3, useState as useState10 } from "react";
3
+
4
+ // ../../../node_modules/.pnpm/react-icons@4.12.0_react@18.3.1/node_modules/react-icons/lib/esm/iconBase.js
5
+ import React2 from "react";
6
+
7
+ // ../../../node_modules/.pnpm/react-icons@4.12.0_react@18.3.1/node_modules/react-icons/lib/esm/iconContext.js
8
+ import React from "react";
9
+ var DefaultContext = {
10
+ color: void 0,
11
+ size: void 0,
12
+ className: void 0,
13
+ style: void 0,
14
+ attr: void 0
15
+ };
16
+ var IconContext = React.createContext && React.createContext(DefaultContext);
17
+
18
+ // ../../../node_modules/.pnpm/react-icons@4.12.0_react@18.3.1/node_modules/react-icons/lib/esm/iconBase.js
19
+ var __assign = function() {
20
+ __assign = Object.assign || function(t) {
21
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
22
+ s = arguments[i];
23
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
24
+ }
25
+ return t;
26
+ };
27
+ return __assign.apply(this, arguments);
28
+ };
29
+ var __rest = function(s, e) {
30
+ var t = {};
31
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p];
32
+ if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
33
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]];
34
+ }
35
+ return t;
36
+ };
37
+ function Tree2Element(tree) {
38
+ return tree && tree.map(function(node, i) {
39
+ return React2.createElement(node.tag, __assign({
40
+ key: i
41
+ }, node.attr), Tree2Element(node.child));
42
+ });
43
+ }
44
+ function GenIcon(data) {
45
+ return function(props) {
46
+ return React2.createElement(IconBase, __assign({
47
+ attr: __assign({}, data.attr)
48
+ }, props), Tree2Element(data.child));
49
+ };
50
+ }
51
+ function IconBase(props) {
52
+ var elem = function(conf) {
53
+ var attr = props.attr, size = props.size, title = props.title, svgProps = __rest(props, ["attr", "size", "title"]);
54
+ var computedSize = size || conf.size || "1em";
55
+ var className;
56
+ if (conf.className) className = conf.className;
57
+ if (props.className) className = (className ? className + " " : "") + props.className;
58
+ return React2.createElement("svg", __assign({
59
+ stroke: "currentColor",
60
+ fill: "currentColor",
61
+ strokeWidth: "0"
62
+ }, conf.attr, attr, svgProps, {
63
+ className,
64
+ style: __assign(__assign({
65
+ color: props.color || conf.color
66
+ }, conf.style), props.style),
67
+ height: computedSize,
68
+ width: computedSize,
69
+ xmlns: "http://www.w3.org/2000/svg"
70
+ }), title && React2.createElement("title", null, title), props.children);
71
+ };
72
+ return IconContext !== void 0 ? React2.createElement(IconContext.Consumer, null, function(conf) {
73
+ return elem(conf);
74
+ }) : elem(DefaultContext);
75
+ }
76
+
77
+ // ../../../node_modules/.pnpm/react-icons@4.12.0_react@18.3.1/node_modules/react-icons/hi/index.esm.js
78
+ function HiAnnotation(props) {
79
+ return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 20 20", "fill": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "fillRule": "evenodd", "d": "M18 13V5a2 2 0 00-2-2H4a2 2 0 00-2 2v8a2 2 0 002 2h3l3 3 3-3h3a2 2 0 002-2zM5 7a1 1 0 011-1h8a1 1 0 110 2H6a1 1 0 01-1-1zm1 3a1 1 0 100 2h3a1 1 0 100-2H6z", "clipRule": "evenodd" } }] })(props);
80
+ }
81
+ function HiChatAlt2(props) {
82
+ return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 20 20", "fill": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "d": "M2 5a2 2 0 012-2h7a2 2 0 012 2v4a2 2 0 01-2 2H9l-3 3v-3H4a2 2 0 01-2-2V5z" } }, { "tag": "path", "attr": { "d": "M15 7v2a4 4 0 01-4 4H9.828l-1.766 1.767c.28.149.599.233.938.233h2l3 3v-3h2a2 2 0 002-2V9a2 2 0 00-2-2h-1z" } }] })(props);
83
+ }
84
+ function HiCheck(props) {
85
+ return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 20 20", "fill": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "fillRule": "evenodd", "d": "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", "clipRule": "evenodd" } }] })(props);
86
+ }
87
+ function HiChevronDown(props) {
88
+ return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 20 20", "fill": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "fillRule": "evenodd", "d": "M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z", "clipRule": "evenodd" } }] })(props);
89
+ }
90
+ function HiChevronRight(props) {
91
+ return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 20 20", "fill": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "fillRule": "evenodd", "d": "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", "clipRule": "evenodd" } }] })(props);
92
+ }
93
+ function HiChevronUp(props) {
94
+ return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 20 20", "fill": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "fillRule": "evenodd", "d": "M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z", "clipRule": "evenodd" } }] })(props);
95
+ }
96
+ function HiCog(props) {
97
+ return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 20 20", "fill": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "fillRule": "evenodd", "d": "M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z", "clipRule": "evenodd" } }] })(props);
98
+ }
99
+ function HiColorSwatch(props) {
100
+ return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 20 20", "fill": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "fillRule": "evenodd", "d": "M4 2a2 2 0 00-2 2v11a3 3 0 106 0V4a2 2 0 00-2-2H4zm1 14a1 1 0 100-2 1 1 0 000 2zm5-1.757l4.9-4.9a2 2 0 000-2.828L13.485 5.1a2 2 0 00-2.828 0L10 5.757v8.486zM16 18H9.071l6-6H16a2 2 0 012 2v2a2 2 0 01-2 2z", "clipRule": "evenodd" } }] })(props);
101
+ }
102
+ function HiDotsVertical(props) {
103
+ 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);
104
+ }
105
+ function HiDownload(props) {
106
+ return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 20 20", "fill": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "fillRule": "evenodd", "d": "M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", "clipRule": "evenodd" } }] })(props);
107
+ }
108
+ function HiDuplicate(props) {
109
+ return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 20 20", "fill": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "d": "M7 9a2 2 0 012-2h6a2 2 0 012 2v6a2 2 0 01-2 2H9a2 2 0 01-2-2V9z" } }, { "tag": "path", "attr": { "d": "M5 3a2 2 0 00-2 2v6a2 2 0 002 2V5h8a2 2 0 00-2-2H5z" } }] })(props);
110
+ }
111
+ function HiEye(props) {
112
+ 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);
113
+ }
114
+ function HiFilter(props) {
115
+ return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 20 20", "fill": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "fillRule": "evenodd", "d": "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z", "clipRule": "evenodd" } }] })(props);
116
+ }
117
+ function HiReply(props) {
118
+ return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 20 20", "fill": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "fillRule": "evenodd", "d": "M7.707 3.293a1 1 0 010 1.414L5.414 7H11a7 7 0 017 7v2a1 1 0 11-2 0v-2a5 5 0 00-5-5H5.414l2.293 2.293a1 1 0 11-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z", "clipRule": "evenodd" } }] })(props);
119
+ }
120
+ function HiSortAscending(props) {
121
+ 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);
122
+ }
123
+ function HiViewBoards(props) {
124
+ 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);
125
+ }
126
+ function HiX(props) {
127
+ return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 20 20", "fill": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "fillRule": "evenodd", "d": "M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z", "clipRule": "evenodd" } }] })(props);
128
+ }
129
+ function HiZoomIn(props) {
130
+ return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 20 20", "fill": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "d": "M5 8a1 1 0 011-1h1V6a1 1 0 012 0v1h1a1 1 0 110 2H9v1a1 1 0 11-2 0V9H6a1 1 0 01-1-1z" } }, { "tag": "path", "attr": { "fillRule": "evenodd", "d": "M2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8zm6-4a4 4 0 100 8 4 4 0 000-8z", "clipRule": "evenodd" } }] })(props);
131
+ }
132
+ function HiZoomOut(props) {
133
+ return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 20 20", "fill": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "fillRule": "evenodd", "d": "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z", "clipRule": "evenodd" } }, { "tag": "path", "attr": { "fillRule": "evenodd", "d": "M5 8a1 1 0 011-1h4a1 1 0 110 2H6a1 1 0 01-1-1z", "clipRule": "evenodd" } }] })(props);
134
+ }
135
+ function HiOutlineAnnotation(props) {
136
+ return GenIcon({ "tag": "svg", "attr": { "fill": "none", "viewBox": "0 0 24 24", "strokeWidth": "2", "stroke": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "strokeLinecap": "round", "strokeLinejoin": "round", "d": "M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z" } }] })(props);
137
+ }
138
+ function HiOutlineChatAlt(props) {
139
+ return GenIcon({ "tag": "svg", "attr": { "fill": "none", "viewBox": "0 0 24 24", "strokeWidth": "2", "stroke": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "strokeLinecap": "round", "strokeLinejoin": "round", "d": "M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" } }] })(props);
140
+ }
141
+ function HiOutlineClipboardCheck(props) {
142
+ return GenIcon({ "tag": "svg", "attr": { "fill": "none", "viewBox": "0 0 24 24", "strokeWidth": "2", "stroke": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "strokeLinecap": "round", "strokeLinejoin": "round", "d": "M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" } }] })(props);
143
+ }
144
+ function HiOutlineClipboardCopy(props) {
145
+ return GenIcon({ "tag": "svg", "attr": { "fill": "none", "viewBox": "0 0 24 24", "strokeWidth": "2", "stroke": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "strokeLinecap": "round", "strokeLinejoin": "round", "d": "M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" } }] })(props);
146
+ }
147
+ function HiOutlinePencil(props) {
148
+ return GenIcon({ "tag": "svg", "attr": { "fill": "none", "viewBox": "0 0 24 24", "strokeWidth": "2", "stroke": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "strokeLinecap": "round", "strokeLinejoin": "round", "d": "M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" } }] })(props);
149
+ }
150
+ function HiOutlineQuestionMarkCircle(props) {
151
+ return GenIcon({ "tag": "svg", "attr": { "fill": "none", "viewBox": "0 0 24 24", "strokeWidth": "2", "stroke": "currentColor", "aria-hidden": "true" }, "child": [{ "tag": "path", "attr": { "strokeLinecap": "round", "strokeLinejoin": "round", "d": "M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" } }] })(props);
152
+ }
153
+
154
+ // src/utils.ts
155
+ import { clsx } from "clsx";
156
+ import { twMerge } from "tailwind-merge";
157
+ var isBlankValue = (value) => {
158
+ return value === null || value === void 0 || value === "" || typeof value === "string" && value.trim() === "";
159
+ };
160
+ function cn(...inputs) {
161
+ return twMerge(clsx(inputs));
162
+ }
163
+
164
+ // src/components/SpreadsheetCell.tsx
165
+ import { useState, useRef, useEffect } from "react";
166
+ import { jsx, jsxs } from "react/jsx-runtime";
167
+ var cellPaddingCompact = "px-1.5 py-0.5";
168
+ var cellPaddingNormal = "px-2 py-1";
169
+ var SpreadsheetCell = ({
170
+ value,
171
+ column,
172
+ row,
173
+ rowIndex,
174
+ rowId: _rowId,
175
+ isEditable = false,
176
+ isEditing = false,
177
+ isFocused = false,
178
+ isRowSelected = false,
179
+ isRowHovered = false,
180
+ highlightColor,
181
+ hasComments = false,
182
+ unresolvedCommentCount = 0,
183
+ isCopied = false,
184
+ compactMode = false,
185
+ isPinned = false,
186
+ pinSide,
187
+ leftOffset = 0,
188
+ rightOffset = 0,
189
+ onClick,
190
+ onChange,
191
+ onConfirm,
192
+ onCancel,
193
+ onCopyDown,
194
+ onCopyToSelected,
195
+ onHighlight,
196
+ onAddComment,
197
+ onViewComments,
198
+ hasSelectedRows = false,
199
+ className
200
+ }) => {
201
+ const [localValue, setLocalValue] = useState(value);
202
+ const inputRef = useRef(null);
203
+ const selectRef = useRef(null);
204
+ useEffect(() => {
205
+ setLocalValue(value);
206
+ }, [value]);
207
+ useEffect(() => {
208
+ if (isEditing) {
209
+ if (column.type === "select") {
210
+ selectRef.current?.focus();
211
+ } else {
212
+ inputRef.current?.focus();
213
+ inputRef.current?.select();
214
+ }
215
+ }
216
+ }, [isEditing, column.type]);
217
+ const handleKeyDown = (e) => {
218
+ if (e.key === "Enter") {
219
+ e.preventDefault();
220
+ onConfirm?.();
221
+ } else if (e.key === "Escape") {
222
+ e.preventDefault();
223
+ setLocalValue(value);
224
+ onChange?.(value);
225
+ onCancel?.();
226
+ }
227
+ };
228
+ const getBackgroundColor = () => {
229
+ if (highlightColor) return highlightColor;
230
+ if (isRowSelected) return "rgb(219 234 254)";
231
+ if (isRowHovered) return "rgb(243 244 246)";
232
+ return "white";
233
+ };
234
+ const renderContent = () => {
235
+ if (column.render) {
236
+ return column.render(value, row, rowIndex);
237
+ }
238
+ if (value === null || value === void 0 || value === "") {
239
+ return /* @__PURE__ */ jsx("span", { className: "text-gray-400", children: "-" });
240
+ }
241
+ if (column.type === "boolean") {
242
+ return value ? "Yes" : "No";
243
+ }
244
+ if (column.type === "number") {
245
+ return typeof value === "number" ? value.toLocaleString() : value;
246
+ }
247
+ return String(value);
248
+ };
249
+ const renderEditInput = () => {
250
+ if (column.type === "select" && column.options) {
251
+ return /* @__PURE__ */ jsx(
252
+ "select",
253
+ {
254
+ ref: selectRef,
255
+ value: localValue ?? "",
256
+ onChange: (e) => {
257
+ setLocalValue(e.target.value);
258
+ onChange?.(e.target.value);
259
+ onConfirm?.();
260
+ },
261
+ onKeyDown: handleKeyDown,
262
+ onBlur: () => onConfirm?.(),
263
+ className: cn(
264
+ "w-full border border-gray-300 rounded text-xs focus:outline-none focus:ring-1 focus:ring-blue-500",
265
+ compactMode ? "px-1 py-0.5" : "px-2 py-1"
266
+ ),
267
+ children: column.options.map((option) => /* @__PURE__ */ jsx("option", { value: option, children: option }, option))
268
+ }
269
+ );
270
+ }
271
+ return /* @__PURE__ */ jsx(
272
+ "input",
273
+ {
274
+ ref: inputRef,
275
+ type: column.type === "number" ? "number" : "text",
276
+ step: column.type === "number" ? "0.01" : void 0,
277
+ value: localValue ?? "",
278
+ onChange: (e) => {
279
+ const newValue = column.type === "number" ? e.target.value === "" ? "" : parseFloat(e.target.value) : e.target.value;
280
+ setLocalValue(newValue);
281
+ onChange?.(newValue);
282
+ },
283
+ onKeyDown: handleKeyDown,
284
+ onBlur: () => onConfirm?.(),
285
+ className: cn(
286
+ "w-full border border-gray-300 rounded text-xs focus:outline-none focus:ring-1 focus:ring-blue-500 bg-yellow-50",
287
+ compactMode ? "px-1 py-0.5" : "px-2 py-1"
288
+ )
289
+ }
290
+ );
291
+ };
292
+ const cellPadding = compactMode ? cellPaddingCompact : cellPaddingNormal;
293
+ const handleCellKeyDown = (e) => {
294
+ if (e.key === "Enter" || e.key === " ") {
295
+ e.preventDefault();
296
+ onClick?.(e);
297
+ }
298
+ };
299
+ const positionStyles = {};
300
+ if (isPinned) {
301
+ if (pinSide === "left") {
302
+ positionStyles.left = `${leftOffset}px`;
303
+ positionStyles.position = "sticky";
304
+ } else if (pinSide === "right") {
305
+ positionStyles.right = `${rightOffset}px`;
306
+ positionStyles.position = "sticky";
307
+ }
308
+ }
309
+ return /* @__PURE__ */ jsx(
310
+ "td",
311
+ {
312
+ onClick,
313
+ onKeyDown: handleCellKeyDown,
314
+ className: cn(
315
+ "border border-gray-200 text-xs group cursor-pointer transition-colors",
316
+ cellPadding,
317
+ column.align === "right" && "text-right",
318
+ column.align === "center" && "text-center",
319
+ isCopied && "animate-pulse",
320
+ isFocused && "ring-2 ring-blue-500 ring-inset",
321
+ isPinned ? "z-20" : "z-0",
322
+ className
323
+ ),
324
+ style: {
325
+ backgroundColor: getBackgroundColor(),
326
+ minWidth: column.minWidth || column.width,
327
+ ...positionStyles
328
+ },
329
+ children: isEditing ? renderEditInput() : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
330
+ /* @__PURE__ */ jsx(
331
+ "div",
332
+ {
333
+ className: cn(
334
+ "flex-1 truncate",
335
+ isEditable && "cursor-text hover:bg-gray-50 px-0.5 rounded min-h-[18px] flex items-center bg-yellow-50/50"
336
+ ),
337
+ title: String(value ?? ""),
338
+ children: renderContent()
339
+ }
340
+ ),
341
+ hasComments && /* @__PURE__ */ jsxs(
342
+ "button",
343
+ {
344
+ type: "button",
345
+ onClick: (e) => {
346
+ e.stopPropagation();
347
+ onViewComments?.();
348
+ },
349
+ className: "p-0.5 hover:bg-gray-100 rounded relative shrink-0",
350
+ title: `${unresolvedCommentCount} unresolved comment(s)`,
351
+ children: [
352
+ /* @__PURE__ */ jsx(
353
+ HiOutlineChatAlt,
354
+ {
355
+ className: cn(
356
+ "h-3 w-3",
357
+ unresolvedCommentCount > 0 ? "text-amber-500" : "text-gray-400"
358
+ )
359
+ }
360
+ ),
361
+ unresolvedCommentCount > 0 && /* @__PURE__ */ jsx("span", { className: "absolute -top-1 -right-1 bg-amber-500 text-white text-[8px] rounded-full w-3 h-3 flex items-center justify-center", children: unresolvedCommentCount })
362
+ ]
363
+ }
364
+ ),
365
+ /* @__PURE__ */ jsxs("div", { className: "opacity-0 group-hover:opacity-100 flex items-center gap-0.5 transition-opacity shrink-0", children: [
366
+ value !== null && value !== void 0 && value !== "" && onCopyDown && /* @__PURE__ */ jsx(
367
+ "button",
368
+ {
369
+ type: "button",
370
+ onClick: (e) => {
371
+ e.stopPropagation();
372
+ onCopyDown();
373
+ },
374
+ className: "p-0.5 bg-gray-100 hover:bg-gray-200 rounded",
375
+ title: "Copy value down to rows below",
376
+ children: /* @__PURE__ */ jsx(HiOutlineClipboardCopy, { className: "h-2.5 w-2.5 text-gray-500" })
377
+ }
378
+ ),
379
+ hasSelectedRows && value !== null && value !== void 0 && value !== "" && onCopyToSelected && /* @__PURE__ */ jsx(
380
+ "button",
381
+ {
382
+ type: "button",
383
+ onClick: (e) => {
384
+ e.stopPropagation();
385
+ onCopyToSelected();
386
+ },
387
+ className: "p-0.5 bg-green-100 hover:bg-green-200 rounded",
388
+ title: "Copy to selected rows",
389
+ children: /* @__PURE__ */ jsx(HiOutlineClipboardCheck, { className: "h-2.5 w-2.5 text-green-600" })
390
+ }
391
+ ),
392
+ onHighlight && /* @__PURE__ */ jsx(
393
+ "button",
394
+ {
395
+ type: "button",
396
+ onClick: (e) => {
397
+ e.stopPropagation();
398
+ onHighlight();
399
+ },
400
+ className: "p-0.5 hover:bg-gray-100 rounded",
401
+ title: "Highlight cell",
402
+ children: /* @__PURE__ */ jsx(
403
+ HiOutlinePencil,
404
+ {
405
+ className: cn(
406
+ "h-2.5 w-2.5",
407
+ highlightColor ? "text-amber-500" : "text-gray-400"
408
+ )
409
+ }
410
+ )
411
+ }
412
+ ),
413
+ onAddComment && /* @__PURE__ */ jsx(
414
+ "button",
415
+ {
416
+ type: "button",
417
+ onClick: (e) => {
418
+ e.stopPropagation();
419
+ onAddComment();
420
+ },
421
+ className: "p-0.5 hover:bg-gray-100 rounded",
422
+ title: "Add comment",
423
+ children: /* @__PURE__ */ jsx(HiOutlineAnnotation, { className: "h-2.5 w-2.5 text-gray-400" })
424
+ }
425
+ )
426
+ ] })
427
+ ] })
428
+ }
429
+ );
430
+ };
431
+ SpreadsheetCell.displayName = "SpreadsheetCell";
432
+
433
+ // src/components/SpreadsheetFilterDropdown.tsx
434
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
435
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
436
+ var TEXT_OPERATORS = [
437
+ { value: "contains", label: "Contains" },
438
+ { value: "notContains", label: "Does not contain" },
439
+ { value: "equals", label: "Equals" },
440
+ { value: "notEquals", label: "Does not equal" },
441
+ { value: "startsWith", label: "Starts with" },
442
+ { value: "endsWith", label: "Ends with" },
443
+ { value: "isEmpty", label: "Is empty" },
444
+ { value: "isNotEmpty", label: "Is not empty" }
445
+ ];
446
+ var NUMBER_OPERATORS = [
447
+ { value: "equals", label: "Equals" },
448
+ { value: "notEquals", label: "Does not equal" },
449
+ { value: "greaterThan", label: "Greater than" },
450
+ { value: "greaterThanOrEqual", label: "Greater than or equal" },
451
+ { value: "lessThan", label: "Less than" },
452
+ { value: "lessThanOrEqual", label: "Less than or equal" },
453
+ { value: "between", label: "Between" },
454
+ { value: "isEmpty", label: "Is empty" },
455
+ { value: "isNotEmpty", label: "Is not empty" }
456
+ ];
457
+ var DATE_OPERATORS = [
458
+ { value: "equals", label: "Equals" },
459
+ { value: "notEquals", label: "Does not equal" },
460
+ { value: "before", label: "Before" },
461
+ { value: "after", label: "After" },
462
+ { value: "between", label: "Between" },
463
+ { value: "today", label: "Today" },
464
+ { value: "yesterday", label: "Yesterday" },
465
+ { value: "thisWeek", label: "This week" },
466
+ { value: "lastWeek", label: "Last week" },
467
+ { value: "thisMonth", label: "This month" },
468
+ { value: "lastMonth", label: "Last month" },
469
+ { value: "thisYear", label: "This year" },
470
+ { value: "isEmpty", label: "Is empty" },
471
+ { value: "isNotEmpty", label: "Is not empty" }
472
+ ];
473
+ var SpreadsheetFilterDropdown = ({
474
+ column,
475
+ filter,
476
+ onFilterChange,
477
+ onClose,
478
+ className
479
+ }) => {
480
+ const [textOperator, setTextOperator] = useState2(
481
+ filter?.textCondition?.operator || "contains"
482
+ );
483
+ const [textValue, setTextValue] = useState2(filter?.textCondition?.value || "");
484
+ const [numberOperator, setNumberOperator] = useState2(
485
+ filter?.numberCondition?.operator || "equals"
486
+ );
487
+ const [numberValue, setNumberValue] = useState2(
488
+ filter?.numberCondition?.value?.toString() || ""
489
+ );
490
+ const [numberValueTo, setNumberValueTo] = useState2(
491
+ filter?.numberCondition?.valueTo?.toString() || ""
492
+ );
493
+ const [dateOperator, setDateOperator] = useState2(
494
+ filter?.dateCondition?.operator || "equals"
495
+ );
496
+ const [dateValue, setDateValue] = useState2(filter?.dateCondition?.value || "");
497
+ const [dateValueTo, setDateValueTo] = useState2(filter?.dateCondition?.valueTo || "");
498
+ const dropdownRef = useRef2(null);
499
+ const isNumeric = column.type === "number";
500
+ const isDate = column.type === "date";
501
+ useEffect2(() => {
502
+ const handleClickOutside = (event) => {
503
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
504
+ onClose();
505
+ }
506
+ };
507
+ document.addEventListener("mousedown", handleClickOutside);
508
+ return () => document.removeEventListener("mousedown", handleClickOutside);
509
+ }, [onClose]);
510
+ const handleApplyFilter = () => {
511
+ let newFilter;
512
+ if (isNumeric) {
513
+ const needsValue = !["isEmpty", "isNotEmpty"].includes(numberOperator);
514
+ if (needsValue && !numberValue) {
515
+ onFilterChange(void 0);
516
+ onClose();
517
+ return;
518
+ }
519
+ newFilter = {
520
+ numberCondition: {
521
+ operator: numberOperator,
522
+ value: numberValue ? parseFloat(numberValue) : void 0,
523
+ valueTo: numberValueTo ? parseFloat(numberValueTo) : void 0
524
+ }
525
+ };
526
+ } else if (isDate) {
527
+ const needsValue = ![
528
+ "isEmpty",
529
+ "isNotEmpty",
530
+ "today",
531
+ "yesterday",
532
+ "thisWeek",
533
+ "lastWeek",
534
+ "thisMonth",
535
+ "lastMonth",
536
+ "thisYear"
537
+ ].includes(dateOperator);
538
+ if (needsValue && !dateValue) {
539
+ onFilterChange(void 0);
540
+ onClose();
541
+ return;
542
+ }
543
+ newFilter = {
544
+ dateCondition: {
545
+ operator: dateOperator,
546
+ value: dateValue || void 0,
547
+ valueTo: dateValueTo || void 0
548
+ }
549
+ };
550
+ } else {
551
+ const needsValue = !["isEmpty", "isNotEmpty"].includes(textOperator);
552
+ if (needsValue && !textValue) {
553
+ onFilterChange(void 0);
554
+ onClose();
555
+ return;
556
+ }
557
+ newFilter = {
558
+ textCondition: {
559
+ operator: textOperator,
560
+ value: textValue || void 0
561
+ }
562
+ };
563
+ }
564
+ onFilterChange(newFilter);
565
+ onClose();
566
+ };
567
+ const handleClearFilter = () => {
568
+ setTextValue("");
569
+ setNumberValue("");
570
+ setNumberValueTo("");
571
+ setDateValue("");
572
+ setDateValueTo("");
573
+ onFilterChange(void 0);
574
+ onClose();
575
+ };
576
+ const textNeedsValue = !["isEmpty", "isNotEmpty"].includes(textOperator);
577
+ const numberNeedsValue = !["isEmpty", "isNotEmpty"].includes(numberOperator);
578
+ const dateNeedsValue = ![
579
+ "isEmpty",
580
+ "isNotEmpty",
581
+ "today",
582
+ "yesterday",
583
+ "thisWeek",
584
+ "lastWeek",
585
+ "thisMonth",
586
+ "lastMonth",
587
+ "thisYear"
588
+ ].includes(dateOperator);
589
+ return /* @__PURE__ */ jsxs2(
590
+ "div",
591
+ {
592
+ ref: dropdownRef,
593
+ className: cn(
594
+ "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]",
595
+ className
596
+ ),
597
+ onClick: (e) => e.stopPropagation(),
598
+ children: [
599
+ /* @__PURE__ */ jsx2("div", { className: "px-3 py-2 border-b border-gray-200 bg-gray-50", children: /* @__PURE__ */ jsxs2("span", { className: "text-xs font-medium text-gray-700", children: [
600
+ "Filter: ",
601
+ column.label
602
+ ] }) }),
603
+ /* @__PURE__ */ jsx2("div", { className: "p-3 space-y-3", children: isNumeric ? /* @__PURE__ */ jsxs2(Fragment, { children: [
604
+ /* @__PURE__ */ jsxs2("div", { children: [
605
+ /* @__PURE__ */ jsx2("label", { className: "text-xs text-gray-500 mb-1 block", children: "Condition" }),
606
+ /* @__PURE__ */ jsx2(
607
+ "select",
608
+ {
609
+ value: numberOperator,
610
+ onChange: (e) => setNumberOperator(e.target.value),
611
+ 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",
612
+ children: NUMBER_OPERATORS.map((op) => /* @__PURE__ */ jsx2("option", { value: op.value, children: op.label }, op.value))
613
+ }
614
+ )
615
+ ] }),
616
+ numberNeedsValue && /* @__PURE__ */ jsxs2("div", { children: [
617
+ /* @__PURE__ */ jsx2("label", { className: "text-xs text-gray-500 mb-1 block", children: "Value" }),
618
+ /* @__PURE__ */ jsx2(
619
+ "input",
620
+ {
621
+ type: "number",
622
+ placeholder: "Enter value",
623
+ value: numberValue,
624
+ onChange: (e) => setNumberValue(e.target.value),
625
+ 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"
626
+ }
627
+ )
628
+ ] }),
629
+ numberOperator === "between" && /* @__PURE__ */ jsxs2("div", { children: [
630
+ /* @__PURE__ */ jsx2("label", { className: "text-xs text-gray-500 mb-1 block", children: "And" }),
631
+ /* @__PURE__ */ jsx2(
632
+ "input",
633
+ {
634
+ type: "number",
635
+ placeholder: "Enter end value",
636
+ value: numberValueTo,
637
+ onChange: (e) => setNumberValueTo(e.target.value),
638
+ 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"
639
+ }
640
+ )
641
+ ] })
642
+ ] }) : isDate ? /* @__PURE__ */ jsxs2(Fragment, { children: [
643
+ /* @__PURE__ */ jsxs2("div", { children: [
644
+ /* @__PURE__ */ jsx2("label", { className: "text-xs text-gray-500 mb-1 block", children: "Condition" }),
645
+ /* @__PURE__ */ jsx2(
646
+ "select",
647
+ {
648
+ value: dateOperator,
649
+ onChange: (e) => setDateOperator(e.target.value),
650
+ 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",
651
+ children: DATE_OPERATORS.map((op) => /* @__PURE__ */ jsx2("option", { value: op.value, children: op.label }, op.value))
652
+ }
653
+ )
654
+ ] }),
655
+ dateNeedsValue && /* @__PURE__ */ jsxs2("div", { children: [
656
+ /* @__PURE__ */ jsx2("label", { className: "text-xs text-gray-500 mb-1 block", children: "Date" }),
657
+ /* @__PURE__ */ jsx2(
658
+ "input",
659
+ {
660
+ type: "date",
661
+ value: dateValue,
662
+ onChange: (e) => setDateValue(e.target.value),
663
+ 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"
664
+ }
665
+ )
666
+ ] }),
667
+ dateOperator === "between" && /* @__PURE__ */ jsxs2("div", { children: [
668
+ /* @__PURE__ */ jsx2("label", { className: "text-xs text-gray-500 mb-1 block", children: "And" }),
669
+ /* @__PURE__ */ jsx2(
670
+ "input",
671
+ {
672
+ type: "date",
673
+ value: dateValueTo,
674
+ onChange: (e) => setDateValueTo(e.target.value),
675
+ 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"
676
+ }
677
+ )
678
+ ] })
679
+ ] }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
680
+ /* @__PURE__ */ jsxs2("div", { children: [
681
+ /* @__PURE__ */ jsx2("label", { className: "text-xs text-gray-500 mb-1 block", children: "Condition" }),
682
+ /* @__PURE__ */ jsx2(
683
+ "select",
684
+ {
685
+ value: textOperator,
686
+ onChange: (e) => setTextOperator(e.target.value),
687
+ 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",
688
+ children: TEXT_OPERATORS.map((op) => /* @__PURE__ */ jsx2("option", { value: op.value, children: op.label }, op.value))
689
+ }
690
+ )
691
+ ] }),
692
+ textNeedsValue && /* @__PURE__ */ jsxs2("div", { children: [
693
+ /* @__PURE__ */ jsx2("label", { className: "text-xs text-gray-500 mb-1 block", children: "Value" }),
694
+ /* @__PURE__ */ jsx2(
695
+ "input",
696
+ {
697
+ type: "text",
698
+ placeholder: "Enter text",
699
+ value: textValue,
700
+ onChange: (e) => setTextValue(e.target.value),
701
+ 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"
702
+ }
703
+ )
704
+ ] })
705
+ ] }) }),
706
+ /* @__PURE__ */ jsxs2("div", { className: "p-2 border-t border-gray-200 flex gap-2", children: [
707
+ /* @__PURE__ */ jsxs2(
708
+ "button",
709
+ {
710
+ type: "button",
711
+ onClick: handleClearFilter,
712
+ className: "flex-1 px-3 py-1.5 text-xs text-red-600 hover:bg-red-50 border border-red-200 rounded transition-colors flex items-center justify-center gap-1",
713
+ children: [
714
+ /* @__PURE__ */ jsx2(HiX, { className: "h-3 w-3" }),
715
+ "Clear"
716
+ ]
717
+ }
718
+ ),
719
+ /* @__PURE__ */ jsxs2(
720
+ "button",
721
+ {
722
+ type: "button",
723
+ onClick: handleApplyFilter,
724
+ className: "flex-1 px-3 py-1.5 text-xs bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors flex items-center justify-center gap-1",
725
+ children: [
726
+ /* @__PURE__ */ jsx2(HiCheck, { className: "h-3 w-3" }),
727
+ "Apply"
728
+ ]
729
+ }
730
+ )
731
+ ] })
732
+ ]
733
+ }
734
+ );
735
+ };
736
+ SpreadsheetFilterDropdown.displayName = "SpreadsheetFilterDropdown";
737
+
738
+ // src/components/SpreadsheetToolbar.tsx
739
+ import React3 from "react";
740
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
741
+ var SpreadsheetToolbar = ({
742
+ zoom,
743
+ canUndo,
744
+ canRedo,
745
+ undoCount = 0,
746
+ redoCount = 0,
747
+ selectedRowCount,
748
+ hasUnsavedChanges,
749
+ saveStatus,
750
+ autoSave,
751
+ summary,
752
+ onZoomIn,
753
+ onZoomOut,
754
+ onZoomReset,
755
+ onUndo,
756
+ onRedo,
757
+ onClearSelection,
758
+ onSave,
759
+ onExport,
760
+ onSettings,
761
+ onShowShortcuts,
762
+ hasActiveFilters,
763
+ onClearFilters,
764
+ className
765
+ }) => {
766
+ const [showMoreMenu, setShowMoreMenu] = React3.useState(false);
767
+ const menuRef = React3.useRef(null);
768
+ React3.useEffect(() => {
769
+ const handleClickOutside = (event) => {
770
+ if (menuRef.current && !menuRef.current.contains(event.target)) {
771
+ setShowMoreMenu(false);
772
+ }
773
+ };
774
+ document.addEventListener("mousedown", handleClickOutside);
775
+ return () => document.removeEventListener("mousedown", handleClickOutside);
776
+ }, []);
777
+ const buttonBaseClasses = "p-1.5 rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed";
778
+ const getSaveStatusDisplay = () => {
779
+ switch (saveStatus) {
780
+ case "saved":
781
+ return {
782
+ text: "\u2713 All changes saved",
783
+ className: "text-gray-500"
784
+ };
785
+ case "saving":
786
+ return {
787
+ text: "\u27F3 Saving...",
788
+ className: "text-amber-500"
789
+ };
790
+ case "unsaved":
791
+ return {
792
+ text: "\u25CF Unsaved changes",
793
+ className: "text-amber-500"
794
+ };
795
+ case "error":
796
+ return {
797
+ text: "\u26A0 Error saving",
798
+ className: "text-red-500"
799
+ };
800
+ default:
801
+ return null;
802
+ }
803
+ };
804
+ const saveStatusDisplay = getSaveStatusDisplay();
805
+ const getSummaryVariantClasses = (variant) => {
806
+ switch (variant) {
807
+ case "success":
808
+ return "bg-green-50 border-green-200 text-green-700";
809
+ case "danger":
810
+ return "bg-red-50 border-red-200 text-red-700";
811
+ case "warning":
812
+ return "bg-amber-50 border-amber-200 text-amber-700";
813
+ default:
814
+ return "bg-blue-50 border-blue-200 text-blue-700";
815
+ }
816
+ };
817
+ return /* @__PURE__ */ jsxs3(
818
+ "div",
819
+ {
820
+ className: cn(
821
+ "flex flex-wrap items-center justify-between gap-2 px-4 py-2 border-b border-gray-200 bg-white",
822
+ className
823
+ ),
824
+ children: [
825
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
826
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1", children: [
827
+ /* @__PURE__ */ jsx3(
828
+ "button",
829
+ {
830
+ type: "button",
831
+ onClick: onUndo,
832
+ disabled: !canUndo,
833
+ className: cn(
834
+ buttonBaseClasses,
835
+ canUndo ? "bg-gray-100 text-gray-700 hover:bg-gray-200" : "bg-gray-50 text-gray-400"
836
+ ),
837
+ title: `Undo (${undoCount} changes)`,
838
+ children: /* @__PURE__ */ jsx3(HiReply, { className: "h-4 w-4" })
839
+ }
840
+ ),
841
+ /* @__PURE__ */ jsx3(
842
+ "button",
843
+ {
844
+ type: "button",
845
+ onClick: onRedo,
846
+ disabled: !canRedo,
847
+ className: cn(
848
+ buttonBaseClasses,
849
+ canRedo ? "bg-gray-100 text-gray-700 hover:bg-gray-200" : "bg-gray-50 text-gray-400"
850
+ ),
851
+ title: `Redo (${redoCount} changes)`,
852
+ style: { transform: "scaleX(-1)" },
853
+ children: /* @__PURE__ */ jsx3(HiReply, { className: "h-4 w-4" })
854
+ }
855
+ )
856
+ ] }),
857
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1 px-1.5 py-1 bg-gray-100 rounded", children: [
858
+ /* @__PURE__ */ jsx3(
859
+ "button",
860
+ {
861
+ type: "button",
862
+ onClick: onZoomOut,
863
+ className: "p-1 hover:bg-white rounded",
864
+ title: "Zoom out",
865
+ children: /* @__PURE__ */ jsx3(HiZoomOut, { className: "h-4 w-4 text-gray-600" })
866
+ }
867
+ ),
868
+ /* @__PURE__ */ jsxs3(
869
+ "button",
870
+ {
871
+ type: "button",
872
+ onClick: onZoomReset,
873
+ className: "px-2 py-0.5 hover:bg-white rounded text-xs min-w-[45px] text-center text-gray-600",
874
+ title: "Reset zoom",
875
+ children: [
876
+ zoom,
877
+ "%"
878
+ ]
879
+ }
880
+ ),
881
+ /* @__PURE__ */ jsx3(
882
+ "button",
883
+ {
884
+ type: "button",
885
+ onClick: onZoomIn,
886
+ className: "p-1 hover:bg-white rounded",
887
+ title: "Zoom in",
888
+ children: /* @__PURE__ */ jsx3(HiZoomIn, { className: "h-4 w-4 text-gray-600" })
889
+ }
890
+ )
891
+ ] })
892
+ ] }),
893
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2 flex-1 min-w-0", children: [
894
+ selectedRowCount > 0 && /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2 px-2.5 py-1.5 bg-blue-600 text-white rounded", children: [
895
+ /* @__PURE__ */ jsxs3("span", { className: "text-xs font-medium whitespace-nowrap", children: [
896
+ selectedRowCount,
897
+ " row",
898
+ selectedRowCount !== 1 ? "s" : "",
899
+ " selected"
900
+ ] }),
901
+ /* @__PURE__ */ jsx3(
902
+ "button",
903
+ {
904
+ type: "button",
905
+ onClick: onClearSelection,
906
+ className: "p-0.5 hover:bg-blue-700 rounded",
907
+ title: "Clear selection",
908
+ children: /* @__PURE__ */ jsx3(HiX, { className: "h-3 w-3" })
909
+ }
910
+ )
911
+ ] }),
912
+ hasActiveFilters && onClearFilters && /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2 px-2.5 py-1.5 bg-amber-500 text-white rounded", children: [
913
+ /* @__PURE__ */ jsx3(HiFilter, { className: "h-3.5 w-3.5" }),
914
+ /* @__PURE__ */ jsx3("span", { className: "text-xs font-medium whitespace-nowrap", children: "Filters active" }),
915
+ /* @__PURE__ */ jsx3(
916
+ "button",
917
+ {
918
+ type: "button",
919
+ onClick: onClearFilters,
920
+ className: "p-0.5 hover:bg-amber-600 rounded",
921
+ title: "Clear all filters",
922
+ children: /* @__PURE__ */ jsx3(HiX, { className: "h-3 w-3" })
923
+ }
924
+ )
925
+ ] }),
926
+ summary && /* @__PURE__ */ jsxs3(
927
+ "div",
928
+ {
929
+ className: cn(
930
+ "flex items-center gap-2 px-2.5 py-1.5 rounded border text-xs",
931
+ getSummaryVariantClasses(summary.variant)
932
+ ),
933
+ children: [
934
+ /* @__PURE__ */ jsxs3("span", { className: "font-semibold whitespace-nowrap", children: [
935
+ summary.label,
936
+ ":"
937
+ ] }),
938
+ /* @__PURE__ */ jsx3("span", { className: "font-bold whitespace-nowrap", children: summary.value })
939
+ ]
940
+ }
941
+ )
942
+ ] }),
943
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
944
+ saveStatusDisplay && /* @__PURE__ */ jsx3(
945
+ "span",
946
+ {
947
+ className: cn(
948
+ "text-xs flex items-center gap-1",
949
+ saveStatusDisplay.className
950
+ ),
951
+ children: saveStatusDisplay.text
952
+ }
953
+ ),
954
+ !autoSave && onSave && /* @__PURE__ */ jsxs3(
955
+ "button",
956
+ {
957
+ type: "button",
958
+ onClick: onSave,
959
+ disabled: !hasUnsavedChanges,
960
+ className: cn(
961
+ "px-3 py-1.5 text-xs bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors flex items-center gap-1.5",
962
+ "disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-blue-600"
963
+ ),
964
+ children: [
965
+ /* @__PURE__ */ jsx3(HiCheck, { className: "h-3.5 w-3.5" }),
966
+ "Save"
967
+ ]
968
+ }
969
+ ),
970
+ /* @__PURE__ */ jsxs3("div", { className: "relative", ref: menuRef, children: [
971
+ /* @__PURE__ */ jsxs3(
972
+ "button",
973
+ {
974
+ type: "button",
975
+ onClick: () => setShowMoreMenu(!showMoreMenu),
976
+ className: "px-2.5 py-1.5 bg-gray-100 text-gray-700 rounded hover:bg-gray-200 transition-colors flex items-center gap-1.5 text-xs",
977
+ title: "More actions",
978
+ children: [
979
+ /* @__PURE__ */ jsx3(HiDotsVertical, { className: "h-3.5 w-3.5" }),
980
+ /* @__PURE__ */ jsx3("span", { className: "hidden lg:inline", children: "More" })
981
+ ]
982
+ }
983
+ ),
984
+ showMoreMenu && /* @__PURE__ */ jsxs3("div", { className: "absolute right-0 top-full mt-1 bg-white border border-gray-200 shadow-lg rounded py-1 min-w-[180px] z-20", children: [
985
+ onSettings && /* @__PURE__ */ jsxs3(
986
+ "button",
987
+ {
988
+ type: "button",
989
+ onClick: () => {
990
+ onSettings();
991
+ setShowMoreMenu(false);
992
+ },
993
+ className: "w-full px-3 py-2 text-left hover:bg-gray-50 flex items-center gap-2 text-xs transition-colors",
994
+ children: [
995
+ /* @__PURE__ */ jsx3(HiCog, { className: "h-3.5 w-3.5 text-gray-500" }),
996
+ /* @__PURE__ */ jsx3("span", { className: "text-gray-700", children: "Settings" })
997
+ ]
998
+ }
999
+ ),
1000
+ onShowShortcuts && /* @__PURE__ */ jsxs3(
1001
+ "button",
1002
+ {
1003
+ type: "button",
1004
+ onClick: () => {
1005
+ onShowShortcuts();
1006
+ setShowMoreMenu(false);
1007
+ },
1008
+ className: "w-full px-3 py-2 text-left hover:bg-gray-50 flex items-center gap-2 text-xs transition-colors",
1009
+ children: [
1010
+ /* @__PURE__ */ jsx3(HiOutlineQuestionMarkCircle, { className: "h-3.5 w-3.5 text-gray-500" }),
1011
+ /* @__PURE__ */ jsx3("span", { className: "text-gray-700", children: "Keyboard Shortcuts" })
1012
+ ]
1013
+ }
1014
+ ),
1015
+ (onSettings || onShowShortcuts) && onExport && /* @__PURE__ */ jsx3("div", { className: "border-t border-gray-100 my-1" }),
1016
+ onExport && /* @__PURE__ */ jsxs3(
1017
+ "button",
1018
+ {
1019
+ type: "button",
1020
+ onClick: () => {
1021
+ onExport();
1022
+ setShowMoreMenu(false);
1023
+ },
1024
+ className: "w-full px-3 py-2 text-left hover:bg-gray-50 flex items-center gap-2 text-xs transition-colors",
1025
+ children: [
1026
+ /* @__PURE__ */ jsx3(HiDownload, { className: "h-3.5 w-3.5 text-gray-500" }),
1027
+ /* @__PURE__ */ jsx3("span", { className: "text-gray-700", children: "Export" })
1028
+ ]
1029
+ }
1030
+ )
1031
+ ] })
1032
+ ] })
1033
+ ] })
1034
+ ]
1035
+ }
1036
+ );
1037
+ };
1038
+ SpreadsheetToolbar.displayName = "SpreadsheetToolbar";
1039
+
1040
+ // ../../../node_modules/.pnpm/react-icons@4.12.0_react@18.3.1/node_modules/react-icons/md/index.esm.js
1041
+ function MdPushPin(props) {
1042
+ 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);
1043
+ }
1044
+ function MdOutlinePushPin(props) {
1045
+ return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 24 24" }, "child": [{ "tag": "path", "attr": { "fill": "none", "d": "M0 0h24v24H0z" } }, { "tag": "path", "attr": { "d": "M14 4v5c0 1.12.37 2.16 1 3H9c.65-.86 1-1.9 1-3V4h4m3-2H7c-.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-3V4h1c.55 0 1-.45 1-1s-.45-1-1-1z" } }] })(props);
1046
+ }
1047
+
1048
+ // src/components/ColumnHeaderActions.tsx
1049
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1050
+ function ColumnHeaderActions({
1051
+ enableFiltering = false,
1052
+ enableHighlighting = false,
1053
+ enablePinning = true,
1054
+ hasActiveFilter = false,
1055
+ hasActiveHighlight = false,
1056
+ isPinned = false,
1057
+ onFilterClick,
1058
+ onHighlightClick,
1059
+ onPinClick,
1060
+ filterTitle = "Filter column",
1061
+ highlightTitle = "Highlight column",
1062
+ pinnedTitle = "Unpin column",
1063
+ unpinnedTitle = "Pin column",
1064
+ className
1065
+ }) {
1066
+ const handleClick = (e) => {
1067
+ e.stopPropagation();
1068
+ };
1069
+ const handleKeyDown = (e) => {
1070
+ e.stopPropagation();
1071
+ };
1072
+ return /* @__PURE__ */ jsxs4(
1073
+ "button",
1074
+ {
1075
+ type: "button",
1076
+ className: cn("flex items-center gap-0.5", className),
1077
+ onClick: handleClick,
1078
+ onKeyDown: handleKeyDown,
1079
+ children: [
1080
+ enableFiltering && onFilterClick && /* @__PURE__ */ jsx4(
1081
+ "button",
1082
+ {
1083
+ type: "button",
1084
+ onClick: (e) => {
1085
+ e.stopPropagation();
1086
+ onFilterClick();
1087
+ },
1088
+ className: cn(
1089
+ "p-0.5 hover:bg-gray-200 rounded transition-opacity",
1090
+ hasActiveFilter ? "text-blue-600 opacity-100" : "text-gray-400 opacity-0 group-hover:opacity-100"
1091
+ ),
1092
+ title: filterTitle,
1093
+ children: /* @__PURE__ */ jsx4(HiFilter, { className: "h-3 w-3" })
1094
+ }
1095
+ ),
1096
+ enableHighlighting && onHighlightClick && /* @__PURE__ */ jsx4(
1097
+ "button",
1098
+ {
1099
+ type: "button",
1100
+ onClick: (e) => {
1101
+ e.stopPropagation();
1102
+ onHighlightClick();
1103
+ },
1104
+ className: cn(
1105
+ "p-0.5 hover:bg-gray-200 rounded transition-opacity",
1106
+ hasActiveHighlight ? "text-amber-500 opacity-100" : "text-gray-400 opacity-0 group-hover:opacity-100"
1107
+ ),
1108
+ title: highlightTitle,
1109
+ children: /* @__PURE__ */ jsx4(HiColorSwatch, { className: "h-3 w-3" })
1110
+ }
1111
+ ),
1112
+ enablePinning && onPinClick && /* @__PURE__ */ jsx4(
1113
+ "button",
1114
+ {
1115
+ type: "button",
1116
+ onClick: (e) => {
1117
+ e.stopPropagation();
1118
+ onPinClick();
1119
+ },
1120
+ className: cn(
1121
+ "p-0.5 hover:bg-gray-200 rounded transition-opacity",
1122
+ isPinned ? "text-blue-600 opacity-100" : "text-gray-400 opacity-0 group-hover:opacity-100"
1123
+ ),
1124
+ title: isPinned ? pinnedTitle : unpinnedTitle,
1125
+ children: isPinned ? /* @__PURE__ */ jsx4(MdPushPin, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx4(MdOutlinePushPin, { className: "h-3 w-3" })
1126
+ }
1127
+ )
1128
+ ]
1129
+ }
1130
+ );
1131
+ }
1132
+ ColumnHeaderActions.displayName = "ColumnHeaderActions";
1133
+
1134
+ // src/components/SpreadsheetHeader.tsx
1135
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1136
+ var cellPaddingCompact2 = "px-1.5 py-1";
1137
+ var cellPaddingNormal2 = "px-2 py-1.5";
1138
+ var SpreadsheetHeader = ({
1139
+ column,
1140
+ sortConfig,
1141
+ hasActiveFilter = false,
1142
+ isPinned = false,
1143
+ pinSide,
1144
+ leftOffset = 0,
1145
+ rightOffset = 0,
1146
+ highlightColor,
1147
+ compactMode = false,
1148
+ onClick,
1149
+ onFilterClick,
1150
+ onPinClick,
1151
+ onHighlightClick,
1152
+ className,
1153
+ children
1154
+ }) => {
1155
+ const isSorted = sortConfig?.columnId === column.id;
1156
+ const sortDirection = isSorted ? sortConfig.direction : null;
1157
+ const cellPadding = compactMode ? cellPaddingCompact2 : cellPaddingNormal2;
1158
+ const positionStyles = {};
1159
+ if (isPinned) {
1160
+ positionStyles.position = "sticky";
1161
+ if (pinSide === "left") {
1162
+ positionStyles.left = `${leftOffset}px`;
1163
+ } else if (pinSide === "right") {
1164
+ positionStyles.right = `${rightOffset}px`;
1165
+ }
1166
+ }
1167
+ return /* @__PURE__ */ jsxs5(
1168
+ "th",
1169
+ {
1170
+ onClick: column.sortable ? onClick : void 0,
1171
+ className: cn(
1172
+ "border border-gray-200 text-xs font-semibold text-gray-700 sticky group",
1173
+ cellPadding,
1174
+ column.align === "right" && "text-right",
1175
+ column.align === "center" && "text-center",
1176
+ column.sortable && "cursor-pointer hover:bg-gray-100",
1177
+ isPinned ? "z-30" : "z-20",
1178
+ className
1179
+ ),
1180
+ style: {
1181
+ backgroundColor: highlightColor || "rgb(243 244 246)",
1182
+ // gray-100
1183
+ minWidth: column.minWidth || column.width,
1184
+ top: 0,
1185
+ // For sticky header
1186
+ ...positionStyles
1187
+ },
1188
+ children: [
1189
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between gap-1", children: [
1190
+ /* @__PURE__ */ jsxs5("span", { className: "flex-1 flex items-center gap-1", children: [
1191
+ column.label,
1192
+ isSorted && /* @__PURE__ */ jsx5("span", { className: "text-blue-600", children: sortDirection === "asc" ? /* @__PURE__ */ jsx5(HiChevronUp, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx5(HiChevronDown, { className: "h-3 w-3" }) })
1193
+ ] }),
1194
+ /* @__PURE__ */ jsx5(
1195
+ ColumnHeaderActions,
1196
+ {
1197
+ enableFiltering: column.filterable,
1198
+ enableHighlighting: !!onHighlightClick,
1199
+ enablePinning: column.pinnable !== false,
1200
+ hasActiveFilter,
1201
+ hasActiveHighlight: !!highlightColor,
1202
+ isPinned,
1203
+ onFilterClick,
1204
+ onHighlightClick,
1205
+ onPinClick
1206
+ }
1207
+ )
1208
+ ] }),
1209
+ children
1210
+ ]
1211
+ }
1212
+ );
1213
+ };
1214
+ SpreadsheetHeader.displayName = "SpreadsheetHeader";
1215
+
1216
+ // src/hooks/useSpreadsheetPinning.ts
1217
+ import { useCallback, useMemo, useState as useState3 } from "react";
1218
+ var ROW_INDEX_COLUMN_WIDTH = 56;
1219
+ function useSpreadsheetPinning({
1220
+ columns,
1221
+ columnGroups,
1222
+ showRowIndex = true,
1223
+ defaultPinnedColumns = [],
1224
+ defaultPinRowIndex = false
1225
+ }) {
1226
+ const [pinnedColumns, setPinnedColumns] = useState3(() => {
1227
+ const map = /* @__PURE__ */ new Map();
1228
+ defaultPinnedColumns.forEach((col) => {
1229
+ map.set(col, "left");
1230
+ });
1231
+ return map;
1232
+ });
1233
+ const [isRowIndexPinned, setIsRowIndexPinned] = useState3(defaultPinRowIndex);
1234
+ const [collapsedGroups, setCollapsedGroups] = useState3(/* @__PURE__ */ new Set());
1235
+ const handleTogglePin = useCallback((columnId) => {
1236
+ setPinnedColumns((prev) => {
1237
+ const newMap = new Map(prev);
1238
+ if (newMap.has(columnId)) {
1239
+ newMap.delete(columnId);
1240
+ } else {
1241
+ newMap.set(columnId, "left");
1242
+ }
1243
+ return newMap;
1244
+ });
1245
+ }, []);
1246
+ const handleToggleRowIndexPin = useCallback(() => {
1247
+ setIsRowIndexPinned((prev) => !prev);
1248
+ }, []);
1249
+ const handleToggleGroupCollapse = useCallback((groupId) => {
1250
+ setCollapsedGroups((prev) => {
1251
+ const newSet = new Set(prev);
1252
+ if (newSet.has(groupId)) {
1253
+ newSet.delete(groupId);
1254
+ } else {
1255
+ newSet.add(groupId);
1256
+ }
1257
+ return newSet;
1258
+ });
1259
+ }, []);
1260
+ const visibleColumns = useMemo(() => {
1261
+ if (!columns || !Array.isArray(columns) || columns.length === 0) {
1262
+ return [];
1263
+ }
1264
+ let result = [...columns];
1265
+ if (columnGroups && Array.isArray(columnGroups)) {
1266
+ result = result.filter((column) => {
1267
+ const group = columnGroups.find((g) => g.columns.includes(column.id));
1268
+ if (!group) return true;
1269
+ if (!collapsedGroups.has(group.id)) return true;
1270
+ return pinnedColumns.has(column.id);
1271
+ });
1272
+ }
1273
+ if (pinnedColumns.size === 0) {
1274
+ return result;
1275
+ }
1276
+ const leftPinned = [];
1277
+ const unpinned = [];
1278
+ const rightPinned = [];
1279
+ const pinnedLeftIds = Array.from(pinnedColumns.entries()).filter(([, side]) => side === "left").map(([id]) => id);
1280
+ const pinnedRightIds = Array.from(pinnedColumns.entries()).filter(([, side]) => side === "right").map(([id]) => id);
1281
+ for (const column of result) {
1282
+ const pinSide = pinnedColumns.get(column.id);
1283
+ if (pinSide === "left") {
1284
+ leftPinned.push(column);
1285
+ } else if (pinSide === "right") {
1286
+ rightPinned.push(column);
1287
+ } else {
1288
+ unpinned.push(column);
1289
+ }
1290
+ }
1291
+ leftPinned.sort((a, b) => pinnedLeftIds.indexOf(a.id) - pinnedLeftIds.indexOf(b.id));
1292
+ rightPinned.sort((a, b) => pinnedRightIds.indexOf(a.id) - pinnedRightIds.indexOf(b.id));
1293
+ return [...leftPinned, ...unpinned, ...rightPinned];
1294
+ }, [columns, columnGroups, collapsedGroups, pinnedColumns]);
1295
+ const getRowIndexLeftOffset = useCallback(() => {
1296
+ return 0;
1297
+ }, []);
1298
+ const getColumnLeftOffset = useCallback(
1299
+ (columnId) => {
1300
+ const pinnedLeft = Array.from(pinnedColumns.entries()).filter(([, side]) => side === "left").map(([id]) => id);
1301
+ const index = pinnedLeft.indexOf(columnId);
1302
+ const baseOffset = showRowIndex && isRowIndexPinned ? ROW_INDEX_COLUMN_WIDTH : 0;
1303
+ if (index === -1) return baseOffset;
1304
+ let offset = baseOffset;
1305
+ for (let i = 0; i < index; i++) {
1306
+ const col = columns.find((c) => c.id === pinnedLeft[i]);
1307
+ offset += col?.width || col?.minWidth || 100;
1308
+ }
1309
+ return offset;
1310
+ },
1311
+ [pinnedColumns, columns, showRowIndex, isRowIndexPinned]
1312
+ );
1313
+ const isColumnPinned = useCallback(
1314
+ (columnId) => {
1315
+ return pinnedColumns.has(columnId);
1316
+ },
1317
+ [pinnedColumns]
1318
+ );
1319
+ const getColumnPinSide = useCallback(
1320
+ (columnId) => {
1321
+ return pinnedColumns.get(columnId);
1322
+ },
1323
+ [pinnedColumns]
1324
+ );
1325
+ return {
1326
+ pinnedColumns,
1327
+ isRowIndexPinned,
1328
+ collapsedGroups,
1329
+ visibleColumns,
1330
+ handleTogglePin,
1331
+ handleToggleRowIndexPin,
1332
+ handleToggleGroupCollapse,
1333
+ getColumnLeftOffset,
1334
+ getRowIndexLeftOffset,
1335
+ isColumnPinned,
1336
+ getColumnPinSide
1337
+ };
1338
+ }
1339
+
1340
+ // src/components/RowIndexColumnHeader.tsx
1341
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1342
+ function RowIndexColumnHeader({
1343
+ enableHighlighting = false,
1344
+ highlightColor,
1345
+ isPinned = false,
1346
+ onHighlightClick,
1347
+ onPinClick,
1348
+ hasColumnGroups = false,
1349
+ className
1350
+ }) {
1351
+ return /* @__PURE__ */ jsx6(
1352
+ "th",
1353
+ {
1354
+ className: cn(
1355
+ "border border-gray-200 px-2 py-1.5 text-center font-bold text-gray-700 group",
1356
+ isPinned ? "z-30" : "z-20",
1357
+ className
1358
+ ),
1359
+ rowSpan: hasColumnGroups ? 2 : void 0,
1360
+ style: {
1361
+ minWidth: `${ROW_INDEX_COLUMN_WIDTH}px`,
1362
+ width: `${ROW_INDEX_COLUMN_WIDTH}px`,
1363
+ position: "sticky",
1364
+ top: 0,
1365
+ left: isPinned ? 0 : void 0,
1366
+ backgroundColor: highlightColor || "rgb(243 244 246)"
1367
+ },
1368
+ children: /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-center gap-1", children: [
1369
+ /* @__PURE__ */ jsx6("span", { children: "#" }),
1370
+ /* @__PURE__ */ jsx6(
1371
+ ColumnHeaderActions,
1372
+ {
1373
+ enableHighlighting,
1374
+ enablePinning: true,
1375
+ hasActiveHighlight: !!highlightColor,
1376
+ isPinned,
1377
+ onHighlightClick,
1378
+ onPinClick,
1379
+ highlightTitle: "Highlight row index column",
1380
+ pinnedTitle: "Unpin row index column",
1381
+ unpinnedTitle: "Pin row index column"
1382
+ }
1383
+ )
1384
+ ] })
1385
+ }
1386
+ );
1387
+ }
1388
+ RowIndexColumnHeader.displayName = "RowIndexColumnHeader";
1389
+
1390
+ // src/hooks/useSpreadsheetHighlighting.ts
1391
+ import { useCallback as useCallback2, useState as useState4 } from "react";
1392
+ var HIGHLIGHT_COLORS = {
1393
+ // Darker colors for rows (more visible)
1394
+ row: [
1395
+ "#fef08a",
1396
+ // yellow
1397
+ "#bbf7d0",
1398
+ // green
1399
+ "#bfdbfe",
1400
+ // blue
1401
+ "#fecaca",
1402
+ // red
1403
+ "#e9d5ff",
1404
+ // purple
1405
+ "#fed7aa",
1406
+ // orange
1407
+ "#a5f3fc",
1408
+ // cyan
1409
+ "#fce7f3",
1410
+ // pink
1411
+ "#d1d5db"
1412
+ // gray
1413
+ ],
1414
+ // Lighter colors for columns/headers (subtle background)
1415
+ column: [
1416
+ "#fef9c3",
1417
+ // yellow-100
1418
+ "#dcfce7",
1419
+ // green-100
1420
+ "#dbeafe",
1421
+ // blue-100
1422
+ "#fee2e2",
1423
+ // red-100
1424
+ "#f3e8ff",
1425
+ // purple-100
1426
+ "#ffedd5",
1427
+ // orange-100
1428
+ "#cffafe",
1429
+ // cyan-100
1430
+ "#fce7f3",
1431
+ // pink-100
1432
+ "#e5e7eb"
1433
+ // gray-200
1434
+ ]
1435
+ };
1436
+ var ROW_INDEX_COLUMN_ID = "__row_index__";
1437
+ function useSpreadsheetHighlighting({
1438
+ externalRowHighlights,
1439
+ onRowHighlight
1440
+ } = {}) {
1441
+ const [cellHighlights, setCellHighlights] = useState4([]);
1442
+ const [rowHighlightsInternal, setRowHighlightsInternal] = useState4([]);
1443
+ const [columnHighlights, setColumnHighlights] = useState4({});
1444
+ const [highlightPickerRow, setHighlightPickerRow] = useState4(null);
1445
+ const [highlightPickerColumn, setHighlightPickerColumn] = useState4(null);
1446
+ const rowHighlights = externalRowHighlights || rowHighlightsInternal;
1447
+ const getCellHighlight = useCallback2(
1448
+ (rowId, columnId) => {
1449
+ return cellHighlights.find((h) => h.rowId === rowId && h.columnId === columnId)?.color;
1450
+ },
1451
+ [cellHighlights]
1452
+ );
1453
+ const handleCellHighlightToggle = useCallback2(
1454
+ (rowId, columnId, color = "#fef08a") => {
1455
+ setCellHighlights((prev) => {
1456
+ const existing = prev.find((h) => h.rowId === rowId && h.columnId === columnId);
1457
+ if (existing) {
1458
+ return prev.filter((h) => !(h.rowId === rowId && h.columnId === columnId));
1459
+ }
1460
+ return [...prev, { rowId, columnId, color }];
1461
+ });
1462
+ },
1463
+ []
1464
+ );
1465
+ const getRowHighlight = useCallback2(
1466
+ (rowId) => {
1467
+ return rowHighlights.find((h) => h.rowId === rowId && !h.columnId);
1468
+ },
1469
+ [rowHighlights]
1470
+ );
1471
+ const handleRowHighlightToggle = useCallback2(
1472
+ (rowId, color) => {
1473
+ if (onRowHighlight) {
1474
+ onRowHighlight(rowId, color);
1475
+ } else {
1476
+ setRowHighlightsInternal((prev) => {
1477
+ const existing = prev.find((h) => h.rowId === rowId && !h.columnId);
1478
+ if (existing) {
1479
+ if (color === null) {
1480
+ return prev.filter((h) => !(h.rowId === rowId && !h.columnId));
1481
+ }
1482
+ return prev.map(
1483
+ (h) => h.rowId === rowId && !h.columnId ? { ...h, color } : h
1484
+ );
1485
+ }
1486
+ if (color) {
1487
+ return [...prev, { rowId, color }];
1488
+ }
1489
+ return prev;
1490
+ });
1491
+ }
1492
+ setHighlightPickerRow(null);
1493
+ },
1494
+ [onRowHighlight]
1495
+ );
1496
+ const getColumnHighlight = useCallback2(
1497
+ (columnId) => {
1498
+ return columnHighlights[columnId];
1499
+ },
1500
+ [columnHighlights]
1501
+ );
1502
+ const handleColumnHighlightToggle = useCallback2((columnId, color) => {
1503
+ setColumnHighlights((prev) => {
1504
+ const newHighlights = { ...prev };
1505
+ if (color === null) {
1506
+ delete newHighlights[columnId];
1507
+ } else {
1508
+ newHighlights[columnId] = color;
1509
+ }
1510
+ return newHighlights;
1511
+ });
1512
+ setHighlightPickerColumn(null);
1513
+ }, []);
1514
+ const clearAllHighlights = useCallback2(() => {
1515
+ setCellHighlights([]);
1516
+ setRowHighlightsInternal([]);
1517
+ setColumnHighlights({});
1518
+ }, []);
1519
+ return {
1520
+ // Cell highlights
1521
+ cellHighlights,
1522
+ getCellHighlight,
1523
+ handleCellHighlightToggle,
1524
+ // Row highlights
1525
+ rowHighlights,
1526
+ getRowHighlight,
1527
+ handleRowHighlightToggle,
1528
+ // Column highlights
1529
+ columnHighlights,
1530
+ getColumnHighlight,
1531
+ handleColumnHighlightToggle,
1532
+ // Picker state
1533
+ highlightPickerRow,
1534
+ setHighlightPickerRow,
1535
+ highlightPickerColumn,
1536
+ setHighlightPickerColumn,
1537
+ // Utility
1538
+ clearAllHighlights
1539
+ };
1540
+ }
1541
+
1542
+ // src/components/ColorPickerPopover.tsx
1543
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1544
+ function ColorPickerPopover({
1545
+ title,
1546
+ paletteType = "column",
1547
+ colors,
1548
+ onSelectColor,
1549
+ onClose,
1550
+ className
1551
+ }) {
1552
+ const colorPalette = colors ?? [...HIGHLIGHT_COLORS[paletteType], null];
1553
+ return /* @__PURE__ */ jsx7("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsxs7("div", { className: cn("bg-white rounded-lg shadow-xl p-4 w-64", className), children: [
1554
+ /* @__PURE__ */ jsx7("h3", { className: "text-sm font-semibold mb-3", children: title }),
1555
+ /* @__PURE__ */ jsx7("div", { className: "grid grid-cols-5 gap-2", children: colorPalette.map((color) => /* @__PURE__ */ jsx7(
1556
+ "button",
1557
+ {
1558
+ type: "button",
1559
+ onClick: () => onSelectColor(color),
1560
+ className: cn(
1561
+ "w-8 h-8 rounded border-2 transition-transform hover:scale-110",
1562
+ color ? "border-gray-300" : "border-gray-300 bg-white flex items-center justify-center text-gray-400 text-xs"
1563
+ ),
1564
+ style: color ? { backgroundColor: color } : void 0,
1565
+ title: color || "Clear highlight",
1566
+ children: !color && "\u2715"
1567
+ },
1568
+ color || "clear"
1569
+ )) }),
1570
+ /* @__PURE__ */ jsx7("div", { className: "flex justify-end mt-4", children: /* @__PURE__ */ jsx7(
1571
+ "button",
1572
+ {
1573
+ type: "button",
1574
+ onClick: onClose,
1575
+ className: "px-3 py-1.5 text-sm text-gray-600 hover:bg-gray-100 rounded transition-colors",
1576
+ children: "Cancel"
1577
+ }
1578
+ ) })
1579
+ ] }) });
1580
+ }
1581
+ ColorPickerPopover.displayName = "ColorPickerPopover";
1582
+
1583
+ // src/components/SpreadsheetSettingsModal.tsx
1584
+ import { useState as useState5, useEffect as useEffect3 } from "react";
1585
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1586
+ var DEFAULT_SETTINGS = {
1587
+ defaultPinnedColumns: [],
1588
+ defaultSort: null,
1589
+ defaultPageSize: 25,
1590
+ defaultZoom: 100,
1591
+ autoSave: true,
1592
+ compactView: false,
1593
+ showRowIndex: true,
1594
+ pinRowIndex: false,
1595
+ rowIndexHighlightColor: void 0
1596
+ };
1597
+ var SpreadsheetSettingsModal = ({
1598
+ isOpen,
1599
+ onClose,
1600
+ settings,
1601
+ onSave,
1602
+ columns,
1603
+ title = "Spreadsheet Settings",
1604
+ pageSizeOptions = [25, 50, 100, 200]
1605
+ }) => {
1606
+ const [activeTab, setActiveTab] = useState5("columns");
1607
+ const [localSettings, setLocalSettings] = useState5(settings);
1608
+ useEffect3(() => {
1609
+ setLocalSettings(settings);
1610
+ }, [settings]);
1611
+ if (!isOpen) return null;
1612
+ const handleSave = () => {
1613
+ onSave(localSettings);
1614
+ onClose();
1615
+ };
1616
+ const handleReset = () => {
1617
+ setLocalSettings(DEFAULT_SETTINGS);
1618
+ };
1619
+ const togglePinnedColumn = (columnId) => {
1620
+ const isPinned = localSettings.defaultPinnedColumns.includes(columnId);
1621
+ if (isPinned) {
1622
+ setLocalSettings({
1623
+ ...localSettings,
1624
+ defaultPinnedColumns: localSettings.defaultPinnedColumns.filter(
1625
+ (col) => col !== columnId
1626
+ )
1627
+ });
1628
+ } else {
1629
+ setLocalSettings({
1630
+ ...localSettings,
1631
+ defaultPinnedColumns: [...localSettings.defaultPinnedColumns, columnId]
1632
+ });
1633
+ }
1634
+ };
1635
+ const handleSortChange = (columnId, direction) => {
1636
+ if (columnId === "") {
1637
+ setLocalSettings({
1638
+ ...localSettings,
1639
+ defaultSort: null
1640
+ });
1641
+ } else {
1642
+ setLocalSettings({
1643
+ ...localSettings,
1644
+ defaultSort: { columnId, direction }
1645
+ });
1646
+ }
1647
+ };
1648
+ const handleDisplaySettingChange = (key, value) => {
1649
+ const newSettings = {
1650
+ ...localSettings,
1651
+ [key]: value
1652
+ };
1653
+ setLocalSettings(newSettings);
1654
+ if (key === "compactView" || key === "autoSave") {
1655
+ onSave(newSettings);
1656
+ }
1657
+ };
1658
+ const tabs = [
1659
+ { id: "columns", label: "Pinned Columns", Icon: HiViewBoards },
1660
+ { id: "sorting", label: "Default Sorting", Icon: HiSortAscending },
1661
+ { id: "display", label: "Display Options", Icon: HiEye }
1662
+ ];
1663
+ return /* @__PURE__ */ jsxs8(
1664
+ "div",
1665
+ {
1666
+ className: "fixed inset-0 bg-black/50 flex items-center justify-center z-50",
1667
+ role: "dialog",
1668
+ "aria-modal": "true",
1669
+ "aria-labelledby": "settings-modal-title",
1670
+ children: [
1671
+ /* @__PURE__ */ jsx8(
1672
+ "button",
1673
+ {
1674
+ type: "button",
1675
+ className: "absolute inset-0 cursor-default",
1676
+ onClick: onClose,
1677
+ onKeyDown: (e) => e.key === "Escape" && onClose(),
1678
+ "aria-label": "Close settings"
1679
+ }
1680
+ ),
1681
+ /* @__PURE__ */ jsxs8("div", { className: "bg-white rounded-lg w-[90%] max-w-[700px] max-h-[90vh] flex flex-col shadow-xl relative z-10", children: [
1682
+ /* @__PURE__ */ jsxs8("div", { className: "px-6 py-5 border-b border-gray-200 flex items-center justify-between", children: [
1683
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-3", children: [
1684
+ /* @__PURE__ */ jsx8(HiCog, { className: "h-6 w-6 text-blue-600" }),
1685
+ /* @__PURE__ */ jsx8("h2", { id: "settings-modal-title", className: "text-xl font-bold text-gray-900", children: title })
1686
+ ] }),
1687
+ /* @__PURE__ */ jsx8(
1688
+ "button",
1689
+ {
1690
+ type: "button",
1691
+ onClick: onClose,
1692
+ className: "p-2 hover:bg-gray-100 rounded-lg transition-colors text-gray-500 hover:text-gray-700",
1693
+ children: /* @__PURE__ */ jsx8(HiX, { className: "h-5 w-5" })
1694
+ }
1695
+ )
1696
+ ] }),
1697
+ /* @__PURE__ */ jsx8("div", { className: "flex border-b border-gray-200 px-6", children: tabs.map((tab) => /* @__PURE__ */ jsxs8(
1698
+ "button",
1699
+ {
1700
+ type: "button",
1701
+ onClick: () => setActiveTab(tab.id),
1702
+ className: `px-4 py-3 flex items-center gap-2 text-sm font-medium transition-colors border-b-2 ${activeTab === tab.id ? "text-blue-600 border-blue-600" : "text-gray-500 border-transparent hover:text-gray-700"}`,
1703
+ children: [
1704
+ /* @__PURE__ */ jsx8(tab.Icon, { className: "h-4 w-4" }),
1705
+ tab.label
1706
+ ]
1707
+ },
1708
+ tab.id
1709
+ )) }),
1710
+ /* @__PURE__ */ jsxs8("div", { className: "flex-1 overflow-auto p-6", children: [
1711
+ activeTab === "columns" && /* @__PURE__ */ jsxs8("div", { children: [
1712
+ /* @__PURE__ */ jsxs8("div", { className: "p-4 bg-blue-50 border border-blue-200 rounded-lg mb-4 flex gap-3", children: [
1713
+ /* @__PURE__ */ jsx8(HiViewBoards, { className: "h-4 w-4 text-blue-600 shrink-0 mt-0.5" }),
1714
+ /* @__PURE__ */ jsxs8("div", { children: [
1715
+ /* @__PURE__ */ jsx8("p", { className: "text-sm font-semibold text-gray-900 mb-1", children: "About Pinned Columns" }),
1716
+ /* @__PURE__ */ jsx8("p", { className: "text-sm text-gray-600", children: "Pinned columns stay visible while you scroll horizontally through the table." })
1717
+ ] })
1718
+ ] }),
1719
+ /* @__PURE__ */ jsx8("p", { className: "text-sm text-gray-600 mb-4", children: "Select which columns should be pinned to the left by default." }),
1720
+ /* @__PURE__ */ jsxs8("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2", children: [
1721
+ /* @__PURE__ */ jsxs8(
1722
+ "button",
1723
+ {
1724
+ type: "button",
1725
+ onClick: () => setLocalSettings({
1726
+ ...localSettings,
1727
+ pinRowIndex: !localSettings.pinRowIndex
1728
+ }),
1729
+ className: `flex items-center gap-2 p-3 rounded-lg border transition-colors text-left ${localSettings.pinRowIndex !== false ? "bg-blue-50 border-blue-300 text-blue-700" : "bg-gray-50 border-gray-200 text-gray-700 hover:border-blue-300"}`,
1730
+ children: [
1731
+ /* @__PURE__ */ jsx8(HiViewBoards, { className: "h-4 w-4 shrink-0" }),
1732
+ /* @__PURE__ */ jsx8("span", { className: "text-sm flex-1 truncate", children: "# (Row Index)" })
1733
+ ]
1734
+ }
1735
+ ),
1736
+ columns.map((column) => {
1737
+ const isPinned = localSettings.defaultPinnedColumns.includes(
1738
+ column.id
1739
+ );
1740
+ return /* @__PURE__ */ jsxs8(
1741
+ "button",
1742
+ {
1743
+ type: "button",
1744
+ onClick: () => togglePinnedColumn(column.id),
1745
+ className: `flex items-center gap-2 p-3 rounded-lg border transition-colors text-left ${isPinned ? "bg-blue-50 border-blue-300 text-blue-700" : "bg-gray-50 border-gray-200 text-gray-700 hover:border-blue-300"}`,
1746
+ children: [
1747
+ /* @__PURE__ */ jsx8(HiViewBoards, { className: "h-4 w-4 shrink-0" }),
1748
+ /* @__PURE__ */ jsx8("span", { className: "text-sm flex-1 truncate", children: column.label })
1749
+ ]
1750
+ },
1751
+ column.id
1752
+ );
1753
+ })
1754
+ ] }),
1755
+ /* @__PURE__ */ jsxs8("div", { className: "mt-6 p-4 bg-amber-50 border border-amber-200 rounded-lg", children: [
1756
+ /* @__PURE__ */ jsx8("p", { className: "text-sm font-semibold text-gray-900 mb-3", children: "Row Index Column Highlight" }),
1757
+ /* @__PURE__ */ jsx8("p", { className: "text-sm text-gray-600 mb-3", children: "Apply a highlight color to the # (row index) column to make it stand out." }),
1758
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2 flex-wrap", children: [
1759
+ [
1760
+ "#fef3c7",
1761
+ "#dbeafe",
1762
+ "#dcfce7",
1763
+ "#fce7f3",
1764
+ "#f3e8ff",
1765
+ "#e0e7ff",
1766
+ "#fed7d7",
1767
+ "#c6f6d5"
1768
+ ].map((color) => /* @__PURE__ */ jsx8(
1769
+ "button",
1770
+ {
1771
+ type: "button",
1772
+ onClick: () => setLocalSettings({
1773
+ ...localSettings,
1774
+ rowIndexHighlightColor: localSettings.rowIndexHighlightColor === color ? void 0 : color
1775
+ }),
1776
+ className: `w-8 h-8 rounded-lg border-2 transition-all ${localSettings.rowIndexHighlightColor === color ? "border-blue-600 scale-110 ring-2 ring-blue-300" : "border-gray-300 hover:border-gray-400"}`,
1777
+ style: { backgroundColor: color },
1778
+ title: localSettings.rowIndexHighlightColor === color ? "Remove highlight" : "Apply highlight"
1779
+ },
1780
+ color
1781
+ )),
1782
+ localSettings.rowIndexHighlightColor && /* @__PURE__ */ jsx8(
1783
+ "button",
1784
+ {
1785
+ type: "button",
1786
+ onClick: () => setLocalSettings({
1787
+ ...localSettings,
1788
+ rowIndexHighlightColor: void 0
1789
+ }),
1790
+ className: "text-sm text-red-600 hover:text-red-700 ml-2 px-2 py-1 rounded hover:bg-red-50",
1791
+ children: "Clear"
1792
+ }
1793
+ )
1794
+ ] })
1795
+ ] })
1796
+ ] }),
1797
+ activeTab === "sorting" && /* @__PURE__ */ jsxs8("div", { children: [
1798
+ /* @__PURE__ */ jsx8("p", { className: "text-sm text-gray-600 mb-4", children: "Set the default column sorting when opening the spreadsheet." }),
1799
+ /* @__PURE__ */ jsxs8("div", { className: "space-y-4", children: [
1800
+ /* @__PURE__ */ jsxs8("div", { children: [
1801
+ /* @__PURE__ */ jsx8("label", { className: "block text-sm font-medium text-gray-900 mb-2", children: "Sort Column" }),
1802
+ /* @__PURE__ */ jsxs8(
1803
+ "select",
1804
+ {
1805
+ value: localSettings.defaultSort?.columnId || "",
1806
+ onChange: (e) => handleSortChange(
1807
+ e.target.value,
1808
+ localSettings.defaultSort?.direction || "asc"
1809
+ ),
1810
+ className: "w-full p-3 text-sm bg-white border border-gray-300 rounded-lg text-gray-900 cursor-pointer focus:ring-2 focus:ring-blue-500 focus:border-blue-500",
1811
+ children: [
1812
+ /* @__PURE__ */ jsx8("option", { value: "", children: "No default sorting" }),
1813
+ columns.filter((col) => col.sortable !== false).map((column) => /* @__PURE__ */ jsx8("option", { value: column.id, children: column.label }, column.id))
1814
+ ]
1815
+ }
1816
+ )
1817
+ ] }),
1818
+ localSettings.defaultSort && /* @__PURE__ */ jsxs8("div", { children: [
1819
+ /* @__PURE__ */ jsx8("label", { className: "block text-sm font-medium text-gray-900 mb-2", children: "Sort Direction" }),
1820
+ /* @__PURE__ */ jsxs8(
1821
+ "select",
1822
+ {
1823
+ value: localSettings.defaultSort.direction,
1824
+ onChange: (e) => handleSortChange(
1825
+ localSettings.defaultSort.columnId,
1826
+ e.target.value
1827
+ ),
1828
+ className: "w-full p-3 text-sm bg-white border border-gray-300 rounded-lg text-gray-900 cursor-pointer focus:ring-2 focus:ring-blue-500 focus:border-blue-500",
1829
+ children: [
1830
+ /* @__PURE__ */ jsx8("option", { value: "asc", children: "Ascending (A \u2192 Z, 0 \u2192 9)" }),
1831
+ /* @__PURE__ */ jsx8("option", { value: "desc", children: "Descending (Z \u2192 A, 9 \u2192 0)" })
1832
+ ]
1833
+ }
1834
+ )
1835
+ ] })
1836
+ ] })
1837
+ ] }),
1838
+ activeTab === "display" && /* @__PURE__ */ jsxs8("div", { className: "space-y-5", children: [
1839
+ /* @__PURE__ */ jsx8("p", { className: "text-sm text-gray-600", children: "Customize the display and behavior of the spreadsheet." }),
1840
+ /* @__PURE__ */ jsxs8("div", { className: "p-4 bg-blue-50 border border-blue-200 rounded-lg", children: [
1841
+ /* @__PURE__ */ jsx8("p", { className: "text-sm font-semibold text-gray-900 mb-3", children: "Row Index Column" }),
1842
+ /* @__PURE__ */ jsxs8("div", { className: "space-y-3", children: [
1843
+ /* @__PURE__ */ jsxs8("label", { className: "flex items-center gap-3 cursor-pointer", children: [
1844
+ /* @__PURE__ */ jsx8(
1845
+ "input",
1846
+ {
1847
+ type: "checkbox",
1848
+ checked: localSettings.showRowIndex !== false,
1849
+ onChange: (e) => setLocalSettings({
1850
+ ...localSettings,
1851
+ showRowIndex: e.target.checked
1852
+ }),
1853
+ className: "w-4 h-4 cursor-pointer rounded border-gray-300 text-blue-600 focus:ring-blue-500"
1854
+ }
1855
+ ),
1856
+ /* @__PURE__ */ jsx8("span", { className: "text-sm text-gray-700", children: "Show row index (#) column" })
1857
+ ] }),
1858
+ /* @__PURE__ */ jsx8("p", { className: "text-xs text-gray-500 ml-7", children: 'Tip: Use the "Pinned Columns" tab to pin and highlight the row index column.' })
1859
+ ] })
1860
+ ] }),
1861
+ /* @__PURE__ */ jsxs8("div", { children: [
1862
+ /* @__PURE__ */ jsx8("label", { className: "block text-sm font-medium text-gray-900 mb-2", children: "Default Page Size" }),
1863
+ /* @__PURE__ */ jsx8(
1864
+ "select",
1865
+ {
1866
+ value: localSettings.defaultPageSize,
1867
+ onChange: (e) => setLocalSettings({
1868
+ ...localSettings,
1869
+ defaultPageSize: parseInt(e.target.value, 10)
1870
+ }),
1871
+ className: "w-full p-3 text-sm bg-white border border-gray-300 rounded-lg text-gray-900 cursor-pointer focus:ring-2 focus:ring-blue-500 focus:border-blue-500",
1872
+ children: pageSizeOptions.map((size) => /* @__PURE__ */ jsxs8("option", { value: size, children: [
1873
+ size,
1874
+ " rows"
1875
+ ] }, size))
1876
+ }
1877
+ )
1878
+ ] }),
1879
+ /* @__PURE__ */ jsxs8("div", { children: [
1880
+ /* @__PURE__ */ jsxs8("label", { className: "block text-sm font-medium text-gray-900 mb-2", children: [
1881
+ "Default Zoom Level: ",
1882
+ localSettings.defaultZoom,
1883
+ "%"
1884
+ ] }),
1885
+ /* @__PURE__ */ jsx8(
1886
+ "input",
1887
+ {
1888
+ type: "range",
1889
+ min: "50",
1890
+ max: "150",
1891
+ step: "10",
1892
+ value: localSettings.defaultZoom,
1893
+ onChange: (e) => setLocalSettings({
1894
+ ...localSettings,
1895
+ defaultZoom: parseInt(e.target.value, 10)
1896
+ }),
1897
+ className: "w-full cursor-pointer"
1898
+ }
1899
+ ),
1900
+ /* @__PURE__ */ jsxs8("div", { className: "flex justify-between mt-1 text-xs text-gray-500", children: [
1901
+ /* @__PURE__ */ jsx8("span", { children: "50%" }),
1902
+ /* @__PURE__ */ jsx8("span", { children: "150%" })
1903
+ ] })
1904
+ ] }),
1905
+ /* @__PURE__ */ jsxs8("div", { className: "space-y-3", children: [
1906
+ /* @__PURE__ */ jsxs8("label", { className: "flex items-center gap-3 p-4 bg-gray-50 border border-gray-200 rounded-lg cursor-pointer hover:bg-gray-100 transition-colors", children: [
1907
+ /* @__PURE__ */ jsx8(
1908
+ "input",
1909
+ {
1910
+ type: "checkbox",
1911
+ checked: localSettings.autoSave,
1912
+ onChange: (e) => handleDisplaySettingChange("autoSave", e.target.checked),
1913
+ className: "w-5 h-5 cursor-pointer rounded border-gray-300 text-blue-600 focus:ring-blue-500"
1914
+ }
1915
+ ),
1916
+ /* @__PURE__ */ jsxs8("div", { className: "flex-1", children: [
1917
+ /* @__PURE__ */ jsx8("div", { className: "text-sm font-medium text-gray-900", children: "Auto-save changes" }),
1918
+ /* @__PURE__ */ jsx8("div", { className: "text-sm text-gray-500 mt-0.5", children: "Automatically save changes without confirmation" })
1919
+ ] })
1920
+ ] }),
1921
+ /* @__PURE__ */ jsxs8("label", { className: "flex items-center gap-3 p-4 bg-gray-50 border border-gray-200 rounded-lg cursor-pointer hover:bg-gray-100 transition-colors", children: [
1922
+ /* @__PURE__ */ jsx8(
1923
+ "input",
1924
+ {
1925
+ type: "checkbox",
1926
+ checked: localSettings.compactView,
1927
+ onChange: (e) => handleDisplaySettingChange(
1928
+ "compactView",
1929
+ e.target.checked
1930
+ ),
1931
+ className: "w-5 h-5 cursor-pointer rounded border-gray-300 text-blue-600 focus:ring-blue-500"
1932
+ }
1933
+ ),
1934
+ /* @__PURE__ */ jsxs8("div", { className: "flex-1", children: [
1935
+ /* @__PURE__ */ jsx8("div", { className: "text-sm font-medium text-gray-900", children: "Compact view" }),
1936
+ /* @__PURE__ */ jsx8("div", { className: "text-sm text-gray-500 mt-0.5", children: "Reduce padding and spacing to show more rows on screen" })
1937
+ ] })
1938
+ ] })
1939
+ ] })
1940
+ ] })
1941
+ ] }),
1942
+ /* @__PURE__ */ jsxs8("div", { className: "px-6 py-4 border-t border-gray-200 flex justify-between items-center gap-3", children: [
1943
+ /* @__PURE__ */ jsx8(
1944
+ "button",
1945
+ {
1946
+ type: "button",
1947
+ onClick: handleReset,
1948
+ className: "px-4 py-2.5 text-sm font-medium text-red-600 bg-red-50 border border-red-200 rounded-lg hover:bg-red-100 transition-colors",
1949
+ children: "Reset to Defaults"
1950
+ }
1951
+ ),
1952
+ /* @__PURE__ */ jsxs8("div", { className: "flex gap-2", children: [
1953
+ /* @__PURE__ */ jsx8(
1954
+ "button",
1955
+ {
1956
+ type: "button",
1957
+ onClick: onClose,
1958
+ className: "px-4 py-2.5 text-sm font-medium text-gray-700 bg-gray-100 border border-gray-200 rounded-lg hover:bg-gray-200 transition-colors",
1959
+ children: "Cancel"
1960
+ }
1961
+ ),
1962
+ /* @__PURE__ */ jsx8(
1963
+ "button",
1964
+ {
1965
+ type: "button",
1966
+ onClick: handleSave,
1967
+ className: "px-4 py-2.5 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors",
1968
+ children: "Save Settings"
1969
+ }
1970
+ )
1971
+ ] })
1972
+ ] })
1973
+ ] })
1974
+ ]
1975
+ }
1976
+ );
1977
+ };
1978
+ SpreadsheetSettingsModal.displayName = "SpreadsheetSettingsModal";
1979
+
1980
+ // src/components/CommentModals.tsx
1981
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1982
+ function AddCommentModal({
1983
+ isOpen,
1984
+ commentText,
1985
+ onCommentTextChange,
1986
+ onAdd,
1987
+ onClose
1988
+ }) {
1989
+ if (!isOpen) return null;
1990
+ return /* @__PURE__ */ jsx9("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsxs9("div", { className: "bg-white rounded-lg shadow-xl p-6 w-96 max-w-full mx-4", children: [
1991
+ /* @__PURE__ */ jsx9("h3", { className: "text-lg font-semibold mb-4", children: "Add Row Comment" }),
1992
+ /* @__PURE__ */ jsx9(
1993
+ "textarea",
1994
+ {
1995
+ value: commentText,
1996
+ onChange: (e) => onCommentTextChange(e.target.value),
1997
+ placeholder: "Enter your comment...",
1998
+ className: "w-full h-24 p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none"
1999
+ }
2000
+ ),
2001
+ /* @__PURE__ */ jsxs9("div", { className: "flex justify-end gap-2 mt-4", children: [
2002
+ /* @__PURE__ */ jsx9(
2003
+ "button",
2004
+ {
2005
+ type: "button",
2006
+ onClick: onClose,
2007
+ className: "px-4 py-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
2008
+ children: "Cancel"
2009
+ }
2010
+ ),
2011
+ /* @__PURE__ */ jsx9(
2012
+ "button",
2013
+ {
2014
+ type: "button",
2015
+ onClick: onAdd,
2016
+ disabled: !commentText.trim(),
2017
+ className: "px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors",
2018
+ children: "Add Comment"
2019
+ }
2020
+ )
2021
+ ] })
2022
+ ] }) });
2023
+ }
2024
+ AddCommentModal.displayName = "AddCommentModal";
2025
+ function ViewCommentsModal({
2026
+ isOpen,
2027
+ comments,
2028
+ onToggleResolved,
2029
+ onClose
2030
+ }) {
2031
+ if (!isOpen) return null;
2032
+ return /* @__PURE__ */ jsx9("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsxs9("div", { className: "bg-white rounded-lg shadow-xl p-6 w-[480px] max-w-full mx-4 max-h-[80vh] flex flex-col", children: [
2033
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between mb-4", children: [
2034
+ /* @__PURE__ */ jsx9("h3", { className: "text-lg font-semibold", children: "Row Comments" }),
2035
+ /* @__PURE__ */ jsx9(
2036
+ "button",
2037
+ {
2038
+ type: "button",
2039
+ onClick: onClose,
2040
+ className: "p-1 hover:bg-gray-100 rounded-lg transition-colors",
2041
+ children: "\u2715"
2042
+ }
2043
+ )
2044
+ ] }),
2045
+ /* @__PURE__ */ jsxs9("div", { className: "flex-1 overflow-y-auto space-y-3", children: [
2046
+ comments.map((comment) => /* @__PURE__ */ jsxs9(
2047
+ "div",
2048
+ {
2049
+ className: cn(
2050
+ "p-3 rounded-lg border",
2051
+ comment.resolved ? "bg-gray-50 border-gray-200" : "bg-yellow-50 border-yellow-200"
2052
+ ),
2053
+ children: [
2054
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-start justify-between gap-2", children: [
2055
+ /* @__PURE__ */ jsx9("p", { className: "text-sm text-gray-700", children: comment.text }),
2056
+ /* @__PURE__ */ jsx9(
2057
+ "button",
2058
+ {
2059
+ type: "button",
2060
+ onClick: () => onToggleResolved(comment.id),
2061
+ className: cn(
2062
+ "flex-shrink-0 px-2 py-1 text-xs rounded transition-colors",
2063
+ comment.resolved ? "bg-gray-200 text-gray-600 hover:bg-gray-300" : "bg-green-100 text-green-700 hover:bg-green-200"
2064
+ ),
2065
+ children: comment.resolved ? "Reopen" : "Resolve"
2066
+ }
2067
+ )
2068
+ ] }),
2069
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2 mt-2 text-xs text-gray-500", children: [
2070
+ comment.author && /* @__PURE__ */ jsx9("span", { children: comment.author }),
2071
+ /* @__PURE__ */ jsx9("span", { children: new Date(comment.timestamp).toLocaleString() })
2072
+ ] })
2073
+ ]
2074
+ },
2075
+ comment.id
2076
+ )),
2077
+ comments.length === 0 && /* @__PURE__ */ jsx9("p", { className: "text-center text-gray-500 py-8", children: "No comments for this row." })
2078
+ ] })
2079
+ ] }) });
2080
+ }
2081
+ ViewCommentsModal.displayName = "ViewCommentsModal";
2082
+
2083
+ // src/components/KeyboardShortcutsModal.tsx
2084
+ import React4 from "react";
2085
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
2086
+ function KeyboardShortcutsModal({
2087
+ isOpen,
2088
+ onClose,
2089
+ shortcuts
2090
+ }) {
2091
+ if (!isOpen) return null;
2092
+ return /* @__PURE__ */ jsx10("div", { className: "fixed inset-0 bg-black/50 flex items-center justify-center z-50", children: /* @__PURE__ */ jsxs10("div", { className: "bg-white rounded-lg shadow-xl p-6 w-full max-w-2xl max-h-[80vh] overflow-y-auto mx-4", children: [
2093
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-between mb-4", children: [
2094
+ /* @__PURE__ */ jsx10("h3", { className: "text-xl font-bold text-gray-900", children: "Keyboard Shortcuts" }),
2095
+ /* @__PURE__ */ jsx10(
2096
+ "button",
2097
+ {
2098
+ type: "button",
2099
+ onClick: onClose,
2100
+ className: "p-1 hover:bg-gray-100 rounded",
2101
+ children: /* @__PURE__ */ jsx10("span", { className: "text-gray-500 text-xl", children: "\u2715" })
2102
+ }
2103
+ )
2104
+ ] }),
2105
+ /* @__PURE__ */ jsxs10("div", { className: "space-y-6", children: [
2106
+ /* @__PURE__ */ jsx10(ShortcutSection, { title: "General", children: shortcuts.general.map((shortcut, index) => /* @__PURE__ */ jsx10(ShortcutRow, { label: shortcut.label, keys: shortcut.keys }, index)) }),
2107
+ /* @__PURE__ */ jsx10(ShortcutSection, { title: "Row Selection", children: shortcuts.rowSelection.map((shortcut, index) => /* @__PURE__ */ jsx10(ShortcutRow, { label: shortcut.label, keys: shortcut.keys }, index)) }),
2108
+ /* @__PURE__ */ jsx10(ShortcutSection, { title: "Editing", children: shortcuts.editing.map((shortcut, index) => /* @__PURE__ */ jsx10(ShortcutRow, { label: shortcut.label, keys: shortcut.keys }, index)) }),
2109
+ /* @__PURE__ */ jsx10(ShortcutSection, { title: "Row Actions (hover over row #)", children: shortcuts.rowActions.map((action, index) => /* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-between", children: [
2110
+ /* @__PURE__ */ jsx10("span", { className: "text-gray-600 text-sm", children: action.label }),
2111
+ /* @__PURE__ */ jsx10("span", { className: "text-gray-500 text-xs", children: action.description })
2112
+ ] }, index)) })
2113
+ ] })
2114
+ ] }) });
2115
+ }
2116
+ function ShortcutSection({ title, children }) {
2117
+ return /* @__PURE__ */ jsxs10("div", { children: [
2118
+ /* @__PURE__ */ jsx10("h4", { className: "text-gray-900 font-semibold mb-3", children: title }),
2119
+ /* @__PURE__ */ jsx10("div", { className: "space-y-2", children })
2120
+ ] });
2121
+ }
2122
+ function ShortcutRow({ label, keys }) {
2123
+ return /* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-between", children: [
2124
+ /* @__PURE__ */ jsx10("span", { className: "text-gray-600 text-sm", children: label }),
2125
+ /* @__PURE__ */ jsx10("div", { className: "flex items-center gap-1", children: keys.map((key, index) => /* @__PURE__ */ jsxs10(React4.Fragment, { children: [
2126
+ index > 0 && /* @__PURE__ */ jsx10("span", { className: "text-gray-400", children: "+" }),
2127
+ key.includes("Click") ? /* @__PURE__ */ jsx10("span", { className: "text-gray-500 text-xs", children: key }) : /* @__PURE__ */ jsx10("kbd", { className: "px-2 py-1 bg-gray-100 text-gray-800 rounded text-xs border border-gray-200", children: key })
2128
+ ] }, index)) })
2129
+ ] });
2130
+ }
2131
+ KeyboardShortcutsModal.displayName = "KeyboardShortcutsModal";
2132
+
2133
+ // src/components/Spreadsheet.tsx
2134
+ import { Pagination } from "@xcelsior/design-system";
2135
+
2136
+ // src/hooks/useSpreadsheetFiltering.ts
2137
+ import { useCallback as useCallback3, useMemo as useMemo2, useState as useState6 } from "react";
2138
+ function useSpreadsheetFiltering({
2139
+ data,
2140
+ columns,
2141
+ onFilterChange,
2142
+ onSortChange,
2143
+ serverSide = false,
2144
+ controlledFilters,
2145
+ controlledSortConfig
2146
+ }) {
2147
+ const [internalFilters, setInternalFilters] = useState6(
2148
+ {}
2149
+ );
2150
+ const [internalSortConfig, setInternalSortConfig] = useState6(
2151
+ null
2152
+ );
2153
+ const [activeFilterColumn, setActiveFilterColumn] = useState6(null);
2154
+ const filters = controlledFilters ?? internalFilters;
2155
+ const sortConfig = controlledSortConfig !== void 0 ? controlledSortConfig : internalSortConfig;
2156
+ const applyTextCondition = useCallback3(
2157
+ (value, condition) => {
2158
+ const strValue = String(value ?? "").toLowerCase();
2159
+ const filterValue = (condition.value ?? "").toLowerCase();
2160
+ switch (condition.operator) {
2161
+ case "contains":
2162
+ return strValue.includes(filterValue);
2163
+ case "notContains":
2164
+ return !strValue.includes(filterValue);
2165
+ case "equals":
2166
+ return strValue === filterValue;
2167
+ case "notEquals":
2168
+ return strValue !== filterValue;
2169
+ case "startsWith":
2170
+ return strValue.startsWith(filterValue);
2171
+ case "endsWith":
2172
+ return strValue.endsWith(filterValue);
2173
+ case "isEmpty":
2174
+ return isBlankValue(value);
2175
+ case "isNotEmpty":
2176
+ return !isBlankValue(value);
2177
+ default:
2178
+ return true;
2179
+ }
2180
+ },
2181
+ []
2182
+ );
2183
+ const applyNumberCondition = useCallback3(
2184
+ (value, condition) => {
2185
+ if (condition.operator === "isEmpty") return isBlankValue(value);
2186
+ if (condition.operator === "isNotEmpty") return !isBlankValue(value);
2187
+ const numValue = typeof value === "number" ? value : parseFloat(value);
2188
+ if (Number.isNaN(numValue)) return false;
2189
+ const filterValue = condition.value;
2190
+ const filterValueTo = condition.valueTo;
2191
+ switch (condition.operator) {
2192
+ case "equals":
2193
+ return filterValue !== void 0 && numValue === filterValue;
2194
+ case "notEquals":
2195
+ return filterValue !== void 0 && numValue !== filterValue;
2196
+ case "greaterThan":
2197
+ return filterValue !== void 0 && numValue > filterValue;
2198
+ case "greaterThanOrEqual":
2199
+ return filterValue !== void 0 && numValue >= filterValue;
2200
+ case "lessThan":
2201
+ return filterValue !== void 0 && numValue < filterValue;
2202
+ case "lessThanOrEqual":
2203
+ return filterValue !== void 0 && numValue <= filterValue;
2204
+ case "between":
2205
+ return filterValue !== void 0 && filterValueTo !== void 0 && numValue >= filterValue && numValue <= filterValueTo;
2206
+ default:
2207
+ return true;
2208
+ }
2209
+ },
2210
+ []
2211
+ );
2212
+ const applyDateCondition = useCallback3(
2213
+ (value, condition) => {
2214
+ if (condition.operator === "isEmpty") return isBlankValue(value);
2215
+ if (condition.operator === "isNotEmpty") return !isBlankValue(value);
2216
+ const dateValue = value ? new Date(value) : null;
2217
+ if (!dateValue || Number.isNaN(dateValue.getTime())) return false;
2218
+ const today = /* @__PURE__ */ new Date();
2219
+ today.setHours(0, 0, 0, 0);
2220
+ const getStartOfWeek = (date) => {
2221
+ const d = new Date(date);
2222
+ const day = d.getDay();
2223
+ const diff = d.getDate() - day;
2224
+ return new Date(d.setDate(diff));
2225
+ };
2226
+ const getStartOfMonth = (date) => new Date(date.getFullYear(), date.getMonth(), 1);
2227
+ const getStartOfYear = (date) => new Date(date.getFullYear(), 0, 1);
2228
+ switch (condition.operator) {
2229
+ case "equals":
2230
+ if (!condition.value) return false;
2231
+ return dateValue.toDateString() === new Date(condition.value).toDateString();
2232
+ case "notEquals":
2233
+ if (!condition.value) return false;
2234
+ return dateValue.toDateString() !== new Date(condition.value).toDateString();
2235
+ case "before":
2236
+ if (!condition.value) return false;
2237
+ return dateValue < new Date(condition.value);
2238
+ case "after":
2239
+ if (!condition.value) return false;
2240
+ return dateValue > new Date(condition.value);
2241
+ case "between":
2242
+ if (!condition.value || !condition.valueTo) return false;
2243
+ return dateValue >= new Date(condition.value) && dateValue <= new Date(condition.valueTo);
2244
+ case "today":
2245
+ return dateValue.toDateString() === today.toDateString();
2246
+ case "yesterday": {
2247
+ const yesterday = new Date(today);
2248
+ yesterday.setDate(yesterday.getDate() - 1);
2249
+ return dateValue.toDateString() === yesterday.toDateString();
2250
+ }
2251
+ case "thisWeek": {
2252
+ const thisWeekStart = getStartOfWeek(today);
2253
+ const thisWeekEnd = new Date(thisWeekStart);
2254
+ thisWeekEnd.setDate(thisWeekEnd.getDate() + 7);
2255
+ return dateValue >= thisWeekStart && dateValue < thisWeekEnd;
2256
+ }
2257
+ case "lastWeek": {
2258
+ const lastWeekStart = getStartOfWeek(today);
2259
+ lastWeekStart.setDate(lastWeekStart.getDate() - 7);
2260
+ const lastWeekEnd = getStartOfWeek(today);
2261
+ return dateValue >= lastWeekStart && dateValue < lastWeekEnd;
2262
+ }
2263
+ case "thisMonth": {
2264
+ const thisMonthStart = getStartOfMonth(today);
2265
+ const thisMonthEnd = new Date(today.getFullYear(), today.getMonth() + 1, 1);
2266
+ return dateValue >= thisMonthStart && dateValue < thisMonthEnd;
2267
+ }
2268
+ case "lastMonth": {
2269
+ const lastMonthStart = new Date(today.getFullYear(), today.getMonth() - 1, 1);
2270
+ const lastMonthEnd = getStartOfMonth(today);
2271
+ return dateValue >= lastMonthStart && dateValue < lastMonthEnd;
2272
+ }
2273
+ case "thisYear": {
2274
+ const thisYearStart = getStartOfYear(today);
2275
+ const thisYearEnd = new Date(today.getFullYear() + 1, 0, 1);
2276
+ return dateValue >= thisYearStart && dateValue < thisYearEnd;
2277
+ }
2278
+ default:
2279
+ return true;
2280
+ }
2281
+ },
2282
+ []
2283
+ );
2284
+ const filteredData = useMemo2(() => {
2285
+ if (!data || !Array.isArray(data)) return [];
2286
+ if (serverSide) {
2287
+ return data;
2288
+ }
2289
+ if (!columns || !Array.isArray(columns)) return data;
2290
+ let result = [...data];
2291
+ for (const [columnId, filter] of Object.entries(filters)) {
2292
+ if (!filter) continue;
2293
+ const column = columns.find((c) => c.id === columnId);
2294
+ if (!column) continue;
2295
+ result = result.filter((row) => {
2296
+ const value = column.getValue ? column.getValue(row) : row[columnId];
2297
+ if (filter.includeBlanks && isBlankValue(value)) {
2298
+ return true;
2299
+ }
2300
+ if (filter.excludeBlanks && isBlankValue(value)) {
2301
+ return false;
2302
+ }
2303
+ if (filter.textCondition) {
2304
+ return applyTextCondition(value, filter.textCondition);
2305
+ }
2306
+ if (filter.numberCondition) {
2307
+ return applyNumberCondition(value, filter.numberCondition);
2308
+ }
2309
+ if (filter.dateCondition) {
2310
+ return applyDateCondition(value, filter.dateCondition);
2311
+ }
2312
+ if (filter.selectedValues && filter.selectedValues.length > 0) {
2313
+ const strValue = String(value ?? "");
2314
+ return filter.selectedValues.includes(strValue);
2315
+ }
2316
+ if (filter.min !== void 0 || filter.max !== void 0) {
2317
+ const numValue = typeof value === "number" ? value : parseFloat(value);
2318
+ if (Number.isNaN(numValue)) return false;
2319
+ if (filter.min !== void 0 && numValue < Number(filter.min)) return false;
2320
+ if (filter.max !== void 0 && numValue > Number(filter.max)) return false;
2321
+ }
2322
+ return true;
2323
+ });
2324
+ }
2325
+ if (sortConfig) {
2326
+ const column = columns.find((c) => c.id === sortConfig.columnId);
2327
+ result.sort((a, b) => {
2328
+ const aValue = column?.getValue ? column.getValue(a) : a[sortConfig.columnId];
2329
+ const bValue = column?.getValue ? column.getValue(b) : b[sortConfig.columnId];
2330
+ if (aValue === null || aValue === void 0) return 1;
2331
+ if (bValue === null || bValue === void 0) return -1;
2332
+ if (typeof aValue === "string" && typeof bValue === "string") {
2333
+ return sortConfig.direction === "asc" ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
2334
+ }
2335
+ return sortConfig.direction === "asc" ? aValue < bValue ? -1 : aValue > bValue ? 1 : 0 : aValue > bValue ? -1 : aValue < bValue ? 1 : 0;
2336
+ });
2337
+ }
2338
+ return result;
2339
+ }, [
2340
+ data,
2341
+ filters,
2342
+ sortConfig,
2343
+ columns,
2344
+ serverSide,
2345
+ applyDateCondition,
2346
+ applyNumberCondition,
2347
+ applyTextCondition
2348
+ ]);
2349
+ const handleFilterChange = useCallback3(
2350
+ (columnId, filter) => {
2351
+ const newFilters = { ...filters };
2352
+ if (filter) {
2353
+ newFilters[columnId] = filter;
2354
+ } else {
2355
+ delete newFilters[columnId];
2356
+ }
2357
+ if (controlledFilters === void 0) {
2358
+ setInternalFilters(newFilters);
2359
+ }
2360
+ onFilterChange?.(newFilters);
2361
+ },
2362
+ [filters, onFilterChange, controlledFilters]
2363
+ );
2364
+ const handleSort = useCallback3(
2365
+ (columnId) => {
2366
+ const newSortConfig = sortConfig?.columnId === columnId ? { columnId, direction: sortConfig.direction === "asc" ? "desc" : "asc" } : { columnId, direction: "asc" };
2367
+ if (controlledSortConfig === void 0) {
2368
+ setInternalSortConfig(newSortConfig);
2369
+ }
2370
+ onSortChange?.(newSortConfig);
2371
+ },
2372
+ [sortConfig, onSortChange, controlledSortConfig]
2373
+ );
2374
+ const clearAllFilters = useCallback3(() => {
2375
+ if (controlledFilters === void 0) {
2376
+ setInternalFilters({});
2377
+ }
2378
+ onFilterChange?.({});
2379
+ }, [onFilterChange, controlledFilters]);
2380
+ const hasActiveFilters = Object.keys(filters).length > 0;
2381
+ return {
2382
+ filters,
2383
+ sortConfig,
2384
+ filteredData,
2385
+ activeFilterColumn,
2386
+ setActiveFilterColumn,
2387
+ handleFilterChange,
2388
+ handleSort,
2389
+ clearAllFilters,
2390
+ hasActiveFilters
2391
+ };
2392
+ }
2393
+
2394
+ // src/hooks/useSpreadsheetComments.ts
2395
+ import { useCallback as useCallback4, useState as useState7 } from "react";
2396
+ function useSpreadsheetComments({
2397
+ externalRowComments,
2398
+ onAddRowComment
2399
+ } = {}) {
2400
+ const [rowCommentsInternal, setRowCommentsInternal] = useState7([]);
2401
+ const [commentModalRow, setCommentModalRow] = useState7(null);
2402
+ const [commentText, setCommentText] = useState7("");
2403
+ const [viewCommentsRow, setViewCommentsRow] = useState7(null);
2404
+ const rowComments = externalRowComments || rowCommentsInternal;
2405
+ const getRowComments = useCallback4(
2406
+ (rowId) => {
2407
+ return rowComments.filter((c) => c.rowId === rowId && !c.columnId);
2408
+ },
2409
+ [rowComments]
2410
+ );
2411
+ const getUnresolvedCommentCount = useCallback4(
2412
+ (rowId) => {
2413
+ return rowComments.filter((c) => c.rowId === rowId && !c.columnId && !c.resolved).length;
2414
+ },
2415
+ [rowComments]
2416
+ );
2417
+ const hasComments = useCallback4(
2418
+ (rowId) => {
2419
+ return rowComments.some((c) => c.rowId === rowId && !c.columnId);
2420
+ },
2421
+ [rowComments]
2422
+ );
2423
+ const handleAddRowComment = useCallback4(
2424
+ (rowId) => {
2425
+ if (!commentText.trim()) return;
2426
+ if (onAddRowComment) {
2427
+ onAddRowComment(rowId, commentText);
2428
+ } else {
2429
+ setRowCommentsInternal((prev) => [
2430
+ ...prev,
2431
+ {
2432
+ id: `comment-${Date.now()}`,
2433
+ rowId,
2434
+ text: commentText,
2435
+ timestamp: /* @__PURE__ */ new Date(),
2436
+ resolved: false
2437
+ }
2438
+ ]);
2439
+ }
2440
+ setCommentText("");
2441
+ setCommentModalRow(null);
2442
+ },
2443
+ [commentText, onAddRowComment]
2444
+ );
2445
+ const handleToggleCommentResolved = useCallback4((commentId) => {
2446
+ setRowCommentsInternal(
2447
+ (prev) => prev.map((c) => c.id === commentId ? { ...c, resolved: !c.resolved } : c)
2448
+ );
2449
+ }, []);
2450
+ return {
2451
+ // Comments data
2452
+ rowComments,
2453
+ getRowComments,
2454
+ getUnresolvedCommentCount,
2455
+ // Add comment modal state
2456
+ commentModalRow,
2457
+ setCommentModalRow,
2458
+ commentText,
2459
+ setCommentText,
2460
+ // View comments modal state
2461
+ viewCommentsRow,
2462
+ setViewCommentsRow,
2463
+ // Actions
2464
+ handleAddRowComment,
2465
+ handleToggleCommentResolved,
2466
+ // Utility
2467
+ hasComments
2468
+ };
2469
+ }
2470
+
2471
+ // src/hooks/useSpreadsheetUndoRedo.ts
2472
+ import { useCallback as useCallback5, useState as useState8 } from "react";
2473
+ function useSpreadsheetUndoRedo({
2474
+ enabled = true,
2475
+ maxStackSize = 50,
2476
+ autoSave = true
2477
+ }) {
2478
+ const [undoStack, setUndoStack] = useState8([]);
2479
+ const [redoStack, setRedoStack] = useState8([]);
2480
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState8(false);
2481
+ const [saveStatus, setSaveStatus] = useState8("saved");
2482
+ const pushToUndoStack = useCallback5(
2483
+ (snapshot) => {
2484
+ if (!enabled) return;
2485
+ setUndoStack((prev) => {
2486
+ const newStack = [...prev, snapshot];
2487
+ if (newStack.length > maxStackSize) {
2488
+ return newStack.slice(-maxStackSize);
2489
+ }
2490
+ return newStack;
2491
+ });
2492
+ setRedoStack([]);
2493
+ },
2494
+ [enabled, maxStackSize]
2495
+ );
2496
+ const handleUndo = useCallback5(() => {
2497
+ if (!enabled || undoStack.length === 0) return null;
2498
+ const previousSnapshot = undoStack[undoStack.length - 1];
2499
+ setUndoStack((prev) => prev.slice(0, -1));
2500
+ setRedoStack((prev) => {
2501
+ const newStack = [...prev, previousSnapshot];
2502
+ if (newStack.length > maxStackSize) {
2503
+ return newStack.slice(-maxStackSize);
2504
+ }
2505
+ return newStack;
2506
+ });
2507
+ return previousSnapshot;
2508
+ }, [enabled, undoStack, maxStackSize]);
2509
+ const handleRedo = useCallback5(() => {
2510
+ if (!enabled || redoStack.length === 0) return null;
2511
+ const nextSnapshot = redoStack[redoStack.length - 1];
2512
+ setRedoStack((prev) => prev.slice(0, -1));
2513
+ setUndoStack((prev) => {
2514
+ const newStack = [...prev, nextSnapshot];
2515
+ if (newStack.length > maxStackSize) {
2516
+ return newStack.slice(-maxStackSize);
2517
+ }
2518
+ return newStack;
2519
+ });
2520
+ return nextSnapshot;
2521
+ }, [enabled, redoStack, maxStackSize]);
2522
+ const handleSave = useCallback5(() => {
2523
+ if (!hasUnsavedChanges) return;
2524
+ setSaveStatus("saving");
2525
+ setTimeout(() => {
2526
+ setSaveStatus("saved");
2527
+ setHasUnsavedChanges(false);
2528
+ }, 500);
2529
+ }, [hasUnsavedChanges]);
2530
+ const markAsChanged = useCallback5(() => {
2531
+ setHasUnsavedChanges(true);
2532
+ if (autoSave) {
2533
+ setSaveStatus("saving");
2534
+ setTimeout(() => setSaveStatus("saved"), 500);
2535
+ } else {
2536
+ setSaveStatus("unsaved");
2537
+ }
2538
+ }, [autoSave]);
2539
+ const markAsSaved = useCallback5(() => {
2540
+ setHasUnsavedChanges(false);
2541
+ setSaveStatus("saved");
2542
+ }, []);
2543
+ const clearStacks = useCallback5(() => {
2544
+ setUndoStack([]);
2545
+ setRedoStack([]);
2546
+ }, []);
2547
+ return {
2548
+ // State
2549
+ undoStack,
2550
+ redoStack,
2551
+ hasUnsavedChanges,
2552
+ saveStatus,
2553
+ // Computed
2554
+ canUndo: enabled && undoStack.length > 0,
2555
+ canRedo: enabled && redoStack.length > 0,
2556
+ undoCount: undoStack.length,
2557
+ redoCount: redoStack.length,
2558
+ // Actions
2559
+ handleUndo,
2560
+ handleRedo,
2561
+ handleSave,
2562
+ pushToUndoStack,
2563
+ clearStacks,
2564
+ // Change tracking
2565
+ markAsChanged,
2566
+ markAsSaved
2567
+ };
2568
+ }
2569
+
2570
+ // src/hooks/useSpreadsheetKeyboardShortcuts.ts
2571
+ import { useEffect as useEffect4, useState as useState9 } from "react";
2572
+ function useSpreadsheetKeyboardShortcuts({
2573
+ onUndo,
2574
+ onRedo,
2575
+ onEscape,
2576
+ customShortcuts = [],
2577
+ enabled = true
2578
+ } = {}) {
2579
+ const [showKeyboardShortcuts, setShowKeyboardShortcuts] = useState9(false);
2580
+ const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPod|iPad/.test(navigator.platform);
2581
+ const modifierKey = isMac ? "\u2318" : "Ctrl";
2582
+ useEffect4(() => {
2583
+ if (!enabled) return;
2584
+ const handleKeyDown = (event) => {
2585
+ if (event.key === "Escape") {
2586
+ if (showKeyboardShortcuts) {
2587
+ setShowKeyboardShortcuts(false);
2588
+ } else {
2589
+ onEscape?.();
2590
+ }
2591
+ return;
2592
+ }
2593
+ if ((event.metaKey || event.ctrlKey) && event.key === "/") {
2594
+ event.preventDefault();
2595
+ setShowKeyboardShortcuts((prev) => !prev);
2596
+ return;
2597
+ }
2598
+ if ((event.metaKey || event.ctrlKey) && event.key === "z" && !event.shiftKey) {
2599
+ event.preventDefault();
2600
+ onUndo?.();
2601
+ return;
2602
+ }
2603
+ if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.key === "z") {
2604
+ event.preventDefault();
2605
+ onRedo?.();
2606
+ return;
2607
+ }
2608
+ for (const shortcut of customShortcuts) {
2609
+ const modifiersMatch = (!shortcut.modifiers?.meta || event.metaKey) && (!shortcut.modifiers?.ctrl || event.ctrlKey) && (!shortcut.modifiers?.shift || event.shiftKey) && (!shortcut.modifiers?.alt || event.altKey);
2610
+ if (event.key === shortcut.key && modifiersMatch) {
2611
+ event.preventDefault();
2612
+ shortcut.handler();
2613
+ return;
2614
+ }
2615
+ }
2616
+ };
2617
+ document.addEventListener("keydown", handleKeyDown);
2618
+ return () => document.removeEventListener("keydown", handleKeyDown);
2619
+ }, [enabled, showKeyboardShortcuts, onUndo, onRedo, onEscape, customShortcuts]);
2620
+ const shortcuts = {
2621
+ general: [
2622
+ { label: "Show keyboard shortcuts", keys: [modifierKey, "/"] },
2623
+ { label: "Close modal / Clear selection", keys: ["Escape"] }
2624
+ ],
2625
+ rowSelection: [
2626
+ { label: "Select single row", keys: ["Click row number"] },
2627
+ { label: "Select multiple rows", keys: [modifierKey, "Click row number"] },
2628
+ { label: "Select range of rows", keys: ["Shift", "Click row number"] }
2629
+ ],
2630
+ editing: [
2631
+ { label: "Undo", keys: [modifierKey, "Z"] },
2632
+ { label: "Redo", keys: [modifierKey, "Shift", "Z"] },
2633
+ { label: "Confirm cell edit", keys: ["Enter"] },
2634
+ { label: "Cancel cell edit", keys: ["Escape"] }
2635
+ ],
2636
+ rowActions: [
2637
+ { label: "Duplicate row", description: "Click duplicate icon" },
2638
+ { label: "Highlight row", description: "Click highlight icon" },
2639
+ { label: "Add row comment", description: "Click comment icon" }
2640
+ ]
2641
+ };
2642
+ return {
2643
+ showKeyboardShortcuts,
2644
+ setShowKeyboardShortcuts,
2645
+ isMac,
2646
+ modifierKey,
2647
+ shortcuts
2648
+ };
2649
+ }
2650
+
2651
+ // src/components/Spreadsheet.tsx
2652
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
2653
+ function Spreadsheet({
2654
+ data,
2655
+ columns,
2656
+ columnGroups,
2657
+ getRowId,
2658
+ onCellEdit,
2659
+ onSelectionChange,
2660
+ onSortChange,
2661
+ onFilterChange,
2662
+ onRowClick,
2663
+ onRowDoubleClick,
2664
+ onRowClone,
2665
+ onAddRowComment,
2666
+ onRowHighlight,
2667
+ showToolbar = true,
2668
+ showPagination = true,
2669
+ showRowIndex = true,
2670
+ enableRowSelection = true,
2671
+ enableCellEditing = true,
2672
+ enableComments = true,
2673
+ enableHighlighting = true,
2674
+ enableUndoRedo = true,
2675
+ defaultPageSize = 25,
2676
+ pageSizeOptions = [25, 50, 100, 200],
2677
+ defaultZoom = 100,
2678
+ autoSave = true,
2679
+ compactMode = false,
2680
+ isLoading = false,
2681
+ className,
2682
+ emptyMessage = "No data available",
2683
+ rowHighlights: externalRowHighlights,
2684
+ rowComments: externalRowComments,
2685
+ rowActions,
2686
+ // Server-side mode props
2687
+ serverSide = false,
2688
+ totalItems,
2689
+ currentPage: controlledCurrentPage,
2690
+ pageSize: controlledPageSize,
2691
+ onPageChange,
2692
+ sortConfig: controlledSortConfig,
2693
+ filters: controlledFilters
2694
+ }) {
2695
+ const {
2696
+ filters,
2697
+ sortConfig,
2698
+ filteredData,
2699
+ activeFilterColumn,
2700
+ setActiveFilterColumn,
2701
+ handleFilterChange,
2702
+ handleSort,
2703
+ clearAllFilters,
2704
+ hasActiveFilters
2705
+ } = useSpreadsheetFiltering({
2706
+ data,
2707
+ columns,
2708
+ onFilterChange,
2709
+ onSortChange,
2710
+ serverSide,
2711
+ controlledFilters,
2712
+ controlledSortConfig
2713
+ });
2714
+ const {
2715
+ getCellHighlight,
2716
+ handleCellHighlightToggle,
2717
+ getRowHighlight,
2718
+ handleRowHighlightToggle,
2719
+ getColumnHighlight,
2720
+ handleColumnHighlightToggle,
2721
+ highlightPickerRow,
2722
+ setHighlightPickerRow,
2723
+ highlightPickerColumn,
2724
+ setHighlightPickerColumn
2725
+ } = useSpreadsheetHighlighting({
2726
+ externalRowHighlights,
2727
+ onRowHighlight
2728
+ });
2729
+ const {
2730
+ pinnedColumns,
2731
+ isRowIndexPinned,
2732
+ collapsedGroups,
2733
+ visibleColumns,
2734
+ handleTogglePin,
2735
+ handleToggleRowIndexPin,
2736
+ handleToggleGroupCollapse,
2737
+ getColumnLeftOffset,
2738
+ isColumnPinned,
2739
+ getColumnPinSide
2740
+ } = useSpreadsheetPinning({
2741
+ columns,
2742
+ columnGroups,
2743
+ showRowIndex
2744
+ });
2745
+ const {
2746
+ getRowComments,
2747
+ getUnresolvedCommentCount,
2748
+ hasComments,
2749
+ commentModalRow,
2750
+ setCommentModalRow,
2751
+ commentText,
2752
+ setCommentText,
2753
+ viewCommentsRow,
2754
+ setViewCommentsRow,
2755
+ handleAddRowComment,
2756
+ handleToggleCommentResolved
2757
+ } = useSpreadsheetComments({
2758
+ externalRowComments,
2759
+ onAddRowComment
2760
+ });
2761
+ const {
2762
+ canUndo,
2763
+ canRedo,
2764
+ undoCount,
2765
+ redoCount,
2766
+ hasUnsavedChanges,
2767
+ saveStatus,
2768
+ handleUndo: popUndoEntry,
2769
+ handleRedo: popRedoEntry,
2770
+ handleSave,
2771
+ pushToUndoStack,
2772
+ markAsChanged
2773
+ } = useSpreadsheetUndoRedo({
2774
+ enabled: enableUndoRedo,
2775
+ autoSave
2776
+ });
2777
+ const [selectedRows, setSelectedRows] = useState10(/* @__PURE__ */ new Set());
2778
+ const [lastSelectedRow, setLastSelectedRow] = useState10(null);
2779
+ const [focusedCell, setFocusedCell] = useState10(null);
2780
+ const [editingCell, setEditingCell] = useState10(null);
2781
+ const [editValue, setEditValue] = useState10("");
2782
+ const [hoveredRow, setHoveredRow] = useState10(null);
2783
+ const [internalCurrentPage, setInternalCurrentPage] = useState10(1);
2784
+ const [internalPageSize, setInternalPageSize] = useState10(defaultPageSize);
2785
+ const [zoom, setZoom] = useState10(defaultZoom);
2786
+ const currentPage = controlledCurrentPage ?? internalCurrentPage;
2787
+ const pageSize = controlledPageSize ?? internalPageSize;
2788
+ const handlePageChange = useCallback6(
2789
+ (newPage) => {
2790
+ if (controlledCurrentPage === void 0) {
2791
+ setInternalCurrentPage(newPage);
2792
+ }
2793
+ onPageChange?.(newPage, pageSize);
2794
+ },
2795
+ [controlledCurrentPage, onPageChange, pageSize]
2796
+ );
2797
+ const handlePageSizeChange = useCallback6(
2798
+ (newPageSize) => {
2799
+ if (controlledPageSize === void 0) {
2800
+ setInternalPageSize(newPageSize);
2801
+ }
2802
+ if (controlledCurrentPage === void 0) {
2803
+ setInternalCurrentPage(1);
2804
+ }
2805
+ onPageChange?.(1, newPageSize);
2806
+ },
2807
+ [controlledPageSize, controlledCurrentPage, onPageChange]
2808
+ );
2809
+ const [showSettingsModal, setShowSettingsModal] = useState10(false);
2810
+ const [spreadsheetSettings, setSpreadsheetSettings] = useState10({
2811
+ defaultPinnedColumns: [],
2812
+ defaultSort: null,
2813
+ defaultPageSize,
2814
+ defaultZoom,
2815
+ autoSave,
2816
+ compactView: compactMode,
2817
+ showRowIndex,
2818
+ pinRowIndex: false,
2819
+ rowIndexHighlightColor: void 0
2820
+ });
2821
+ const handleEscapeCallback = useCallback6(() => {
2822
+ if (commentModalRow !== null) {
2823
+ setCommentModalRow(null);
2824
+ } else if (viewCommentsRow !== null) {
2825
+ setViewCommentsRow(null);
2826
+ } else if (highlightPickerRow !== null) {
2827
+ setHighlightPickerRow(null);
2828
+ } else if (highlightPickerColumn !== null) {
2829
+ setHighlightPickerColumn(null);
2830
+ } else {
2831
+ setSelectedRows(/* @__PURE__ */ new Set());
2832
+ setLastSelectedRow(null);
2833
+ setFocusedCell(null);
2834
+ setEditingCell(null);
2835
+ }
2836
+ }, [
2837
+ commentModalRow,
2838
+ setCommentModalRow,
2839
+ viewCommentsRow,
2840
+ setViewCommentsRow,
2841
+ highlightPickerRow,
2842
+ setHighlightPickerRow,
2843
+ highlightPickerColumn,
2844
+ setHighlightPickerColumn
2845
+ ]);
2846
+ const applyUndo = useCallback6(() => {
2847
+ const entry = popUndoEntry();
2848
+ if (!entry || !onCellEdit) return;
2849
+ if (entry.type === "cell-edit") {
2850
+ onCellEdit(entry.rowId, entry.columnId, entry.previousValue);
2851
+ markAsChanged();
2852
+ }
2853
+ }, [popUndoEntry, onCellEdit, markAsChanged]);
2854
+ const applyRedo = useCallback6(() => {
2855
+ const entry = popRedoEntry();
2856
+ if (!entry || !onCellEdit) return;
2857
+ if (entry.type === "cell-edit") {
2858
+ onCellEdit(entry.rowId, entry.columnId, entry.nextValue);
2859
+ markAsChanged();
2860
+ }
2861
+ }, [popRedoEntry, onCellEdit, markAsChanged]);
2862
+ const { showKeyboardShortcuts, setShowKeyboardShortcuts, shortcuts } = useSpreadsheetKeyboardShortcuts({
2863
+ onUndo: applyUndo,
2864
+ onRedo: applyRedo,
2865
+ onEscape: handleEscapeCallback,
2866
+ enabled: true
2867
+ });
2868
+ const effectiveShowRowIndex = spreadsheetSettings.showRowIndex !== false;
2869
+ const rowIndexHighlightColor = getColumnHighlight(ROW_INDEX_COLUMN_ID);
2870
+ const tableRef = useRef3(null);
2871
+ const effectiveTotalItems = serverSide ? totalItems ?? data.length : filteredData.length;
2872
+ const paginatedData = useMemo3(() => {
2873
+ if (serverSide) {
2874
+ return filteredData;
2875
+ }
2876
+ const startIndex = (currentPage - 1) * pageSize;
2877
+ return filteredData.slice(startIndex, startIndex + pageSize);
2878
+ }, [filteredData, currentPage, pageSize, serverSide]);
2879
+ const totalPages = Math.max(1, Math.ceil(effectiveTotalItems / pageSize));
2880
+ useEffect5(() => {
2881
+ if (!serverSide && currentPage > totalPages) {
2882
+ setInternalCurrentPage(1);
2883
+ }
2884
+ }, [totalPages, currentPage, serverSide]);
2885
+ const handleRowSelect = useCallback6(
2886
+ (rowId, event) => {
2887
+ if (!enableRowSelection) return;
2888
+ event.stopPropagation();
2889
+ if (event.shiftKey && lastSelectedRow !== null) {
2890
+ const currentIndex = filteredData.findIndex((r) => getRowId(r) === rowId);
2891
+ const lastIndex = filteredData.findIndex((r) => getRowId(r) === lastSelectedRow);
2892
+ if (currentIndex !== -1 && lastIndex !== -1) {
2893
+ const start = Math.min(currentIndex, lastIndex);
2894
+ const end = Math.max(currentIndex, lastIndex);
2895
+ const newSelection = new Set(selectedRows);
2896
+ for (let i = start; i <= end; i++) {
2897
+ newSelection.add(getRowId(filteredData[i]));
2898
+ }
2899
+ setSelectedRows(newSelection);
2900
+ onSelectionChange?.(Array.from(newSelection));
2901
+ }
2902
+ } else if (event.metaKey || event.ctrlKey) {
2903
+ const newSelection = new Set(selectedRows);
2904
+ if (newSelection.has(rowId)) {
2905
+ newSelection.delete(rowId);
2906
+ } else {
2907
+ newSelection.add(rowId);
2908
+ }
2909
+ setSelectedRows(newSelection);
2910
+ setLastSelectedRow(rowId);
2911
+ onSelectionChange?.(Array.from(newSelection));
2912
+ } else {
2913
+ if (selectedRows.has(rowId) && selectedRows.size === 1) {
2914
+ setSelectedRows(/* @__PURE__ */ new Set());
2915
+ setLastSelectedRow(null);
2916
+ onSelectionChange?.([]);
2917
+ } else {
2918
+ setSelectedRows(/* @__PURE__ */ new Set([rowId]));
2919
+ setLastSelectedRow(rowId);
2920
+ onSelectionChange?.([rowId]);
2921
+ }
2922
+ }
2923
+ },
2924
+ [
2925
+ enableRowSelection,
2926
+ lastSelectedRow,
2927
+ filteredData,
2928
+ selectedRows,
2929
+ getRowId,
2930
+ onSelectionChange
2931
+ ]
2932
+ );
2933
+ const handleCellClick = useCallback6(
2934
+ (rowId, columnId, event) => {
2935
+ event.stopPropagation();
2936
+ setFocusedCell({ rowId, columnId });
2937
+ const column = (columns || []).find((c) => c.id === columnId);
2938
+ if (column?.editable && enableCellEditing) {
2939
+ const row = (data || []).find((r) => getRowId(r) === rowId);
2940
+ if (row) {
2941
+ const value = column.getValue ? column.getValue(row) : row[columnId];
2942
+ setEditingCell({ rowId, columnId });
2943
+ setEditValue(value);
2944
+ }
2945
+ }
2946
+ },
2947
+ [columns, data, getRowId, enableCellEditing]
2948
+ );
2949
+ const handleCellChange = useCallback6(
2950
+ (rowId, columnId, newValue) => {
2951
+ const row = data.find((r) => getRowId(r) === rowId);
2952
+ const previousValue = row ? row[columnId] : void 0;
2953
+ if (row && Object.is(previousValue, newValue)) {
2954
+ return;
2955
+ }
2956
+ if (row && enableUndoRedo) {
2957
+ pushToUndoStack({
2958
+ type: "cell-edit",
2959
+ rowId,
2960
+ columnId,
2961
+ previousValue,
2962
+ nextValue: newValue
2963
+ });
2964
+ }
2965
+ onCellEdit?.(rowId, columnId, newValue);
2966
+ markAsChanged();
2967
+ },
2968
+ [data, getRowId, enableUndoRedo, onCellEdit, pushToUndoStack, markAsChanged]
2969
+ );
2970
+ const handleConfirmEdit = useCallback6(() => {
2971
+ if (editingCell) {
2972
+ handleCellChange(editingCell.rowId, editingCell.columnId, editValue);
2973
+ setEditingCell(null);
2974
+ }
2975
+ }, [editingCell, editValue, handleCellChange]);
2976
+ const handleCancelEdit = useCallback6(() => {
2977
+ setEditingCell(null);
2978
+ setEditValue("");
2979
+ }, []);
2980
+ const handleRowClone = useCallback6(
2981
+ (row, rowId) => {
2982
+ onRowClone?.(row, rowId);
2983
+ },
2984
+ [onRowClone]
2985
+ );
2986
+ const handleRowIndexHighlightClick = useCallback6(() => {
2987
+ setHighlightPickerColumn(ROW_INDEX_COLUMN_ID);
2988
+ }, [setHighlightPickerColumn]);
2989
+ return /* @__PURE__ */ jsxs11("div", { className: cn("flex flex-col h-full bg-white", className), children: [
2990
+ showToolbar && /* @__PURE__ */ jsx11(
2991
+ SpreadsheetToolbar,
2992
+ {
2993
+ zoom,
2994
+ canUndo,
2995
+ canRedo,
2996
+ undoCount,
2997
+ redoCount,
2998
+ selectedRowCount: selectedRows.size,
2999
+ hasUnsavedChanges,
3000
+ saveStatus,
3001
+ autoSave,
3002
+ hasActiveFilters,
3003
+ onClearFilters: clearAllFilters,
3004
+ onZoomIn: () => setZoom((z) => Math.min(z + 10, 200)),
3005
+ onZoomOut: () => setZoom((z) => Math.max(z - 10, 50)),
3006
+ onZoomReset: () => setZoom(100),
3007
+ onUndo: applyUndo,
3008
+ onRedo: applyRedo,
3009
+ onClearSelection: () => {
3010
+ setSelectedRows(/* @__PURE__ */ new Set());
3011
+ setLastSelectedRow(null);
3012
+ onSelectionChange?.([]);
3013
+ },
3014
+ onSave: handleSave,
3015
+ onSettings: () => setShowSettingsModal(true),
3016
+ onShowShortcuts: () => setShowKeyboardShortcuts(true),
3017
+ onExport: () => console.log("Export clicked")
3018
+ }
3019
+ ),
3020
+ /* @__PURE__ */ jsx11("div", { ref: tableRef, className: "flex-1 overflow-auto border border-gray-200 rounded", children: /* @__PURE__ */ jsx11(
3021
+ "div",
3022
+ {
3023
+ style: {
3024
+ transform: `scale(${zoom / 100})`,
3025
+ transformOrigin: "top left",
3026
+ width: `${100 / (zoom / 100)}%`
3027
+ },
3028
+ children: /* @__PURE__ */ jsxs11("table", { className: "w-full border-separate border-spacing-0 text-xs", children: [
3029
+ /* @__PURE__ */ jsxs11("thead", { children: [
3030
+ columnGroups && /* @__PURE__ */ jsxs11("tr", { children: [
3031
+ effectiveShowRowIndex && /* @__PURE__ */ jsx11(
3032
+ RowIndexColumnHeader,
3033
+ {
3034
+ enableHighlighting,
3035
+ highlightColor: rowIndexHighlightColor,
3036
+ isPinned: isRowIndexPinned,
3037
+ onHighlightClick: handleRowIndexHighlightClick,
3038
+ onPinClick: handleToggleRowIndexPin,
3039
+ hasColumnGroups: true
3040
+ }
3041
+ ),
3042
+ columnGroups.map((group) => {
3043
+ const groupColumns = (columns || []).filter(
3044
+ (c) => group.columns.includes(c.id)
3045
+ );
3046
+ const isCollapsed = collapsedGroups.has(group.id);
3047
+ const visibleGroupColumns = isCollapsed ? groupColumns.filter((c) => pinnedColumns.has(c.id)) : groupColumns;
3048
+ const colSpan = Math.max(
3049
+ 1,
3050
+ visibleGroupColumns.length + (isCollapsed ? 1 : 0)
3051
+ );
3052
+ return /* @__PURE__ */ jsx11(
3053
+ "th",
3054
+ {
3055
+ colSpan,
3056
+ className: cn(
3057
+ "border border-gray-200 px-2 py-1.5 text-center font-bold text-gray-700",
3058
+ group.collapsible && "cursor-pointer hover:bg-gray-100"
3059
+ ),
3060
+ style: {
3061
+ backgroundColor: group.headerColor || "rgb(243 244 246)"
3062
+ },
3063
+ onClick: () => group.collapsible && handleToggleGroupCollapse(group.id),
3064
+ children: /* @__PURE__ */ jsxs11("div", { className: "flex items-center justify-center gap-1", children: [
3065
+ group.collapsible && (isCollapsed ? /* @__PURE__ */ jsx11(HiChevronRight, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx11(HiChevronDown, { className: "h-3 w-3" })),
3066
+ /* @__PURE__ */ jsx11("span", { children: group.label })
3067
+ ] })
3068
+ },
3069
+ group.id
3070
+ );
3071
+ })
3072
+ ] }),
3073
+ /* @__PURE__ */ jsxs11("tr", { children: [
3074
+ effectiveShowRowIndex && !columnGroups && /* @__PURE__ */ jsx11(
3075
+ RowIndexColumnHeader,
3076
+ {
3077
+ enableHighlighting,
3078
+ highlightColor: rowIndexHighlightColor,
3079
+ isPinned: isRowIndexPinned,
3080
+ onHighlightClick: handleRowIndexHighlightClick,
3081
+ onPinClick: handleToggleRowIndexPin,
3082
+ hasColumnGroups: false
3083
+ }
3084
+ ),
3085
+ visibleColumns.map((column) => {
3086
+ const isPinnedLeft = isColumnPinned(column.id) && getColumnPinSide(column.id) === "left";
3087
+ return /* @__PURE__ */ jsx11(
3088
+ SpreadsheetHeader,
3089
+ {
3090
+ column,
3091
+ sortConfig,
3092
+ hasActiveFilter: !!filters[column.id],
3093
+ isPinned: isColumnPinned(column.id),
3094
+ pinSide: getColumnPinSide(column.id),
3095
+ leftOffset: isPinnedLeft ? getColumnLeftOffset(column.id) : 0,
3096
+ highlightColor: getColumnHighlight(column.id),
3097
+ compactMode,
3098
+ onClick: () => handleSort(column.id),
3099
+ onFilterClick: () => setActiveFilterColumn(
3100
+ activeFilterColumn === column.id ? null : column.id
3101
+ ),
3102
+ onPinClick: () => handleTogglePin(column.id),
3103
+ onHighlightClick: enableHighlighting ? () => setHighlightPickerColumn(column.id) : void 0,
3104
+ children: activeFilterColumn === column.id && /* @__PURE__ */ jsx11(
3105
+ SpreadsheetFilterDropdown,
3106
+ {
3107
+ column,
3108
+ filter: filters[column.id],
3109
+ onFilterChange: (filter) => handleFilterChange(column.id, filter),
3110
+ onClose: () => setActiveFilterColumn(null)
3111
+ }
3112
+ )
3113
+ },
3114
+ column.id
3115
+ );
3116
+ })
3117
+ ] })
3118
+ ] }),
3119
+ /* @__PURE__ */ jsx11("tbody", { children: isLoading ? /* @__PURE__ */ jsx11("tr", { children: /* @__PURE__ */ jsx11(
3120
+ "td",
3121
+ {
3122
+ colSpan: visibleColumns.length + (effectiveShowRowIndex ? 1 : 0),
3123
+ className: "text-center py-8 text-gray-500",
3124
+ children: /* @__PURE__ */ jsxs11("div", { className: "flex items-center justify-center gap-2", children: [
3125
+ /* @__PURE__ */ jsx11("div", { className: "w-4 h-4 border-2 border-blue-600 border-t-transparent rounded-full animate-spin" }),
3126
+ "Loading..."
3127
+ ] })
3128
+ }
3129
+ ) }) : paginatedData.length === 0 ? /* @__PURE__ */ jsx11("tr", { children: /* @__PURE__ */ jsx11(
3130
+ "td",
3131
+ {
3132
+ colSpan: visibleColumns.length + (effectiveShowRowIndex ? 1 : 0),
3133
+ className: "text-center py-8 text-gray-500",
3134
+ children: emptyMessage
3135
+ }
3136
+ ) }) : paginatedData.map((row, rowIndex) => {
3137
+ const rowId = getRowId(row);
3138
+ const isRowSelected = selectedRows.has(rowId);
3139
+ const isRowHovered = hoveredRow === rowId;
3140
+ const rowHighlight = getRowHighlight(rowId);
3141
+ const rowCommentsList = getRowComments(rowId);
3142
+ const hasRowComments = hasComments(rowId);
3143
+ const unresolvedCount = getUnresolvedCommentCount(rowId);
3144
+ const displayIndex = rowIndex + 1 + (currentPage - 1) * pageSize;
3145
+ return /* @__PURE__ */ jsxs11(
3146
+ "tr",
3147
+ {
3148
+ onMouseEnter: () => setHoveredRow(rowId),
3149
+ onMouseLeave: () => setHoveredRow(null),
3150
+ onClick: () => {
3151
+ onRowClick?.(row, rowIndex);
3152
+ },
3153
+ onDoubleClick: () => onRowDoubleClick?.(row, rowIndex),
3154
+ className: "transition-colors",
3155
+ children: [
3156
+ effectiveShowRowIndex && /* @__PURE__ */ jsxs11(
3157
+ "td",
3158
+ {
3159
+ onClick: (e) => handleRowSelect(rowId, e),
3160
+ className: cn(
3161
+ "border border-gray-200 text-center font-semibold cursor-pointer group relative",
3162
+ isRowIndexPinned ? "z-20" : "z-0",
3163
+ isRowSelected && "bg-blue-100",
3164
+ !isRowSelected && rowHighlight && "",
3165
+ isRowHovered && !isRowSelected && !rowHighlight && "bg-gray-50"
3166
+ ),
3167
+ style: {
3168
+ backgroundColor: rowHighlight?.color || (isRowSelected ? "#dbeafe" : isRowHovered ? "#f9fafb" : rowIndexHighlightColor || "white"),
3169
+ minWidth: `${ROW_INDEX_COLUMN_WIDTH}px`,
3170
+ width: `${ROW_INDEX_COLUMN_WIDTH}px`,
3171
+ ...isRowIndexPinned && {
3172
+ position: "sticky",
3173
+ left: 0
3174
+ }
3175
+ },
3176
+ children: [
3177
+ /* @__PURE__ */ jsx11("div", { className: "py-1 px-1", children: displayIndex }),
3178
+ hasRowComments && /* @__PURE__ */ jsxs11(
3179
+ "button",
3180
+ {
3181
+ type: "button",
3182
+ onClick: (e) => {
3183
+ e.stopPropagation();
3184
+ setViewCommentsRow(rowId);
3185
+ },
3186
+ className: "absolute top-0 right-0 p-0.5 hover:bg-gray-200/80 rounded-bl",
3187
+ title: `${rowCommentsList.length} row comment(s) - ${unresolvedCount} unresolved`,
3188
+ children: [
3189
+ /* @__PURE__ */ jsx11(
3190
+ HiChatAlt2,
3191
+ {
3192
+ className: cn(
3193
+ "h-2.5 w-2.5",
3194
+ unresolvedCount > 0 ? "text-amber-500" : "text-gray-400"
3195
+ )
3196
+ }
3197
+ ),
3198
+ unresolvedCount > 0 && /* @__PURE__ */ jsx11("span", { className: "absolute -top-0.5 -right-0.5 bg-amber-500 text-white text-[6px] rounded-full w-2 h-2 flex items-center justify-center", children: unresolvedCount })
3199
+ ]
3200
+ }
3201
+ ),
3202
+ /* @__PURE__ */ jsxs11("div", { className: "absolute inset-x-0 bottom-0 opacity-0 group-hover:opacity-100 flex items-center justify-center gap-0.5 transition-opacity bg-white/90 py-0.5 border-t border-gray-100", children: [
3203
+ onRowClone && /* @__PURE__ */ jsx11(
3204
+ "button",
3205
+ {
3206
+ type: "button",
3207
+ onClick: (e) => {
3208
+ e.stopPropagation();
3209
+ handleRowClone(row, rowId);
3210
+ },
3211
+ className: "p-0.5 hover:bg-gray-200 rounded",
3212
+ title: "Duplicate row",
3213
+ children: /* @__PURE__ */ jsx11(HiDuplicate, { className: "h-2.5 w-2.5 text-gray-500" })
3214
+ }
3215
+ ),
3216
+ enableHighlighting && /* @__PURE__ */ jsx11(
3217
+ "button",
3218
+ {
3219
+ type: "button",
3220
+ onClick: (e) => {
3221
+ e.stopPropagation();
3222
+ setHighlightPickerRow(rowId);
3223
+ },
3224
+ className: "p-0.5 hover:bg-gray-200 rounded",
3225
+ title: "Highlight row",
3226
+ children: /* @__PURE__ */ jsx11(
3227
+ HiColorSwatch,
3228
+ {
3229
+ className: cn(
3230
+ "h-2.5 w-2.5",
3231
+ rowHighlight ? "text-amber-500" : "text-gray-500"
3232
+ )
3233
+ }
3234
+ )
3235
+ }
3236
+ ),
3237
+ enableComments && /* @__PURE__ */ jsx11(
3238
+ "button",
3239
+ {
3240
+ type: "button",
3241
+ onClick: (e) => {
3242
+ e.stopPropagation();
3243
+ setCommentModalRow(rowId);
3244
+ },
3245
+ className: "p-0.5 hover:bg-gray-200 rounded",
3246
+ title: "Add row comment",
3247
+ children: /* @__PURE__ */ jsx11(HiAnnotation, { className: "h-2.5 w-2.5 text-gray-500" })
3248
+ }
3249
+ ),
3250
+ rowActions?.map((action) => {
3251
+ if (action.visible && !action.visible(row))
3252
+ return null;
3253
+ return /* @__PURE__ */ jsx11(
3254
+ "button",
3255
+ {
3256
+ type: "button",
3257
+ onClick: (e) => {
3258
+ e.stopPropagation();
3259
+ action.onClick(row, rowId);
3260
+ },
3261
+ className: cn(
3262
+ "p-0.5 hover:bg-gray-200 rounded",
3263
+ action.className
3264
+ ),
3265
+ title: action.tooltip,
3266
+ children: action.icon
3267
+ },
3268
+ action.id
3269
+ );
3270
+ })
3271
+ ] })
3272
+ ]
3273
+ }
3274
+ ),
3275
+ visibleColumns.map((column) => {
3276
+ const value = column.getValue ? column.getValue(row) : row[column.id];
3277
+ const isEditing = editingCell?.rowId === rowId && editingCell?.columnId === column.id;
3278
+ const isFocused = focusedCell?.rowId === rowId && focusedCell?.columnId === column.id;
3279
+ const cellOrRowOrColumnHighlight = getCellHighlight(rowId, column.id) || rowHighlight?.color || getColumnHighlight(column.id);
3280
+ const isColPinned = isColumnPinned(column.id);
3281
+ const colPinSide = getColumnPinSide(column.id);
3282
+ return /* @__PURE__ */ jsx11(
3283
+ SpreadsheetCell,
3284
+ {
3285
+ value: isEditing ? editValue : value,
3286
+ column,
3287
+ row,
3288
+ rowIndex,
3289
+ rowId,
3290
+ isEditable: column.editable && enableCellEditing,
3291
+ isEditing,
3292
+ isFocused,
3293
+ isRowSelected,
3294
+ isRowHovered,
3295
+ highlightColor: cellOrRowOrColumnHighlight,
3296
+ compactMode,
3297
+ hasSelectedRows: selectedRows.size > 1,
3298
+ isPinned: isColPinned,
3299
+ pinSide: colPinSide,
3300
+ leftOffset: getColumnLeftOffset(column.id),
3301
+ onClick: (e) => handleCellClick(rowId, column.id, e),
3302
+ onChange: (newValue) => setEditValue(newValue),
3303
+ onConfirm: handleConfirmEdit,
3304
+ onCancel: handleCancelEdit,
3305
+ onHighlight: enableHighlighting ? () => {
3306
+ handleCellHighlightToggle(
3307
+ rowId,
3308
+ column.id
3309
+ );
3310
+ } : void 0
3311
+ },
3312
+ column.id
3313
+ );
3314
+ })
3315
+ ]
3316
+ },
3317
+ rowId
3318
+ );
3319
+ }) })
3320
+ ] })
3321
+ }
3322
+ ) }),
3323
+ showPagination && effectiveTotalItems > 0 && /* @__PURE__ */ jsx11(
3324
+ Pagination,
3325
+ {
3326
+ currentPage,
3327
+ totalPages,
3328
+ pageSize,
3329
+ totalItems: effectiveTotalItems,
3330
+ startItem: (currentPage - 1) * pageSize + 1,
3331
+ endItem: Math.min(currentPage * pageSize, effectiveTotalItems),
3332
+ pageSizeOptions,
3333
+ itemLabel: "rows",
3334
+ onPageChange: handlePageChange,
3335
+ onPageSizeChange: handlePageSizeChange
3336
+ }
3337
+ ),
3338
+ /* @__PURE__ */ jsx11(
3339
+ AddCommentModal,
3340
+ {
3341
+ isOpen: commentModalRow !== null,
3342
+ commentText,
3343
+ onCommentTextChange: setCommentText,
3344
+ onAdd: () => commentModalRow !== null && handleAddRowComment(commentModalRow),
3345
+ onClose: () => {
3346
+ setCommentText("");
3347
+ setCommentModalRow(null);
3348
+ }
3349
+ }
3350
+ ),
3351
+ /* @__PURE__ */ jsx11(
3352
+ ViewCommentsModal,
3353
+ {
3354
+ isOpen: viewCommentsRow !== null,
3355
+ comments: viewCommentsRow !== null ? getRowComments(viewCommentsRow) : [],
3356
+ onToggleResolved: handleToggleCommentResolved,
3357
+ onClose: () => setViewCommentsRow(null)
3358
+ }
3359
+ ),
3360
+ highlightPickerRow !== null && /* @__PURE__ */ jsx11(
3361
+ ColorPickerPopover,
3362
+ {
3363
+ title: "Highlight Row",
3364
+ paletteType: "row",
3365
+ onSelectColor: (color) => handleRowHighlightToggle(highlightPickerRow, color),
3366
+ onClose: () => setHighlightPickerRow(null)
3367
+ }
3368
+ ),
3369
+ highlightPickerColumn !== null && /* @__PURE__ */ jsx11(
3370
+ ColorPickerPopover,
3371
+ {
3372
+ title: highlightPickerColumn === ROW_INDEX_COLUMN_ID ? "Highlight Row Index Column" : `Highlight Column: ${columns.find((c) => c.id === highlightPickerColumn)?.label || ""}`,
3373
+ paletteType: "column",
3374
+ onSelectColor: (color) => handleColumnHighlightToggle(highlightPickerColumn, color),
3375
+ onClose: () => setHighlightPickerColumn(null)
3376
+ }
3377
+ ),
3378
+ /* @__PURE__ */ jsx11(
3379
+ KeyboardShortcutsModal,
3380
+ {
3381
+ isOpen: showKeyboardShortcuts,
3382
+ onClose: () => setShowKeyboardShortcuts(false),
3383
+ shortcuts
3384
+ }
3385
+ ),
3386
+ /* @__PURE__ */ jsx11(
3387
+ SpreadsheetSettingsModal,
3388
+ {
3389
+ isOpen: showSettingsModal,
3390
+ onClose: () => setShowSettingsModal(false),
3391
+ settings: spreadsheetSettings,
3392
+ onSave: (newSettings) => {
3393
+ setSpreadsheetSettings(newSettings);
3394
+ setZoom(newSettings.defaultZoom);
3395
+ if (newSettings.defaultPageSize !== pageSize) {
3396
+ handlePageSizeChange(newSettings.defaultPageSize);
3397
+ }
3398
+ if (newSettings.defaultSort) {
3399
+ }
3400
+ },
3401
+ columns: columns || [],
3402
+ title: "Spreadsheet Settings",
3403
+ pageSizeOptions
3404
+ }
3405
+ )
3406
+ ] });
3407
+ }
3408
+ Spreadsheet.displayName = "Spreadsheet";
3409
+ export {
3410
+ Spreadsheet,
3411
+ SpreadsheetCell,
3412
+ SpreadsheetFilterDropdown,
3413
+ SpreadsheetHeader,
3414
+ SpreadsheetSettingsModal,
3415
+ SpreadsheetToolbar
3416
+ };
3417
+ //# sourceMappingURL=index.mjs.map