@lensui/lens-table 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,3662 @@
1
+ // src/Table.tsx
2
+ import { Fragment as Fragment2, useCallback as useCallback2, useEffect as useEffect2, useLayoutEffect as useLayoutEffect2, useMemo, useRef as useRef2, useState as useState4 } from "react";
3
+ import { flushSync } from "react-dom";
4
+
5
+ // src/components/EmptyState.tsx
6
+ import { useId } from "react";
7
+
8
+ // src/core/i18n.ts
9
+ var ZH_CN_GRID_LABELS = {
10
+ choose: "\u8BF7\u9009\u62E9",
11
+ selectDate: "\u9009\u62E9\u65E5\u671F",
12
+ collapseTimePicker: "\u6536\u8D77\u65F6\u95F4\u9009\u62E9\u5668",
13
+ expandTimePicker: "\u5C55\u5F00\u65F6\u95F4\u9009\u62E9\u5668",
14
+ startTime: "\u5F00\u59CB\u65F6\u95F4",
15
+ endTime: "\u7ED3\u675F\u65F6\u95F4",
16
+ start: "\u5F00\u59CB",
17
+ end: "\u7ED3\u675F",
18
+ hour: "\u65F6",
19
+ minute: "\u5206",
20
+ second: "\u79D2",
21
+ confirm: "\u786E\u5B9A",
22
+ reset: "\u91CD\u7F6E",
23
+ search: "\u641C\u7D22",
24
+ filter: "\u7B5B\u9009",
25
+ filterWithValue: (value) => `\u7B5B\u9009\uFF1A${value}`,
26
+ sortAsc: "\u5F53\u524D\uFF1A\u5347\u5E8F",
27
+ sortDesc: "\u5F53\u524D\uFF1A\u964D\u5E8F",
28
+ sortBoth: "\u5347\u5E8F / \u964D\u5E8F",
29
+ dragColumn: "\u62D6\u52A8\u5217\u6392\u5E8F",
30
+ previousYear: "\u4E0A\u4E00\u5E74",
31
+ nextYear: "\u4E0B\u4E00\u5E74",
32
+ previousMonth: "\u4E0A\u4E2A\u6708",
33
+ nextMonth: "\u4E0B\u4E2A\u6708",
34
+ previousPage: "\u4E0A\u4E00\u9875",
35
+ nextPage: "\u4E0B\u4E00\u9875",
36
+ empty: "\u6682\u65E0\u6570\u636E",
37
+ annotation: "\u6807\u6CE8",
38
+ annotationMode: "\u6807\u6CE8\u65B9\u5F0F",
39
+ annotationBackground: "\u80CC\u666F",
40
+ annotationCorner: "\u89D2\u6807",
41
+ annotationColor: (index) => `\u989C\u8272 ${index}`,
42
+ annotationContent: "\u6807\u6CE8\u5185\u5BB9",
43
+ annotationPlaceholder: "\u8F93\u5165\u6807\u6CE8\u5185\u5BB9",
44
+ removeAnnotation: "\u53D6\u6D88\u6807\u6CE8",
45
+ selectRow: "\u9009\u4E2D\u884C",
46
+ deselectRow: "\u53D6\u6D88\u9009\u4E2D\u884C",
47
+ selectColumn: "\u9009\u4E2D\u5217",
48
+ deselectColumn: "\u53D6\u6D88\u9009\u4E2D\u5217",
49
+ copy: "\u590D\u5236",
50
+ copyContent: "\u590D\u5236\u5185\u5BB9",
51
+ copySuccess: "\u590D\u5236\u6210\u529F",
52
+ copyError: "\u590D\u5236\u5931\u8D25",
53
+ clear: "\u6E05\u7A7A",
54
+ clearContent: "\u6E05\u7A7A\u5185\u5BB9",
55
+ undoChange: "\u64A4\u9500\u4FEE\u6539",
56
+ undoConfirmTitle: "\u786E\u8BA4\u64A4\u9500",
57
+ undoConfirmDescription: "\u786E\u5B9A\u64A4\u9500\u8BE5\u5355\u5143\u683C\u7684\u4FEE\u6539\u5417\uFF1F",
58
+ clearConfirmTitle: "\u786E\u8BA4\u6E05\u7A7A",
59
+ clearConfirmDescription: "\u786E\u5B9A\u6E05\u7A7A\u8BE5\u5355\u5143\u683C\u5185\u5BB9\u5417\uFF1F\u6B64\u64CD\u4F5C\u65E0\u6CD5\u64A4\u9500\u3002",
60
+ edit: "\u7F16\u8F91",
61
+ insertAbove: "\u5411\u4E0A\u63D2\u5165",
62
+ insertBelow: "\u5411\u4E0B\u63D2\u5165",
63
+ insertAboveCount: "\u5411\u4E0A\u63D2\u5165\u884C\u6570",
64
+ insertBelowCount: "\u5411\u4E0B\u63D2\u5165\u884C\u6570",
65
+ rowUnit: "\u884C",
66
+ moveUp: "\u5411\u4E0A\u79FB\u52A8",
67
+ moveDown: "\u5411\u4E0B\u79FB\u52A8",
68
+ deleteRow: "\u5220\u9664\u884C",
69
+ deleteConfirmTitle: "\u786E\u8BA4\u5220\u9664",
70
+ deleteConfirmDescription: (count) => `\u786E\u5B9A\u5220\u9664\u9009\u4E2D\u7684 ${count} \u884C\u6570\u636E\u5417\uFF1F\u6B64\u64CD\u4F5C\u65E0\u6CD5\u64A4\u9500\u3002`,
71
+ cancel: "\u53D6\u6D88",
72
+ delete: "\u5220\u9664",
73
+ filterColumn: (title) => `\u7B5B\u9009${title}`,
74
+ initialLoading: "\u8868\u683C\u52A0\u8F7D\u4E2D",
75
+ refreshing: "\u6B63\u5728\u5237\u65B0\u6570\u636E"
76
+ };
77
+ var EN_US_GRID_LABELS = {
78
+ choose: "Choose",
79
+ selectDate: "Select date",
80
+ collapseTimePicker: "Collapse time picker",
81
+ expandTimePicker: "Expand time picker",
82
+ startTime: "Start time",
83
+ endTime: "End time",
84
+ start: "Start",
85
+ end: "End",
86
+ hour: "Hour",
87
+ minute: "Minute",
88
+ second: "Second",
89
+ confirm: "Confirm",
90
+ reset: "Reset",
91
+ search: "Search",
92
+ filter: "Filter",
93
+ filterWithValue: (value) => `Filter: ${value}`,
94
+ sortAsc: "Current: ascending",
95
+ sortDesc: "Current: descending",
96
+ sortBoth: "Ascending / descending",
97
+ dragColumn: "Drag column to reorder",
98
+ previousYear: "Previous year",
99
+ nextYear: "Next year",
100
+ previousMonth: "Previous month",
101
+ nextMonth: "Next month",
102
+ previousPage: "Previous page",
103
+ nextPage: "Next page",
104
+ empty: "No data",
105
+ annotation: "Annotation",
106
+ annotationMode: "Annotation mode",
107
+ annotationBackground: "Background",
108
+ annotationCorner: "Corner",
109
+ annotationColor: (index) => `Color ${index}`,
110
+ annotationContent: "Annotation content",
111
+ annotationPlaceholder: "Enter annotation",
112
+ removeAnnotation: "Remove annotation",
113
+ selectRow: "Select row",
114
+ deselectRow: "Deselect row",
115
+ selectColumn: "Select column",
116
+ deselectColumn: "Deselect column",
117
+ copy: "Copy",
118
+ copyContent: "Copy content",
119
+ copySuccess: "Copied",
120
+ copyError: "Copy failed",
121
+ clear: "Clear",
122
+ clearContent: "Clear content",
123
+ undoChange: "Undo change",
124
+ undoConfirmTitle: "Confirm undo",
125
+ undoConfirmDescription: "Undo the change in this cell?",
126
+ clearConfirmTitle: "Confirm clear",
127
+ clearConfirmDescription: "Clear this cell content? This action cannot be undone.",
128
+ edit: "Edit",
129
+ insertAbove: "Insert above",
130
+ insertBelow: "Insert below",
131
+ insertAboveCount: "Rows to insert above",
132
+ insertBelowCount: "Rows to insert below",
133
+ rowUnit: "rows",
134
+ moveUp: "Move up",
135
+ moveDown: "Move down",
136
+ deleteRow: "Delete row",
137
+ deleteConfirmTitle: "Confirm delete",
138
+ deleteConfirmDescription: (count) => `Delete ${count} selected row(s)? This action cannot be undone.`,
139
+ cancel: "Cancel",
140
+ delete: "Delete",
141
+ filterColumn: (title) => `Filter ${title}`,
142
+ initialLoading: "Loading grid",
143
+ refreshing: "Refreshing"
144
+ };
145
+ var GRID_LABELS_BY_LOCALE = {
146
+ "zh-CN": ZH_CN_GRID_LABELS,
147
+ "en-US": EN_US_GRID_LABELS
148
+ };
149
+ function resolveGridLocale(config) {
150
+ if (!config || typeof config === "string") {
151
+ const language2 = config ?? "zh-CN";
152
+ return { language: language2, labels: GRID_LABELS_BY_LOCALE[language2] };
153
+ }
154
+ const { language = "zh-CN", labels } = config;
155
+ return { language, labels: { ...GRID_LABELS_BY_LOCALE[language], ...labels } };
156
+ }
157
+
158
+ // src/components/EmptyState.tsx
159
+ import { jsx, jsxs } from "react/jsx-runtime";
160
+ function DefaultEmptyState({ label = ZH_CN_GRID_LABELS.empty }) {
161
+ const id = useId().replace(/:/g, "");
162
+ const gradientOne = `${id}-empty-gradient-one`;
163
+ const gradientTwo = `${id}-empty-gradient-two`;
164
+ const mask = `${id}-empty-mask`;
165
+ return /* @__PURE__ */ jsxs("div", { className: "rvg-default-empty", children: [
166
+ /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 79 86", "aria-hidden": "true", children: [
167
+ /* @__PURE__ */ jsxs("defs", { children: [
168
+ /* @__PURE__ */ jsxs("linearGradient", { id: gradientOne, x1: "38.8503086%", y1: "0%", x2: "61.1496914%", y2: "100%", children: [
169
+ /* @__PURE__ */ jsx("stop", { stopColor: "#fcfcfd", offset: "0%" }),
170
+ /* @__PURE__ */ jsx("stop", { stopColor: "#eeeff3", offset: "100%" })
171
+ ] }),
172
+ /* @__PURE__ */ jsxs("linearGradient", { id: gradientTwo, x1: "0%", y1: "9.5%", x2: "100%", y2: "90.5%", children: [
173
+ /* @__PURE__ */ jsx("stop", { stopColor: "#fcfcfd", offset: "0%" }),
174
+ /* @__PURE__ */ jsx("stop", { stopColor: "#e9ebef", offset: "100%" })
175
+ ] }),
176
+ /* @__PURE__ */ jsx("mask", { id: mask, children: /* @__PURE__ */ jsx("rect", { width: "17", height: "36", fill: "#fff" }) })
177
+ ] }),
178
+ /* @__PURE__ */ jsx("path", { d: "M39.5 86C61.315 86 79 83.91 79 81.333 79 78.756 57.315 78 35.5 78S0 78.756 0 81.333C0 83.91 17.685 86 39.5 86Z", fill: "#f7f8fc" }),
179
+ /* @__PURE__ */ jsx("path", { d: "M13 45h40L42 58H2z", fill: "#e5e7e9" }),
180
+ /* @__PURE__ */ jsxs("g", { transform: "translate(34.5 31.5) scale(-1 1) rotate(-25) translate(-27.5 -21.5)", children: [
181
+ /* @__PURE__ */ jsx("path", { d: "M0 3h18l5 4H5z", fill: "#e5e7e9" }),
182
+ /* @__PURE__ */ jsx("path", { d: "M0 7h38v36H0z", fill: "#edeef2" }),
183
+ /* @__PURE__ */ jsx("path", { d: "M38 7h17v36H38z", fill: `url(#${gradientOne})` }),
184
+ /* @__PURE__ */ jsx("path", { d: "M24 7h17L55 0H38z", fill: "#f8f9fb" })
185
+ ] }),
186
+ /* @__PURE__ */ jsx("rect", { x: "13", y: "45", width: "40", height: "36", fill: `url(#${gradientTwo})` }),
187
+ /* @__PURE__ */ jsxs("g", { transform: "translate(53 45)", mask: `url(#${mask})`, children: [
188
+ /* @__PURE__ */ jsx("rect", { width: "17", height: "36", fill: "#e0e3e9" }),
189
+ /* @__PURE__ */ jsx("path", { d: "M7 0h17l-4 18L0 16z", fill: "#d5d7de" })
190
+ ] }),
191
+ /* @__PURE__ */ jsx("path", { d: "M62 45h17l-9 13H53z", fill: "#f8f9fb" })
192
+ ] }),
193
+ /* @__PURE__ */ jsx("span", { children: label })
194
+ ] });
195
+ }
196
+
197
+ // src/components/editors/ChoiceEditor.tsx
198
+ import { Portal } from "@ark-ui/react/portal";
199
+ import { Select, createListCollection } from "@ark-ui/react/select";
200
+
201
+ // src/icons/gridIcons.tsx
202
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
203
+ function CalendarIcon() {
204
+ return /* @__PURE__ */ jsxs2("svg", { className: "rvg-calendar-icon", viewBox: "0 0 1024 1024", "aria-hidden": "true", children: [
205
+ /* @__PURE__ */ jsx2("path", { d: "M825.94 181.85H741.7v-54.42c0-15.79-12.22-28.6-27.3-28.6s-27.3 12.81-27.3 28.6v54.42H336.9v-54.42c0-15.79-12.22-28.6-27.3-28.6s-27.3 12.81-27.3 28.6v54.42h-84.24c-45.16 0-81.9 38.49-81.9 85.8v571.73c0 47.31 36.74 85.79 81.9 85.79h627.88c45.16 0 81.9-38.48 81.9-85.79V267.65c-.01-47.31-36.74-85.8-81.9-85.8zm-627.88 57.2h84.24v50.65c0 15.79 12.22 28.6 27.3 28.6s27.3-12.8 27.3-28.6v-50.65h350.2v50.65c0 15.79 12.22 28.6 27.3 28.6s27.3-12.8 27.3-28.6v-50.65h84.24c15.06 0 27.3 12.82 27.3 28.6v138.93H170.76V267.65c0-15.78 12.25-28.6 27.3-28.6zm627.88 628.92H198.06c-15.06 0-27.3-12.83-27.3-28.6v-375.6h682.47v375.6c.01 15.77-12.24 28.6-27.29 28.6z" }),
206
+ /* @__PURE__ */ jsx2("path", { d: "M641.08 629.99H382.92c-15.08 0-27.3 12.8-27.3 28.6s12.22 28.6 27.3 28.6h258.15c15.08 0 27.3-12.8 27.3-28.6s-12.22-28.6-27.29-28.6z" })
207
+ ] });
208
+ }
209
+ function YearStepIcon({ direction }) {
210
+ const paths = direction === "previous" ? ["M10.5 3.5 6 8l4.5 4.5", "M15 3.5 10.5 8l4.5 4.5"] : ["M5 3.5 9.5 8 5 12.5", "M9.5 3.5 14 8l-4.5 4.5"];
211
+ return /* @__PURE__ */ jsx2("svg", { className: "rvg-calendar-step-icon", viewBox: "0 0 20 16", "aria-hidden": "true", children: paths.map((path) => /* @__PURE__ */ jsx2("path", { d: path }, path)) });
212
+ }
213
+ function MonthStepIcon({ direction }) {
214
+ const path = direction === "previous" ? "M12.25 3.5 7.75 8l4.5 4.5" : "M7.75 3.5 12.25 8l-4.5 4.5";
215
+ return /* @__PURE__ */ jsx2("svg", { className: "rvg-calendar-step-icon", viewBox: "0 0 20 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx2("path", { d: path }) });
216
+ }
217
+ function TimeIcon() {
218
+ return /* @__PURE__ */ jsxs2("svg", { className: "rvg-time-icon", viewBox: "0 0 16 16", "aria-hidden": "true", children: [
219
+ /* @__PURE__ */ jsx2("circle", { cx: "8", cy: "8", r: "5.5" }),
220
+ /* @__PURE__ */ jsx2("path", { d: "M8 4.7v3.6l2.4 1.4" })
221
+ ] });
222
+ }
223
+ function ClearValueIcon() {
224
+ return /* @__PURE__ */ jsx2("svg", { className: "rvg-clear-icon", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx2("path", { d: "m4.25 4.25 7.5 7.5M11.75 4.25l-7.5 7.5" }) });
225
+ }
226
+ function ChoiceChevronIcon() {
227
+ return /* @__PURE__ */ jsx2("svg", { className: "rvg-choice-chevron", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx2("path", { d: "m4 6 4 4 4-4" }) });
228
+ }
229
+ function ChoiceCheckIcon() {
230
+ return /* @__PURE__ */ jsx2("svg", { className: "rvg-choice-check", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx2("path", { d: "m3.5 8 2.6 2.6L12.5 4" }) });
231
+ }
232
+ function HeaderDragIcon({ className, style }) {
233
+ return /* @__PURE__ */ jsxs2("svg", { className, style, viewBox: "0 0 14 14", "aria-hidden": "true", children: [
234
+ /* @__PURE__ */ jsx2("circle", { cx: "7", cy: "2.5", r: "1.25" }),
235
+ /* @__PURE__ */ jsx2("circle", { cx: "7", cy: "7", r: "1.25" }),
236
+ /* @__PURE__ */ jsx2("circle", { cx: "7", cy: "11.5", r: "1.25" })
237
+ ] });
238
+ }
239
+ function HeaderSortIcon({ className, style, direction }) {
240
+ return /* @__PURE__ */ jsxs2("svg", { className, style, viewBox: "0 0 14 14", "aria-hidden": "true", children: [
241
+ /* @__PURE__ */ jsx2("path", { className: direction === "asc" ? "is-active" : "", d: "M3 5.5 7 1.5l4 4Z" }),
242
+ /* @__PURE__ */ jsx2("path", { className: direction === "desc" ? "is-active" : "", d: "M3 8.5 7 12.5l4-4Z" })
243
+ ] });
244
+ }
245
+ function HeaderSearchIcon({ className, style }) {
246
+ return /* @__PURE__ */ jsxs2("svg", { className, style, viewBox: "0 0 14 14", "aria-hidden": "true", children: [
247
+ /* @__PURE__ */ jsx2("circle", { cx: "5.5", cy: "5.5", r: "4.25" }),
248
+ /* @__PURE__ */ jsx2("path", { d: "m8.75 8.75 2.25 2.25" })
249
+ ] });
250
+ }
251
+ function SelectionIcon({ checked, indeterminate = false, radio = false }) {
252
+ if (radio) {
253
+ return /* @__PURE__ */ jsxs2("svg", { className: `rvg-selection-svg rvg-radio-svg${checked ? " is-active" : ""}`, viewBox: "0 0 16 16", focusable: "false", "aria-hidden": "true", children: [
254
+ /* @__PURE__ */ jsx2("circle", { className: "rvg-selection-outline", cx: "8", cy: "8", r: "6.25" }),
255
+ checked && /* @__PURE__ */ jsx2("circle", { className: "rvg-selection-dot", cx: "8", cy: "8", r: "3.25" })
256
+ ] });
257
+ }
258
+ return /* @__PURE__ */ jsxs2("svg", { className: `rvg-selection-svg rvg-checkbox-svg${checked || indeterminate ? " is-active" : ""}`, viewBox: "0 0 16 16", focusable: "false", "aria-hidden": "true", children: [
259
+ /* @__PURE__ */ jsx2("rect", { className: "rvg-selection-outline", x: "1.5", y: "1.5", width: "13", height: "13", rx: "3" }),
260
+ indeterminate ? /* @__PURE__ */ jsx2("path", { className: "rvg-selection-mark", d: "M4.25 8h7.5" }) : checked && /* @__PURE__ */ jsx2("path", { className: "rvg-selection-mark", d: "m4.1 8 2.35 2.35 5.45-5.2" })
261
+ ] });
262
+ }
263
+ function SubmenuArrowIcon() {
264
+ return /* @__PURE__ */ jsx2("svg", { viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx2("path", { d: "m6 3.5 4.5 4.5L6 12.5" }) });
265
+ }
266
+ function ContextMenuIcon({ type }) {
267
+ if (type === "remove-annotation") {
268
+ return /* @__PURE__ */ jsxs2("svg", { className: "rvg-context-menu-svg", viewBox: "0 0 16 16", "aria-hidden": "true", children: [
269
+ /* @__PURE__ */ jsx2("path", { d: "M3.4 3.5h9.2v6.5H7.5L4 13v-3H3.4Z" }),
270
+ /* @__PURE__ */ jsx2("path", { d: "M2.6 6.8h10.8" })
271
+ ] });
272
+ }
273
+ const paths = {
274
+ edit: ["M4 11.5 4.5 8.8 10.8 2.5a1.4 1.4 0 0 1 2 2L6.5 10.8 4 11.5Z", "M9.8 3.5l2.7 2.7"],
275
+ copy: ["M5.5 5.5h6v6h-6Z", "M3.5 9.5h-1v-7h7v1"],
276
+ undo: ["M5.5 5 3 7.5 5.5 10", "M3.2 7.5H9a3 3 0 1 1 0 6H6.5"],
277
+ clear: ["M4 4 12 12", "M12 4 4 12"],
278
+ annotation: ["M3.5 3.5h9v6.5h-5L4 13v-3H3.5Z"],
279
+ "select-row": ["M3 4.5h10", "M3 8h10", "M3 11.5h10"],
280
+ "select-column": ["M4.5 3v10", "M8 3v10", "M11.5 3v10"],
281
+ "insert-above": ["M8 12.5v-7", "M5.5 8 8 5.5 10.5 8", "M3 3.5h10"],
282
+ "insert-below": ["M8 3.5v7", "M5.5 8 8 10.5 10.5 8", "M3 12.5h10"],
283
+ "move-up": ["M8 12.5v-9", "M4.5 7 8 3.5 11.5 7"],
284
+ "move-down": ["M8 3.5v9", "M4.5 9 8 12.5 11.5 9"],
285
+ delete: ["M3.5 4.5h9", "M6 4.5v-1h4v1", "M5 6.5v6h6v-6"]
286
+ };
287
+ return /* @__PURE__ */ jsx2("svg", { className: "rvg-context-menu-svg", viewBox: "0 0 16 16", "aria-hidden": "true", children: paths[type].map((path) => /* @__PURE__ */ jsx2("path", { d: path }, path)) });
288
+ }
289
+ function RowDragHandleIcon() {
290
+ return /* @__PURE__ */ jsxs2("svg", { viewBox: "0 0 16 16", focusable: "false", children: [
291
+ /* @__PURE__ */ jsx2("circle", { cx: "5", cy: "4", r: "1" }),
292
+ /* @__PURE__ */ jsx2("circle", { cx: "11", cy: "4", r: "1" }),
293
+ /* @__PURE__ */ jsx2("circle", { cx: "5", cy: "8", r: "1" }),
294
+ /* @__PURE__ */ jsx2("circle", { cx: "11", cy: "8", r: "1" }),
295
+ /* @__PURE__ */ jsx2("circle", { cx: "5", cy: "12", r: "1" }),
296
+ /* @__PURE__ */ jsx2("circle", { cx: "11", cy: "12", r: "1" })
297
+ ] });
298
+ }
299
+ function LoadingSpinnerIcon() {
300
+ return /* @__PURE__ */ jsx2("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ jsx2("path", { d: "M20 12a8 8 0 1 1-3.1-6.3" }) });
301
+ }
302
+
303
+ // src/components/editors/ChoiceEditor.tsx
304
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
305
+ function ChoiceEditor({ value, options, style, labels, onChange, onCommit, onCancel }) {
306
+ const collection = createListCollection({
307
+ items: options,
308
+ itemToValue: (item) => item.value,
309
+ itemToString: (item) => item.label
310
+ });
311
+ return /* @__PURE__ */ jsxs3(
312
+ Select.Root,
313
+ {
314
+ className: "rvg-choice-editor",
315
+ style,
316
+ collection,
317
+ value: value ? [value] : [],
318
+ defaultOpen: true,
319
+ closeOnSelect: true,
320
+ lazyMount: true,
321
+ unmountOnExit: true,
322
+ positioning: { sameWidth: true, placement: "bottom-start" },
323
+ onSelect: (details) => {
324
+ onChange(details.value);
325
+ onCommit(details.value);
326
+ },
327
+ onValueChange: (details) => onChange(details.value[0] ?? ""),
328
+ onKeyDown: (event) => {
329
+ event.stopPropagation();
330
+ if (event.key === "Escape") onCancel();
331
+ },
332
+ onKeyUp: (event) => {
333
+ event.stopPropagation();
334
+ if (event.key === "Enter") onCommit();
335
+ },
336
+ children: [
337
+ /* @__PURE__ */ jsx3(Select.Control, { children: /* @__PURE__ */ jsxs3(Select.Trigger, { autoFocus: true, children: [
338
+ /* @__PURE__ */ jsx3(Select.ValueText, { placeholder: labels.choose }),
339
+ /* @__PURE__ */ jsx3(Select.Indicator, { children: /* @__PURE__ */ jsx3(ChoiceChevronIcon, {}) })
340
+ ] }) }),
341
+ /* @__PURE__ */ jsx3(Portal, { children: /* @__PURE__ */ jsx3(Select.Positioner, { children: /* @__PURE__ */ jsx3(Select.Content, { className: "rvg-choice-content", children: options.map((option) => /* @__PURE__ */ jsxs3(Select.Item, { className: "rvg-choice-item", item: option, children: [
342
+ /* @__PURE__ */ jsx3(Select.ItemText, { children: option.label }),
343
+ /* @__PURE__ */ jsx3(Select.ItemIndicator, { children: /* @__PURE__ */ jsx3(ChoiceCheckIcon, {}) })
344
+ ] }, option.value)) }) }) }),
345
+ /* @__PURE__ */ jsx3(Select.HiddenSelect, {})
346
+ ]
347
+ }
348
+ );
349
+ }
350
+
351
+ // src/components/editors/DateEditor.tsx
352
+ import { useState as useState2 } from "react";
353
+ import { DatePicker } from "@ark-ui/react/date-picker";
354
+ import { Portal as Portal2 } from "@ark-ui/react/portal";
355
+ import { parseDate as parseDate2 } from "@internationalized/date";
356
+
357
+ // src/core/dateTime.ts
358
+ import { parseDate } from "@internationalized/date";
359
+ var TIME_HOURS = Array.from({ length: 24 }, (_, index) => String(index).padStart(2, "0"));
360
+ var TIME_MINUTES = Array.from({ length: 60 }, (_, index) => String(index).padStart(2, "0"));
361
+ var TIME_SECONDS = Array.from({ length: 60 }, (_, index) => String(index).padStart(2, "0"));
362
+ function inferDateFormat(value) {
363
+ const sample = value.split(" ~ ")[0]?.trim() ?? "";
364
+ const yearFirst = sample.match(/^\d{4}([^\d])\d{1,2}\1\d{1,2}$/);
365
+ if (yearFirst) return `YYYY${yearFirst[1]}MM${yearFirst[1]}DD`;
366
+ const yearLast = sample.match(/^\d{1,2}([^\d])\d{1,2}\1\d{4}$/);
367
+ if (yearLast) return `MM${yearLast[1]}DD${yearLast[1]}YYYY`;
368
+ return "YYYY-MM-DD";
369
+ }
370
+ function parseFormattedDate(value, format) {
371
+ const tokens = ["YYYY", "MM", "DD"];
372
+ const groups = [];
373
+ let source = "";
374
+ for (let index = 0; index < format.length; ) {
375
+ const token = tokens.find((candidate) => format.startsWith(candidate, index));
376
+ if (token) {
377
+ groups.push(token);
378
+ source += token === "YYYY" ? "(\\d{4})" : "(\\d{1,2})";
379
+ index += token.length;
380
+ } else {
381
+ source += format[index].replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
382
+ index += 1;
383
+ }
384
+ }
385
+ const match = value.trim().match(new RegExp(`^${source}$`));
386
+ if (!match) throw new Error("Invalid date");
387
+ const parts = Object.fromEntries(groups.map((token, index) => [token, match[index + 1]]));
388
+ return parseDate(`${parts.YYYY}-${String(parts.MM).padStart(2, "0")}-${String(parts.DD).padStart(2, "0")}`);
389
+ }
390
+ function formatDateValue(value, format) {
391
+ return format.replace(/YYYY/g, String(value.year).padStart(4, "0")).replace(/MM/g, String(value.month).padStart(2, "0")).replace(/DD/g, String(value.day).padStart(2, "0"));
392
+ }
393
+ function timeFormatHasSeconds(format, value) {
394
+ if (format) return format.includes("ss");
395
+ return /\d{2}:\d{2}:\d{2}/.test(value);
396
+ }
397
+
398
+ // src/components/editors/TimeEditors.tsx
399
+ import { useEffect, useLayoutEffect, useRef, useState } from "react";
400
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
401
+ function CompactTimeEditor({ value, range, showSeconds, style, labels, onChange, onCommit, onCancel }) {
402
+ const values = range ? value.split(" ~ ") : [value];
403
+ const [dropdownOpen, setDropdownOpen] = useState(true);
404
+ const optionRefs = useRef([[], []]);
405
+ const editorRef = useRef(null);
406
+ const hasPositionedTimeRef = useRef(false);
407
+ useEffect(() => {
408
+ const closeOnOutsidePointer = (event) => {
409
+ if (!editorRef.current?.contains(event.target)) setDropdownOpen(false);
410
+ };
411
+ document.addEventListener("pointerdown", closeOnOutsidePointer, true);
412
+ return () => document.removeEventListener("pointerdown", closeOnOutsidePointer, true);
413
+ }, []);
414
+ useLayoutEffect(() => {
415
+ if (!dropdownOpen) return;
416
+ const behavior = hasPositionedTimeRef.current ? "smooth" : "auto";
417
+ values.forEach((time, index) => {
418
+ const [hour = "00", minute = "00", second = "00"] = time.split(":");
419
+ optionRefs.current[index]?.[0]?.scrollTo({ top: Math.max(0, Number(hour) * 28), behavior });
420
+ optionRefs.current[index]?.[1]?.scrollTo({ top: Math.max(0, Number(minute) * 28), behavior });
421
+ optionRefs.current[index]?.[2]?.scrollTo({ top: Math.max(0, Number(second) * 28), behavior });
422
+ });
423
+ hasPositionedTimeRef.current = true;
424
+ }, [dropdownOpen, values]);
425
+ const updatePart = (index, part, nextPart) => {
426
+ const [hour = "00", minute = "00", second = "00"] = values[index]?.split(":") ?? [];
427
+ const nextTimeParts = [
428
+ part === "hour" ? nextPart : hour,
429
+ part === "minute" ? nextPart : minute
430
+ ];
431
+ if (showSeconds) nextTimeParts.push(part === "second" ? nextPart : second);
432
+ const nextTime = nextTimeParts.join(":");
433
+ const nextValues = [...values];
434
+ nextValues[index] = nextTime;
435
+ const nextValue = range ? `${nextValues[0] ?? ""} ~ ${nextValues[1] ?? ""}` : nextTime;
436
+ onChange(nextValue);
437
+ };
438
+ return /* @__PURE__ */ jsxs4("div", { ref: editorRef, className: "rvg-time-editor", style, onKeyDown: (event) => {
439
+ event.stopPropagation();
440
+ if (event.key === "Enter") onCommit();
441
+ if (event.key === "Escape") onCancel();
442
+ }, children: [
443
+ (range ? [value] : values).map((part, index) => /* @__PURE__ */ jsx4("div", { className: "rvg-time-field-group", children: /* @__PURE__ */ jsx4(
444
+ "input",
445
+ {
446
+ value: part,
447
+ inputMode: "numeric",
448
+ autoFocus: index === 0,
449
+ onChange: (event) => onChange(event.target.value)
450
+ }
451
+ ) }, index)),
452
+ /* @__PURE__ */ jsx4(
453
+ "button",
454
+ {
455
+ type: "button",
456
+ className: "rvg-time-trigger",
457
+ "aria-label": dropdownOpen ? labels.collapseTimePicker : labels.expandTimePicker,
458
+ "aria-expanded": dropdownOpen,
459
+ onClick: () => setDropdownOpen((open) => !open),
460
+ children: /* @__PURE__ */ jsx4(TimeIcon, {})
461
+ }
462
+ ),
463
+ /* @__PURE__ */ jsxs4("div", { className: `rvg-time-dropdown${range ? " is-range" : ""}${showSeconds ? " has-seconds" : ""}${dropdownOpen ? " is-open" : ""}`, "aria-hidden": !dropdownOpen, children: [
464
+ /* @__PURE__ */ jsx4("div", { className: "rvg-time-panels", children: values.map((time, index) => {
465
+ const [hour = "00", minute = "00", second = "00"] = time.split(":");
466
+ return /* @__PURE__ */ jsxs4("section", { className: "rvg-time-picker-panel", children: [
467
+ range && /* @__PURE__ */ jsx4("strong", { children: index === 0 ? labels.startTime : labels.endTime }),
468
+ /* @__PURE__ */ jsxs4("div", { className: `rvg-time-picker-columns${showSeconds ? " has-seconds" : ""}`, children: [
469
+ /* @__PURE__ */ jsx4("span", { className: "rvg-time-column-label", children: labels.hour }),
470
+ /* @__PURE__ */ jsx4("span", { className: "rvg-time-column-label", children: labels.minute }),
471
+ showSeconds && /* @__PURE__ */ jsx4("span", { className: "rvg-time-column-label", children: labels.second }),
472
+ /* @__PURE__ */ jsx4("div", { className: "rvg-time-options", ref: (node) => {
473
+ optionRefs.current[index][0] = node;
474
+ }, "aria-label": labels.hour, children: TIME_HOURS.map((option) => /* @__PURE__ */ jsx4("button", { type: "button", className: option === hour ? "is-selected" : "", onClick: () => updatePart(index, "hour", option), children: option }, option)) }),
475
+ /* @__PURE__ */ jsx4("div", { className: "rvg-time-options", ref: (node) => {
476
+ optionRefs.current[index][1] = node;
477
+ }, "aria-label": labels.minute, children: TIME_MINUTES.map((option) => /* @__PURE__ */ jsx4("button", { type: "button", className: option === minute ? "is-selected" : "", onClick: () => updatePart(index, "minute", option), children: option }, option)) }),
478
+ showSeconds && /* @__PURE__ */ jsx4("div", { className: "rvg-time-options", ref: (node) => {
479
+ optionRefs.current[index][2] = node;
480
+ }, "aria-label": labels.second, children: TIME_SECONDS.map((option) => /* @__PURE__ */ jsx4("button", { type: "button", className: option === second ? "is-selected" : "", onClick: () => updatePart(index, "second", option), children: option }, option)) })
481
+ ] })
482
+ ] }, index);
483
+ }) }),
484
+ /* @__PURE__ */ jsx4("div", { className: "rvg-time-footer", children: /* @__PURE__ */ jsx4("button", { type: "button", onClick: () => onCommit(), children: labels.confirm }) })
485
+ ] })
486
+ ] });
487
+ }
488
+ function DateTimePanel({ values, labels, showSeconds, onChange }) {
489
+ const [activeIndex, setActiveIndex] = useState(0);
490
+ const activeValue = values[activeIndex] ?? "";
491
+ const [, hour = "00", minute = "00", second = "00"] = activeValue.match(/[T ](\d{2}):(\d{2})(?::(\d{2}))?/) ?? [];
492
+ const hoursRef = useRef(null);
493
+ const minutesRef = useRef(null);
494
+ const secondsRef = useRef(null);
495
+ const positionedRef = useRef(false);
496
+ useLayoutEffect(() => {
497
+ const behavior = positionedRef.current ? "smooth" : "auto";
498
+ hoursRef.current?.scrollTo({ top: Number(hour) * 28, behavior });
499
+ minutesRef.current?.scrollTo({ top: Number(minute) * 28, behavior });
500
+ secondsRef.current?.scrollTo({ top: Number(second) * 28, behavior });
501
+ positionedRef.current = true;
502
+ }, [activeIndex, hour, minute, second]);
503
+ const updatePart = (part, nextPart) => {
504
+ const nextTimeParts = [
505
+ part === "hour" ? nextPart : hour,
506
+ part === "minute" ? nextPart : minute
507
+ ];
508
+ if (showSeconds) nextTimeParts.push(part === "second" ? nextPart : second);
509
+ const nextTime = nextTimeParts.join(":");
510
+ const nextValues = [...values];
511
+ const current = nextValues[activeIndex] ?? "";
512
+ nextValues[activeIndex] = /[T ]\d{2}:\d{2}(?::\d{2})?/.test(current) ? current.replace(/([T ])\d{2}:\d{2}(?::\d{2})?/, `$1${nextTime}`) : `${current || "1970-01-01"}T${nextTime}`;
513
+ onChange(nextValues);
514
+ };
515
+ return /* @__PURE__ */ jsxs4("div", { className: "rvg-date-time-panel", children: [
516
+ values.length > 1 && /* @__PURE__ */ jsx4("div", { className: "rvg-date-time-tabs", children: values.map((_, index) => /* @__PURE__ */ jsx4("button", { type: "button", className: activeIndex === index ? "is-active" : "", onClick: () => setActiveIndex(index), children: index === 0 ? labels.start : labels.end }, index)) }),
517
+ /* @__PURE__ */ jsxs4("div", { className: `rvg-date-time-columns${showSeconds ? " has-seconds" : ""}`, children: [
518
+ /* @__PURE__ */ jsx4("span", { className: "rvg-time-column-label", children: labels.hour }),
519
+ /* @__PURE__ */ jsx4("span", { className: "rvg-time-column-label", children: labels.minute }),
520
+ showSeconds && /* @__PURE__ */ jsx4("span", { className: "rvg-time-column-label", children: labels.second }),
521
+ /* @__PURE__ */ jsx4("div", { className: "rvg-time-options", ref: hoursRef, "aria-label": labels.hour, children: TIME_HOURS.map((option) => /* @__PURE__ */ jsx4("button", { type: "button", className: option === hour ? "is-selected" : "", onClick: () => updatePart("hour", option), children: option }, option)) }),
522
+ /* @__PURE__ */ jsx4("div", { className: "rvg-time-options", ref: minutesRef, "aria-label": labels.minute, children: TIME_MINUTES.map((option) => /* @__PURE__ */ jsx4("button", { type: "button", className: option === minute ? "is-selected" : "", onClick: () => updatePart("minute", option), children: option }, option)) }),
523
+ showSeconds && /* @__PURE__ */ jsx4("div", { className: "rvg-time-options", ref: secondsRef, "aria-label": labels.second, children: TIME_SECONDS.map((option) => /* @__PURE__ */ jsx4("button", { type: "button", className: option === second ? "is-selected" : "", onClick: () => updatePart("second", option), children: option }, option)) })
524
+ ] })
525
+ ] });
526
+ }
527
+
528
+ // src/components/editors/DateEditor.tsx
529
+ import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
530
+ function DateEditor({ editor, value: draft, style, locale, labels, onChange, onCommit, onCancel }) {
531
+ const [rangeHoverValue, setRangeHoverValue] = useState2();
532
+ const editorType = editor.type;
533
+ const isRange = editorType === "date-range" || editorType === "date-time-range";
534
+ const isYear = editorType === "year";
535
+ const isMonth = editorType === "month";
536
+ const isDateTime = editorType === "date-time" || editorType === "date-time-range";
537
+ const parts = isRange ? draft.split(" ~ ") : [draft];
538
+ const editorFormat = editor.format;
539
+ const dateFormat = editorFormat ?? inferDateFormat(draft);
540
+ let selectedDates;
541
+ try {
542
+ selectedDates = parts.filter(Boolean).map((part) => {
543
+ if (isYear) return parseDate2(`${part.slice(0, 4)}-01-01`);
544
+ if (isMonth) return parseDate2(`${part.slice(0, 7)}-01`);
545
+ if (isDateTime) return parseDate2(part.slice(0, 10).replace(/\//g, "-"));
546
+ return parseFormattedDate(part, dateFormat);
547
+ });
548
+ } catch {
549
+ selectedDates = [];
550
+ }
551
+ const formatSelectedValue = (date, index) => {
552
+ if (isYear) return String(date.year).padStart(4, "0");
553
+ if (isMonth) return `${String(date.year).padStart(4, "0")}-${String(date.month).padStart(2, "0")}`;
554
+ if (isDateTime) {
555
+ const original = parts[index] ?? "";
556
+ const [, hour = "00", minute = "00", second = "00"] = original.match(/[T ](\d{2}):(\d{2})(?::(\d{2}))?/) ?? [];
557
+ if (editorFormat) {
558
+ return editorFormat.replace(/YYYY/g, String(date.year).padStart(4, "0")).replace(/MM/g, String(date.month).padStart(2, "0")).replace(/DD/g, String(date.day).padStart(2, "0")).replace(/HH/g, hour).replace(/mm/g, minute).replace(/ss/g, second);
559
+ }
560
+ const separator = original.includes("/") ? "/" : "-";
561
+ const datePart = [date.year, String(date.month).padStart(2, "0"), String(date.day).padStart(2, "0")].join(separator);
562
+ const timePart = original.match(/[T ]\d{2}:\d{2}(?::\d{2})?/)?.[0] ?? " 00:00";
563
+ return `${datePart}${timePart}`;
564
+ }
565
+ return formatDateValue(date, dateFormat);
566
+ };
567
+ const initialView = isYear ? "year" : isMonth ? "month" : "day";
568
+ const rangePreviewClass = (day, month) => {
569
+ const start = selectedDates[0];
570
+ const end = selectedDates.length > 1 ? selectedDates[1] : rangeHoverValue;
571
+ if (!start || !end) return void 0;
572
+ if (day.year !== month.year || day.month !== month.month) return void 0;
573
+ const lower = start.compare(end) <= 0 ? start : end;
574
+ const upper = start.compare(end) <= 0 ? end : start;
575
+ if (day.compare(lower) < 0 || day.compare(upper) > 0) return void 0;
576
+ if (day.compare(start) === 0 || day.compare(end) === 0) return "is-range-preview is-range-preview-end";
577
+ return "is-range-preview";
578
+ };
579
+ const updateRangeHover = (event) => {
580
+ event.stopPropagation();
581
+ const target = event.target.closest("[data-value]");
582
+ if (target?.hasAttribute("data-outside-range")) return;
583
+ const rawValue = target?.dataset.value;
584
+ if (!rawValue) return;
585
+ try {
586
+ setRangeHoverValue(parseDate2(rawValue.slice(0, 10)));
587
+ } catch {
588
+ }
589
+ };
590
+ return /* @__PURE__ */ jsxs5(
591
+ DatePicker.Root,
592
+ {
593
+ className: `rvg-date-editor${isDateTime ? " is-date-time" : ""}${isRange ? " has-range-selection" : ""}${editorType === "date-range" ? " is-date-range" : ""}`,
594
+ style,
595
+ locale,
596
+ value: selectedDates,
597
+ defaultOpen: true,
598
+ openOnClick: true,
599
+ startOfWeek: 1,
600
+ selectionMode: isRange ? "range" : "single",
601
+ numOfMonths: 1,
602
+ defaultView: initialView,
603
+ minView: initialView,
604
+ maxView: initialView,
605
+ closeOnSelect: !isDateTime,
606
+ positioning: { placement: "bottom-start" },
607
+ onValueChange: (details) => {
608
+ if (details.value.length === 0) return;
609
+ const next = details.value.map(formatSelectedValue).join(" ~ ");
610
+ onChange(next);
611
+ if (!isDateTime && (!isRange || details.value.length === 2)) onCommit(next);
612
+ },
613
+ children: [
614
+ /* @__PURE__ */ jsxs5(DatePicker.Control, { children: [
615
+ /* @__PURE__ */ jsx5(
616
+ "input",
617
+ {
618
+ className: "rvg-date-value",
619
+ value: isRange ? draft : parts[0] ?? "",
620
+ autoFocus: true,
621
+ onChange: (event) => onChange(event.target.value),
622
+ onKeyDown: (event) => {
623
+ event.stopPropagation();
624
+ if (event.key === "Enter" && !isDateTime) onCommit();
625
+ if (event.key === "Escape") onCancel();
626
+ }
627
+ }
628
+ ),
629
+ /* @__PURE__ */ jsx5(DatePicker.Trigger, { "aria-label": labels.selectDate, children: /* @__PURE__ */ jsx5(CalendarIcon, {}) })
630
+ ] }),
631
+ /* @__PURE__ */ jsx5(Portal2, { children: /* @__PURE__ */ jsx5(DatePicker.Positioner, { children: /* @__PURE__ */ jsxs5(DatePicker.Content, { className: `rvg-date-content${isDateTime ? " is-date-time" : ""}${isRange ? " has-range-selection" : ""}${editorType === "date-range" ? " is-date-range" : ""}`, children: [
632
+ /* @__PURE__ */ jsx5(DatePicker.Context, { children: (datePicker) => /* @__PURE__ */ jsx5(DatePicker.View, { className: `rvg-date-view-${initialView}`, view: initialView, children: editorType === "date-range" ? /* @__PURE__ */ jsxs5(Fragment, { children: [
633
+ /* @__PURE__ */ jsxs5(DatePicker.ViewControl, { className: "rvg-date-range-control", children: [
634
+ /* @__PURE__ */ jsx5("button", { type: "button", className: "rvg-year-step", "aria-label": labels.previousYear, onClick: () => datePicker.setFocusedValue(datePicker.visibleRange.start.subtract({ years: 1 })), children: /* @__PURE__ */ jsx5(YearStepIcon, { direction: "previous" }) }),
635
+ /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": labels.previousMonth, onClick: () => datePicker.setFocusedValue(datePicker.visibleRange.start.subtract({ months: 1 })), children: /* @__PURE__ */ jsx5(MonthStepIcon, { direction: "previous" }) }),
636
+ [0, 1].map((index) => {
637
+ const offset = datePicker.getOffset({ months: index });
638
+ return /* @__PURE__ */ jsx5("strong", { children: datePicker.format(offset.visibleRange.start, { year: "numeric", month: "long" }) }, index);
639
+ }),
640
+ /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": labels.nextMonth, onClick: () => datePicker.setFocusedValue(datePicker.visibleRange.start.add({ months: 1 })), children: /* @__PURE__ */ jsx5(MonthStepIcon, { direction: "next" }) }),
641
+ /* @__PURE__ */ jsx5("button", { type: "button", className: "rvg-year-step", "aria-label": labels.nextYear, onClick: () => datePicker.setFocusedValue(datePicker.visibleRange.start.add({ years: 1 })), children: /* @__PURE__ */ jsx5(YearStepIcon, { direction: "next" }) })
642
+ ] }),
643
+ /* @__PURE__ */ jsx5("div", { className: "rvg-date-range-months", children: [0, 1].map((index) => {
644
+ const offset = datePicker.getOffset({ months: index });
645
+ return /* @__PURE__ */ jsxs5(DatePicker.Table, { onPointerMoveCapture: updateRangeHover, onPointerLeave: () => setRangeHoverValue(void 0), children: [
646
+ /* @__PURE__ */ jsx5(DatePicker.TableHead, { children: /* @__PURE__ */ jsx5(DatePicker.TableRow, { children: datePicker.weekDays.map((day) => /* @__PURE__ */ jsx5(DatePicker.TableHeader, { children: day.narrow }, day.short)) }) }),
647
+ /* @__PURE__ */ jsx5(DatePicker.TableBody, { children: offset.weeks.map((week, weekIndex) => /* @__PURE__ */ jsx5(DatePicker.TableRow, { children: week.map((day) => /* @__PURE__ */ jsx5(DatePicker.TableCell, { value: day, visibleRange: offset.visibleRange, children: /* @__PURE__ */ jsx5(DatePicker.TableCellTrigger, { className: rangePreviewClass(day, offset.visibleRange.start), children: day.day }) }, day.toString())) }, weekIndex)) })
648
+ ] }, index);
649
+ }) })
650
+ ] }) : /* @__PURE__ */ jsxs5(Fragment, { children: [
651
+ /* @__PURE__ */ jsxs5(DatePicker.ViewControl, { className: initialView === "day" ? "rvg-date-day-control" : void 0, children: [
652
+ initialView === "day" && /* @__PURE__ */ jsx5("button", { type: "button", className: "rvg-year-step", "aria-label": labels.previousYear, onClick: () => datePicker.setFocusedValue(datePicker.focusedValue.subtract({ years: 1 })), children: /* @__PURE__ */ jsx5(YearStepIcon, { direction: "previous" }) }),
653
+ /* @__PURE__ */ jsx5(DatePicker.PrevTrigger, { "aria-label": labels.previousPage, children: /* @__PURE__ */ jsx5(MonthStepIcon, { direction: "previous" }) }),
654
+ /* @__PURE__ */ jsx5(DatePicker.ViewTrigger, { children: /* @__PURE__ */ jsx5(DatePicker.RangeText, {}) }),
655
+ /* @__PURE__ */ jsx5(DatePicker.NextTrigger, { "aria-label": labels.nextPage, children: /* @__PURE__ */ jsx5(MonthStepIcon, { direction: "next" }) }),
656
+ initialView === "day" && /* @__PURE__ */ jsx5("button", { type: "button", className: "rvg-year-step", "aria-label": labels.nextYear, onClick: () => datePicker.setFocusedValue(datePicker.focusedValue.add({ years: 1 })), children: /* @__PURE__ */ jsx5(YearStepIcon, { direction: "next" }) })
657
+ ] }),
658
+ initialView === "day" && /* @__PURE__ */ jsxs5(DatePicker.Table, { onPointerMoveCapture: isRange ? updateRangeHover : void 0, onPointerLeave: isRange ? () => setRangeHoverValue(void 0) : void 0, children: [
659
+ /* @__PURE__ */ jsx5(DatePicker.TableHead, { children: /* @__PURE__ */ jsx5(DatePicker.TableRow, { children: datePicker.weekDays.map((day) => /* @__PURE__ */ jsx5(DatePicker.TableHeader, { children: day.narrow }, day.short)) }) }),
660
+ /* @__PURE__ */ jsx5(DatePicker.TableBody, { children: datePicker.weeks.map((week, weekIndex) => /* @__PURE__ */ jsx5(DatePicker.TableRow, { children: week.map((day) => /* @__PURE__ */ jsx5(DatePicker.TableCell, { value: day, children: /* @__PURE__ */ jsx5(DatePicker.TableCellTrigger, { className: isRange ? rangePreviewClass(day, datePicker.visibleRange.start) : void 0, children: day.day }) }, day.toString())) }, weekIndex)) })
661
+ ] }),
662
+ initialView === "month" && /* @__PURE__ */ jsx5(DatePicker.Table, { columns: 4, children: /* @__PURE__ */ jsx5(DatePicker.TableBody, { children: datePicker.getMonthsGrid({ columns: 4, format: "short" }).map((months, rowIndex) => /* @__PURE__ */ jsx5(DatePicker.TableRow, { children: months.map((month) => /* @__PURE__ */ jsx5(DatePicker.TableCell, { value: month.value, children: /* @__PURE__ */ jsx5(DatePicker.TableCellTrigger, { children: month.label }) }, month.value)) }, rowIndex)) }) }),
663
+ initialView === "year" && /* @__PURE__ */ jsx5(DatePicker.Table, { columns: 4, children: /* @__PURE__ */ jsx5(DatePicker.TableBody, { children: datePicker.getYearsGrid({ columns: 4 }).map((years, rowIndex) => /* @__PURE__ */ jsx5(DatePicker.TableRow, { children: years.map((year) => /* @__PURE__ */ jsx5(DatePicker.TableCell, { value: year.value, children: /* @__PURE__ */ jsx5(DatePicker.TableCellTrigger, { children: year.label }) }, year.value)) }, rowIndex)) }) })
664
+ ] }) }) }),
665
+ isDateTime && /* @__PURE__ */ jsx5(DateTimePanel, { values: parts, labels, showSeconds: timeFormatHasSeconds(editorFormat, draft), onChange: (nextParts) => onChange(nextParts.join(" ~ ")) }),
666
+ isDateTime && /* @__PURE__ */ jsx5("div", { className: "rvg-date-footer", children: /* @__PURE__ */ jsx5("button", { type: "button", disabled: isRange ? parts.length < 2 || parts.some((part) => !part) : !parts[0], onClick: () => onCommit(), children: labels.confirm }) })
667
+ ] }) }) })
668
+ ]
669
+ }
670
+ );
671
+ }
672
+
673
+ // src/components/editors/TextEditor.tsx
674
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
675
+ function TextEditor({ value, type, style, labels, onChange, onCommit, onCancel }) {
676
+ const handleKeyDown = (event) => {
677
+ event.stopPropagation();
678
+ if (event.key === "Enter") onCommit();
679
+ if (event.key === "Escape") onCancel();
680
+ };
681
+ return /* @__PURE__ */ jsxs6("div", { className: "rvg-native-editor has-clear", style, children: [
682
+ /* @__PURE__ */ jsx6(
683
+ "input",
684
+ {
685
+ type,
686
+ value,
687
+ autoFocus: true,
688
+ onChange: (event) => onChange(event.target.value),
689
+ onBlur: () => onCommit(),
690
+ onKeyDown: handleKeyDown
691
+ }
692
+ ),
693
+ value && /* @__PURE__ */ jsx6(
694
+ "button",
695
+ {
696
+ type: "button",
697
+ className: "rvg-clear-trigger",
698
+ "aria-label": labels.clearContent,
699
+ onPointerDown: (event) => {
700
+ event.preventDefault();
701
+ event.stopPropagation();
702
+ },
703
+ onClick: () => {
704
+ onChange("");
705
+ onCommit("");
706
+ },
707
+ children: /* @__PURE__ */ jsx6(ClearValueIcon, {})
708
+ }
709
+ )
710
+ ] });
711
+ }
712
+
713
+ // src/icons/domIcons.ts
714
+ function createRowDragHandleSvg() {
715
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
716
+ svg.setAttribute("viewBox", "0 0 16 16");
717
+ for (const [cx, cy] of [[5, 4], [11, 4], [5, 8], [11, 8], [5, 12], [11, 12]]) {
718
+ const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
719
+ circle.setAttribute("cx", String(cx));
720
+ circle.setAttribute("cy", String(cy));
721
+ circle.setAttribute("r", "1");
722
+ svg.appendChild(circle);
723
+ }
724
+ return svg;
725
+ }
726
+
727
+ // src/Table.tsx
728
+ import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
729
+ import { setCustomNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview";
730
+ import { attachClosestEdge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
731
+
732
+ // src/core/format.ts
733
+ function getDisplayLabel(column, row, rowIndex) {
734
+ const value = column.dataIndex === void 0 ? void 0 : row[column.dataIndex];
735
+ if (column.formatter) return column.formatter(value, row, rowIndex);
736
+ if (value == null) return "";
737
+ const label = String(value);
738
+ if (column.editor?.type === "date-time" || column.editor?.type === "date-time-range") {
739
+ return label.replace(/(\d{4}[-/]\d{2}[-/]\d{2})T(?=\d{2}:\d{2})/g, "$1 ");
740
+ }
741
+ return label;
742
+ }
743
+
744
+ // src/core/layout.ts
745
+ var DEFAULT_COLUMN_WIDTH = 140;
746
+ function isDataColumn(column) {
747
+ return !column.rowSelection && !column.rowDragHandle && !column.rowNumber;
748
+ }
749
+ function isColumnSortable(column) {
750
+ return isDataColumn(column) && column.sortable !== false;
751
+ }
752
+ function isColumnFilterable(column) {
753
+ return isDataColumn(column) && column.filterable !== false;
754
+ }
755
+ function getHeaderTitleRequiredWidth(column, titleWidth) {
756
+ const minimumReadableTitleWidth = Math.min(Math.ceil(titleWidth), 28);
757
+ return minimumReadableTitleWidth + (column.align === "center" ? 20 : 22);
758
+ }
759
+ function getVisibleHeaderActions(column, width, columnDraggable = false, titleWidth = 26) {
760
+ const capacity = Math.max(0, Math.floor((width - getHeaderTitleRequiredWidth(column, titleWidth)) / 18));
761
+ const candidates = [
762
+ isColumnFilterable(column) ? "filter" : null,
763
+ isColumnSortable(column) ? "sort" : null,
764
+ columnDraggable ? "drag" : null
765
+ ].filter((action) => action !== null);
766
+ const visible = new Set(candidates.slice(Math.max(0, candidates.length - capacity)));
767
+ return { filter: visible.has("filter"), sort: visible.has("sort"), drag: visible.has("drag") };
768
+ }
769
+ function getMinimumColumnWidth(column, columnDraggable = false) {
770
+ if (column.rowDragHandle) return 36;
771
+ if (column.rowSelection || column.rowNumber) return 44;
772
+ return 60;
773
+ }
774
+ function buildColumnMetrics(columns, columnDraggable = false) {
775
+ let left = 0;
776
+ return columns.map((column) => {
777
+ const width = Math.max(getMinimumColumnWidth(column, columnDraggable), column.width ?? DEFAULT_COLUMN_WIDTH);
778
+ const metric = { left, width, right: left + width };
779
+ left += width;
780
+ return metric;
781
+ });
782
+ }
783
+ function lowerBound(metrics, value) {
784
+ let low = 0;
785
+ let high = metrics.length;
786
+ while (low < high) {
787
+ const middle = low + high >>> 1;
788
+ if (metrics[middle].right <= value) low = middle + 1;
789
+ else high = middle;
790
+ }
791
+ return low;
792
+ }
793
+ function getViewportRange(scrollLeft, scrollTop, viewportWidth, viewportHeight, rowCount, rowHeight, metrics, overscan) {
794
+ const rowStart = Math.max(0, Math.floor(scrollTop / rowHeight) - overscan);
795
+ const rowEnd = Math.min(rowCount, Math.ceil((scrollTop + viewportHeight) / rowHeight) + overscan);
796
+ const columnStart = Math.max(0, lowerBound(metrics, scrollLeft) - overscan);
797
+ const visibleEnd = lowerBound(metrics, scrollLeft + viewportWidth);
798
+ const columnEnd = Math.min(metrics.length, visibleEnd + 1 + overscan);
799
+ return { rowStart, rowEnd, columnStart, columnEnd };
800
+ }
801
+ function hitTestColumn(metrics, contentX) {
802
+ const index = lowerBound(metrics, contentX);
803
+ return index < metrics.length ? index : -1;
804
+ }
805
+
806
+ // src/core/render.ts
807
+ var getCellCoordKey = (rowIndex, columnIndex) => `${rowIndex}:${columnIndex}`;
808
+ var COLORS = {
809
+ background: "#ffffff",
810
+ header: "#f5f6f7",
811
+ grid: "#e3e6e8",
812
+ text: "#202124",
813
+ muted: "#5f6368",
814
+ selection: "#1677ff",
815
+ selectionFill: "#edf4ff",
816
+ axisSelectionFill: "#e8f2ff",
817
+ rowHoverFill: "#f6f9fc",
818
+ editedFill: "#fff1b8",
819
+ insertedFill: "#c8ead4",
820
+ stripe: "#fafbfc"
821
+ };
822
+ function ellipsizeText(context, text, maxWidth) {
823
+ if (maxWidth <= 0) return "";
824
+ if (context.measureText(text).width <= maxWidth) return text;
825
+ const ellipsis = "...";
826
+ const ellipsisWidth = context.measureText(ellipsis).width;
827
+ if (ellipsisWidth >= maxWidth) return "";
828
+ let start = 0;
829
+ let end = text.length;
830
+ while (start < end) {
831
+ const middle = Math.ceil((start + end) / 2);
832
+ const candidate = `${text.slice(0, middle)}${ellipsis}`;
833
+ if (context.measureText(candidate).width <= maxWidth) start = middle;
834
+ else end = middle - 1;
835
+ }
836
+ return `${text.slice(0, start)}${ellipsis}`;
837
+ }
838
+ function paintGrid(options) {
839
+ const { context: ctx, width, height, pixelRatio, scrollLeft, scrollTop, rowHeight, headerHeight, fixedHeader, verticalBorderless, striped, columnDraggable, sortState, filterValues, hoveredHeaderAction, rows, columns, metrics, range, selection, editing, hoveredRowIndex, selectionRange, selectedRowKeys, selectedColumnKeys, highlightEditedCells, highlightInsertedRows, insertedRowKeys, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview } = options;
840
+ const colors = { ...COLORS, ...options.colors };
841
+ const bodyTop = options.bodyTop ?? headerHeight;
842
+ const getCellSpan = (rowIndex, columnIndex) => options.cellSpans.get(getCellCoordKey(rowIndex, columnIndex));
843
+ const getCellWidth = (columnIndex, colSpan) => {
844
+ let cellWidth = 0;
845
+ for (let index = columnIndex; index < Math.min(columns.length, columnIndex + colSpan); index += 1) {
846
+ cellWidth += metrics[index]?.width ?? 0;
847
+ }
848
+ return cellWidth;
849
+ };
850
+ ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
851
+ ctx.clearRect(0, 0, width, height);
852
+ ctx.fillStyle = colors.background;
853
+ ctx.fillRect(0, 0, width, height);
854
+ ctx.font = "13px Inter, ui-sans-serif, system-ui, sans-serif";
855
+ ctx.textBaseline = "middle";
856
+ const leftFixedWidth = columns.reduce((value, column, index) => column.fixed === "left" ? Math.max(value, metrics[index].right) : value, 0);
857
+ const rightFixedWidth = columns.reduce((value, column, index) => column.fixed === "right" ? value + metrics[index].width : value, 0);
858
+ const rightFixedLeft = width - rightFixedWidth;
859
+ const rightOffsets = /* @__PURE__ */ new Map();
860
+ let rightOffset = 0;
861
+ for (let index = columns.length - 1; index >= 0; index -= 1) {
862
+ if (columns[index].fixed !== "right") continue;
863
+ rightOffsets.set(index, rightOffset);
864
+ rightOffset += metrics[index].width;
865
+ }
866
+ const getColumnX = (index) => {
867
+ return columns[index].fixed === "left" ? metrics[index].left : columns[index].fixed === "right" ? width - (rightOffsets.get(index) ?? 0) - metrics[index].width : metrics[index].left - scrollLeft;
868
+ };
869
+ const paintBodyColumns = (layer) => {
870
+ const rowStart = Math.max(0, range.rowStart - Math.max(0, options.maxRowSpan - 1));
871
+ for (let rowIndex = rowStart; rowIndex < range.rowEnd; rowIndex += 1) {
872
+ if (rowDragPreview?.sourceIndex === rowIndex) continue;
873
+ const shift = rowDragPreview ? rowDragPreview.sourceIndex < rowDragPreview.targetIndex ? rowIndex > rowDragPreview.sourceIndex && rowIndex <= rowDragPreview.targetIndex ? -rowHeight : 0 : rowIndex >= rowDragPreview.targetIndex && rowIndex < rowDragPreview.sourceIndex ? rowHeight : 0 : 0;
874
+ const y = bodyTop + rowIndex * rowHeight - scrollTop + shift;
875
+ const row = rows[rowIndex];
876
+ const rowKey = getRowKey(row, rowIndex);
877
+ for (let columnIndex = 0; columnIndex < columns.length; columnIndex += 1) {
878
+ if (layer === "scroll" ? columns[columnIndex].fixed !== void 0 : columns[columnIndex].fixed !== layer) continue;
879
+ if (layer === "scroll" && (columnIndex < range.columnStart || columnIndex >= range.columnEnd)) continue;
880
+ const metric = metrics[columnIndex];
881
+ const x = getColumnX(columnIndex);
882
+ const column = columns[columnIndex];
883
+ const span = getCellSpan(rowIndex, columnIndex);
884
+ if (span && (span.rowIndex !== rowIndex || span.columnIndex !== columnIndex)) continue;
885
+ const rowSpan = span?.rowSpan ?? 1;
886
+ const colSpan = span?.colSpan ?? 1;
887
+ const cellWidth = getCellWidth(columnIndex, colSpan);
888
+ const cellHeight = rowHeight * rowSpan;
889
+ const rangeRowStart = selectionRange ? Math.min(selectionRange.anchor.rowIndex, selectionRange.focus.rowIndex) : -1;
890
+ const rangeRowEnd = selectionRange ? Math.max(selectionRange.anchor.rowIndex, selectionRange.focus.rowIndex) : -1;
891
+ const rangeColumnStart = selectionRange ? Math.min(selectionRange.anchor.columnIndex, selectionRange.focus.columnIndex) : -1;
892
+ const rangeColumnEnd = selectionRange ? Math.max(selectionRange.anchor.columnIndex, selectionRange.focus.columnIndex) : -1;
893
+ const isRangeCell = !column.rowDragHandle && !column.rowSelection && !column.rowNumber && rowIndex >= rangeRowStart && rowIndex <= rangeRowEnd && columnIndex >= rangeColumnStart && columnIndex <= rangeColumnEnd;
894
+ if (layer !== "scroll" || striped && rowIndex % 2 === 1) {
895
+ ctx.fillStyle = striped && rowIndex % 2 === 1 ? colors.stripe : colors.background;
896
+ ctx.fillRect(x, y, cellWidth, cellHeight);
897
+ }
898
+ if (selectedRowKeys.has(rowKey) || selectedColumnKeys.has(column.key)) {
899
+ ctx.fillStyle = colors.axisSelectionFill;
900
+ ctx.fillRect(x, y, cellWidth, cellHeight);
901
+ }
902
+ if (hoveredRowIndex === rowIndex) {
903
+ ctx.fillStyle = colors.rowHoverFill;
904
+ ctx.fillRect(x, y, cellWidth, cellHeight);
905
+ }
906
+ if (highlightInsertedRows && insertedRowKeys.has(rowKey)) {
907
+ ctx.fillStyle = colors.insertedFill;
908
+ ctx.fillRect(x, y, cellWidth, cellHeight);
909
+ }
910
+ const isSelectedCell = !column.rowDragHandle && !column.rowSelection && !column.rowNumber && selection?.rowIndex === rowIndex && selection.columnIndex === columnIndex && selection.rowKey === rowKey && selection.columnKey === column.key;
911
+ const annotation = cellAnnotations.get(`${typeof rowKey}:${String(rowKey)}\0${column.key}`);
912
+ const rawValue = column.dataIndex === void 0 ? void 0 : row[column.dataIndex];
913
+ const cellStyle = column.cellStyle?.(rawValue, row, rowIndex);
914
+ if (!isSelectedCell && !isRangeCell && cellStyle?.backgroundColor) {
915
+ ctx.fillStyle = cellStyle.backgroundColor;
916
+ ctx.fillRect(x, y, cellWidth, cellHeight);
917
+ }
918
+ if (highlightEditedCells && !insertedRowKeys.has(rowKey) && !isSelectedCell && editedCellKeys.has(`${typeof rowKey}:${String(rowKey)}\0${column.key}`)) {
919
+ ctx.fillStyle = colors.editedFill;
920
+ ctx.fillRect(x, y, cellWidth, cellHeight);
921
+ }
922
+ if (isSelectedCell || isRangeCell) {
923
+ ctx.fillStyle = colors.selectionFill;
924
+ ctx.fillRect(x, y, cellWidth, cellHeight);
925
+ }
926
+ if (annotation?.type === "background") {
927
+ ctx.fillStyle = annotation.color;
928
+ ctx.fillRect(x, y, cellWidth, cellHeight);
929
+ }
930
+ ctx.fillStyle = colors.grid;
931
+ if (!verticalBorderless) ctx.fillRect(x + cellWidth - 1, y, 1, cellHeight);
932
+ if (!options.suppressLastRowBottomBorder || rowIndex !== rows.length - 1) {
933
+ ctx.fillRect(x, y + cellHeight - 1, cellWidth, 1);
934
+ }
935
+ if (rowSpan > 1) {
936
+ const mergedFill = annotation?.type === "background" ? annotation.color : isSelectedCell || isRangeCell ? colors.selectionFill : highlightEditedCells && !insertedRowKeys.has(rowKey) && editedCellKeys.has(`${typeof rowKey}:${String(rowKey)}\0${column.key}`) ? colors.editedFill : highlightInsertedRows && insertedRowKeys.has(rowKey) ? colors.insertedFill : hoveredRowIndex === rowIndex ? colors.rowHoverFill : selectedRowKeys.has(rowKey) || selectedColumnKeys.has(column.key) ? colors.axisSelectionFill : cellStyle?.backgroundColor ?? (striped && rowIndex % 2 === 1 ? colors.stripe : colors.background);
937
+ ctx.fillStyle = mergedFill;
938
+ for (let offset = 1; offset < rowSpan; offset += 1) {
939
+ ctx.fillRect(x, y + offset * rowHeight - 1, cellWidth, 2);
940
+ }
941
+ }
942
+ if (colSpan > 1 && !verticalBorderless) {
943
+ const mergedFill = annotation?.type === "background" ? annotation.color : isSelectedCell || isRangeCell ? colors.selectionFill : highlightEditedCells && !insertedRowKeys.has(rowKey) && editedCellKeys.has(`${typeof rowKey}:${String(rowKey)}\0${column.key}`) ? colors.editedFill : highlightInsertedRows && insertedRowKeys.has(rowKey) ? colors.insertedFill : hoveredRowIndex === rowIndex ? colors.rowHoverFill : selectedRowKeys.has(rowKey) || selectedColumnKeys.has(column.key) ? colors.axisSelectionFill : cellStyle?.backgroundColor ?? (striped && rowIndex % 2 === 1 ? colors.stripe : colors.background);
944
+ ctx.fillStyle = mergedFill;
945
+ let offsetLeft = 0;
946
+ for (let offset = 1; offset < colSpan; offset += 1) {
947
+ offsetLeft += metrics[columnIndex + offset - 1]?.width ?? 0;
948
+ ctx.fillRect(x + offsetLeft - 1, y, 2, cellHeight);
949
+ }
950
+ }
951
+ if (isRangeCell) {
952
+ ctx.fillStyle = colors.selection;
953
+ if (rowIndex === rangeRowStart) ctx.fillRect(x, y, cellWidth, 2);
954
+ if (rowIndex === rangeRowEnd) ctx.fillRect(x, y + cellHeight - 2, cellWidth, 2);
955
+ if (columnIndex === rangeColumnStart) ctx.fillRect(x, y, 2, cellHeight);
956
+ if (columnIndex === rangeColumnEnd) ctx.fillRect(x + cellWidth - 2, y, 2, cellHeight);
957
+ }
958
+ if (!column.rowDragHandle && !column.rowSelection && !column.renderCell && !(editing?.rowIndex === rowIndex && editing.columnIndex === columnIndex)) {
959
+ let label = "";
960
+ try {
961
+ label = column.rowNumber ? String(rowIndex + 1) : getDisplayLabel(column, row, rowIndex);
962
+ } catch {
963
+ label = "Render error";
964
+ }
965
+ if (label) {
966
+ const padding = 10;
967
+ const textX = column.align === "right" ? x + cellWidth - padding : column.align === "center" ? x + cellWidth / 2 : x + padding;
968
+ ctx.save();
969
+ ctx.beginPath();
970
+ ctx.rect(x + 2, y + 1, Math.max(0, cellWidth - 4), cellHeight - 2);
971
+ ctx.clip();
972
+ ctx.fillStyle = cellStyle?.color ?? colors.text;
973
+ ctx.font = "13px Inter, ui-sans-serif, system-ui, sans-serif";
974
+ ctx.textAlign = column.align === "right" ? "right" : column.align === "center" ? "center" : "left";
975
+ ctx.fillText(label, textX, y + cellHeight / 2);
976
+ ctx.restore();
977
+ }
978
+ }
979
+ if (annotation?.type === "corner") {
980
+ const cornerTop = y - 1;
981
+ ctx.fillStyle = annotation.color;
982
+ ctx.beginPath();
983
+ ctx.moveTo(x + cellWidth - 10, cornerTop);
984
+ ctx.lineTo(x + cellWidth, cornerTop);
985
+ ctx.lineTo(x + cellWidth, y + 10);
986
+ ctx.closePath();
987
+ ctx.fill();
988
+ }
989
+ }
990
+ }
991
+ };
992
+ paintBodyColumns("scroll");
993
+ paintBodyColumns("left");
994
+ paintBodyColumns("right");
995
+ const headerY = fixedHeader ? 0 : -scrollTop;
996
+ ctx.fillStyle = colors.header;
997
+ ctx.fillRect(0, headerY, width, headerHeight);
998
+ ctx.font = "600 13px Inter, ui-sans-serif, system-ui, sans-serif";
999
+ const paintHeaderColumns = (layer) => {
1000
+ for (let columnIndex = 0; columnIndex < columns.length; columnIndex += 1) {
1001
+ if (layer === "scroll" ? columns[columnIndex].fixed !== void 0 : columns[columnIndex].fixed !== layer) continue;
1002
+ if (layer === "scroll" && (columnIndex < range.columnStart || columnIndex >= range.columnEnd)) continue;
1003
+ const metric = metrics[columnIndex];
1004
+ const x = getColumnX(columnIndex);
1005
+ if (layer !== "scroll") {
1006
+ ctx.fillStyle = colors.header;
1007
+ ctx.fillRect(x, headerY, metric.width, headerHeight);
1008
+ }
1009
+ if (selectedColumnKeys.has(columns[columnIndex].key)) {
1010
+ ctx.fillStyle = colors.selectionFill;
1011
+ ctx.fillRect(x, headerY, metric.width, headerHeight);
1012
+ }
1013
+ ctx.fillStyle = colors.grid;
1014
+ if (!verticalBorderless) ctx.fillRect(x + metric.width - 1, headerY, 1, headerHeight);
1015
+ ctx.fillRect(x, headerY + headerHeight - 1, metric.width, 1);
1016
+ const column = columns[columnIndex];
1017
+ if (!column.rowSelection && !column.rowDragHandle && !column.rowNumber && column.title) {
1018
+ ctx.font = "600 13px Inter, ui-sans-serif, system-ui, sans-serif";
1019
+ const titleWidth = ctx.measureText(column.title).width;
1020
+ const visibleActions = getVisibleHeaderActions(column, metric.width, columnDraggable, titleWidth);
1021
+ const actionWidth = (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * 18;
1022
+ const contentWidth = Math.max(0, metric.width - actionWidth);
1023
+ const padding = 10;
1024
+ const clipWidth = contentWidth;
1025
+ const centeredTextX = metric.width / 2;
1026
+ const centerTitleWidth = Math.max(0, Math.min(centeredTextX - padding, contentWidth - padding - centeredTextX) * 2);
1027
+ const centerOverflows = column.align === "center" && titleWidth > centerTitleWidth;
1028
+ const titleAlign = centerOverflows ? "right" : column.align;
1029
+ const maxTitleWidth = titleAlign === "center" ? centerTitleWidth : Math.max(0, clipWidth - padding * 2);
1030
+ const title = ellipsizeText(ctx, column.title, maxTitleWidth);
1031
+ const textX = titleAlign === "right" ? x + contentWidth - padding : titleAlign === "center" ? x + centeredTextX : x + padding;
1032
+ ctx.save();
1033
+ ctx.beginPath();
1034
+ ctx.rect(x + 2, headerY + 1, Math.max(0, clipWidth - 4), headerHeight - 2);
1035
+ ctx.clip();
1036
+ ctx.fillStyle = colors.muted;
1037
+ ctx.textAlign = titleAlign === "right" ? "right" : titleAlign === "center" ? "center" : "left";
1038
+ ctx.fillText(title, textX, headerY + headerHeight / 2);
1039
+ ctx.restore();
1040
+ }
1041
+ }
1042
+ };
1043
+ paintHeaderColumns("scroll");
1044
+ paintHeaderColumns("left");
1045
+ paintHeaderColumns("right");
1046
+ if (leftFixedWidth > 0 && scrollLeft > 0) {
1047
+ const shadowWidth = 10;
1048
+ const shadow = ctx.createLinearGradient(leftFixedWidth, 0, leftFixedWidth + shadowWidth, 0);
1049
+ shadow.addColorStop(0, "rgba(5, 5, 5, 0.06)");
1050
+ shadow.addColorStop(1, "rgba(5, 5, 5, 0)");
1051
+ ctx.fillStyle = shadow;
1052
+ ctx.fillRect(leftFixedWidth, 0, shadowWidth, height);
1053
+ }
1054
+ if (rightFixedWidth > 0 && scrollLeft < (metrics[metrics.length - 1]?.right ?? 0) - width) {
1055
+ const shadowWidth = 10;
1056
+ const shadow = ctx.createLinearGradient(rightFixedLeft - shadowWidth, 0, rightFixedLeft, 0);
1057
+ shadow.addColorStop(0, "rgba(5, 5, 5, 0)");
1058
+ shadow.addColorStop(1, "rgba(5, 5, 5, 0.06)");
1059
+ ctx.fillStyle = shadow;
1060
+ ctx.fillRect(rightFixedLeft - shadowWidth, 0, shadowWidth, height);
1061
+ }
1062
+ if (rightFixedWidth > 0 && !verticalBorderless) {
1063
+ ctx.fillStyle = colors.grid;
1064
+ ctx.fillRect(rightFixedLeft - 1, 0, 1, height);
1065
+ }
1066
+ ctx.fillStyle = colors.grid;
1067
+ ctx.fillRect(0, 0, width, 1);
1068
+ if (!verticalBorderless) {
1069
+ ctx.fillRect(0, 0, 1, height);
1070
+ ctx.fillRect(width - 1, 0, 1, height);
1071
+ }
1072
+ if (!options.suppressFrameBottomBorder) ctx.fillRect(0, height - 1, width, 1);
1073
+ if (selectionRange) {
1074
+ const rowStart = Math.min(selectionRange.anchor.rowIndex, selectionRange.focus.rowIndex);
1075
+ const rowEnd = Math.max(selectionRange.anchor.rowIndex, selectionRange.focus.rowIndex);
1076
+ const columnStart = Math.min(selectionRange.anchor.columnIndex, selectionRange.focus.columnIndex);
1077
+ const columnEnd = Math.max(selectionRange.anchor.columnIndex, selectionRange.focus.columnIndex);
1078
+ const top = bodyTop + rowStart * rowHeight - scrollTop;
1079
+ const bottom = bodyTop + (rowEnd + 1) * rowHeight - scrollTop;
1080
+ const clipTop = fixedHeader ? bodyTop : 0;
1081
+ const markerTop = Math.max(top, clipTop);
1082
+ const markerBottom = Math.min(bottom, height);
1083
+ const startX = getColumnX(columnStart);
1084
+ const endX = getColumnX(columnEnd) + metrics[columnEnd].width;
1085
+ const visibleLeft = columns[columnStart].fixed === "left" ? Math.max(0, startX) : Math.max(leftFixedWidth - 1, startX);
1086
+ const visibleRight = columns[columnEnd].fixed === "right" ? Math.min(width, endX) : Math.min(rightFixedLeft, endX);
1087
+ ctx.fillStyle = colors.selection;
1088
+ if (markerBottom > markerTop) {
1089
+ if (columns[columnStart].fixed === void 0 && startX < leftFixedWidth) {
1090
+ ctx.fillRect(Math.max(0, leftFixedWidth - 1), markerTop, 2, markerBottom - markerTop);
1091
+ }
1092
+ if (columns[columnEnd].fixed === void 0 && endX > rightFixedLeft) {
1093
+ ctx.fillRect(Math.max(0, rightFixedLeft - 1), markerTop, 2, markerBottom - markerTop);
1094
+ }
1095
+ }
1096
+ if (visibleRight > visibleLeft) {
1097
+ if (top < clipTop) ctx.fillRect(visibleLeft, clipTop, visibleRight - visibleLeft, 2);
1098
+ if (bottom > height) ctx.fillRect(visibleLeft, height - 2, visibleRight - visibleLeft, 2);
1099
+ } else if (top < clipTop || bottom > height) {
1100
+ const markerX = endX <= leftFixedWidth ? Math.max(0, leftFixedWidth - 1) : Math.max(0, rightFixedLeft - 1);
1101
+ ctx.fillRect(markerX, top < clipTop ? clipTop : height - 2, 2, 2);
1102
+ }
1103
+ }
1104
+ const selectedRow = selection ? rows[selection.rowIndex] : void 0;
1105
+ const hasValidSelection = Boolean(selection && selectedRow && getRowKey(selectedRow, selection.rowIndex) === selection.rowKey);
1106
+ if (selection && hasValidSelection && !selectionRange) {
1107
+ const metric = metrics[selection.columnIndex];
1108
+ if (metric) {
1109
+ const fixedSide = columns[selection.columnIndex].fixed;
1110
+ const isFixed = fixedSide !== void 0;
1111
+ const span = getCellSpan(selection.rowIndex, selection.columnIndex);
1112
+ const selectionWidth = getCellWidth(selection.columnIndex, span?.colSpan ?? 1);
1113
+ const selectionHeight = rowHeight * (span?.rowSpan ?? 1);
1114
+ const x = Math.round(getColumnX(selection.columnIndex));
1115
+ const y = Math.round(bodyTop + selection.rowIndex * rowHeight - scrollTop);
1116
+ const borderWidth = 2;
1117
+ const visibleLeft = fixedSide === "left" ? 0 : leftFixedWidth;
1118
+ const selectionAreaRight = fixedSide === "right" ? width : width - rightFixedWidth;
1119
+ const visibleRight = x + selectionWidth;
1120
+ const visibleBottom = y + selectionHeight;
1121
+ const selectionTop = fixedHeader ? bodyTop : 0;
1122
+ const selectionClipTop = fixedHeader ? Math.max(0, selectionTop - 1) : selectionTop;
1123
+ const hiddenAboveHeader = visibleBottom <= selectionTop;
1124
+ const hiddenBelowViewport = y >= height;
1125
+ const hiddenBehindLeft = !isFixed && visibleRight <= visibleLeft;
1126
+ const hiddenBehindRight = !isFixed && x >= selectionAreaRight;
1127
+ const leftMarkerX = Math.max(0, leftFixedWidth - 1);
1128
+ if ((hiddenAboveHeader || hiddenBelowViewport) && (hiddenBehindLeft || hiddenBehindRight)) {
1129
+ const markerSize = 2;
1130
+ const markerX = hiddenBehindLeft ? leftMarkerX : selectionAreaRight - borderWidth;
1131
+ const markerY = hiddenAboveHeader ? selectionClipTop : height - markerSize;
1132
+ ctx.fillStyle = colors.selection;
1133
+ ctx.fillRect(markerX, markerY, markerSize, markerSize);
1134
+ return;
1135
+ }
1136
+ ctx.save();
1137
+ ctx.beginPath();
1138
+ const clipLeft = fixedSide === "left" ? 0 : Math.max(0, leftFixedWidth - 1);
1139
+ const clipRight = Math.min(width, selectionAreaRight + 1);
1140
+ ctx.rect(clipLeft, selectionClipTop, clipRight - clipLeft, height - selectionClipTop);
1141
+ ctx.clip();
1142
+ ctx.fillStyle = colors.selection;
1143
+ if (hiddenAboveHeader) {
1144
+ const markerLeft = Math.max(x - borderWidth, visibleLeft - 1);
1145
+ const markerRight = Math.min(visibleRight + 1, selectionAreaRight);
1146
+ if (markerRight > markerLeft) ctx.fillRect(markerLeft, selectionClipTop, markerRight - markerLeft, borderWidth);
1147
+ }
1148
+ if (hiddenBelowViewport) {
1149
+ const markerLeft = Math.max(x - borderWidth, visibleLeft - 1);
1150
+ const markerRight = Math.min(visibleRight + 1, selectionAreaRight);
1151
+ if (markerRight > markerLeft) ctx.fillRect(markerLeft, height - borderWidth, markerRight - markerLeft, borderWidth);
1152
+ }
1153
+ if (hiddenBehindLeft || hiddenBehindRight) {
1154
+ const markerTop = Math.max(y - borderWidth, selectionTop);
1155
+ const markerBottom = Math.min(y + selectionHeight + 1, height);
1156
+ const markerX = hiddenBehindLeft ? leftMarkerX : selectionAreaRight - borderWidth;
1157
+ ctx.fillRect(markerX, markerTop, borderWidth, Math.max(0, markerBottom - markerTop));
1158
+ }
1159
+ if (hiddenAboveHeader || hiddenBelowViewport || hiddenBehindLeft || hiddenBehindRight) {
1160
+ ctx.restore();
1161
+ return;
1162
+ }
1163
+ const borderLeft = x <= 0 ? 0 : Math.max(x - borderWidth, visibleLeft - 1);
1164
+ const borderRight = Math.min(x + selectionWidth - 1, selectionAreaRight - borderWidth, width - borderWidth);
1165
+ const borderTop = Math.max(y - borderWidth, selectionClipTop);
1166
+ const borderBottom = Math.min(y + selectionHeight - 1, height - borderWidth);
1167
+ const borderOuterWidth = borderRight + borderWidth - borderLeft;
1168
+ ctx.fillRect(borderLeft, borderTop, borderOuterWidth, borderWidth);
1169
+ ctx.fillRect(borderLeft, borderBottom, borderOuterWidth, borderWidth);
1170
+ ctx.fillRect(borderLeft, borderTop, borderWidth, borderBottom + borderWidth - borderTop);
1171
+ ctx.fillRect(borderRight, borderTop, borderWidth, borderBottom + borderWidth - borderTop);
1172
+ ctx.restore();
1173
+ }
1174
+ }
1175
+ }
1176
+
1177
+ // src/hooks/useControllable.ts
1178
+ import { useCallback, useState as useState3 } from "react";
1179
+ function useControllableKeys(value, defaultValue, onChange) {
1180
+ const [internal, setInternal] = useState3(defaultValue ?? []);
1181
+ const keys = value === void 0 ? internal : value;
1182
+ const setKeys = useCallback((next) => {
1183
+ if (value === void 0) setInternal(next);
1184
+ onChange?.(next);
1185
+ }, [onChange, value]);
1186
+ return [keys, setKeys];
1187
+ }
1188
+ function useControllableValue(value, defaultValue, onChange) {
1189
+ const [internal, setInternal] = useState3(defaultValue ?? null);
1190
+ const selected = value === void 0 ? internal : value;
1191
+ const setSelected = useCallback((next) => {
1192
+ if (value === void 0) setInternal(next);
1193
+ onChange?.(next);
1194
+ }, [onChange, value]);
1195
+ return [selected, setSelected];
1196
+ }
1197
+
1198
+ // src/Table.tsx
1199
+ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1200
+ var ANNOTATION_COLORS = ["#f44336", "#ff7a45", "#f5a623", "#f2c94c", "#52c41a", "#13a8a8", "#1677ff", "#597ef7", "#9254de", "#eb2f96"];
1201
+ var FALLBACK_HEIGHT = 480;
1202
+ var VIRTUAL_OVERSCAN = 4;
1203
+ var TOOLTIP_DELAY = 1e3;
1204
+ var SUMMARY_CHUNK_SIZE = 5e3;
1205
+ var DEFAULT_HEADER_CONTEXT_MENU = ["copy", "select-column"];
1206
+ var DEFAULT_CELL_CONTEXT_MENU = ["edit", "copy", "annotation", "|", "select-row", "select-column", "|", "insert-above", "insert-below", "move-up", "move-down", "|", "undo", "clear", "delete-row"];
1207
+ var DEFAULT_RANGE_CONTEXT_MENU = ["copy", "annotation", "|", "undo", "clear"];
1208
+ var getRowNumberColumnWidth = (rowCount) => {
1209
+ const digits = String(Math.max(1, rowCount)).length;
1210
+ return digits <= 4 ? 44 : 44 + (digits - 4) * 8;
1211
+ };
1212
+ var createPendingInsertId = () => `table-inserted-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`;
1213
+ var waitForPaint = () => new Promise((resolve) => {
1214
+ requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
1215
+ });
1216
+ function readThemeColor(styles, name, fallback) {
1217
+ return styles.getPropertyValue(name).trim() || fallback;
1218
+ }
1219
+ function getDefaultRowKey(row, index) {
1220
+ const id = row.id;
1221
+ return typeof id === "string" || typeof id === "number" ? id : index;
1222
+ }
1223
+ function isEmptyCellValue(value) {
1224
+ return value === null || value === void 0 || value === "";
1225
+ }
1226
+ var getCellCoordKey2 = (rowIndex, columnIndex) => `${rowIndex}:${columnIndex}`;
1227
+ var createEmptyCellSpanLookup = () => ({
1228
+ anchors: /* @__PURE__ */ new Map(),
1229
+ covered: /* @__PURE__ */ new Map(),
1230
+ maxRowSpan: 1
1231
+ });
1232
+ async function calculateSummaryValues(columns, rows) {
1233
+ const values = /* @__PURE__ */ new Map();
1234
+ for (const column of columns) {
1235
+ if (!column.summary) continue;
1236
+ if (typeof column.summary === "function") {
1237
+ values.set(column.key, column.summary(rows, column));
1238
+ await waitForPaint();
1239
+ continue;
1240
+ }
1241
+ if (column.dataIndex === void 0) continue;
1242
+ let total = 0;
1243
+ let hasNumber = false;
1244
+ for (let start = 0; start < rows.length; start += SUMMARY_CHUNK_SIZE) {
1245
+ const end = Math.min(rows.length, start + SUMMARY_CHUNK_SIZE);
1246
+ for (let index = start; index < end; index += 1) {
1247
+ const value = rows[index][column.dataIndex];
1248
+ if (typeof value === "number" && Number.isFinite(value)) {
1249
+ total += value;
1250
+ hasNumber = true;
1251
+ }
1252
+ }
1253
+ await waitForPaint();
1254
+ }
1255
+ if (!hasNumber) continue;
1256
+ values.set(column.key, column.formatter ? column.formatter(total, {}, -1) : total.toLocaleString());
1257
+ }
1258
+ return values;
1259
+ }
1260
+ function resolveContextMenuSection(section, defaults) {
1261
+ if (section === false) return [];
1262
+ if (Array.isArray(section)) return section;
1263
+ if (section && typeof section === "object") {
1264
+ const excluded = new Set(section.exclude ?? []);
1265
+ return defaults.filter((item) => item === "|" || !excluded.has(item));
1266
+ }
1267
+ return defaults;
1268
+ }
1269
+ function Table({
1270
+ columns: sourceColumns,
1271
+ rows: sourceRows,
1272
+ rowKey,
1273
+ width = "100%",
1274
+ height = "100%",
1275
+ rowHeight = 36,
1276
+ headerHeight = 40,
1277
+ fixedHeader = true,
1278
+ locale = "zh-CN",
1279
+ verticalBorderless = false,
1280
+ striped = false,
1281
+ highlight,
1282
+ cellSpans = [],
1283
+ loading = false,
1284
+ loadingContent,
1285
+ virtualized = true,
1286
+ tooltip = true,
1287
+ summary = false,
1288
+ rowNumber = true,
1289
+ selectedCell,
1290
+ defaultSelectedCell,
1291
+ onSelectedCellChange,
1292
+ rangeSelection = false,
1293
+ rowSelection,
1294
+ selectedRowKeys,
1295
+ defaultSelectedRowKeys,
1296
+ onSelectedRowKeysChange,
1297
+ columnSelection,
1298
+ selectedColumnKeys,
1299
+ defaultSelectedColumnKeys,
1300
+ onSelectedColumnKeysChange,
1301
+ columnDraggable = true,
1302
+ columnResizable = false,
1303
+ onColumnOrderChange,
1304
+ onColumnResize,
1305
+ rowDraggable = false,
1306
+ onRowOrderChange,
1307
+ onInsertRows,
1308
+ onDeleteRows,
1309
+ sortState = null,
1310
+ onSortStateChange,
1311
+ filterValues = {},
1312
+ onFilterValuesChange,
1313
+ onCellChange,
1314
+ onCellContextMenu,
1315
+ contextMenu: contextMenuConfig = true,
1316
+ emptyContent,
1317
+ className = "",
1318
+ style,
1319
+ ariaLabel = "Data grid"
1320
+ }) {
1321
+ const { language, labels } = useMemo(() => resolveGridLocale(locale), [locale]);
1322
+ const hasVerticalBorders = !verticalBorderless;
1323
+ const hasStripedRows = Boolean(striped);
1324
+ const stripedColor = typeof striped === "string" ? striped : void 0;
1325
+ const editedCellsHighlight = highlight?.editedCells ?? false;
1326
+ const insertedRowsHighlight = highlight?.insertedRows ?? false;
1327
+ const highlightsEditedCells = Boolean(editedCellsHighlight);
1328
+ const editedCellHighlightColor = typeof editedCellsHighlight === "string" ? editedCellsHighlight : void 0;
1329
+ const highlightsInsertedRows = Boolean(insertedRowsHighlight);
1330
+ const insertedRowHighlightColor = typeof insertedRowsHighlight === "string" ? insertedRowsHighlight : void 0;
1331
+ const hasCustomLoading = loadingContent !== void 0;
1332
+ const isVirtualized = typeof virtualized === "object" ? virtualized.enabled ?? true : virtualized;
1333
+ const virtualOverscan = typeof virtualized === "object" ? Math.max(0, Math.floor(virtualized.overscan ?? VIRTUAL_OVERSCAN)) : VIRTUAL_OVERSCAN;
1334
+ const tooltipConfig = typeof tooltip === "object" ? { header: tooltip.header ?? true, cell: tooltip.cell ?? true } : { header: tooltip, cell: tooltip };
1335
+ const summaryEnabled = summary === true || typeof summary === "object";
1336
+ const summaryPosition = typeof summary === "object" ? summary.position ?? "bottom" : "bottom";
1337
+ const summaryVerticalBordered = hasVerticalBorders && (typeof summary === "object" ? summary.verticalBordered ?? true : true);
1338
+ const summaryEmptyValue = typeof summary === "object" ? summary.emptyValue ?? "" : "";
1339
+ const contextMenuEnabled = contextMenuConfig !== false;
1340
+ const customContextMenuConfig = typeof contextMenuConfig === "object" ? contextMenuConfig : void 0;
1341
+ const columns = useMemo(() => {
1342
+ const utilityColumns = [];
1343
+ if (rowDraggable) {
1344
+ utilityColumns.push({ key: "__rvg_row_drag__", title: "", width: 36, align: "center", fixed: "left", rowDragHandle: true });
1345
+ }
1346
+ if (rowSelection) {
1347
+ utilityColumns.push({ key: "__rvg_row_selection__", title: "", width: 44, align: "center", fixed: "left", rowSelection: true });
1348
+ }
1349
+ if (rowNumber) {
1350
+ utilityColumns.push({ key: "__rvg_row_number__", title: "#", width: getRowNumberColumnWidth(sourceRows.length), align: "center", fixed: "left", rowNumber: true });
1351
+ }
1352
+ return [...utilityColumns, ...sourceColumns];
1353
+ }, [rowDraggable, rowNumber, rowSelection, sourceColumns, sourceRows.length]);
1354
+ const utilityColumnCount = (rowDraggable ? 1 : 0) + (rowSelection ? 1 : 0) + (rowNumber ? 1 : 0);
1355
+ const canvasRef = useRef2(null);
1356
+ const scrollerRef = useRef2(null);
1357
+ const frameRef = useRef2(null);
1358
+ const textFrameRef = useRef2(null);
1359
+ const toastTimerRef = useRef2(null);
1360
+ const sortDebounceRef = useRef2(null);
1361
+ const headerTooltipTimerRef = useRef2(null);
1362
+ const cellTooltipTimerRef = useRef2(null);
1363
+ const pendingHeaderTooltipKeyRef = useRef2("");
1364
+ const pendingCellTooltipKeyRef = useRef2("");
1365
+ const clickedCellRef = useRef2(null);
1366
+ const rowAnchorRef = useRef2(null);
1367
+ const columnAnchorRef = useRef2(null);
1368
+ const columnDragRef = useRef2(null);
1369
+ const dragGuideRef = useRef2(null);
1370
+ const rowDragGuideRef = useRef2(null);
1371
+ const selectionFocusRef = useRef2(null);
1372
+ const rowByKeyRef = useRef2(/* @__PURE__ */ new Map());
1373
+ const rowIndexByKeyRef = useRef2(/* @__PURE__ */ new Map());
1374
+ const headerIconsRef = useRef2(null);
1375
+ const scrollingHeaderIconsRef = useRef2(null);
1376
+ const scrollingTextRef = useRef2(null);
1377
+ const filterRef = useRef2(null);
1378
+ const contextMenuRef = useRef2(null);
1379
+ const suppressClickRef = useRef2(false);
1380
+ const hasCompletedLoadRef = useRef2(!loading);
1381
+ const columnDragPreviewRef = useRef2(null);
1382
+ const hoveredHeaderActionRef = useRef2(null);
1383
+ const hoveredRowIndexRef = useRef2(null);
1384
+ const [viewport, setViewport] = useState4({ width: 0, height: typeof height === "number" ? height : FALLBACK_HEIGHT });
1385
+ const [horizontalScrollbarHeight, setHorizontalScrollbarHeight] = useState4(0);
1386
+ const resolvedHeight = viewport.height || FALLBACK_HEIGHT;
1387
+ const [scrollPosition, setScrollPosition] = useState4({ left: 0, top: 0 });
1388
+ const scrollRef = useRef2({ left: 0, top: 0 });
1389
+ const [selection, setSelection] = useControllableValue(selectedCell, defaultSelectedCell, onSelectedCellChange);
1390
+ const [selectionRange, setSelectionRange] = useState4(null);
1391
+ const rangeDragRef = useRef2(null);
1392
+ const [rowKeys, setRowKeys] = useControllableKeys(selectedRowKeys, defaultSelectedRowKeys, (keys) => {
1393
+ if (!onSelectedRowKeysChange) return;
1394
+ const missing = new Set(keys.filter((key) => !rowByKeyRef.current.has(key)));
1395
+ if (missing.size > 0) {
1396
+ for (let index = 0; index < rows.length && missing.size > 0; index += 1) {
1397
+ const row = rows[index];
1398
+ const key = getRowKey(row, index);
1399
+ if (missing.delete(key)) {
1400
+ rowByKeyRef.current.set(key, row);
1401
+ rowIndexByKeyRef.current.set(key, index);
1402
+ }
1403
+ }
1404
+ }
1405
+ const entries = keys.flatMap((key) => {
1406
+ const row = rowByKeyRef.current.get(key);
1407
+ const index = rowIndexByKeyRef.current.get(key);
1408
+ return row && index !== void 0 ? [{ row, index }] : [];
1409
+ });
1410
+ onSelectedRowKeysChange(keys, entries.map(({ row }) => row), entries.map(({ index }) => index));
1411
+ });
1412
+ const [columnKeys, setColumnKeys] = useControllableKeys(selectedColumnKeys, defaultSelectedColumnKeys, onSelectedColumnKeysChange);
1413
+ const [editing, setEditing] = useState4(null);
1414
+ const [draft, setDraft] = useState4("");
1415
+ const [rowDragPreview, setRowDragPreview] = useState4(null);
1416
+ useEffect2(() => {
1417
+ if (!loading) hasCompletedLoadRef.current = true;
1418
+ }, [loading]);
1419
+ useEffect2(() => {
1420
+ if (rangeSelection) return;
1421
+ rangeDragRef.current = null;
1422
+ setSelectionRange(null);
1423
+ }, [rangeSelection]);
1424
+ useEffect2(() => () => {
1425
+ if (headerTooltipTimerRef.current !== null) clearTimeout(headerTooltipTimerRef.current);
1426
+ if (cellTooltipTimerRef.current !== null) clearTimeout(cellTooltipTimerRef.current);
1427
+ }, []);
1428
+ const [filterEditor, setFilterEditor] = useState4(null);
1429
+ const [contextMenu, setContextMenu] = useState4(null);
1430
+ const [insertCounts, setInsertCounts] = useState4({ before: 1, after: 1 });
1431
+ const [confirmAction, setConfirmAction] = useState4(null);
1432
+ const [confirmLoading, setConfirmLoading] = useState4(false);
1433
+ const [insertBusy, setInsertBusy] = useState4(false);
1434
+ const [deleteBusy, setDeleteBusy] = useState4(false);
1435
+ const [toast, setToast] = useState4(null);
1436
+ const [hoveredHeaderAction, setHoveredHeaderAction] = useState4(null);
1437
+ const [visibleHeaderTooltip, setVisibleHeaderTooltip] = useState4(null);
1438
+ const [hoveredCellTooltip, setHoveredCellTooltip] = useState4(null);
1439
+ const [displaySortState, setDisplaySortState] = useState4(sortState);
1440
+ const [editedCellKeys, setEditedCellKeys] = useState4(() => /* @__PURE__ */ new Set());
1441
+ const [cellAnnotations, setCellAnnotations] = useState4(() => /* @__PURE__ */ new Map());
1442
+ const [annotationType, setAnnotationType] = useState4("corner");
1443
+ const [annotationColor, setAnnotationColor] = useState4(ANNOTATION_COLORS[0]);
1444
+ const [annotationContent, setAnnotationContent] = useState4("");
1445
+ const [annotationOpen, setAnnotationOpen] = useState4(false);
1446
+ const [annotationPlacement, setAnnotationPlacement] = useState4("right-down");
1447
+ const originalCellValuesRef = useRef2(/* @__PURE__ */ new Map());
1448
+ const [pendingDeletedRowKeys, setPendingDeletedRowKeys] = useState4(() => /* @__PURE__ */ new Set());
1449
+ const [pendingInserts, setPendingInserts] = useState4(() => []);
1450
+ const [insertedRowKeys, setInsertedRowKeys] = useState4(() => /* @__PURE__ */ new Set());
1451
+ const settledPendingInsertIdsRef = useRef2(/* @__PURE__ */ new Set());
1452
+ const getDataRowKey = useCallback2((row, index) => {
1453
+ return typeof rowKey === "function" ? rowKey(row, index) : rowKey ? row[rowKey] : getDefaultRowKey(row, index);
1454
+ }, [rowKey]);
1455
+ const getSourceRowIndexByKey = useCallback2((key, preferredIndex) => {
1456
+ const preferredRow = sourceRows[preferredIndex];
1457
+ if (preferredRow && getDataRowKey(preferredRow, preferredIndex) === key) return preferredIndex;
1458
+ const start = Math.max(0, preferredIndex - 8);
1459
+ const end = Math.min(sourceRows.length, preferredIndex + 9);
1460
+ for (let index = start; index < end; index += 1) {
1461
+ if (getDataRowKey(sourceRows[index], index) === key) return index;
1462
+ }
1463
+ return sourceRows.findIndex((sourceRow, index) => getDataRowKey(sourceRow, index) === key);
1464
+ }, [getDataRowKey, sourceRows]);
1465
+ const rows = useMemo(() => {
1466
+ let next = sourceRows;
1467
+ if (pendingInserts.length > 0) {
1468
+ const activeInserts = pendingInserts.filter((insert) => {
1469
+ if (settledPendingInsertIdsRef.current.has(insert.id)) return false;
1470
+ const isSettled = insert.keys.every((key, offset) => {
1471
+ return getSourceRowIndexByKey(key, insert.index + offset) >= 0;
1472
+ });
1473
+ if (!isSettled) return true;
1474
+ settledPendingInsertIdsRef.current.add(insert.id);
1475
+ return false;
1476
+ });
1477
+ if (activeInserts.length > 0) next = sourceRows.slice();
1478
+ let insertedOffset = 0;
1479
+ activeInserts.slice().sort((left, right) => left.index - right.index).forEach((insert) => {
1480
+ next.splice(Math.min(next.length, insert.index + insertedOffset), 0, ...insert.rows);
1481
+ insertedOffset += insert.rows.length;
1482
+ });
1483
+ }
1484
+ if (pendingDeletedRowKeys.size === 0) return next;
1485
+ return next.filter((row, index) => !pendingDeletedRowKeys.has(getDataRowKey(row, index)));
1486
+ }, [getDataRowKey, pendingDeletedRowKeys, pendingInserts, sourceRows]);
1487
+ useEffect2(() => {
1488
+ if (insertBusy || settledPendingInsertIdsRef.current.size === 0) return;
1489
+ setPendingInserts((current) => {
1490
+ const next = current.filter((insert) => !settledPendingInsertIdsRef.current.has(insert.id));
1491
+ if (next.length === current.length) return current;
1492
+ current.forEach((insert) => {
1493
+ if (!next.includes(insert)) settledPendingInsertIdsRef.current.delete(insert.id);
1494
+ });
1495
+ return next;
1496
+ });
1497
+ }, [insertBusy, pendingInserts]);
1498
+ useEffect2(() => {
1499
+ if (pendingDeletedRowKeys.size === 0) return;
1500
+ setPendingDeletedRowKeys((current) => {
1501
+ if (current.size === 0) return current;
1502
+ const sourceKeys = new Set(sourceRows.map((row, index) => getDataRowKey(row, index)));
1503
+ let changed = false;
1504
+ const next = /* @__PURE__ */ new Set();
1505
+ current.forEach((key) => {
1506
+ if (sourceKeys.has(key)) next.add(key);
1507
+ else changed = true;
1508
+ });
1509
+ return changed ? next : current;
1510
+ });
1511
+ }, [getDataRowKey, pendingDeletedRowKeys.size, sourceRows]);
1512
+ useLayoutEffect2(() => {
1513
+ if (!editing) return;
1514
+ if (selection?.rowKey === editing.rowKey && selection.columnKey === editing.columnKey) return;
1515
+ setEditing(null);
1516
+ }, [editing, selection]);
1517
+ const metrics = useMemo(() => buildColumnMetrics(columns, columnDraggable), [columnDraggable, columns]);
1518
+ const contentWidth = metrics.length > 0 ? metrics[metrics.length - 1].right : 0;
1519
+ const contentHeight = rows.length * rowHeight;
1520
+ const fixedWidth = useMemo(() => columns.reduce((width2, column, index) => column.fixed === "left" ? Math.max(width2, metrics[index].right) : width2, 0), [columns, metrics]);
1521
+ const rightFixedWidth = useMemo(() => columns.reduce((width2, column, index) => column.fixed === "right" ? width2 + metrics[index].width : width2, 0), [columns, metrics]);
1522
+ const rightFixedOffsets = useMemo(() => {
1523
+ const offsets = /* @__PURE__ */ new Map();
1524
+ let offset = 0;
1525
+ for (let index = columns.length - 1; index >= 0; index -= 1) {
1526
+ if (columns[index].fixed !== "right") continue;
1527
+ offsets.set(index, offset);
1528
+ offset += metrics[index].width;
1529
+ }
1530
+ return offsets;
1531
+ }, [columns, metrics]);
1532
+ const hasRowSelectionColumn = useMemo(() => columns.some((column) => column.rowSelection), [columns]);
1533
+ const rowSelectionMode = typeof rowSelection === "object" ? rowSelection.mode ?? "multiple" : "multiple";
1534
+ const hasSummaryRow = useMemo(() => summaryEnabled && columns.some((column) => Boolean(column.summary)), [columns, summaryEnabled]);
1535
+ const topSummaryHeight = hasSummaryRow && summaryPosition === "top" ? rowHeight : 0;
1536
+ const bottomSummaryHeight = hasSummaryRow && summaryPosition === "bottom" ? rowHeight : 0;
1537
+ const bodyTop = headerHeight + topSummaryHeight;
1538
+ const bodyViewportHeight = Math.max(0, resolvedHeight - (fixedHeader ? headerHeight : 0) - topSummaryHeight);
1539
+ const [summaryState, setSummaryState] = useState4(() => ({ columns: null, rows: null, values: /* @__PURE__ */ new Map(), pending: false }));
1540
+ const summaryPending = hasSummaryRow && (loading || summaryState.pending || summaryState.columns !== columns || summaryState.rows !== rows);
1541
+ const summaryValues = hasSummaryRow && summaryState.columns === columns && summaryState.rows === rows ? summaryState.values : /* @__PURE__ */ new Map();
1542
+ useEffect2(() => {
1543
+ let cancelled = false;
1544
+ if (!hasSummaryRow) {
1545
+ setSummaryState((current) => current.columns === null && current.rows === null && current.values.size === 0 && !current.pending ? current : { columns: null, rows: null, values: /* @__PURE__ */ new Map(), pending: false });
1546
+ return () => {
1547
+ cancelled = true;
1548
+ };
1549
+ }
1550
+ setSummaryState((current) => ({
1551
+ columns,
1552
+ rows,
1553
+ values: current.columns === columns && current.rows === rows ? current.values : /* @__PURE__ */ new Map(),
1554
+ pending: true
1555
+ }));
1556
+ if (loading) {
1557
+ return () => {
1558
+ cancelled = true;
1559
+ };
1560
+ }
1561
+ const run = async () => {
1562
+ await waitForPaint();
1563
+ if (cancelled) return;
1564
+ const values = await calculateSummaryValues(columns, rows);
1565
+ if (cancelled) return;
1566
+ setSummaryState({ columns, rows, values, pending: false });
1567
+ };
1568
+ void run();
1569
+ return () => {
1570
+ cancelled = true;
1571
+ };
1572
+ }, [columns, hasSummaryRow, loading, rows]);
1573
+ const domRange = useMemo(() => {
1574
+ if (!isVirtualized) return { rowStart: 0, rowEnd: rows.length, columnStart: 0, columnEnd: columns.length };
1575
+ const currentScroll2 = scrollRef.current;
1576
+ const rowScrollTop = fixedHeader ? currentScroll2.top : Math.max(0, currentScroll2.top - bodyTop);
1577
+ return getViewportRange(currentScroll2.left, rowScrollTop, viewport.width, bodyViewportHeight, rows.length, rowHeight, metrics, virtualOverscan);
1578
+ }, [bodyTop, bodyViewportHeight, columns.length, fixedHeader, isVirtualized, metrics, rowHeight, rows.length, scrollPosition, viewport.width, virtualOverscan]);
1579
+ const getRowKey = useCallback2((row, index) => {
1580
+ const key = getDataRowKey(row, index);
1581
+ rowByKeyRef.current.set(key, row);
1582
+ rowIndexByKeyRef.current.set(key, index);
1583
+ return key;
1584
+ }, [getDataRowKey]);
1585
+ const cellSpanLookup = useMemo(() => {
1586
+ if (cellSpans.length === 0) return createEmptyCellSpanLookup();
1587
+ const rowIndexByKey = /* @__PURE__ */ new Map();
1588
+ rows.forEach((row, index) => rowIndexByKey.set(getRowKey(row, index), index));
1589
+ const columnIndexByKey = new Map(columns.map((column, index) => [column.key, index]));
1590
+ const lookup = createEmptyCellSpanLookup();
1591
+ const occupied = /* @__PURE__ */ new Set();
1592
+ for (const span of cellSpans) {
1593
+ const rowIndex = span.rowKey !== void 0 ? rowIndexByKey.get(span.rowKey) : span.rowIndex;
1594
+ const columnIndex = columnIndexByKey.get(span.columnKey);
1595
+ if (rowIndex === void 0 || columnIndex === void 0) continue;
1596
+ if (rowIndex < 0 || rowIndex >= rows.length) continue;
1597
+ const column = columns[columnIndex];
1598
+ if (column.rowSelection || column.rowDragHandle || column.rowNumber) continue;
1599
+ const fixedSide = column.fixed;
1600
+ let colSpan = Math.max(1, Math.floor(span.colSpan ?? 1));
1601
+ while (colSpan > 1 && (columnIndex + colSpan > columns.length || columns[columnIndex + colSpan - 1]?.fixed !== fixedSide || columns[columnIndex + colSpan - 1]?.rowSelection || columns[columnIndex + colSpan - 1]?.rowDragHandle || columns[columnIndex + colSpan - 1]?.rowNumber)) {
1602
+ colSpan -= 1;
1603
+ }
1604
+ const rowSpan = Math.min(rows.length - rowIndex, Math.max(1, Math.floor(span.rowSpan ?? 1)));
1605
+ if (rowSpan === 1 && colSpan === 1) continue;
1606
+ let overlaps = false;
1607
+ for (let rowOffset = 0; rowOffset < rowSpan && !overlaps; rowOffset += 1) {
1608
+ for (let columnOffset = 0; columnOffset < colSpan; columnOffset += 1) {
1609
+ if (occupied.has(getCellCoordKey2(rowIndex + rowOffset, columnIndex + columnOffset))) {
1610
+ overlaps = true;
1611
+ break;
1612
+ }
1613
+ }
1614
+ }
1615
+ if (overlaps) continue;
1616
+ const resolved = { rowIndex, columnIndex, rowSpan, colSpan };
1617
+ lookup.anchors.set(getCellCoordKey2(rowIndex, columnIndex), resolved);
1618
+ lookup.maxRowSpan = Math.max(lookup.maxRowSpan, rowSpan);
1619
+ for (let rowOffset = 0; rowOffset < rowSpan; rowOffset += 1) {
1620
+ for (let columnOffset = 0; columnOffset < colSpan; columnOffset += 1) {
1621
+ const key = getCellCoordKey2(rowIndex + rowOffset, columnIndex + columnOffset);
1622
+ occupied.add(key);
1623
+ lookup.covered.set(key, resolved);
1624
+ }
1625
+ }
1626
+ }
1627
+ return lookup;
1628
+ }, [cellSpans, columns, getRowKey, rows]);
1629
+ const selectedRowKeySet = useMemo(() => new Set(rowKeys), [rowKeys]);
1630
+ const selectedColumnKeySet = useMemo(() => new Set(columnKeys), [columnKeys]);
1631
+ const insertedRowKeySet = useMemo(() => insertedRowKeys, [insertedRowKeys]);
1632
+ const measureHeaderTitleWidth = useCallback2((columnIndex) => {
1633
+ const title = columns[columnIndex]?.title ?? "";
1634
+ const context = canvasRef.current?.getContext("2d");
1635
+ if (!context) return Array.from(title).reduce((width3, character) => width3 + (/^[\x00-\x7F]$/.test(character) ? 7 : 13), 0);
1636
+ context.save();
1637
+ context.font = "13px Inter, ui-sans-serif, system-ui, sans-serif";
1638
+ const width2 = context.measureText(title).width;
1639
+ context.restore();
1640
+ return width2;
1641
+ }, [columns]);
1642
+ const getCellLabel = useCallback2((rowIndex, columnIndex) => {
1643
+ const row = rows[rowIndex];
1644
+ const column = columns[columnIndex];
1645
+ if (!row || !column || column.rowSelection || column.rowDragHandle) return "";
1646
+ if (column.rowNumber) return String(rowIndex + 1);
1647
+ try {
1648
+ return getDisplayLabel(column, row, rowIndex);
1649
+ } catch {
1650
+ return "Render error";
1651
+ }
1652
+ }, [columns, rows]);
1653
+ const getCellSpan = useCallback2((rowIndex, columnIndex) => cellSpanLookup.covered.get(getCellCoordKey2(rowIndex, columnIndex)), [cellSpanLookup]);
1654
+ const getCellDisplayWidth = useCallback2((columnIndex, colSpan = 1) => {
1655
+ let width2 = 0;
1656
+ for (let index = columnIndex; index < Math.min(columns.length, columnIndex + colSpan); index += 1) {
1657
+ width2 += metrics[index]?.width ?? 0;
1658
+ }
1659
+ return width2;
1660
+ }, [columns.length, metrics]);
1661
+ useEffect2(() => {
1662
+ if (!selection) return;
1663
+ const nextRowIndex = rows.findIndex((row, index) => getRowKey(row, index) === selection.rowKey);
1664
+ const nextColumnIndex = columns.findIndex((column) => column.key === selection.columnKey);
1665
+ if (nextRowIndex < 0 || nextColumnIndex < 0) return;
1666
+ if (nextRowIndex === selection.rowIndex && nextColumnIndex === selection.columnIndex) return;
1667
+ setSelection({ ...selection, rowIndex: nextRowIndex, columnIndex: nextColumnIndex });
1668
+ }, [columns, getRowKey, rows, selection, setSelection]);
1669
+ const draw = useCallback2(() => {
1670
+ frameRef.current = null;
1671
+ const canvas = canvasRef.current;
1672
+ if (!canvas || viewport.width <= 0) return;
1673
+ const ratio = Math.min(window.devicePixelRatio || 1, 2);
1674
+ const displayHeight = resolvedHeight;
1675
+ const physicalWidth = Math.round(viewport.width * ratio);
1676
+ const physicalHeight = Math.round(displayHeight * ratio);
1677
+ if (canvas.width !== physicalWidth) canvas.width = physicalWidth;
1678
+ if (canvas.height !== physicalHeight) canvas.height = physicalHeight;
1679
+ const context = canvas.getContext("2d");
1680
+ if (!context) return;
1681
+ const themeStyles = getComputedStyle(canvas);
1682
+ const themeColors = {
1683
+ background: readThemeColor(themeStyles, "--rvg-color-bg", "#ffffff"),
1684
+ header: readThemeColor(themeStyles, "--rvg-color-header-bg", "#f5f6f7"),
1685
+ grid: readThemeColor(themeStyles, "--rvg-color-grid", "#e3e6e8"),
1686
+ text: readThemeColor(themeStyles, "--rvg-color-text", "#202124"),
1687
+ muted: readThemeColor(themeStyles, "--rvg-color-muted", "#5f6368"),
1688
+ selection: readThemeColor(themeStyles, "--rvg-color-primary", "#1677ff"),
1689
+ selectionFill: readThemeColor(themeStyles, "--rvg-color-selection-fill", "#edf4ff"),
1690
+ axisSelectionFill: readThemeColor(themeStyles, "--rvg-color-axis-selection-fill", "#e8f2ff"),
1691
+ rowHoverFill: readThemeColor(themeStyles, "--rvg-color-row-hover-fill", "#f6f9fc"),
1692
+ editedFill: editedCellHighlightColor ?? readThemeColor(themeStyles, "--rvg-color-edited-fill", "#fff1b8"),
1693
+ insertedFill: insertedRowHighlightColor ?? readThemeColor(themeStyles, "--rvg-color-inserted-fill", "#c8ead4"),
1694
+ stripe: stripedColor ?? readThemeColor(themeStyles, "--rvg-color-stripe", "#fafbfc")
1695
+ };
1696
+ const scroll = scrollRef.current;
1697
+ const rowScrollTop = fixedHeader ? scroll.top : Math.max(0, scroll.top - bodyTop);
1698
+ const range = isVirtualized ? getViewportRange(scroll.left, rowScrollTop, viewport.width, bodyViewportHeight, rows.length, rowHeight, metrics, virtualOverscan) : { rowStart: 0, rowEnd: rows.length, columnStart: 0, columnEnd: columns.length };
1699
+ paintGrid({ context, width: viewport.width, height: resolvedHeight, pixelRatio: ratio, scrollLeft: scroll.left, scrollTop: scroll.top, rowHeight, headerHeight, bodyTop, suppressLastRowBottomBorder: bottomSummaryHeight > 0, suppressFrameBottomBorder: bottomSummaryHeight > 0, fixedHeader, verticalBorderless: !hasVerticalBorders, striped: hasStripedRows, columnDraggable, sortState, filterValues, hoveredHeaderAction: hoveredHeaderActionRef.current, rows, columns, metrics, range, selection, editing, hoveredRowIndex: hoveredRowIndexRef.current, selectionRange, selectedRowKeys: selectedRowKeySet, selectedColumnKeys: selectedColumnKeySet, cellSpans: cellSpanLookup.covered, maxRowSpan: cellSpanLookup.maxRowSpan, highlightEditedCells: highlightsEditedCells, highlightInsertedRows: highlightsInsertedRows, insertedRowKeys: insertedRowKeySet, editedCellKeys, cellAnnotations, getRowKey, rowDragPreview, colors: themeColors });
1700
+ }, [bodyTop, bodyViewportHeight, bottomSummaryHeight, cellAnnotations, cellSpanLookup, columnDraggable, columns, editedCellHighlightColor, editedCellKeys, editing, filterValues, fixedHeader, getRowKey, hasStripedRows, hasVerticalBorders, headerHeight, highlightsEditedCells, highlightsInsertedRows, insertedRowHighlightColor, insertedRowKeySet, isVirtualized, metrics, resolvedHeight, rowDragPreview, rowHeight, rows, selectedColumnKeySet, selectedRowKeySet, selection, selectionRange, sortState, stripedColor, viewport.width, virtualOverscan]);
1701
+ const scheduleDraw = useCallback2(() => {
1702
+ if (frameRef.current === null) frameRef.current = requestAnimationFrame(draw);
1703
+ }, [draw]);
1704
+ useLayoutEffect2(() => {
1705
+ const element = scrollerRef.current;
1706
+ if (!element) return;
1707
+ const update = () => {
1708
+ const measuredHeight = element.clientHeight || (typeof height === "number" ? height : FALLBACK_HEIGHT);
1709
+ setViewport({ width: element.clientWidth, height: measuredHeight });
1710
+ setHorizontalScrollbarHeight(Math.max(0, element.offsetHeight - element.clientHeight));
1711
+ };
1712
+ update();
1713
+ if (typeof ResizeObserver === "undefined") {
1714
+ window.addEventListener("resize", update);
1715
+ return () => window.removeEventListener("resize", update);
1716
+ }
1717
+ const observer = new ResizeObserver(update);
1718
+ observer.observe(element);
1719
+ return () => observer.disconnect();
1720
+ }, [height]);
1721
+ useEffect2(() => {
1722
+ scheduleDraw();
1723
+ return () => {
1724
+ if (frameRef.current !== null) {
1725
+ cancelAnimationFrame(frameRef.current);
1726
+ frameRef.current = null;
1727
+ }
1728
+ };
1729
+ }, [scheduleDraw]);
1730
+ useEffect2(() => {
1731
+ setDisplaySortState(sortState);
1732
+ }, [sortState]);
1733
+ useEffect2(() => () => {
1734
+ if (sortDebounceRef.current !== null) clearTimeout(sortDebounceRef.current);
1735
+ if (textFrameRef.current !== null) cancelAnimationFrame(textFrameRef.current);
1736
+ if (toastTimerRef.current !== null) clearTimeout(toastTimerRef.current);
1737
+ }, []);
1738
+ useEffect2(() => {
1739
+ const scroller = scrollerRef.current;
1740
+ if (!scroller) return;
1741
+ const containHorizontalOverscroll = (event) => {
1742
+ if (Math.abs(event.deltaX) <= Math.abs(event.deltaY)) return;
1743
+ const atStart = scroller.scrollLeft <= 0 && event.deltaX < 0;
1744
+ const atEnd = scroller.scrollLeft >= scroller.scrollWidth - scroller.clientWidth - 1 && event.deltaX > 0;
1745
+ if (atStart || atEnd) event.preventDefault();
1746
+ };
1747
+ scroller.addEventListener("wheel", containHorizontalOverscroll, { passive: false });
1748
+ return () => scroller.removeEventListener("wheel", containHorizontalOverscroll);
1749
+ }, []);
1750
+ useEffect2(() => {
1751
+ if (!filterEditor) return;
1752
+ const closeOnOutsidePointer = (event) => {
1753
+ if (!filterRef.current?.contains(event.target)) setFilterEditor(null);
1754
+ };
1755
+ document.addEventListener("pointerdown", closeOnOutsidePointer);
1756
+ return () => document.removeEventListener("pointerdown", closeOnOutsidePointer);
1757
+ }, [filterEditor]);
1758
+ useEffect2(() => {
1759
+ if (!contextMenu) return;
1760
+ const close = (event) => {
1761
+ if (!contextMenuRef.current?.contains(event.target)) setContextMenu(null);
1762
+ };
1763
+ const closeOnEscape = (event) => {
1764
+ if (event.key === "Escape") setContextMenu(null);
1765
+ };
1766
+ document.addEventListener("pointerdown", close);
1767
+ document.addEventListener("keydown", closeOnEscape);
1768
+ return () => {
1769
+ document.removeEventListener("pointerdown", close);
1770
+ document.removeEventListener("keydown", closeOnEscape);
1771
+ };
1772
+ }, [contextMenu]);
1773
+ useEffect2(() => {
1774
+ if (!confirmAction) setConfirmLoading(false);
1775
+ }, [confirmAction]);
1776
+ useEffect2(() => {
1777
+ const scroller = scrollerRef.current;
1778
+ if (!scroller || !insertBusy) return;
1779
+ const left = scroller.scrollLeft;
1780
+ const top = scroller.scrollTop;
1781
+ const lockScroll = () => {
1782
+ scroller.scrollLeft = left;
1783
+ scroller.scrollTop = top;
1784
+ };
1785
+ scroller.addEventListener("scroll", lockScroll);
1786
+ return () => scroller.removeEventListener("scroll", lockScroll);
1787
+ }, [insertBusy]);
1788
+ const handleScroll = useCallback2(() => {
1789
+ const element = scrollerRef.current;
1790
+ if (!element) return;
1791
+ scrollRef.current = { left: element.scrollLeft, top: element.scrollTop };
1792
+ const focus = selectionFocusRef.current;
1793
+ if (focus && selection && !selectionRange) {
1794
+ const row = rows[selection.rowIndex];
1795
+ const metric = metrics[selection.columnIndex];
1796
+ const column = columns[selection.columnIndex];
1797
+ const validSelection = row && metric && column && getRowKey(row, selection.rowIndex) === selection.rowKey;
1798
+ if (!validSelection) {
1799
+ focus.style.display = "none";
1800
+ } else {
1801
+ const rawLeft = column.fixed === "left" ? metric.left : column.fixed === "right" ? viewport.width - (rightFixedOffsets.get(selection.columnIndex) ?? 0) - metric.width : metric.left - element.scrollLeft;
1802
+ const rawTop = bodyTop + selection.rowIndex * rowHeight - element.scrollTop;
1803
+ const span = getCellSpan(selection.rowIndex, selection.columnIndex);
1804
+ const rawRight = rawLeft + getCellDisplayWidth(selection.columnIndex, span?.colSpan ?? 1);
1805
+ const rawBottom = rawTop + rowHeight * (span?.rowSpan ?? 1);
1806
+ const horizontalStart = column.fixed === void 0 ? fixedWidth : 0;
1807
+ const horizontalEnd = column.fixed === void 0 ? viewport.width - rightFixedWidth : viewport.width;
1808
+ const verticalStart = fixedHeader ? bodyTop : 0;
1809
+ if (rawRight <= horizontalStart || rawLeft >= horizontalEnd || rawBottom <= verticalStart || rawTop >= resolvedHeight) {
1810
+ focus.style.display = "none";
1811
+ } else {
1812
+ const borderWidth = 2;
1813
+ const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart - 1));
1814
+ const right = Math.round(Math.min(rawRight + 1, horizontalEnd, viewport.width));
1815
+ const top = Math.round(Math.max(rawTop - borderWidth, verticalStart - 1));
1816
+ const bottom = Math.round(Math.min(rawBottom + 1, resolvedHeight));
1817
+ focus.style.display = "block";
1818
+ focus.style.left = `${left}px`;
1819
+ focus.style.top = `${top}px`;
1820
+ focus.style.width = `${Math.max(0, right - left)}px`;
1821
+ focus.style.height = `${Math.max(0, bottom - top)}px`;
1822
+ }
1823
+ }
1824
+ }
1825
+ if (textFrameRef.current === null) {
1826
+ textFrameRef.current = requestAnimationFrame(() => {
1827
+ textFrameRef.current = null;
1828
+ setScrollPosition({ ...scrollRef.current });
1829
+ });
1830
+ }
1831
+ if (scrollingTextRef.current) scrollingTextRef.current.style.transform = `translateX(${-element.scrollLeft}px)`;
1832
+ if (scrollingHeaderIconsRef.current) scrollingHeaderIconsRef.current.style.transform = `translateX(${-element.scrollLeft}px)`;
1833
+ if (headerIconsRef.current) headerIconsRef.current.style.transform = `translateY(${fixedHeader ? 0 : -element.scrollTop}px)`;
1834
+ if (hoveredHeaderActionRef.current) {
1835
+ hoveredHeaderActionRef.current = null;
1836
+ setHoveredHeaderAction(null);
1837
+ }
1838
+ setHoveredCellTooltip(null);
1839
+ scheduleDraw();
1840
+ setEditing(null);
1841
+ }, [bodyTop, columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, metrics, resolvedHeight, rightFixedOffsets, rightFixedWidth, rowHeight, rows, scheduleDraw, selection, selectionRange, viewport.width]);
1842
+ const locateColumn = useCallback2((clientX) => {
1843
+ const canvas = canvasRef.current;
1844
+ if (!canvas) return -1;
1845
+ const rect = canvas.getBoundingClientRect();
1846
+ const localX = clientX - rect.left;
1847
+ let columnIndex;
1848
+ if (localX < fixedWidth) {
1849
+ columnIndex = hitTestColumn(metrics, localX);
1850
+ } else if (rightFixedWidth > 0 && localX >= viewport.width - rightFixedWidth) {
1851
+ columnIndex = columns.findIndex((column, index) => {
1852
+ if (column.fixed !== "right") return false;
1853
+ const left = viewport.width - (rightFixedOffsets.get(index) ?? 0) - metrics[index].width;
1854
+ return localX >= left && localX < left + metrics[index].width;
1855
+ });
1856
+ } else {
1857
+ columnIndex = hitTestColumn(metrics, localX + scrollRef.current.left);
1858
+ }
1859
+ return columnIndex;
1860
+ }, [columns, fixedWidth, metrics, rightFixedOffsets, rightFixedWidth, viewport.width]);
1861
+ const getDisplayedColumnLeft = useCallback2((columnIndex) => {
1862
+ const metric = metrics[columnIndex];
1863
+ if (columns[columnIndex].fixed === "left") return metric.left;
1864
+ if (columns[columnIndex].fixed === "right") return viewport.width - (rightFixedOffsets.get(columnIndex) ?? 0) - metric.width;
1865
+ return metric.left - scrollRef.current.left;
1866
+ }, [columns, metrics, rightFixedOffsets, viewport.width]);
1867
+ const locateResizeHandle = useCallback2((clientX) => {
1868
+ if (!columnResizable || !canvasRef.current) return -1;
1869
+ const localX = clientX - canvasRef.current.getBoundingClientRect().left;
1870
+ let closest = -1;
1871
+ let distance = 6;
1872
+ for (let index = 0; index < columns.length; index += 1) {
1873
+ if (columns[index].rowSelection || columns[index].rowDragHandle || columns[index].rowNumber) continue;
1874
+ const nextDistance = Math.abs(localX - (getDisplayedColumnLeft(index) + metrics[index].width));
1875
+ if (nextDistance < distance) {
1876
+ distance = nextDistance;
1877
+ closest = index;
1878
+ }
1879
+ }
1880
+ return closest;
1881
+ }, [columnResizable, columns, getDisplayedColumnLeft, metrics]);
1882
+ const locateHeaderAction = useCallback2((clientX, columnIndex) => {
1883
+ if (columnIndex < 0 || !canvasRef.current) return null;
1884
+ const column = columns[columnIndex];
1885
+ if (column.rowSelection || column.rowDragHandle || column.rowNumber) return null;
1886
+ const localX = clientX - canvasRef.current.getBoundingClientRect().left;
1887
+ const columnRight = getDisplayedColumnLeft(columnIndex) + metrics[columnIndex].width;
1888
+ const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex));
1889
+ let right = columnRight - (visibleActions.drag ? 18 : 4);
1890
+ if (visibleActions.sort) {
1891
+ if (localX >= right - 18 && localX < right) return "sort";
1892
+ right -= 18;
1893
+ }
1894
+ if (visibleActions.filter && localX >= right - 18 && localX < right) return "filter";
1895
+ return null;
1896
+ }, [columnDraggable, columns, getDisplayedColumnLeft, measureHeaderTitleWidth, metrics]);
1897
+ const isHeaderTitleTruncated = useCallback2((columnIndex) => {
1898
+ const canvas = canvasRef.current;
1899
+ if (columnIndex < 0 || !canvas) return false;
1900
+ const column = columns[columnIndex];
1901
+ const metric = metrics[columnIndex];
1902
+ const titleWidth = measureHeaderTitleWidth(columnIndex);
1903
+ const visibleActions = getVisibleHeaderActions(column, metric.width, columnDraggable, titleWidth);
1904
+ const actionWidth = (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * 18;
1905
+ return getHeaderTitleRequiredWidth(column, titleWidth) > Math.max(0, metric.width - actionWidth);
1906
+ }, [columnDraggable, columns, measureHeaderTitleWidth, metrics]);
1907
+ const locateHeaderDragHandle = useCallback2((clientX, columnIndex) => {
1908
+ if (!columnDraggable || columnIndex < 0 || !canvasRef.current) return false;
1909
+ const column = columns[columnIndex];
1910
+ if (column.rowSelection || column.rowDragHandle || column.rowNumber) return false;
1911
+ if (!getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex)).drag) return false;
1912
+ const localX = clientX - canvasRef.current.getBoundingClientRect().left;
1913
+ const columnRight = getDisplayedColumnLeft(columnIndex) + metrics[columnIndex].width;
1914
+ return localX >= columnRight - 16 && localX < columnRight - 5;
1915
+ }, [columnDraggable, columns, getDisplayedColumnLeft, measureHeaderTitleWidth, metrics]);
1916
+ const setDragGuide = useCallback2((x) => {
1917
+ const guide = dragGuideRef.current;
1918
+ if (!guide) return;
1919
+ guide.style.display = x === null ? "none" : "block";
1920
+ if (x !== null) guide.style.transform = `translateX(${Math.round(x)}px)`;
1921
+ }, []);
1922
+ const setRowDragGuide = useCallback2((y) => {
1923
+ const guide = rowDragGuideRef.current;
1924
+ if (!guide) return;
1925
+ guide.style.display = y === null ? "none" : "block";
1926
+ if (y !== null) guide.style.transform = `translateY(${Math.round(y)}px)`;
1927
+ }, []);
1928
+ const locateRow = useCallback2((clientY) => {
1929
+ const canvas = canvasRef.current;
1930
+ if (!canvas) return -1;
1931
+ const localY = clientY - canvas.getBoundingClientRect().top;
1932
+ const rowIndex = Math.floor((localY - bodyTop + scrollRef.current.top) / rowHeight);
1933
+ return rowIndex >= 0 && rowIndex < rows.length ? rowIndex : -1;
1934
+ }, [bodyTop, rowHeight, rows.length]);
1935
+ useEffect2(() => {
1936
+ const canvas = canvasRef.current;
1937
+ if (!canvas || !columnDraggable && !rowDraggable) return;
1938
+ const getHeaderColumn = (clientX, clientY) => {
1939
+ const rect = canvas.getBoundingClientRect();
1940
+ const headerY = fixedHeader ? 0 : -scrollRef.current.top;
1941
+ const localY = clientY - rect.top;
1942
+ if (localY < headerY || localY >= headerY + headerHeight) return -1;
1943
+ return locateColumn(clientX);
1944
+ };
1945
+ const cleanupDrag = draggable({
1946
+ element: canvas,
1947
+ canDrag: ({ input }) => {
1948
+ const index = getHeaderColumn(input.clientX, input.clientY);
1949
+ if (columnDraggable && index >= 0) return locateHeaderDragHandle(input.clientX, index) && locateResizeHandle(input.clientX) < 0;
1950
+ const columnIndex = locateColumn(input.clientX);
1951
+ return rowDraggable && columnIndex >= 0 && Boolean(columns[columnIndex].rowDragHandle) && locateRow(input.clientY) >= 0;
1952
+ },
1953
+ getInitialData: ({ input }) => {
1954
+ const headerColumn = getHeaderColumn(input.clientX, input.clientY);
1955
+ if (headerColumn < 0) {
1956
+ return { type: "table-row", sourceIndex: locateRow(input.clientY) };
1957
+ }
1958
+ const sourceIndex = headerColumn;
1959
+ columnDragPreviewRef.current = { sourceIndex, targetIndex: sourceIndex };
1960
+ return { type: "table-column", sourceIndex };
1961
+ },
1962
+ onGenerateDragPreview: ({ nativeSetDragImage, source }) => {
1963
+ const sourceIndex = Number(source.data.sourceIndex);
1964
+ setCustomNativeDragPreview({
1965
+ nativeSetDragImage,
1966
+ render: ({ container }) => {
1967
+ const canvasRect = canvas.getBoundingClientRect();
1968
+ if (source.data.type === "table-row") {
1969
+ const preview2 = document.createElement("div");
1970
+ preview2.className = "rvg-row-drag-preview";
1971
+ preview2.style.width = `${canvasRect.width}px`;
1972
+ preview2.style.height = `${rowHeight}px`;
1973
+ const row = rows[sourceIndex];
1974
+ const rowKey2 = getRowKey(row, sourceIndex);
1975
+ columns.forEach((column2, columnIndex) => {
1976
+ const left = getDisplayedColumnLeft(columnIndex);
1977
+ const columnWidth2 = metrics[columnIndex].width;
1978
+ if (left + columnWidth2 <= 0 || left >= canvasRect.width) return;
1979
+ const cell = document.createElement("div");
1980
+ cell.className = "rvg-row-drag-preview-cell";
1981
+ cell.style.left = `${left}px`;
1982
+ cell.style.width = `${columnWidth2}px`;
1983
+ cell.style.justifyContent = column2.align === "right" ? "flex-end" : column2.align === "center" ? "center" : "flex-start";
1984
+ if (column2.rowDragHandle) {
1985
+ cell.appendChild(createRowDragHandleSvg());
1986
+ } else if (column2.rowSelection) {
1987
+ const checkbox = document.createElement("span");
1988
+ checkbox.className = selectedRowKeySet.has(rowKey2) ? "rvg-preview-checkbox is-checked" : "rvg-preview-checkbox";
1989
+ checkbox.textContent = selectedRowKeySet.has(rowKey2) ? "\u2713" : "";
1990
+ cell.appendChild(checkbox);
1991
+ } else {
1992
+ cell.textContent = getCellLabel(sourceIndex, columnIndex);
1993
+ }
1994
+ preview2.appendChild(cell);
1995
+ });
1996
+ container.appendChild(preview2);
1997
+ return () => preview2.remove();
1998
+ }
1999
+ const preview = document.createElement("div");
2000
+ preview.className = "rvg-column-drag-preview";
2001
+ const columnWidth = metrics[sourceIndex].width;
2002
+ preview.style.width = `${columnWidth}px`;
2003
+ preview.style.height = `${canvasRect.height}px`;
2004
+ const column = columns[sourceIndex];
2005
+ const header = document.createElement("div");
2006
+ header.className = "rvg-column-drag-preview-header";
2007
+ header.style.top = `${fixedHeader ? 0 : -scrollRef.current.top}px`;
2008
+ header.style.height = `${headerHeight}px`;
2009
+ header.style.justifyContent = column.align === "right" ? "flex-end" : column.align === "center" ? "center" : "flex-start";
2010
+ header.textContent = column.title;
2011
+ preview.appendChild(header);
2012
+ for (let rowIndex = domRange.rowStart; rowIndex < domRange.rowEnd; rowIndex += 1) {
2013
+ const row = rows[rowIndex];
2014
+ const rowKey2 = getRowKey(row, rowIndex);
2015
+ const cell = document.createElement("div");
2016
+ cell.className = "rvg-column-drag-preview-cell";
2017
+ cell.style.top = `${bodyTop + rowIndex * rowHeight - scrollRef.current.top}px`;
2018
+ cell.style.height = `${rowHeight}px`;
2019
+ cell.style.justifyContent = column.align === "right" ? "flex-end" : column.align === "center" ? "center" : "flex-start";
2020
+ const key = `${typeof rowKey2}:${String(rowKey2)}\0${column.key}`;
2021
+ if (selectedRowKeySet.has(rowKey2) || selectedColumnKeySet.has(column.key)) {
2022
+ cell.classList.add("is-axis-selected");
2023
+ }
2024
+ if (!insertedRowKeys.has(rowKey2) && editedCellKeys.has(key)) cell.classList.add("is-edited");
2025
+ if (selection?.rowKey === rowKey2 && selection.columnKey === column.key) {
2026
+ cell.classList.remove("is-edited");
2027
+ cell.classList.add("is-selected");
2028
+ }
2029
+ cell.textContent = getCellLabel(rowIndex, sourceIndex);
2030
+ preview.appendChild(cell);
2031
+ }
2032
+ container.appendChild(preview);
2033
+ return () => preview.remove();
2034
+ }
2035
+ });
2036
+ },
2037
+ onDragStart: ({ source }) => {
2038
+ suppressClickRef.current = true;
2039
+ canvas.style.cursor = "move";
2040
+ if (source.data.type === "table-row") {
2041
+ const sourceIndex = Number(source.data.sourceIndex);
2042
+ setRowDragPreview({ sourceIndex, targetIndex: sourceIndex });
2043
+ }
2044
+ },
2045
+ onDrag: ({ source, location }) => {
2046
+ const target = location.current.dropTargets[0]?.data;
2047
+ const sourceIndex = Number(source.data.sourceIndex);
2048
+ if (source.data.type === "table-row") {
2049
+ const rawTargetIndex = Number(target?.targetIndex);
2050
+ if (!Number.isInteger(rawTargetIndex)) {
2051
+ setRowDragPreview(null);
2052
+ return setRowDragGuide(null);
2053
+ }
2054
+ const edge2 = target?.rowEdge;
2055
+ let targetIndex2 = rawTargetIndex + (edge2 === "bottom" ? 1 : 0);
2056
+ if (sourceIndex < targetIndex2) targetIndex2 -= 1;
2057
+ targetIndex2 = Math.max(0, Math.min(rows.length - 1, targetIndex2));
2058
+ setRowDragPreview((current) => current?.sourceIndex === sourceIndex && current.targetIndex === targetIndex2 ? current : { sourceIndex, targetIndex: targetIndex2 });
2059
+ setRowDragGuide(bodyTop + rawTargetIndex * rowHeight - scrollRef.current.top + (edge2 === "bottom" ? rowHeight : 0));
2060
+ return;
2061
+ }
2062
+ const targetIndex = Number(target?.targetIndex);
2063
+ if (!Number.isInteger(targetIndex) || columns[targetIndex]?.fixed !== columns[sourceIndex]?.fixed) {
2064
+ setDragGuide(null);
2065
+ return;
2066
+ }
2067
+ const edge = target?.columnEdge;
2068
+ let destinationIndex = targetIndex + (edge === "right" ? 1 : 0);
2069
+ if (sourceIndex < destinationIndex) destinationIndex -= 1;
2070
+ destinationIndex = Math.max(0, Math.min(columns.length - 1, destinationIndex));
2071
+ columnDragPreviewRef.current = { sourceIndex, targetIndex: destinationIndex };
2072
+ const targetLeft = getDisplayedColumnLeft(targetIndex);
2073
+ setDragGuide(edge === "right" ? targetLeft + metrics[targetIndex].width : targetLeft);
2074
+ },
2075
+ onDrop: ({ source, location }) => {
2076
+ const target = location.current.dropTargets[0]?.data;
2077
+ const sourceIndex = Number(source.data.sourceIndex);
2078
+ if (source.data.type === "table-row") {
2079
+ const rawTargetIndex = Number(target?.targetIndex);
2080
+ let targetIndex2 = rawTargetIndex + (target?.rowEdge === "bottom" ? 1 : 0);
2081
+ if (sourceIndex < targetIndex2) targetIndex2 -= 1;
2082
+ targetIndex2 = Math.max(0, Math.min(rows.length - 1, targetIndex2));
2083
+ scrollerRef.current?.classList.add("rvg-row-drop-settling");
2084
+ setRowDragPreview(null);
2085
+ setRowDragGuide(null);
2086
+ canvas.style.cursor = "default";
2087
+ if (Number.isInteger(rawTargetIndex) && sourceIndex !== targetIndex2) onRowOrderChange?.(sourceIndex, targetIndex2);
2088
+ requestAnimationFrame(() => {
2089
+ requestAnimationFrame(() => scrollerRef.current?.classList.remove("rvg-row-drop-settling"));
2090
+ });
2091
+ window.setTimeout(() => {
2092
+ suppressClickRef.current = false;
2093
+ }, 0);
2094
+ return;
2095
+ }
2096
+ const targetIndex = columnDragPreviewRef.current?.targetIndex ?? Number(target?.targetIndex);
2097
+ canvas.style.cursor = "default";
2098
+ setDragGuide(null);
2099
+ columnDragPreviewRef.current = null;
2100
+ if (Number.isInteger(targetIndex) && sourceIndex !== targetIndex && columns[targetIndex]?.fixed === columns[sourceIndex]?.fixed) {
2101
+ onColumnOrderChange?.(sourceIndex - utilityColumnCount, targetIndex - utilityColumnCount);
2102
+ }
2103
+ window.setTimeout(() => {
2104
+ suppressClickRef.current = false;
2105
+ }, 0);
2106
+ }
2107
+ });
2108
+ const cleanupDrop = dropTargetForElements({
2109
+ element: canvas,
2110
+ canDrop: ({ source }) => source.data.type === "table-column" || source.data.type === "table-row",
2111
+ getData: ({ input, element, source }) => {
2112
+ if (source.data.type === "table-row") {
2113
+ const targetIndex2 = locateRow(input.clientY);
2114
+ if (targetIndex2 < 0) return { targetIndex: -1 };
2115
+ const rowTop = bodyTop + targetIndex2 * rowHeight - scrollRef.current.top;
2116
+ const rowEdge = input.clientY - canvas.getBoundingClientRect().top < rowTop + rowHeight / 2 ? "top" : "bottom";
2117
+ return attachClosestEdge({ targetIndex: targetIndex2, rowEdge }, { element, input, allowedEdges: ["top", "bottom"] });
2118
+ }
2119
+ let targetIndex = getHeaderColumn(input.clientX, input.clientY);
2120
+ if (targetIndex < 0) return { targetIndex: -1 };
2121
+ if (columns[targetIndex].rowSelection || columns[targetIndex].rowDragHandle || columns[targetIndex].rowNumber) {
2122
+ const fixedSide = columns[Number(source.data.sourceIndex)]?.fixed;
2123
+ targetIndex = columns.findIndex((column, index) => index > targetIndex && column.fixed === fixedSide && !column.rowSelection && !column.rowDragHandle && !column.rowNumber);
2124
+ if (targetIndex < 0) return { targetIndex: -1 };
2125
+ return { targetIndex, columnEdge: "left" };
2126
+ }
2127
+ const left = getDisplayedColumnLeft(targetIndex);
2128
+ const columnEdge = input.clientX - canvas.getBoundingClientRect().left < left + metrics[targetIndex].width / 2 ? "left" : "right";
2129
+ return attachClosestEdge({ targetIndex, columnEdge }, { element, input, allowedEdges: ["left", "right"] });
2130
+ }
2131
+ });
2132
+ return () => {
2133
+ cleanupDrag();
2134
+ cleanupDrop();
2135
+ };
2136
+ }, [bodyTop, columnDraggable, columns, fixedHeader, getCellLabel, getDisplayedColumnLeft, getRowKey, headerHeight, locateColumn, locateHeaderDragHandle, locateResizeHandle, locateRow, metrics, onColumnOrderChange, onRowOrderChange, rowDraggable, rowHeight, rows, selectedRowKeySet, setDragGuide, setRowDragGuide, utilityColumnCount]);
2137
+ const locateCell = useCallback2((clientX, clientY) => {
2138
+ const canvas = canvasRef.current;
2139
+ if (!canvas) return null;
2140
+ const rect = canvas.getBoundingClientRect();
2141
+ const localY = clientY - rect.top;
2142
+ const contentY = localY + (fixedHeader ? 0 : scrollRef.current.top);
2143
+ if (contentY < bodyTop) return null;
2144
+ const rowIndex = Math.floor((localY - bodyTop + scrollRef.current.top) / rowHeight);
2145
+ const columnIndex = locateColumn(clientX);
2146
+ if (rowIndex < 0 || rowIndex >= rows.length || columnIndex < 0) return null;
2147
+ const span = getCellSpan(rowIndex, columnIndex);
2148
+ const targetRowIndex = span?.rowIndex ?? rowIndex;
2149
+ const targetColumnIndex = span?.columnIndex ?? columnIndex;
2150
+ return {
2151
+ rowIndex: targetRowIndex,
2152
+ columnIndex: targetColumnIndex,
2153
+ rowKey: getRowKey(rows[targetRowIndex], targetRowIndex),
2154
+ columnKey: columns[targetColumnIndex].key
2155
+ };
2156
+ }, [bodyTop, columns, fixedHeader, getCellSpan, getRowKey, locateColumn, rowHeight, rows]);
2157
+ const beginRangeDrag = useCallback2((event) => {
2158
+ if (!rangeSelection) return;
2159
+ if (event.button !== 0 || editing || event.target !== event.currentTarget) return;
2160
+ const cell = locateCell(event.clientX, event.clientY);
2161
+ if (!cell || columns[cell.columnIndex].rowSelection || columns[cell.columnIndex].rowDragHandle || columns[cell.columnIndex].rowNumber) return;
2162
+ rangeDragRef.current = { anchor: cell, moved: false };
2163
+ setSelectionRange(null);
2164
+ event.currentTarget.setPointerCapture(event.pointerId);
2165
+ }, [columns, editing, locateCell, rangeSelection]);
2166
+ const updateRangeDrag = useCallback2((event) => {
2167
+ const drag = rangeDragRef.current;
2168
+ if (!drag) return;
2169
+ const focus = locateCell(event.clientX, event.clientY);
2170
+ if (!focus || columns[focus.columnIndex].rowSelection || columns[focus.columnIndex].rowDragHandle || columns[focus.columnIndex].rowNumber) return;
2171
+ if (focus.rowIndex === drag.anchor.rowIndex && focus.columnIndex === drag.anchor.columnIndex) return;
2172
+ drag.moved = true;
2173
+ event.preventDefault();
2174
+ setSelectionRange((current) => current?.focus.rowIndex === focus.rowIndex && current.focus.columnIndex === focus.columnIndex ? current : { anchor: drag.anchor, focus });
2175
+ }, [columns, locateCell]);
2176
+ const endRangeDrag = useCallback2((event) => {
2177
+ const drag = rangeDragRef.current;
2178
+ if (!drag) return;
2179
+ rangeDragRef.current = null;
2180
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
2181
+ if (!drag.moved) return;
2182
+ suppressClickRef.current = true;
2183
+ setSelection(drag.anchor);
2184
+ scrollerRef.current?.focus({ preventScroll: true });
2185
+ }, [setSelection]);
2186
+ const selectRow = useCallback2((rowIndex, event, forceToggle = false) => {
2187
+ if (!rowSelection) return;
2188
+ const mode = typeof rowSelection === "object" ? rowSelection.mode ?? "multiple" : "multiple";
2189
+ const key = getRowKey(rows[rowIndex], rowIndex);
2190
+ let next;
2191
+ if (mode === "multiple" && event.shiftKey && rowAnchorRef.current !== null) {
2192
+ const start = Math.min(rowAnchorRef.current, rowIndex);
2193
+ const end = Math.max(rowAnchorRef.current, rowIndex);
2194
+ next = rows.slice(start, end + 1).map(getRowKey);
2195
+ } else if (mode === "multiple" && (forceToggle || event.ctrlKey || event.metaKey)) {
2196
+ next = selectedRowKeySet.has(key) ? rowKeys.filter((item) => item !== key) : [...rowKeys, key];
2197
+ } else {
2198
+ next = rowKeys.length === 1 && selectedRowKeySet.has(key) ? [] : [key];
2199
+ }
2200
+ rowAnchorRef.current = rowIndex;
2201
+ setRowKeys(next);
2202
+ }, [getRowKey, rowKeys, rowSelection, rows, selectedRowKeySet, setRowKeys]);
2203
+ const selectColumn = useCallback2((columnIndex, event) => {
2204
+ if (!columnSelection || columnIndex < 0) return;
2205
+ const mode = typeof columnSelection === "object" ? columnSelection.mode ?? "multiple" : "multiple";
2206
+ const key = columns[columnIndex].key;
2207
+ let next;
2208
+ if (mode === "multiple" && event.shiftKey && columnAnchorRef.current !== null) {
2209
+ const start = Math.min(columnAnchorRef.current, columnIndex);
2210
+ const end = Math.max(columnAnchorRef.current, columnIndex);
2211
+ next = columns.slice(start, end + 1).map((column) => column.key);
2212
+ } else if (mode === "multiple") {
2213
+ next = selectedColumnKeySet.has(key) ? columnKeys.filter((item) => item !== key) : [...columnKeys, key];
2214
+ } else {
2215
+ next = columnKeys.length === 1 && selectedColumnKeySet.has(key) ? [] : [key];
2216
+ }
2217
+ columnAnchorRef.current = columnIndex;
2218
+ setColumnKeys(next);
2219
+ }, [columnKeys, columnSelection, columns, selectedColumnKeySet, setColumnKeys]);
2220
+ const toggleColumnFromMenu = useCallback2((columnIndex) => {
2221
+ if (!columnSelection || columnIndex < 0) return;
2222
+ const key = columns[columnIndex].key;
2223
+ const mode = typeof columnSelection === "object" ? columnSelection.mode ?? "multiple" : "multiple";
2224
+ const selected = selectedColumnKeySet.has(key);
2225
+ const next = selected ? columnKeys.filter((value) => value !== key) : mode === "multiple" ? [...columnKeys, key] : [key];
2226
+ columnAnchorRef.current = columnIndex;
2227
+ setColumnKeys(next);
2228
+ }, [columnKeys, columnSelection, columns, selectedColumnKeySet, setColumnKeys]);
2229
+ const revealCell = useCallback2((cell) => {
2230
+ const scroller = scrollerRef.current;
2231
+ const metric = metrics[cell.columnIndex];
2232
+ const column = columns[cell.columnIndex];
2233
+ if (!scroller || !metric || !column) return;
2234
+ let nextLeft = scroller.scrollLeft;
2235
+ let nextTop = scroller.scrollTop;
2236
+ const span = getCellSpan(cell.rowIndex, cell.columnIndex);
2237
+ const colSpan = span?.colSpan ?? 1;
2238
+ const rowSpan = span?.rowSpan ?? 1;
2239
+ const cellWidth = getCellDisplayWidth(cell.columnIndex, colSpan);
2240
+ const horizontalFocusGap = 0;
2241
+ const verticalFocusGap = 0;
2242
+ if (column.fixed === void 0) {
2243
+ if (metric.left - nextLeft < fixedWidth) nextLeft = Math.max(0, metric.left - fixedWidth - horizontalFocusGap);
2244
+ if (metric.left + cellWidth - nextLeft > viewport.width - rightFixedWidth) nextLeft = metric.left + cellWidth - viewport.width + rightFixedWidth + horizontalFocusGap;
2245
+ }
2246
+ const rowTop = (fixedHeader ? 0 : bodyTop) + cell.rowIndex * rowHeight;
2247
+ const rowBottom = rowTop + rowHeight * rowSpan;
2248
+ if (rowTop < nextTop + verticalFocusGap) nextTop = Math.max(0, rowTop - verticalFocusGap);
2249
+ const viewportHeight = scroller.clientHeight;
2250
+ const visibleRowHeight = fixedHeader ? viewportHeight - bodyTop : viewportHeight;
2251
+ if (rowBottom > nextTop + visibleRowHeight - verticalFocusGap) nextTop = rowBottom - visibleRowHeight + verticalFocusGap;
2252
+ if (nextLeft === scroller.scrollLeft && nextTop === scroller.scrollTop) return;
2253
+ scroller.scrollLeft = nextLeft;
2254
+ scroller.scrollTop = nextTop;
2255
+ scrollRef.current = { left: scroller.scrollLeft, top: scroller.scrollTop };
2256
+ scheduleDraw();
2257
+ }, [bodyTop, columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, metrics, rightFixedWidth, rowHeight, scheduleDraw, viewport.width]);
2258
+ const beginEdit = useCallback2((cell) => {
2259
+ const column = columns[cell.columnIndex];
2260
+ if (!column?.editable || column.dataIndex === void 0) return;
2261
+ const value = rows[cell.rowIndex]?.[column.dataIndex];
2262
+ setDraft(value == null ? "" : String(value));
2263
+ const scroller = scrollerRef.current;
2264
+ const metric = metrics[cell.columnIndex];
2265
+ if (!scroller || !metric) {
2266
+ setEditing(cell);
2267
+ return;
2268
+ }
2269
+ let nextLeft = scroller.scrollLeft;
2270
+ let nextTop = scroller.scrollTop;
2271
+ const span = getCellSpan(cell.rowIndex, cell.columnIndex);
2272
+ const colSpan = span?.colSpan ?? 1;
2273
+ const rowSpan = span?.rowSpan ?? 1;
2274
+ const cellWidth = getCellDisplayWidth(cell.columnIndex, colSpan);
2275
+ const horizontalFocusGap = 0;
2276
+ const verticalFocusGap = 0;
2277
+ if (column.fixed === void 0) {
2278
+ if (metric.left - nextLeft < fixedWidth) nextLeft = Math.max(0, metric.left - fixedWidth - horizontalFocusGap);
2279
+ if (metric.left + cellWidth - nextLeft > viewport.width - rightFixedWidth) nextLeft = metric.left + cellWidth - viewport.width + rightFixedWidth + horizontalFocusGap;
2280
+ }
2281
+ const rowTop = (fixedHeader ? 0 : bodyTop) + cell.rowIndex * rowHeight;
2282
+ const rowBottom = rowTop + rowHeight * rowSpan;
2283
+ if (rowTop < nextTop + verticalFocusGap) nextTop = Math.max(0, rowTop - verticalFocusGap);
2284
+ const viewportHeight = scroller.clientHeight;
2285
+ const visibleRowHeight = fixedHeader ? viewportHeight - bodyTop : viewportHeight;
2286
+ if (rowBottom > nextTop + visibleRowHeight - verticalFocusGap) nextTop = rowBottom - visibleRowHeight + verticalFocusGap;
2287
+ const needsScroll = nextLeft !== scroller.scrollLeft || nextTop !== scroller.scrollTop;
2288
+ if (needsScroll) {
2289
+ scroller.scrollLeft = nextLeft;
2290
+ scroller.scrollTop = nextTop;
2291
+ scrollRef.current = { left: scroller.scrollLeft, top: scroller.scrollTop };
2292
+ scheduleDraw();
2293
+ requestAnimationFrame(() => {
2294
+ scrollRef.current = { left: scroller.scrollLeft, top: scroller.scrollTop };
2295
+ scheduleDraw();
2296
+ requestAnimationFrame(() => setEditing(cell));
2297
+ });
2298
+ return;
2299
+ }
2300
+ setEditing(cell);
2301
+ }, [columns, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, metrics, rightFixedWidth, rowHeight, rows, scheduleDraw, viewport.width]);
2302
+ const getCellEditKey = (cell) => `${typeof cell.rowKey}:${String(cell.rowKey)}\0${cell.columnKey}`;
2303
+ const markCellEdited = (cell, previousValue, nextValue) => {
2304
+ const key = getCellEditKey(cell);
2305
+ if (insertedRowKeys.has(cell.rowKey)) {
2306
+ originalCellValuesRef.current.delete(key);
2307
+ setEditedCellKeys((current) => {
2308
+ if (!current.has(key)) return current;
2309
+ const next = new Set(current);
2310
+ next.delete(key);
2311
+ return next;
2312
+ });
2313
+ return;
2314
+ }
2315
+ if (!originalCellValuesRef.current.has(key)) {
2316
+ originalCellValuesRef.current.set(key, String(previousValue ?? ""));
2317
+ }
2318
+ const differsFromOriginal = String(nextValue) !== originalCellValuesRef.current.get(key);
2319
+ setEditedCellKeys((current) => {
2320
+ if (current.has(key) === differsFromOriginal) return current;
2321
+ const next = new Set(current);
2322
+ if (differsFromOriginal) next.add(key);
2323
+ else next.delete(key);
2324
+ return next;
2325
+ });
2326
+ };
2327
+ const commitEdit = useCallback2((nextDraft, close = true) => {
2328
+ if (!editing) return;
2329
+ const committedDraft = nextDraft ?? draft;
2330
+ const column = columns[editing.columnIndex];
2331
+ const row = rows[editing.rowIndex];
2332
+ if (column?.dataIndex !== void 0 && row) {
2333
+ const previousValue = row[column.dataIndex];
2334
+ const committedValue = typeof previousValue === "number" && committedDraft !== "" ? Number(committedDraft) : committedDraft;
2335
+ if (String(previousValue ?? "") === String(committedValue ?? "")) {
2336
+ if (close) setEditing(null);
2337
+ return;
2338
+ }
2339
+ markCellEdited(editing, previousValue, committedValue);
2340
+ onCellChange?.({ ...editing, row, value: committedValue, previousValue });
2341
+ }
2342
+ if (close) setEditing(null);
2343
+ }, [columns, draft, editing, onCellChange, rows]);
2344
+ const renderEditor = () => {
2345
+ if (!editing || !editorStyle) return null;
2346
+ const column = columns[editing.columnIndex];
2347
+ if (column.editor?.type === "select") {
2348
+ return /* @__PURE__ */ jsx7(
2349
+ ChoiceEditor,
2350
+ {
2351
+ value: draft,
2352
+ options: column.editor.options,
2353
+ style: editorStyle,
2354
+ labels,
2355
+ onChange: setDraft,
2356
+ onCommit: (next) => commitEdit(next),
2357
+ onCancel: () => setEditing(null)
2358
+ }
2359
+ );
2360
+ }
2361
+ if (column.editor?.type === "time" || column.editor?.type === "time-range") {
2362
+ return /* @__PURE__ */ jsx7(
2363
+ CompactTimeEditor,
2364
+ {
2365
+ value: draft,
2366
+ range: column.editor.type === "time-range",
2367
+ showSeconds: timeFormatHasSeconds(column.editor.format, draft),
2368
+ style: editorStyle,
2369
+ labels,
2370
+ onChange: setDraft,
2371
+ onCommit: () => commitEdit(),
2372
+ onCancel: () => setEditing(null)
2373
+ }
2374
+ );
2375
+ }
2376
+ if (column.editor && ["date", "date-range", "date-time", "date-time-range", "year", "month"].includes(column.editor.type)) {
2377
+ return /* @__PURE__ */ jsx7(
2378
+ DateEditor,
2379
+ {
2380
+ editor: column.editor,
2381
+ value: draft,
2382
+ style: editorStyle,
2383
+ locale: language,
2384
+ labels,
2385
+ onChange: setDraft,
2386
+ onCommit: (next) => commitEdit(next),
2387
+ onCancel: () => setEditing(null)
2388
+ }
2389
+ );
2390
+ }
2391
+ const row = rows[editing.rowIndex];
2392
+ const cellValue = column.dataIndex === void 0 ? void 0 : row?.[column.dataIndex];
2393
+ return /* @__PURE__ */ jsx7(
2394
+ TextEditor,
2395
+ {
2396
+ value: draft,
2397
+ type: typeof cellValue === "number" ? "number" : "text",
2398
+ style: editorStyle,
2399
+ labels,
2400
+ onChange: setDraft,
2401
+ onCommit: (next) => commitEdit(next),
2402
+ onCancel: () => setEditing(null)
2403
+ }
2404
+ );
2405
+ };
2406
+ const handleKeyDown = useCallback2((event) => {
2407
+ if (editing || !selection) return;
2408
+ if (event.key === "Enter") {
2409
+ event.preventDefault();
2410
+ beginEdit(selection);
2411
+ return;
2412
+ }
2413
+ const delta = event.key === "ArrowUp" ? [-1, 0] : event.key === "ArrowDown" ? [1, 0] : event.key === "ArrowLeft" ? [0, -1] : event.key === "ArrowRight" ? [0, 1] : null;
2414
+ if (!delta) return;
2415
+ event.preventDefault();
2416
+ const targetRowIndex = Math.max(0, Math.min(rows.length - 1, selection.rowIndex + delta[0]));
2417
+ const targetColumnIndex = Math.max(0, Math.min(columns.length - 1, selection.columnIndex + delta[1]));
2418
+ const span = getCellSpan(targetRowIndex, targetColumnIndex);
2419
+ const rowIndex = span?.rowIndex ?? targetRowIndex;
2420
+ const columnIndex = span?.columnIndex ?? targetColumnIndex;
2421
+ const next = { rowIndex, columnIndex, rowKey: getRowKey(rows[rowIndex], rowIndex), columnKey: columns[columnIndex].key };
2422
+ setSelection(next);
2423
+ const scroller = scrollerRef.current;
2424
+ const metric = metrics[columnIndex];
2425
+ if (scroller && metric) {
2426
+ const rowTop = (fixedHeader ? 0 : bodyTop) + rowIndex * rowHeight;
2427
+ const rowBottom = rowTop + rowHeight * (span?.rowSpan ?? 1);
2428
+ const cellWidth = getCellDisplayWidth(columnIndex, span?.colSpan ?? 1);
2429
+ const visibleRowHeight = fixedHeader ? resolvedHeight - bodyTop : resolvedHeight;
2430
+ if (rowTop < scroller.scrollTop) scroller.scrollTop = rowTop;
2431
+ if (rowBottom > scroller.scrollTop + visibleRowHeight) scroller.scrollTop = rowBottom - visibleRowHeight;
2432
+ if (columns[columnIndex].fixed === void 0 && metric.left < scroller.scrollLeft + fixedWidth) scroller.scrollLeft = Math.max(0, metric.left - fixedWidth);
2433
+ if (columns[columnIndex].fixed === void 0 && metric.left + cellWidth > scroller.scrollLeft + viewport.width - rightFixedWidth) scroller.scrollLeft = metric.left + cellWidth - viewport.width + rightFixedWidth;
2434
+ }
2435
+ }, [beginEdit, bodyTop, columns, editing, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, getRowKey, metrics, resolvedHeight, rightFixedWidth, rowHeight, rows, selection, setSelection, viewport.width]);
2436
+ const editorStyle = useMemo(() => {
2437
+ if (!editing) return void 0;
2438
+ const metric = metrics[editing.columnIndex];
2439
+ const fixedSide = columns[editing.columnIndex].fixed;
2440
+ const span = getCellSpan(editing.rowIndex, editing.columnIndex);
2441
+ const cellWidth = getCellDisplayWidth(editing.columnIndex, span?.colSpan ?? 1);
2442
+ const cellHeight = rowHeight * (span?.rowSpan ?? 1);
2443
+ const cellLeft = fixedSide === "left" ? metric.left : fixedSide === "right" ? viewport.width - (rightFixedOffsets.get(editing.columnIndex) ?? 0) - metric.width : metric.left - scrollRef.current.left;
2444
+ return {
2445
+ left: (fixedSide ? cellLeft : Math.max(cellLeft, fixedWidth)) + 1,
2446
+ top: Math.max(fixedHeader ? bodyTop : 0, bodyTop + editing.rowIndex * rowHeight - scrollRef.current.top) + 1,
2447
+ width: cellWidth - 2,
2448
+ height: cellHeight - 2
2449
+ };
2450
+ }, [bodyTop, columns, editing, fixedHeader, fixedWidth, getCellDisplayWidth, getCellSpan, metrics, rightFixedOffsets, rowHeight, viewport.width]);
2451
+ const handleColumnPointerDown = useCallback2((event) => {
2452
+ const rect = event.currentTarget.getBoundingClientRect();
2453
+ const localY = event.clientY - rect.top;
2454
+ const headerY = fixedHeader ? 0 : -scrollRef.current.top;
2455
+ if (localY < headerY || localY >= headerY + headerHeight) return;
2456
+ const resizeIndex = locateResizeHandle(event.clientX);
2457
+ if (resizeIndex >= 0) {
2458
+ event.preventDefault();
2459
+ event.currentTarget.setPointerCapture(event.pointerId);
2460
+ columnDragRef.current = { type: "resize", columnIndex: resizeIndex, startX: event.clientX, startWidth: metrics[resizeIndex].width };
2461
+ suppressClickRef.current = true;
2462
+ setDragGuide(getDisplayedColumnLeft(resizeIndex) + metrics[resizeIndex].width);
2463
+ return;
2464
+ }
2465
+ }, [fixedHeader, getDisplayedColumnLeft, headerHeight, locateResizeHandle, metrics, setDragGuide]);
2466
+ const handleColumnPointerMove = useCallback2((event) => {
2467
+ const drag = columnDragRef.current;
2468
+ if (!drag) return;
2469
+ const width2 = Math.max(getMinimumColumnWidth(columns[drag.columnIndex], columnDraggable), Math.round(drag.startWidth + event.clientX - drag.startX));
2470
+ onColumnResize?.(columns[drag.columnIndex].key, width2);
2471
+ setDragGuide(getDisplayedColumnLeft(drag.columnIndex) + width2);
2472
+ }, [columnDraggable, columns, getDisplayedColumnLeft, onColumnResize, setDragGuide]);
2473
+ const handleColumnPointerUp = useCallback2((event) => {
2474
+ const drag = columnDragRef.current;
2475
+ if (!drag) return;
2476
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
2477
+ columnDragRef.current = null;
2478
+ setDragGuide(null);
2479
+ }, [setDragGuide]);
2480
+ const queueSortStateChange = useCallback2((next) => {
2481
+ setDisplaySortState(next);
2482
+ if (sortDebounceRef.current !== null) clearTimeout(sortDebounceRef.current);
2483
+ sortDebounceRef.current = setTimeout(() => {
2484
+ sortDebounceRef.current = null;
2485
+ onSortStateChange?.(next);
2486
+ }, 200);
2487
+ }, [onSortStateChange]);
2488
+ const renderHeaderIcons = (columnIndex, left) => {
2489
+ const column = columns[columnIndex];
2490
+ if (column.rowSelection || column.rowDragHandle || column.rowNumber) return null;
2491
+ const top = (headerHeight - 14) / 2;
2492
+ const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex));
2493
+ let right = left + metrics[columnIndex].width - (visibleActions.drag ? 18 : 4);
2494
+ const icons = [];
2495
+ if (visibleActions.drag) {
2496
+ const hovered = hoveredHeaderAction?.columnIndex === columnIndex && hoveredHeaderAction.action === "drag";
2497
+ icons.push(
2498
+ /* @__PURE__ */ jsx7(HeaderDragIcon, { className: `rvg-header-icon rvg-header-drag-icon${hovered ? " is-hovered" : ""}`, style: { left: left + metrics[columnIndex].width - 16, top } }, "drag")
2499
+ );
2500
+ }
2501
+ if (visibleActions.sort) {
2502
+ const hovered = hoveredHeaderAction?.columnIndex === columnIndex && hoveredHeaderAction.action === "sort";
2503
+ const direction = displaySortState?.columnKey === column.key ? displaySortState.direction : null;
2504
+ icons.push(
2505
+ /* @__PURE__ */ jsx7(HeaderSortIcon, { className: `rvg-header-icon${hovered ? " is-hovered" : ""}`, style: { left: right - 16, top }, direction }, "sort")
2506
+ );
2507
+ right -= 18;
2508
+ }
2509
+ if (visibleActions.filter) {
2510
+ const hovered = hoveredHeaderAction?.columnIndex === columnIndex && hoveredHeaderAction.action === "filter";
2511
+ icons.push(
2512
+ /* @__PURE__ */ jsx7(HeaderSearchIcon, { className: `rvg-header-icon rvg-header-search-icon${hovered ? " is-hovered" : ""}${filterValues[column.key] ? " is-active" : ""}`, style: { left: right - 16, top: top + 1 } }, "filter")
2513
+ );
2514
+ }
2515
+ return icons;
2516
+ };
2517
+ const headerTooltip = (() => {
2518
+ if (!visibleHeaderTooltip) return null;
2519
+ const { columnIndex, action } = visibleHeaderTooltip;
2520
+ const column = columns[columnIndex];
2521
+ if (!column) return null;
2522
+ const columnRight = getDisplayedColumnLeft(columnIndex) + metrics[columnIndex].width;
2523
+ const visibleActions = getVisibleHeaderActions(column, metrics[columnIndex].width, columnDraggable, measureHeaderTitleWidth(columnIndex));
2524
+ let anchorX = columnRight - (visibleActions.drag ? 18 : 4);
2525
+ let anchorLeft = anchorX;
2526
+ let label;
2527
+ if (action === "title") {
2528
+ const titleWidth = measureHeaderTitleWidth(columnIndex);
2529
+ const actionWidth = (Number(visibleActions.drag) + Number(visibleActions.filter) + Number(visibleActions.sort)) * 18;
2530
+ const contentWidth2 = Math.max(0, metrics[columnIndex].width - actionWidth);
2531
+ const textWidth = Math.min(titleWidth, Math.max(0, contentWidth2 - 20));
2532
+ const columnLeft = getDisplayedColumnLeft(columnIndex);
2533
+ anchorX = column.align === "right" ? columnLeft + contentWidth2 - 10 : column.align === "center" ? columnLeft + metrics[columnIndex].width / 2 + textWidth / 2 : columnLeft + 10 + textWidth;
2534
+ anchorLeft = column.align === "right" ? anchorX - textWidth : column.align === "center" ? columnLeft + metrics[columnIndex].width / 2 - textWidth / 2 : columnLeft + 10;
2535
+ label = column.title;
2536
+ } else if (action === "drag") {
2537
+ anchorX = columnRight;
2538
+ anchorLeft = anchorX;
2539
+ label = labels.dragColumn;
2540
+ } else if (action === "sort") {
2541
+ const direction = displaySortState?.columnKey === column.key ? displaySortState.direction : null;
2542
+ label = direction === "asc" ? labels.sortAsc : direction === "desc" ? labels.sortDesc : labels.sortBoth;
2543
+ } else {
2544
+ if (visibleActions.sort) anchorX -= 18;
2545
+ const value = filterValues[column.key];
2546
+ label = value ? labels.filterWithValue(value) : labels.filter;
2547
+ }
2548
+ const placement = anchorX + 328 > viewport.width ? "left" : "right";
2549
+ return {
2550
+ left: placement === "right" ? anchorX + 8 : Math.max(8, anchorLeft - 8),
2551
+ top: (fixedHeader ? 0 : -scrollPosition.top) + headerHeight / 2,
2552
+ label,
2553
+ placement
2554
+ };
2555
+ })();
2556
+ const getRangeBounds = () => selectionRange ? {
2557
+ rowStart: Math.min(selectionRange.anchor.rowIndex, selectionRange.focus.rowIndex),
2558
+ rowEnd: Math.max(selectionRange.anchor.rowIndex, selectionRange.focus.rowIndex),
2559
+ columnStart: Math.min(selectionRange.anchor.columnIndex, selectionRange.focus.columnIndex),
2560
+ columnEnd: Math.max(selectionRange.anchor.columnIndex, selectionRange.focus.columnIndex)
2561
+ } : null;
2562
+ const getRangeCells = () => {
2563
+ const bounds = getRangeBounds();
2564
+ if (!bounds) return [];
2565
+ const cells = [];
2566
+ for (let rowIndex = bounds.rowStart; rowIndex <= bounds.rowEnd; rowIndex += 1) {
2567
+ for (let columnIndex = bounds.columnStart; columnIndex <= bounds.columnEnd; columnIndex += 1) {
2568
+ const column = columns[columnIndex];
2569
+ if (column.rowSelection || column.rowDragHandle || column.rowNumber) continue;
2570
+ cells.push({ rowIndex, columnIndex, rowKey: getRowKey(rows[rowIndex], rowIndex), columnKey: column.key });
2571
+ }
2572
+ }
2573
+ return cells;
2574
+ };
2575
+ const getRangeText = () => {
2576
+ const bounds = getRangeBounds();
2577
+ if (!bounds) return "";
2578
+ const columnIndices = columns.map((column, index) => ({ column, index })).slice(bounds.columnStart, bounds.columnEnd + 1).filter(({ column }) => !column.rowSelection && !column.rowDragHandle && !column.rowNumber).map(({ index }) => index);
2579
+ return rows.slice(bounds.rowStart, bounds.rowEnd + 1).map((_, rowOffset) => columnIndices.map((columnIndex) => getCellLabel(bounds.rowStart + rowOffset, columnIndex)).join(" ")).join("\n");
2580
+ };
2581
+ const isCellInRange = (cell) => {
2582
+ const bounds = getRangeBounds();
2583
+ return Boolean(bounds && cell.rowIndex >= bounds.rowStart && cell.rowIndex <= bounds.rowEnd && cell.columnIndex >= bounds.columnStart && cell.columnIndex <= bounds.columnEnd);
2584
+ };
2585
+ const handleCopy = (event) => {
2586
+ const nativeSelection = window.getSelection();
2587
+ if (nativeSelection && !nativeSelection.isCollapsed && nativeSelection.toString()) return;
2588
+ if (selectionRange) {
2589
+ event.clipboardData.setData("text/plain", getRangeText());
2590
+ event.preventDefault();
2591
+ return;
2592
+ }
2593
+ if (!selection) return;
2594
+ const row = rows[selection.rowIndex];
2595
+ const column = columns[selection.columnIndex];
2596
+ if (!row || !column || getRowKey(row, selection.rowIndex) !== selection.rowKey || column.key !== selection.columnKey) return;
2597
+ event.clipboardData.setData("text/plain", getCellLabel(selection.rowIndex, selection.columnIndex));
2598
+ event.preventDefault();
2599
+ };
2600
+ const selectionIsCollapsed = () => {
2601
+ const nativeSelection = window.getSelection();
2602
+ return !nativeSelection || nativeSelection.isCollapsed;
2603
+ };
2604
+ const showToast = (type, message) => {
2605
+ if (toastTimerRef.current !== null) clearTimeout(toastTimerRef.current);
2606
+ setToast({ type, message });
2607
+ toastTimerRef.current = setTimeout(() => {
2608
+ toastTimerRef.current = null;
2609
+ setToast(null);
2610
+ }, 1800);
2611
+ };
2612
+ const showHeaderTooltipAfterDelay = (next) => {
2613
+ const nextKey = next ? `${next.columnIndex}:${next.action}` : "";
2614
+ if (nextKey && pendingHeaderTooltipKeyRef.current === nextKey) return;
2615
+ pendingHeaderTooltipKeyRef.current = nextKey;
2616
+ if (headerTooltipTimerRef.current !== null) clearTimeout(headerTooltipTimerRef.current);
2617
+ if (!next) {
2618
+ setVisibleHeaderTooltip(null);
2619
+ return;
2620
+ }
2621
+ setVisibleHeaderTooltip(null);
2622
+ headerTooltipTimerRef.current = setTimeout(() => {
2623
+ headerTooltipTimerRef.current = null;
2624
+ setVisibleHeaderTooltip(next);
2625
+ }, TOOLTIP_DELAY);
2626
+ };
2627
+ const showCellTooltip = (next) => {
2628
+ const nextKey = next ? `${next.rowIndex}:${next.columnIndex}:${next.label}:${next.color ?? ""}:${Number(next.annotation)}` : "";
2629
+ if (nextKey && pendingCellTooltipKeyRef.current === nextKey) return;
2630
+ pendingCellTooltipKeyRef.current = nextKey;
2631
+ if (cellTooltipTimerRef.current !== null) clearTimeout(cellTooltipTimerRef.current);
2632
+ setHoveredCellTooltip(next);
2633
+ };
2634
+ const showCellTooltipAfterDelay = (next) => {
2635
+ const nextKey = next ? `${next.rowIndex}:${next.columnIndex}:${next.label}:${next.color ?? ""}:${Number(next.annotation)}` : "";
2636
+ if (nextKey && pendingCellTooltipKeyRef.current === nextKey) return;
2637
+ pendingCellTooltipKeyRef.current = nextKey;
2638
+ if (cellTooltipTimerRef.current !== null) clearTimeout(cellTooltipTimerRef.current);
2639
+ if (!next) {
2640
+ setHoveredCellTooltip(null);
2641
+ return;
2642
+ }
2643
+ setHoveredCellTooltip(null);
2644
+ cellTooltipTimerRef.current = setTimeout(() => {
2645
+ cellTooltipTimerRef.current = null;
2646
+ setHoveredCellTooltip(next);
2647
+ }, TOOLTIP_DELAY);
2648
+ };
2649
+ const openCellContextMenu = (cell, clientX, clientY) => {
2650
+ const root = canvasRef.current?.getBoundingClientRect();
2651
+ if (!root) return;
2652
+ setInsertCounts({ before: 1, after: 1 });
2653
+ setAnnotationOpen(false);
2654
+ setSelection(cell);
2655
+ setContextMenu({
2656
+ type: "cell",
2657
+ ...cell,
2658
+ left: Math.min(Math.max(0, clientX - root.left), Math.max(0, viewport.width - 190)),
2659
+ top: Math.min(Math.max(0, clientY - root.top), Math.max(0, resolvedHeight - 350))
2660
+ });
2661
+ onCellContextMenu?.({ ...cell, row: rows[cell.rowIndex], clientX, clientY });
2662
+ };
2663
+ const openHeaderContextMenu = (columnIndex, clientX, clientY) => {
2664
+ const root = canvasRef.current?.getBoundingClientRect();
2665
+ if (!root || columnIndex < 0) return;
2666
+ setContextMenu({
2667
+ type: "header",
2668
+ columnIndex,
2669
+ left: Math.min(Math.max(0, clientX - root.left), Math.max(0, viewport.width - 180)),
2670
+ top: Math.min(Math.max(0, clientY - root.top), Math.max(0, resolvedHeight - 68))
2671
+ });
2672
+ };
2673
+ const getRowDragOffset = (rowIndex) => {
2674
+ if (!rowDragPreview || rowIndex === rowDragPreview.sourceIndex) return 0;
2675
+ if (rowDragPreview.sourceIndex < rowDragPreview.targetIndex) {
2676
+ return rowIndex > rowDragPreview.sourceIndex && rowIndex <= rowDragPreview.targetIndex ? -rowHeight : 0;
2677
+ }
2678
+ return rowIndex >= rowDragPreview.targetIndex && rowIndex < rowDragPreview.sourceIndex ? rowHeight : 0;
2679
+ };
2680
+ const renderAnnotationControls = (targetCells) => {
2681
+ const getKey = (cell) => `${typeof cell.rowKey}:${String(cell.rowKey)}\0${cell.columnKey}`;
2682
+ const hasAnnotation = targetCells.some((cell) => cellAnnotations.has(getKey(cell)));
2683
+ const openAnnotation = (event) => {
2684
+ if (!annotationOpen) {
2685
+ const existing = targetCells.map((cell) => cellAnnotations.get(getKey(cell))).find(Boolean);
2686
+ setAnnotationType(existing?.type ?? "corner");
2687
+ setAnnotationColor(existing?.color ?? ANNOTATION_COLORS[0]);
2688
+ setAnnotationContent(existing?.content ?? "");
2689
+ }
2690
+ const rect = event.currentTarget.getBoundingClientRect();
2691
+ const viewportWidth = window.visualViewport?.width ?? window.innerWidth;
2692
+ const viewportHeight = window.visualViewport?.height ?? window.innerHeight;
2693
+ const horizontal = rect.right + 216 > viewportWidth - 8 ? "left" : "right";
2694
+ const vertical = rect.top + 280 > viewportHeight - 8 ? "up" : "down";
2695
+ setAnnotationPlacement(`${horizontal}-${vertical}`);
2696
+ setAnnotationOpen(true);
2697
+ };
2698
+ const saveAnnotation = () => {
2699
+ setCellAnnotations((current) => {
2700
+ const next = new Map(current);
2701
+ targetCells.forEach((cell) => next.set(getKey(cell), { type: annotationType, color: annotationColor, content: annotationContent }));
2702
+ return next;
2703
+ });
2704
+ setAnnotationOpen(false);
2705
+ setContextMenu(null);
2706
+ };
2707
+ return /* @__PURE__ */ jsxs7(Fragment3, { children: [
2708
+ /* @__PURE__ */ jsxs7("div", { className: `rvg-annotation-submenu is-${annotationPlacement}`, onMouseEnter: openAnnotation, onMouseLeave: () => setAnnotationOpen(false), children: [
2709
+ /* @__PURE__ */ jsxs7("button", { type: "button", role: "menuitem", className: "rvg-context-annotation-trigger", "aria-expanded": annotationOpen, children: [
2710
+ /* @__PURE__ */ jsx7("span", { className: "rvg-context-menu-icon", children: /* @__PURE__ */ jsx7(ContextMenuIcon, { type: "annotation" }) }),
2711
+ /* @__PURE__ */ jsx7("span", { className: "rvg-context-menu-label", children: labels.annotation }),
2712
+ /* @__PURE__ */ jsx7(SubmenuArrowIcon, {})
2713
+ ] }),
2714
+ annotationOpen && /* @__PURE__ */ jsxs7("div", { className: `rvg-annotation-panel is-${annotationPlacement}`, role: "menu", children: [
2715
+ /* @__PURE__ */ jsxs7("div", { className: `rvg-annotation-types${annotationType === "corner" ? " is-corner" : ""}`, role: "group", "aria-label": labels.annotationMode, children: [
2716
+ /* @__PURE__ */ jsx7("button", { type: "button", className: annotationType === "background" ? "is-active" : "", onClick: () => setAnnotationType("background"), children: labels.annotationBackground }),
2717
+ /* @__PURE__ */ jsx7("button", { type: "button", className: annotationType === "corner" ? "is-active" : "", onClick: () => setAnnotationType("corner"), children: labels.annotationCorner })
2718
+ ] }),
2719
+ /* @__PURE__ */ jsx7("div", { className: "rvg-annotation-colors", children: ANNOTATION_COLORS.map((color, index) => /* @__PURE__ */ jsx7("button", { type: "button", className: annotationColor === color ? "is-active" : "", "aria-label": labels.annotationColor(index + 1), title: labels.annotationColor(index + 1), style: { backgroundColor: color, "--annotation-color": color }, onClick: () => setAnnotationColor(color) }, color)) }),
2720
+ /* @__PURE__ */ jsx7(
2721
+ "textarea",
2722
+ {
2723
+ className: "rvg-annotation-content",
2724
+ "aria-label": labels.annotationContent,
2725
+ placeholder: labels.annotationPlaceholder,
2726
+ rows: 3,
2727
+ maxLength: 100,
2728
+ value: annotationContent,
2729
+ onChange: (event) => setAnnotationContent(event.target.value)
2730
+ }
2731
+ ),
2732
+ /* @__PURE__ */ jsxs7("div", { className: "rvg-annotation-actions", children: [
2733
+ /* @__PURE__ */ jsxs7("span", { children: [
2734
+ annotationContent.length,
2735
+ "/100"
2736
+ ] }),
2737
+ /* @__PURE__ */ jsx7("button", { type: "button", onClick: saveAnnotation, children: labels.confirm })
2738
+ ] })
2739
+ ] })
2740
+ ] }),
2741
+ hasAnnotation && /* @__PURE__ */ jsxs7("button", { type: "button", role: "menuitem", onClick: () => {
2742
+ setCellAnnotations((current) => {
2743
+ const next = new Map(current);
2744
+ targetCells.forEach((cell) => next.delete(getKey(cell)));
2745
+ return next;
2746
+ });
2747
+ setContextMenu(null);
2748
+ }, children: [
2749
+ /* @__PURE__ */ jsx7("span", { className: "rvg-context-menu-icon", children: /* @__PURE__ */ jsx7(ContextMenuIcon, { type: "remove-annotation" }) }),
2750
+ /* @__PURE__ */ jsx7("span", { className: "rvg-context-menu-label", children: labels.removeAnnotation })
2751
+ ] })
2752
+ ] });
2753
+ };
2754
+ const createEmptyRows = (count) => {
2755
+ return Array.from({ length: count }, () => {
2756
+ const record = { id: createPendingInsertId() };
2757
+ if (typeof rowKey === "string") record[rowKey] = record.id;
2758
+ columns.forEach((column) => {
2759
+ if (column.dataIndex !== void 0) record[String(column.dataIndex)] = "";
2760
+ });
2761
+ return record;
2762
+ });
2763
+ };
2764
+ const insertRowsOptimistically = async (cell, position, count) => {
2765
+ const row = rows[cell.rowIndex];
2766
+ if (!row || !onInsertRows) return;
2767
+ const sourceRowIndex = getSourceRowIndexByKey(cell.rowKey, cell.rowIndex);
2768
+ const insertIndex = (sourceRowIndex >= 0 ? sourceRowIndex : cell.rowIndex) + (position === "after" ? 1 : 0);
2769
+ const insertedRows = createEmptyRows(count);
2770
+ const pending = {
2771
+ id: createPendingInsertId(),
2772
+ index: insertIndex,
2773
+ rows: insertedRows,
2774
+ keys: insertedRows.map((insertedRow, offset) => getDataRowKey(insertedRow, insertIndex + offset))
2775
+ };
2776
+ setContextMenu(null);
2777
+ setInsertBusy(true);
2778
+ setInsertedRowKeys((current) => {
2779
+ const next = new Set(current);
2780
+ pending.keys.forEach((key) => next.add(key));
2781
+ return next;
2782
+ });
2783
+ setPendingInserts((current) => [...current, pending]);
2784
+ try {
2785
+ await Promise.resolve(onInsertRows({ rowIndex: sourceRowIndex >= 0 ? sourceRowIndex : cell.rowIndex, row, position, count, insertedRows }));
2786
+ await waitForPaint();
2787
+ setInsertBusy(false);
2788
+ } catch (error) {
2789
+ setPendingInserts((current) => current.filter((insert) => insert.id !== pending.id));
2790
+ setInsertedRowKeys((current) => {
2791
+ const next = new Set(current);
2792
+ pending.keys.forEach((key) => next.delete(key));
2793
+ return next;
2794
+ });
2795
+ setInsertBusy(false);
2796
+ throw error;
2797
+ }
2798
+ };
2799
+ const renderCellText = (rowIndex, columnIndex, left) => {
2800
+ const column = columns[columnIndex];
2801
+ const span = getCellSpan(rowIndex, columnIndex);
2802
+ if (span && (span.rowIndex !== rowIndex || span.columnIndex !== columnIndex)) return null;
2803
+ const colSpan = span?.colSpan ?? 1;
2804
+ const rowSpan = span?.rowSpan ?? 1;
2805
+ const cellWidth = getCellDisplayWidth(columnIndex, colSpan);
2806
+ const cellHeight = rowHeight * rowSpan;
2807
+ const dragOffset = getRowDragOffset(rowIndex);
2808
+ const isDraggedSource = rowDragPreview?.sourceIndex === rowIndex;
2809
+ const currentScrollTop = scrollRef.current.top;
2810
+ const top = (fixedHeader ? 0 : bodyTop) + rowIndex * rowHeight - currentScrollTop;
2811
+ if (column.rowSelection) {
2812
+ const rowKey2 = getRowKey(rows[rowIndex], rowIndex);
2813
+ return /* @__PURE__ */ jsx7(
2814
+ "div",
2815
+ {
2816
+ className: "rvg-row-selection-icon",
2817
+ style: { left, top, width: cellWidth, height: cellHeight, opacity: isDraggedSource ? 0 : 1, transform: `translateY(${dragOffset}px)` },
2818
+ children: /* @__PURE__ */ jsx7(SelectionIcon, { checked: selectedRowKeySet.has(rowKey2), radio: rowSelectionMode === "single" })
2819
+ },
2820
+ `selection:${String(rowKey2)}`
2821
+ );
2822
+ }
2823
+ if (column.rowDragHandle) {
2824
+ return /* @__PURE__ */ jsx7(
2825
+ "div",
2826
+ {
2827
+ className: "rvg-row-drag-icon",
2828
+ style: { left, top, width: cellWidth, height: cellHeight, opacity: isDraggedSource ? 0 : 1, transform: `translateY(${dragOffset}px)` },
2829
+ "aria-hidden": "true",
2830
+ children: /* @__PURE__ */ jsx7(RowDragHandleIcon, {})
2831
+ },
2832
+ `drag:${String(getRowKey(rows[rowIndex], rowIndex))}`
2833
+ );
2834
+ }
2835
+ if (column.renderCell && !(editing?.rowIndex === rowIndex && editing.columnIndex === columnIndex)) {
2836
+ const row = rows[rowIndex];
2837
+ const value = column.dataIndex === void 0 ? void 0 : row[column.dataIndex];
2838
+ return /* @__PURE__ */ jsx7(
2839
+ "div",
2840
+ {
2841
+ className: "rvg-cell-render",
2842
+ style: {
2843
+ left,
2844
+ top,
2845
+ width: cellWidth,
2846
+ height: cellHeight,
2847
+ justifyContent: column.align === "right" ? "flex-end" : column.align === "center" ? "center" : "flex-start",
2848
+ opacity: isDraggedSource ? 0 : 1,
2849
+ transform: `translateY(${dragOffset}px)`
2850
+ },
2851
+ children: column.renderCell(value, row, rowIndex)
2852
+ },
2853
+ `render:${String(getRowKey(row, rowIndex))}:${column.key}`
2854
+ );
2855
+ }
2856
+ return null;
2857
+ };
2858
+ const renderHeaderTitle = (columnIndex, left) => {
2859
+ const column = columns[columnIndex];
2860
+ const currentScrollTop = scrollRef.current.top;
2861
+ if (column.rowNumber) {
2862
+ return /* @__PURE__ */ jsx7(
2863
+ "div",
2864
+ {
2865
+ className: "rvg-header-title",
2866
+ style: { left, top: fixedHeader ? 0 : -currentScrollTop, width: metrics[columnIndex].width, height: headerHeight, justifyContent: "center", padding: 0, textAlign: "center" },
2867
+ children: /* @__PURE__ */ jsx7("span", { children: column.title })
2868
+ },
2869
+ column.key
2870
+ );
2871
+ }
2872
+ if (column.rowSelection) {
2873
+ if (rowSelectionMode === "single") return null;
2874
+ const allSelected = rows.length > 0 && selectedRowKeySet.size === rows.length;
2875
+ const indeterminate = selectedRowKeySet.size > 0 && !allSelected;
2876
+ return /* @__PURE__ */ jsx7(
2877
+ "div",
2878
+ {
2879
+ className: "rvg-header-selection-icon",
2880
+ style: { left, top: fixedHeader ? 0 : -currentScrollTop, width: metrics[columnIndex].width, height: headerHeight },
2881
+ children: /* @__PURE__ */ jsx7(SelectionIcon, { checked: allSelected, indeterminate })
2882
+ },
2883
+ column.key
2884
+ );
2885
+ }
2886
+ return null;
2887
+ };
2888
+ const rangeHandlePosition = (() => {
2889
+ if (!rangeSelection) return null;
2890
+ const bounds = getRangeBounds();
2891
+ if (!bounds) return null;
2892
+ const metric = metrics[bounds.columnEnd];
2893
+ if (!metric) return null;
2894
+ const rawLeft = getDisplayedColumnLeft(bounds.columnEnd) + metric.width;
2895
+ const rawTop = bodyTop + (bounds.rowEnd + 1) * rowHeight - scrollPosition.top;
2896
+ const column = columns[bounds.columnEnd];
2897
+ const horizontalStart = column.fixed === void 0 ? fixedWidth : 0;
2898
+ const horizontalEnd = column.fixed === void 0 ? viewport.width - rightFixedWidth : viewport.width;
2899
+ const verticalStart = fixedHeader ? bodyTop : 0;
2900
+ if (rawLeft <= horizontalStart || rawLeft > horizontalEnd || rawTop <= verticalStart || rawTop > resolvedHeight) return null;
2901
+ return { left: rawLeft, top: rawTop, bounds };
2902
+ })();
2903
+ const selectionOutlineStyle = (() => {
2904
+ if (!selection || selectionRange) return null;
2905
+ const row = rows[selection.rowIndex];
2906
+ const metric = metrics[selection.columnIndex];
2907
+ const column = columns[selection.columnIndex];
2908
+ if (!row || !metric || !column || getRowKey(row, selection.rowIndex) !== selection.rowKey) return null;
2909
+ const span = getCellSpan(selection.rowIndex, selection.columnIndex);
2910
+ const rawLeft = getDisplayedColumnLeft(selection.columnIndex);
2911
+ const rawRight = rawLeft + getCellDisplayWidth(selection.columnIndex, span?.colSpan ?? 1);
2912
+ const rawTop = bodyTop + selection.rowIndex * rowHeight - scrollPosition.top;
2913
+ const rawBottom = rawTop + rowHeight * (span?.rowSpan ?? 1);
2914
+ const horizontalStart = column.fixed === void 0 ? fixedWidth : 0;
2915
+ const horizontalEnd = column.fixed === void 0 ? viewport.width - rightFixedWidth : viewport.width;
2916
+ const verticalStart = fixedHeader ? bodyTop : 0;
2917
+ if (rawRight <= horizontalStart || rawLeft >= horizontalEnd || rawBottom <= verticalStart || rawTop >= resolvedHeight) return null;
2918
+ const borderWidth = 2;
2919
+ const left = Math.round(Math.max(rawLeft - borderWidth, horizontalStart - 1));
2920
+ const right = Math.round(Math.min(rawRight + 1, horizontalEnd, viewport.width));
2921
+ const top = Math.round(Math.max(rawTop - borderWidth, verticalStart - 1));
2922
+ const bottom = Math.round(Math.min(rawBottom + 1, resolvedHeight));
2923
+ return {
2924
+ left,
2925
+ top,
2926
+ width: Math.max(0, right - left),
2927
+ height: Math.max(0, bottom - top)
2928
+ };
2929
+ })();
2930
+ const handleContextMenu = (event) => {
2931
+ event.preventDefault();
2932
+ event.stopPropagation();
2933
+ if (!contextMenuEnabled) return;
2934
+ const rect = event.currentTarget.getBoundingClientRect();
2935
+ const localY = event.clientY - rect.top;
2936
+ const headerY = fixedHeader ? 0 : -scrollRef.current.top;
2937
+ if (localY >= headerY && localY < headerY + headerHeight) {
2938
+ const columnIndex = locateColumn(event.clientX);
2939
+ if (columnIndex < 0) return;
2940
+ if (columns[columnIndex].rowSelection || columns[columnIndex].rowDragHandle || columns[columnIndex].rowNumber) return;
2941
+ openHeaderContextMenu(columnIndex, event.clientX, event.clientY);
2942
+ return;
2943
+ }
2944
+ const cell = locateCell(event.clientX, event.clientY);
2945
+ if (!cell) return;
2946
+ if (columns[cell.columnIndex].rowSelection || columns[cell.columnIndex].rowDragHandle || columns[cell.columnIndex].rowNumber) return;
2947
+ openCellContextMenu(cell, event.clientX, event.clientY);
2948
+ };
2949
+ const getMenuState = (item, context) => ({
2950
+ hidden: typeof item?.hidden === "function" ? item.hidden(context) : item?.hidden,
2951
+ disabled: typeof item?.disabled === "function" ? item.disabled(context) : item?.disabled
2952
+ });
2953
+ const renderContextMenuButton = (key, context, label, onClick, override, options) => {
2954
+ const state = getMenuState(override, context);
2955
+ if (state.hidden) return null;
2956
+ const disabled = state.disabled ?? options?.disabled;
2957
+ const danger = override?.danger ?? options?.danger;
2958
+ const icon = override?.icon ?? options?.icon;
2959
+ if (override?.render !== void 0 || override?.childrens !== void 0) {
2960
+ return renderContextMenuSubmenu(key, context, override?.label ?? label, override, icon, disabled, danger);
2961
+ }
2962
+ return /* @__PURE__ */ jsxs7(
2963
+ "button",
2964
+ {
2965
+ type: "button",
2966
+ role: "menuitem",
2967
+ className: danger ? "rvg-context-danger" : void 0,
2968
+ disabled,
2969
+ onClick: () => {
2970
+ if (disabled) return;
2971
+ if (override?.onClick) override.onClick(context);
2972
+ else onClick();
2973
+ setContextMenu(null);
2974
+ },
2975
+ children: [
2976
+ icon && /* @__PURE__ */ jsx7("span", { className: "rvg-context-menu-icon", children: icon }),
2977
+ /* @__PURE__ */ jsx7("span", { className: "rvg-context-menu-label", children: override?.label ?? label }),
2978
+ override?.shortcut && /* @__PURE__ */ jsx7("span", { className: "rvg-context-menu-shortcut", children: override.shortcut })
2979
+ ]
2980
+ },
2981
+ key
2982
+ );
2983
+ };
2984
+ const renderCustomContextMenuItem = (item, context) => {
2985
+ if (!item.label || !item.onClick && item.childrens === void 0 && item.render === void 0) return null;
2986
+ return renderContextMenuButton(item.key, context, item.label, () => item.onClick?.(context), item);
2987
+ };
2988
+ const renderContextMenuSubmenu = (key, context, label, item, icon, disabled, danger) => {
2989
+ let separatorPending = false;
2990
+ const content = item.render !== void 0 ? typeof item.render === "function" ? item.render(context) : item.render : item.childrens?.map((child, index) => {
2991
+ if (child === "|") {
2992
+ separatorPending = true;
2993
+ return null;
2994
+ }
2995
+ const node = renderCustomContextMenuItem(child, context);
2996
+ if (!node) return null;
2997
+ const wrapped = /* @__PURE__ */ jsxs7(Fragment2, { children: [
2998
+ separatorPending && /* @__PURE__ */ jsx7("div", { className: "rvg-context-separator" }),
2999
+ node
3000
+ ] }, child.key);
3001
+ separatorPending = false;
3002
+ return wrapped;
3003
+ });
3004
+ return /* @__PURE__ */ jsxs7("div", { className: "rvg-context-submenu", children: [
3005
+ /* @__PURE__ */ jsxs7("button", { type: "button", role: "menuitem", className: danger ? "rvg-context-danger" : void 0, disabled, "aria-haspopup": "menu", children: [
3006
+ icon && /* @__PURE__ */ jsx7("span", { className: "rvg-context-menu-icon", children: icon }),
3007
+ /* @__PURE__ */ jsx7("span", { className: "rvg-context-menu-label", children: label }),
3008
+ /* @__PURE__ */ jsx7(SubmenuArrowIcon, {})
3009
+ ] }),
3010
+ /* @__PURE__ */ jsx7("div", { className: "rvg-context-submenu-panel", role: "menu", children: content })
3011
+ ] }, key);
3012
+ };
3013
+ const renderConfiguredContextMenu = (items, context, renderBuiltin) => {
3014
+ const nodes = [];
3015
+ let separatorPending = false;
3016
+ items.forEach((item, index) => {
3017
+ if (item === "|") {
3018
+ if (nodes.length > 0) separatorPending = true;
3019
+ return;
3020
+ }
3021
+ const override = typeof item === "string" ? void 0 : item;
3022
+ const key = typeof item === "string" ? item : item.key;
3023
+ if (override && getMenuState(override, context).hidden) return;
3024
+ const node = renderBuiltin(key, override) ?? (override ? renderCustomContextMenuItem(override, context) : null);
3025
+ if (!node) return;
3026
+ if (separatorPending) {
3027
+ nodes.push(/* @__PURE__ */ jsx7("div", { className: "rvg-context-separator" }, `separator:${index}`));
3028
+ separatorPending = false;
3029
+ }
3030
+ nodes.push(/* @__PURE__ */ jsx7(Fragment2, { children: node }, typeof item === "string" ? `${item}:${index}` : item.key));
3031
+ });
3032
+ return nodes;
3033
+ };
3034
+ const renderSummaryCell = (columnIndex, left) => {
3035
+ const column = columns[columnIndex];
3036
+ const isUtility = column.rowSelection || column.rowDragHandle || column.rowNumber;
3037
+ const summaryContent = summaryValues.has(column.key) ? summaryValues.get(column.key) : summaryEmptyValue;
3038
+ const content = column.rowNumber ? language === "zh-CN" ? "\u5408\u8BA1" : "Total" : summaryPending ? /* @__PURE__ */ jsx7("span", { className: "rvg-summary-skeleton" }) : summaryContent;
3039
+ return /* @__PURE__ */ jsx7(
3040
+ "div",
3041
+ {
3042
+ className: `rvg-summary-cell${isUtility ? " is-utility" : ""}${column.summary ? " is-summary" : ""}${summaryVerticalBordered ? "" : " is-borderless"}`,
3043
+ style: {
3044
+ left,
3045
+ width: metrics[columnIndex].width,
3046
+ justifyContent: column.align === "right" ? "flex-end" : column.align === "center" || isUtility ? "center" : "flex-start"
3047
+ },
3048
+ children: content
3049
+ },
3050
+ `summary:${column.key}`
3051
+ );
3052
+ };
3053
+ const rootStyle = {
3054
+ width,
3055
+ height,
3056
+ minHeight: typeof height === "number" ? void 0 : FALLBACK_HEIGHT,
3057
+ ...stripedColor ? { "--rvg-color-stripe": stripedColor } : null,
3058
+ ...style
3059
+ };
3060
+ const currentScroll = scrollRef.current;
3061
+ const scrollerBottom = Math.max(0, bottomSummaryHeight - horizontalScrollbarHeight);
3062
+ return /* @__PURE__ */ jsxs7("div", { className: `rvg-root${!hasVerticalBorders ? " is-vertical-borderless" : ""}${hasStripedRows ? " is-striped" : ""}${insertBusy ? " is-inserting" : ""}${deleteBusy ? " is-deleting" : ""} ${loading && !hasCompletedLoadRef.current && !hasCustomLoading ? "is-initial-loading" : ""} ${className}`, style: rootStyle, children: [
3063
+ /* @__PURE__ */ jsxs7("div", { ref: scrollerRef, className: `rvg-scroller${contextMenu ? " is-context-menu-open" : ""}`, style: { bottom: scrollerBottom }, onScroll: handleScroll, onCopy: handleCopy, onContextMenu: handleContextMenu, tabIndex: 0, role: "grid", "aria-label": ariaLabel, "aria-rowcount": rows.length, "aria-colcount": columns.length, onKeyDown: handleKeyDown, children: [
3064
+ /* @__PURE__ */ jsx7(
3065
+ "canvas",
3066
+ {
3067
+ ref: canvasRef,
3068
+ className: "rvg-canvas",
3069
+ style: { width: viewport.width, height: resolvedHeight },
3070
+ onMouseMove: (event) => {
3071
+ if (columnDragRef.current) return;
3072
+ const rect = event.currentTarget.getBoundingClientRect();
3073
+ const localY = event.clientY - rect.top;
3074
+ const headerY = fixedHeader ? 0 : -scrollRef.current.top;
3075
+ const columnIndex = locateColumn(event.clientX);
3076
+ const inHeader = localY >= headerY && localY < headerY + headerHeight;
3077
+ const hoveredAction = inHeader ? locateHeaderAction(event.clientX, columnIndex) : null;
3078
+ const hoveredDragHandle = inHeader && locateHeaderDragHandle(event.clientX, columnIndex);
3079
+ const hoveredTitle = inHeader && !hoveredAction && !hoveredDragHandle && columnIndex >= 0 && Boolean(columns[columnIndex]?.title) && (tooltipConfig.header || isHeaderTitleTruncated(columnIndex));
3080
+ const nextHover = hoveredAction ? { columnIndex, action: hoveredAction } : hoveredDragHandle ? { columnIndex, action: "drag" } : hoveredTitle ? { columnIndex, action: "title" } : null;
3081
+ const previousHover = hoveredHeaderActionRef.current;
3082
+ if (previousHover?.columnIndex !== nextHover?.columnIndex || previousHover?.action !== nextHover?.action) {
3083
+ hoveredHeaderActionRef.current = nextHover;
3084
+ setHoveredHeaderAction(nextHover);
3085
+ showHeaderTooltipAfterDelay(nextHover);
3086
+ scheduleDraw();
3087
+ }
3088
+ if (inHeader) {
3089
+ if (hoveredRowIndexRef.current !== null) {
3090
+ hoveredRowIndexRef.current = null;
3091
+ scheduleDraw();
3092
+ }
3093
+ showCellTooltip(null);
3094
+ } else {
3095
+ showHeaderTooltipAfterDelay(null);
3096
+ const cell = locateCell(event.clientX, event.clientY);
3097
+ const nextHoveredRowIndex = cell?.rowIndex ?? null;
3098
+ if (hoveredRowIndexRef.current !== nextHoveredRowIndex) {
3099
+ hoveredRowIndexRef.current = nextHoveredRowIndex;
3100
+ scheduleDraw();
3101
+ }
3102
+ if (!cell) {
3103
+ showCellTooltipAfterDelay(null);
3104
+ } else {
3105
+ const annotation = cellAnnotations.get(`${typeof cell.rowKey}:${String(cell.rowKey)}\0${cell.columnKey}`);
3106
+ if (annotation?.content.trim()) {
3107
+ const cellLeft = getDisplayedColumnLeft(cell.columnIndex);
3108
+ const cellTop = bodyTop + cell.rowIndex * rowHeight - scrollRef.current.top;
3109
+ const nextTooltip = { rowIndex: cell.rowIndex, columnIndex: cell.columnIndex, left: cellLeft + metrics[cell.columnIndex].width + 6, top: cellTop + rowHeight / 2, label: annotation.content, color: annotation.color, annotation: true };
3110
+ showCellTooltip(nextTooltip);
3111
+ } else {
3112
+ const label = getCellLabel(cell.rowIndex, cell.columnIndex);
3113
+ const context = event.currentTarget.getContext("2d");
3114
+ if (context) context.font = "13px Inter, ui-sans-serif, system-ui, sans-serif";
3115
+ const truncated = Boolean(label && context && context.measureText(label).width + 20 > metrics[cell.columnIndex].width);
3116
+ if (!label || !tooltipConfig.cell && !truncated) {
3117
+ showCellTooltipAfterDelay(null);
3118
+ } else {
3119
+ const cellLeft = getDisplayedColumnLeft(cell.columnIndex);
3120
+ const cellTop = bodyTop + cell.rowIndex * rowHeight - scrollRef.current.top;
3121
+ const column = columns[cell.columnIndex];
3122
+ const labelWidth = context?.measureText(label).width ?? 0;
3123
+ const textWidth = Math.min(labelWidth, Math.max(0, metrics[cell.columnIndex].width - 20));
3124
+ const textRight = column.align === "right" ? cellLeft + metrics[cell.columnIndex].width - 10 : column.align === "center" ? cellLeft + metrics[cell.columnIndex].width / 2 + textWidth / 2 : cellLeft + 10 + textWidth;
3125
+ const textLeft = column.align === "right" ? textRight - textWidth : column.align === "center" ? cellLeft + metrics[cell.columnIndex].width / 2 - textWidth / 2 : cellLeft + 10;
3126
+ const localX = event.clientX - rect.left;
3127
+ const localY2 = event.clientY - rect.top;
3128
+ const textHeight = 22;
3129
+ const textTop = cellTop + (rowHeight - textHeight) / 2;
3130
+ const textBottom = textTop + textHeight;
3131
+ const hitPadding = column.renderCell ? 8 : 0;
3132
+ const isInsideText = localX >= textLeft - hitPadding && localX <= textRight + hitPadding && localY2 >= textTop && localY2 <= textBottom;
3133
+ if (!isInsideText) {
3134
+ showCellTooltipAfterDelay(null);
3135
+ } else {
3136
+ const placement = textRight + 330 > viewport.width ? "left" : "right";
3137
+ const nextTooltip = {
3138
+ rowIndex: cell.rowIndex,
3139
+ columnIndex: cell.columnIndex,
3140
+ left: placement === "right" ? textRight + 16 : Math.max(8, textLeft - 16),
3141
+ top: cellTop + rowHeight / 2,
3142
+ label,
3143
+ placement
3144
+ };
3145
+ showCellTooltipAfterDelay(nextTooltip);
3146
+ }
3147
+ }
3148
+ }
3149
+ }
3150
+ }
3151
+ const cursor = inHeader && locateResizeHandle(event.clientX) >= 0 ? "col-resize" : hoveredAction ? "pointer" : hoveredDragHandle ? "move" : columnIndex >= 0 && columns[columnIndex].rowSelection ? "pointer" : !inHeader && rowDraggable && columnIndex >= 0 && columns[columnIndex].rowDragHandle ? "move" : "default";
3152
+ if (event.currentTarget.style.cursor !== cursor) event.currentTarget.style.cursor = cursor;
3153
+ },
3154
+ onMouseLeave: (event) => {
3155
+ event.currentTarget.style.cursor = "default";
3156
+ showCellTooltip(null);
3157
+ showHeaderTooltipAfterDelay(null);
3158
+ if (hoveredHeaderActionRef.current) {
3159
+ hoveredHeaderActionRef.current = null;
3160
+ setHoveredHeaderAction(null);
3161
+ scheduleDraw();
3162
+ }
3163
+ if (hoveredRowIndexRef.current !== null) {
3164
+ hoveredRowIndexRef.current = null;
3165
+ scheduleDraw();
3166
+ }
3167
+ },
3168
+ onPointerDown: (event) => {
3169
+ handleColumnPointerDown(event);
3170
+ beginRangeDrag(event);
3171
+ },
3172
+ onPointerMove: (event) => {
3173
+ handleColumnPointerMove(event);
3174
+ updateRangeDrag(event);
3175
+ },
3176
+ onPointerUp: (event) => {
3177
+ handleColumnPointerUp(event);
3178
+ endRangeDrag(event);
3179
+ },
3180
+ onPointerCancel: (event) => {
3181
+ handleColumnPointerUp(event);
3182
+ endRangeDrag(event);
3183
+ },
3184
+ onClick: (event) => {
3185
+ if (suppressClickRef.current) {
3186
+ suppressClickRef.current = false;
3187
+ return;
3188
+ }
3189
+ const rect = event.currentTarget.getBoundingClientRect();
3190
+ const localY = event.clientY - rect.top;
3191
+ const headerY = fixedHeader ? 0 : -scrollRef.current.top;
3192
+ if (localY >= headerY && localY < headerY + headerHeight) {
3193
+ clickedCellRef.current = null;
3194
+ const columnIndex = locateColumn(event.clientX);
3195
+ const action = locateHeaderAction(event.clientX, columnIndex);
3196
+ if (action === "sort") {
3197
+ const columnKey = columns[columnIndex].key;
3198
+ const next = displaySortState?.columnKey !== columnKey ? { columnKey, direction: "asc" } : displaySortState.direction === "asc" ? { columnKey, direction: "desc" } : null;
3199
+ queueSortStateChange(next);
3200
+ return;
3201
+ }
3202
+ if (action === "filter") {
3203
+ const left = Math.min(Math.max(0, getDisplayedColumnLeft(columnIndex)), Math.max(0, viewport.width - 220));
3204
+ setFilterEditor({ columnIndex, left, top: Math.max(0, headerY + headerHeight), draft: filterValues[columns[columnIndex].key] ?? "" });
3205
+ return;
3206
+ }
3207
+ if (locateHeaderDragHandle(event.clientX, columnIndex)) return;
3208
+ if (columnIndex >= 0 && columns[columnIndex].rowSelection && rowSelection) {
3209
+ setRowKeys(rowKeys.length === rows.length ? [] : rows.map(getRowKey));
3210
+ } else if (columnIndex >= 0 && (columns[columnIndex].rowDragHandle || columns[columnIndex].rowNumber)) {
3211
+ return;
3212
+ } else {
3213
+ if (event.detail > 1) return;
3214
+ selectColumn(columnIndex, event);
3215
+ }
3216
+ return;
3217
+ }
3218
+ const cell = locateCell(event.clientX, event.clientY);
3219
+ if (!cell) return;
3220
+ if (columns[cell.columnIndex].rowDragHandle || columns[cell.columnIndex].rowNumber) {
3221
+ clickedCellRef.current = null;
3222
+ return;
3223
+ }
3224
+ const clickedRowSelector = Boolean(columns[cell.columnIndex].rowSelection);
3225
+ if (clickedRowSelector) {
3226
+ clickedCellRef.current = null;
3227
+ selectRow(cell.rowIndex, event, true);
3228
+ return;
3229
+ }
3230
+ if (event.detail > 1) return;
3231
+ clickedCellRef.current = cell;
3232
+ setSelectionRange(null);
3233
+ scrollerRef.current?.focus({ preventScroll: true });
3234
+ setSelection(cell);
3235
+ if (!hasRowSelectionColumn) {
3236
+ selectRow(cell.rowIndex, event);
3237
+ }
3238
+ revealCell(cell);
3239
+ },
3240
+ onDoubleClick: (event) => {
3241
+ const cell = clickedCellRef.current ?? locateCell(event.clientX, event.clientY);
3242
+ if (cell) beginEdit(cell);
3243
+ },
3244
+ onContextMenu: handleContextMenu
3245
+ }
3246
+ ),
3247
+ /* @__PURE__ */ jsxs7("div", { className: "rvg-text-layer", style: { width: viewport.width, height: resolvedHeight, marginTop: -resolvedHeight }, onContextMenu: handleContextMenu, children: [
3248
+ /* @__PURE__ */ jsx7("div", { className: "rvg-text-scroll-clip", style: { left: fixedWidth, right: rightFixedWidth }, children: /* @__PURE__ */ jsxs7(
3249
+ "div",
3250
+ {
3251
+ ref: scrollingTextRef,
3252
+ className: "rvg-text-scroll-content",
3253
+ style: { left: -fixedWidth, width: contentWidth, transform: `translateX(${-currentScroll.left}px)` },
3254
+ children: [
3255
+ columns.map((column, columnIndex) => column.fixed ? null : columnIndex >= domRange.columnStart && columnIndex < domRange.columnEnd ? /* @__PURE__ */ jsx7("span", { children: renderHeaderTitle(columnIndex, metrics[columnIndex].left) }, column.key) : null),
3256
+ /* @__PURE__ */ jsx7("div", { className: "rvg-body-text-clip", style: { top: fixedHeader ? bodyTop : 0, bottom: 0 }, children: Array.from({ length: domRange.rowEnd - domRange.rowStart }, (_, offset) => domRange.rowStart + offset).flatMap((rowIndex) => columns.map((column, columnIndex) => {
3257
+ if (column.fixed || columnIndex < domRange.columnStart || columnIndex >= domRange.columnEnd) return null;
3258
+ return renderCellText(rowIndex, columnIndex, metrics[columnIndex].left);
3259
+ })) })
3260
+ ]
3261
+ }
3262
+ ) }),
3263
+ /* @__PURE__ */ jsx7("div", { className: "rvg-body-text-clip", style: { top: fixedHeader ? bodyTop : 0, bottom: 0 }, children: Array.from({ length: domRange.rowEnd - domRange.rowStart }, (_, offset) => domRange.rowStart + offset).flatMap((rowIndex) => columns.map((column, columnIndex) => {
3264
+ if (!column.fixed) return null;
3265
+ const left = column.fixed === "left" ? metrics[columnIndex].left : viewport.width - (rightFixedOffsets.get(columnIndex) ?? 0) - metrics[columnIndex].width;
3266
+ return renderCellText(rowIndex, columnIndex, left);
3267
+ })) }),
3268
+ columns.map((column, columnIndex) => column.fixed === "left" ? renderHeaderTitle(columnIndex, metrics[columnIndex].left) : null),
3269
+ columns.map((column, columnIndex) => column.fixed === "right" ? renderHeaderTitle(columnIndex, viewport.width - (rightFixedOffsets.get(columnIndex) ?? 0) - metrics[columnIndex].width) : null)
3270
+ ] }),
3271
+ /* @__PURE__ */ jsx7("div", { className: "rvg-spacer", style: { width: contentWidth, height: contentHeight + bodyTop, marginTop: -resolvedHeight } })
3272
+ ] }),
3273
+ hasSummaryRow && /* @__PURE__ */ jsxs7(
3274
+ "div",
3275
+ {
3276
+ className: `rvg-summary-row is-${summaryPosition}${hasVerticalBorders ? "" : " is-vertical-borderless"}`,
3277
+ style: { height: rowHeight, top: summaryPosition === "top" ? headerHeight : void 0 },
3278
+ children: [
3279
+ /* @__PURE__ */ jsx7("div", { className: "rvg-summary-scroll-clip", style: { left: fixedWidth, right: rightFixedWidth }, children: /* @__PURE__ */ jsx7(
3280
+ "div",
3281
+ {
3282
+ className: "rvg-summary-scroll-content",
3283
+ style: { left: -fixedWidth, width: contentWidth, transform: `translateX(${-currentScroll.left}px)` },
3284
+ children: columns.map((column, columnIndex) => column.fixed ? null : renderSummaryCell(columnIndex, metrics[columnIndex].left))
3285
+ }
3286
+ ) }),
3287
+ /* @__PURE__ */ jsxs7("div", { className: "rvg-summary-fixed-layer", children: [
3288
+ columns.map((column, columnIndex) => column.fixed === "left" ? renderSummaryCell(columnIndex, metrics[columnIndex].left) : null),
3289
+ columns.map((column, columnIndex) => column.fixed === "right" ? renderSummaryCell(columnIndex, viewport.width - (rightFixedOffsets.get(columnIndex) ?? 0) - metrics[columnIndex].width) : null)
3290
+ ] }),
3291
+ fixedWidth > 0 && currentScroll.left > 0 && /* @__PURE__ */ jsx7("div", { className: "rvg-summary-fixed-shadow is-left", style: { left: fixedWidth } }),
3292
+ rightFixedWidth > 0 && currentScroll.left < contentWidth - viewport.width && /* @__PURE__ */ jsx7("div", { className: "rvg-summary-fixed-shadow is-right", style: { right: rightFixedWidth } }),
3293
+ summaryVerticalBordered && fixedWidth > 0 && /* @__PURE__ */ jsx7("div", { className: "rvg-summary-fixed-border is-left", style: { left: fixedWidth - 1 } }),
3294
+ summaryVerticalBordered && rightFixedWidth > 0 && /* @__PURE__ */ jsx7("div", { className: "rvg-summary-fixed-border is-right", style: { right: rightFixedWidth } })
3295
+ ]
3296
+ }
3297
+ ),
3298
+ /* @__PURE__ */ jsxs7("div", { ref: headerIconsRef, className: "rvg-header-icons", style: { height: headerHeight, transform: `translateY(${fixedHeader ? 0 : -scrollRef.current.top}px)` }, children: [
3299
+ /* @__PURE__ */ jsx7("div", { className: "rvg-header-scroll-clip", style: { left: fixedWidth, right: rightFixedWidth }, children: /* @__PURE__ */ jsx7(
3300
+ "div",
3301
+ {
3302
+ ref: scrollingHeaderIconsRef,
3303
+ className: "rvg-header-icon-layer rvg-header-icon-layer-scroll",
3304
+ style: { left: -fixedWidth, width: contentWidth, transform: `translateX(${-scrollRef.current.left}px)` },
3305
+ children: columns.map((column, index) => column.fixed ? null : /* @__PURE__ */ jsx7("span", { children: renderHeaderIcons(index, metrics[index].left) }, column.key))
3306
+ }
3307
+ ) }),
3308
+ /* @__PURE__ */ jsxs7("div", { className: "rvg-header-icon-layer", children: [
3309
+ columns.map((column, index) => column.fixed === "left" ? /* @__PURE__ */ jsx7("span", { children: renderHeaderIcons(index, metrics[index].left) }, column.key) : null),
3310
+ columns.map((column, index) => column.fixed === "right" ? /* @__PURE__ */ jsx7("span", { children: renderHeaderIcons(index, viewport.width - (rightFixedOffsets.get(index) ?? 0) - metrics[index].width) }, column.key) : null)
3311
+ ] })
3312
+ ] }),
3313
+ headerTooltip && /* @__PURE__ */ jsx7("div", { className: `rvg-header-tooltip is-${headerTooltip.placement}`, role: "tooltip", style: { left: headerTooltip.left, top: headerTooltip.top }, children: headerTooltip.label }),
3314
+ hoveredCellTooltip && /* @__PURE__ */ jsx7(
3315
+ "div",
3316
+ {
3317
+ className: `rvg-cell-tooltip${hoveredCellTooltip.annotation ? " is-annotation" : ` is-${hoveredCellTooltip.placement ?? "right"}`}`,
3318
+ role: "tooltip",
3319
+ style: {
3320
+ left: hoveredCellTooltip.left,
3321
+ top: hoveredCellTooltip.top,
3322
+ ...hoveredCellTooltip.color ? {
3323
+ backgroundColor: hoveredCellTooltip.color,
3324
+ color: (() => {
3325
+ const hex = hoveredCellTooltip.color.slice(1);
3326
+ const red = Number.parseInt(hex.slice(0, 2), 16);
3327
+ const green = Number.parseInt(hex.slice(2, 4), 16);
3328
+ const blue = Number.parseInt(hex.slice(4, 6), 16);
3329
+ return red * 0.299 + green * 0.587 + blue * 0.114 > 165 ? "#202124" : "#ffffff";
3330
+ })()
3331
+ } : {}
3332
+ },
3333
+ children: hoveredCellTooltip.label
3334
+ }
3335
+ ),
3336
+ rangeHandlePosition && /* @__PURE__ */ jsx7(
3337
+ "div",
3338
+ {
3339
+ className: "rvg-range-handle",
3340
+ style: { left: rangeHandlePosition.left, top: rangeHandlePosition.top },
3341
+ role: "presentation",
3342
+ onPointerDown: (event) => {
3343
+ event.preventDefault();
3344
+ event.stopPropagation();
3345
+ const { rowStart, rowEnd, columnStart, columnEnd } = rangeHandlePosition.bounds;
3346
+ const anchor = { rowIndex: rowStart, columnIndex: columnStart, rowKey: getRowKey(rows[rowStart], rowStart), columnKey: columns[columnStart].key };
3347
+ const focus = { rowIndex: rowEnd, columnIndex: columnEnd, rowKey: getRowKey(rows[rowEnd], rowEnd), columnKey: columns[columnEnd].key };
3348
+ rangeDragRef.current = { anchor, moved: true };
3349
+ setSelectionRange({ anchor, focus });
3350
+ event.currentTarget.setPointerCapture(event.pointerId);
3351
+ },
3352
+ onPointerMove: updateRangeDrag,
3353
+ onPointerUp: endRangeDrag,
3354
+ onPointerCancel: endRangeDrag,
3355
+ onClick: () => {
3356
+ suppressClickRef.current = false;
3357
+ }
3358
+ }
3359
+ ),
3360
+ selectionOutlineStyle && /* @__PURE__ */ jsx7("div", { ref: selectionFocusRef, className: "rvg-selection-focus", style: selectionOutlineStyle, "aria-hidden": "true" }),
3361
+ contextMenu && /* @__PURE__ */ jsx7("div", { ref: contextMenuRef, className: "rvg-context-menu", role: "menu", style: { left: contextMenu.left, top: contextMenu.top }, children: contextMenu.type === "header" ? (() => {
3362
+ const column = columns[contextMenu.columnIndex];
3363
+ const headerContext = { column, columnIndex: contextMenu.columnIndex };
3364
+ return renderConfiguredContextMenu(resolveContextMenuSection(customContextMenuConfig?.header, DEFAULT_HEADER_CONTEXT_MENU), headerContext, (item, override) => {
3365
+ if (item === "copy") return renderContextMenuButton("header-copy", headerContext, labels.copyContent, async () => {
3366
+ try {
3367
+ await navigator.clipboard.writeText(rows.map((_, rowIndex) => getCellLabel(rowIndex, contextMenu.columnIndex)).join("\n"));
3368
+ showToast("success", labels.copySuccess);
3369
+ } catch {
3370
+ showToast("error", labels.copyError);
3371
+ }
3372
+ }, override, { icon: /* @__PURE__ */ jsx7(ContextMenuIcon, { type: "copy" }) });
3373
+ if (item === "select-column") {
3374
+ if (!columnSelection) return null;
3375
+ return renderContextMenuButton("header-select-column", headerContext, selectedColumnKeySet.has(column.key) ? labels.deselectColumn : labels.selectColumn, () => {
3376
+ toggleColumnFromMenu(contextMenu.columnIndex);
3377
+ }, override, { icon: /* @__PURE__ */ jsx7(ContextMenuIcon, { type: "select-column" }) });
3378
+ }
3379
+ return null;
3380
+ });
3381
+ })() : selectionRange && isCellInRange(contextMenu) ? (() => {
3382
+ const rangeCells = getRangeCells();
3383
+ const rangeContext = {
3384
+ cells: rangeCells.map((cell) => {
3385
+ const column = columns[cell.columnIndex];
3386
+ const row = rows[cell.rowIndex];
3387
+ return { ...cell, row, column, value: column.dataIndex === void 0 ? void 0 : row[column.dataIndex] };
3388
+ })
3389
+ };
3390
+ return renderConfiguredContextMenu(resolveContextMenuSection(customContextMenuConfig?.range, DEFAULT_RANGE_CONTEXT_MENU), rangeContext, (item, override) => {
3391
+ if (item === "copy") return renderContextMenuButton("range-copy", rangeContext, labels.copyContent, async () => {
3392
+ try {
3393
+ await navigator.clipboard.writeText(getRangeText());
3394
+ showToast("success", labels.copySuccess);
3395
+ } catch {
3396
+ showToast("error", labels.copyError);
3397
+ }
3398
+ }, override, { icon: /* @__PURE__ */ jsx7(ContextMenuIcon, { type: "copy" }) });
3399
+ if (item === "annotation") return renderAnnotationControls(rangeCells);
3400
+ if (item === "undo") {
3401
+ const changedCells = rangeCells.flatMap((cell) => {
3402
+ const column = columns[cell.columnIndex];
3403
+ const row = rows[cell.rowIndex];
3404
+ if (column.dataIndex === void 0) return [];
3405
+ const originalValue = originalCellValuesRef.current.get(getCellEditKey(cell));
3406
+ if (originalValue === void 0) return [];
3407
+ return [{ cell, row, column, originalValue }];
3408
+ });
3409
+ if (changedCells.length === 0) return null;
3410
+ return renderContextMenuButton("range-undo", rangeContext, labels.undoChange, () => {
3411
+ changedCells.forEach(({ cell, row, column, originalValue }) => {
3412
+ if (column.dataIndex === void 0) return;
3413
+ const editKey = getCellEditKey(cell);
3414
+ const previousValue = row[column.dataIndex];
3415
+ const value = typeof previousValue === "number" && originalValue !== "" ? Number(originalValue) : originalValue;
3416
+ originalCellValuesRef.current.delete(editKey);
3417
+ setEditedCellKeys((current) => {
3418
+ if (!current.has(editKey)) return current;
3419
+ const next = new Set(current);
3420
+ next.delete(editKey);
3421
+ return next;
3422
+ });
3423
+ onCellChange?.({ ...cell, row, previousValue, value });
3424
+ });
3425
+ }, override, { disabled: !onCellChange, icon: /* @__PURE__ */ jsx7(ContextMenuIcon, { type: "undo" }) });
3426
+ }
3427
+ if (item === "clear") {
3428
+ const hasClearableCell = rangeCells.some((cell) => {
3429
+ const column = columns[cell.columnIndex];
3430
+ const row = rows[cell.rowIndex];
3431
+ return column.dataIndex !== void 0 && !isEmptyCellValue(row[column.dataIndex]);
3432
+ });
3433
+ if (!hasClearableCell) return null;
3434
+ return renderContextMenuButton("range-clear", rangeContext, labels.clearContent, () => {
3435
+ rangeCells.forEach((cell) => {
3436
+ const column = columns[cell.columnIndex];
3437
+ const row = rows[cell.rowIndex];
3438
+ if (column.dataIndex === void 0 || isEmptyCellValue(row[column.dataIndex])) return;
3439
+ markCellEdited(cell, row[column.dataIndex], "");
3440
+ onCellChange?.({ ...cell, row, previousValue: row[column.dataIndex], value: "" });
3441
+ });
3442
+ }, override, { disabled: !onCellChange, icon: /* @__PURE__ */ jsx7(ContextMenuIcon, { type: "clear" }) });
3443
+ }
3444
+ return null;
3445
+ });
3446
+ })() : (() => {
3447
+ const cell = contextMenu;
3448
+ const column = columns[cell.columnIndex];
3449
+ const row = rows[cell.rowIndex];
3450
+ const dataIndex = column.dataIndex;
3451
+ const editKey = getCellEditKey(cell);
3452
+ const originalValue = originalCellValuesRef.current.get(editKey);
3453
+ const selectedIndices = rows.reduce((indices, item, index) => {
3454
+ if (selectedRowKeySet.has(getRowKey(item, index))) indices.push(index);
3455
+ return indices;
3456
+ }, []);
3457
+ const affectedRows = selectedIndices.includes(cell.rowIndex) ? selectedIndices : [cell.rowIndex];
3458
+ const cellContext = { ...cell, row, column, value: dataIndex === void 0 ? void 0 : row[dataIndex] };
3459
+ return renderConfiguredContextMenu(resolveContextMenuSection(customContextMenuConfig?.cell, DEFAULT_CELL_CONTEXT_MENU), cellContext, (item, override) => {
3460
+ if (item === "edit") return renderContextMenuButton("cell-edit", cellContext, labels.edit, () => beginEdit(cell), override, { disabled: !column.editable, icon: /* @__PURE__ */ jsx7(ContextMenuIcon, { type: "edit" }) });
3461
+ if (item === "copy") return renderContextMenuButton("cell-copy", cellContext, labels.copyContent, async () => {
3462
+ try {
3463
+ await navigator.clipboard.writeText(getCellLabel(cell.rowIndex, cell.columnIndex));
3464
+ showToast("success", labels.copySuccess);
3465
+ } catch {
3466
+ showToast("error", labels.copyError);
3467
+ }
3468
+ }, override, { icon: /* @__PURE__ */ jsx7(ContextMenuIcon, { type: "copy" }) });
3469
+ if (item === "undo") return renderContextMenuButton("cell-undo", cellContext, labels.undoChange, () => {
3470
+ if (dataIndex === void 0 || originalValue === void 0) return;
3471
+ const previousValue = row[dataIndex];
3472
+ const value = typeof previousValue === "number" && originalValue !== "" ? Number(originalValue) : originalValue;
3473
+ setConfirmAction({ type: "undo", cell, row, dataIndex, previousValue, value });
3474
+ }, override, { disabled: !onCellChange || dataIndex === void 0 || originalValue === void 0, icon: /* @__PURE__ */ jsx7(ContextMenuIcon, { type: "undo" }) });
3475
+ if (item === "clear") {
3476
+ if (dataIndex === void 0 || isEmptyCellValue(row[dataIndex])) return null;
3477
+ return renderContextMenuButton("cell-clear", cellContext, labels.clearContent, () => {
3478
+ if (dataIndex === void 0) return;
3479
+ const previousValue = row[dataIndex];
3480
+ setConfirmAction({ type: "clear", cell, row, dataIndex, previousValue });
3481
+ }, override, { disabled: !onCellChange || dataIndex === void 0, danger: true, icon: /* @__PURE__ */ jsx7(ContextMenuIcon, { type: "clear" }) });
3482
+ }
3483
+ if (item === "annotation") return renderAnnotationControls([cell]);
3484
+ if (item === "select-row") {
3485
+ if (!rowSelection) return null;
3486
+ return renderContextMenuButton("cell-select-row", cellContext, selectedRowKeySet.has(cell.rowKey) ? labels.deselectRow : labels.selectRow, () => {
3487
+ if (selectedRowKeySet.has(cell.rowKey)) setRowKeys(rowKeys.filter((key) => key !== cell.rowKey));
3488
+ else setRowKeys(rowSelectionMode === "single" ? [cell.rowKey] : [...rowKeys, cell.rowKey]);
3489
+ }, override, { icon: /* @__PURE__ */ jsx7(ContextMenuIcon, { type: "select-row" }) });
3490
+ }
3491
+ if (item === "select-column") {
3492
+ if (!columnSelection) return null;
3493
+ return renderContextMenuButton("cell-select-column", cellContext, selectedColumnKeySet.has(cell.columnKey) ? labels.deselectColumn : labels.selectColumn, () => {
3494
+ toggleColumnFromMenu(cell.columnIndex);
3495
+ }, override, { icon: /* @__PURE__ */ jsx7(ContextMenuIcon, { type: "select-column" }) });
3496
+ }
3497
+ if (item === "insert-above") return /* @__PURE__ */ jsxs7("div", { className: "rvg-context-insert", children: [
3498
+ /* @__PURE__ */ jsxs7("button", { type: "button", role: "menuitem", disabled: !onInsertRows, onClick: () => {
3499
+ void insertRowsOptimistically(cell, "before", insertCounts.before);
3500
+ }, children: [
3501
+ /* @__PURE__ */ jsx7("span", { className: "rvg-context-menu-icon", children: /* @__PURE__ */ jsx7(ContextMenuIcon, { type: "insert-above" }) }),
3502
+ /* @__PURE__ */ jsx7("span", { className: "rvg-context-menu-label", children: labels.insertAbove })
3503
+ ] }),
3504
+ /* @__PURE__ */ jsx7("input", { "aria-label": labels.insertAboveCount, type: "number", min: 1, max: 5, step: 1, value: insertCounts.before, onChange: (event) => setInsertCounts((current) => ({ ...current, before: Math.max(1, Math.min(5, Math.floor(Number(event.target.value) || 1))) })) }),
3505
+ /* @__PURE__ */ jsx7("span", { children: labels.rowUnit })
3506
+ ] });
3507
+ if (item === "insert-below") return /* @__PURE__ */ jsxs7("div", { className: "rvg-context-insert", children: [
3508
+ /* @__PURE__ */ jsxs7("button", { type: "button", role: "menuitem", disabled: !onInsertRows, onClick: () => {
3509
+ void insertRowsOptimistically(cell, "after", insertCounts.after);
3510
+ }, children: [
3511
+ /* @__PURE__ */ jsx7("span", { className: "rvg-context-menu-icon", children: /* @__PURE__ */ jsx7(ContextMenuIcon, { type: "insert-below" }) }),
3512
+ /* @__PURE__ */ jsx7("span", { className: "rvg-context-menu-label", children: labels.insertBelow })
3513
+ ] }),
3514
+ /* @__PURE__ */ jsx7("input", { "aria-label": labels.insertBelowCount, type: "number", min: 1, max: 5, step: 1, value: insertCounts.after, onChange: (event) => setInsertCounts((current) => ({ ...current, after: Math.max(1, Math.min(5, Math.floor(Number(event.target.value) || 1))) })) }),
3515
+ /* @__PURE__ */ jsx7("span", { children: labels.rowUnit })
3516
+ ] });
3517
+ if (item === "move-up") {
3518
+ if (!rowDraggable || !onRowOrderChange) return null;
3519
+ return renderContextMenuButton("cell-move-up", cellContext, labels.moveUp, () => onRowOrderChange(cell.rowIndex, cell.rowIndex - 1), override, { disabled: cell.rowIndex === 0, icon: /* @__PURE__ */ jsx7(ContextMenuIcon, { type: "move-up" }) });
3520
+ }
3521
+ if (item === "move-down") {
3522
+ if (!rowDraggable || !onRowOrderChange) return null;
3523
+ return renderContextMenuButton("cell-move-down", cellContext, labels.moveDown, () => onRowOrderChange(cell.rowIndex, cell.rowIndex + 1), override, { disabled: cell.rowIndex === rows.length - 1, icon: /* @__PURE__ */ jsx7(ContextMenuIcon, { type: "move-down" }) });
3524
+ }
3525
+ if (item === "delete-row") return renderContextMenuButton("cell-delete-row", cellContext, labels.deleteRow, () => setConfirmAction({ type: "delete-row", rows: affectedRows.map((rowIndex) => ({ rowIndex, row: rows[rowIndex] })) }), override, { disabled: !onDeleteRows, danger: true, icon: /* @__PURE__ */ jsx7(ContextMenuIcon, { type: "delete" }) });
3526
+ return null;
3527
+ });
3528
+ })() }),
3529
+ confirmAction && /* @__PURE__ */ jsx7("div", { className: "rvg-confirm-backdrop", role: "presentation", onMouseDown: (event) => {
3530
+ if (!confirmLoading && event.target === event.currentTarget) setConfirmAction(null);
3531
+ }, children: /* @__PURE__ */ jsxs7("div", { className: "rvg-confirm", role: "alertdialog", "aria-modal": "true", "aria-labelledby": "rvg-delete-title", children: [
3532
+ /* @__PURE__ */ jsx7("strong", { id: "rvg-delete-title", children: confirmAction.type === "undo" ? labels.undoConfirmTitle : confirmAction.type === "clear" ? labels.clearConfirmTitle : labels.deleteConfirmTitle }),
3533
+ /* @__PURE__ */ jsx7("p", { children: confirmAction.type === "undo" ? labels.undoConfirmDescription : confirmAction.type === "clear" ? labels.clearConfirmDescription : labels.deleteConfirmDescription(confirmAction.rows.length) }),
3534
+ /* @__PURE__ */ jsxs7("div", { className: "rvg-confirm-actions", children: [
3535
+ /* @__PURE__ */ jsx7("button", { type: "button", disabled: confirmLoading, onClick: () => setConfirmAction(null), children: labels.cancel }),
3536
+ /* @__PURE__ */ jsxs7("button", { type: "button", className: "rvg-confirm-danger", disabled: confirmLoading, autoFocus: true, onClick: () => {
3537
+ if (confirmLoading) return;
3538
+ flushSync(() => setConfirmLoading(true));
3539
+ window.requestAnimationFrame(async () => {
3540
+ try {
3541
+ if (confirmAction.type === "undo") {
3542
+ const editKey = getCellEditKey(confirmAction.cell);
3543
+ setEditedCellKeys((current) => {
3544
+ if (!current.has(editKey)) return current;
3545
+ const next = new Set(current);
3546
+ next.delete(editKey);
3547
+ return next;
3548
+ });
3549
+ await Promise.resolve(onCellChange?.({ ...confirmAction.cell, row: confirmAction.row, previousValue: confirmAction.previousValue, value: confirmAction.value }));
3550
+ } else if (confirmAction.type === "clear") {
3551
+ markCellEdited(confirmAction.cell, confirmAction.previousValue, "");
3552
+ await Promise.resolve(onCellChange?.({ ...confirmAction.cell, row: confirmAction.row, previousValue: confirmAction.previousValue, value: "" }));
3553
+ } else {
3554
+ const deletingKeys = confirmAction.rows.map(({ row, rowIndex }) => getDataRowKey(row, rowIndex));
3555
+ setConfirmAction(null);
3556
+ setConfirmLoading(false);
3557
+ if (onDeleteRows) {
3558
+ setDeleteBusy(true);
3559
+ try {
3560
+ await Promise.resolve(onDeleteRows({ rows: confirmAction.rows, viewportRange: domRange }));
3561
+ } finally {
3562
+ setDeleteBusy(false);
3563
+ }
3564
+ } else {
3565
+ setPendingDeletedRowKeys((current) => {
3566
+ const next = new Set(current);
3567
+ deletingKeys.forEach((key) => next.add(key));
3568
+ return next;
3569
+ });
3570
+ }
3571
+ return;
3572
+ }
3573
+ setConfirmAction(null);
3574
+ } catch (error) {
3575
+ setConfirmLoading(false);
3576
+ throw error;
3577
+ }
3578
+ });
3579
+ }, children: [
3580
+ confirmLoading && /* @__PURE__ */ jsx7(LoadingSpinnerIcon, {}),
3581
+ confirmAction.type === "undo" ? labels.undoChange : confirmAction.type === "clear" ? labels.clear : labels.delete
3582
+ ] })
3583
+ ] })
3584
+ ] }) }),
3585
+ toast && /* @__PURE__ */ jsx7("div", { className: `rvg-toast is-${toast.type}`, role: "status", children: toast.message }),
3586
+ /* @__PURE__ */ jsx7("div", { ref: dragGuideRef, className: "rvg-column-guide" }),
3587
+ /* @__PURE__ */ jsx7("div", { ref: rowDragGuideRef, className: "rvg-row-guide" }),
3588
+ filterEditor && /* @__PURE__ */ jsxs7("div", { ref: filterRef, className: "rvg-filter", style: { left: filterEditor.left, top: filterEditor.top }, children: [
3589
+ /* @__PURE__ */ jsx7(
3590
+ "input",
3591
+ {
3592
+ autoFocus: true,
3593
+ "aria-label": labels.filterColumn(columns[filterEditor.columnIndex].title),
3594
+ placeholder: labels.search,
3595
+ value: filterEditor.draft,
3596
+ onChange: (event) => {
3597
+ const value = event.target.value;
3598
+ setFilterEditor((current) => current ? { ...current, draft: value } : current);
3599
+ },
3600
+ onKeyDown: (event) => {
3601
+ if (event.key === "Escape") setFilterEditor(null);
3602
+ if (event.key === "Enter") {
3603
+ const key = columns[filterEditor.columnIndex].key;
3604
+ const next = { ...filterValues };
3605
+ if (filterEditor.draft) next[key] = filterEditor.draft;
3606
+ else delete next[key];
3607
+ onFilterValuesChange?.(next);
3608
+ setFilterEditor(null);
3609
+ }
3610
+ }
3611
+ }
3612
+ ),
3613
+ /* @__PURE__ */ jsxs7("div", { className: "rvg-filter-actions", children: [
3614
+ /* @__PURE__ */ jsx7("button", { type: "button", onClick: () => {
3615
+ const key = columns[filterEditor.columnIndex].key;
3616
+ const next = { ...filterValues };
3617
+ delete next[key];
3618
+ onFilterValuesChange?.(next);
3619
+ setFilterEditor(null);
3620
+ }, children: labels.reset }),
3621
+ /* @__PURE__ */ jsx7("button", { type: "button", className: "rvg-filter-confirm", onClick: () => {
3622
+ const key = columns[filterEditor.columnIndex].key;
3623
+ const next = { ...filterValues };
3624
+ if (filterEditor.draft) next[key] = filterEditor.draft;
3625
+ else delete next[key];
3626
+ onFilterValuesChange?.(next);
3627
+ setFilterEditor(null);
3628
+ }, children: labels.confirm })
3629
+ ] })
3630
+ ] }),
3631
+ renderEditor(),
3632
+ rows.length === 0 && !loading && /* @__PURE__ */ jsx7("div", { className: "rvg-empty", children: emptyContent ?? /* @__PURE__ */ jsx7(DefaultEmptyState, { label: labels.empty }) }),
3633
+ loading && hasCustomLoading && /* @__PURE__ */ jsx7("div", { className: "rvg-custom-loading", "aria-label": labels.refreshing, children: loadingContent }),
3634
+ loading && !hasCustomLoading && !hasCompletedLoadRef.current && /* @__PURE__ */ jsxs7("div", { className: "rvg-initial-loading", style: { top: headerHeight }, "aria-label": labels.initialLoading, children: [
3635
+ Array.from({ length: Math.ceil((resolvedHeight - headerHeight) / rowHeight) }, (_, rowIndex) => /* @__PURE__ */ jsx7("div", { className: "rvg-loading-row", style: { height: rowHeight }, children: columns.map((column, columnIndex) => {
3636
+ const left = getDisplayedColumnLeft(columnIndex);
3637
+ const columnWidth = metrics[columnIndex].width;
3638
+ if (left + columnWidth <= 0 || left >= viewport.width) return null;
3639
+ const isUtility = column.rowSelection || column.rowDragHandle || column.rowNumber;
3640
+ return /* @__PURE__ */ jsx7(
3641
+ "span",
3642
+ {
3643
+ className: column.rowDragHandle ? "is-utility is-drag" : column.rowSelection ? "is-utility is-selection" : void 0,
3644
+ style: { left, width: columnWidth, zIndex: column.fixed ? 1 : 0, background: column.fixed ? "#fff" : void 0, justifyContent: column.align === "right" ? "flex-end" : column.align === "center" || isUtility ? "center" : "flex-start" },
3645
+ children: /* @__PURE__ */ jsx7("i", { style: { width: isUtility ? 14 : `${38 + (rowIndex * 17 + columnIndex * 13) % 43}%`, animationDelay: `${(rowIndex * columns.length + columnIndex) * 24}ms` } })
3646
+ },
3647
+ column.key
3648
+ );
3649
+ }) }, rowIndex)),
3650
+ fixedWidth > 0 && /* @__PURE__ */ jsx7("div", { className: "rvg-loading-fixed-shadow is-left", style: { left: fixedWidth } }),
3651
+ rightFixedWidth > 0 && /* @__PURE__ */ jsx7("div", { className: "rvg-loading-fixed-shadow is-right", style: { right: rightFixedWidth } })
3652
+ ] }),
3653
+ loading && !hasCustomLoading && hasCompletedLoadRef.current && /* @__PURE__ */ jsx7("div", { className: "rvg-refresh-loading", "aria-label": labels.refreshing, children: /* @__PURE__ */ jsx7(LoadingSpinnerIcon, {}) })
3654
+ ] });
3655
+ }
3656
+ export {
3657
+ Table,
3658
+ Table as VelocityGrid,
3659
+ buildColumnMetrics,
3660
+ getViewportRange,
3661
+ hitTestColumn
3662
+ };