gridsmith-ui 0.21.0 → 0.23.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.
Files changed (48) hide show
  1. package/dist/chat.js +3 -3
  2. package/dist/chunk-3BRQRKDO.js +72 -0
  3. package/dist/chunk-3BRQRKDO.js.map +1 -0
  4. package/dist/{chunk-GGVASDXT.js → chunk-6PL7FXYH.js} +284 -521
  5. package/dist/chunk-6PL7FXYH.js.map +1 -0
  6. package/dist/chunk-7TOJ4G7S.js +197 -0
  7. package/dist/chunk-7TOJ4G7S.js.map +1 -0
  8. package/dist/chunk-CKWITUSH.js +40 -0
  9. package/dist/chunk-CKWITUSH.js.map +1 -0
  10. package/dist/{chunk-ILIODA7W.js → chunk-CXMV7SXJ.js} +4 -3
  11. package/dist/chunk-CXMV7SXJ.js.map +1 -0
  12. package/dist/{chunk-EQDR57AM.js → chunk-EYMGLK4M.js} +3 -3
  13. package/dist/{chunk-EQDR57AM.js.map → chunk-EYMGLK4M.js.map} +1 -1
  14. package/dist/chunk-ICNWPNMM.js +1363 -0
  15. package/dist/chunk-ICNWPNMM.js.map +1 -0
  16. package/dist/chunk-IVCVNAXG.js +115 -0
  17. package/dist/chunk-IVCVNAXG.js.map +1 -0
  18. package/dist/{chunk-XYDIBW3J.js → chunk-SBTBMAIU.js} +4 -3
  19. package/dist/chunk-SBTBMAIU.js.map +1 -0
  20. package/dist/data-grid.d.ts +38 -4
  21. package/dist/data-grid.js +305 -264
  22. package/dist/data-grid.js.map +1 -1
  23. package/dist/editor.js +10 -3
  24. package/dist/editor.js.map +1 -1
  25. package/dist/index.d.ts +122 -146
  26. package/dist/index.js +1091 -1885
  27. package/dist/index.js.map +1 -1
  28. package/dist/kanban.js +4 -2
  29. package/dist/kanban.js.map +1 -1
  30. package/dist/resizable.d.ts +1 -1
  31. package/dist/resizable.js +26 -14
  32. package/dist/resizable.js.map +1 -1
  33. package/dist/roots.css +24 -4
  34. package/dist/{table-utils-GounRE6s.d.ts → table-utils-BkjNEWts.d.ts} +5 -1
  35. package/dist/tokens.css +20 -3
  36. package/dist/use-pagination-DiJo0aW5.d.ts +153 -0
  37. package/dist/virtualized.d.ts +11 -1
  38. package/dist/virtualized.js +69 -18
  39. package/dist/virtualized.js.map +1 -1
  40. package/dist-static/tokens.css +20 -3
  41. package/package.json +1 -1
  42. package/dist/chunk-GGVASDXT.js.map +0 -1
  43. package/dist/chunk-ILIODA7W.js.map +0 -1
  44. package/dist/chunk-JN37OVTP.js +0 -67
  45. package/dist/chunk-JN37OVTP.js.map +0 -1
  46. package/dist/chunk-TKVTZWBN.js +0 -50
  47. package/dist/chunk-TKVTZWBN.js.map +0 -1
  48. package/dist/chunk-XYDIBW3J.js.map +0 -1
package/dist/data-grid.js CHANGED
@@ -1,13 +1,16 @@
1
- import { inferColumnKinds, CardValue } from './chunk-JN37OVTP.js';
2
- import { useShellPreferences, TableCardCtx } from './chunk-TKVTZWBN.js';
1
+ import { useCardsSwitch, CARDS_BELOW, inferColumnKinds, CardValue } from './chunk-IVCVNAXG.js';
2
+ import { usePagination, useWholeListCheck, pagerPart, Pagination, Input } from './chunk-ICNWPNMM.js';
3
+ import { useShellPreferences } from './chunk-7TOJ4G7S.js';
3
4
  import { useBreakpoint } from './chunk-CRQ6KCRD.js';
4
- import { useTextOverflow, Tooltip } from './chunk-ILIODA7W.js';
5
+ import { useGridKeyboard, FormChangeCtx } from './chunk-RT7RYGC3.js';
6
+ import { useFramesItself, TableCardCtx } from './chunk-CKWITUSH.js';
7
+ import { useTextOverflow, Tooltip } from './chunk-CXMV7SXJ.js';
5
8
  import './chunk-7Q46JXSK.js';
