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