@zuilib/data-grid 0.3.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.
@@ -0,0 +1,1573 @@
1
+ "use client";
2
+
3
+ // src/data-grid.tsx
4
+ import { useMemo as useMemo3 } from "react";
5
+ import { cn as cn8 } from "@zuilib/components/lib/cn";
6
+
7
+ // src/column-manager.tsx
8
+ import { Fragment } from "react";
9
+ import Button2 from "@zuilib/components/button";
10
+ import Checkbox2 from "@zuilib/components/checkbox";
11
+ import Popover from "@zuilib/components/popover";
12
+ import { cn as cn2 } from "@zuilib/components/lib/cn";
13
+
14
+ // src/column-extras.ts
15
+ function columnExtras(column) {
16
+ const def = column.columnDef;
17
+ const meta = column.columnDef.meta ?? {};
18
+ return {
19
+ enableInlineEdit: def.enableInlineEdit ?? meta.enableInlineEdit,
20
+ align: def.align ?? meta.align ?? "start",
21
+ width: def.width ?? meta.width,
22
+ filter: def.filter ?? meta.filter
23
+ };
24
+ }
25
+ function columnLabel(column) {
26
+ const meta = column.columnDef.meta ?? {};
27
+ if (meta.label) return meta.label;
28
+ const header = column.columnDef.header;
29
+ return typeof header === "string" ? header : column.id;
30
+ }
31
+
32
+ // src/context.ts
33
+ import { createContext, useContext } from "react";
34
+ var DataGridContext = createContext(null);
35
+ function useDataGridContext(part) {
36
+ const value = useContext(DataGridContext);
37
+ if (!value) throw new Error(`<DataGrid.${part}> must be rendered inside <DataGrid>`);
38
+ return value;
39
+ }
40
+
41
+ // src/expand-column.tsx
42
+ import Button from "@zuilib/components/button";
43
+ import { cn } from "@zuilib/components/lib/cn";
44
+ import { ChevronDownIcon } from "@zuilib/components/lib/icons";
45
+ import { jsx } from "react/jsx-runtime";
46
+ var EXPAND_COLUMN_ID = "__expand";
47
+ function ExpandToggle({ row }) {
48
+ const { labels } = useDataGridContext("Body");
49
+ if (!row.getCanExpand()) return null;
50
+ const expanded = row.getIsExpanded();
51
+ return /* @__PURE__ */ jsx(
52
+ Button,
53
+ {
54
+ type: "button",
55
+ variant: "ghost",
56
+ size: "icon",
57
+ "data-slot": "data-grid-expand-toggle",
58
+ "data-expanded": expanded ? "" : void 0,
59
+ tabIndex: -1,
60
+ "aria-expanded": expanded,
61
+ "aria-label": expanded ? labels.collapseRow : labels.expandRow,
62
+ className: "size-6 rounded-sm text-muted-foreground",
63
+ onClick: (event) => {
64
+ event.stopPropagation();
65
+ row.toggleExpanded();
66
+ },
67
+ onKeyDown: (event) => {
68
+ if (event.key !== "Escape") event.stopPropagation();
69
+ },
70
+ children: /* @__PURE__ */ jsx(
71
+ ChevronDownIcon,
72
+ {
73
+ "aria-hidden": "true",
74
+ className: cn("size-3.5 transition-transform duration-(--duration-fast) motion-reduce:transition-none", !expanded && "-rotate-90")
75
+ }
76
+ )
77
+ }
78
+ );
79
+ }
80
+ function ExpandHead() {
81
+ const { labels } = useDataGridContext("Body");
82
+ return /* @__PURE__ */ jsx("span", { className: "sr-only", children: labels.expandColumn });
83
+ }
84
+ function createExpandColumn() {
85
+ return {
86
+ id: EXPAND_COLUMN_ID,
87
+ size: 40,
88
+ enableSorting: false,
89
+ enableHiding: false,
90
+ enableResizing: false,
91
+ enableColumnFilter: false,
92
+ header: () => /* @__PURE__ */ jsx(ExpandHead, {}),
93
+ cell: ({ row }) => /* @__PURE__ */ jsx(ExpandToggle, { row })
94
+ };
95
+ }
96
+
97
+ // src/select-column.tsx
98
+ import Checkbox from "@zuilib/components/checkbox";
99
+ import { jsx as jsx2 } from "react/jsx-runtime";
100
+ var SELECT_COLUMN_ID = "__select";
101
+ function SelectAll({ table }) {
102
+ const { labels } = useDataGridContext("Body");
103
+ return /* @__PURE__ */ jsx2(
104
+ Checkbox,
105
+ {
106
+ "data-slot": "data-grid-select-all",
107
+ size: "sm",
108
+ "aria-label": labels.selectAllOnPage,
109
+ checked: table.getIsAllPageRowsSelected(),
110
+ indeterminate: table.getIsSomePageRowsSelected() && !table.getIsAllPageRowsSelected(),
111
+ onCheckedChange: (checked) => table.toggleAllPageRowsSelected(checked)
112
+ }
113
+ );
114
+ }
115
+ function SelectRow({ row }) {
116
+ const { labels } = useDataGridContext("Body");
117
+ return /* @__PURE__ */ jsx2(
118
+ Checkbox,
119
+ {
120
+ "data-slot": "data-grid-select-row",
121
+ size: "sm",
122
+ tabIndex: -1,
123
+ "aria-label": labels.selectRow,
124
+ checked: row.getIsSelected(),
125
+ disabled: !row.getCanSelect(),
126
+ onCheckedChange: (checked) => row.toggleSelected(checked)
127
+ }
128
+ );
129
+ }
130
+ function createSelectColumn() {
131
+ return {
132
+ id: SELECT_COLUMN_ID,
133
+ size: 40,
134
+ enableSorting: false,
135
+ enableHiding: false,
136
+ enableResizing: false,
137
+ enableColumnFilter: false,
138
+ header: ({ table }) => /* @__PURE__ */ jsx2(SelectAll, { table }),
139
+ cell: ({ row }) => /* @__PURE__ */ jsx2(SelectRow, { row })
140
+ };
141
+ }
142
+
143
+ // src/labels.ts
144
+ var defaultDataGridLabels = {
145
+ pinStart: "Pin to start",
146
+ pinEnd: "Pin to end",
147
+ unpin: "Unpin",
148
+ expandRow: "Expand row",
149
+ collapseRow: "Collapse row",
150
+ expandColumn: "Expand",
151
+ selectedCount: "{count} selected",
152
+ allMatchingSelected: "All {count} selected",
153
+ selectAllMatching: "Select all {count}",
154
+ clearSelection: "Clear selection",
155
+ selectionActions: "Bulk actions",
156
+ selectAllOnPage: "Select all rows on this page",
157
+ selectRow: "Select row",
158
+ pagination: "Pagination",
159
+ rowsPerPage: "Rows per page",
160
+ loading: "Loading\u2026",
161
+ range: "{first}\u2013{last} of {total}",
162
+ firstPage: "First page",
163
+ previousPage: "Previous page",
164
+ nextPage: "Next page",
165
+ lastPage: "Last page",
166
+ search: "Search",
167
+ searchPlaceholder: "Search\u2026",
168
+ activeFilters: "Active filters",
169
+ removeFilter: "Remove {column} filter",
170
+ columns: "Columns",
171
+ moveUp: "Move {column} up",
172
+ moveDown: "Move {column} down",
173
+ addColumnSection: "Add column",
174
+ addColumn: "Add {column}",
175
+ filterColumn: "Filter {column}",
176
+ filterColumnActive: "Filter {column} (active)",
177
+ filterValue: "Filter value",
178
+ filterPlaceholder: "Contains\u2026",
179
+ filterOptions: "Options",
180
+ from: "From",
181
+ to: "To",
182
+ clear: "Clear",
183
+ apply: "Apply",
184
+ rangeFrom: "from {date}",
185
+ rangeUntil: "until {date}",
186
+ resizeColumn: "Resize {column}",
187
+ editColumn: "Edit {column}",
188
+ noResults: "No results",
189
+ noResultsDescription: "Try a different search or clear the filters."
190
+ };
191
+ function fillLabel(template, values) {
192
+ return template.replace(/\{(\w+)\}/g, (match, name) => name in values ? String(values[name]) : match);
193
+ }
194
+
195
+ // src/column-manager.tsx
196
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
197
+ var FIXED_COLUMN_IDS = [SELECT_COLUMN_ID, EXPAND_COLUMN_ID];
198
+ function ColumnsIcon({ className }) {
199
+ return /* @__PURE__ */ jsx3("svg", { "aria-hidden": "true", focusable: "false", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20", fill: "currentColor", className: cn2("size-[1em] shrink-0", className), children: /* @__PURE__ */ jsx3("path", { d: "M14 17h2.75A2.25 2.25 0 0 0 19 14.75v-9.5A2.25 2.25 0 0 0 16.75 3H14v14ZM12.5 3h-5v14h5V3ZM3.25 3H6v14H3.25A2.25 2.25 0 0 1 1 14.75v-9.5A2.25 2.25 0 0 1 3.25 3Z" }) });
200
+ }
201
+ function DataGridColumnManagerRow({ column, index, count, onMove }) {
202
+ const { labels } = useDataGridContext("ColumnManager");
203
+ const label = columnLabel(column);
204
+ const pinLabels = { left: labels.pinStart, right: labels.pinEnd };
205
+ const pinned = column.getIsPinned();
206
+ const setPin = (position) => column.pin(pinned === position ? false : position);
207
+ return /* @__PURE__ */ jsxs("li", { "data-slot": "data-grid-column-manager-row", "data-column": column.id, className: "flex items-center gap-2 py-1", children: [
208
+ /* @__PURE__ */ jsx3(
209
+ Checkbox2,
210
+ {
211
+ size: "sm",
212
+ label,
213
+ checked: column.getIsVisible(),
214
+ disabled: !column.getCanHide(),
215
+ onCheckedChange: (checked) => column.toggleVisibility(checked)
216
+ }
217
+ ),
218
+ /* @__PURE__ */ jsxs("span", { className: "ms-auto inline-flex items-center gap-0.5", children: [
219
+ /* @__PURE__ */ jsx3(Button2, { variant: "ghost", size: "sm", "aria-label": fillLabel(labels.moveUp, { column: label }), disabled: index === 0, onClick: () => onMove(column, -1), className: "size-7 p-0", children: /* @__PURE__ */ jsx3("span", { "aria-hidden": "true", children: "\u2191" }) }),
220
+ /* @__PURE__ */ jsx3(Button2, { variant: "ghost", size: "sm", "aria-label": fillLabel(labels.moveDown, { column: label }), disabled: index === count - 1, onClick: () => onMove(column, 1), className: "size-7 p-0", children: /* @__PURE__ */ jsx3("span", { "aria-hidden": "true", children: "\u2193" }) }),
221
+ ["left", "right"].map((side) => /* @__PURE__ */ jsx3(
222
+ Button2,
223
+ {
224
+ variant: "ghost",
225
+ size: "sm",
226
+ "aria-label": pinned === side ? `${labels.unpin} ${label}` : `${pinLabels[side]}: ${label}`,
227
+ "aria-pressed": pinned === side,
228
+ onClick: () => setPin(side),
229
+ className: cn2("size-7 p-0 text-xs", pinned === side && "text-primary"),
230
+ children: /* @__PURE__ */ jsx3("span", { "aria-hidden": "true", children: side === "left" ? "\u21E4" : "\u21E5" })
231
+ },
232
+ side
233
+ ))
234
+ ] })
235
+ ] });
236
+ }
237
+ function DataGridColumnManager({ label: labelProp, className, addable, onAddColumn }) {
238
+ const { table, labels } = useDataGridContext("ColumnManager");
239
+ const label = labelProp ?? labels.columns;
240
+ const columns = table.getAllLeafColumns().filter((column) => !FIXED_COLUMN_IDS.includes(column.id));
241
+ const move = (column, direction) => {
242
+ const order = columns.map((c) => c.id);
243
+ const from = order.indexOf(column.id);
244
+ const to = from + direction;
245
+ if (to < 0 || to >= order.length) return;
246
+ order.splice(from, 1);
247
+ order.splice(to, 0, column.id);
248
+ const present = new Set(table.getAllLeafColumns().map((c) => c.id));
249
+ table.setColumnOrder([...FIXED_COLUMN_IDS.filter((id) => present.has(id)), ...order]);
250
+ };
251
+ return /* @__PURE__ */ jsxs(Popover, { "data-slot": "data-grid-column-manager", className: cn2("inline-flex", className), children: [
252
+ /* @__PURE__ */ jsx3(Popover.Button, { as: Fragment, children: /* @__PURE__ */ jsx3(Button2, { variant: "outline", size: "sm", leadingIcon: /* @__PURE__ */ jsx3(ColumnsIcon, {}), "data-slot": "data-grid-column-manager-button", children: label }) }),
253
+ /* @__PURE__ */ jsxs(Popover.Panel, { "data-slot": "data-grid-column-manager-panel", anchor: "bottom end", className: "min-w-64", children: [
254
+ /* @__PURE__ */ jsx3("ul", { "data-slot": "data-grid-column-manager-list", "aria-label": label, className: "m-0 flex list-none flex-col p-0", children: columns.map((column, index) => /* @__PURE__ */ jsx3(DataGridColumnManagerRow, { column, index, count: columns.length, onMove: move }, column.id)) }),
255
+ onAddColumn && addable && addable.length > 0 ? /* @__PURE__ */ jsxs("div", { "data-slot": "data-grid-column-manager-add", className: "mt-2 border-t border-border pt-2", children: [
256
+ /* @__PURE__ */ jsx3("p", { className: "m-0 pb-1 text-xs font-medium text-muted-foreground", children: labels.addColumnSection }),
257
+ /* @__PURE__ */ jsx3("ul", { "aria-label": labels.addColumnSection, className: "m-0 flex list-none flex-col p-0", children: addable.map((field) => /* @__PURE__ */ jsx3("li", { "data-slot": "data-grid-column-manager-add-row", "data-column": field.id, className: "flex items-center gap-2 py-0.5", children: /* @__PURE__ */ jsxs(
258
+ Button2,
259
+ {
260
+ variant: "ghost",
261
+ size: "sm",
262
+ "aria-label": fillLabel(labels.addColumn, { column: field.label }),
263
+ onClick: () => onAddColumn(field.id),
264
+ className: "w-full justify-start gap-2 px-1 font-normal",
265
+ children: [
266
+ /* @__PURE__ */ jsx3("span", { "aria-hidden": "true", className: "text-muted-foreground", children: "\uFF0B" }),
267
+ field.label
268
+ ]
269
+ }
270
+ ) }, field.id)) })
271
+ ] }) : null
272
+ ] })
273
+ ] });
274
+ }
275
+
276
+ // src/data-grid-body.tsx
277
+ import { flexRender as flexRender2 } from "@tanstack/react-table";
278
+ import { Fragment as Fragment2, useEffect, useRef as useRef2, useState as useState3 } from "react";
279
+ import Alert from "@zuilib/components/alert";
280
+ import EmptyState from "@zuilib/components/empty-state";
281
+ import Skeleton from "@zuilib/components/skeleton";
282
+ import Table from "@zuilib/components/table";
283
+ import { cn as cn4 } from "@zuilib/components/lib/cn";
284
+ import { focusOutlineInsetClasses, focusRingInsetClasses } from "@zuilib/components/lib/focus";
285
+ import { nativeControlResetClasses } from "@zuilib/components/lib/reset";
286
+
287
+ // src/dev.ts
288
+ function isDev() {
289
+ try {
290
+ return process.env.NODE_ENV !== "production";
291
+ } catch {
292
+ return false;
293
+ }
294
+ }
295
+ var warned = /* @__PURE__ */ new Set();
296
+ function warnOnce(key, message) {
297
+ if (!isDev() || warned.has(key)) return;
298
+ warned.add(key);
299
+ console.warn(message);
300
+ }
301
+
302
+ // src/editable-cell.tsx
303
+ import { flexRender } from "@tanstack/react-table";
304
+ import { useRef, useState } from "react";
305
+ import Input from "@zuilib/components/input";
306
+ import { jsx as jsx4 } from "react/jsx-runtime";
307
+ function EditableCell({ cell, editing, onEditEnd, commitOnBlur, onCellCommit, labels }) {
308
+ const value = cell.getValue();
309
+ const initial = value === void 0 || value === null ? "" : String(value);
310
+ const [draft, setDraft] = useState(initial);
311
+ const settled = useRef(false);
312
+ const wasEditing = useRef(false);
313
+ if (editing && !wasEditing.current) {
314
+ settled.current = false;
315
+ if (draft !== initial) setDraft(initial);
316
+ }
317
+ wasEditing.current = editing;
318
+ const finish = (commit, fromKeyboard) => {
319
+ if (settled.current) return;
320
+ settled.current = true;
321
+ if (commit) onCellCommit?.({ rowId: cell.row.id, columnId: cell.column.id, value: draft });
322
+ onEditEnd(fromKeyboard);
323
+ };
324
+ const handleInputKeyDown = (event) => {
325
+ if (event.key === "Enter") {
326
+ event.preventDefault();
327
+ event.stopPropagation();
328
+ finish(true, true);
329
+ } else if (event.key === "Escape") {
330
+ event.preventDefault();
331
+ event.stopPropagation();
332
+ finish(false, true);
333
+ } else if (event.key.startsWith("Arrow") || event.key === "Home" || event.key === "End" || event.key === " ") {
334
+ event.stopPropagation();
335
+ }
336
+ };
337
+ if (editing) {
338
+ return /* @__PURE__ */ jsx4(
339
+ Input,
340
+ {
341
+ "data-slot": "data-grid-cell-editor",
342
+ size: "sm",
343
+ autoFocus: true,
344
+ fullWidth: true,
345
+ "aria-label": fillLabel(labels.editColumn, { column: columnLabel(cell.column) }),
346
+ value: draft,
347
+ onChange: (event) => setDraft(event.target.value),
348
+ onKeyDown: handleInputKeyDown,
349
+ onBlur: () => finish(commitOnBlur, false),
350
+ onClick: (event) => event.stopPropagation(),
351
+ onDoubleClick: (event) => event.stopPropagation()
352
+ }
353
+ );
354
+ }
355
+ return /* @__PURE__ */ jsx4("div", { "data-slot": "data-grid-cell-content", "data-editable": "", className: "-mx-1 cursor-text rounded-sm px-1", children: flexRender(cell.column.columnDef.cell, cell.getContext()) });
356
+ }
357
+
358
+ // src/filter-popover.tsx
359
+ import { useState as useState2 } from "react";
360
+ import Button3 from "@zuilib/components/button";
361
+ import Checkbox3 from "@zuilib/components/checkbox";
362
+ import Input2 from "@zuilib/components/input";
363
+ import Popover2 from "@zuilib/components/popover";
364
+ import { cn as cn3 } from "@zuilib/components/lib/cn";
365
+ import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
366
+ function FilterIcon({ className }) {
367
+ return /* @__PURE__ */ jsx5(
368
+ "svg",
369
+ {
370
+ "aria-hidden": "true",
371
+ focusable: "false",
372
+ xmlns: "http://www.w3.org/2000/svg",
373
+ viewBox: "0 0 20 20",
374
+ fill: "currentColor",
375
+ className: cn3("size-[1em] shrink-0", className),
376
+ children: /* @__PURE__ */ jsx5(
377
+ "path",
378
+ {
379
+ fillRule: "evenodd",
380
+ d: "M2.628 1.601C5.028 1.206 7.49 1 10 1s4.973.206 7.372.601a.75.75 0 0 1 .628.74v2.288a2.25 2.25 0 0 1-.659 1.59l-4.682 4.683a2.25 2.25 0 0 0-.659 1.59v3.037c0 .684-.31 1.33-.844 1.757l-1.937 1.55A.75.75 0 0 1 8 18.25v-5.757a2.25 2.25 0 0 0-.659-1.591L2.659 6.22A2.25 2.25 0 0 1 2 4.629V2.34a.75.75 0 0 1 .628-.74Z",
381
+ clipRule: "evenodd"
382
+ }
383
+ )
384
+ }
385
+ );
386
+ }
387
+ function isFilterActive(value) {
388
+ if (value === void 0 || value === null || value === "") return false;
389
+ if (Array.isArray(value)) return value.length > 0;
390
+ if (typeof value === "object") return Object.values(value).some((v) => v !== void 0 && v !== "");
391
+ return true;
392
+ }
393
+ function formatFilterValue(filter, value, labels = defaultDataGridLabels) {
394
+ if (Array.isArray(value)) {
395
+ if (filter?.control === "select") {
396
+ return value.map((v) => filter.options.find((o) => o.value === v)?.label ?? String(v)).join(", ");
397
+ }
398
+ return value.map(String).join(", ");
399
+ }
400
+ if (value && typeof value === "object") {
401
+ const { from, to } = value;
402
+ if (from && to) return `${from} \u2013 ${to}`;
403
+ if (from) return fillLabel(labels.rangeFrom, { date: from });
404
+ if (to) return fillLabel(labels.rangeUntil, { date: to });
405
+ return "";
406
+ }
407
+ return String(value ?? "");
408
+ }
409
+ function TextFilterForm({ filter, value, labels, onApply, onClear }) {
410
+ const [draft, setDraft] = useState2(typeof value === "string" ? value : "");
411
+ return /* @__PURE__ */ jsxs2(
412
+ "form",
413
+ {
414
+ "data-slot": "data-grid-filter-form",
415
+ className: "flex flex-col gap-3",
416
+ onSubmit: (event) => {
417
+ event.preventDefault();
418
+ onApply(draft);
419
+ },
420
+ children: [
421
+ /* @__PURE__ */ jsx5(
422
+ Input2,
423
+ {
424
+ size: "sm",
425
+ autoFocus: true,
426
+ "aria-label": labels.filterValue,
427
+ placeholder: filter.placeholder ?? labels.filterPlaceholder,
428
+ value: draft,
429
+ onChange: (event) => setDraft(event.target.value)
430
+ }
431
+ ),
432
+ /* @__PURE__ */ jsx5(FilterActions, { labels, onClear })
433
+ ]
434
+ }
435
+ );
436
+ }
437
+ function SelectFilterForm({ filter, value, labels, onApply, onClear }) {
438
+ const [draft, setDraft] = useState2(Array.isArray(value) ? value : []);
439
+ const toggle = (option, checked) => setDraft((current) => checked ? [...current, option] : current.filter((v) => v !== option));
440
+ return /* @__PURE__ */ jsxs2(
441
+ "form",
442
+ {
443
+ "data-slot": "data-grid-filter-form",
444
+ className: "flex flex-col gap-3",
445
+ onSubmit: (event) => {
446
+ event.preventDefault();
447
+ onApply(draft);
448
+ },
449
+ children: [
450
+ /* @__PURE__ */ jsx5("div", { role: "group", "aria-label": labels.filterOptions, className: "flex max-h-60 flex-col gap-2 overflow-auto", children: filter.options.map((option) => /* @__PURE__ */ jsx5(
451
+ Checkbox3,
452
+ {
453
+ size: "sm",
454
+ label: option.label ?? option.value,
455
+ checked: draft.includes(option.value),
456
+ onCheckedChange: (checked) => toggle(option.value, checked)
457
+ },
458
+ option.value
459
+ )) }),
460
+ /* @__PURE__ */ jsx5(FilterActions, { labels, onClear })
461
+ ]
462
+ }
463
+ );
464
+ }
465
+ function DateRangeFilterForm({ value, labels, onApply, onClear }) {
466
+ const initial = value && typeof value === "object" ? value : {};
467
+ const [from, setFrom] = useState2(initial.from ?? "");
468
+ const [to, setTo] = useState2(initial.to ?? "");
469
+ return /* @__PURE__ */ jsxs2(
470
+ "form",
471
+ {
472
+ "data-slot": "data-grid-filter-form",
473
+ className: "flex flex-col gap-3",
474
+ onSubmit: (event) => {
475
+ event.preventDefault();
476
+ onApply({ from: from || void 0, to: to || void 0 });
477
+ },
478
+ children: [
479
+ /* @__PURE__ */ jsxs2("label", { className: "flex flex-col gap-1 text-sm text-muted-foreground", children: [
480
+ labels.from,
481
+ /* @__PURE__ */ jsx5(Input2, { size: "sm", type: "date", value: from, max: to || void 0, onChange: (event) => setFrom(event.target.value) })
482
+ ] }),
483
+ /* @__PURE__ */ jsxs2("label", { className: "flex flex-col gap-1 text-sm text-muted-foreground", children: [
484
+ labels.to,
485
+ /* @__PURE__ */ jsx5(Input2, { size: "sm", type: "date", value: to, min: from || void 0, onChange: (event) => setTo(event.target.value) })
486
+ ] }),
487
+ /* @__PURE__ */ jsx5(FilterActions, { labels, onClear })
488
+ ]
489
+ }
490
+ );
491
+ }
492
+ function FilterActions({ labels, onClear }) {
493
+ return /* @__PURE__ */ jsxs2("div", { "data-slot": "data-grid-filter-actions", className: "flex items-center justify-end gap-2", children: [
494
+ /* @__PURE__ */ jsx5(Button3, { variant: "ghost", size: "sm", onClick: onClear, children: labels.clear }),
495
+ /* @__PURE__ */ jsx5(Button3, { variant: "solid", size: "sm", type: "submit", children: labels.apply })
496
+ ] });
497
+ }
498
+ function FilterPopover({ column, filter }) {
499
+ const { labels } = useDataGridContext("Body");
500
+ const value = column.getFilterValue();
501
+ const active = isFilterActive(value);
502
+ const label = columnLabel(column);
503
+ const form = (close) => {
504
+ const apply = (next) => {
505
+ column.setFilterValue(isFilterActive(next) ? next : void 0);
506
+ close();
507
+ };
508
+ const clear = () => {
509
+ column.setFilterValue(void 0);
510
+ close();
511
+ };
512
+ return filter.control === "text" ? /* @__PURE__ */ jsx5(TextFilterForm, { filter, value, labels, onApply: apply, onClear: clear }) : filter.control === "select" ? /* @__PURE__ */ jsx5(SelectFilterForm, { filter, value, labels, onApply: apply, onClear: clear }) : /* @__PURE__ */ jsx5(DateRangeFilterForm, { filter, value, labels, onApply: apply, onClear: clear });
513
+ };
514
+ return /* @__PURE__ */ jsxs2(Popover2, { "data-slot": "data-grid-filter", className: "inline-flex shrink-0", children: [
515
+ /* @__PURE__ */ jsx5(
516
+ Popover2.Button,
517
+ {
518
+ "data-slot": "data-grid-filter-button",
519
+ "data-active": active ? "" : void 0,
520
+ "aria-label": fillLabel(active ? labels.filterColumnActive : labels.filterColumn, { column: label }),
521
+ className: cn3(
522
+ "inline-flex size-6 items-center justify-center rounded-sm text-muted-foreground",
523
+ "transition-colors duration-(--duration-fast) hover:bg-accent hover:text-accent-foreground",
524
+ "data-[active]:text-primary data-[open]:bg-accent"
525
+ ),
526
+ children: /* @__PURE__ */ jsx5(FilterIcon, { className: "size-3.5" })
527
+ }
528
+ ),
529
+ /* @__PURE__ */ jsx5(Popover2.Panel, { "data-slot": "data-grid-filter-panel", anchor: "bottom start", className: "min-w-56", children: ({ close }) => form(close) })
530
+ ] });
531
+ }
532
+
533
+ // src/data-grid-body.tsx
534
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
535
+ var densityToSize = { compact: "sm", comfortable: "md" };
536
+ function pinnedSide(column) {
537
+ const pinned = column.getIsPinned();
538
+ return pinned === "left" ? "start" : pinned === "right" ? "end" : void 0;
539
+ }
540
+ function pinnedStyle(column) {
541
+ const pinned = column.getIsPinned();
542
+ if (!pinned) return void 0;
543
+ return pinned === "left" ? { insetInlineStart: column.getStart("left") } : { insetInlineEnd: column.getAfter("right") };
544
+ }
545
+ function pinnedClasses(column, section) {
546
+ const pinned = column.getIsPinned();
547
+ if (!pinned) return false;
548
+ const lastLeft = pinned === "left" && column.getIsLastColumn("left");
549
+ const firstRight = pinned === "right" && column.getIsFirstColumn("right");
550
+ return cn4(
551
+ "sticky bg-background",
552
+ section === "head" ? "z-20" : "z-[1] group-hover/row:bg-muted group-data-[selected]/row:bg-accent",
553
+ lastLeft && "shadow-[inset_-1px_0_0_0_var(--color-border)] rtl:shadow-[inset_1px_0_0_0_var(--color-border)]",
554
+ firstRight && "shadow-[inset_1px_0_0_0_var(--color-border)] rtl:shadow-[inset_-1px_0_0_0_var(--color-border)]"
555
+ );
556
+ }
557
+ var HEADER_ROW_ID = null;
558
+ var ROW_SELECTOR = '[data-slot="data-grid-header-row"][data-leaf], [data-slot="data-grid-row"]';
559
+ var CELL_SELECTOR = ':scope > [data-slot="data-grid-header-cell"], :scope > [data-slot="data-grid-cell"]';
560
+ var CELL_CONTROL_SELECTOR = '[role="checkbox"], [data-slot="data-grid-expand-toggle"]';
561
+ function navigableRows(from) {
562
+ const table = from.closest("table");
563
+ return Array.from(table?.querySelectorAll(ROW_SELECTOR) ?? []);
564
+ }
565
+ var cellsOf = (row) => Array.from(row.querySelectorAll(CELL_SELECTOR));
566
+ var clamp = (value, max) => Math.min(max, Math.max(0, value));
567
+ function handleGridKeyDown(event, { row, expandable, activate }) {
568
+ const target = event.currentTarget;
569
+ if (event.target !== target) {
570
+ if (event.key === "Escape" && target.tagName !== "TR") {
571
+ event.preventDefault();
572
+ target.focus();
573
+ }
574
+ return;
575
+ }
576
+ const rowEl = target.closest("tr");
577
+ if (!rowEl) return;
578
+ const rows = navigableRows(rowEl);
579
+ const rowIndex = rows.indexOf(rowEl);
580
+ const inRowMode = target === rowEl;
581
+ const cells = cellsOf(rowEl);
582
+ const col = inRowMode ? null : cells.indexOf(target);
583
+ const isHeader = rowEl.dataset.slot === "data-grid-header-row";
584
+ const firstRow = inRowMode ? 1 : 0;
585
+ if (event.shiftKey && (event.key === "ArrowRight" || event.key === "ArrowLeft")) {
586
+ if (!expandable || !row?.getCanExpand()) return;
587
+ const next = event.key === "ArrowRight";
588
+ event.preventDefault();
589
+ if (next !== row.getIsExpanded()) row.toggleExpanded(next);
590
+ return;
591
+ }
592
+ const focusAt = (nextRow, nextCol) => {
593
+ event.preventDefault();
594
+ const targetRow = rows[clamp(nextRow, rows.length - 1)];
595
+ if (!targetRow) return;
596
+ if (nextCol === null) return targetRow.focus();
597
+ const targetCells = cellsOf(targetRow);
598
+ targetCells[clamp(nextCol, targetCells.length - 1)]?.focus();
599
+ };
600
+ switch (event.key) {
601
+ case "ArrowRight":
602
+ return focusAt(rowIndex, col === null ? 0 : col + 1);
603
+ case "ArrowLeft":
604
+ if (col === null) return;
605
+ return focusAt(rowIndex, col === 0 && !isHeader ? null : Math.max(0, col - 1));
606
+ case "ArrowDown":
607
+ return focusAt(rowIndex + 1, col);
608
+ case "ArrowUp":
609
+ return focusAt(Math.max(firstRow, rowIndex - 1), col);
610
+ case "Home":
611
+ if (event.ctrlKey || col === null) return focusAt(firstRow, col);
612
+ return focusAt(rowIndex, 0);
613
+ case "End":
614
+ if (event.ctrlKey || col === null) return focusAt(rows.length - 1, col);
615
+ return focusAt(rowIndex, cells.length - 1);
616
+ case "Enter":
617
+ case " ": {
618
+ if (col === null) return;
619
+ const control = target.querySelector(CELL_CONTROL_SELECTOR);
620
+ event.preventDefault();
621
+ if (control) return control.click();
622
+ return activate?.();
623
+ }
624
+ }
625
+ }
626
+ var sortDirection = (header) => {
627
+ const sorted = header.column.getIsSorted();
628
+ return sorted === false ? "none" : sorted === "asc" ? "ascending" : "descending";
629
+ };
630
+ function SortIcon({ direction }) {
631
+ return /* @__PURE__ */ jsxs3("svg", { "data-slot": "data-grid-sort-icon", "data-sort": direction, "aria-hidden": "true", className: "size-[1em] shrink-0", viewBox: "0 0 16 16", fill: "currentColor", children: [
632
+ /* @__PURE__ */ jsx6("path", { className: cn4(direction === "descending" && "opacity-30", direction === "none" && "opacity-40"), d: "M8 2.5 11.5 6.5h-7z" }),
633
+ /* @__PURE__ */ jsx6("path", { className: cn4(direction === "ascending" && "opacity-30", direction === "none" && "opacity-40"), d: "M8 13.5 4.5 9.5h7z" })
634
+ ] });
635
+ }
636
+ var headAlignClasses = { start: "justify-start", center: "justify-center", end: "justify-end" };
637
+ function HeaderCell({ header, stickyHeader, active, onFocus }) {
638
+ const { table, loading, labels } = useDataGridContext("Body");
639
+ const column = header.column;
640
+ const extras = columnExtras(column);
641
+ const sortable = column.getCanSort();
642
+ const direction = sortDirection(header);
643
+ const filter = column.getCanFilter() ? extras.filter : void 0;
644
+ const unfilterable = Boolean(extras.filter) && !column.getCanFilter() && !column.accessorFn;
645
+ useEffect(() => {
646
+ if (unfilterable) {
647
+ warnOnce(`display-column-filter:${column.id}`, `[DataGrid] Column "${column.id}" declares a filter but has no accessorKey / accessorFn, so it cannot be filtered and shows no funnel.`);
648
+ }
649
+ }, [unfilterable, column.id]);
650
+ const resizable = column.getCanResize();
651
+ const pinned = column.getIsPinned();
652
+ const label = header.isPlaceholder ? null : flexRender2(column.columnDef.header, header.getContext());
653
+ const colIndex = (header.getLeafHeaders()[0]?.column ?? column).getIndex();
654
+ const minSize = column.columnDef.minSize ?? 20;
655
+ const maxSize = column.columnDef.maxSize ?? 1e3;
656
+ const leaf = header.subHeaders.length === 0;
657
+ return /* @__PURE__ */ jsxs3(
658
+ Table.Head,
659
+ {
660
+ "data-slot": "data-grid-header-cell",
661
+ "data-column": column.id,
662
+ "data-pinned": pinnedSide(column),
663
+ "data-sort": sortable ? direction : void 0,
664
+ "aria-sort": sortable ? direction : void 0,
665
+ "aria-colindex": colIndex + 1,
666
+ align: extras.align,
667
+ colSpan: header.colSpan,
668
+ style: { width: header.getSize(), minWidth: header.getSize(), ...pinnedStyle(column) },
669
+ className: cn4("group/head select-none p-0", !stickyHeader && !pinned && "relative", pinnedClasses(column, "head"), focusOutlineInsetClasses),
670
+ "aria-busy": loading || void 0,
671
+ tabIndex: leaf ? active ? 0 : -1 : void 0,
672
+ onFocus: (event) => {
673
+ if (event.target === event.currentTarget) onFocus(colIndex);
674
+ },
675
+ onKeyDown: (event) => handleGridKeyDown(event, {
676
+ expandable: false,
677
+ activate: sortable ? () => column.toggleSorting(void 0, column.getCanMultiSort()) : void 0
678
+ }),
679
+ children: [
680
+ /* @__PURE__ */ jsxs3("div", { "data-slot": "data-grid-header-content", className: cn4("flex items-center gap-1", extras.align === "end" && "flex-row-reverse"), children: [
681
+ sortable ? /* @__PURE__ */ jsxs3(
682
+ "button",
683
+ {
684
+ type: "button",
685
+ "data-slot": "data-grid-sort-button",
686
+ "data-sort": direction,
687
+ onClick: (event) => column.toggleSorting(void 0, column.getCanMultiSort() && event.shiftKey),
688
+ className: cn4(
689
+ nativeControlResetClasses,
690
+ "flex min-w-0 flex-1 cursor-pointer items-center gap-1 px-(--table-cell-px) py-(--table-cell-py)",
691
+ "transition-colors duration-(--duration-fast) motion-reduce:transition-none",
692
+ "hover:text-foreground data-[sort=ascending]:text-foreground data-[sort=descending]:text-foreground",
693
+ focusRingInsetClasses,
694
+ headAlignClasses[extras.align ?? "start"]
695
+ ),
696
+ children: [
697
+ /* @__PURE__ */ jsx6("span", { className: "truncate", children: label }),
698
+ /* @__PURE__ */ jsx6(SortIcon, { direction })
699
+ ]
700
+ }
701
+ ) : /* @__PURE__ */ jsx6("span", { "data-slot": "data-grid-header-label", className: "min-w-0 flex-1 truncate px-(--table-cell-px) py-(--table-cell-py)", children: label }),
702
+ filter ? /* @__PURE__ */ jsx6("span", { "data-slot": "data-grid-header-tools", className: "shrink-0 px-1", children: /* @__PURE__ */ jsx6(FilterPopover, { column, filter }) }) : null
703
+ ] }),
704
+ resizable ? /* @__PURE__ */ jsx6(
705
+ "div",
706
+ {
707
+ "data-slot": "data-grid-resize-handle",
708
+ role: "separator",
709
+ "aria-orientation": "vertical",
710
+ "aria-label": fillLabel(labels.resizeColumn, { column: columnLabel(column) }),
711
+ "aria-valuenow": Math.round(header.getSize()),
712
+ "aria-valuemin": minSize,
713
+ "aria-valuemax": maxSize,
714
+ tabIndex: active ? 0 : -1,
715
+ onMouseDown: header.getResizeHandler(),
716
+ onTouchStart: header.getResizeHandler(),
717
+ onKeyDown: (event) => {
718
+ const step = event.shiftKey ? 50 : 10;
719
+ if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
720
+ event.preventDefault();
721
+ event.stopPropagation();
722
+ const delta = event.key === "ArrowLeft" ? -step : step;
723
+ const next = Math.min(maxSize, Math.max(minSize, header.getSize() + delta));
724
+ table.setColumnSizing((sizing) => ({ ...sizing, [column.id]: next }));
725
+ } else if (event.key === "Escape") {
726
+ event.preventDefault();
727
+ event.currentTarget.closest("th")?.focus();
728
+ }
729
+ },
730
+ onDoubleClick: () => column.resetSize(),
731
+ className: cn4(
732
+ "absolute top-0 end-0 h-full w-1.5 cursor-col-resize touch-none select-none",
733
+ "opacity-0 transition-opacity duration-(--duration-fast) group-hover/head:opacity-100 focus-visible:opacity-100",
734
+ "bg-border hover:bg-primary focus-visible:bg-primary focus:outline-none",
735
+ column.getIsResizing() && "bg-primary opacity-100"
736
+ )
737
+ }
738
+ ) : null
739
+ ]
740
+ }
741
+ );
742
+ }
743
+ function BodyCell({ cell, tabStop, expandable, onFocus, onActivateRow }) {
744
+ const { onCellCommit, commitOnBlur, labels } = useDataGridContext("Body");
745
+ const column = cell.column;
746
+ const extras = columnExtras(column);
747
+ const editable = Boolean(extras.enableInlineEdit);
748
+ const [editing, setEditing] = useState3(false);
749
+ const ref = useRef2(null);
750
+ const refocus = useRef2(false);
751
+ useEffect(() => {
752
+ if (!editing && refocus.current) {
753
+ refocus.current = false;
754
+ ref.current?.focus();
755
+ }
756
+ }, [editing]);
757
+ const startEdit = () => {
758
+ if (!editable) return;
759
+ setEditing(true);
760
+ };
761
+ const endEdit = (fromKeyboard) => {
762
+ refocus.current = fromKeyboard;
763
+ setEditing(false);
764
+ };
765
+ return /* @__PURE__ */ jsx6(
766
+ Table.Cell,
767
+ {
768
+ ref,
769
+ role: "gridcell",
770
+ "data-slot": "data-grid-cell",
771
+ "data-column": column.id,
772
+ "data-pinned": pinnedSide(column),
773
+ "data-editable": editable ? "" : void 0,
774
+ "data-editing": editing ? "" : void 0,
775
+ "aria-colindex": column.getIndex() + 1,
776
+ "aria-readonly": editable ? false : void 0,
777
+ align: extras.align,
778
+ tabIndex: tabStop ? 0 : -1,
779
+ style: { width: column.getSize(), minWidth: column.getSize(), ...pinnedStyle(column) },
780
+ className: cn4("group-data-[selected]/row:bg-accent", pinnedClasses(column, "cell"), focusOutlineInsetClasses),
781
+ onFocus: (event) => {
782
+ if (event.target === event.currentTarget) onFocus(column.getIndex());
783
+ },
784
+ onClick: editable ? (event) => event.stopPropagation() : void 0,
785
+ onDoubleClick: editable ? (event) => {
786
+ event.stopPropagation();
787
+ startEdit();
788
+ } : void 0,
789
+ onKeyDown: (event) => {
790
+ if (editing) return;
791
+ handleGridKeyDown(event, { row: cell.row, expandable, activate: editable ? startEdit : onActivateRow });
792
+ },
793
+ children: editable ? /* @__PURE__ */ jsx6(EditableCell, { cell, editing, onEditEnd: endEdit, commitOnBlur, onCellCommit, labels }) : flexRender2(column.columnDef.cell, cell.getContext())
794
+ }
795
+ );
796
+ }
797
+ function DataRow({ row, ariaRowIndex, activeCol, onFocus, onRowClick }) {
798
+ const { renderExpanded } = useDataGridContext("Table");
799
+ const expandable = Boolean(renderExpanded) && row.getCanExpand();
800
+ const expanded = expandable && row.getIsExpanded();
801
+ const ref = useRef2(null);
802
+ const cells = row.getVisibleCells();
803
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
804
+ /* @__PURE__ */ jsx6(
805
+ Table.Row,
806
+ {
807
+ ref,
808
+ "data-slot": "data-grid-row",
809
+ "data-row-id": row.id,
810
+ "data-expanded": expanded ? "" : void 0,
811
+ "aria-rowindex": ariaRowIndex,
812
+ selected: row.getIsSelected(),
813
+ interactive: true,
814
+ tabIndex: activeCol === null ? 0 : -1,
815
+ "aria-selected": row.getCanSelect() ? row.getIsSelected() : void 0,
816
+ onClick: onRowClick ? (event) => onRowClick(row, event) : void 0,
817
+ onFocus: (event) => {
818
+ if (event.target === event.currentTarget) onFocus(null);
819
+ },
820
+ onKeyDown: (event) => handleGridKeyDown(event, { row, expandable: Boolean(renderExpanded) }),
821
+ className: cn4("group/row", focusOutlineInsetClasses),
822
+ children: cells.map((cell, index) => /* @__PURE__ */ jsx6(
823
+ BodyCell,
824
+ {
825
+ cell,
826
+ tabStop: activeCol === index,
827
+ expandable: Boolean(renderExpanded),
828
+ onFocus,
829
+ onActivateRow: onRowClick ? () => ref.current?.click() : void 0
830
+ },
831
+ cell.id
832
+ ))
833
+ }
834
+ ),
835
+ expanded ? /* @__PURE__ */ jsx6(Table.Row, { "data-slot": "data-grid-expanded-row", "data-row-id": row.id, "aria-rowindex": ariaRowIndex, className: "bg-muted/40", children: /* @__PURE__ */ jsx6(Table.Cell, { "data-slot": "data-grid-expanded-cell", colSpan: cells.length, className: "whitespace-normal", children: renderExpanded?.(row) }) }) : null
836
+ ] });
837
+ }
838
+ function SkeletonRows({ count, columnCount }) {
839
+ return /* @__PURE__ */ jsx6(Fragment3, { children: Array.from({ length: count }, (_, rowIndex) => /* @__PURE__ */ jsx6(Table.Row, { "data-slot": "data-grid-skeleton-row", "aria-hidden": "true", children: Array.from({ length: columnCount }, (_2, cellIndex) => /* @__PURE__ */ jsx6(Table.Cell, { "data-slot": "data-grid-cell", children: /* @__PURE__ */ jsx6(Skeleton, { shape: "text", width: `${55 + (rowIndex * 7 + cellIndex * 13) % 40}%` }) }, cellIndex)) }, rowIndex)) });
840
+ }
841
+ function DataGridBody({
842
+ stickyHeader = false,
843
+ onRowClick,
844
+ error,
845
+ skeletonWhileRefreshing = false,
846
+ emptyState,
847
+ className,
848
+ scrollAreaClassName,
849
+ ...aria
850
+ }) {
851
+ const { table, state, loading, density, rowCount, labels } = useDataGridContext("Table");
852
+ const rows = table.getRowModel().rows;
853
+ const headerGroups = table.getHeaderGroups();
854
+ const headerRowCount = headerGroups.length;
855
+ const columnCount = table.getVisibleLeafColumns().length;
856
+ const { pageIndex, pageSize } = state.pagination;
857
+ const skeletonCount = Math.min(pageSize, 10);
858
+ const [active, setActive] = useState3({ rowId: HEADER_ROW_ID, col: null });
859
+ const inHeader = active.rowId === HEADER_ROW_ID && active.col !== null;
860
+ const onPage = active.rowId !== HEADER_ROW_ID && rows.some((row) => row.id === active.rowId);
861
+ const activeRowId = inHeader || onPage ? active.rowId : rows[0]?.id ?? HEADER_ROW_ID;
862
+ const activeCol = activeRowId === active.rowId ? active.col : null;
863
+ const showSkeleton = loading && (rows.length === 0 ? !error : skeletonWhileRefreshing);
864
+ const messageRowIndex = headerRowCount + 1;
865
+ return /* @__PURE__ */ jsxs3(
866
+ Table,
867
+ {
868
+ role: "grid",
869
+ "data-slot": "data-grid-table",
870
+ "data-loading": loading ? "" : void 0,
871
+ "data-density": density,
872
+ "aria-busy": loading || void 0,
873
+ "aria-rowcount": rowCount + headerRowCount,
874
+ "aria-colcount": columnCount,
875
+ size: densityToSize[density],
876
+ stickyHeader,
877
+ fullWidth: true,
878
+ className: cn4("table-fixed", className),
879
+ scrollAreaClassName: cn4("rounded-md border border-border", scrollAreaClassName),
880
+ style: { width: table.getTotalSize(), minWidth: "100%" },
881
+ ...aria,
882
+ children: [
883
+ /* @__PURE__ */ jsx6(Table.Header, { "data-slot": "data-grid-header", children: headerGroups.map((group, groupIndex) => /* @__PURE__ */ jsx6(
884
+ Table.Row,
885
+ {
886
+ "data-slot": "data-grid-header-row",
887
+ "data-leaf": groupIndex === headerRowCount - 1 ? "" : void 0,
888
+ "aria-rowindex": groupIndex + 1,
889
+ children: group.headers.map((header) => /* @__PURE__ */ jsx6(
890
+ HeaderCell,
891
+ {
892
+ header,
893
+ stickyHeader,
894
+ active: activeRowId === HEADER_ROW_ID && activeCol === header.column.getIndex(),
895
+ onFocus: (col) => setActive({ rowId: HEADER_ROW_ID, col })
896
+ },
897
+ header.id
898
+ ))
899
+ },
900
+ group.id
901
+ )) }),
902
+ /* @__PURE__ */ jsxs3(Table.Body, { "data-slot": "data-grid-body", children: [
903
+ error ? /* @__PURE__ */ jsx6(Table.Row, { "data-slot": "data-grid-message-row", "aria-rowindex": messageRowIndex, children: /* @__PURE__ */ jsx6(Table.Cell, { colSpan: columnCount, "data-slot": "data-grid-error", children: typeof error === "string" ? /* @__PURE__ */ jsx6(Alert, { tone: "danger", children: error }) : error }) }) : null,
904
+ showSkeleton ? /* @__PURE__ */ jsx6(SkeletonRows, { count: skeletonCount, columnCount }) : rows.length === 0 ? error ? null : /* @__PURE__ */ jsx6(Table.Row, { "data-slot": "data-grid-message-row", "aria-rowindex": messageRowIndex, children: /* @__PURE__ */ jsx6(Table.Cell, { colSpan: columnCount, "data-slot": "data-grid-empty", children: emptyState ?? /* @__PURE__ */ jsx6(EmptyState, { size: "sm", title: labels.noResults, description: labels.noResultsDescription }) }) }) : rows.map((row, index) => /* @__PURE__ */ jsx6(
905
+ DataRow,
906
+ {
907
+ row,
908
+ ariaRowIndex: pageIndex * pageSize + index + headerRowCount + 1,
909
+ activeCol: row.id === activeRowId ? activeCol : false,
910
+ onFocus: (col) => setActive({ rowId: row.id, col }),
911
+ onRowClick
912
+ },
913
+ row.id
914
+ ))
915
+ ] })
916
+ ]
917
+ }
918
+ );
919
+ }
920
+
921
+ // src/pagination.tsx
922
+ import { useMemo } from "react";
923
+ import Button4 from "@zuilib/components/button";
924
+ import NativeSelect from "@zuilib/components/native-select";
925
+ import { cn as cn5 } from "@zuilib/components/lib/cn";
926
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
927
+ function useNumberFormat(locale) {
928
+ return useMemo(() => {
929
+ try {
930
+ return new Intl.NumberFormat(locale);
931
+ } catch {
932
+ warnOnce(`invalid-locale:${locale}`, `[DataGrid] "${locale}" is not a valid BCP 47 locale tag; numbers are formatted in the user's locale instead.`);
933
+ return new Intl.NumberFormat();
934
+ }
935
+ }, [locale]);
936
+ }
937
+ function sizeOptions(options, pageSize) {
938
+ return options.includes(pageSize) ? [...options] : [...options, pageSize].sort((a, b) => a - b);
939
+ }
940
+ function DataGridPagination({ locale: localeProp, className, ...props }) {
941
+ const { table, state, rowCount, pageSizeOptions, loading, locale, labels } = useDataGridContext("Pagination");
942
+ const numberFormat = useNumberFormat(localeProp ?? locale);
943
+ const { pageIndex, pageSize } = state.pagination;
944
+ const pageCount = Math.max(1, Math.ceil(rowCount / pageSize));
945
+ const first = rowCount === 0 ? 0 : pageIndex * pageSize + 1;
946
+ const last = Math.min(rowCount, (pageIndex + 1) * pageSize);
947
+ const canPrevious = pageIndex > 0;
948
+ const canNext = pageIndex < pageCount - 1;
949
+ const options = sizeOptions(pageSizeOptions, pageSize);
950
+ return /* @__PURE__ */ jsxs4(
951
+ "nav",
952
+ {
953
+ "data-slot": "data-grid-pagination",
954
+ "aria-label": labels.pagination,
955
+ className: cn5("flex flex-wrap items-center gap-4 text-sm text-muted-foreground", className),
956
+ ...props,
957
+ children: [
958
+ /* @__PURE__ */ jsxs4("label", { "data-slot": "data-grid-page-size", className: "flex items-center gap-2", children: [
959
+ labels.rowsPerPage,
960
+ /* @__PURE__ */ jsx7(
961
+ NativeSelect,
962
+ {
963
+ size: "sm",
964
+ value: pageSize,
965
+ onChange: (event) => table.setPagination({ pageIndex: 0, pageSize: Number(event.target.value) }),
966
+ children: options.map((size) => /* @__PURE__ */ jsx7("option", { value: size, children: size }, size))
967
+ }
968
+ )
969
+ ] }),
970
+ /* @__PURE__ */ jsx7("p", { "data-slot": "data-grid-range", "aria-live": "polite", className: "m-0 ms-auto tabular-nums", children: loading ? labels.loading : fillLabel(labels.range, { first: numberFormat.format(first), last: numberFormat.format(last), total: numberFormat.format(rowCount) }) }),
971
+ /* @__PURE__ */ jsxs4("div", { "data-slot": "data-grid-page-buttons", className: "flex items-center gap-1", children: [
972
+ /* @__PURE__ */ jsx7(Button4, { variant: "ghost", size: "sm", "aria-label": labels.firstPage, disabled: !canPrevious, onClick: () => table.setPageIndex(0), children: /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", children: "\xAB" }) }),
973
+ /* @__PURE__ */ jsx7(Button4, { variant: "ghost", size: "sm", "aria-label": labels.previousPage, disabled: !canPrevious, onClick: () => table.previousPage(), children: /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", children: "\u2039" }) }),
974
+ /* @__PURE__ */ jsxs4("span", { "data-slot": "data-grid-page-indicator", className: "px-1 tabular-nums", children: [
975
+ pageIndex + 1,
976
+ " / ",
977
+ pageCount
978
+ ] }),
979
+ /* @__PURE__ */ jsx7(Button4, { variant: "ghost", size: "sm", "aria-label": labels.nextPage, disabled: !canNext, onClick: () => table.nextPage(), children: /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", children: "\u203A" }) }),
980
+ /* @__PURE__ */ jsx7(Button4, { variant: "ghost", size: "sm", "aria-label": labels.lastPage, disabled: !canNext, onClick: () => table.setPageIndex(pageCount - 1), children: /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", children: "\xBB" }) })
981
+ ] })
982
+ ]
983
+ }
984
+ );
985
+ }
986
+
987
+ // src/selection-bar.tsx
988
+ import { useEffect as useEffect2 } from "react";
989
+ import Button5 from "@zuilib/components/button";
990
+ import { cn as cn6 } from "@zuilib/components/lib/cn";
991
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
992
+ function DataGridSelectionBar({ className, ...props }) {
993
+ const { table, state, setState, selectionActions, rowCount, labels, locale, selectAllMatching } = useDataGridContext("SelectionBar");
994
+ const numberFormat = useNumberFormat(locale);
995
+ const selectedIds = Object.keys(state.rowSelection).filter((id) => state.rowSelection[id]);
996
+ const allMatching = state.allMatching === true;
997
+ const visible = selectedIds.length > 0 || allMatching;
998
+ const keyedByIndex = visible && !table.options.getRowId;
999
+ useEffect2(() => {
1000
+ if (keyedByIndex) {
1001
+ warnOnce("row-selection-without-getRowId", "[DataGrid] `rowSelection` is keyed by row index because `getRowId` is not set: the ids handed to selection actions change with every page and sort. Pass `getRowId`.");
1002
+ }
1003
+ }, [keyedByIndex]);
1004
+ if (!visible) return null;
1005
+ const pageRows = table.getRowModel().rows.length;
1006
+ const offerAll = selectAllMatching && setState !== void 0 && !allMatching && rowCount > pageRows && table.getIsAllPageRowsSelected();
1007
+ const count = numberFormat.format(allMatching ? rowCount : selectedIds.length);
1008
+ return /* @__PURE__ */ jsxs5(
1009
+ "div",
1010
+ {
1011
+ "data-slot": "data-grid-selection-bar",
1012
+ role: "region",
1013
+ "aria-label": labels.selectionActions,
1014
+ "data-all-matching": allMatching ? "" : void 0,
1015
+ className: cn6(
1016
+ "flex flex-wrap items-center gap-2 rounded-md border border-border bg-accent px-3 py-2 text-sm text-accent-foreground",
1017
+ className
1018
+ ),
1019
+ ...props,
1020
+ children: [
1021
+ /* @__PURE__ */ jsx8("span", { "data-slot": "data-grid-selection-count", "aria-live": "polite", className: "font-medium", children: fillLabel(allMatching ? labels.allMatchingSelected : labels.selectedCount, { count }) }),
1022
+ offerAll ? /* @__PURE__ */ jsx8(
1023
+ Button5,
1024
+ {
1025
+ "data-slot": "data-grid-selection-select-all",
1026
+ size: "sm",
1027
+ variant: "ghost",
1028
+ onClick: () => setState?.("allMatching", true),
1029
+ children: fillLabel(labels.selectAllMatching, { count: numberFormat.format(rowCount) })
1030
+ }
1031
+ ) : null,
1032
+ /* @__PURE__ */ jsx8("span", { className: "flex flex-wrap items-center gap-1", children: selectionActions.map((action) => /* @__PURE__ */ jsx8(
1033
+ Button5,
1034
+ {
1035
+ "data-slot": "data-grid-selection-action",
1036
+ "data-action": action.id,
1037
+ size: "sm",
1038
+ variant: action.variant ?? "outline",
1039
+ tone: action.tone,
1040
+ onClick: () => action.onSelect(selectedIds, { allMatching, rowCount }),
1041
+ children: action.label
1042
+ },
1043
+ action.id
1044
+ )) }),
1045
+ /* @__PURE__ */ jsx8(Button5, { "data-slot": "data-grid-selection-clear", size: "sm", variant: "ghost", className: "ms-auto", onClick: () => table.resetRowSelection(true), children: labels.clearSelection })
1046
+ ]
1047
+ }
1048
+ );
1049
+ }
1050
+
1051
+ // src/toolbar.tsx
1052
+ import { useEffect as useEffect3, useRef as useRef3, useState as useState4 } from "react";
1053
+ import Badge from "@zuilib/components/badge";
1054
+ import SearchInput from "@zuilib/components/search-input";
1055
+ import { cn as cn7 } from "@zuilib/components/lib/cn";
1056
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1057
+ function DataGridToolbar({
1058
+ showSearch = true,
1059
+ searchPlaceholder,
1060
+ searchDebounceMs = 300,
1061
+ showFilterChips = true,
1062
+ showColumnManager = true,
1063
+ addableColumns,
1064
+ onAddColumn,
1065
+ children,
1066
+ className,
1067
+ ...props
1068
+ }) {
1069
+ const { table, state, labels } = useDataGridContext("Toolbar");
1070
+ const [draft, setDraft] = useState4(state.search);
1071
+ const timer = useRef3(void 0);
1072
+ useEffect3(() => {
1073
+ setDraft(state.search);
1074
+ }, [state.search]);
1075
+ useEffect3(() => () => clearTimeout(timer.current), []);
1076
+ const commitSearch = (value) => {
1077
+ clearTimeout(timer.current);
1078
+ table.setGlobalFilter(value);
1079
+ };
1080
+ const changeSearch = (value) => {
1081
+ setDraft(value);
1082
+ clearTimeout(timer.current);
1083
+ if (value === "") return commitSearch(value);
1084
+ timer.current = setTimeout(() => table.setGlobalFilter(value), searchDebounceMs);
1085
+ };
1086
+ const activeFilters = state.columnFilters.filter((filter) => isFilterActive(filter.value));
1087
+ return /* @__PURE__ */ jsxs6(
1088
+ "div",
1089
+ {
1090
+ "data-slot": "data-grid-toolbar",
1091
+ className: cn7("flex flex-wrap items-center gap-2", className),
1092
+ ...props,
1093
+ children: [
1094
+ showSearch ? /* @__PURE__ */ jsx9(
1095
+ SearchInput,
1096
+ {
1097
+ "data-slot": "data-grid-search",
1098
+ size: "sm",
1099
+ "aria-label": labels.search,
1100
+ placeholder: searchPlaceholder ?? labels.searchPlaceholder,
1101
+ value: draft,
1102
+ onChange: (event) => changeSearch(event.target.value),
1103
+ onSubmit: commitSearch,
1104
+ className: "w-64 max-w-full"
1105
+ }
1106
+ ) : null,
1107
+ showFilterChips && activeFilters.length > 0 ? /* @__PURE__ */ jsx9("ul", { "data-slot": "data-grid-filter-chips", "aria-label": labels.activeFilters, className: "m-0 flex list-none flex-wrap items-center gap-1 p-0", children: activeFilters.map((filter) => {
1108
+ const column = table.getColumn(filter.id);
1109
+ const label = column ? columnLabel(column) : filter.id;
1110
+ return /* @__PURE__ */ jsx9("li", { children: /* @__PURE__ */ jsxs6(
1111
+ Badge,
1112
+ {
1113
+ "data-slot": "data-grid-filter-chip",
1114
+ "data-column": filter.id,
1115
+ variant: "subtle",
1116
+ tone: "primary",
1117
+ shape: "pill",
1118
+ removeLabel: fillLabel(labels.removeFilter, { column: label }),
1119
+ onRemove: () => column?.setFilterValue(void 0),
1120
+ children: [
1121
+ label,
1122
+ ": ",
1123
+ formatFilterValue(column ? columnExtras(column).filter : void 0, filter.value, labels)
1124
+ ]
1125
+ }
1126
+ ) }, filter.id);
1127
+ }) }) : null,
1128
+ children,
1129
+ showColumnManager ? /* @__PURE__ */ jsx9(DataGridColumnManager, { className: "ms-auto", addable: addableColumns, onAddColumn }) : null
1130
+ ]
1131
+ }
1132
+ );
1133
+ }
1134
+
1135
+ // src/use-data-grid.ts
1136
+ import { useCallback, useEffect as useEffect4, useLayoutEffect, useMemo as useMemo2, useRef as useRef4, useState as useState5 } from "react";
1137
+ import {
1138
+ getCoreRowModel,
1139
+ getFilteredRowModel,
1140
+ getPaginationRowModel,
1141
+ getSortedRowModel,
1142
+ useReactTable
1143
+ } from "@tanstack/react-table";
1144
+
1145
+ // src/state.ts
1146
+ var DEFAULT_PAGE_SIZE = 25;
1147
+ var defaultDataGridState = {
1148
+ pagination: { pageIndex: 0, pageSize: DEFAULT_PAGE_SIZE },
1149
+ sorting: [],
1150
+ columnFilters: [],
1151
+ search: "",
1152
+ columnVisibility: {},
1153
+ columnOrder: [],
1154
+ columnPinning: { start: [], end: [] },
1155
+ rowSelection: {},
1156
+ expanded: {},
1157
+ allMatching: false
1158
+ };
1159
+ function resolveDataGridState(state) {
1160
+ return { ...defaultDataGridState, ...state };
1161
+ }
1162
+
1163
+ // src/use-data-grid.ts
1164
+ function resolve(updater, previous) {
1165
+ return typeof updater === "function" ? updater(previous) : updater;
1166
+ }
1167
+ var NO_FIXED_COLUMNS = [];
1168
+ var sameList = (a, b) => a.length === b.length && a.every((id, i) => id === b[i]);
1169
+ function toTanstackPinning(pinning) {
1170
+ return { left: pinning.start, right: pinning.end };
1171
+ }
1172
+ function fromTanstackPinning(pinning) {
1173
+ return { start: pinning.left ?? [], end: pinning.right ?? [] };
1174
+ }
1175
+ function normalizeColumnState(state, fixedColumnIds) {
1176
+ if (fixedColumnIds.length === 0) return state;
1177
+ const fixed = (ids) => fixedColumnIds.filter((id) => ids.includes(id));
1178
+ const rest = (ids) => ids.filter((id) => !fixedColumnIds.includes(id));
1179
+ const order = state.columnOrder.length === 0 ? state.columnOrder : [...fixed(state.columnOrder), ...rest(state.columnOrder)];
1180
+ const start = [...fixedColumnIds, ...rest(state.columnPinning.start ?? [])];
1181
+ const end = rest(state.columnPinning.end ?? []);
1182
+ const pinningChanged = !sameList(start, state.columnPinning.start ?? []) || !sameList(end, state.columnPinning.end ?? []);
1183
+ const orderChanged = !sameList(order, state.columnOrder);
1184
+ if (!pinningChanged && !orderChanged) return state;
1185
+ return {
1186
+ ...state,
1187
+ columnOrder: orderChanged ? order : state.columnOrder,
1188
+ columnPinning: pinningChanged ? { start, end } : state.columnPinning
1189
+ };
1190
+ }
1191
+ var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect4 : useLayoutEffect;
1192
+ var inDateRange = (row, columnId, value) => {
1193
+ const raw = row.getValue(columnId);
1194
+ if (!value || !value.from && !value.to) return true;
1195
+ if (raw === void 0 || raw === null || raw === "") return false;
1196
+ const day = (raw instanceof Date ? raw.toISOString() : String(raw)).slice(0, 10);
1197
+ if (value.from && day < value.from.slice(0, 10)) return false;
1198
+ if (value.to && day > value.to.slice(0, 10)) return false;
1199
+ return true;
1200
+ };
1201
+ inDateRange.autoRemove = (value) => !value || !value.from && !value.to;
1202
+ var filterFnByControl = { text: "includesString", select: "arrIncludesSome", "date-range": inDateRange };
1203
+ function toTanstackColumns(columns) {
1204
+ return columns.map((column) => {
1205
+ const filter = column.filter ?? column.meta?.filter;
1206
+ const next = { ...column };
1207
+ if (column.width !== void 0 && column.size === void 0) next.size = column.width;
1208
+ if (filter && column.filterFn === void 0) next.filterFn = filterFnByControl[filter.control];
1209
+ return next;
1210
+ });
1211
+ }
1212
+ function useDataGrid(options) {
1213
+ const {
1214
+ columns,
1215
+ rows,
1216
+ rowCount,
1217
+ state: partialState,
1218
+ defaultState,
1219
+ onStateChange,
1220
+ fixedColumnIds,
1221
+ manualPagination = true,
1222
+ manualSorting = true,
1223
+ manualFiltering = true,
1224
+ getRowId,
1225
+ enableRowSelection = true,
1226
+ enableColumnResizing = true,
1227
+ getRowCanExpand,
1228
+ tableOptions
1229
+ } = options;
1230
+ const controlled = partialState !== void 0;
1231
+ const [internalState, setInternalState] = useState5(() => resolveDataGridState(defaultState));
1232
+ const fixedIds = fixedColumnIds ?? NO_FIXED_COLUMNS;
1233
+ const state = useMemo2(
1234
+ () => normalizeColumnState(controlled ? resolveDataGridState(partialState) : internalState, fixedIds),
1235
+ [controlled, partialState, internalState, fixedIds]
1236
+ );
1237
+ const latest = useRef4({ state, onStateChange, pending: null });
1238
+ useIsomorphicLayoutEffect(() => {
1239
+ latest.current.state = state;
1240
+ latest.current.onStateChange = onStateChange;
1241
+ latest.current.pending = null;
1242
+ });
1243
+ const emit = useCallback((next) => {
1244
+ if (!controlled) {
1245
+ latest.current.pending = next;
1246
+ setInternalState(next);
1247
+ }
1248
+ latest.current.onStateChange?.(next);
1249
+ }, [controlled]);
1250
+ const current = () => latest.current.pending ?? latest.current.state;
1251
+ const [internalSizing, setInternalSizing] = useState5({});
1252
+ const tableState = useMemo2(
1253
+ () => ({
1254
+ ...state,
1255
+ globalFilter: state.search,
1256
+ columnPinning: toTanstackPinning(state.columnPinning),
1257
+ columnSizing: state.columnSizing ?? internalSizing
1258
+ }),
1259
+ [state, internalSizing]
1260
+ );
1261
+ const setState = useCallback((key, updater) => {
1262
+ const previous = current();
1263
+ const value = resolve(updater, previous[key]);
1264
+ if (Object.is(value, previous[key])) return;
1265
+ emit({ ...previous, [key]: value });
1266
+ }, [emit]);
1267
+ const replaceState = useCallback((next) => {
1268
+ emit({ ...current(), ...next });
1269
+ }, [emit]);
1270
+ const tanstackColumns = useMemo2(() => toTanstackColumns(columns), [columns]);
1271
+ const table = useReactTable({
1272
+ ...tableOptions,
1273
+ data: rows,
1274
+ columns: tanstackColumns,
1275
+ state: tableState,
1276
+ /* Client-side paging counts the rows that pass the filters; a server total would be wrong. */
1277
+ rowCount: manualPagination ? rowCount : void 0,
1278
+ manualPagination,
1279
+ manualSorting,
1280
+ manualFiltering,
1281
+ enableRowSelection,
1282
+ enableColumnResizing,
1283
+ columnResizeMode: "onChange",
1284
+ getRowId,
1285
+ getRowCanExpand,
1286
+ getCoreRowModel: getCoreRowModel(),
1287
+ getSortedRowModel: manualSorting ? void 0 : getSortedRowModel(),
1288
+ getFilteredRowModel: manualFiltering ? void 0 : getFilteredRowModel(),
1289
+ getPaginationRowModel: manualPagination ? void 0 : getPaginationRowModel(),
1290
+ onPaginationChange: (updater) => setState("pagination", updater),
1291
+ /* A new sort or filter starts from the first page: the old page index has
1292
+ no meaning against a different ordering. */
1293
+ onSortingChange: (updater) => {
1294
+ const previous = current();
1295
+ const sorting = resolve(updater, previous.sorting);
1296
+ emit({ ...previous, sorting, pagination: { ...previous.pagination, pageIndex: 0 }, allMatching: false });
1297
+ },
1298
+ onColumnFiltersChange: (updater) => {
1299
+ const previous = current();
1300
+ const columnFilters = resolve(updater, previous.columnFilters);
1301
+ emit({ ...previous, columnFilters, pagination: { ...previous.pagination, pageIndex: 0 }, allMatching: false });
1302
+ },
1303
+ onGlobalFilterChange: (updater) => {
1304
+ const previous = current();
1305
+ const search = resolve(updater, previous.search) ?? "";
1306
+ if (search === previous.search) return;
1307
+ emit({ ...previous, search, pagination: { ...previous.pagination, pageIndex: 0 }, allMatching: false });
1308
+ },
1309
+ onColumnVisibilityChange: (updater) => setState("columnVisibility", updater),
1310
+ onColumnOrderChange: (updater) => setState("columnOrder", updater),
1311
+ onColumnPinningChange: (updater) => {
1312
+ const previous = current();
1313
+ const pinning = resolve(updater, toTanstackPinning(previous.columnPinning));
1314
+ setState("columnPinning", fromTanstackPinning(pinning));
1315
+ },
1316
+ /* "Select all N" describes the query's whole result set; any change to
1317
+ the explicit selection (or to the query, above) is narrower than that. */
1318
+ onRowSelectionChange: (updater) => {
1319
+ const previous = current();
1320
+ const rowSelection = resolve(updater, previous.rowSelection);
1321
+ if (Object.is(rowSelection, previous.rowSelection)) return;
1322
+ emit({ ...previous, rowSelection, allMatching: false });
1323
+ },
1324
+ onExpandedChange: (updater) => setState("expanded", updater),
1325
+ onColumnSizingChange: (updater) => {
1326
+ const previous = current();
1327
+ if (previous.columnSizing === void 0) return setInternalSizing(updater);
1328
+ const columnSizing = resolve(updater, previous.columnSizing);
1329
+ if (columnSizing !== previous.columnSizing) emit({ ...previous, columnSizing });
1330
+ }
1331
+ });
1332
+ return { table, state, setState, replaceState };
1333
+ }
1334
+
1335
+ // src/server-adapter.ts
1336
+ var INTERNAL_COLUMN_IDS = /* @__PURE__ */ new Set(["__select", "__expand"]);
1337
+ function toServerState(state) {
1338
+ const s = { ...defaultDataGridState, ...state };
1339
+ return {
1340
+ pagination: s.pagination,
1341
+ sorting: s.sorting,
1342
+ columnFilters: s.columnFilters,
1343
+ search: s.search,
1344
+ allMatching: s.allMatching ?? false
1345
+ };
1346
+ }
1347
+ var isEmptyFilter = (value) => value === void 0 || value === "" || Array.isArray(value) && value.length === 0;
1348
+ function toQuery(state) {
1349
+ const s = toServerState(state);
1350
+ const filters = {};
1351
+ const active = s.columnFilters.filter((filter) => !isEmptyFilter(filter.value));
1352
+ for (const filter of [...active].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0)) {
1353
+ filters[filter.id] = filter.value;
1354
+ }
1355
+ return {
1356
+ page: s.pagination.pageIndex + 1,
1357
+ pageSize: s.pagination.pageSize,
1358
+ sort: s.sorting.map(({ id, desc }) => ({ id, desc })),
1359
+ filters,
1360
+ search: s.search,
1361
+ allMatching: s.allMatching ?? false
1362
+ };
1363
+ }
1364
+ function toQueryKey(state) {
1365
+ return JSON.stringify(toQuery(state));
1366
+ }
1367
+ var isPositiveInteger = (value) => typeof value === "number" && Number.isInteger(value) && value > 0;
1368
+ var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1369
+ function toSortEntry(entry) {
1370
+ if (!isPlainObject(entry) || typeof entry.id !== "string" || entry.id === "") return null;
1371
+ return { id: entry.id, desc: entry.desc === true };
1372
+ }
1373
+ function fromQuery(query) {
1374
+ const pageSize = isPositiveInteger(query.pageSize) ? query.pageSize : defaultDataGridState.pagination.pageSize;
1375
+ const page = isPositiveInteger(query.page) ? query.page : 1;
1376
+ const sorting = [];
1377
+ if (Array.isArray(query.sort)) {
1378
+ for (const entry of query.sort) {
1379
+ const sort = toSortEntry(entry);
1380
+ if (sort) sorting.push(sort);
1381
+ }
1382
+ }
1383
+ const filters = isPlainObject(query.filters) ? query.filters : {};
1384
+ return {
1385
+ pagination: { pageIndex: page - 1, pageSize },
1386
+ sorting,
1387
+ columnFilters: Object.entries(filters).filter(([, value]) => !isEmptyFilter(value)).map(([id, value]) => ({ id, value })),
1388
+ search: typeof query.search === "string" ? query.search : "",
1389
+ allMatching: query.allMatching === true
1390
+ };
1391
+ }
1392
+ function toColumnState(state) {
1393
+ const s = { ...defaultDataGridState, ...state };
1394
+ const own = (id) => !INTERNAL_COLUMN_IDS.has(id);
1395
+ const visibility = {};
1396
+ for (const [id, visible] of Object.entries(s.columnVisibility)) if (own(id)) visibility[id] = visible;
1397
+ const sizing = {};
1398
+ for (const [id, size] of Object.entries(s.columnSizing ?? {})) if (own(id)) sizing[id] = size;
1399
+ return {
1400
+ columnVisibility: visibility,
1401
+ columnOrder: s.columnOrder.filter(own),
1402
+ columnPinning: { start: (s.columnPinning.start ?? []).filter(own), end: (s.columnPinning.end ?? []).filter(own) },
1403
+ columnSizing: sizing
1404
+ };
1405
+ }
1406
+ function fromColumnState(saved) {
1407
+ const visibility = { ...saved.columnVisibility };
1408
+ const order = [...saved.columnOrder ?? []];
1409
+ const pinning = { start: [...saved.columnPinning?.start ?? []], end: [...saved.columnPinning?.end ?? []] };
1410
+ return {
1411
+ columnVisibility: visibility,
1412
+ columnOrder: order,
1413
+ columnPinning: pinning,
1414
+ columnSizing: { ...saved.columnSizing }
1415
+ };
1416
+ }
1417
+
1418
+ // src/data-grid.tsx
1419
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
1420
+ var FIXED_COLUMN_IDS2 = [SELECT_COLUMN_ID, EXPAND_COLUMN_ID];
1421
+ var DEFAULT_PAGE_SIZE_OPTIONS = [10, 25, 50, 100];
1422
+ function DataGridRoot({
1423
+ columns,
1424
+ rows,
1425
+ rowCount,
1426
+ state: stateProp,
1427
+ defaultState,
1428
+ onStateChange,
1429
+ manualPagination,
1430
+ manualSorting,
1431
+ manualFiltering,
1432
+ getRowId,
1433
+ enableRowSelection = true,
1434
+ enableColumnResizing,
1435
+ getRowCanExpand,
1436
+ renderExpanded,
1437
+ tableOptions,
1438
+ loading = false,
1439
+ error,
1440
+ skeletonWhileRefreshing = false,
1441
+ labels: labelsProp,
1442
+ emptyState,
1443
+ density = "comfortable",
1444
+ stickyHeader = false,
1445
+ onRowClick,
1446
+ onCellCommit,
1447
+ commitOnBlur = true,
1448
+ selectAllMatching = true,
1449
+ locale,
1450
+ selectionActions = [],
1451
+ pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS,
1452
+ showPagination = true,
1453
+ "aria-label": ariaLabel = "Data grid",
1454
+ "aria-labelledby": ariaLabelledBy,
1455
+ children,
1456
+ className,
1457
+ tableClassName,
1458
+ scrollAreaClassName,
1459
+ ...props
1460
+ }) {
1461
+ const expandable = Boolean(renderExpanded);
1462
+ const allColumns = useMemo3(() => {
1463
+ const leading = [];
1464
+ if (enableRowSelection) leading.push(createSelectColumn());
1465
+ if (expandable) leading.push(createExpandColumn());
1466
+ return leading.length ? [...leading, ...columns] : columns;
1467
+ }, [columns, enableRowSelection, expandable]);
1468
+ const fixedColumnIds = useMemo3(
1469
+ () => FIXED_COLUMN_IDS2.filter((id) => id === SELECT_COLUMN_ID ? Boolean(enableRowSelection) : expandable),
1470
+ [enableRowSelection, expandable]
1471
+ );
1472
+ const canExpand = useMemo3(
1473
+ () => expandable ? getRowCanExpand ?? (() => true) : () => false,
1474
+ [expandable, getRowCanExpand]
1475
+ );
1476
+ const { table, state, setState } = useDataGrid({
1477
+ columns: allColumns,
1478
+ rows,
1479
+ rowCount,
1480
+ state: stateProp,
1481
+ defaultState,
1482
+ onStateChange,
1483
+ fixedColumnIds,
1484
+ manualPagination,
1485
+ manualSorting,
1486
+ manualFiltering,
1487
+ getRowId,
1488
+ enableRowSelection,
1489
+ enableColumnResizing,
1490
+ getRowCanExpand: canExpand,
1491
+ tableOptions
1492
+ });
1493
+ const labels = useMemo3(() => ({ ...defaultDataGridLabels, ...labelsProp }), [labelsProp]);
1494
+ const context = {
1495
+ table,
1496
+ state,
1497
+ setState,
1498
+ loading,
1499
+ density,
1500
+ onCellCommit,
1501
+ renderExpanded,
1502
+ selectionActions,
1503
+ pageSizeOptions,
1504
+ /* The prop for a server-driven grid; with client-side paging, the rows that pass the filters. */
1505
+ rowCount: table.getRowCount(),
1506
+ labels,
1507
+ locale,
1508
+ commitOnBlur,
1509
+ selectAllMatching
1510
+ };
1511
+ return /* @__PURE__ */ jsx10(DataGridContext.Provider, { value: context, children: /* @__PURE__ */ jsxs7(
1512
+ "div",
1513
+ {
1514
+ "data-slot": "data-grid",
1515
+ "data-density": density,
1516
+ "data-loading": loading ? "" : void 0,
1517
+ className: cn8("flex flex-col gap-3 text-foreground", className),
1518
+ ...props,
1519
+ children: [
1520
+ children,
1521
+ /* @__PURE__ */ jsx10(DataGridSelectionBar, {}),
1522
+ /* @__PURE__ */ jsx10(
1523
+ DataGridBody,
1524
+ {
1525
+ "aria-label": ariaLabelledBy ? void 0 : ariaLabel,
1526
+ "aria-labelledby": ariaLabelledBy,
1527
+ stickyHeader,
1528
+ onRowClick,
1529
+ error,
1530
+ skeletonWhileRefreshing,
1531
+ emptyState,
1532
+ className: tableClassName,
1533
+ scrollAreaClassName
1534
+ }
1535
+ ),
1536
+ showPagination ? /* @__PURE__ */ jsx10(DataGridPagination, {}) : null
1537
+ ]
1538
+ }
1539
+ ) });
1540
+ }
1541
+ var DataGrid = DataGridRoot;
1542
+ DataGrid.displayName = "DataGrid";
1543
+ DataGrid.Toolbar = DataGridToolbar;
1544
+ DataGrid.ColumnManager = DataGridColumnManager;
1545
+ DataGrid.SelectionBar = DataGridSelectionBar;
1546
+ DataGrid.Body = DataGridBody;
1547
+ DataGrid.Pagination = DataGridPagination;
1548
+ var data_grid_default = DataGrid;
1549
+ export {
1550
+ DEFAULT_PAGE_SIZE,
1551
+ DEFAULT_PAGE_SIZE_OPTIONS,
1552
+ DataGridBody,
1553
+ DataGridColumnManager,
1554
+ DataGridContext,
1555
+ DataGridPagination,
1556
+ DataGridSelectionBar,
1557
+ DataGridToolbar,
1558
+ EXPAND_COLUMN_ID,
1559
+ SELECT_COLUMN_ID,
1560
+ data_grid_default as default,
1561
+ defaultDataGridLabels,
1562
+ defaultDataGridState,
1563
+ fromColumnState,
1564
+ fromQuery,
1565
+ normalizeColumnState,
1566
+ resolveDataGridState,
1567
+ toColumnState,
1568
+ toQuery,
1569
+ toQueryKey,
1570
+ toServerState,
1571
+ useDataGrid,
1572
+ useDataGridContext
1573
+ };