6
9
  import './chunk-WH2FUFAC.js';
7
10
  import { Icon } from './chunk-4LDYR7QP.js';
8
11
  import { cn } from './chunk-M2QRNBDQ.js';
9
- import { useState, useMemo, useCallback } from 'react';
10
- import { useReactTable, getPaginationRowModel, getSortedRowModel, getCoreRowModel, flexRender } from '@tanstack/react-table';
12
+ import { useState, useMemo, useCallback, useRef, useEffect, useLayoutEffect } from 'react';
13
+ import { useReactTable, getSortedRowModel, getCoreRowModel, flexRender } from '@tanstack/react-table';
11
14
  import { jsx, jsxs } from 'react/jsx-runtime';
12
15
 
13
16
  function TruncCell({ children, lines }) {
@@ -17,21 +20,27 @@ function TruncCell({ children, lines }) {
17
20
  if (!isTruncated) return cell;
18
21
  return /* @__PURE__ */ jsx(Tooltip, { content: children, children: cell });
19
22
  }
23
+ var cellKey = (p) => p.r < 0 ? `h:${p.c}` : `${p.r}:${p.c}`;
24
+ var SAVED_TICK_MS = 1500;
20
25
  function DataGrid({
21
26
  data,
22
27
  columns,
23
- pageSize = 10,
28
+ pageSize,
24
29
  editable = false,
25
30
  onCellEdit,
26
31
  mobileBreakpoint: mobileBreakpointProp,
32
+ cardsBelow = CARDS_BELOW,
33
+ pager: pagerProp,
34
+ "aria-label": ariaLabel,
27
35
  className
28
36
  }) {
37
+ useFramesItself();
29
38
  const preferences = useShellPreferences();
30
39
  const mobileBreakpoint = mobileBreakpointProp ?? preferences.tableToCardsBreakpoint ?? "md";
31
- const isMobile = useBreakpoint(mobileBreakpoint);
40
+ const narrowScreen = useBreakpoint(mobileBreakpoint);
41
+ const { cards: isMobile, rootRef } = useCardsSwitch(editable ? false : narrowScreen, editable ? 0 : cardsBelow);
32
42
  const [sorting, setSorting] = useState([]);
33
- const [editCell, setEditCell] = useState(null);
34
- const [editValue, setEditValue] = useState("");
43
+ const [editing, setEditing] = useState(null);
35
44
  const columnKeys = useMemo(() => columns.map((c) => c.id ?? ("accessorKey" in c ? String(c.accessorKey) : "")).filter(Boolean), [columns]);
36
45
  const inferredKinds = useMemo(
37
46
  () => inferColumnKinds(data, columnKeys),
@@ -41,74 +50,151 @@ function DataGrid({
41
50
  (colId, meta) => meta?.kind ?? inferredKinds.get(colId),
42
51
  [inferredKinds]
43
52
  );
44
- const tableColumns = useMemo(() => {
45
- if (!editable) return columns;
46
- return columns.map((col) => ({
47
- ...col,
48
- cell: (info) => {
49
- const rowIdx = info.row.index;
50
- const colId = info.column.id;
51
- const isEditing = editCell?.row === rowIdx && editCell?.col === colId;
52
- if (isEditing) {
53
- return /* @__PURE__ */ jsx(
54
- "input",
55
- {
56
- autoFocus: true,
57
- className: cn(
58
- "w-full h-full bg-transparent text-sm text-[var(--ds-text-primary)]",
59
- "outline-none border-none px-0"
60
- ),
61
- value: editValue,
62
- onChange: (e) => setEditValue(e.target.value),
63
- onBlur: () => {
64
- onCellEdit?.(rowIdx, colId, editValue);
65
- setEditCell(null);
66
- },
67
- onKeyDown: (e) => {
68
- if (e.key === "Enter") {
69
- onCellEdit?.(rowIdx, colId, editValue);
70
- setEditCell(null);
71
- } else if (e.key === "Escape") {
72
- setEditCell(null);
73
- }
74
- }
75
- }
76
- );
77
- }
78
- const rendered = typeof col.cell === "function" ? col.cell(info) : info.getValue();
79
- return /* @__PURE__ */ jsx(
80
- "span",
81
- {
82
- onDoubleClick: () => {
83
- setEditCell({ row: rowIdx, col: colId });
84
- setEditValue(String(info.getValue() ?? ""));
85
- },
86
- className: "cursor-default",
87
- children: rendered
88
- }
89
- );
90
- }
91
- }));
92
- }, [columns, editable, editCell, editValue, onCellEdit]);
93
53
  const table = useReactTable({
94
54
  data,
95
- columns: tableColumns,
55
+ columns,
96
56
  state: { sorting },
97
- onSortingChange: setSorting,
57
+ onSortingChange: (updater) => {
58
+ setSorting(updater);
59
+ pagerProp?.setPage(1);
60
+ },
98
61
  getCoreRowModel: getCoreRowModel(),
99
- getSortedRowModel: getSortedRowModel(),
100
- getPaginationRowModel: getPaginationRowModel(),
101
- initialState: { pagination: { pageSize } }
62
+ getSortedRowModel: getSortedRowModel()
102
63
  });
103
- const handlePageChange = useCallback(
104
- (page) => table.setPageIndex(page),
105
- [table]
106
- );
107
- const pageCount = table.getPageCount();
108
- const currentPage = table.getState().pagination.pageIndex;
64
+ const sortedRows = table.getRowModel().rows;
65
+ const ownPager = usePagination(sortedRows, { pageSize, resetOn: sorting });
66
+ const pager = pagerProp ?? ownPager;
67
+ useWholeListCheck("DataGrid", data.length, pagerProp);
68
+ const rows = pagerPart(sortedRows, pager);
69
+ const leafColumns = table.getVisibleLeafColumns();
70
+ const headerText = (id) => {
71
+ const header = leafColumns.find((c) => c.id === id)?.columnDef.header;
72
+ return typeof header === "string" ? header : id;
73
+ };
74
+ const gridRef = useRef(null);
75
+ const [activeId, setActiveId] = useState({ row: null, c: 0 });
76
+ const lastRow = rows.length - 1;
77
+ const rowAt = (id) => id === null ? -1 : rows.findIndex((row) => row.id === id);
78
+ const idAt = (p) => ({ row: p.r < 0 ? null : rows[p.r]?.id ?? null, c: p.c });
79
+ const current = { r: Math.min(rowAt(activeId.row), lastRow), c: Math.min(activeId.c, Math.max(0, leafColumns.length - 1)) };
80
+ const setActive = (p) => setActiveId(idAt(p));
81
+ const move = (p, e) => {
82
+ if (e.altKey) return null;
83
+ const toCorner = e.ctrlKey || e.metaKey;
84
+ switch (e.key) {
85
+ case "ArrowRight":
86
+ return !toCorner && p.c < leafColumns.length - 1 ? { ...p, c: p.c + 1 } : null;
87
+ case "ArrowLeft":
88
+ return !toCorner && p.c > 0 ? { ...p, c: p.c - 1 } : null;
89
+ case "ArrowDown":
90
+ return !toCorner && p.r < lastRow ? { ...p, r: p.r + 1 } : null;
91
+ case "ArrowUp":
92
+ return !toCorner && p.r > -1 ? { ...p, r: p.r - 1 } : null;
93
+ case "Home":
94
+ return toCorner ? { r: -1, c: 0 } : { ...p, c: 0 };
95
+ case "End":
96
+ return toCorner ? { r: lastRow, c: leafColumns.length - 1 } : { ...p, c: leafColumns.length - 1 };
97
+ default:
98
+ return null;
99
+ }
100
+ };
101
+ const ended = useRef(false);
102
+ const startEdit = (p) => {
103
+ const row = rows[p.r];
104
+ const column = leafColumns[p.c];
105
+ if (!row || !column || column.columnDef.meta?.editable === false) return;
106
+ ended.current = false;
107
+ setActive(p);
108
+ const text = String(row.getValue(column.id) ?? "");
109
+ setEditing({ row: row.id, c: p.c, value: text, was: text });
110
+ };
111
+ const grid = useGridKeyboard({
112
+ container: gridRef,
113
+ active: current,
114
+ setActive,
115
+ keyOf: cellKey,
116
+ move,
117
+ onActivate: (p, e) => {
118
+ if (p.r < 0) {
119
+ if (leafColumns[p.c]?.getCanSort()) leafColumns[p.c].toggleSorting();
120
+ } else if (e.key === "Enter") startEdit(p);
121
+ },
122
+ modifierKeys: true
123
+ });
124
+ const onGridKeyDown = (e) => {
125
+ if (e.key === "F2" && !editing && current.r >= 0 && e.target instanceof HTMLElement && e.target.getAttribute("data-grid-key") === cellKey(current)) {
126
+ e.preventDefault();
127
+ startEdit(current);
128
+ return;
129
+ }
130
+ grid.onKeyDown(e);
131
+ };
132
+ const onGridFocus = (e) => {
133
+ const key = e.target instanceof HTMLElement ? e.target.getAttribute("data-grid-key") : null;
134
+ if (!key) return;
135
+ const [r, c] = key.startsWith("h:") ? [-1, Number(key.slice(2))] : key.split(":").map(Number);
136
+ if (Number.isFinite(r) && Number.isFinite(c) && cellKey(current) !== key) setActive({ r, c });
137
+ };
138
+ const [saved, setSaved] = useState(null);
139
+ useEffect(() => {
140
+ if (!saved) return;
141
+ const t = setTimeout(() => setSaved((s) => s && s.n === saved.n ? null : s), SAVED_TICK_MS);
142
+ return () => clearTimeout(t);
143
+ }, [saved]);
144
+ const heard = useRef(0);
145
+ const refocus = useRef(null);
146
+ useLayoutEffect(() => {
147
+ const want = refocus.current;
148
+ if (!want) return;
149
+ const r = rowAt(want.row);
150
+ if (r < 0) {
151
+ const all = sortedRows.findIndex((row) => row.id === want.row);
152
+ if (all >= 0 && pager.style === "pagination") {
153
+ const page = Math.floor(all / pager.pageSize) + 1;
154
+ if (page !== pager.page) {
155
+ pager.setPage(page);
156
+ return;
157
+ }
158
+ }
159
+ refocus.current = null;
160
+ return;
161
+ }
162
+ refocus.current = null;
163
+ grid.focus({ r, c: want.c });
164
+ });
165
+ const finishEdit = (commit, focusBack) => {
166
+ if (!editing || ended.current) return;
167
+ ended.current = true;
168
+ const r = rowAt(editing.row);
169
+ const row = rows[r];
170
+ const column = leafColumns[editing.c];
171
+ const cell = { row: editing.row, c: editing.c };
172
+ setEditing(null);
173
+ if (focusBack) {
174
+ setActiveId(cell);
175
+ refocus.current = cell;
176
+ }
177
+ if (!commit || !row || !column || editing.value === editing.was) return;
178
+ const meta = column.columnDef.meta;
179
+ const done = (ok) => {
180
+ heard.current += 1;
181
+ setSaved({ ...cell, row: editing.row, n: heard.current, ok });
182
+ };
183
+ const result = onCellEdit?.(row.index, column.id, meta?.parse ? meta.parse(editing.value) : editing.value);
184
+ if (result && typeof result.then === "function") result.then(() => done(true), () => done(false));
185
+ else done(true);
186
+ };
187
+ const control = data.length > 0 ? /* @__PURE__ */ jsx(Pagination, { ...pager.props }) : null;
188
+ const setRoot = (node) => {
189
+ rootRef.current = node;
190
+ };
191
+ const withControl = (list) => /* @__PURE__ */ jsxs("div", { ref: setRoot, "data-gs-paged": "", className: cn("flex flex-col min-w-0", className), children: [
192
+ list,
193
+ control
194
+ ] });
109
195
  if (isMobile) {
110
- return /* @__PURE__ */ jsxs("div", { "data-ds-list": "", className: cn("space-y-ds-3", className), children: [
111
- /* @__PURE__ */ jsx(TableCardCtx, { value: true, children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-ds-3", children: table.getRowModel().rows.map((row) => /* @__PURE__ */ jsx(
196
+ return withControl(
197
+ /* @__PURE__ */ jsx("div", { "data-ds-list": "", className: "space-y-ds-3", children: /* @__PURE__ */ jsx(TableCardCtx, { value: true, children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-ds-3", children: rows.map((row) => /* @__PURE__ */ jsx(
112
198
  "div",
113
199
  {
114
200
  className: cn(
@@ -122,209 +208,164 @@ function DataGrid({
122
208
  ] }, cell.id))
123
209
  },
124
210
  row.id
125
- )) }) }),
126
- pageCount > 1 && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-sm", children: [
127
- /* @__PURE__ */ jsxs("span", { className: "text-[var(--ds-text-muted)]", children: [
128
- "Page ",
129
- currentPage + 1,
130
- " of ",
131
- pageCount
132
- ] }),
133
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-1", children: [
134
- /* @__PURE__ */ jsx(PagerBtn, { onClick: () => handlePageChange(currentPage - 1), disabled: currentPage === 0, label: "Prev" }),
135
- /* @__PURE__ */ jsx(PagerBtn, { onClick: () => handlePageChange(currentPage + 1), disabled: currentPage >= pageCount - 1, label: "Next" })
136
- ] })
137
- ] })
138
- ] });
211
+ )) }) }) })
212
+ );
139
213
  }
