@x12i/data 1.0.0 → 1.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/react.js ADDED
@@ -0,0 +1,3078 @@
1
+ "use client";
2
+ import {
3
+ DEFAULT_COLUMN_MAX_WIDTH,
4
+ DEFAULT_COLUMN_MIN_WIDTH,
5
+ EMPTY_SELECTION,
6
+ applySavedView,
7
+ buildDisplayRows,
8
+ captureView,
9
+ clampColumnWidth,
10
+ clampNav,
11
+ clearSelection,
12
+ computeAggregate,
13
+ computeViewIndexes,
14
+ countSelected,
15
+ createCellFormatter,
16
+ createSearchIndex,
17
+ createServerRowStore,
18
+ createViewState,
19
+ defaultMessages,
20
+ defineColumns,
21
+ distinctValues,
22
+ formatMessage,
23
+ getColumnValue,
24
+ gridToCsv,
25
+ gridToTsv,
26
+ indexColumns,
27
+ initialColumnWidth,
28
+ isEditable,
29
+ isEmptyValue,
30
+ isFilterEffective,
31
+ isFilterable,
32
+ isNavigationKey,
33
+ isResizable,
34
+ isSelected,
35
+ isSortable,
36
+ isValuelessOperator,
37
+ layoutColumns,
38
+ moveColumn,
39
+ moveColumnTo,
40
+ navigate,
41
+ operatorsFor,
42
+ resizeColumn,
43
+ resolveColumnAlign,
44
+ resolveColumnOrder,
45
+ resolveEditor,
46
+ resolveFilterType,
47
+ resolveMessages,
48
+ selectAll,
49
+ selectAllExceptNone,
50
+ selectOnly,
51
+ selectRange,
52
+ setColumnFilter,
53
+ setPinning,
54
+ setSort,
55
+ summarizeSelection,
56
+ toBoolean,
57
+ toDayString,
58
+ toNumber,
59
+ toServerQuery,
60
+ toText,
61
+ toggleGroup,
62
+ toggleRow,
63
+ toggleSort
64
+ } from "./chunk-ASP4ABDG.js";
65
+
66
+ // src/react/data-grid.tsx
67
+ import { defaultRangeExtractor, useVirtualizer } from "@tanstack/react-virtual";
68
+ import { Icon as Icon8 } from "@x12i/icons";
69
+ import { Button as Button4, Spinner, VisuallyHidden as VisuallyHidden4 } from "@x12i/ui";
70
+ import {
71
+ useCallback as useCallback3,
72
+ useDeferredValue,
73
+ useEffect as useEffect3,
74
+ useImperativeHandle,
75
+ useLayoutEffect as useLayoutEffect3,
76
+ useMemo as useMemo2,
77
+ useRef as useRef7,
78
+ useState as useState8
79
+ } from "react";
80
+
81
+ // src/react/editors.tsx
82
+ import { useId, useRef as useRef2 } from "react";
83
+
84
+ // src/react/internal/fields.tsx
85
+ import { useLayoutEffect, useRef } from "react";
86
+ import { jsx } from "react/jsx-runtime";
87
+ function Checkbox({ checked, label, ...props }) {
88
+ const ref = useRef(null);
89
+ useLayoutEffect(() => {
90
+ if (ref.current !== null) ref.current.indeterminate = checked === "mixed";
91
+ }, [checked]);
92
+ return /* @__PURE__ */ jsx(
93
+ "input",
94
+ {
95
+ ...props,
96
+ ref,
97
+ type: "checkbox",
98
+ "data-kit": "data-grid-checkbox",
99
+ checked: checked === true,
100
+ "aria-label": label
101
+ }
102
+ );
103
+ }
104
+ function TextInput({ label, ...props }) {
105
+ return /* @__PURE__ */ jsx("input", { "data-kit": "data-grid-input", "aria-label": label, ...props });
106
+ }
107
+ function SelectInput({ label, ...props }) {
108
+ return /* @__PURE__ */ jsx("select", { "data-kit": "data-grid-select", "aria-label": label, ...props });
109
+ }
110
+
111
+ // src/react/editors.tsx
112
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
113
+ function parseEditorInput(column, input, row, messages) {
114
+ if (column.parseValue !== void 0) return { ok: true, value: column.parseValue(input, row) };
115
+ const editor = resolveEditor(column);
116
+ const text = input.trim();
117
+ switch (editor) {
118
+ case "number": {
119
+ if (text === "") return { ok: true, value: null };
120
+ let normalized = text.replace(/\s/g, "");
121
+ if (normalized.includes(",") && normalized.includes(".")) normalized = normalized.replace(/,/g, "");
122
+ else if (normalized.includes(",")) normalized = normalized.replace(",", ".");
123
+ const number = Number(normalized);
124
+ return Number.isFinite(number) ? { ok: true, value: number } : { ok: false, message: messages.invalidNumber };
125
+ }
126
+ case "date": {
127
+ if (text === "") return { ok: true, value: null };
128
+ return toDayString(text) === null ? { ok: false, message: messages.invalidDate } : { ok: true, value: text };
129
+ }
130
+ case "boolean":
131
+ return { ok: true, value: text === "true" };
132
+ default:
133
+ return { ok: true, value: input };
134
+ }
135
+ }
136
+ function editorStartText(column, value) {
137
+ if (isEmptyValue(value)) return "";
138
+ switch (resolveEditor(column)) {
139
+ case "date":
140
+ return toDayString(value) ?? "";
141
+ default:
142
+ return toText(value);
143
+ }
144
+ }
145
+ function CellEditor({ column, value, messages, locale, error, onCommit, onCancel }) {
146
+ const focused = useRef2(false);
147
+ const finished = useRef2(false);
148
+ const errorId = useId();
149
+ const editor = resolveEditor(column);
150
+ const label = formatMessage(messages.editCell, { column: column.header }, locale);
151
+ const focusOnce = (element) => {
152
+ if (element === null || focused.current) return;
153
+ focused.current = true;
154
+ element.focus({ preventScroll: true });
155
+ if (element instanceof HTMLInputElement) element.select();
156
+ };
157
+ const commit = (input, source) => {
158
+ if (finished.current) return;
159
+ finished.current = true;
160
+ onCommit(input, source);
161
+ queueMicrotask(() => {
162
+ finished.current = false;
163
+ });
164
+ };
165
+ const cancel = () => {
166
+ if (finished.current) return;
167
+ finished.current = true;
168
+ onCancel();
169
+ };
170
+ const onKeyDown = (event) => {
171
+ if (event.key === "Enter") {
172
+ event.preventDefault();
173
+ event.stopPropagation();
174
+ commit(event.currentTarget.value, "enter");
175
+ } else if (event.key === "Escape") {
176
+ event.preventDefault();
177
+ event.stopPropagation();
178
+ cancel();
179
+ }
180
+ };
181
+ const shared = {
182
+ label,
183
+ "aria-invalid": error === null ? void 0 : true,
184
+ "aria-describedby": error === null ? void 0 : errorId,
185
+ onKeyDown,
186
+ onBlur: (event) => commit(event.currentTarget.value, "blur")
187
+ };
188
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
189
+ editor === "select" ? /* @__PURE__ */ jsxs(
190
+ SelectInput,
191
+ {
192
+ ...shared,
193
+ ref: focusOnce,
194
+ defaultValue: editorStartText(column, value),
195
+ onChange: (event) => commit(event.currentTarget.value, "change"),
196
+ children: [
197
+ isEmptyValue(value) ? /* @__PURE__ */ jsx2("option", { value: "" }) : null,
198
+ (column.options ?? []).map((option) => /* @__PURE__ */ jsx2("option", { value: option.value, children: option.label }, option.value))
199
+ ]
200
+ }
201
+ ) : /* @__PURE__ */ jsx2(
202
+ TextInput,
203
+ {
204
+ ...shared,
205
+ ref: focusOnce,
206
+ type: editor === "date" ? "date" : "text",
207
+ inputMode: editor === "number" ? "decimal" : void 0,
208
+ defaultValue: editorStartText(column, value)
209
+ }
210
+ ),
211
+ error === null ? null : /* @__PURE__ */ jsx2("span", { id: errorId, role: "alert", "data-kit": "data-grid-edit-error", children: error })
212
+ ] });
213
+ }
214
+
215
+ // src/react/filter-bar.tsx
216
+ import { Icon } from "@x12i/icons";
217
+ import { Button, IconButton } from "@x12i/ui";
218
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
219
+ function FilterBar({ chips, messages, onClearAll }) {
220
+ if (chips.length === 0) return null;
221
+ const filters = chips.filter((chip) => chip.kind === "filter").length;
222
+ return /* @__PURE__ */ jsxs2("ul", { "aria-label": messages.activeFilters, "data-kit": "data-grid-chips", children: [
223
+ chips.map((chip) => /* @__PURE__ */ jsxs2("li", { "data-kit": "data-grid-chip", "data-kind": chip.kind, children: [
224
+ /* @__PURE__ */ jsx3("span", { "data-kit-slot": "chip-text", children: chip.text }),
225
+ /* @__PURE__ */ jsx3(IconButton, { label: chip.removeLabel, variant: "ghost", tone: "neutral", size: "xs", onClick: chip.onRemove, children: /* @__PURE__ */ jsx3(Icon, { name: "close", size: 12 }) })
226
+ ] }, chip.key)),
227
+ filters > 1 ? /* @__PURE__ */ jsx3("li", { "data-kit-slot": "chips-clear", children: /* @__PURE__ */ jsx3(Button, { variant: "ghost", tone: "neutral", size: "sm", onClick: onClearAll, children: messages.clearAllFilters }) }) : null
228
+ ] });
229
+ }
230
+ function groupChipText(messages, column, locale) {
231
+ return formatMessage(messages.groupedBy, { column }, locale);
232
+ }
233
+
234
+ // src/react/filters.tsx
235
+ import { Button as Button2 } from "@x12i/ui";
236
+ import { useId as useId2, useState } from "react";
237
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
238
+ function textOrEmpty(value) {
239
+ return value === void 0 || value === null ? "" : toText(value);
240
+ }
241
+ function parseValue(type, text) {
242
+ if (text.trim() === "") return void 0;
243
+ switch (type) {
244
+ case "number": {
245
+ const number = Number(text.replace(",", "."));
246
+ return Number.isNaN(number) ? void 0 : number;
247
+ }
248
+ case "boolean":
249
+ return toBoolean(text) ?? void 0;
250
+ default:
251
+ return text;
252
+ }
253
+ }
254
+ function startText(type, value) {
255
+ if (value === void 0 || value === null) return "";
256
+ if (type === "date") return toDayString(value) ?? "";
257
+ return textOrEmpty(value);
258
+ }
259
+ function FilterEditor({ column, item, choices, messages, onApply }) {
260
+ const type = resolveFilterType(column);
261
+ const operators = operatorsFor(type);
262
+ const fieldId = useId2();
263
+ const initialOperator = item !== void 0 && operators.includes(item.operator) ? item.operator : operators[0];
264
+ const [operator, setOperator] = useState(initialOperator);
265
+ const [first, setFirst] = useState(() => {
266
+ if (item === void 0) return "";
267
+ return item.operator === "between" && Array.isArray(item.value) ? startText(type, item.value[0]) : Array.isArray(item.value) ? "" : startText(type, item.value);
268
+ });
269
+ const [second, setSecond] = useState(
270
+ () => item !== void 0 && item.operator === "between" && Array.isArray(item.value) ? startText(type, item.value[1]) : ""
271
+ );
272
+ const [picked, setPicked] = useState(
273
+ () => item !== void 0 && Array.isArray(item.value) && item.operator === "in" ? item.value.map(String) : []
274
+ );
275
+ const valueless = isValuelessOperator(operator);
276
+ const showList = type === "enum" && operator === "in";
277
+ const between = operator === "between";
278
+ const submit = (event) => {
279
+ event?.preventDefault();
280
+ let candidate;
281
+ if (valueless) candidate = { field: column.id, operator };
282
+ else if (showList) candidate = { field: column.id, operator, value: picked };
283
+ else if (between) candidate = { field: column.id, operator, value: [parseValue(type, first), parseValue(type, second)] };
284
+ else candidate = { field: column.id, operator, value: parseValue(type, first) };
285
+ onApply(isFilterEffective(candidate) ? candidate : null);
286
+ };
287
+ const inputType = type === "number" ? "number" : type === "date" ? "date" : "text";
288
+ const valueInput = (text, set, label) => type === "boolean" ? /* @__PURE__ */ jsxs3(SelectInput, { label, value: text, onChange: (event) => set(event.currentTarget.value), children: [
289
+ /* @__PURE__ */ jsx4("option", { value: "", children: messages.filterAny }),
290
+ /* @__PURE__ */ jsx4("option", { value: "true", children: messages.yes }),
291
+ /* @__PURE__ */ jsx4("option", { value: "false", children: messages.no })
292
+ ] }) : /* @__PURE__ */ jsx4(
293
+ TextInput,
294
+ {
295
+ label,
296
+ type: inputType,
297
+ step: type === "number" ? "any" : void 0,
298
+ value: text,
299
+ onChange: (event) => set(event.currentTarget.value)
300
+ }
301
+ );
302
+ return /* @__PURE__ */ jsxs3("form", { "data-kit": "data-grid-filter", onSubmit: submit, children: [
303
+ /* @__PURE__ */ jsxs3("label", { "data-kit": "data-grid-field", htmlFor: `${fieldId}-operator`, children: [
304
+ /* @__PURE__ */ jsx4("span", { "data-kit-slot": "field-label", children: messages.filterOperator }),
305
+ /* @__PURE__ */ jsx4(
306
+ "select",
307
+ {
308
+ id: `${fieldId}-operator`,
309
+ "data-kit": "data-grid-select",
310
+ value: operator,
311
+ onChange: (event) => setOperator(event.currentTarget.value),
312
+ children: operators.map((entry) => /* @__PURE__ */ jsx4("option", { value: entry, children: messages.operators[entry] }, entry))
313
+ }
314
+ )
315
+ ] }),
316
+ valueless ? null : showList ? /* @__PURE__ */ jsx4("div", { role: "group", "aria-label": messages.filterValue, "data-kit": "data-grid-choices", children: choices.length === 0 ? /* @__PURE__ */ jsx4("span", { "data-kit": "data-grid-hint", children: messages.filterNoOptions }) : choices.map((choice) => /* @__PURE__ */ jsxs3("label", { "data-kit": "data-grid-choice", children: [
317
+ /* @__PURE__ */ jsx4(
318
+ Checkbox,
319
+ {
320
+ label: choice.label,
321
+ checked: picked.includes(choice.value),
322
+ onChange: (event) => {
323
+ const on = event.currentTarget.checked;
324
+ setPicked((previous) => on ? [...previous, choice.value] : previous.filter((entry) => entry !== choice.value));
325
+ }
326
+ }
327
+ ),
328
+ /* @__PURE__ */ jsx4("span", { children: choice.label })
329
+ ] }, choice.value)) }) : between ? /* @__PURE__ */ jsxs3("div", { "data-kit": "data-grid-range", children: [
330
+ /* @__PURE__ */ jsxs3("label", { "data-kit": "data-grid-field", children: [
331
+ /* @__PURE__ */ jsx4("span", { "data-kit-slot": "field-label", children: messages.filterFrom }),
332
+ valueInput(first, setFirst, messages.filterFrom)
333
+ ] }),
334
+ /* @__PURE__ */ jsxs3("label", { "data-kit": "data-grid-field", children: [
335
+ /* @__PURE__ */ jsx4("span", { "data-kit-slot": "field-label", children: messages.filterTo }),
336
+ valueInput(second, setSecond, messages.filterTo)
337
+ ] })
338
+ ] }) : /* @__PURE__ */ jsxs3("label", { "data-kit": "data-grid-field", children: [
339
+ /* @__PURE__ */ jsx4("span", { "data-kit-slot": "field-label", children: messages.filterValue }),
340
+ valueInput(first, setFirst, messages.filterValue)
341
+ ] }),
342
+ /* @__PURE__ */ jsxs3("div", { "data-kit": "data-grid-filter-actions", children: [
343
+ /* @__PURE__ */ jsx4(Button2, { type: "button", variant: "ghost", tone: "neutral", size: "sm", onClick: () => onApply(null), children: messages.filterClear }),
344
+ /* @__PURE__ */ jsx4(Button2, { type: "submit", size: "sm", children: messages.filterApply })
345
+ ] })
346
+ ] });
347
+ }
348
+
349
+ // src/react/grid-rows.tsx
350
+ import { Icon as Icon4 } from "@x12i/icons";
351
+ import { IconButton as IconButton3, VisuallyHidden as VisuallyHidden2 } from "@x12i/ui";
352
+ import { memo } from "react";
353
+
354
+ // src/react/cells.tsx
355
+ import { Icon as Icon3 } from "@x12i/icons";
356
+ import { Badge, IconButton as IconButton2, VisuallyHidden } from "@x12i/ui";
357
+ import { useState as useState3 } from "react";
358
+
359
+ // src/react/internal/format.ts
360
+ function createFormatters(columns, context) {
361
+ const formatters = /* @__PURE__ */ new Map();
362
+ for (const column of columns) formatters.set(column.id, createCellFormatter(column, context));
363
+ return formatters;
364
+ }
365
+ function columnWidth(column, sizing) {
366
+ const stored = sizing[column.id];
367
+ return stored === void 0 ? initialColumnWidth(column) : clampColumnWidth(column, stored);
368
+ }
369
+ function plainFormatter(column, context) {
370
+ const { formatter: _ignored, ...plain } = column;
371
+ void _ignored;
372
+ return createCellFormatter(plain, context);
373
+ }
374
+ function formatAggregate(column, value, context) {
375
+ if (isEmptyValue(value)) return "";
376
+ if (column.aggregate === "count") {
377
+ return typeof value === "number" ? new Intl.NumberFormat(context.locale).format(value) : toText(value);
378
+ }
379
+ return plainFormatter(column, context)(value, void 0);
380
+ }
381
+ function initialsOf(text) {
382
+ const words = text.trim().split(/\s+/).filter((word) => word !== "");
383
+ if (words.length === 0) return "";
384
+ const first = Array.from(words[0] ?? "")[0] ?? "";
385
+ const last = words.length > 1 ? Array.from(words[words.length - 1] ?? "")[0] ?? "" : "";
386
+ return `${first}${last}`.toUpperCase();
387
+ }
388
+ var SAFE_SCHEME = /^(?:https?:|mailto:|tel:)/i;
389
+ var RELATIVE = /^(?:\/(?!\/)|#|\.\.?\/)/;
390
+ var DANGEROUS = /^\s*(?:javascript|data|vbscript):/i;
391
+ function isSafeHref(value) {
392
+ const trimmed = value.trim();
393
+ return !DANGEROUS.test(trimmed) && (SAFE_SCHEME.test(trimmed) || RELATIVE.test(trimmed));
394
+ }
395
+ function isAllowedHref(value) {
396
+ return !DANGEROUS.test(value);
397
+ }
398
+ function describeFilterValue(item, column, context, messages) {
399
+ if (isValuelessOperator(item.operator)) return "";
400
+ const format = plainFormatter(column, context);
401
+ const one = (value2) => {
402
+ if (isEmptyValue(value2)) return "";
403
+ if (typeof value2 === "boolean") return value2 ? messages.yes : messages.no;
404
+ const option = column.options?.find((entry) => String(entry.value) === String(value2));
405
+ if (option !== void 0) return option.label;
406
+ return format(value2, void 0);
407
+ };
408
+ const value = item.value;
409
+ if (item.operator === "between" && Array.isArray(value)) {
410
+ const [from, to] = value;
411
+ return `${one(from)} \u2013 ${one(to)}`;
412
+ }
413
+ if (Array.isArray(value)) return value.map(one).join(", ");
414
+ return one(value);
415
+ }
416
+ function summarizeFilter(item, column, context, messages) {
417
+ return formatMessage(
418
+ messages.filterSummary,
419
+ {
420
+ column: column.header,
421
+ operator: messages.operators[item.operator],
422
+ value: describeFilterValue(item, column, context, messages)
423
+ },
424
+ context.locale
425
+ ).trim();
426
+ }
427
+
428
+ // src/react/internal/menu.tsx
429
+ import { Icon as Icon2 } from "@x12i/icons";
430
+ import { useRef as useRef4 } from "react";
431
+
432
+ // src/react/internal/popover.tsx
433
+ import { useEffect, useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState2 } from "react";
434
+
435
+ // src/react/internal/dom.ts
436
+ var FOCUSABLE = 'input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), a[href], [tabindex]:not([tabindex="-1"]), [role="menuitem"]:not([disabled]), [role="menuitemcheckbox"]:not([disabled])';
437
+ function detectDirection(element) {
438
+ if (element === null || typeof getComputedStyle !== "function") return "ltr";
439
+ const attribute = element.closest("[dir]")?.getAttribute("dir");
440
+ if (attribute === "rtl" || attribute === "ltr") return attribute;
441
+ return getComputedStyle(element).direction === "rtl" ? "rtl" : "ltr";
442
+ }
443
+ function isTextEntry(target) {
444
+ if (!(target instanceof HTMLElement)) return false;
445
+ if (target.isContentEditable) return true;
446
+ if (target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement) return true;
447
+ if (target instanceof HTMLInputElement) return target.type !== "checkbox" && target.type !== "radio";
448
+ return false;
449
+ }
450
+ function supportsPopover() {
451
+ return typeof HTMLElement !== "undefined" && typeof HTMLElement.prototype.showPopover === "function";
452
+ }
453
+ function clamp(value, min, max) {
454
+ return Math.min(Math.max(value, min), Math.max(min, max));
455
+ }
456
+
457
+ // src/react/internal/popover.tsx
458
+ import { jsx as jsx5 } from "react/jsx-runtime";
459
+ var MARGIN = 8;
460
+ var GAP = 4;
461
+ function Popover({ open, anchor, onClose, role, label, align = "start", initialFocus = "first", children }) {
462
+ const ref = useRef3(null);
463
+ const [placement, setPlacement] = useState2(null);
464
+ const native = supportsPopover();
465
+ const close = useRef3(onClose);
466
+ close.current = onClose;
467
+ useLayoutEffect2(() => {
468
+ const element = ref.current;
469
+ if (!open || element === null || anchor === null) return;
470
+ if (native) {
471
+ try {
472
+ element.showPopover();
473
+ } catch {
474
+ }
475
+ }
476
+ const place = () => {
477
+ const a = anchor.getBoundingClientRect();
478
+ const box = element.getBoundingClientRect();
479
+ const viewportWidth = window.innerWidth;
480
+ const viewportHeight = window.innerHeight;
481
+ const rtl = detectDirection(anchor) === "rtl";
482
+ let top = a.bottom + GAP;
483
+ if (top + box.height > viewportHeight - MARGIN && a.top - GAP - box.height >= MARGIN) {
484
+ top = a.top - GAP - box.height;
485
+ }
486
+ top = clamp(top, MARGIN, viewportHeight - MARGIN - Math.min(box.height, viewportHeight - 2 * MARGIN));
487
+ const startsAtLeft = rtl ? align === "end" : align === "start";
488
+ const left = clamp(startsAtLeft ? a.left : a.right - box.width, MARGIN, viewportWidth - MARGIN - box.width);
489
+ const inline = rtl ? viewportWidth - (left + box.width) : left;
490
+ setPlacement((previous) => {
491
+ const next = { top, inline, maxBlock: Math.max(viewportHeight - top - MARGIN, 0) };
492
+ return previous !== null && previous.top === next.top && previous.inline === next.inline && previous.maxBlock === next.maxBlock ? previous : next;
493
+ });
494
+ };
495
+ place();
496
+ window.addEventListener("resize", place);
497
+ window.addEventListener("scroll", place, true);
498
+ return () => {
499
+ window.removeEventListener("resize", place);
500
+ window.removeEventListener("scroll", place, true);
501
+ setPlacement(null);
502
+ };
503
+ }, [open, anchor, align, native]);
504
+ const placed = placement !== null;
505
+ useEffect(() => {
506
+ const element = ref.current;
507
+ if (!open || !placed || element === null) return;
508
+ const first = initialFocus === "first" ? element.querySelector(FOCUSABLE) : null;
509
+ (first ?? element).focus({ preventScroll: true });
510
+ }, [open, placed, initialFocus]);
511
+ useEffect(() => {
512
+ if (!open) return;
513
+ const onPointerDown = (event) => {
514
+ const target = event.target;
515
+ if (!(target instanceof Node)) return;
516
+ if (ref.current?.contains(target) === true || anchor?.contains(target) === true) return;
517
+ close.current("outside");
518
+ };
519
+ document.addEventListener("pointerdown", onPointerDown, true);
520
+ return () => document.removeEventListener("pointerdown", onPointerDown, true);
521
+ }, [open, anchor]);
522
+ if (!open) return null;
523
+ const onKeyDown = (event) => {
524
+ if (event.key === "Escape") {
525
+ event.preventDefault();
526
+ event.stopPropagation();
527
+ onClose("escape");
528
+ }
529
+ };
530
+ return /* @__PURE__ */ jsx5(
531
+ "div",
532
+ {
533
+ ref,
534
+ role,
535
+ "aria-label": label,
536
+ tabIndex: -1,
537
+ "data-kit": "data-grid-popover",
538
+ "data-role": role,
539
+ ...native ? { popover: "manual" } : {},
540
+ style: placement === null ? { visibility: "hidden", insetBlockStart: 0, insetInlineStart: 0 } : { insetBlockStart: placement.top, insetInlineStart: placement.inline, maxBlockSize: placement.maxBlock },
541
+ onKeyDown,
542
+ onBlur: (event) => {
543
+ const next = event.relatedTarget;
544
+ if (!(next instanceof Node)) return;
545
+ if (ref.current?.contains(next) === true || anchor?.contains(next) === true) return;
546
+ onClose("blur");
547
+ },
548
+ children
549
+ }
550
+ );
551
+ }
552
+
553
+ // src/react/internal/menu.tsx
554
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
555
+ function Menu({ open, anchor, onClose, label, items, align }) {
556
+ const list = useRef4(null);
557
+ const enabled = () => Array.from(list.current?.querySelectorAll('[role^="menuitem"]:not([aria-disabled="true"])') ?? []);
558
+ const onKeyDown = (event) => {
559
+ const entries = enabled();
560
+ if (entries.length === 0) return;
561
+ const current = entries.indexOf(document.activeElement);
562
+ let next = -1;
563
+ if (event.key === "ArrowDown") next = current < 0 ? 0 : (current + 1) % entries.length;
564
+ else if (event.key === "ArrowUp") next = current < 0 ? entries.length - 1 : (current - 1 + entries.length) % entries.length;
565
+ else if (event.key === "Home") next = 0;
566
+ else if (event.key === "End") next = entries.length - 1;
567
+ else if (event.key === "Tab") {
568
+ onClose("blur");
569
+ return;
570
+ }
571
+ if (next < 0) return;
572
+ event.preventDefault();
573
+ entries[next]?.focus();
574
+ };
575
+ return /* @__PURE__ */ jsx6(Popover, { open, anchor, onClose, role: "menu", label, ...align === void 0 ? {} : { align }, children: /* @__PURE__ */ jsx6("div", { ref: list, "data-kit": "data-grid-menu", onKeyDown, children: items.map((item) => {
576
+ const checkable = item.checked !== void 0;
577
+ return /* @__PURE__ */ jsx6("div", { role: "none", "data-kit": "data-grid-menu-entry", "data-separator": item.separatorBefore === true ? "" : void 0, children: /* @__PURE__ */ jsxs4(
578
+ "button",
579
+ {
580
+ type: "button",
581
+ role: checkable ? "menuitemcheckbox" : "menuitem",
582
+ "aria-checked": checkable ? item.checked : void 0,
583
+ "aria-disabled": item.disabled === true ? true : void 0,
584
+ tabIndex: -1,
585
+ "data-kit": "data-grid-menu-item",
586
+ "data-danger": item.danger === true ? "" : void 0,
587
+ "data-checked": item.checked === true ? "" : void 0,
588
+ onClick: () => {
589
+ if (item.disabled === true) return;
590
+ item.onSelect();
591
+ if (item.keepOpen !== true) onClose("select");
592
+ },
593
+ children: [
594
+ /* @__PURE__ */ jsx6("span", { "data-kit-slot": "menu-check", "aria-hidden": "true", children: item.checked === true ? /* @__PURE__ */ jsx6(Icon2, { name: "check", size: 16 }) : null }),
595
+ /* @__PURE__ */ jsx6("span", { "data-kit-slot": "menu-label", children: item.label })
596
+ ]
597
+ }
598
+ ) }, item.id);
599
+ }) }) });
600
+ }
601
+
602
+ // src/react/cells.tsx
603
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
604
+ var TAG_WIDTH = 76;
605
+ var TAG_PADDING = 16;
606
+ function optionFor(column, value) {
607
+ return column.options?.find((option) => String(option.value) === String(value));
608
+ }
609
+ function Sparkline({ values, label }) {
610
+ if (values.length === 0) return null;
611
+ const low = Math.min(...values);
612
+ const high = Math.max(...values);
613
+ const span = high - low || 1;
614
+ const last = Math.max(values.length - 1, 1);
615
+ const points = values.map((value, index) => `${(index / last * 100).toFixed(2)},${(100 - (value - low) / span * 100).toFixed(2)}`).join(" ");
616
+ return /* @__PURE__ */ jsx7("svg", { "data-kit": "data-grid-sparkline", viewBox: "0 0 100 100", preserveAspectRatio: "none", role: "img", "aria-label": label, focusable: "false", children: /* @__PURE__ */ jsx7("polyline", { points, fill: "none" }) });
617
+ }
618
+ function ProgressBar({ ratio, label, valueText }) {
619
+ return /* @__PURE__ */ jsxs5("span", { "data-kit": "data-grid-progress", children: [
620
+ /* @__PURE__ */ jsx7(
621
+ "span",
622
+ {
623
+ role: "progressbar",
624
+ "aria-label": label,
625
+ "aria-valuemin": 0,
626
+ "aria-valuemax": 100,
627
+ "aria-valuenow": Math.round(ratio * 100),
628
+ "aria-valuetext": valueText,
629
+ "data-kit": "data-grid-progress-track",
630
+ children: /* @__PURE__ */ jsx7("span", { "data-kit": "data-grid-progress-fill", style: { inlineSize: `${(ratio * 100).toFixed(1)}%` } })
631
+ }
632
+ ),
633
+ /* @__PURE__ */ jsx7("span", { "data-kit": "data-grid-progress-text", "aria-hidden": "true", children: valueText })
634
+ ] });
635
+ }
636
+ function RowActionsCell({ column, row, messages }) {
637
+ const [anchor, setAnchor] = useState3(null);
638
+ const [open, setOpen] = useState3(false);
639
+ const actions = column.actions?.(row) ?? [];
640
+ if (actions.length === 0) return null;
641
+ const items = actions.map((action) => ({
642
+ id: action.id,
643
+ label: action.label,
644
+ onSelect: () => action.onSelect(row),
645
+ ...action.disabled === void 0 ? {} : { disabled: action.disabled },
646
+ danger: action.tone === "danger"
647
+ }));
648
+ return /* @__PURE__ */ jsxs5(Fragment2, { children: [
649
+ /* @__PURE__ */ jsx7(
650
+ IconButton2,
651
+ {
652
+ ref: setAnchor,
653
+ label: messages.rowActions,
654
+ variant: "ghost",
655
+ tone: "neutral",
656
+ size: "sm",
657
+ tabIndex: -1,
658
+ "aria-haspopup": "menu",
659
+ "aria-expanded": open,
660
+ "data-grid-primary": "",
661
+ onClick: () => setOpen((value) => !value),
662
+ children: /* @__PURE__ */ jsx7(Icon3, { name: "more", size: 16 })
663
+ }
664
+ ),
665
+ /* @__PURE__ */ jsx7(
666
+ Menu,
667
+ {
668
+ open,
669
+ anchor,
670
+ label: messages.rowActions,
671
+ items,
672
+ align: "end",
673
+ onClose: (reason) => {
674
+ setOpen(false);
675
+ if (reason === "escape" || reason === "select") anchor?.closest('[role="gridcell"]')?.focus({ preventScroll: true });
676
+ }
677
+ }
678
+ )
679
+ ] });
680
+ }
681
+ function renderCellContent(context, options) {
682
+ const { column, value, text, messages, row } = context;
683
+ if (column.cell !== void 0) return column.cell(context);
684
+ const empty = isEmptyValue(value);
685
+ const editor = resolveEditor(column);
686
+ if (editor === "boolean" && !options.readOnly && isEditable(column, row) && options.onToggle !== void 0) {
687
+ const checked = toBoolean(value) === true;
688
+ return /* @__PURE__ */ jsx7(
689
+ Checkbox,
690
+ {
691
+ checked,
692
+ label: column.header,
693
+ tabIndex: -1,
694
+ "data-grid-primary": "",
695
+ "data-grid-space": "",
696
+ onChange: (event) => options.onToggle?.(event.currentTarget.checked)
697
+ }
698
+ );
699
+ }
700
+ switch (column.cellType) {
701
+ case "boolean": {
702
+ const flag = toBoolean(value);
703
+ if (flag === null) return empty ? null : /* @__PURE__ */ jsx7("span", { "data-kit": "data-grid-text", children: text });
704
+ return /* @__PURE__ */ jsxs5("span", { "data-kit": "data-grid-boolean", "data-value": flag ? "true" : "false", children: [
705
+ /* @__PURE__ */ jsx7(Icon3, { name: flag ? "check" : "close", size: 16 }),
706
+ /* @__PURE__ */ jsx7(VisuallyHidden, { children: flag ? messages.yes : messages.no })
707
+ ] });
708
+ }
709
+ case "status": {
710
+ if (empty) return null;
711
+ const option = optionFor(column, value);
712
+ return /* @__PURE__ */ jsx7(Badge, { tone: option?.tone ?? "neutral", children: option?.label ?? text });
713
+ }
714
+ case "tags": {
715
+ const items = Array.isArray(value) ? value : empty ? [] : [value];
716
+ if (items.length === 0) return null;
717
+ const fit = Math.max(1, Math.floor((options.width - TAG_PADDING) / TAG_WIDTH));
718
+ const shown = items.length > fit ? items.slice(0, Math.max(fit - 1, 1)) : items;
719
+ const hidden = items.slice(shown.length);
720
+ const labelOf = (item) => optionFor(column, item)?.label ?? String(item);
721
+ return /* @__PURE__ */ jsxs5("span", { "data-kit": "data-grid-tags", children: [
722
+ shown.map((item, index) => /* @__PURE__ */ jsx7(Badge, { size: "sm", tone: optionFor(column, item)?.tone ?? "neutral", children: labelOf(item) }, `${String(item)}:${index}`)),
723
+ hidden.length > 0 ? /* @__PURE__ */ jsxs5(Badge, { size: "sm", variant: "outline", title: hidden.map(labelOf).join(", "), children: [
724
+ formatMessage(messages.moreTags, { count: hidden.length }),
725
+ /* @__PURE__ */ jsx7(VisuallyHidden, { children: `: ${hidden.map(labelOf).join(", ")}` })
726
+ ] }) : null
727
+ ] });
728
+ }
729
+ case "progress": {
730
+ if (empty) return null;
731
+ const max = column.progressMax ?? 100;
732
+ const number = toNumber(value);
733
+ const ratio = Number.isNaN(number) || max <= 0 ? 0 : Math.min(Math.max(number / max, 0), 1);
734
+ return /* @__PURE__ */ jsx7(ProgressBar, { ratio, label: column.header, valueText: text });
735
+ }
736
+ case "link": {
737
+ if (empty) return null;
738
+ const target = column.href?.(row) ?? (typeof value === "string" ? value : void 0);
739
+ const allowed = target !== void 0 && (column.href === void 0 ? isSafeHref(target) : isAllowedHref(target));
740
+ if (!allowed || target === void 0) return /* @__PURE__ */ jsx7("span", { "data-kit": "data-grid-text", children: text });
741
+ return /* @__PURE__ */ jsx7("a", { href: target, tabIndex: -1, "data-kit": "data-grid-link", "data-grid-primary": "", rel: "noopener noreferrer", children: text });
742
+ }
743
+ case "actions":
744
+ return /* @__PURE__ */ jsx7(RowActionsCell, { column, row, messages });
745
+ case "avatar": {
746
+ if (empty) return null;
747
+ return /* @__PURE__ */ jsxs5("span", { "data-kit": "data-grid-avatar-cell", children: [
748
+ /* @__PURE__ */ jsx7("span", { "data-kit": "data-grid-avatar", "aria-hidden": "true", children: initialsOf(text) }),
749
+ /* @__PURE__ */ jsx7("span", { "data-kit": "data-grid-text", title: text, children: text })
750
+ ] });
751
+ }
752
+ case "sparkline": {
753
+ const series = (Array.isArray(value) ? value : []).map(toNumber).filter((entry) => !Number.isNaN(entry));
754
+ return /* @__PURE__ */ jsx7(Sparkline, { values: series, label: text });
755
+ }
756
+ default:
757
+ if (empty) return null;
758
+ return /* @__PURE__ */ jsx7("span", { "data-kit": "data-grid-text", title: text, children: text });
759
+ }
760
+ }
761
+
762
+ // src/react/grid-rows.tsx
763
+ import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
764
+ var CONTROL_SELECTOR = 'button, a[href], input, select, textarea, label, [role="menuitem"], [role="menuitemcheckbox"]';
765
+ function startedOnControl(event) {
766
+ const target = event.target;
767
+ if (!(target instanceof Element)) return false;
768
+ const control = target.closest(CONTROL_SELECTOR);
769
+ return control !== null && event.currentTarget.contains(control);
770
+ }
771
+ function cellStyle(info) {
772
+ const style = { inlineSize: info.width };
773
+ if (info.pin === "start") style.insetInlineStart = info.pinOffset;
774
+ if (info.pin === "end") style.insetInlineEnd = info.pinOffset;
775
+ return style;
776
+ }
777
+ function GridCell({
778
+ info,
779
+ index,
780
+ navRow,
781
+ cursor,
782
+ children,
783
+ state
784
+ }) {
785
+ return /* @__PURE__ */ jsx8(
786
+ "div",
787
+ {
788
+ role: "gridcell",
789
+ "aria-colindex": index + 1,
790
+ tabIndex: cursor ? 0 : -1,
791
+ "data-kit": "data-grid-cell",
792
+ "data-nav-row": navRow,
793
+ "data-nav-col": index,
794
+ "data-type": info.kind === "data" ? info.column?.cellType ?? "text" : info.kind,
795
+ "data-align": info.align,
796
+ "data-pinned": info.pin ?? void 0,
797
+ "data-pinned-edge": info.pinEdge ? "" : void 0,
798
+ "data-first": info.first ? "" : void 0,
799
+ style: cellStyle(info),
800
+ ...state,
801
+ children
802
+ }
803
+ );
804
+ }
805
+ function GridRowView(props) {
806
+ const { item, ariaRowIndex, displayIndex, top, height, columns, slots, totalWidth, selected, cursorCol, env, treegrid } = props;
807
+ const { messages, locale } = env;
808
+ const navRow = displayIndex + 1;
809
+ const style = {
810
+ transform: `translateY(${top}px)`,
811
+ inlineSize: totalWidth,
812
+ ...height === void 0 ? {} : { blockSize: height },
813
+ ...item.depth > 0 ? { "--_depth": item.depth } : {}
814
+ };
815
+ const renderSlots = (renderColumn) => slots.map(
816
+ (slot, position) => slot.kind === "spacer" ? /* @__PURE__ */ jsx8("div", { role: "presentation", "data-kit": "data-grid-spacer", style: { inlineSize: slot.size } }, `spacer-${position}`) : renderColumn(columns[slot.index], slot.index)
817
+ );
818
+ if (item.kind === "detail") {
819
+ return /* @__PURE__ */ jsx8(
820
+ "div",
821
+ {
822
+ ref: props.measureRef,
823
+ role: "row",
824
+ "aria-rowindex": ariaRowIndex,
825
+ "aria-level": treegrid ? item.depth + 1 : void 0,
826
+ "data-kit": "data-grid-row",
827
+ "data-kind": "detail",
828
+ "data-index": displayIndex,
829
+ style: { ...style, blockSize: void 0 },
830
+ children: /* @__PURE__ */ jsx8("div", { role: "gridcell", "aria-colindex": 1, "aria-colspan": Math.max(columns.length, 1), "aria-label": messages.detailRegion, "data-kit": "data-grid-detail", children: /* @__PURE__ */ jsx8("div", { "data-kit-slot": "detail-content", style: { inlineSize: props.viewportWidth > 0 ? props.viewportWidth : void 0 }, children: env.renderDetail?.(item.row) }) })
831
+ }
832
+ );
833
+ }
834
+ if (item.kind === "group") {
835
+ const label = formatMessage(
836
+ messages.groupLabel,
837
+ { value: isEmptyValue(item.value) ? messages.filterBlank : env.formatGroupValue(item.field, item.value), count: item.count },
838
+ locale
839
+ );
840
+ return /* @__PURE__ */ jsx8(
841
+ "div",
842
+ {
843
+ role: "row",
844
+ "aria-rowindex": ariaRowIndex,
845
+ "aria-level": item.depth + 1,
846
+ "aria-expanded": item.expanded,
847
+ "data-kit": "data-grid-row",
848
+ "data-kind": "group",
849
+ "data-index": displayIndex,
850
+ "data-expanded": item.expanded ? "" : void 0,
851
+ style,
852
+ children: renderSlots((info, index) => {
853
+ const cursor = cursorCol === index;
854
+ if (info.kind !== "data") return /* @__PURE__ */ jsx8(GridCell, { info, index, navRow, cursor, children: null }, info.id);
855
+ const aggregate = info.column !== null && info.column.aggregate !== void 0 ? item.aggregates[info.column.id] : void 0;
856
+ return /* @__PURE__ */ jsx8(GridCell, { info, index, navRow, cursor, children: info.first ? /* @__PURE__ */ jsxs6(
857
+ "button",
858
+ {
859
+ type: "button",
860
+ tabIndex: -1,
861
+ "data-kit": "data-grid-group-toggle",
862
+ "data-grid-primary": "",
863
+ "data-grid-space": "",
864
+ "aria-label": item.expanded ? messages.collapseGroup : messages.expandGroup,
865
+ onClick: () => env.toggleGroup(item.id),
866
+ children: [
867
+ /* @__PURE__ */ jsx8("span", { "data-flip": "", "data-expanded": item.expanded ? "" : void 0, children: /* @__PURE__ */ jsx8(Icon4, { name: item.expanded ? "chevron-down" : "chevron-right", size: 16 }) }),
868
+ /* @__PURE__ */ jsx8("span", { "data-kit-slot": "group-label", children: label })
869
+ ]
870
+ }
871
+ ) : isEmptyValue(aggregate) ? null : /* @__PURE__ */ jsx8("span", { "data-kit": "data-grid-text", "data-kit-slot": "aggregate", children: env.formatAggregate(info.id, aggregate) }) }, info.id);
872
+ })
873
+ }
874
+ );
875
+ }
876
+ if (item.kind === "placeholder") {
877
+ return /* @__PURE__ */ jsx8(
878
+ "div",
879
+ {
880
+ role: "row",
881
+ "aria-rowindex": ariaRowIndex,
882
+ "aria-busy": item.failed === true ? void 0 : true,
883
+ "data-kit": "data-grid-row",
884
+ "data-kind": "placeholder",
885
+ "data-failed": item.failed === true ? "" : void 0,
886
+ "data-index": displayIndex,
887
+ style,
888
+ children: renderSlots((info, index) => /* @__PURE__ */ jsx8(GridCell, { info, index, navRow, cursor: cursorCol === index, children: info.kind === "data" ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
889
+ /* @__PURE__ */ jsx8("span", { "data-kit": "data-grid-skeleton", "aria-hidden": "true" }),
890
+ info.first ? /* @__PURE__ */ jsx8(VisuallyHidden2, { children: item.failed === true ? messages.rowFailed : messages.rowLoading }) : null
891
+ ] }) : null }, info.id))
892
+ }
893
+ );
894
+ }
895
+ const { row, id } = item;
896
+ const selectable = env.selectionMode !== "none" && (env.isRowSelectable?.(row) ?? true);
897
+ const expandable = env.renderDetail !== void 0 && (env.isRowExpandable?.(row) ?? true);
898
+ return /* @__PURE__ */ jsx8(
899
+ "div",
900
+ {
901
+ role: "row",
902
+ "aria-rowindex": ariaRowIndex,
903
+ "aria-selected": env.selectionMode !== "none" && selectable ? selected : void 0,
904
+ "aria-level": treegrid ? item.depth + 1 : void 0,
905
+ "aria-expanded": expandable ? props.expanded : void 0,
906
+ "data-kit": "data-grid-row",
907
+ "data-kind": "data",
908
+ "data-index": displayIndex,
909
+ "data-row-id": id,
910
+ "data-selected": selected ? "" : void 0,
911
+ "data-expanded": props.expanded ? "" : void 0,
912
+ style,
913
+ onClick: env.onRowClick === void 0 ? void 0 : (event) => {
914
+ if (!startedOnControl(event)) env.onRowClick?.(row, event);
915
+ },
916
+ children: renderSlots((info, index) => {
917
+ const cursor = cursorCol === index;
918
+ if (info.kind === "select") {
919
+ return /* @__PURE__ */ jsx8(GridCell, { info, index, navRow, cursor, children: selectable ? /* @__PURE__ */ jsx8(
920
+ Checkbox,
921
+ {
922
+ checked: selected,
923
+ label: messages.selectRow,
924
+ tabIndex: -1,
925
+ "data-grid-primary": "",
926
+ "data-grid-space": "",
927
+ onChange: (event) => env.toggleSelect(
928
+ id,
929
+ displayIndex,
930
+ env.selectionMode === "multiple" && event.nativeEvent.shiftKey === true
931
+ )
932
+ }
933
+ ) : null }, info.id);
934
+ }
935
+ if (info.kind === "expand") {
936
+ return /* @__PURE__ */ jsx8(GridCell, { info, index, navRow, cursor, children: expandable ? /* @__PURE__ */ jsx8(
937
+ IconButton3,
938
+ {
939
+ label: props.expanded ? messages.collapseRow : messages.expandRow,
940
+ variant: "ghost",
941
+ tone: "neutral",
942
+ size: "sm",
943
+ tabIndex: -1,
944
+ "aria-expanded": props.expanded,
945
+ "data-grid-primary": "",
946
+ "data-grid-space": "",
947
+ onClick: () => env.toggleExpand(id),
948
+ children: /* @__PURE__ */ jsx8("span", { "data-flip": "", children: /* @__PURE__ */ jsx8(Icon4, { name: props.expanded ? "chevron-down" : "chevron-right", size: 16 }) })
949
+ }
950
+ ) : null }, info.id);
951
+ }
952
+ const column = info.column;
953
+ const override = props.overrides?.[column.id];
954
+ const value = override === void 0 ? getColumnValue(column, row) : override.value;
955
+ const format = env.formatters.get(column.id);
956
+ const text = format === void 0 ? "" : format(value, row);
957
+ const editing = props.editingColumnId === column.id;
958
+ const editable = isEditable(column, row);
959
+ const state = {
960
+ "data-editing": editing ? "" : void 0,
961
+ "data-editable": editable ? "" : void 0,
962
+ "data-dirty": override !== void 0 && override.status !== "error" ? "" : void 0,
963
+ "data-pending": override?.status === "pending" ? "" : void 0,
964
+ "data-error": override?.status === "error" ? "" : void 0
965
+ };
966
+ return /* @__PURE__ */ jsx8(GridCell, { info, index, navRow, cursor, state, children: editing ? /* @__PURE__ */ jsx8(
967
+ CellEditor,
968
+ {
969
+ column,
970
+ value,
971
+ messages,
972
+ locale,
973
+ error: props.editingError,
974
+ onCommit: env.commitEdit,
975
+ onCancel: env.cancelEdit
976
+ }
977
+ ) : /* @__PURE__ */ jsx8(
978
+ "span",
979
+ {
980
+ "data-kit-slot": "cell-content",
981
+ onDoubleClick: editable ? () => env.startEdit(id, column.id, row) : void 0,
982
+ children: renderCellContent(
983
+ {
984
+ row,
985
+ rowId: id,
986
+ rowIndex: item.index,
987
+ column,
988
+ value,
989
+ text,
990
+ selected,
991
+ messages,
992
+ locale
993
+ },
994
+ {
995
+ width: info.width,
996
+ readOnly: false,
997
+ onToggle: (next) => env.toggleBoolean(id, column.id, next, row)
998
+ }
999
+ )
1000
+ }
1001
+ ) }, info.id);
1002
+ })
1003
+ }
1004
+ );
1005
+ }
1006
+ var GridRowItem = memo(GridRowView);
1007
+
1008
+ // src/react/header.tsx
1009
+ import { Icon as Icon5 } from "@x12i/icons";
1010
+ import { IconButton as IconButton4, VisuallyHidden as VisuallyHidden3 } from "@x12i/ui";
1011
+ import { memo as memo2, useState as useState4 } from "react";
1012
+ import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
1013
+ var RESIZE_STEP = 16;
1014
+ function placementOf(event, rtl) {
1015
+ const box = event.currentTarget.getBoundingClientRect();
1016
+ const leftHalf = event.clientX < box.left + box.width / 2;
1017
+ return leftHalf !== rtl ? "before" : "after";
1018
+ }
1019
+ function ariaSort(direction, sortable) {
1020
+ if (!sortable) return void 0;
1021
+ return direction === "asc" ? "ascending" : direction === "desc" ? "descending" : "none";
1022
+ }
1023
+ function HeaderCellView(props) {
1024
+ const {
1025
+ column,
1026
+ navCol,
1027
+ width,
1028
+ minWidth,
1029
+ maxWidth,
1030
+ pin,
1031
+ pinOffset,
1032
+ pinEdge,
1033
+ sortDirection,
1034
+ sortRank,
1035
+ sortable,
1036
+ filterable,
1037
+ filtered,
1038
+ resizable,
1039
+ cursor,
1040
+ rtl,
1041
+ popover,
1042
+ align,
1043
+ messages,
1044
+ locale,
1045
+ actions,
1046
+ reorderable,
1047
+ dragging,
1048
+ dropPlacement
1049
+ } = props;
1050
+ const [menuAnchor, setMenuAnchor] = useState4(null);
1051
+ const [filterAnchor, setFilterAnchor] = useState4(null);
1052
+ const [drag, setDrag] = useState4(null);
1053
+ const style = { inlineSize: width };
1054
+ if (pin === "start") style.insetInlineStart = pinOffset;
1055
+ if (pin === "end") style.insetInlineEnd = pinOffset;
1056
+ const label = column.headerCell === void 0 ? column.header : column.headerCell({ column, sortDirection, messages });
1057
+ const restoreFocus = (reason) => {
1058
+ if (reason === "escape" || reason === "select") {
1059
+ const cell = menuAnchor?.closest('[role="columnheader"]') ?? filterAnchor?.closest('[role="columnheader"]');
1060
+ cell?.focus({ preventScroll: true });
1061
+ }
1062
+ };
1063
+ const onResizerKeyDown = (event) => {
1064
+ const grow = rtl ? "ArrowLeft" : "ArrowRight";
1065
+ const shrink = rtl ? "ArrowRight" : "ArrowLeft";
1066
+ const step = event.shiftKey ? RESIZE_STEP * 4 : RESIZE_STEP;
1067
+ let next = null;
1068
+ if (event.key === grow) next = width + step;
1069
+ else if (event.key === shrink) next = width - step;
1070
+ else if (event.key === "Home") next = minWidth;
1071
+ else if (event.key === "End") next = maxWidth;
1072
+ if (next === null) return;
1073
+ event.preventDefault();
1074
+ event.stopPropagation();
1075
+ actions.resize(column.id, next, true);
1076
+ };
1077
+ const onDragStart = (event) => {
1078
+ if (drag !== null) {
1079
+ event.preventDefault();
1080
+ return;
1081
+ }
1082
+ event.dataTransfer.effectAllowed = "move";
1083
+ event.dataTransfer.setData("text/plain", column.header);
1084
+ actions.dragStart(column.id);
1085
+ };
1086
+ const onDragOver = (event) => {
1087
+ if (!actions.dragOver(column.id, placementOf(event, rtl))) return;
1088
+ event.preventDefault();
1089
+ event.dataTransfer.dropEffect = "move";
1090
+ };
1091
+ const onDragLeave = (event) => {
1092
+ if (!event.currentTarget.contains(event.relatedTarget)) actions.dragLeave(column.id);
1093
+ };
1094
+ const onDrop = (event) => {
1095
+ event.preventDefault();
1096
+ actions.drop(column.id, placementOf(event, rtl));
1097
+ };
1098
+ const onPointerDown = (event) => {
1099
+ if (event.button !== 0) return;
1100
+ event.preventDefault();
1101
+ event.currentTarget.setPointerCapture(event.pointerId);
1102
+ setDrag({ startX: event.clientX, startWidth: width });
1103
+ };
1104
+ const onPointerMove = (event) => {
1105
+ if (drag === null) return;
1106
+ const delta = event.clientX - drag.startX;
1107
+ actions.resize(column.id, drag.startWidth + (rtl ? -delta : delta));
1108
+ };
1109
+ const endDrag = (event) => {
1110
+ if (drag === null) return;
1111
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
1112
+ setDrag(null);
1113
+ };
1114
+ return /* @__PURE__ */ jsxs7(
1115
+ "div",
1116
+ {
1117
+ role: "columnheader",
1118
+ "aria-colindex": navCol + 1,
1119
+ "aria-sort": ariaSort(sortDirection, sortable),
1120
+ tabIndex: cursor ? 0 : -1,
1121
+ "data-kit": "data-grid-header-cell",
1122
+ "data-nav-row": 0,
1123
+ "data-nav-col": navCol,
1124
+ "data-column": column.id,
1125
+ "data-align": align,
1126
+ "data-sorted": sortDirection ?? void 0,
1127
+ "data-filtered": filtered ? "" : void 0,
1128
+ "data-pinned": pin ?? void 0,
1129
+ "data-pinned-edge": pinEdge ? "" : void 0,
1130
+ "data-resizing": drag === null ? void 0 : "",
1131
+ "data-dragging": dragging ? "" : void 0,
1132
+ "data-drop": dropPlacement ?? void 0,
1133
+ draggable: reorderable ? true : void 0,
1134
+ onDragStart: reorderable ? onDragStart : void 0,
1135
+ onDragOver: reorderable ? onDragOver : void 0,
1136
+ onDragLeave: reorderable ? onDragLeave : void 0,
1137
+ onDrop: reorderable ? onDrop : void 0,
1138
+ onDragEnd: reorderable ? actions.dragEnd : void 0,
1139
+ style,
1140
+ children: [
1141
+ /* @__PURE__ */ jsxs7("div", { "data-kit": "data-grid-header-content", children: [
1142
+ sortable ? /* @__PURE__ */ jsxs7(
1143
+ "button",
1144
+ {
1145
+ type: "button",
1146
+ tabIndex: -1,
1147
+ "data-kit": "data-grid-sort",
1148
+ "data-grid-primary": "",
1149
+ "data-grid-space": "",
1150
+ onClick: (event) => actions.sort(column.id, event.shiftKey),
1151
+ children: [
1152
+ /* @__PURE__ */ jsx9("span", { "data-kit-slot": "header-label", children: label }),
1153
+ /* @__PURE__ */ jsx9("span", { "data-kit-slot": "sort-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx9(Icon5, { name: sortDirection === "desc" ? "chevron-down" : "chevron-up", size: 12 }) }),
1154
+ sortRank !== null ? /* @__PURE__ */ jsxs7("span", { "data-kit-slot": "sort-rank", children: [
1155
+ /* @__PURE__ */ jsx9("span", { "aria-hidden": "true", children: sortRank }),
1156
+ /* @__PURE__ */ jsx9(VisuallyHidden3, { children: formatMessage(messages.sortRank, { rank: sortRank }, locale) })
1157
+ ] }) : null
1158
+ ]
1159
+ }
1160
+ ) : /* @__PURE__ */ jsx9("span", { "data-kit-slot": "header-label", children: label }),
1161
+ filterable ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
1162
+ /* @__PURE__ */ jsx9(
1163
+ IconButton4,
1164
+ {
1165
+ ref: setFilterAnchor,
1166
+ label: formatMessage(messages.filterColumn, { column: column.header }, locale),
1167
+ variant: "ghost",
1168
+ tone: filtered ? "accent" : "neutral",
1169
+ size: "xs",
1170
+ tabIndex: -1,
1171
+ "aria-haspopup": "dialog",
1172
+ "aria-expanded": popover === "filter",
1173
+ "data-kit-slot": "filter-trigger",
1174
+ "data-active": filtered ? "" : void 0,
1175
+ onClick: () => actions.setPopover(column.id, popover === "filter" ? null : "filter"),
1176
+ children: /* @__PURE__ */ jsx9(Icon5, { name: "filter", size: 12 })
1177
+ }
1178
+ ),
1179
+ /* @__PURE__ */ jsx9(
1180
+ Popover,
1181
+ {
1182
+ open: popover === "filter",
1183
+ anchor: filterAnchor,
1184
+ role: "dialog",
1185
+ label: formatMessage(messages.filterColumn, { column: column.header }, locale),
1186
+ align: rtl ? "end" : "start",
1187
+ onClose: (reason) => {
1188
+ actions.closePopover(column.id, "filter");
1189
+ restoreFocus(reason);
1190
+ },
1191
+ children: popover === "filter" ? actions.renderFilter(column.id, () => {
1192
+ actions.setPopover(column.id, null);
1193
+ filterAnchor?.closest('[role="columnheader"]')?.focus({ preventScroll: true });
1194
+ }) : null
1195
+ }
1196
+ )
1197
+ ] }) : null,
1198
+ /* @__PURE__ */ jsx9(
1199
+ IconButton4,
1200
+ {
1201
+ ref: setMenuAnchor,
1202
+ label: formatMessage(messages.columnMenu, { column: column.header }, locale),
1203
+ variant: "ghost",
1204
+ tone: "neutral",
1205
+ size: "xs",
1206
+ tabIndex: -1,
1207
+ "aria-haspopup": "menu",
1208
+ "aria-expanded": popover === "menu",
1209
+ "data-kit-slot": "menu-trigger",
1210
+ onClick: () => actions.setPopover(column.id, popover === "menu" ? null : "menu"),
1211
+ children: /* @__PURE__ */ jsx9(Icon5, { name: "chevron-down", size: 12 })
1212
+ }
1213
+ ),
1214
+ /* @__PURE__ */ jsx9(
1215
+ Menu,
1216
+ {
1217
+ open: popover === "menu",
1218
+ anchor: menuAnchor,
1219
+ label: formatMessage(messages.columnMenu, { column: column.header }, locale),
1220
+ items: popover === "menu" ? actions.getMenuItems(column.id) : [],
1221
+ align: rtl ? "end" : "start",
1222
+ onClose: (reason) => {
1223
+ actions.closePopover(column.id, "menu");
1224
+ restoreFocus(reason);
1225
+ }
1226
+ }
1227
+ )
1228
+ ] }),
1229
+ resizable ? /* @__PURE__ */ jsx9(
1230
+ "div",
1231
+ {
1232
+ role: "separator",
1233
+ "aria-orientation": "vertical",
1234
+ "aria-label": formatMessage(messages.resizeColumn, { column: column.header }, locale),
1235
+ "aria-valuenow": width,
1236
+ "aria-valuemin": minWidth,
1237
+ "aria-valuemax": maxWidth,
1238
+ tabIndex: -1,
1239
+ "data-kit": "data-grid-resizer",
1240
+ "data-active": drag === null ? void 0 : "",
1241
+ onPointerDown,
1242
+ onPointerMove,
1243
+ onPointerUp: endDrag,
1244
+ onPointerCancel: endDrag,
1245
+ onKeyDown: onResizerKeyDown,
1246
+ onClick: (event) => event.stopPropagation(),
1247
+ onDoubleClick: () => actions.resize(column.id, initialColumnWidth(column))
1248
+ }
1249
+ ) : null
1250
+ ]
1251
+ }
1252
+ );
1253
+ }
1254
+ var HeaderCell = memo2(HeaderCellView);
1255
+
1256
+ // src/react/internal/clipboard.ts
1257
+ async function writeClipboard(text) {
1258
+ try {
1259
+ if (typeof navigator !== "undefined" && navigator.clipboard !== void 0 && typeof navigator.clipboard.writeText === "function") {
1260
+ await navigator.clipboard.writeText(text);
1261
+ return true;
1262
+ }
1263
+ } catch {
1264
+ }
1265
+ if (typeof document === "undefined") return false;
1266
+ const area = document.createElement("textarea");
1267
+ area.value = text;
1268
+ area.setAttribute("readonly", "");
1269
+ area.style.position = "fixed";
1270
+ area.style.insetBlockStart = "0";
1271
+ area.style.opacity = "0";
1272
+ document.body.append(area);
1273
+ area.select();
1274
+ let copied;
1275
+ try {
1276
+ copied = document.execCommand("copy");
1277
+ } catch {
1278
+ copied = false;
1279
+ }
1280
+ area.remove();
1281
+ return copied;
1282
+ }
1283
+ function downloadText(text, fileName, type = "text/csv;charset=utf-8") {
1284
+ if (typeof document === "undefined" || typeof URL === "undefined" || typeof URL.createObjectURL !== "function") return;
1285
+ const url = URL.createObjectURL(new Blob([text], { type }));
1286
+ const link = document.createElement("a");
1287
+ link.href = url;
1288
+ link.download = fileName;
1289
+ link.hidden = true;
1290
+ document.body.append(link);
1291
+ link.click();
1292
+ link.remove();
1293
+ setTimeout(() => URL.revokeObjectURL(url), 0);
1294
+ }
1295
+
1296
+ // src/react/internal/use-controllable.ts
1297
+ import { useCallback, useRef as useRef5, useState as useState5 } from "react";
1298
+ function useControllableState(value, defaultValue, onChange) {
1299
+ const [inner, setInner] = useState5(defaultValue);
1300
+ const controlled = value !== void 0;
1301
+ const current = controlled ? value : inner;
1302
+ const latest = useRef5(current);
1303
+ latest.current = current;
1304
+ const isControlled = useRef5(controlled);
1305
+ isControlled.current = controlled;
1306
+ const handler = useRef5(onChange);
1307
+ handler.current = onChange;
1308
+ const set = useCallback((next) => {
1309
+ const resolved = typeof next === "function" ? next(latest.current) : next;
1310
+ if (Object.is(resolved, latest.current)) return;
1311
+ latest.current = resolved;
1312
+ if (!isControlled.current) setInner(resolved);
1313
+ handler.current?.(resolved);
1314
+ }, []);
1315
+ return [current, set];
1316
+ }
1317
+ function useEvent(callback) {
1318
+ const ref = useRef5(callback);
1319
+ ref.current = callback;
1320
+ return useCallback((...args) => ref.current(...args), []);
1321
+ }
1322
+
1323
+ // src/react/pagination.tsx
1324
+ import { Icon as Icon6 } from "@x12i/icons";
1325
+ import { IconButton as IconButton5 } from "@x12i/ui";
1326
+ import { useMemo } from "react";
1327
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
1328
+ function Pagination({
1329
+ page,
1330
+ pageSize,
1331
+ total,
1332
+ pageSizeOptions,
1333
+ onPageChange,
1334
+ onPageSizeChange,
1335
+ messages: overrides,
1336
+ locale,
1337
+ disabled = false,
1338
+ className
1339
+ }) {
1340
+ const messages = useMemo(() => resolveMessages(overrides), [overrides]);
1341
+ const pages = total === null ? null : Math.max(1, Math.ceil(total / Math.max(pageSize, 1)));
1342
+ const lastPage = pages === null ? Number.POSITIVE_INFINITY : pages - 1;
1343
+ const current = Math.min(Math.max(page, 0), lastPage);
1344
+ const start = total === 0 ? 0 : current * pageSize + 1;
1345
+ const end = total === null ? (current + 1) * pageSize : Math.min((current + 1) * pageSize, total);
1346
+ const atStart = disabled || current <= 0;
1347
+ const atEnd = disabled || current >= lastPage;
1348
+ const go = (target) => onPageChange(Math.min(Math.max(target, 0), lastPage));
1349
+ const sizes = useMemo(() => {
1350
+ const set = /* @__PURE__ */ new Set([...pageSizeOptions ?? [], pageSize]);
1351
+ return [...set].sort((a, b) => a - b);
1352
+ }, [pageSizeOptions, pageSize]);
1353
+ return /* @__PURE__ */ jsxs8("nav", { "aria-label": messages.paginationLabel, "data-kit": "data-grid-pagination", ...className === void 0 ? {} : { className }, children: [
1354
+ onPageSizeChange !== void 0 ? /* @__PURE__ */ jsxs8("label", { "data-kit-slot": "page-size", children: [
1355
+ /* @__PURE__ */ jsx10("span", { children: messages.pageSize }),
1356
+ /* @__PURE__ */ jsx10(
1357
+ SelectInput,
1358
+ {
1359
+ label: messages.pageSize,
1360
+ value: pageSize,
1361
+ disabled,
1362
+ onChange: (event) => onPageSizeChange(Number(event.currentTarget.value)),
1363
+ children: sizes.map((size) => /* @__PURE__ */ jsx10("option", { value: size, children: new Intl.NumberFormat(locale).format(size) }, size))
1364
+ }
1365
+ )
1366
+ ] }) : null,
1367
+ /* @__PURE__ */ jsx10("span", { "data-kit-slot": "range", children: total === null ? formatMessage(messages.pageStatus, { page: current + 1, pages: "?" }, locale) : formatMessage(messages.pageRange, { start, end, total }, locale) }),
1368
+ /* @__PURE__ */ jsxs8("span", { "data-kit-slot": "buttons", children: [
1369
+ /* @__PURE__ */ jsx10(IconButton5, { label: messages.firstPage, variant: "ghost", tone: "neutral", size: "sm", disabled: atStart, onClick: () => go(0), children: /* @__PURE__ */ jsxs8("span", { "data-flip": "", "data-kit-slot": "double-chevron", children: [
1370
+ /* @__PURE__ */ jsx10(Icon6, { name: "chevron-left", size: 12 }),
1371
+ /* @__PURE__ */ jsx10(Icon6, { name: "chevron-left", size: 12 })
1372
+ ] }) }),
1373
+ /* @__PURE__ */ jsx10(IconButton5, { label: messages.previousPage, variant: "ghost", tone: "neutral", size: "sm", disabled: atStart, onClick: () => go(current - 1), children: /* @__PURE__ */ jsx10("span", { "data-flip": "", children: /* @__PURE__ */ jsx10(Icon6, { name: "chevron-left", size: 16 }) }) }),
1374
+ pages === null ? null : /* @__PURE__ */ jsx10("span", { "data-kit-slot": "status", children: formatMessage(messages.pageStatus, { page: current + 1, pages }, locale) }),
1375
+ /* @__PURE__ */ jsx10(IconButton5, { label: messages.nextPage, variant: "ghost", tone: "neutral", size: "sm", disabled: atEnd, onClick: () => go(current + 1), children: /* @__PURE__ */ jsx10("span", { "data-flip": "", children: /* @__PURE__ */ jsx10(Icon6, { name: "chevron-right", size: 16 }) }) }),
1376
+ /* @__PURE__ */ jsx10(
1377
+ IconButton5,
1378
+ {
1379
+ label: messages.lastPage,
1380
+ variant: "ghost",
1381
+ tone: "neutral",
1382
+ size: "sm",
1383
+ disabled: atEnd || pages === null,
1384
+ onClick: () => go(lastPage),
1385
+ children: /* @__PURE__ */ jsxs8("span", { "data-flip": "", "data-kit-slot": "double-chevron", children: [
1386
+ /* @__PURE__ */ jsx10(Icon6, { name: "chevron-right", size: 12 }),
1387
+ /* @__PURE__ */ jsx10(Icon6, { name: "chevron-right", size: 12 })
1388
+ ] })
1389
+ }
1390
+ )
1391
+ ] })
1392
+ ] });
1393
+ }
1394
+
1395
+ // src/react/toolbar.tsx
1396
+ import { Icon as Icon7 } from "@x12i/icons";
1397
+ import { Button as Button3, IconButton as IconButton6 } from "@x12i/ui";
1398
+ import { useState as useState6 } from "react";
1399
+ import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
1400
+ function Toolbar(props) {
1401
+ const { messages, locale, quickFilter, selectedCount, totalCount } = props;
1402
+ const [columnsAnchor, setColumnsAnchor] = useState6(null);
1403
+ const [columnsOpen, setColumnsOpen] = useState6(false);
1404
+ const items = [
1405
+ ...props.columns.map((column) => ({
1406
+ id: column.id,
1407
+ label: column.header,
1408
+ checked: column.visible,
1409
+ keepOpen: true,
1410
+ onSelect: () => props.onToggleColumn(column.id, !column.visible)
1411
+ })),
1412
+ {
1413
+ id: "__show-all",
1414
+ label: messages.showAllColumns,
1415
+ separatorBefore: true,
1416
+ disabled: props.columns.every((column) => column.visible),
1417
+ onSelect: props.onShowAllColumns,
1418
+ keepOpen: true
1419
+ }
1420
+ ];
1421
+ const hasStart = props.start !== void 0 && props.start !== null;
1422
+ const hasEnd = props.end !== void 0 && props.end !== null;
1423
+ const showSelection = selectedCount > 0;
1424
+ const showAnything = props.search || props.columnsMenu || props.exportButton || props.copyButton || showSelection || hasStart || hasEnd;
1425
+ if (!showAnything) return null;
1426
+ const allSelected = totalCount !== null && totalCount > 0 && selectedCount === totalCount;
1427
+ return /* @__PURE__ */ jsxs9("div", { role: "group", "aria-label": messages.toolbarLabel, "data-kit": "data-grid-toolbar", children: [
1428
+ hasStart ? /* @__PURE__ */ jsx11("div", { "data-kit-slot": "toolbar-start", children: props.start }) : null,
1429
+ props.search ? /* @__PURE__ */ jsxs9("div", { "data-kit-slot": "search", children: [
1430
+ /* @__PURE__ */ jsx11("span", { "data-kit-slot": "search-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx11(Icon7, { name: "search", size: 16 }) }),
1431
+ /* @__PURE__ */ jsx11(
1432
+ TextInput,
1433
+ {
1434
+ label: messages.quickSearch,
1435
+ type: "search",
1436
+ value: quickFilter,
1437
+ placeholder: messages.quickSearchPlaceholder,
1438
+ onChange: (event) => props.onQuickFilterChange(event.currentTarget.value),
1439
+ onKeyDown: (event) => {
1440
+ if (event.key === "Escape" && quickFilter !== "") {
1441
+ event.preventDefault();
1442
+ props.onQuickFilterChange("");
1443
+ }
1444
+ }
1445
+ }
1446
+ ),
1447
+ quickFilter !== "" ? /* @__PURE__ */ jsx11(
1448
+ IconButton6,
1449
+ {
1450
+ label: messages.clearSearch,
1451
+ variant: "ghost",
1452
+ tone: "neutral",
1453
+ size: "xs",
1454
+ onClick: () => props.onQuickFilterChange(""),
1455
+ children: /* @__PURE__ */ jsx11(Icon7, { name: "close", size: 12 })
1456
+ }
1457
+ ) : null
1458
+ ] }) : null,
1459
+ showSelection ? /* @__PURE__ */ jsxs9("div", { "data-kit-slot": "selection", children: [
1460
+ /* @__PURE__ */ jsx11("span", { "data-kit-slot": "selection-count", children: allSelected ? formatMessage(messages.allSelected, { count: selectedCount }, locale) : formatMessage(messages.selectedCount, { count: selectedCount }, locale) }),
1461
+ /* @__PURE__ */ jsx11(Button3, { variant: "ghost", tone: "neutral", size: "sm", onClick: props.onClearSelection, children: messages.clearSelection })
1462
+ ] }) : null,
1463
+ /* @__PURE__ */ jsxs9("div", { "data-kit-slot": "toolbar-actions", children: [
1464
+ props.copyButton && showSelection ? /* @__PURE__ */ jsxs9(Button3, { variant: "outline", tone: "neutral", size: "sm", onClick: props.onCopy, children: [
1465
+ /* @__PURE__ */ jsx11(Icon7, { name: "copy", size: 16 }),
1466
+ messages.copySelection
1467
+ ] }) : null,
1468
+ props.exportButton ? /* @__PURE__ */ jsxs9(Button3, { variant: "outline", tone: "neutral", size: "sm", loading: props.exporting, loadingLabel: messages.loading, onClick: props.onExport, children: [
1469
+ /* @__PURE__ */ jsx11(Icon7, { name: "download", size: 16 }),
1470
+ messages.exportCsv
1471
+ ] }) : null,
1472
+ props.columnsMenu ? /* @__PURE__ */ jsxs9(Fragment5, { children: [
1473
+ /* @__PURE__ */ jsxs9(
1474
+ Button3,
1475
+ {
1476
+ ref: setColumnsAnchor,
1477
+ variant: "outline",
1478
+ tone: "neutral",
1479
+ size: "sm",
1480
+ "aria-haspopup": "menu",
1481
+ "aria-expanded": columnsOpen,
1482
+ onClick: () => setColumnsOpen((open) => !open),
1483
+ children: [
1484
+ /* @__PURE__ */ jsx11(Icon7, { name: "eye", size: 16 }),
1485
+ messages.columns
1486
+ ]
1487
+ }
1488
+ ),
1489
+ /* @__PURE__ */ jsx11(
1490
+ Menu,
1491
+ {
1492
+ open: columnsOpen,
1493
+ anchor: columnsAnchor,
1494
+ label: messages.columnsMenuLabel,
1495
+ items,
1496
+ align: "end",
1497
+ onClose: (reason) => {
1498
+ setColumnsOpen(false);
1499
+ if (reason === "escape" || reason === "select") columnsAnchor?.focus({ preventScroll: true });
1500
+ }
1501
+ }
1502
+ )
1503
+ ] }) : null,
1504
+ hasEnd ? /* @__PURE__ */ jsx11("div", { "data-kit-slot": "toolbar-end", children: props.end }) : null
1505
+ ] })
1506
+ ] });
1507
+ }
1508
+
1509
+ // src/react/use-server-rows.ts
1510
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef6, useState as useState7, useSyncExternalStore } from "react";
1511
+ var EMPTY = { version: 0, total: null, loading: false, error: null, stale: false };
1512
+ function useServerRowStore({ dataSource, blockSize, query }) {
1513
+ const [store, setStore] = useState7(null);
1514
+ const latest = useRef6({ blockSize, query });
1515
+ latest.current = { blockSize, query };
1516
+ useEffect2(() => {
1517
+ if (dataSource === void 0) {
1518
+ setStore(null);
1519
+ return;
1520
+ }
1521
+ const created = createServerRowStore({ dataSource, blockSize: latest.current.blockSize, query: latest.current.query });
1522
+ setStore(created);
1523
+ return () => {
1524
+ created.destroy();
1525
+ };
1526
+ }, [dataSource]);
1527
+ useEffect2(() => {
1528
+ store?.setBlockSize(blockSize);
1529
+ }, [store, blockSize]);
1530
+ useEffect2(() => {
1531
+ store?.setQuery(query);
1532
+ }, [store, query]);
1533
+ const subscribe = useCallback2((listener) => store === null ? () => {
1534
+ } : store.subscribe(listener), [store]);
1535
+ const getSnapshot = useCallback2(() => store === null ? EMPTY : store.getSnapshot(), [store]);
1536
+ const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
1537
+ return { store, snapshot };
1538
+ }
1539
+ async function fetchAllRows(dataSource, query, options = {}) {
1540
+ const pageSize = options.pageSize ?? 500;
1541
+ const rows = [];
1542
+ let start = 0;
1543
+ let total = Number.POSITIVE_INFINITY;
1544
+ let cursor;
1545
+ while (start < total) {
1546
+ const request = {
1547
+ startRow: start,
1548
+ endRow: start + pageSize,
1549
+ sort: query.sort,
1550
+ filter: query.filter,
1551
+ group: query.group,
1552
+ ...query.quickFilter === void 0 || query.quickFilter === "" ? {} : { quickFilter: query.quickFilter },
1553
+ ...cursor === void 0 ? {} : { cursor }
1554
+ };
1555
+ const result = await dataSource.getRows(request, options.signal === void 0 ? {} : { signal: options.signal });
1556
+ rows.push(...result.rows);
1557
+ total = result.total;
1558
+ if (result.rows.length === 0) break;
1559
+ start += result.rows.length;
1560
+ if (result.nextCursor !== void 0) {
1561
+ if (result.nextCursor === null) break;
1562
+ cursor = result.nextCursor;
1563
+ }
1564
+ }
1565
+ return rows;
1566
+ }
1567
+
1568
+ // src/react/data-grid.tsx
1569
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1570
+ var SYSTEM_COLUMN_WIDTH = 44;
1571
+ var DEFAULT_ROW_HEIGHT = 40;
1572
+ var DEFAULT_MAX_HEIGHT = 640;
1573
+ var ROW_VIRTUALIZE_THRESHOLD = 100;
1574
+ var COLUMN_VIRTUALIZE_THRESHOLD = 24;
1575
+ var DEFAULT_BLOCK_SIZE = 100;
1576
+ var INITIAL_VIEWPORT_WIDTH = 1024;
1577
+ var DEFAULT_PAGE_SIZES = [10, 25, 50, 100];
1578
+ var DEFAULT_PAGINATION = { page: 0, pageSize: 25 };
1579
+ var SELECT_ID = "__select";
1580
+ var EXPAND_ID = "__expand";
1581
+ var NO_ROWS = [];
1582
+ var NO_INDEXES = [];
1583
+ var NO_IDS = [];
1584
+ var NO_FLAGS = {};
1585
+ var NO_DISPLAY = { rows: [], groups: [] };
1586
+ function useDebouncedValue(value, delay) {
1587
+ const [debounced, setDebounced] = useState8(value);
1588
+ useEffect3(() => {
1589
+ if (delay <= 0) return;
1590
+ const timer = setTimeout(() => setDebounced(value), delay);
1591
+ return () => clearTimeout(timer);
1592
+ }, [value, delay]);
1593
+ return delay <= 0 ? value : debounced;
1594
+ }
1595
+ function resolveToolbar(option) {
1596
+ if (option === false) return null;
1597
+ const all = { search: true, columns: true, export: true, copy: true };
1598
+ if (option === void 0 || option === true) return all;
1599
+ return { ...all, ...option };
1600
+ }
1601
+ function sameText(a, b) {
1602
+ if (isEmptyValue(a) && isEmptyValue(b)) return true;
1603
+ return Object.is(a, b) || toText(a) === toText(b);
1604
+ }
1605
+ function activate(element, event) {
1606
+ element.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true, shiftKey: event.shiftKey }));
1607
+ }
1608
+ function DataGrid(props) {
1609
+ const {
1610
+ columns,
1611
+ rows,
1612
+ dataSource,
1613
+ locale,
1614
+ timeZone,
1615
+ density,
1616
+ dir,
1617
+ height,
1618
+ maxHeight,
1619
+ estimateRowHeight = DEFAULT_ROW_HEIGHT,
1620
+ virtualize = "auto",
1621
+ overscan = 8,
1622
+ columnOverscan = 3,
1623
+ selectionMode = "none",
1624
+ multiSort = true,
1625
+ groupsExpandedByDefault = true,
1626
+ paginated = false,
1627
+ pageSizeOptions = DEFAULT_PAGE_SIZES,
1628
+ renderDetail,
1629
+ detailHeight,
1630
+ isRowSelectable,
1631
+ isRowExpandable,
1632
+ onRowClick,
1633
+ showTotals = false,
1634
+ quickFilterDelay = 250
1635
+ } = props;
1636
+ const serverMode = dataSource !== void 0;
1637
+ const getRowId = useEvent(props.getRowId);
1638
+ const messages = useMemo2(() => resolveMessages(props.messages), [props.messages]);
1639
+ const t = useCallback3(
1640
+ (key, values) => formatMessage(messages[key], values, locale),
1641
+ [messages, locale]
1642
+ );
1643
+ const formatContext = useMemo2(
1644
+ () => ({ ...locale === void 0 ? {} : { locale }, ...timeZone === void 0 ? {} : { timeZone }, labels: { yes: messages.yes, no: messages.no } }),
1645
+ [locale, timeZone, messages.yes, messages.no]
1646
+ );
1647
+ const formatters = useMemo2(() => createFormatters(columns, formatContext), [columns, formatContext]);
1648
+ const byId = useMemo2(() => indexColumns(columns), [columns]);
1649
+ const [initialView] = useState8(() => createViewState(columns));
1650
+ const [sort, setSort2] = useControllableState(props.sort, props.defaultSort ?? [], props.onSortChange);
1651
+ const [filter, setFilter] = useControllableState(props.filter, props.defaultFilter ?? [], props.onFilterChange);
1652
+ const [quickFilter, setQuickFilter] = useControllableState(props.quickFilter, props.defaultQuickFilter ?? "", props.onQuickFilterChange);
1653
+ const [group, setGroup] = useControllableState(props.group, props.defaultGroup ?? [], props.onGroupChange);
1654
+ const [groupExpansion, setGroupExpansion] = useControllableState(props.groupExpansion, props.defaultGroupExpansion ?? NO_FLAGS, props.onGroupExpansionChange);
1655
+ const [columnOrder, setColumnOrder] = useControllableState(props.columnOrder, props.defaultColumnOrder ?? initialView.columnOrder, props.onColumnOrderChange);
1656
+ const [columnVisibility, setColumnVisibility] = useControllableState(props.columnVisibility, props.defaultColumnVisibility ?? initialView.columnVisibility, props.onColumnVisibilityChange);
1657
+ const [columnSizing, setColumnSizing] = useControllableState(props.columnSizing, props.defaultColumnSizing ?? initialView.columnSizing, props.onColumnSizingChange);
1658
+ const [columnPinning, setColumnPinning] = useControllableState(props.columnPinning, props.defaultColumnPinning ?? initialView.columnPinning, props.onColumnPinningChange);
1659
+ const [selection, setSelection] = useControllableState(props.selection, props.defaultSelection ?? EMPTY_SELECTION, props.onSelectionChange);
1660
+ const [expanded, setExpanded] = useControllableState(props.expanded, props.defaultExpanded ?? NO_FLAGS, props.onExpandedChange);
1661
+ const [pagination, setPagination] = useControllableState(props.pagination, props.defaultPagination ?? DEFAULT_PAGINATION, props.onPaginationChange);
1662
+ const effectiveGroup = useMemo2(() => serverMode ? [] : group.filter((item) => byId.has(item.field)), [serverMode, group, byId]);
1663
+ const effectiveFilters = useMemo2(() => filter.filter((item) => byId.has(item.field) && isFilterEffective(item)), [filter, byId]);
1664
+ const [nav, setNav] = useState8({ row: 0, col: 0 });
1665
+ const [editing, setEditing] = useState8(null);
1666
+ const [overrides, setOverrides] = useState8({});
1667
+ const [popover, setPopover] = useState8(null);
1668
+ const [announcement, setAnnouncement] = useState8({ text: "", flip: false });
1669
+ const [exporting, setExporting] = useState8(false);
1670
+ const [rtl, setRtl] = useState8(dir === "rtl");
1671
+ const [rowHeight, setRowHeight] = useState8(estimateRowHeight);
1672
+ const [viewport, setViewport] = useState8({ width: 0, height: 0 });
1673
+ const rootRef = useRef7(null);
1674
+ const scrollerRef = useRef7(null);
1675
+ const headerRef = useRef7(null);
1676
+ const focusRequest = useRef7(false);
1677
+ const scrollRequest = useRef7(false);
1678
+ const selectionAnchor = useRef7(null);
1679
+ const pendingColumnFocus = useRef7(null);
1680
+ const editingRef = useRef7(null);
1681
+ editingRef.current = editing;
1682
+ const announce = useCallback3((text) => {
1683
+ setAnnouncement((previous) => ({ text, flip: previous.text === text ? !previous.flip : previous.flip }));
1684
+ }, []);
1685
+ useLayoutEffect3(() => {
1686
+ setRtl(dir !== void 0 ? dir === "rtl" : detectDirection(rootRef.current) === "rtl");
1687
+ }, [dir]);
1688
+ const layout = useMemo2(
1689
+ () => layoutColumns(columns, { columnOrder, columnVisibility, columnPinning }),
1690
+ [columns, columnOrder, columnVisibility, columnPinning]
1691
+ );
1692
+ const hasSelectColumn = selectionMode !== "none";
1693
+ const hasExpandColumn = renderDetail !== void 0 && !serverMode;
1694
+ const geometry = useMemo2(() => {
1695
+ const system = (kind, id) => ({
1696
+ kind,
1697
+ id,
1698
+ column: null,
1699
+ width: SYSTEM_COLUMN_WIDTH,
1700
+ minWidth: SYSTEM_COLUMN_WIDTH,
1701
+ maxWidth: SYSTEM_COLUMN_WIDTH,
1702
+ pin: "start",
1703
+ pinOffset: 0,
1704
+ pinEdge: false,
1705
+ align: "center",
1706
+ first: false
1707
+ });
1708
+ const data = (id) => {
1709
+ const column = byId.get(id);
1710
+ if (column === void 0) return null;
1711
+ return {
1712
+ kind: "data",
1713
+ id,
1714
+ column,
1715
+ width: columnWidth(column, columnSizing),
1716
+ minWidth: column.minWidth ?? DEFAULT_COLUMN_MIN_WIDTH,
1717
+ maxWidth: column.maxWidth ?? DEFAULT_COLUMN_MAX_WIDTH,
1718
+ pin: null,
1719
+ pinOffset: 0,
1720
+ pinEdge: false,
1721
+ align: resolveColumnAlign(column),
1722
+ first: false
1723
+ };
1724
+ };
1725
+ const known = (ids) => ids.map(data).filter((info) => info !== null);
1726
+ const start = [
1727
+ ...hasSelectColumn ? [system("select", SELECT_ID)] : [],
1728
+ ...hasExpandColumn ? [system("expand", EXPAND_ID)] : [],
1729
+ ...known(layout.start)
1730
+ ];
1731
+ const center = known(layout.center);
1732
+ const end = known(layout.end);
1733
+ let offset = 0;
1734
+ for (const info of start) {
1735
+ info.pin = "start";
1736
+ info.pinOffset = offset;
1737
+ offset += info.width;
1738
+ }
1739
+ const startWidth2 = offset;
1740
+ const lastStart = start[start.length - 1];
1741
+ if (lastStart !== void 0) lastStart.pinEdge = true;
1742
+ offset = 0;
1743
+ for (let i = end.length - 1; i >= 0; i -= 1) {
1744
+ const info = end[i];
1745
+ info.pin = "end";
1746
+ info.pinOffset = offset;
1747
+ offset += info.width;
1748
+ }
1749
+ const endWidth2 = offset;
1750
+ const firstEnd = end[0];
1751
+ if (firstEnd !== void 0) firstEnd.pinEdge = true;
1752
+ const centerOffsets2 = [0];
1753
+ for (const info of center) centerOffsets2.push((centerOffsets2[centerOffsets2.length - 1] ?? 0) + info.width);
1754
+ const centerWidth2 = centerOffsets2[centerOffsets2.length - 1] ?? 0;
1755
+ const all = [...start, ...center, ...end];
1756
+ const firstData = all.find((info) => info.kind === "data");
1757
+ if (firstData !== void 0) firstData.first = true;
1758
+ return {
1759
+ all,
1760
+ startCount: start.length,
1761
+ centerCount: center.length,
1762
+ startWidth: startWidth2,
1763
+ centerWidth: centerWidth2,
1764
+ endWidth: endWidth2,
1765
+ centerOffsets: centerOffsets2,
1766
+ totalWidth: startWidth2 + centerWidth2 + endWidth2
1767
+ };
1768
+ }, [byId, columnSizing, hasSelectColumn, hasExpandColumn, layout]);
1769
+ const { all: gridColumns, startCount, centerCount, startWidth, endWidth, centerWidth, centerOffsets, totalWidth } = geometry;
1770
+ const dataColumns = useMemo2(
1771
+ () => gridColumns.flatMap((info) => info.column === null ? [] : [info.column]),
1772
+ [gridColumns]
1773
+ );
1774
+ const hasColumnGroups = dataColumns.some((column) => column.group !== void 0 && column.group !== "");
1775
+ const headerRowCount = hasColumnGroups ? 2 : 1;
1776
+ const headerHeight = rowHeight * headerRowCount;
1777
+ const quickText = useDeferredValue(quickFilter).trim();
1778
+ const serverQuick = useDebouncedValue(quickFilter.trim(), serverMode ? quickFilterDelay : 0);
1779
+ const blockSize = paginated ? Math.max(1, pagination.pageSize) : props.blockSize ?? DEFAULT_BLOCK_SIZE;
1780
+ const queryKey = JSON.stringify([sort, effectiveFilters, serverQuick]);
1781
+ const query = useMemo2(
1782
+ () => toServerQuery(columns, {
1783
+ sort,
1784
+ filter: effectiveFilters,
1785
+ group: [],
1786
+ ...serverQuick === "" ? {} : { quickFilter: serverQuick }
1787
+ }),
1788
+ // `queryKey` stands for the values it was built from, so a new array with the same content is not a new query.
1789
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1790
+ [queryKey, columns]
1791
+ );
1792
+ const { store, snapshot } = useServerRowStore({ dataSource, blockSize, query });
1793
+ const rowsArray = rows ?? NO_ROWS;
1794
+ const hasQuick = quickText !== "";
1795
+ const searchIndex = useMemo2(
1796
+ () => serverMode || !hasQuick ? void 0 : createSearchIndex(rowsArray, columns, formatContext),
1797
+ [serverMode, hasQuick, rowsArray, columns, formatContext]
1798
+ );
1799
+ const indexes = useMemo2(
1800
+ () => serverMode ? NO_INDEXES : computeViewIndexes(rowsArray, columns, {
1801
+ filter: effectiveFilters,
1802
+ sort,
1803
+ quickFilter: quickText,
1804
+ searchIndex,
1805
+ ...locale === void 0 ? {} : { locale },
1806
+ ...timeZone === void 0 ? {} : { timeZone },
1807
+ labels: formatContext.labels
1808
+ }),
1809
+ [serverMode, rowsArray, columns, effectiveFilters, sort, quickText, searchIndex, locale, timeZone, formatContext]
1810
+ );
1811
+ const pageSize = Math.max(1, pagination.pageSize);
1812
+ const totalCount = serverMode ? snapshot.total : indexes.length;
1813
+ const lastPageIndex = totalCount === null ? Number.POSITIVE_INFINITY : Math.max(0, Math.ceil(totalCount / pageSize) - 1);
1814
+ const page = paginated ? Math.min(Math.max(pagination.page, 0), lastPageIndex) : 0;
1815
+ const pageOffset = paginated ? page * pageSize : 0;
1816
+ const pageIndexes = useMemo2(
1817
+ () => !paginated || serverMode ? indexes : indexes.slice(pageOffset, pageOffset + pageSize),
1818
+ [paginated, serverMode, indexes, pageOffset, pageSize]
1819
+ );
1820
+ const withDetail = renderDetail !== void 0;
1821
+ const display = useMemo2(
1822
+ () => serverMode ? NO_DISPLAY : buildDisplayRows(rowsArray, pageIndexes, columns, {
1823
+ getRowId,
1824
+ group: effectiveGroup,
1825
+ groupExpansion,
1826
+ groupsExpandedByDefault,
1827
+ expandedRows: expanded,
1828
+ withDetail,
1829
+ sort,
1830
+ ...locale === void 0 ? {} : { locale }
1831
+ }),
1832
+ [serverMode, rowsArray, pageIndexes, columns, getRowId, effectiveGroup, groupExpansion, groupsExpandedByDefault, expanded, withDetail, sort, locale]
1833
+ );
1834
+ const serverRowCount = snapshot.total === null ? 0 : paginated ? Math.max(0, Math.min(pageSize, snapshot.total - pageOffset)) : snapshot.total;
1835
+ const rowCount = serverMode ? serverRowCount : display.rows.length;
1836
+ const serverItems = useMemo2(() => /* @__PURE__ */ new Map(), [store, snapshot.version, pageOffset]);
1837
+ const getItem = useCallback3(
1838
+ (index) => {
1839
+ if (!serverMode) return display.rows[index];
1840
+ if (index < 0 || index >= serverRowCount) return void 0;
1841
+ const cached = serverItems.get(index);
1842
+ if (cached !== void 0) return cached;
1843
+ const globalIndex = pageOffset + index;
1844
+ const row = store?.getRow(globalIndex);
1845
+ const item = row === void 0 ? { kind: "placeholder", id: `~loading:${globalIndex}`, index: globalIndex, depth: 0, failed: store?.getBlockStatus(globalIndex) === "error" } : { kind: "data", id: getRowId(row, globalIndex), row, index: globalIndex, depth: 0 };
1846
+ serverItems.set(index, item);
1847
+ return item;
1848
+ },
1849
+ [serverMode, display, serverRowCount, serverItems, pageOffset, store, getRowId]
1850
+ );
1851
+ const treegrid = effectiveGroup.length > 0;
1852
+ const footerRows = showTotals ? 1 : 0;
1853
+ const ariaRowOffset = headerRowCount + (treegrid ? 0 : pageOffset);
1854
+ const ariaRowCount = serverMode && snapshot.total === null ? -1 : (treegrid ? rowCount : serverMode ? snapshot.total ?? 0 : paginated ? indexes.length : rowCount) + headerRowCount + footerRows;
1855
+ useLayoutEffect3(() => {
1856
+ const scroller = scrollerRef.current;
1857
+ if (scroller === null) return;
1858
+ const measure = () => {
1859
+ const token = getComputedStyle(scroller).getPropertyValue("--kit-grid-row-height").trim();
1860
+ let next = token.endsWith("px") ? Number.parseFloat(token) : Number.NaN;
1861
+ if (Number.isNaN(next)) {
1862
+ const measured = headerRef.current?.offsetHeight ?? 0;
1863
+ next = measured > 0 ? measured : estimateRowHeight;
1864
+ }
1865
+ setRowHeight((previous) => previous === next ? previous : next);
1866
+ };
1867
+ measure();
1868
+ const header = headerRef.current;
1869
+ if (header === null || typeof ResizeObserver === "undefined") return;
1870
+ const observer = new ResizeObserver(measure);
1871
+ observer.observe(header);
1872
+ return () => observer.disconnect();
1873
+ }, [density, estimateRowHeight, headerRowCount]);
1874
+ useLayoutEffect3(() => {
1875
+ const scroller = scrollerRef.current;
1876
+ if (scroller === null) return;
1877
+ const read = () => setViewport(
1878
+ (previous) => previous.width === scroller.clientWidth && previous.height === scroller.clientHeight ? previous : { width: scroller.clientWidth, height: scroller.clientHeight }
1879
+ );
1880
+ read();
1881
+ if (typeof ResizeObserver === "undefined") return;
1882
+ const observer = new ResizeObserver(read);
1883
+ observer.observe(scroller);
1884
+ return () => observer.disconnect();
1885
+ }, []);
1886
+ const virtualizeRows = virtualize === true || virtualize === "auto" && rowCount > ROW_VIRTUALIZE_THRESHOLD;
1887
+ const virtualizeColumns = !hasColumnGroups && (virtualize === true || virtualize === "auto" && centerCount > COLUMN_VIRTUALIZE_THRESHOLD);
1888
+ const detailEstimate = detailHeight ?? rowHeight * 4;
1889
+ const navClamped = useMemo2(
1890
+ () => clampNav(nav, { rowCount: rowCount + 1, columnCount: Math.max(gridColumns.length, 1), pageSize: 1 }),
1891
+ [nav, rowCount, gridColumns.length]
1892
+ );
1893
+ const cursorRow = navClamped.row - 1;
1894
+ const cursorCenterColumn = navClamped.col - startCount;
1895
+ const rowRange = useCallback3(
1896
+ (range) => {
1897
+ if (!virtualizeRows) return Array.from({ length: range.count }, (_, index) => index);
1898
+ const base = defaultRangeExtractor(range);
1899
+ if (cursorRow < 0 || cursorRow >= range.count || base.includes(cursorRow)) return base;
1900
+ return [...base, cursorRow].sort((a, b) => a - b);
1901
+ },
1902
+ [virtualizeRows, cursorRow]
1903
+ );
1904
+ const estimateRow = useCallback3((index) => getItem(index)?.kind === "detail" ? detailEstimate : rowHeight, [getItem, detailEstimate, rowHeight]);
1905
+ const rowKey = useCallback3((index) => serverMode ? index : getItem(index)?.id ?? index, [serverMode, getItem]);
1906
+ const rowVirtualizer = useVirtualizer({
1907
+ count: rowCount,
1908
+ getScrollElement: () => scrollerRef.current,
1909
+ estimateSize: estimateRow,
1910
+ overscan,
1911
+ scrollMargin: headerHeight,
1912
+ scrollPaddingStart: headerHeight,
1913
+ scrollPaddingEnd: footerRows * rowHeight,
1914
+ getItemKey: rowKey,
1915
+ rangeExtractor: rowRange,
1916
+ // Gives the first render (and the server render) a window before the element is measured.
1917
+ initialRect: {
1918
+ width: 0,
1919
+ height: virtualizeRows ? typeof height === "number" ? height : DEFAULT_MAX_HEIGHT : Math.max(rowCount * rowHeight, 1)
1920
+ }
1921
+ });
1922
+ const columnRange = useCallback3(
1923
+ (range) => {
1924
+ if (!virtualizeColumns) return Array.from({ length: range.count }, (_, index) => index);
1925
+ const base = defaultRangeExtractor(range);
1926
+ if (cursorCenterColumn < 0 || cursorCenterColumn >= range.count || base.includes(cursorCenterColumn)) return base;
1927
+ return [...base, cursorCenterColumn].sort((a, b) => a - b);
1928
+ },
1929
+ [virtualizeColumns, cursorCenterColumn]
1930
+ );
1931
+ const estimateColumn = useCallback3((index) => (centerOffsets[index + 1] ?? 0) - (centerOffsets[index] ?? 0), [centerOffsets]);
1932
+ const columnKey = useCallback3((index) => gridColumns[startCount + index]?.id ?? index, [gridColumns, startCount]);
1933
+ const columnVirtualizer = useVirtualizer({
1934
+ horizontal: true,
1935
+ count: centerCount,
1936
+ getScrollElement: () => scrollerRef.current,
1937
+ estimateSize: estimateColumn,
1938
+ overscan: columnOverscan,
1939
+ scrollMargin: startWidth,
1940
+ scrollPaddingStart: startWidth,
1941
+ scrollPaddingEnd: endWidth,
1942
+ getItemKey: columnKey,
1943
+ rangeExtractor: columnRange,
1944
+ isRtl: rtl,
1945
+ initialRect: { width: virtualizeColumns ? INITIAL_VIEWPORT_WIDTH : Math.max(totalWidth, 1), height: 0 }
1946
+ });
1947
+ useEffect3(() => {
1948
+ rowVirtualizer.measure();
1949
+ }, [rowHeight, detailEstimate]);
1950
+ useEffect3(() => {
1951
+ columnVirtualizer.measure();
1952
+ }, [centerOffsets]);
1953
+ const virtualRows = rowVirtualizer.getVirtualItems();
1954
+ const virtualColumns = columnVirtualizer.getVirtualItems();
1955
+ const firstVirtual = virtualRows[0]?.index ?? 0;
1956
+ const lastVirtual = virtualRows[virtualRows.length - 1]?.index ?? 0;
1957
+ const slots = useMemo2(() => {
1958
+ const out = [];
1959
+ for (let i = 0; i < startCount; i += 1) out.push({ kind: "col", index: i });
1960
+ let previous = -1;
1961
+ for (const item of virtualColumns) {
1962
+ const gap = (centerOffsets[item.index] ?? 0) - (previous < 0 ? 0 : centerOffsets[previous + 1] ?? 0);
1963
+ if (gap > 0) out.push({ kind: "spacer", size: gap });
1964
+ out.push({ kind: "col", index: startCount + item.index });
1965
+ previous = item.index;
1966
+ }
1967
+ const tail = centerWidth - (previous < 0 ? 0 : centerOffsets[previous + 1] ?? 0);
1968
+ if (tail > 0) out.push({ kind: "spacer", size: tail });
1969
+ for (let i = startCount + centerCount; i < gridColumns.length; i += 1) out.push({ kind: "col", index: i });
1970
+ return out;
1971
+ }, [virtualColumns, centerOffsets, centerWidth, startCount, centerCount, gridColumns.length]);
1972
+ useEffect3(() => {
1973
+ if (store === null) return;
1974
+ if (snapshot.total === null) {
1975
+ store.request(pageOffset, pageOffset + 1);
1976
+ } else if (rowCount > 0) {
1977
+ const from = Math.max(0, firstVirtual - overscan);
1978
+ store.request(pageOffset + from, pageOffset + Math.min(rowCount, lastVirtual + overscan + 1));
1979
+ }
1980
+ }, [store, snapshot.version, snapshot.total, pageOffset, rowCount, firstVirtual, lastVirtual, overscan]);
1981
+ const resetKey = `${JSON.stringify(sort)}|${JSON.stringify(effectiveFilters)}|${serverMode ? serverQuick : quickText}`;
1982
+ const lastResetKey = useRef7(resetKey);
1983
+ const resetPage = useEvent(() => {
1984
+ if (paginated && pagination.page !== 0) setPagination({ ...pagination, page: 0 });
1985
+ });
1986
+ useEffect3(() => {
1987
+ if (lastResetKey.current === resetKey) return;
1988
+ lastResetKey.current = resetKey;
1989
+ resetPage();
1990
+ }, [resetKey, resetPage]);
1991
+ const clampPage = useEvent(() => {
1992
+ if (paginated && pagination.page !== page) setPagination({ ...pagination, page });
1993
+ });
1994
+ useEffect3(() => {
1995
+ clampPage();
1996
+ }, [page, pagination.page, clampPage]);
1997
+ const loadingFlag = serverMode ? snapshot.loading || store === null : props.loading === true;
1998
+ const filterKey = `${JSON.stringify(effectiveFilters)}|${serverMode ? serverQuick : quickText}`;
1999
+ const lastFilterKey = useRef7(filterKey);
2000
+ const pendingFilterAnnouncement = useRef7(false);
2001
+ useEffect3(() => {
2002
+ if (lastFilterKey.current !== filterKey) {
2003
+ lastFilterKey.current = filterKey;
2004
+ pendingFilterAnnouncement.current = true;
2005
+ }
2006
+ let count;
2007
+ if (serverMode) {
2008
+ const live = store?.getSnapshot();
2009
+ count = live !== void 0 && !live.loading && !live.stale ? live.total : null;
2010
+ } else {
2011
+ count = loadingFlag ? null : totalCount;
2012
+ }
2013
+ if (!pendingFilterAnnouncement.current || count === null) return;
2014
+ pendingFilterAnnouncement.current = false;
2015
+ const active = effectiveFilters.length > 0 || (serverMode ? serverQuick : quickText) !== "";
2016
+ announce(active ? t("announceFiltered", { count }) : t("announceFilterCleared", { count }));
2017
+ }, [filterKey, totalCount, loadingFlag, store, snapshot.version, effectiveFilters.length, serverMode, serverQuick, quickText, announce, t]);
2018
+ useEffect3(() => {
2019
+ setOverrides((previous) => Object.keys(previous).length === 0 ? previous : {});
2020
+ }, [rows, dataSource]);
2021
+ const selectableIds = useMemo2(() => {
2022
+ if (serverMode || selectionMode !== "multiple") return NO_IDS;
2023
+ const out = [];
2024
+ for (const index of indexes) {
2025
+ const row = rowsArray[index];
2026
+ if (isRowSelectable?.(row) === false) continue;
2027
+ out.push(getRowId(row, index));
2028
+ }
2029
+ return out;
2030
+ }, [serverMode, selectionMode, indexes, rowsArray, isRowSelectable, getRowId]);
2031
+ const selectAllState = useMemo2(() => {
2032
+ if (selectionMode !== "multiple") return "none";
2033
+ if (serverMode) {
2034
+ const total = snapshot.total ?? 0;
2035
+ const count = countSelected(selection, total);
2036
+ return count === 0 ? "none" : count >= total ? "all" : "some";
2037
+ }
2038
+ return summarizeSelection(selection, selectableIds);
2039
+ }, [selectionMode, serverMode, snapshot.total, selection, selectableIds]);
2040
+ const selectedCount = countSelected(selection, totalCount ?? 0);
2041
+ const commitSelection = useEvent((next) => {
2042
+ setSelection(next);
2043
+ announce(t("announceSelection", { count: countSelected(next, totalCount ?? 0) }));
2044
+ });
2045
+ const idsBetween = (from, to) => {
2046
+ const ids = [];
2047
+ for (let index = Math.min(from, to); index <= Math.max(from, to); index += 1) {
2048
+ const item = getItem(index);
2049
+ if (item?.kind !== "data") continue;
2050
+ if (isRowSelectable?.(item.row) === false) continue;
2051
+ ids.push(item.id);
2052
+ }
2053
+ return ids;
2054
+ };
2055
+ const toggleSelect = useEvent((rowId, displayIndex, range) => {
2056
+ if (selectionMode === "none") return;
2057
+ if (selectionMode === "single") {
2058
+ commitSelection(isSelected(selection, rowId) ? clearSelection() : selectOnly(rowId));
2059
+ selectionAnchor.current = displayIndex;
2060
+ return;
2061
+ }
2062
+ const target = !isSelected(selection, rowId);
2063
+ const anchor = selectionAnchor.current;
2064
+ if (range && anchor !== null) {
2065
+ const ids = idsBetween(anchor, displayIndex);
2066
+ commitSelection(selectRange(selection, ids, 0, ids.length - 1, target));
2067
+ } else {
2068
+ commitSelection(toggleRow(selection, rowId, target));
2069
+ }
2070
+ selectionAnchor.current = displayIndex;
2071
+ });
2072
+ const toggleSelectAll = useEvent(() => {
2073
+ if (selectionMode !== "multiple") return;
2074
+ if (selectAllState === "all") commitSelection(clearSelection());
2075
+ else commitSelection(serverMode ? selectAllExceptNone() : selectAll(selectableIds));
2076
+ });
2077
+ const totals = useMemo2(() => {
2078
+ if (!showTotals) return {};
2079
+ if (serverMode) return props.totals ?? {};
2080
+ const subset = indexes.map((index) => rowsArray[index]);
2081
+ const out = {};
2082
+ for (const column of dataColumns) {
2083
+ if (column.aggregate !== void 0) out[column.id] = computeAggregate(column, subset);
2084
+ }
2085
+ return out;
2086
+ }, [showTotals, serverMode, props.totals, indexes, rowsArray, dataColumns]);
2087
+ const hasTotals = showTotals && (dataColumns.some((column) => column.aggregate !== void 0 || column.footerCell !== void 0) || Object.keys(totals).length > 0);
2088
+ const formatGroupValue = useCallback3(
2089
+ (field, value) => {
2090
+ const column = byId.get(field);
2091
+ return column === void 0 ? toText(value) : plainFormatter(column, formatContext)(value, void 0);
2092
+ },
2093
+ [byId, formatContext]
2094
+ );
2095
+ const formatColumnAggregate = useCallback3(
2096
+ (columnId, value) => {
2097
+ const column = byId.get(columnId);
2098
+ return column === void 0 ? toText(value) : formatAggregate(column, value, formatContext);
2099
+ },
2100
+ [byId, formatContext]
2101
+ );
2102
+ const totalForAnnouncement = totalCount ?? rowCount;
2103
+ const sortColumn = useEvent((columnId, multi) => {
2104
+ const column = byId.get(columnId);
2105
+ if (column === void 0 || !isSortable(column)) return;
2106
+ const next = toggleSort(sort, columnId, multi && multiSort);
2107
+ setSort2(next);
2108
+ const direction = next.find((item) => item.field === columnId)?.direction;
2109
+ announce(
2110
+ direction === void 0 ? t("announceSortCleared", { count: totalForAnnouncement }) : t("announceSorted", { column: column.header, direction, count: totalForAnnouncement })
2111
+ );
2112
+ });
2113
+ const setColumnSort = (columnId, direction) => {
2114
+ const column = byId.get(columnId);
2115
+ if (column === void 0) return;
2116
+ setSort2(setSort(sort, columnId, direction, false));
2117
+ announce(
2118
+ direction === null ? t("announceSortCleared", { count: totalForAnnouncement }) : t("announceSorted", { column: column.header, direction, count: totalForAnnouncement })
2119
+ );
2120
+ };
2121
+ const resizeTo = useEvent((columnId, width, announceIt) => {
2122
+ const column = byId.get(columnId);
2123
+ if (column === void 0) return;
2124
+ const next = resizeColumn(columns, columnSizing, columnId, width);
2125
+ if (next[columnId] === columnSizing[columnId]) return;
2126
+ setColumnSizing(next);
2127
+ if (announceIt === true) announce(t("announceColumnResized", { column: column.header, width: next[columnId] ?? width }));
2128
+ });
2129
+ const applyMove = (columnId, moved) => {
2130
+ const column = byId.get(columnId);
2131
+ if (column === void 0) return;
2132
+ const fingerprint = (order, pinning) => JSON.stringify([order, pinning.start, pinning.end]);
2133
+ if (fingerprint(moved.columnOrder, moved.columnPinning) === fingerprint(resolveColumnOrder(columns, columnOrder), columnPinning)) return;
2134
+ setColumnOrder(moved.columnOrder);
2135
+ if (moved.columnPinning !== columnPinning) setColumnPinning(moved.columnPinning);
2136
+ const next = layoutColumns(columns, { columnOrder: moved.columnOrder, columnVisibility, columnPinning: moved.columnPinning });
2137
+ const ids = [...next.start, ...next.center, ...next.end];
2138
+ announce(t("announceColumnMoved", { column: column.header, position: ids.indexOf(columnId) + 1, total: ids.length }));
2139
+ pendingColumnFocus.current = columnId;
2140
+ };
2141
+ const moveBy = useEvent((columnId, delta) => {
2142
+ applyMove(columnId, moveColumn(columns, { columnOrder, columnVisibility, columnPinning }, columnId, delta));
2143
+ });
2144
+ const draggedColumn = useRef7(null);
2145
+ const [columnDrag, setColumnDrag] = useState8(null);
2146
+ const zoneOfColumn = (columnId) => layout.start.includes(columnId) ? "start" : layout.end.includes(columnId) ? "end" : layout.center.includes(columnId) ? "center" : null;
2147
+ const dragStartColumn = useEvent((columnId) => {
2148
+ draggedColumn.current = columnId;
2149
+ setColumnDrag({ id: columnId, over: null });
2150
+ });
2151
+ const dragOverColumn = useEvent((targetId, placement) => {
2152
+ const dragged = draggedColumn.current;
2153
+ if (dragged === null || dragged === targetId || zoneOfColumn(dragged) !== zoneOfColumn(targetId)) return false;
2154
+ setColumnDrag(
2155
+ (current) => current === null || current.over?.id === targetId && current.over.placement === placement ? current : { ...current, over: { id: targetId, placement } }
2156
+ );
2157
+ return true;
2158
+ });
2159
+ const dragLeaveColumn = useEvent((targetId) => {
2160
+ setColumnDrag((current) => current?.over?.id === targetId ? { ...current, over: null } : current);
2161
+ });
2162
+ const dragEndColumn = useEvent(() => {
2163
+ draggedColumn.current = null;
2164
+ setColumnDrag(null);
2165
+ });
2166
+ const dropColumn = useEvent((targetId, placement) => {
2167
+ const dragged = draggedColumn.current;
2168
+ dragEndColumn();
2169
+ if (dragged === null) return;
2170
+ applyMove(dragged, moveColumnTo(columns, { columnOrder, columnVisibility, columnPinning }, dragged, targetId, placement));
2171
+ });
2172
+ const hideColumn = (columnId) => {
2173
+ const column = byId.get(columnId);
2174
+ if (column === void 0) return;
2175
+ setColumnVisibility({ ...columnVisibility, [columnId]: false });
2176
+ announce(t("announceColumnHidden", { column: column.header }));
2177
+ setNav((previous) => ({ row: 0, col: Math.min(previous.col, Math.max(gridColumns.length - 2, 0)) }));
2178
+ };
2179
+ const pinColumn = (columnId, side) => {
2180
+ const column = byId.get(columnId);
2181
+ if (column === void 0) return;
2182
+ setColumnPinning(setPinning(columnPinning, columnId, side));
2183
+ if (side !== false) announce(t("announcePinned", { column: column.header, side }));
2184
+ pendingColumnFocus.current = columnId;
2185
+ };
2186
+ const changeGroup = (columnId) => {
2187
+ const column = byId.get(columnId);
2188
+ if (column === void 0) return;
2189
+ const adding = !group.some((item) => item.field === columnId);
2190
+ setGroup(toggleGroup(group, columnId));
2191
+ if (adding) announce(t("announceGrouped", { column: column.header }));
2192
+ };
2193
+ const applyFilter = (columnId, item) => {
2194
+ setFilter(setColumnFilter(filter, columnId, item));
2195
+ };
2196
+ const toggleExpand = useEvent((rowId) => {
2197
+ if (expanded[rowId] === true) {
2198
+ const { [rowId]: _closed, ...rest } = expanded;
2199
+ void _closed;
2200
+ setExpanded(rest);
2201
+ } else setExpanded({ ...expanded, [rowId]: true });
2202
+ });
2203
+ const toggleGroupRow = useEvent((groupId) => {
2204
+ const current = groupExpansion[groupId] ?? groupsExpandedByDefault;
2205
+ setGroupExpansion({ ...groupExpansion, [groupId]: !current });
2206
+ });
2207
+ const applyEdit = useEvent(async (rowId, column, row, value) => {
2208
+ const previousValue = overrides[rowId]?.[column.id]?.value ?? getColumnValue(column, row);
2209
+ if (sameText(previousValue, value)) return;
2210
+ const put = (status, stored) => setOverrides((all) => {
2211
+ const current = all[rowId]?.[column.id];
2212
+ if (status !== "pending" && (current === void 0 || !Object.is(current.value, value))) return all;
2213
+ return { ...all, [rowId]: { ...all[rowId], [column.id]: { value: stored, status } } };
2214
+ });
2215
+ put("pending", value);
2216
+ if (props.onCellEdit === void 0) {
2217
+ put("saved", value);
2218
+ return;
2219
+ }
2220
+ try {
2221
+ await props.onCellEdit({ row, rowId, column, value, previousValue });
2222
+ put("saved", value);
2223
+ announce(t("cellSaved", { column: column.header }));
2224
+ } catch {
2225
+ put("error", previousValue);
2226
+ announce(t("cellEditFailed", { column: column.header }));
2227
+ }
2228
+ });
2229
+ const focusCellSoon = () => {
2230
+ focusRequest.current = true;
2231
+ setNav((previous) => ({ ...previous }));
2232
+ };
2233
+ const startEdit = useEvent((rowId, columnId, row) => {
2234
+ const column = byId.get(columnId);
2235
+ if (column === void 0 || !isEditable(column, row) || resolveEditor(column) === "boolean") return;
2236
+ setOverrides((all) => {
2237
+ const current = all[rowId]?.[columnId];
2238
+ if (current?.status !== "error") return all;
2239
+ const { [columnId]: _failed, ...rest } = all[rowId] ?? {};
2240
+ void _failed;
2241
+ return { ...all, [rowId]: rest };
2242
+ });
2243
+ setEditing({ rowId, columnId, row, error: null });
2244
+ });
2245
+ const cancelEdit = useEvent(() => {
2246
+ setEditing(null);
2247
+ focusCellSoon();
2248
+ });
2249
+ const commitEdit = useEvent((input, source) => {
2250
+ const edit = editingRef.current;
2251
+ if (edit === null) return;
2252
+ const column = byId.get(edit.columnId);
2253
+ if (column === void 0) return;
2254
+ const parsed = parseEditorInput(column, input, edit.row, messages);
2255
+ const problem = parsed.ok ? column.validate?.(parsed.value, edit.row) : parsed.message;
2256
+ if (problem !== void 0 && problem !== null && problem !== "") {
2257
+ if (source === "blur") {
2258
+ setEditing(null);
2259
+ announce(problem);
2260
+ } else {
2261
+ setEditing({ ...edit, error: problem });
2262
+ }
2263
+ return;
2264
+ }
2265
+ if (!parsed.ok) return;
2266
+ setEditing(null);
2267
+ if (source !== "blur") focusCellSoon();
2268
+ void applyEdit(edit.rowId, column, edit.row, parsed.value);
2269
+ });
2270
+ const toggleBoolean = useEvent((rowId, columnId, value, row) => {
2271
+ const column = byId.get(columnId);
2272
+ if (column === void 0 || !isEditable(column, row)) return;
2273
+ const problem = column.validate?.(value, row);
2274
+ if (problem !== void 0 && problem !== null && problem !== "") {
2275
+ announce(problem);
2276
+ return;
2277
+ }
2278
+ void applyEdit(rowId, column, row, value);
2279
+ });
2280
+ const visibleColumnList = dataColumns;
2281
+ const collectRows = useEvent(async () => {
2282
+ if (dataSource !== void 0) return fetchAllRows(dataSource, query);
2283
+ return indexes.map((index) => rowsArray[index]);
2284
+ });
2285
+ const exportCsv = useEvent(async (options) => {
2286
+ const all = await collectRows();
2287
+ return gridToCsv(all, visibleColumnList, { ...formatContext, ...options });
2288
+ });
2289
+ const selectedRows = useEvent(() => {
2290
+ const out = [];
2291
+ if (serverMode) {
2292
+ for (let index = 0; index < serverRowCount; index += 1) {
2293
+ const item = getItem(index);
2294
+ if (item?.kind === "data" && isSelected(selection, item.id)) out.push(item.row);
2295
+ }
2296
+ return out;
2297
+ }
2298
+ for (const index of indexes) {
2299
+ const row = rowsArray[index];
2300
+ if (isSelected(selection, getRowId(row, index))) out.push(row);
2301
+ }
2302
+ return out;
2303
+ });
2304
+ const copySelection = useEvent(async () => {
2305
+ let text;
2306
+ const chosen = selectedRows();
2307
+ if (chosen.length > 0) {
2308
+ text = gridToTsv(chosen, visibleColumnList, { ...formatContext, header: false });
2309
+ } else {
2310
+ const info = gridColumns[navClamped.col];
2311
+ const item = getItem(navClamped.row - 1);
2312
+ if (info?.column == null || item?.kind !== "data") return false;
2313
+ text = formatters.get(info.column.id)?.(getColumnValue(info.column, item.row), item.row) ?? "";
2314
+ }
2315
+ const ok = await writeClipboard(text);
2316
+ announce(ok ? t("copied", { count: Math.max(chosen.length, 1) }) : t("copyFailed"));
2317
+ return ok;
2318
+ });
2319
+ const runExport = useEvent(async () => {
2320
+ setExporting(true);
2321
+ try {
2322
+ const csv = await exportCsv({ bom: true });
2323
+ if (props.onExport !== void 0) props.onExport(csv);
2324
+ else downloadText(csv, props.exportFileName ?? "export.csv");
2325
+ } finally {
2326
+ setExporting(false);
2327
+ }
2328
+ });
2329
+ const moveTo = (next) => {
2330
+ focusRequest.current = true;
2331
+ scrollRequest.current = true;
2332
+ setNav(next);
2333
+ };
2334
+ useImperativeHandle(
2335
+ props.ref,
2336
+ () => ({
2337
+ scrollToIndex: (index) => rowVirtualizer.scrollToIndex(Math.max(0, Math.min(index, rowCount - 1)), { align: "auto" }),
2338
+ scrollToRow: (rowId) => {
2339
+ for (let index = 0; index < rowCount; index += 1) {
2340
+ if (getItem(index)?.id === rowId) {
2341
+ rowVirtualizer.scrollToIndex(index, { align: "auto" });
2342
+ return;
2343
+ }
2344
+ }
2345
+ },
2346
+ focusCell: (row, column) => moveTo(clampNav({ row, col: column }, { rowCount: rowCount + 1, columnCount: Math.max(gridColumns.length, 1), pageSize: 1 })),
2347
+ exportCsv: (options) => exportCsv(options),
2348
+ copySelection: () => copySelection(),
2349
+ getSelectedRows: () => selectedRows(),
2350
+ reload: () => store?.reset(),
2351
+ getViewState: () => ({ sort, filter, group, columnOrder, columnVisibility, columnSizing, columnPinning })
2352
+ }),
2353
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2354
+ [rowVirtualizer, rowCount, getItem, gridColumns.length, store, sort, filter, group, columnOrder, columnVisibility, columnSizing, columnPinning]
2355
+ );
2356
+ const findCell = (row, col) => scrollerRef.current?.querySelector(`[data-nav-row="${row}"][data-nav-col="${col}"]`) ?? null;
2357
+ useLayoutEffect3(() => {
2358
+ if (!scrollRequest.current) return;
2359
+ scrollRequest.current = false;
2360
+ if (navClamped.row >= 1) rowVirtualizer.scrollToIndex(navClamped.row - 1, { align: "auto" });
2361
+ const centerIndex = navClamped.col - startCount;
2362
+ if (centerIndex >= 0 && centerIndex < centerCount) columnVirtualizer.scrollToIndex(centerIndex, { align: "auto" });
2363
+ }, [nav]);
2364
+ useLayoutEffect3(() => {
2365
+ if (!focusRequest.current) return;
2366
+ const cell = findCell(navClamped.row, navClamped.col);
2367
+ if (cell === null) return;
2368
+ focusRequest.current = false;
2369
+ cell.focus({ preventScroll: true });
2370
+ });
2371
+ useLayoutEffect3(() => {
2372
+ const id = pendingColumnFocus.current;
2373
+ if (id === null) return;
2374
+ const index = gridColumns.findIndex((info) => info.id === id);
2375
+ if (index < 0) return;
2376
+ pendingColumnFocus.current = null;
2377
+ focusRequest.current = true;
2378
+ scrollRequest.current = true;
2379
+ setNav({ row: 0, col: index });
2380
+ }, [gridColumns]);
2381
+ const onFocus = (event) => {
2382
+ const target = event.target;
2383
+ if (target.closest('[data-kit="data-grid-popover"]') !== null) return;
2384
+ const cell = target.closest("[data-nav-row]");
2385
+ if (cell === null) return;
2386
+ const row = Number(cell.dataset["navRow"]);
2387
+ const col = Number(cell.dataset["navCol"]);
2388
+ setNav((previous) => previous.row === row && previous.col === col ? previous : { row, col });
2389
+ };
2390
+ const pageRows = Math.max(1, Math.floor((viewport.height - headerHeight) / rowHeight));
2391
+ const onKeyDown = (event) => {
2392
+ if (event.defaultPrevented) return;
2393
+ const target = event.target;
2394
+ if (isTextEntry(target) || target.closest('[data-kit="data-grid-popover"]') !== null) return;
2395
+ const cell = target.closest("[data-nav-row]");
2396
+ if (cell === null || scrollerRef.current?.contains(cell) !== true) return;
2397
+ const row = Number(cell.dataset["navRow"]);
2398
+ const col = Number(cell.dataset["navCol"]);
2399
+ const onCell = target === cell;
2400
+ const info = gridColumns[col];
2401
+ const key = event.key;
2402
+ const forward = rtl ? "ArrowLeft" : "ArrowRight";
2403
+ const backward = rtl ? "ArrowRight" : "ArrowLeft";
2404
+ if (row === 0 && onCell && info?.column != null) {
2405
+ const column = info.column;
2406
+ if (event.altKey && key === "ArrowDown" || key === "ContextMenu" || event.shiftKey && key === "F10") {
2407
+ event.preventDefault();
2408
+ setPopover({ columnId: column.id, kind: "menu" });
2409
+ return;
2410
+ }
2411
+ if (event.ctrlKey && event.shiftKey && (key === "ArrowLeft" || key === "ArrowRight")) {
2412
+ event.preventDefault();
2413
+ moveBy(column.id, key === backward ? -1 : 1);
2414
+ return;
2415
+ }
2416
+ if (event.shiftKey && !event.ctrlKey && (key === "ArrowLeft" || key === "ArrowRight") && isResizable(column)) {
2417
+ event.preventDefault();
2418
+ resizeTo(column.id, info.width + (key === forward ? 16 : -16), true);
2419
+ return;
2420
+ }
2421
+ }
2422
+ if (onCell && (key === "Enter" || key === " " || key === "F2")) {
2423
+ const primary = cell.querySelector("[data-grid-primary]");
2424
+ if (row === 0) {
2425
+ if ((key === "Enter" || key === " ") && primary !== null) {
2426
+ event.preventDefault();
2427
+ activate(primary, event);
2428
+ }
2429
+ return;
2430
+ }
2431
+ const item = getItem(row - 1);
2432
+ if (item === void 0) return;
2433
+ if (item.kind === "group" && key !== "F2") {
2434
+ event.preventDefault();
2435
+ toggleGroupRow(item.id);
2436
+ return;
2437
+ }
2438
+ if (item.kind === "data") {
2439
+ const column = info?.column;
2440
+ if ((key === "Enter" || key === "F2") && column != null && isEditable(column, item.row) && resolveEditor(column) !== "boolean") {
2441
+ event.preventDefault();
2442
+ startEdit(item.id, column.id, item.row);
2443
+ return;
2444
+ }
2445
+ if (key === " ") {
2446
+ event.preventDefault();
2447
+ if (primary?.hasAttribute("data-grid-space") === true) activate(primary, event);
2448
+ else if (selectionMode !== "none" && isRowSelectable?.(item.row) !== false) toggleSelect(item.id, row - 1, event.shiftKey);
2449
+ return;
2450
+ }
2451
+ if (key === "Enter" && primary !== null) {
2452
+ event.preventDefault();
2453
+ activate(primary, event);
2454
+ }
2455
+ }
2456
+ return;
2457
+ }
2458
+ if (!onCell && (key === "Enter" || key === " ")) return;
2459
+ if ((event.ctrlKey || event.metaKey) && !event.shiftKey && !event.altKey) {
2460
+ if ((key === "a" || key === "A") && selectionMode === "multiple" && row > 0) {
2461
+ event.preventDefault();
2462
+ if (selectAllState !== "all") toggleSelectAll();
2463
+ return;
2464
+ }
2465
+ if ((key === "c" || key === "C") && row > 0) {
2466
+ event.preventDefault();
2467
+ void copySelection();
2468
+ return;
2469
+ }
2470
+ }
2471
+ if (row > 0 && info?.first === true) {
2472
+ const item = getItem(row - 1);
2473
+ if (item?.kind === "group") {
2474
+ if (key === forward && !item.expanded) {
2475
+ event.preventDefault();
2476
+ toggleGroupRow(item.id);
2477
+ return;
2478
+ }
2479
+ if (key === backward && item.expanded) {
2480
+ event.preventDefault();
2481
+ toggleGroupRow(item.id);
2482
+ return;
2483
+ }
2484
+ }
2485
+ }
2486
+ if (!isNavigationKey(event)) return;
2487
+ if (event.shiftKey && (key === "ArrowLeft" || key === "ArrowRight")) return;
2488
+ event.preventDefault();
2489
+ const bounds = { rowCount: rowCount + 1, columnCount: Math.max(gridColumns.length, 1), pageSize: pageRows, rtl };
2490
+ const from = { row, col };
2491
+ let next = navigate(from, event, bounds);
2492
+ const step = Math.sign(next.row - from.row);
2493
+ while (step !== 0 && next.row > 0 && getItem(next.row - 1)?.kind === "detail") {
2494
+ const moved = { row: next.row + step, col: next.col };
2495
+ if (moved.row < 1 || moved.row > rowCount) {
2496
+ next = from;
2497
+ break;
2498
+ }
2499
+ next = moved;
2500
+ }
2501
+ if (next.row === from.row && next.col === from.col) return;
2502
+ if (event.shiftKey && selectionMode === "multiple" && (key === "ArrowUp" || key === "ArrowDown") && row > 0 && next.row > 0) {
2503
+ const origin = getItem(row - 1);
2504
+ const destination = getItem(next.row - 1);
2505
+ if (origin?.kind === "data" && destination?.kind === "data") {
2506
+ const anchor = selectionAnchor.current ?? row - 1;
2507
+ const ids = idsBetween(anchor, next.row - 1);
2508
+ if (selectionAnchor.current === null) selectionAnchor.current = row - 1;
2509
+ commitSelection(selectRange(clearSelection(), ids, 0, ids.length - 1, true));
2510
+ }
2511
+ }
2512
+ moveTo(next);
2513
+ };
2514
+ const columnChoices = (column) => {
2515
+ if (column.options !== void 0) return column.options.map((option) => ({ value: option.value, label: option.label }));
2516
+ if (serverMode) return [];
2517
+ const format = plainFormatter(column, formatContext);
2518
+ return distinctValues(rowsArray, column).map((value) => ({ value: String(value), label: format(value, void 0) }));
2519
+ };
2520
+ const getMenuItems = useEvent((columnId) => menuItemsFor(columnId));
2521
+ const renderFilter = useEvent((columnId, close) => {
2522
+ const column = byId.get(columnId);
2523
+ if (column === void 0) return null;
2524
+ return /* @__PURE__ */ jsx12(
2525
+ FilterEditor,
2526
+ {
2527
+ column,
2528
+ item: filter.find((entry) => entry.field === columnId),
2529
+ choices: columnChoices(column),
2530
+ messages,
2531
+ onApply: (item) => {
2532
+ applyFilter(columnId, item);
2533
+ close();
2534
+ }
2535
+ }
2536
+ );
2537
+ });
2538
+ const headerActions = useMemo2(
2539
+ () => ({
2540
+ sort: sortColumn,
2541
+ setPopover: (columnId, kind) => setPopover(kind === null ? null : { columnId, kind }),
2542
+ closePopover: (columnId, kind) => setPopover((current) => current !== null && current.columnId === columnId && current.kind === kind ? null : current),
2543
+ resize: resizeTo,
2544
+ getMenuItems,
2545
+ renderFilter,
2546
+ dragStart: dragStartColumn,
2547
+ dragOver: dragOverColumn,
2548
+ dragLeave: dragLeaveColumn,
2549
+ drop: dropColumn,
2550
+ dragEnd: dragEndColumn
2551
+ }),
2552
+ [sortColumn, resizeTo, getMenuItems, renderFilter, dragStartColumn, dragOverColumn, dragLeaveColumn, dropColumn, dragEndColumn]
2553
+ );
2554
+ function menuItemsFor(columnId) {
2555
+ const column = byId.get(columnId);
2556
+ if (column === void 0) return [];
2557
+ const items = [];
2558
+ const direction = sort.find((item) => item.field === columnId)?.direction ?? null;
2559
+ if (isSortable(column)) {
2560
+ items.push({ id: "sort-asc", label: messages.sortAscending, checked: direction === "asc", onSelect: () => setColumnSort(columnId, "asc") });
2561
+ items.push({ id: "sort-desc", label: messages.sortDescending, checked: direction === "desc", onSelect: () => setColumnSort(columnId, "desc") });
2562
+ if (direction !== null) items.push({ id: "sort-clear", label: messages.clearSort, onSelect: () => setColumnSort(columnId, null) });
2563
+ }
2564
+ if (isFilterable(column)) {
2565
+ items.push({
2566
+ id: "filter",
2567
+ label: formatMessage(messages.filterColumn, { column: column.header }, locale),
2568
+ separatorBefore: items.length > 0,
2569
+ onSelect: () => setPopover({ columnId, kind: "filter" })
2570
+ });
2571
+ }
2572
+ if (!serverMode && column.groupable !== false && column.cellType !== "actions") {
2573
+ const grouped = group.some((item) => item.field === columnId);
2574
+ items.push({
2575
+ id: "group",
2576
+ label: formatMessage(grouped ? messages.ungroup : messages.groupBy, { column: column.header }, locale),
2577
+ separatorBefore: items.length > 0,
2578
+ onSelect: () => changeGroup(columnId)
2579
+ });
2580
+ }
2581
+ const zone = layout.start.includes(columnId) ? "start" : layout.end.includes(columnId) ? "end" : "center";
2582
+ const ids = zone === "start" ? layout.start : zone === "end" ? layout.end : layout.center;
2583
+ const position = ids.indexOf(columnId);
2584
+ items.push({ id: "pin-start", label: messages.pinStart, checked: zone === "start", separatorBefore: items.length > 0, onSelect: () => pinColumn(columnId, "start") });
2585
+ items.push({ id: "pin-end", label: messages.pinEnd, checked: zone === "end", onSelect: () => pinColumn(columnId, "end") });
2586
+ if (zone !== "center") items.push({ id: "unpin", label: messages.unpin, onSelect: () => pinColumn(columnId, false) });
2587
+ items.push({ id: "move-earlier", label: messages.moveEarlier, disabled: position <= 0, separatorBefore: true, onSelect: () => moveBy(columnId, -1) });
2588
+ items.push({ id: "move-later", label: messages.moveLater, disabled: position < 0 || position >= ids.length - 1, onSelect: () => moveBy(columnId, 1) });
2589
+ if (column.hideable !== false) items.push({ id: "hide", label: messages.hideColumn, separatorBefore: true, onSelect: () => hideColumn(columnId) });
2590
+ return items;
2591
+ }
2592
+ const chips = [];
2593
+ for (const item of effectiveFilters) {
2594
+ const column = byId.get(item.field);
2595
+ if (column === void 0) continue;
2596
+ const summary = summarizeFilter(item, column, formatContext, messages);
2597
+ chips.push({
2598
+ key: `filter:${item.field}:${item.operator}:${JSON.stringify(item.value)}`,
2599
+ text: summary,
2600
+ removeLabel: formatMessage(messages.removeFilter, { summary }, locale),
2601
+ kind: "filter",
2602
+ onRemove: () => setFilter(filter.filter((entry) => entry !== item))
2603
+ });
2604
+ }
2605
+ for (const item of effectiveGroup) {
2606
+ const column = byId.get(item.field);
2607
+ if (column === void 0) continue;
2608
+ chips.push({
2609
+ key: `group:${item.field}`,
2610
+ text: groupChipText(messages, column.header, locale),
2611
+ removeLabel: formatMessage(messages.removeGrouping, { column: column.header }, locale),
2612
+ kind: "group",
2613
+ onRemove: () => setGroup(group.filter((entry) => entry.field !== item.field))
2614
+ });
2615
+ }
2616
+ const env = useMemo2(
2617
+ () => ({
2618
+ messages,
2619
+ locale,
2620
+ formatters,
2621
+ selectionMode,
2622
+ isRowSelectable,
2623
+ renderDetail,
2624
+ isRowExpandable,
2625
+ formatGroupValue,
2626
+ formatAggregate: formatColumnAggregate,
2627
+ toggleSelect,
2628
+ toggleExpand,
2629
+ toggleGroup: toggleGroupRow,
2630
+ startEdit,
2631
+ commitEdit,
2632
+ cancelEdit,
2633
+ toggleBoolean,
2634
+ onRowClick
2635
+ }),
2636
+ [messages, locale, formatters, selectionMode, isRowSelectable, renderDetail, isRowExpandable, formatGroupValue, formatColumnAggregate, toggleSelect, toggleExpand, toggleGroupRow, startEdit, commitEdit, cancelEdit, toggleBoolean, onRowClick]
2637
+ );
2638
+ const error = serverMode ? snapshot.error : props.error;
2639
+ const hasError = error !== void 0 && error !== null && error !== false;
2640
+ const retry = () => {
2641
+ if (serverMode) store?.retry();
2642
+ props.onRetry?.();
2643
+ };
2644
+ const filteredView = effectiveFilters.length > 0 || (serverMode ? serverQuick : quickText) !== "";
2645
+ const knowsTotal = serverMode ? snapshot.total !== null : true;
2646
+ const showEmpty = !hasError && !loadingFlag && knowsTotal && rowCount === 0;
2647
+ const showLoadingOverlay = !hasError && loadingFlag && rowCount === 0;
2648
+ const showProgress = loadingFlag && rowCount > 0;
2649
+ const toolbar = resolveToolbar(props.toolbar);
2650
+ const toolbarColumns = columns.filter((column) => column.hideable !== false).map((column) => ({ id: column.id, header: column.header, visible: columnVisibility[column.id] !== false }));
2651
+ const clearFilters = () => {
2652
+ setFilter([]);
2653
+ setQuickFilter("");
2654
+ };
2655
+ const groupRuns = useMemo2(() => {
2656
+ if (!hasColumnGroups) return [];
2657
+ const runs = [];
2658
+ gridColumns.forEach((info, index) => {
2659
+ const label = info.column?.group !== void 0 && info.column.group !== "" ? info.column.group : void 0;
2660
+ const last = runs[runs.length - 1];
2661
+ if (last !== void 0 && last.label === label && label !== void 0 && last.pin === info.pin) {
2662
+ last.to = index;
2663
+ last.width += info.width;
2664
+ if (info.pin === "end") last.offset = info.pinOffset;
2665
+ return;
2666
+ }
2667
+ runs.push({ key: `${index}`, label, from: index, to: index, width: info.width, pin: info.pin, offset: info.pinOffset });
2668
+ });
2669
+ return runs;
2670
+ }, [hasColumnGroups, gridColumns]);
2671
+ const sortDirectionOf = (columnId) => sort.find((item) => item.field === columnId)?.direction ?? null;
2672
+ const filteredColumns = new Set(effectiveFilters.map((item) => item.field));
2673
+ const reorderableColumns = gridColumns.filter((info) => info.kind === "data").length > 1;
2674
+ const bodyStyle = { blockSize: rowVirtualizer.getTotalSize(), inlineSize: totalWidth };
2675
+ const rowStyle = { inlineSize: totalWidth };
2676
+ const frameStyle = height === void 0 ? void 0 : { blockSize: height };
2677
+ const scrollerStyle = height === void 0 ? { maxBlockSize: maxHeight ?? DEFAULT_MAX_HEIGHT } : {};
2678
+ const renderHeaderCells = () => slots.map((slot, position) => {
2679
+ if (slot.kind === "spacer") {
2680
+ return /* @__PURE__ */ jsx12("div", { role: "presentation", "data-kit": "data-grid-spacer", style: { inlineSize: slot.size } }, `spacer-${position}`);
2681
+ }
2682
+ const info = gridColumns[slot.index];
2683
+ const cursor = navClamped.row === 0 && navClamped.col === slot.index;
2684
+ if (info.kind !== "data" || info.column === null) {
2685
+ const label = info.kind === "select" ? messages.selectAll : messages.detailRegion;
2686
+ return /* @__PURE__ */ jsx12(
2687
+ "div",
2688
+ {
2689
+ role: "columnheader",
2690
+ "aria-label": label,
2691
+ "aria-colindex": slot.index + 1,
2692
+ tabIndex: cursor ? 0 : -1,
2693
+ "data-kit": "data-grid-header-cell",
2694
+ "data-type": info.kind,
2695
+ "data-nav-row": 0,
2696
+ "data-nav-col": slot.index,
2697
+ "data-pinned": "start",
2698
+ "data-pinned-edge": info.pinEdge ? "" : void 0,
2699
+ style: { inlineSize: info.width, insetInlineStart: info.pinOffset },
2700
+ children: info.kind === "select" && selectionMode === "multiple" ? /* @__PURE__ */ jsx12(
2701
+ Checkbox,
2702
+ {
2703
+ label: messages.selectAll,
2704
+ checked: selectAllState === "all" ? true : selectAllState === "some" ? "mixed" : false,
2705
+ tabIndex: -1,
2706
+ "data-grid-primary": "",
2707
+ "data-grid-space": "",
2708
+ onChange: toggleSelectAll
2709
+ }
2710
+ ) : null
2711
+ },
2712
+ info.id
2713
+ );
2714
+ }
2715
+ const column = info.column;
2716
+ const direction = sortDirectionOf(column.id);
2717
+ return /* @__PURE__ */ jsx12(
2718
+ HeaderCell,
2719
+ {
2720
+ column,
2721
+ navCol: slot.index,
2722
+ width: info.width,
2723
+ minWidth: info.minWidth,
2724
+ maxWidth: info.maxWidth,
2725
+ pin: info.pin,
2726
+ pinOffset: info.pinOffset,
2727
+ pinEdge: info.pinEdge,
2728
+ sortDirection: direction,
2729
+ sortRank: sort.length > 1 && direction !== null ? sort.findIndex((item) => item.field === column.id) + 1 : null,
2730
+ sortable: isSortable(column),
2731
+ filterable: isFilterable(column),
2732
+ filtered: filteredColumns.has(column.id),
2733
+ resizable: isResizable(column),
2734
+ cursor,
2735
+ rtl,
2736
+ popover: popover !== null && popover.columnId === column.id ? popover.kind : null,
2737
+ align: info.align,
2738
+ messages,
2739
+ locale,
2740
+ actions: headerActions,
2741
+ reorderable: reorderableColumns,
2742
+ dragging: columnDrag?.id === column.id,
2743
+ dropPlacement: columnDrag?.over?.id === column.id ? columnDrag.over.placement : null
2744
+ },
2745
+ info.id
2746
+ );
2747
+ });
2748
+ const renderFooterCells = () => gridColumns.map((info, index) => {
2749
+ const column = info.column;
2750
+ const value = column === null ? void 0 : totals[column.id];
2751
+ const text = column === null || value === void 0 ? "" : formatAggregate(column, value, formatContext);
2752
+ let content = text;
2753
+ if (column !== null) {
2754
+ if (column.footerCell !== void 0) content = column.footerCell({ column, value, text, messages });
2755
+ else if (info.first && text === "") content = messages.totals;
2756
+ }
2757
+ return /* @__PURE__ */ jsx12(
2758
+ "div",
2759
+ {
2760
+ role: "gridcell",
2761
+ "aria-colindex": index + 1,
2762
+ "data-kit": "data-grid-cell",
2763
+ "data-type": info.kind === "data" ? "total" : info.kind,
2764
+ "data-align": info.align,
2765
+ "data-pinned": info.pin ?? void 0,
2766
+ "data-pinned-edge": info.pinEdge ? "" : void 0,
2767
+ style: {
2768
+ inlineSize: info.width,
2769
+ ...info.pin === "start" ? { insetInlineStart: info.pinOffset } : {},
2770
+ ...info.pin === "end" ? { insetInlineEnd: info.pinOffset } : {}
2771
+ },
2772
+ children: content === "" ? null : /* @__PURE__ */ jsx12("span", { "data-kit": "data-grid-text", children: content })
2773
+ },
2774
+ info.id
2775
+ );
2776
+ });
2777
+ return /* @__PURE__ */ jsxs10(
2778
+ "div",
2779
+ {
2780
+ ref: rootRef,
2781
+ "data-kit": "data-grid",
2782
+ "data-density": density,
2783
+ "data-loading": loadingFlag ? "" : void 0,
2784
+ "data-stale": serverMode && snapshot.stale ? "" : void 0,
2785
+ "data-server": serverMode ? "" : void 0,
2786
+ dir,
2787
+ className: props.className,
2788
+ style: frameStyle,
2789
+ children: [
2790
+ toolbar === null ? null : /* @__PURE__ */ jsx12(
2791
+ Toolbar,
2792
+ {
2793
+ messages,
2794
+ locale,
2795
+ search: toolbar.search,
2796
+ columnsMenu: toolbar.columns,
2797
+ exportButton: toolbar.export,
2798
+ copyButton: toolbar.copy,
2799
+ quickFilter,
2800
+ onQuickFilterChange: setQuickFilter,
2801
+ columns: toolbarColumns,
2802
+ onToggleColumn: (id, visible) => setColumnVisibility({ ...columnVisibility, [id]: visible }),
2803
+ onShowAllColumns: () => setColumnVisibility({}),
2804
+ selectedCount,
2805
+ totalCount,
2806
+ onClearSelection: () => commitSelection(clearSelection()),
2807
+ onCopy: () => void copySelection(),
2808
+ onExport: () => void runExport(),
2809
+ exporting,
2810
+ start: props.toolbarStart,
2811
+ end: props.toolbarEnd
2812
+ }
2813
+ ),
2814
+ /* @__PURE__ */ jsx12(FilterBar, { chips, messages, locale, onClearAll: () => setFilter([]) }),
2815
+ /* @__PURE__ */ jsxs10("div", { "data-kit": "data-grid-viewport", children: [
2816
+ /* @__PURE__ */ jsxs10(
2817
+ "div",
2818
+ {
2819
+ ref: scrollerRef,
2820
+ role: treegrid ? "treegrid" : "grid",
2821
+ "aria-label": props["aria-label"],
2822
+ "aria-labelledby": props["aria-labelledby"],
2823
+ "aria-rowcount": ariaRowCount,
2824
+ "aria-colcount": gridColumns.length,
2825
+ "aria-multiselectable": selectionMode === "multiple" ? true : void 0,
2826
+ "aria-busy": loadingFlag ? true : void 0,
2827
+ "data-kit": "data-grid-scroller",
2828
+ "data-virtual-rows": virtualizeRows ? "" : void 0,
2829
+ "data-virtual-columns": virtualizeColumns ? "" : void 0,
2830
+ style: scrollerStyle,
2831
+ onKeyDown,
2832
+ onFocus,
2833
+ children: [
2834
+ /* @__PURE__ */ jsxs10("div", { role: "rowgroup", "data-kit": "data-grid-header-group", children: [
2835
+ hasColumnGroups ? /* @__PURE__ */ jsx12("div", { role: "row", "aria-rowindex": 1, "data-kit": "data-grid-group-header", style: rowStyle, children: groupRuns.map(
2836
+ (run) => run.label === void 0 ? /* @__PURE__ */ jsx12("div", { role: "presentation", "data-kit": "data-grid-group-header-cell", "data-blank": "", style: { inlineSize: run.width } }, run.key) : /* @__PURE__ */ jsx12(
2837
+ "div",
2838
+ {
2839
+ role: "columnheader",
2840
+ "aria-colindex": run.from + 1,
2841
+ "aria-colspan": run.to - run.from + 1,
2842
+ "data-kit": "data-grid-group-header-cell",
2843
+ "data-pinned": run.pin ?? void 0,
2844
+ style: {
2845
+ inlineSize: run.width,
2846
+ ...run.pin === "start" ? { insetInlineStart: run.offset } : {},
2847
+ ...run.pin === "end" ? { insetInlineEnd: run.offset } : {}
2848
+ },
2849
+ children: run.label
2850
+ },
2851
+ run.key
2852
+ )
2853
+ ) }) : null,
2854
+ /* @__PURE__ */ jsx12("div", { ref: headerRef, role: "row", "aria-rowindex": headerRowCount, "data-kit": "data-grid-header", style: rowStyle, children: renderHeaderCells() })
2855
+ ] }),
2856
+ /* @__PURE__ */ jsx12("div", { role: "rowgroup", "data-kit": "data-grid-body", style: bodyStyle, children: virtualRows.map((virtualRow) => {
2857
+ const item = getItem(virtualRow.index);
2858
+ if (item === void 0) return null;
2859
+ const isData = item.kind === "data";
2860
+ const isEditingRow = isData && editing !== null && editing.rowId === item.id;
2861
+ return /* @__PURE__ */ jsx12(
2862
+ GridRowItem,
2863
+ {
2864
+ item,
2865
+ ariaRowIndex: ariaRowOffset + virtualRow.index + 1,
2866
+ displayIndex: virtualRow.index,
2867
+ top: virtualRow.start - headerHeight,
2868
+ height: item.kind === "detail" ? void 0 : rowHeight,
2869
+ columns: gridColumns,
2870
+ slots,
2871
+ totalWidth,
2872
+ selected: isData && isSelected(selection, item.id),
2873
+ cursorCol: navClamped.row === virtualRow.index + 1 ? navClamped.col : -1,
2874
+ editingColumnId: isEditingRow ? editing.columnId : null,
2875
+ editingError: isEditingRow ? editing.error : null,
2876
+ overrides: isData ? overrides[item.id] : void 0,
2877
+ expanded: isData && expanded[item.id] === true,
2878
+ treegrid,
2879
+ viewportWidth: viewport.width,
2880
+ env,
2881
+ measureRef: item.kind === "detail" ? rowVirtualizer.measureElement : void 0
2882
+ },
2883
+ virtualRow.key
2884
+ );
2885
+ }) }),
2886
+ hasTotals ? /* @__PURE__ */ jsx12("div", { role: "rowgroup", "data-kit": "data-grid-footer-group", children: /* @__PURE__ */ jsx12("div", { role: "row", "aria-rowindex": ariaRowCount < 0 ? ariaRowOffset + rowCount + 1 : ariaRowCount, "data-kit": "data-grid-footer", style: rowStyle, children: renderFooterCells() }) }) : null
2887
+ ]
2888
+ }
2889
+ ),
2890
+ showProgress ? /* @__PURE__ */ jsx12("div", { "data-kit": "data-grid-progressbar", "aria-hidden": "true" }) : null,
2891
+ showLoadingOverlay ? /* @__PURE__ */ jsx12("div", { "data-kit": "data-grid-overlay", "data-state": "loading", style: { insetBlockStart: headerHeight }, children: props.slots?.loading ?? /* @__PURE__ */ jsxs10("div", { role: "status", "data-kit": "data-grid-loading", children: [
2892
+ /* @__PURE__ */ jsx12(Spinner, { size: "md" }),
2893
+ /* @__PURE__ */ jsx12("span", { children: messages.loading })
2894
+ ] }) }) : null,
2895
+ showEmpty ? /* @__PURE__ */ jsx12("div", { "data-kit": "data-grid-overlay", "data-state": "empty", style: { insetBlockStart: headerHeight }, children: props.slots?.empty ?? /* @__PURE__ */ jsxs10("div", { "data-kit": "data-grid-empty", children: [
2896
+ /* @__PURE__ */ jsx12("span", { children: filteredView ? messages.emptyFiltered : messages.empty }),
2897
+ filteredView ? /* @__PURE__ */ jsx12(Button4, { variant: "outline", tone: "neutral", size: "sm", onClick: clearFilters, children: messages.clearFilters }) : null
2898
+ ] }) }) : null,
2899
+ hasError ? /* @__PURE__ */ jsx12("div", { "data-kit": "data-grid-overlay", "data-state": "error", "data-placement": rowCount > 0 ? "banner" : "overlay", style: rowCount > 0 ? void 0 : { insetBlockStart: headerHeight }, children: props.slots?.error !== void 0 ? props.slots.error(error, retry) : /* @__PURE__ */ jsxs10("div", { role: "alert", "data-kit": "data-grid-error", children: [
2900
+ /* @__PURE__ */ jsx12(Icon8, { name: "alert-triangle", size: 20 }),
2901
+ /* @__PURE__ */ jsx12("span", { children: messages.error }),
2902
+ serverMode || props.onRetry !== void 0 ? /* @__PURE__ */ jsx12(Button4, { variant: "outline", tone: "neutral", size: "sm", onClick: retry, children: messages.retry }) : null
2903
+ ] }) }) : null
2904
+ ] }),
2905
+ paginated ? /* @__PURE__ */ jsx12(
2906
+ Pagination,
2907
+ {
2908
+ page,
2909
+ pageSize,
2910
+ total: totalCount,
2911
+ pageSizeOptions,
2912
+ messages: props.messages,
2913
+ ...locale === void 0 ? {} : { locale },
2914
+ onPageChange: (next) => setPagination({ ...pagination, page: next }),
2915
+ onPageSizeChange: (size) => setPagination({ page: 0, pageSize: size })
2916
+ }
2917
+ ) : null,
2918
+ /* @__PURE__ */ jsxs10(VisuallyHidden4, { role: "status", "aria-live": "polite", "aria-atomic": "true", "data-kit": "data-grid-live", children: [
2919
+ announcement.text,
2920
+ announcement.flip ? "\xA0" : ""
2921
+ ] })
2922
+ ]
2923
+ }
2924
+ );
2925
+ }
2926
+
2927
+ // src/react/use-grid-state.ts
2928
+ import { useCallback as useCallback4, useMemo as useMemo3, useState as useState9 } from "react";
2929
+ function useGridState({ columns, initial, initialPageSize = 25 }) {
2930
+ const [start] = useState9(() => ({ ...createViewState(columns), ...initial }));
2931
+ const [sort, setSort2] = useState9(start.sort);
2932
+ const [filter, setFilter] = useState9(start.filter);
2933
+ const [group, setGroup] = useState9(start.group);
2934
+ const [columnOrder, setColumnOrder] = useState9(start.columnOrder);
2935
+ const [columnVisibility, setColumnVisibility] = useState9(start.columnVisibility);
2936
+ const [columnSizing, setColumnSizing] = useState9(start.columnSizing);
2937
+ const [columnPinning, setColumnPinning] = useState9(start.columnPinning);
2938
+ const [quickFilter, setQuickFilter] = useState9("");
2939
+ const [selection, setSelection] = useState9(EMPTY_SELECTION);
2940
+ const [pagination, setPagination] = useState9({ page: 0, pageSize: initialPageSize });
2941
+ const view = useMemo3(
2942
+ () => ({ sort, filter, group, columnOrder, columnVisibility, columnSizing, columnPinning }),
2943
+ [sort, filter, group, columnOrder, columnVisibility, columnSizing, columnPinning]
2944
+ );
2945
+ const setView = useCallback4((next) => {
2946
+ setSort2(next.sort);
2947
+ setFilter(next.filter);
2948
+ setGroup(next.group);
2949
+ setColumnOrder(next.columnOrder);
2950
+ setColumnVisibility(next.columnVisibility);
2951
+ setColumnSizing(next.columnSizing);
2952
+ setColumnPinning(next.columnPinning);
2953
+ setPagination((previous) => ({ ...previous, page: 0 }));
2954
+ }, []);
2955
+ const reset = useCallback4(() => {
2956
+ setView(createViewState(columns));
2957
+ setQuickFilter("");
2958
+ setSelection(EMPTY_SELECTION);
2959
+ }, [columns, setView]);
2960
+ const props = useMemo3(
2961
+ () => ({
2962
+ sort,
2963
+ onSortChange: setSort2,
2964
+ filter,
2965
+ onFilterChange: setFilter,
2966
+ quickFilter,
2967
+ onQuickFilterChange: setQuickFilter,
2968
+ group,
2969
+ onGroupChange: setGroup,
2970
+ columnOrder,
2971
+ onColumnOrderChange: setColumnOrder,
2972
+ columnVisibility,
2973
+ onColumnVisibilityChange: setColumnVisibility,
2974
+ columnSizing,
2975
+ onColumnSizingChange: setColumnSizing,
2976
+ columnPinning,
2977
+ onColumnPinningChange: setColumnPinning,
2978
+ selection,
2979
+ onSelectionChange: setSelection,
2980
+ pagination,
2981
+ onPaginationChange: setPagination
2982
+ }),
2983
+ [sort, filter, quickFilter, group, columnOrder, columnVisibility, columnSizing, columnPinning, selection, pagination]
2984
+ );
2985
+ return { props, view, setView, reset };
2986
+ }
2987
+
2988
+ // src/react/use-saved-views.ts
2989
+ import { useCallback as useCallback5, useEffect as useEffect4, useRef as useRef8, useState as useState10 } from "react";
2990
+ function useSavedViews({ store, columns, grid }) {
2991
+ const [views, setViews] = useState10([]);
2992
+ const [loading, setLoading] = useState10(true);
2993
+ const [error, setError] = useState10(null);
2994
+ const [activeId, setActiveId] = useState10(null);
2995
+ const latest = useRef8({ columns, grid });
2996
+ latest.current = { columns, grid };
2997
+ const alive = useRef8(true);
2998
+ useEffect4(() => {
2999
+ alive.current = true;
3000
+ return () => {
3001
+ alive.current = false;
3002
+ };
3003
+ }, []);
3004
+ const refresh = useCallback5(async () => {
3005
+ setLoading(true);
3006
+ try {
3007
+ const list = await store.list();
3008
+ if (!alive.current) return;
3009
+ setViews(list);
3010
+ setError(null);
3011
+ } catch (caught) {
3012
+ if (alive.current) setError(caught);
3013
+ } finally {
3014
+ if (alive.current) setLoading(false);
3015
+ }
3016
+ }, [store]);
3017
+ useEffect4(() => {
3018
+ void refresh();
3019
+ }, [refresh]);
3020
+ const capture = useCallback5(
3021
+ (name) => captureView(latest.current.grid.view, latest.current.columns, name === void 0 ? {} : { name }),
3022
+ []
3023
+ );
3024
+ const save = useCallback5(
3025
+ async (id, name) => {
3026
+ try {
3027
+ await store.save(id, capture(name));
3028
+ if (alive.current) {
3029
+ setActiveId(id);
3030
+ setError(null);
3031
+ }
3032
+ await refresh();
3033
+ } catch (caught) {
3034
+ if (alive.current) setError(caught);
3035
+ }
3036
+ },
3037
+ [store, capture, refresh]
3038
+ );
3039
+ const apply = useCallback5(
3040
+ async (id) => {
3041
+ try {
3042
+ const view = await store.load(id);
3043
+ if (view === null || !alive.current) return false;
3044
+ latest.current.grid.setView(applySavedView(view, latest.current.columns));
3045
+ setActiveId(id);
3046
+ setError(null);
3047
+ return true;
3048
+ } catch (caught) {
3049
+ if (alive.current) setError(caught);
3050
+ return false;
3051
+ }
3052
+ },
3053
+ [store]
3054
+ );
3055
+ const remove = useCallback5(
3056
+ async (id) => {
3057
+ try {
3058
+ await store.delete(id);
3059
+ if (alive.current) setActiveId((current) => current === id ? null : current);
3060
+ await refresh();
3061
+ } catch (caught) {
3062
+ if (alive.current) setError(caught);
3063
+ }
3064
+ },
3065
+ [store, refresh]
3066
+ );
3067
+ return { views, loading, error, activeId, refresh, save, apply, remove, capture };
3068
+ }
3069
+ export {
3070
+ DataGrid,
3071
+ Pagination,
3072
+ defaultMessages,
3073
+ defineColumns,
3074
+ resolveMessages,
3075
+ useGridState,
3076
+ useSavedViews
3077
+ };
3078
+ //# sourceMappingURL=react.js.map