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