140
- return /* @__PURE__ */ jsxs("div", { "data-ds-list": "", className: cn("space-y-ds-3", className), children: [
141
- /* @__PURE__ */ jsx(
142
- "div",
214
+ const sortIcons = (sorted) => /* @__PURE__ */ jsxs("span", { className: "inline-flex flex-col -space-y-ds-1.5", "aria-hidden": "true", children: [
215
+ /* @__PURE__ */ jsx(Icon, { name: "chevron-up", size: 10, className: sorted === "asc" ? "text-[var(--ds-accent-primary)]" : "opacity-30" }),
216
+ /* @__PURE__ */ jsx(Icon, { name: "chevron-down", size: 10, className: sorted === "desc" ? "text-[var(--ds-accent-primary)]" : "opacity-30" })
217
+ ] });
218
+ const headerCell = (header, c) => {
219
+ const canSort = header.column.getCanSort();
220
+ const sorted = header.column.getIsSorted();
221
+ const meta = header.column.columnDef.meta;
222
+ const headerKind = kindOf(header.column.id, meta);
223
+ const headerAlign = meta?.align ?? (headerKind === "number" ? "right" : "left");
224
+ const shrink = (headerKind === "number" || headerKind === "date") && !meta?.maxWidth;
225
+ const at = { r: -1, c };
226
+ const label = flexRender(header.column.columnDef.header, header.getContext());
227
+ const inside = canSort ? /* @__PURE__ */ jsxs(
228
+ "button",
143
229
  {
230
+ type: "button",
231
+ onClick: header.column.getToggleSortingHandler(),
232
+ ...editable ? { "data-grid-key": cellKey(at), tabIndex: grid.tabIndexOf(at) } : {},
144
233
  className: cn(
145
- "w-full overflow-auto rounded-[var(--ds-radius-md)]",
146
- "border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
147
- "shadow-[var(--ds-shadow-card)]",
148
- "bg-[var(--ds-bg-surface)]"
234
+ "inline-flex items-center gap-ds-1 uppercase tracking-wider cursor-pointer select-none [font:inherit] text-inherit",
235
+ "hover:text-[var(--ds-text-primary)] rounded-[var(--ds-radius-sm)] focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]"
149
236
  ),
150
- children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
151
- /* @__PURE__ */ jsx("thead", { className: "bg-[var(--ds-bg-muted)]", children: table.getHeaderGroups().map((hg) => /* @__PURE__ */ jsx("tr", { className: "border-b-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)]", children: hg.headers.map((header) => {
152
- const canSort = header.column.getCanSort();
153
- const sorted = header.column.getIsSorted();
154
- const meta = header.column.columnDef.meta;
155
- const headerKind = kindOf(header.column.id, meta);
156
- const headerAlign = meta?.align ?? (headerKind === "number" ? "right" : "left");
157
- const shrink = (headerKind === "number" || headerKind === "date") && !meta?.maxWidth;
158
- return /* @__PURE__ */ jsx(
159
- "th",
160
- {
161
- className: cn(
162
- "px-ds-4 py-ds-3 text-xs font-[var(--ds-weight-heading)] uppercase tracking-wider",
163
- headerAlign === "right" ? "text-right" : headerAlign === "center" ? "text-center" : "text-left",
164
- shrink && "w-px whitespace-nowrap",
165
- "text-[var(--ds-text-secondary)]",
166
- canSort && "cursor-pointer select-none hover:text-[var(--ds-text-primary)]"
167
- ),
168
- style: meta?.maxWidth || meta?.width || meta?.minWidth ? { maxWidth: meta?.maxWidth, width: meta?.width, minWidth: meta?.minWidth } : void 0,
169
- onClick: canSort ? header.column.getToggleSortingHandler() : void 0,
170
- children: /* @__PURE__ */ jsxs("span", { className: cn("flex items-center gap-ds-1", headerAlign === "right" && "justify-end", headerAlign === "center" && "justify-center"), children: [
171
- flexRender(header.column.columnDef.header, header.getContext()),
172
- canSort && /* @__PURE__ */ jsxs("span", { className: "inline-flex flex-col -space-y-ds-1.5", children: [
173
- /* @__PURE__ */ jsx(
174
- Icon,
175
- {
176
- name: "chevron-up",
177
- size: 10,
178
- className: sorted === "asc" ? "text-[var(--ds-accent-primary)]" : "opacity-30"
179
- }
180
- ),
181
- /* @__PURE__ */ jsx(
182
- Icon,
183
- {
184
- name: "chevron-down",
185
- size: 10,
186
- className: sorted === "desc" ? "text-[var(--ds-accent-primary)]" : "opacity-30"
187
- }
188
- )
189
- ] })
190
- ] })
191
- },
192
- header.id
193
- );
194
- }) }, hg.id)) }),
195
- /* @__PURE__ */ jsx("tbody", { children: table.getRowModel().rows.map((row) => /* @__PURE__ */ jsx(
196
- "tr",
237
+ children: [
238
+ label,
239
+ sortIcons(sorted)
240
+ ]
241
+ }
242
+ ) : label;
243
+ return /* @__PURE__ */ jsx(
244
+ "th",
245
+ {
246
+ role: editable ? "columnheader" : void 0,
247
+ "aria-sort": sorted === "asc" ? "ascending" : sorted === "desc" ? "descending" : void 0,
248
+ ...editable && !canSort ? { "data-grid-key": cellKey(at), tabIndex: grid.tabIndexOf(at) } : {},
249
+ className: cn(
250
+ "px-ds-4 py-ds-3 text-xs font-[var(--ds-weight-heading)] uppercase tracking-wider",
251
+ headerAlign === "right" ? "text-right" : headerAlign === "center" ? "text-center" : "text-left",
252
+ shrink && "w-px whitespace-nowrap",
253
+ "text-[var(--ds-text-secondary)] outline-none focus-visible:shadow-[inset_var(--ds-shadow-focus)]"
254
+ ),
255
+ style: meta?.maxWidth || meta?.width || meta?.minWidth ? { maxWidth: meta?.maxWidth, width: meta?.width, minWidth: meta?.minWidth } : void 0,
256
+ children: /* @__PURE__ */ jsx("span", { className: cn("flex items-center gap-ds-1", headerAlign === "right" && "justify-end", headerAlign === "center" && "justify-center"), children: inside })
257
+ },
258
+ header.id
259
+ );
260
+ };
261
+ return withControl(
262
+ /* @__PURE__ */ jsxs("div", { "data-ds-list": "", className: "min-w-0", children: [
263
+ /* @__PURE__ */ jsx(
264
+ "div",
265
+ {
266
+ "data-table-scroll": "",
267
+ className: cn(
268
+ "w-full overflow-auto rounded-[var(--ds-radius-md)]",
269
+ "border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
270
+ "shadow-[var(--ds-shadow-card)]",
271
+ "bg-[var(--ds-bg-surface)]"
272
+ ),
273
+ children: /* @__PURE__ */ jsxs(
274
+ "table",
197
275
  {
198
- className: cn(
199
- "border-b-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] last:border-b-0",
200
- "hover:bg-[var(--ds-bg-muted)] transition-colors"
201
- ),
202
- children: row.getVisibleCells().map((cell) => {
203
- const meta = cell.column.columnDef.meta;
204
- const truncLines = meta?.truncate === true ? 1 : meta?.truncate ? meta.truncate.lines : 0;
205
- const kind = kindOf(cell.column.id, meta);
206
- const rendered = flexRender(cell.column.columnDef.cell, cell.getContext());
207
- const shrink = (kind === "number" || kind === "date") && !meta?.maxWidth;
208
- let inner;
209
- if (truncLines > 0) {
210
- inner = /* @__PURE__ */ jsx(TruncCell, { lines: truncLines, children: rendered });
211
- } else if (kind === "email") {
212
- inner = /* @__PURE__ */ jsx("span", { className: "max-w-[40ch] truncate inline-block align-bottom", title: typeof cell.getValue() === "string" ? cell.getValue() : void 0, children: rendered });
213
- } else {
214
- inner = rendered;
215
- }
216
- const align = meta?.align ?? (kind === "number" ? "right" : "left");
217
- const value = cell.getValue();
218
- if (align !== "left" && typeof value !== "string" && typeof value !== "number" && value !== null && value !== void 0) {
219
- inner = /* @__PURE__ */ jsx("div", { className: cn("flex items-center", align === "center" ? "justify-center" : "justify-end"), children: inner });
220
- }
221
- return /* @__PURE__ */ jsx(
222
- "td",
276
+ ref: gridRef,
277
+ role: editable ? "grid" : void 0,
278
+ "aria-label": ariaLabel,
279
+ onKeyDown: editable ? onGridKeyDown : void 0,
280
+ onFocus: editable ? onGridFocus : void 0,
281
+ className: "w-full text-sm",
282
+ children: [
283
+ /* @__PURE__ */ jsx("thead", { className: "bg-[var(--ds-bg-muted)]", children: table.getHeaderGroups().map((hg) => /* @__PURE__ */ jsx("tr", { role: editable ? "row" : void 0, className: "border-b-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)]", children: hg.headers.map((header, c) => headerCell(header, c)) }, hg.id)) }),
284
+ /* @__PURE__ */ jsx("tbody", { children: rows.map((row, r) => /* @__PURE__ */ jsx(
285
+ "tr",
223
286
  {
287
+ role: editable ? "row" : void 0,
224
288
  className: cn(
225
- "px-ds-4 py-ds-3 text-[var(--ds-text-primary)]",
226
- align === "right" && "text-right",
227
- align === "center" && "text-center",
228
- shrink && "w-px whitespace-nowrap"
289
+ "border-b-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] last:border-b-0",
290
+ "hover:bg-[var(--ds-bg-muted)] transition-colors"
229
291
  ),
230
- style: meta?.maxWidth || meta?.width || meta?.minWidth ? { maxWidth: meta?.maxWidth, width: meta?.width, minWidth: meta?.minWidth } : void 0,
231
- children: inner
292
+ children: row.getVisibleCells().map((cell, c) => {
293
+ const meta = cell.column.columnDef.meta;
294
+ const truncLines = meta?.truncate === true ? 1 : meta?.truncate ? meta.truncate.lines : 0;
295
+ const kind = kindOf(cell.column.id, meta);
296
+ const rendered = flexRender(cell.column.columnDef.cell, cell.getContext());
297
+ const at = { r, c };
298
+ const isEditing = editable && editing?.row === row.id && editing.c === c;
299
+ const shrink = (kind === "number" || kind === "date") && !meta?.maxWidth;
300
+ let inner;
301
+ if (isEditing) {
302
+ inner = /* @__PURE__ */ jsx(FormChangeCtx, { value: null, children: /* @__PURE__ */ jsx(
303
+ Input,
304
+ {
305
+ size: "sm",
306
+ autoFocus: true,
307
+ "aria-label": headerText(cell.column.id),
308
+ onFocus: (e) => e.currentTarget.select(),
309
+ value: editing.value,
310
+ onChange: (e) => setEditing({ ...editing, value: e.target.value }),
311
+ onBlur: () => finishEdit(true, false),
312
+ onKeyDown: (e) => {
313
+ if (e.key === "Enter") {
314
+ e.preventDefault();
315
+ finishEdit(true, true);
316
+ } else if (e.key === "Escape") {
317
+ e.preventDefault();
318
+ e.stopPropagation();
319
+ finishEdit(false, true);
320
+ }
321
+ }
322
+ }
323
+ ) });
324
+ } else if (truncLines > 0) {
325
+ inner = /* @__PURE__ */ jsx(TruncCell, { lines: truncLines, children: rendered });
326
+ } else if (kind === "email") {
327
+ inner = /* @__PURE__ */ jsx("span", { className: "max-w-[40ch] truncate inline-block align-bottom", title: typeof cell.getValue() === "string" ? cell.getValue() : void 0, children: rendered });
328
+ } else {
329
+ inner = rendered;
330
+ }
331
+ const align = meta?.align ?? (kind === "number" ? "right" : "left");
332
+ const value = cell.getValue();
333
+ if (!isEditing && align !== "left" && typeof value !== "string" && typeof value !== "number" && value !== null && value !== void 0) {
334
+ inner = /* @__PURE__ */ jsx("div", { className: cn("flex items-center", align === "center" ? "justify-center" : "justify-end"), children: inner });
335
+ }
336
+ const tick = editable && saved?.ok && saved.row === row.id && saved.c === c ? /* @__PURE__ */ jsx(Icon, { name: "check", size: 14, className: "ml-ds-1 inline-block align-middle text-[var(--ds-status-success)]" }) : null;
337
+ return /* @__PURE__ */ jsxs(
338
+ "td",
339
+ {
340
+ ...editable ? { role: "gridcell", "data-grid-key": cellKey(at), tabIndex: grid.tabIndexOf(at), onDoubleClick: () => {
341
+ if (!isEditing) startEdit(at);
342
+ } } : {},
343
+ className: cn(
344
+ "px-ds-4 py-ds-3 text-[var(--ds-text-primary)]",
345
+ align === "right" && "text-right",
346
+ align === "center" && "text-center",
347
+ shrink && "w-px whitespace-nowrap",
348
+ editable && "outline-none focus-visible:shadow-[inset_var(--ds-shadow-focus)]"
349
+ ),
350
+ style: meta?.maxWidth || meta?.width || meta?.minWidth ? { maxWidth: meta?.maxWidth, width: meta?.width, minWidth: meta?.minWidth } : void 0,
351
+ children: [
352
+ inner,
353
+ tick
354
+ ]
355
+ },
356
+ cell.id
357
+ );
358
+ })
232
359
  },
233
- cell.id
234
- );
235
- })
236
- },
237
- row.id
238
- )) })
239
- ] })
240
- }
241
- ),
242
- pageCount > 1 && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-sm", children: [
243
- /* @__PURE__ */ jsxs("span", { className: "text-[var(--ds-text-muted)]", children: [
244
- "Page ",
245
- currentPage + 1,
246
- " of ",
247
- pageCount
248
- ] }),
249
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-1", children: [
250
- /* @__PURE__ */ jsx(
251
- PagerBtn,
252
- {
253
- onClick: () => handlePageChange(0),
254
- disabled: currentPage === 0,
255
- label: "First"
256
- }
257
- ),
258
- /* @__PURE__ */ jsx(
259
- PagerBtn,
260
- {
261
- onClick: () => handlePageChange(currentPage - 1),
262
- disabled: currentPage === 0,
263
- label: "Prev"
264
- }
265
- ),
266
- Array.from({ length: Math.min(pageCount, 5) }, (_, i) => {
267
- let page;
268
- if (pageCount <= 5) {
269
- page = i;
270
- } else if (currentPage < 3) {
271
- page = i;
272
- } else if (currentPage > pageCount - 4) {
273
- page = pageCount - 5 + i;
274
- } else {
275
- page = currentPage - 2 + i;
276
- }
277
- return /* @__PURE__ */ jsx(
278
- PagerBtn,
279
- {
280
- onClick: () => handlePageChange(page),
281
- active: page === currentPage,
282
- label: String(page + 1)
283
- },
284
- page
285
- );
286
- }),
287
- /* @__PURE__ */ jsx(
288
- PagerBtn,
289
- {
290
- onClick: () => handlePageChange(currentPage + 1),
291
- disabled: currentPage >= pageCount - 1,
292
- label: "Next"
293
- }
294
- ),
295
- /* @__PURE__ */ jsx(
296
- PagerBtn,
297
- {
298
- onClick: () => handlePageChange(pageCount - 1),
299
- disabled: currentPage >= pageCount - 1,
300
- label: "Last"
301
- }
302
- )
303
- ] })
304
- ] })
305
- ] });
306
- }
307
- function PagerBtn({
308
- onClick,
309
- disabled,
310
- active,
311
- label
312
- }) {
313
- return /* @__PURE__ */ jsx(
314
- "button",
315
- {
316
- type: "button",
317
- onClick,
318
- disabled,
319
- className: cn(
320
- "h-8 min-w-[32px] px-ds-2 text-xs font-medium rounded-[var(--ds-radius-sm)]",
321
- "transition-colors duration-[var(--ds-transition-speed)] cursor-pointer",
322
- "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
323
- "disabled:opacity-disabled disabled:cursor-not-allowed",
324
- active ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)]" : "text-[var(--ds-text-secondary)] hover:bg-[var(--ds-bg-muted)] hover:text-[var(--ds-text-primary)]"
360
+ row.id
361
+ )) })
362
+ ]
363
+ }
364
+ )
365
+ }
325
366
  ),
326
- children: label
327
- }
367
+ editable && /* @__PURE__ */ jsx("div", { role: "status", className: "sr-only", children: /* @__PURE__ */ jsx("span", { children: saved ? saved.ok ? "Saved" : "Not saved" : "" }, saved?.n ?? 0) })
368
+ ] })
328
369
  );
329
370
  }
330
371