@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.
- package/.storybook/main.ts +27 -0
- package/.storybook/preview.tsx +28 -0
- package/.turbo/turbo-build.log +22 -0
- package/CHANGELOG.md +9 -0
- package/biome.json +3 -0
- package/dist/index.d.mts +687 -0
- package/dist/index.d.ts +687 -0
- package/dist/index.js +3459 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +3417 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +51 -0
- package/postcss.config.js +5 -0
- package/src/components/ColorPickerPopover.tsx +73 -0
- package/src/components/ColumnHeaderActions.tsx +139 -0
- package/src/components/CommentModals.tsx +137 -0
- package/src/components/KeyboardShortcutsModal.tsx +119 -0
- package/src/components/RowIndexColumnHeader.tsx +70 -0
- package/src/components/Spreadsheet.stories.tsx +1146 -0
- package/src/components/Spreadsheet.tsx +1005 -0
- package/src/components/SpreadsheetCell.tsx +341 -0
- package/src/components/SpreadsheetFilterDropdown.tsx +341 -0
- package/src/components/SpreadsheetHeader.tsx +111 -0
- package/src/components/SpreadsheetSettingsModal.tsx +555 -0
- package/src/components/SpreadsheetToolbar.tsx +346 -0
- package/src/hooks/index.ts +40 -0
- package/src/hooks/useSpreadsheetComments.ts +132 -0
- package/src/hooks/useSpreadsheetFiltering.ts +379 -0
- package/src/hooks/useSpreadsheetHighlighting.ts +201 -0
- package/src/hooks/useSpreadsheetKeyboardShortcuts.ts +149 -0
- package/src/hooks/useSpreadsheetPinning.ts +203 -0
- package/src/hooks/useSpreadsheetUndoRedo.ts +167 -0
- package/src/index.ts +31 -0
- package/src/types.ts +612 -0
- package/src/utils.ts +16 -0
- package/tsconfig.json +30 -0
- 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
|