cortena-ui 1.4.2 → 1.5.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 (133) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +149 -1
  3. package/dist/a2ui/views.js +2 -2
  4. package/dist/agent-chat/a2ui-block.d.ts +60 -0
  5. package/dist/agent-chat/a2ui-block.js +69 -0
  6. package/dist/agent-chat/a2ui-block.js.map +1 -0
  7. package/dist/agent-chat/agui-client.d.ts +40 -0
  8. package/dist/agent-chat/agui-client.js +251 -0
  9. package/dist/agent-chat/agui-client.js.map +1 -0
  10. package/dist/agent-chat/bridge.d.ts +109 -0
  11. package/dist/agent-chat/bridge.js +349 -0
  12. package/dist/agent-chat/bridge.js.map +1 -0
  13. package/dist/agent-chat/session.d.ts +58 -0
  14. package/dist/agent-chat/session.js +249 -0
  15. package/dist/agent-chat/session.js.map +1 -0
  16. package/dist/agent-chat/store.d.ts +67 -0
  17. package/dist/agent-chat/store.js +548 -0
  18. package/dist/agent-chat/store.js.map +1 -0
  19. package/dist/agent-chat/types.d.ts +187 -0
  20. package/dist/agent-chat/types.js +17 -0
  21. package/dist/agent-chat/types.js.map +1 -0
  22. package/dist/agent-chat.d.ts +10 -0
  23. package/dist/agent-chat.js +10 -0
  24. package/dist/components/admin-permissions/admin-permissions.d.ts +66 -0
  25. package/dist/components/admin-permissions/admin-permissions.js +101 -0
  26. package/dist/components/admin-permissions/admin-permissions.js.map +1 -0
  27. package/dist/components/admin-permissions/context.d.ts +70 -0
  28. package/dist/components/admin-permissions/context.js +258 -0
  29. package/dist/components/admin-permissions/context.js.map +1 -0
  30. package/dist/components/admin-permissions/index.d.ts +10 -0
  31. package/dist/components/admin-permissions/licence.d.ts +15 -0
  32. package/dist/components/admin-permissions/licence.js +78 -0
  33. package/dist/components/admin-permissions/licence.js.map +1 -0
  34. package/dist/components/admin-permissions/matrix.d.ts +20 -0
  35. package/dist/components/admin-permissions/matrix.js +191 -0
  36. package/dist/components/admin-permissions/matrix.js.map +1 -0
  37. package/dist/components/admin-permissions/members.d.ts +18 -0
  38. package/dist/components/admin-permissions/members.js +185 -0
  39. package/dist/components/admin-permissions/members.js.map +1 -0
  40. package/dist/components/admin-permissions/role-assignment.d.ts +35 -0
  41. package/dist/components/admin-permissions/role-assignment.js +174 -0
  42. package/dist/components/admin-permissions/role-assignment.js.map +1 -0
  43. package/dist/components/admin-permissions/roles.d.ts +25 -0
  44. package/dist/components/admin-permissions/roles.js +168 -0
  45. package/dist/components/admin-permissions/roles.js.map +1 -0
  46. package/dist/components/admin-permissions/types.d.ts +152 -0
  47. package/dist/components/admin-permissions/types.js +63 -0
  48. package/dist/components/admin-permissions/types.js.map +1 -0
  49. package/dist/components/agent-chat-popup.d.ts +29 -0
  50. package/dist/components/agent-chat-popup.js +188 -0
  51. package/dist/components/agent-chat-popup.js.map +1 -0
  52. package/dist/components/agent-chat.d.ts +143 -0
  53. package/dist/components/agent-chat.js +578 -0
  54. package/dist/components/agent-chat.js.map +1 -0
  55. package/dist/components/app-shell.d.ts +126 -0
  56. package/dist/components/app-shell.js +297 -0
  57. package/dist/components/app-shell.js.map +1 -0
  58. package/dist/components/badge.d.ts +1 -1
  59. package/dist/components/button-link.js +1 -1
  60. package/dist/components/button.d.ts +2 -2
  61. package/dist/components/checkbox.d.ts +1 -1
  62. package/dist/components/combobox.d.ts +1 -1
  63. package/dist/components/combobox.js +1 -1
  64. package/dist/components/consent-screen.d.ts +65 -0
  65. package/dist/components/consent-screen.js +123 -0
  66. package/dist/components/consent-screen.js.map +1 -0
  67. package/dist/components/data-table/data-table.d.ts +15 -1
  68. package/dist/components/data-table/data-table.js +18 -4
  69. package/dist/components/data-table/data-table.js.map +1 -1
  70. package/dist/components/data-table/index.d.ts +4 -4
  71. package/dist/components/data-table/parts.d.ts +27 -3
  72. package/dist/components/data-table/parts.js +175 -55
  73. package/dist/components/data-table/parts.js.map +1 -1
  74. package/dist/components/data-table/types.d.ts +61 -0
  75. package/dist/components/data-table/use-data-table.js +91 -6
  76. package/dist/components/data-table/use-data-table.js.map +1 -1
  77. package/dist/components/data-table/use-server-source.js +119 -28
  78. package/dist/components/data-table/use-server-source.js.map +1 -1
  79. package/dist/components/help-panel.d.ts +131 -0
  80. package/dist/components/help-panel.js +545 -0
  81. package/dist/components/help-panel.js.map +1 -0
  82. package/dist/components/login-screen.d.ts +127 -0
  83. package/dist/components/login-screen.js +339 -0
  84. package/dist/components/login-screen.js.map +1 -0
  85. package/dist/components/session-guard.d.ts +268 -0
  86. package/dist/components/session-guard.js +632 -0
  87. package/dist/components/session-guard.js.map +1 -0
  88. package/dist/components/toast.d.ts +1 -1
  89. package/dist/core.d.ts +5 -1
  90. package/dist/core.js +11 -7
  91. package/dist/data-table.d.ts +13 -4
  92. package/dist/data-table.js +10 -2
  93. package/dist/hooks/use-cortena-theme.js +49 -3
  94. package/dist/hooks/use-cortena-theme.js.map +1 -1
  95. package/dist/index.d.ts +17 -4
  96. package/dist/index.js +21 -8
  97. package/dist/markdown.d.ts +2 -1
  98. package/dist/markdown.js +2 -1
  99. package/package.json +16 -4
  100. package/src/agent-chat/a2ui-block.ts +118 -0
  101. package/src/agent-chat/agui-client.ts +405 -0
  102. package/src/agent-chat/bridge.ts +433 -0
  103. package/src/agent-chat/session.ts +392 -0
  104. package/src/agent-chat/store.ts +738 -0
  105. package/src/agent-chat/types.ts +213 -0
  106. package/src/components/admin-permissions/admin-permissions.tsx +130 -0
  107. package/src/components/admin-permissions/context.tsx +376 -0
  108. package/src/components/admin-permissions/index.tsx +32 -0
  109. package/src/components/admin-permissions/licence.tsx +84 -0
  110. package/src/components/admin-permissions/matrix.tsx +257 -0
  111. package/src/components/admin-permissions/members.tsx +204 -0
  112. package/src/components/admin-permissions/role-assignment.tsx +239 -0
  113. package/src/components/admin-permissions/roles.tsx +169 -0
  114. package/src/components/admin-permissions/types.ts +231 -0
  115. package/src/components/agent-chat-popup.tsx +289 -0
  116. package/src/components/agent-chat.tsx +843 -0
  117. package/src/components/app-shell.tsx +502 -0
  118. package/src/components/consent-screen.tsx +239 -0
  119. package/src/components/data-table/data-table.tsx +36 -0
  120. package/src/components/data-table/index.tsx +6 -1
  121. package/src/components/data-table/parts.tsx +223 -47
  122. package/src/components/data-table/types.ts +68 -0
  123. package/src/components/data-table/use-data-table.ts +152 -4
  124. package/src/components/data-table/use-server-source.ts +150 -12
  125. package/src/components/help-panel.tsx +765 -0
  126. package/src/components/login-screen.tsx +479 -0
  127. package/src/components/session-guard.tsx +1071 -0
  128. package/src/entries/agent-chat.ts +113 -0
  129. package/src/entries/core.ts +8 -0
  130. package/src/entries/data-table.ts +41 -0
  131. package/src/entries/markdown.ts +25 -0
  132. package/src/hooks/use-cortena-theme.ts +63 -4
  133. package/src/index.ts +6 -0
@@ -1,14 +1,16 @@
1
1
  "use client";
2
2
  "use client";
3
3
  import { cn } from "../../lib/cn.js";
4
+ import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "../dropdown-menu.js";
4
5
  import { Button } from "../button.js";
5
6
  import { Input } from "../input.js";
6
- import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "../dropdown-menu.js";
7
7
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../select.js";
8
+ import { StatusDot } from "../status-dot.js";
9
+ import { Switch } from "../switch.js";
8
10
  import { columnLabel, downloadCsv, downloadExcel } from "./export.js";
9
- import { ArrowDown, ArrowUp, ChevronDown, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, ChevronsUpDown, Download, EyeOff, ListFilter, PinOff, Search, Settings2, X } from "lucide-react";
11
+ import { ArrowDown, ArrowUp, ChevronDown, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, ChevronsUpDown, Download, EyeOff, ListFilter, PinOff, RefreshCw, Search, Settings2, X } from "lucide-react";
10
12
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
- import "react";
13
+ import * as React from "react";
12
14
  //#region src/components/data-table/parts.tsx
13
15
  /**
14
16
  * Header content for one column: the title as a sort toggle when the column
@@ -244,7 +246,7 @@ function DataTableExportMenu({ table, fileName, className }) {
244
246
  * when anything is filtered, and the view / export menus. While rows are
245
247
  * selected the left side becomes the selection summary and `bulkActions`.
246
248
  */
247
- function DataTableToolbar({ table, searchPlaceholder = "Search…", bulkActions, children, enableExport = true, exportFileName, className, ...props }) {
249
+ function DataTableToolbar({ table, searchPlaceholder = "Search…", bulkActions, children, enableExport = true, exportFileName, refresh, className, ...props }) {
248
250
  const globalFilter = table.state.globalFilter ?? "";
249
251
  const canSearch = table.options.enableGlobalFilter !== false;
250
252
  const facetedColumns = table.getAllLeafColumns().filter((c) => c.columnDef.meta?.filter === "faceted" && c.getCanFilter());
@@ -254,57 +256,60 @@ function DataTableToolbar({ table, searchPlaceholder = "Search…", bulkActions,
254
256
  "data-slot": "data-table-toolbar",
255
257
  className: cn("flex flex-wrap items-center gap-2", className),
256
258
  ...props,
257
- children: [selectedRows.length ? /* @__PURE__ */ jsxs("div", {
258
- "data-slot": "data-table-bulk-actions",
259
- className: "flex min-w-0 flex-1 flex-wrap items-center gap-2",
260
- children: [
261
- /* @__PURE__ */ jsxs("span", {
262
- className: "text-[length:var(--ds-text-caption-lg)] text-[color:var(--ds-muted-foreground)]",
263
- children: [/* @__PURE__ */ jsx("span", {
264
- className: "font-medium text-[color:var(--ds-foreground)]",
265
- children: selectedRows.length
266
- }), " selected"]
267
- }),
268
- bulkActions?.({
269
- rows: selectedRows.map((r) => r.original),
270
- table
271
- }),
272
- /* @__PURE__ */ jsxs(Button, {
273
- variant: "ghost",
274
- size: "sm",
275
- onClick: () => table.resetRowSelection(),
276
- children: ["Clear", /* @__PURE__ */ jsx(X, {})]
277
- })
278
- ]
279
- }) : /* @__PURE__ */ jsxs("div", {
259
+ children: [/* @__PURE__ */ jsxs("div", {
280
260
  className: "flex min-w-0 flex-1 flex-wrap items-center gap-2",
281
- children: [
282
- canSearch ? /* @__PURE__ */ jsxs("div", {
283
- className: "relative w-full max-w-64",
284
- children: [/* @__PURE__ */ jsx(Search, {
285
- "aria-hidden": true,
286
- className: "pointer-events-none absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-[color:var(--ds-text-tertiary)]"
287
- }), /* @__PURE__ */ jsx(Input, {
288
- "data-slot": "data-table-search",
289
- type: "search",
290
- "aria-label": "Search",
291
- placeholder: searchPlaceholder,
292
- value: globalFilter,
293
- onChange: (e) => table.setGlobalFilter(e.target.value),
294
- className: "h-8 pl-8 text-[length:var(--ds-text-caption-lg)]"
295
- })]
296
- }) : null,
297
- facetedColumns.map((column) => /* @__PURE__ */ jsx(DataTableFacetedFilter, { column }, column.id)),
298
- isFiltered ? /* @__PURE__ */ jsxs(Button, {
299
- variant: "ghost",
300
- size: "sm",
301
- onClick: () => {
302
- table.resetColumnFilters();
303
- table.setGlobalFilter("");
304
- },
305
- children: ["Reset", /* @__PURE__ */ jsx(X, {})]
306
- }) : null
307
- ]
261
+ children: [selectedRows.length ? /* @__PURE__ */ jsxs("div", {
262
+ "data-slot": "data-table-bulk-actions",
263
+ className: "flex min-w-0 flex-wrap items-center gap-2",
264
+ children: [
265
+ /* @__PURE__ */ jsxs("span", {
266
+ className: "text-[length:var(--ds-text-caption-lg)] text-[color:var(--ds-muted-foreground)]",
267
+ children: [/* @__PURE__ */ jsx("span", {
268
+ className: "font-medium text-[color:var(--ds-foreground)]",
269
+ children: selectedRows.length
270
+ }), " selected"]
271
+ }),
272
+ bulkActions?.({
273
+ rows: selectedRows.map((r) => r.original),
274
+ table
275
+ }),
276
+ /* @__PURE__ */ jsxs(Button, {
277
+ variant: "ghost",
278
+ size: "sm",
279
+ onClick: () => table.resetRowSelection(),
280
+ children: ["Clear", /* @__PURE__ */ jsx(X, {})]
281
+ })
282
+ ]
283
+ }) : /* @__PURE__ */ jsxs("div", {
284
+ className: "flex min-w-0 flex-wrap items-center gap-2",
285
+ children: [
286
+ canSearch ? /* @__PURE__ */ jsxs("div", {
287
+ className: "relative w-64 max-w-full",
288
+ children: [/* @__PURE__ */ jsx(Search, {
289
+ "aria-hidden": true,
290
+ className: "pointer-events-none absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-[color:var(--ds-text-tertiary)]"
291
+ }), /* @__PURE__ */ jsx(Input, {
292
+ "data-slot": "data-table-search",
293
+ type: "search",
294
+ "aria-label": "Search",
295
+ placeholder: searchPlaceholder,
296
+ value: globalFilter,
297
+ onChange: (e) => table.setGlobalFilter(e.target.value),
298
+ className: "h-8 pl-8 text-[length:var(--ds-text-caption-lg)]"
299
+ })]
300
+ }) : null,
301
+ facetedColumns.map((column) => /* @__PURE__ */ jsx(DataTableFacetedFilter, { column }, column.id)),
302
+ isFiltered ? /* @__PURE__ */ jsxs(Button, {
303
+ variant: "ghost",
304
+ size: "sm",
305
+ onClick: () => {
306
+ table.resetColumnFilters();
307
+ table.setGlobalFilter("");
308
+ },
309
+ children: ["Reset", /* @__PURE__ */ jsx(X, {})]
310
+ }) : null
311
+ ]
312
+ }), refresh ? /* @__PURE__ */ jsx(DataTableRefresh, { ...refresh }) : null]
308
313
  }), /* @__PURE__ */ jsxs("div", {
309
314
  className: "flex items-center gap-2",
310
315
  children: [
@@ -318,6 +323,121 @@ function DataTableToolbar({ table, searchPlaceholder = "Search…", bulkActions,
318
323
  })]
319
324
  });
320
325
  }
326
+ /** A failed background refresh says so beside the stamp, then gets out of the way. */
327
+ const ERROR_VISIBLE_MS = 1e4;
328
+ /** Under a minute the stamp is relative; past it, the clock time it was taken. */
329
+ const RELATIVE_WINDOW_MS = 6e4;
330
+ /**
331
+ * Re-renders once, at a moment named in advance.
332
+ *
333
+ * The stamp has exactly two of them — the minute at which "just now" stops
334
+ * being true, and the ten seconds a failure is worth mentioning — and neither
335
+ * is a good reason to tick on a timer: a clock time, once shown, never changes
336
+ * again, so a periodic re-render would be one every fifteen seconds forever to
337
+ * paint the same eight characters.
338
+ */
339
+ function useDeadline(force, at, after) {
340
+ React.useEffect(() => {
341
+ if (at === null) return;
342
+ const left = after - (Date.now() - at);
343
+ if (left <= 0) return;
344
+ const id = setTimeout(force, left);
345
+ return () => clearTimeout(id);
346
+ }, [
347
+ force,
348
+ at,
349
+ after
350
+ ]);
351
+ }
352
+ function formatClock(at) {
353
+ return new Date(at).toLocaleTimeString(void 0, {
354
+ hour: "2-digit",
355
+ minute: "2-digit"
356
+ });
357
+ }
358
+ function pauseNote(pausedBy) {
359
+ switch (pausedBy) {
360
+ case "hidden": return "Paused while this tab is in the background";
361
+ case "editing": return "Paused while a cell is being edited";
362
+ case "selection": return "Paused while rows are selected";
363
+ case "mutation": return "Paused while a change is saving";
364
+ case "app": return "Paused";
365
+ default: return null;
366
+ }
367
+ }
368
+ /**
369
+ * "Updated 14:32", or "Updated just now" for the first minute. A server table
370
+ * is a photograph of a moment, and this is the only thing on screen that says
371
+ * which moment. Its own component so a re-render at the minute boundary costs
372
+ * eight words rather than the whole toolbar.
373
+ */
374
+ function DataTableUpdatedStamp({ lastUpdatedAt, lastErrorAt }) {
375
+ const [, force] = React.useReducer((n) => n + 1, 0);
376
+ useDeadline(force, lastUpdatedAt, RELATIVE_WINDOW_MS);
377
+ useDeadline(force, lastErrorAt, ERROR_VISIBLE_MS);
378
+ const now = Date.now();
379
+ if (lastErrorAt !== null && now - lastErrorAt < ERROR_VISIBLE_MS) return /* @__PURE__ */ jsx("span", {
380
+ "data-slot": "data-table-updated",
381
+ "data-failed": "",
382
+ className: "whitespace-nowrap text-[length:var(--ds-text-caption)] text-[color:var(--ds-destructive)]",
383
+ children: "Update failed"
384
+ });
385
+ if (lastUpdatedAt === null) return null;
386
+ return /* @__PURE__ */ jsx("span", {
387
+ "data-slot": "data-table-updated",
388
+ title: new Date(lastUpdatedAt).toLocaleString(),
389
+ className: "whitespace-nowrap text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]",
390
+ children: now - lastUpdatedAt < RELATIVE_WINDOW_MS ? "Updated just now" : `Updated ${formatClock(lastUpdatedAt)}`
391
+ });
392
+ }
393
+ /**
394
+ * The freshness controls: Refresh, the last-updated stamp, and — only where a
395
+ * table opted in — the auto-refresh switch with its live indicator. Mandatory
396
+ * on every server-backed table (P-39); `DataTable` renders it beside the
397
+ * search box on its own.
398
+ */
399
+ function DataTableRefresh({ onRefresh, isFetching, lastUpdatedAt = null, lastErrorAt = null, autoRefresh, className, ...props }) {
400
+ const note = autoRefresh ? pauseNote(autoRefresh.pausedBy) : null;
401
+ return /* @__PURE__ */ jsxs("div", {
402
+ "data-slot": "data-table-refresh",
403
+ className: cn("flex items-center gap-2", className),
404
+ ...props,
405
+ children: [
406
+ /* @__PURE__ */ jsx(Button, {
407
+ variant: "outline",
408
+ size: "icon-sm",
409
+ "aria-label": "Refresh",
410
+ onClick: onRefresh,
411
+ disabled: isFetching,
412
+ children: /* @__PURE__ */ jsx(RefreshCw, { className: cn(isFetching && "animate-spin") })
413
+ }),
414
+ /* @__PURE__ */ jsx(DataTableUpdatedStamp, {
415
+ lastUpdatedAt,
416
+ lastErrorAt
417
+ }),
418
+ autoRefresh ? /* @__PURE__ */ jsxs("span", {
419
+ "data-slot": "data-table-auto-refresh",
420
+ className: "flex items-center gap-1.5",
421
+ children: [/* @__PURE__ */ jsx(Switch, {
422
+ "aria-label": "Auto refresh",
423
+ checked: autoRefresh.enabled,
424
+ onCheckedChange: autoRefresh.setEnabled,
425
+ "data-backing-off": autoRefresh.backingOff ? "" : void 0
426
+ }), autoRefresh.active ? /* @__PURE__ */ jsx(StatusDot, {
427
+ "data-slot": "data-table-live",
428
+ tone: "success",
429
+ live: true,
430
+ label: "Live"
431
+ }) : /* @__PURE__ */ jsx("span", {
432
+ "data-slot": "data-table-auto-label",
433
+ title: note ?? void 0,
434
+ className: "whitespace-nowrap text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]",
435
+ children: autoRefresh.enabled ? note ? "Paused" : "Auto" : "Auto"
436
+ })]
437
+ }) : null
438
+ ]
439
+ });
440
+ }
321
441
  const DEFAULT_PAGE_SIZES = [
322
442
  10,
323
443
  25,
@@ -395,6 +515,6 @@ function DataTablePagination({ table, pageSizeOptions = DEFAULT_PAGE_SIZES, clas
395
515
  });
396
516
  }
397
517
  //#endregion
398
- export { DataTableColumnHeader, DataTableExportMenu, DataTableFacetedFilter, DataTablePagination, DataTableToolbar, DataTableViewOptions };
518
+ export { DataTableColumnHeader, DataTableExportMenu, DataTableFacetedFilter, DataTablePagination, DataTableRefresh, DataTableToolbar, DataTableViewOptions };
399
519
 
400
520
  //# sourceMappingURL=parts.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"parts.js","names":[],"sources":["../../../src/components/data-table/parts.tsx"],"sourcesContent":["\"use client\";\n\nimport type { RowData } from \"@tanstack/react-table\";\nimport {\n ArrowDown,\n ArrowUp,\n ChevronDown,\n ChevronLeft,\n ChevronRight,\n ChevronsLeft,\n ChevronsRight,\n ChevronsUpDown,\n Download,\n EyeOff,\n ListFilter,\n PinOff,\n Search,\n Settings2,\n X,\n} from \"lucide-react\";\nimport * as React from \"react\";\nimport { Button } from \"@/components/button\";\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"@/components/dropdown-menu\";\nimport { Input } from \"@/components/input\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@/components/select\";\nimport { cn } from \"@/lib/cn\";\nimport { columnLabel, downloadCsv, downloadExcel } from \"./export\";\nimport type { DataTableColumn, DataTableTable } from \"./types\";\n\n/* ------------------------------------------------------------------------ */\n/* Column header */\n/* ------------------------------------------------------------------------ */\n\nexport interface DataTableColumnHeaderProps<Row extends RowData>\n extends Omit<React.ComponentProps<\"div\">, \"title\"> {\n column: DataTableColumn<Row, any>;\n title: React.ReactNode;\n}\n\n/**\n * Header content for one column: the title as a sort toggle when the column\n * sorts, and a menu with sort, pin and hide when any of those apply. The\n * table renders it for every string `header` automatically; use it inside a\n * custom header function to keep the same affordances.\n */\nfunction DataTableColumnHeader<Row extends RowData>({\n column,\n title,\n className,\n ...props\n}: DataTableColumnHeaderProps<Row>) {\n const canSort = column.getCanSort();\n const canHide = column.getCanHide();\n const canPin = column.getCanPin();\n const sorted = column.getIsSorted();\n const align = column.columnDef.meta?.align ?? \"start\";\n\n if (!canSort && !canHide && !canPin) {\n return (\n <div data-slot=\"data-table-column-header\" className={cn(\"truncate\", className)} {...props}>\n {title}\n </div>\n );\n }\n\n const label = typeof title === \"string\" ? title : columnLabel(column);\n\n return (\n <div\n data-slot=\"data-table-column-header\"\n className={cn(\n \"group/header -mx-2 flex min-w-0 items-center\",\n // Leave room for the floating options trigger only while it shows;\n // anchored text does not move, it truncates if it must.\n align === \"end\" ? \"justify-end group-hover/head:pl-6\" : \"group-hover/head:pr-6\",\n align === \"center\" && \"justify-center\",\n className,\n )}\n {...props}\n >\n <button\n type=\"button\"\n data-slot=\"data-table-sort\"\n disabled={!canSort}\n onClick={canSort ? column.getToggleSortingHandler() : undefined}\n // Without a name of its own the button announces as just the column\n // title, which tells a screen-reader user the word \"Name\" and nothing\n // about there being a control or what it would do. The `aria-sort` on\n // the `<th>` (data-table.tsx) states the column's current order; this\n // states the action, and repeats the order because a button reached by\n // tabbing is not necessarily read with its header cell. Only when the\n // column sorts: on a column that only hides or pins, the button is\n // disabled and \"Sort by\" would be a lie.\n aria-label={\n canSort\n ? sorted\n ? `Sort by ${label}, sorted ${sorted === \"asc\" ? \"ascending\" : \"descending\"}`\n : `Sort by ${label}`\n : undefined\n }\n className={cn(\n \"inline-flex h-7 min-w-0 items-center gap-1 rounded-[var(--ds-radius-sm)] px-2 outline-none\",\n \"text-inherit transition-colors duration-[var(--ds-duration-fast)]\",\n canSort && \"hover:bg-[var(--ds-hover)] hover:text-[color:var(--ds-foreground)]\",\n \"focus-visible:ring-[3px] focus-visible:ring-[var(--ds-ring)]/40\",\n \"disabled:cursor-default\",\n )}\n >\n <span className=\"truncate\">{title}</span>\n {canSort ? (\n sorted === \"asc\" ? (\n <ArrowUp aria-hidden className=\"size-3.5 shrink-0 text-[color:var(--ds-primary)]\" />\n ) : sorted === \"desc\" ? (\n <ArrowDown aria-hidden className=\"size-3.5 shrink-0 text-[color:var(--ds-primary)]\" />\n ) : (\n // Out of flow until hover so narrow headers keep their full title.\n <ChevronsUpDown\n aria-hidden\n className=\"hidden size-3.5 shrink-0 opacity-60 group-hover/head:inline-block\"\n />\n )\n ) : null}\n </button>\n <DropdownMenu>\n <DropdownMenuTrigger\n aria-label={`${label} column options`}\n className={cn(\n // Floats over the cell edge so it costs the title no width.\n \"absolute top-1/2 z-[1] inline-flex size-6 -translate-y-1/2 items-center justify-center rounded-[var(--ds-radius-sm)] outline-none\",\n align === \"end\" ? \"left-1\" : \"right-1\",\n \"bg-[var(--ds-muted)] opacity-0 transition-opacity duration-[var(--ds-duration-fast)]\",\n \"group-hover/head:opacity-100 focus-visible:opacity-100 data-[popup-open]:opacity-100\",\n \"hover:bg-[var(--ds-hover)] focus-visible:ring-[3px] focus-visible:ring-[var(--ds-ring)]/40\",\n )}\n >\n <ChevronDown className=\"size-3.5\" />\n </DropdownMenuTrigger>\n <DropdownMenuContent align={align === \"end\" ? \"end\" : \"start\"}>\n {canSort ? (\n <DropdownMenuGroup>\n <DropdownMenuItem onClick={() => column.toggleSorting(false)}>\n <ArrowUp /> Sort ascending\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => column.toggleSorting(true)}>\n <ArrowDown /> Sort descending\n </DropdownMenuItem>\n {sorted ? (\n <DropdownMenuItem onClick={() => column.clearSorting()}>\n <ChevronsUpDown /> Clear sort\n </DropdownMenuItem>\n ) : null}\n </DropdownMenuGroup>\n ) : null}\n {canSort && (canPin || canHide) ? <DropdownMenuSeparator /> : null}\n {canPin ? (\n <DropdownMenuGroup>\n <DropdownMenuItem onClick={() => column.pin(\"start\")}>\n <ChevronsLeft /> Pin left\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => column.pin(\"end\")}>\n <ChevronsRight /> Pin right\n </DropdownMenuItem>\n {column.getIsPinned() ? (\n <DropdownMenuItem onClick={() => column.pin(false)}>\n <PinOff /> Unpin\n </DropdownMenuItem>\n ) : null}\n </DropdownMenuGroup>\n ) : null}\n {canPin && canHide ? <DropdownMenuSeparator /> : null}\n {canHide ? (\n <DropdownMenuItem onClick={() => column.toggleVisibility(false)}>\n <EyeOff /> Hide column\n </DropdownMenuItem>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n );\n}\n\n/* ------------------------------------------------------------------------ */\n/* Faceted filter */\n/* ------------------------------------------------------------------------ */\n\nexport interface DataTableFacetedFilterOption {\n value: string | number | boolean;\n label?: string;\n icon?: React.ComponentType<{ className?: string }>;\n}\n\nexport interface DataTableFacetedFilterProps<Row extends RowData> {\n column: DataTableColumn<Row, any>;\n title?: React.ReactNode;\n /** Fixed options; defaults to the column's distinct values with counts (client source only). */\n options?: DataTableFacetedFilterOption[];\n className?: string;\n}\n\n/**\n * A checkbox list of a column's values. The filter value is the array of\n * picked values; the column's `filterFn` (`oneOf` by default for\n * `meta.filter: \"faceted\"`) decides what that means, and a server source\n * receives it verbatim in `TableQuery.filters`.\n */\nfunction DataTableFacetedFilter<Row extends RowData>({\n column,\n title,\n options,\n className,\n}: DataTableFacetedFilterProps<Row>) {\n const raw = column.getFilterValue();\n const selected = new Set<unknown>(Array.isArray(raw) ? raw : raw == null ? [] : [raw]);\n const facets = options ? null : column.getFacetedUniqueValues();\n const items: DataTableFacetedFilterOption[] =\n options ??\n column.columnDef.meta?.filterOptions ??\n Array.from(facets?.keys() ?? [])\n .filter((v): v is string | number | boolean => v != null && typeof v !== \"object\")\n .sort((a, b) => String(a).localeCompare(String(b)))\n .map((value) => ({ value }));\n const label = title ?? columnLabel(column);\n\n const toggle = (value: unknown, checked: boolean) => {\n const next = new Set(selected);\n if (checked) next.add(value);\n else next.delete(value);\n column.setFilterValue(next.size ? Array.from(next) : undefined);\n };\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger\n data-slot=\"data-table-faceted-filter\"\n render={<Button variant=\"outline\" size=\"sm\" className={cn(\"border-dashed\", className)} />}\n >\n <ListFilter />\n {label}\n {selected.size ? (\n <span\n data-slot=\"data-table-filter-count\"\n className={cn(\n \"ml-1 inline-flex h-5 min-w-5 items-center justify-center rounded-[var(--ds-radius-sm)] px-1.5\",\n \"bg-[var(--ds-primary-soft)] text-[length:var(--ds-text-caption-sm)] font-medium text-[color:var(--ds-primary-soft-foreground)]\",\n )}\n >\n {selected.size}\n </span>\n ) : null}\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"max-h-80 min-w-48 overflow-y-auto\">\n <DropdownMenuGroup>\n <DropdownMenuLabel>{label}</DropdownMenuLabel>\n {items.length === 0 ? (\n <div className=\"px-2 py-1.5 text-[length:var(--ds-text-caption-lg)] text-[color:var(--ds-muted-foreground)]\">\n No values\n </div>\n ) : null}\n {items.map((item) => {\n const Icon = item.icon;\n const count = facets?.get(item.value);\n return (\n <DropdownMenuCheckboxItem\n key={String(item.value)}\n closeOnClick={false}\n checked={selected.has(item.value)}\n onCheckedChange={(checked) => toggle(item.value, checked)}\n >\n {Icon ? <Icon className=\"size-4 text-[color:var(--ds-muted-foreground)]\" /> : null}\n <span className=\"flex-1 truncate\">{item.label ?? String(item.value)}</span>\n {count !== undefined ? (\n <span className=\"ml-auto font-[family-name:var(--ds-font-mono)] text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]\">\n {count}\n </span>\n ) : null}\n </DropdownMenuCheckboxItem>\n );\n })}\n </DropdownMenuGroup>\n {selected.size ? (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem onClick={() => column.setFilterValue(undefined)} className=\"justify-center\">\n Clear filter\n </DropdownMenuItem>\n </>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\n/* ------------------------------------------------------------------------ */\n/* View options */\n/* ------------------------------------------------------------------------ */\n\nexport interface DataTableViewOptionsProps<Row extends RowData> {\n table: DataTableTable<Row>;\n className?: string;\n}\n\n/** Column visibility menu. */\nfunction DataTableViewOptions<Row extends RowData>({ table, className }: DataTableViewOptionsProps<Row>) {\n const columns = table.getAllLeafColumns().filter((c) => c.getCanHide());\n if (columns.length === 0) return null;\n return (\n <DropdownMenu>\n <DropdownMenuTrigger\n data-slot=\"data-table-view-options\"\n aria-label=\"Toggle columns\"\n render={<Button variant=\"outline\" size=\"sm\" className={className} />}\n >\n <Settings2 />\n View\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"max-h-80 min-w-40 overflow-y-auto\">\n <DropdownMenuGroup>\n <DropdownMenuLabel>Toggle columns</DropdownMenuLabel>\n {columns.map((column) => (\n <DropdownMenuCheckboxItem\n key={column.id}\n closeOnClick={false}\n checked={column.getIsVisible()}\n onCheckedChange={(checked) => column.toggleVisibility(checked)}\n >\n {columnLabel(column)}\n </DropdownMenuCheckboxItem>\n ))}\n </DropdownMenuGroup>\n {!table.getIsAllColumnsVisible() ? (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem onClick={() => table.toggleAllColumnsVisible(true)}>Show all</DropdownMenuItem>\n </>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\n/* ------------------------------------------------------------------------ */\n/* Export menu */\n/* ------------------------------------------------------------------------ */\n\nexport interface DataTableExportMenuProps<Row extends RowData> {\n table: DataTableTable<Row>;\n fileName?: string;\n className?: string;\n}\n\nfunction DataTableExportMenu<Row extends RowData>({ table, fileName, className }: DataTableExportMenuProps<Row>) {\n const selected = table.getSelectedRowModel().rows;\n return (\n <DropdownMenu>\n <DropdownMenuTrigger\n data-slot=\"data-table-export\"\n aria-label=\"Export\"\n render={<Button variant=\"outline\" size=\"sm\" className={className} />}\n >\n <Download />\n Export\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onClick={() => downloadCsv(table, fileName)}>CSV</DropdownMenuItem>\n <DropdownMenuItem onClick={() => void downloadExcel(table, fileName)}>Excel</DropdownMenuItem>\n {selected.length ? (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem onClick={() => downloadCsv(table, fileName, selected)}>\n CSV of {selected.length} selected\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => void downloadExcel(table, fileName, selected)}>\n Excel of {selected.length} selected\n </DropdownMenuItem>\n </>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\n/* ------------------------------------------------------------------------ */\n/* Toolbar */\n/* ------------------------------------------------------------------------ */\n\nexport interface DataTableToolbarProps<Row extends RowData>\n extends Omit<React.ComponentProps<\"div\">, \"children\"> {\n table: DataTableTable<Row>;\n /** Placeholder for the search box. @default \"Search…\" */\n searchPlaceholder?: string;\n /** Rendered while rows are selected, in place of the filters. */\n bulkActions?: (ctx: { rows: Row[]; table: DataTableTable<Row> }) => React.ReactNode;\n /** Extra controls on the right, before the view and export menus. */\n children?: React.ReactNode;\n /** @default true */\n enableExport?: boolean;\n exportFileName?: string;\n}\n\n/**\n * Search box, one faceted filter per `meta.filter: \"faceted\"` column, a reset\n * when anything is filtered, and the view / export menus. While rows are\n * selected the left side becomes the selection summary and `bulkActions`.\n */\nfunction DataTableToolbar<Row extends RowData>({\n table,\n searchPlaceholder = \"Search…\",\n bulkActions,\n children,\n enableExport = true,\n exportFileName,\n className,\n ...props\n}: DataTableToolbarProps<Row>) {\n const globalFilter = (table.state.globalFilter as string | undefined) ?? \"\";\n const canSearch = table.options.enableGlobalFilter !== false;\n const facetedColumns = table\n .getAllLeafColumns()\n .filter((c) => c.columnDef.meta?.filter === \"faceted\" && c.getCanFilter());\n const isFiltered = table.state.columnFilters.length > 0 || globalFilter !== \"\";\n const selectedRows = table.options.enableRowSelection ? table.getSelectedRowModel().rows : [];\n\n return (\n <div\n data-slot=\"data-table-toolbar\"\n className={cn(\"flex flex-wrap items-center gap-2\", className)}\n {...props}\n >\n {selectedRows.length ? (\n <div data-slot=\"data-table-bulk-actions\" className=\"flex min-w-0 flex-1 flex-wrap items-center gap-2\">\n <span className=\"text-[length:var(--ds-text-caption-lg)] text-[color:var(--ds-muted-foreground)]\">\n <span className=\"font-medium text-[color:var(--ds-foreground)]\">{selectedRows.length}</span> selected\n </span>\n {bulkActions?.({ rows: selectedRows.map((r) => r.original), table })}\n <Button variant=\"ghost\" size=\"sm\" onClick={() => table.resetRowSelection()}>\n Clear\n <X />\n </Button>\n </div>\n ) : (\n <div className=\"flex min-w-0 flex-1 flex-wrap items-center gap-2\">\n {canSearch ? (\n <div className=\"relative w-full max-w-64\">\n <Search\n aria-hidden\n className=\"pointer-events-none absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-[color:var(--ds-text-tertiary)]\"\n />\n <Input\n data-slot=\"data-table-search\"\n type=\"search\"\n aria-label=\"Search\"\n placeholder={searchPlaceholder}\n value={globalFilter}\n onChange={(e) => table.setGlobalFilter(e.target.value)}\n className=\"h-8 pl-8 text-[length:var(--ds-text-caption-lg)]\"\n />\n </div>\n ) : null}\n {facetedColumns.map((column) => (\n <DataTableFacetedFilter key={column.id} column={column} />\n ))}\n {isFiltered ? (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => {\n table.resetColumnFilters();\n table.setGlobalFilter(\"\");\n }}\n >\n Reset\n <X />\n </Button>\n ) : null}\n </div>\n )}\n <div className=\"flex items-center gap-2\">\n {children}\n <DataTableViewOptions table={table} />\n {enableExport ? <DataTableExportMenu table={table} fileName={exportFileName} /> : null}\n </div>\n </div>\n );\n}\n\n/* ------------------------------------------------------------------------ */\n/* Pagination */\n/* ------------------------------------------------------------------------ */\n\nexport interface DataTablePaginationProps<Row extends RowData> extends React.ComponentProps<\"div\"> {\n table: DataTableTable<Row>;\n /** @default [10, 25, 50, 100] */\n pageSizeOptions?: number[];\n}\n\nconst DEFAULT_PAGE_SIZES = [10, 25, 50, 100];\n\nfunction DataTablePagination<Row extends RowData>({\n table,\n pageSizeOptions = DEFAULT_PAGE_SIZES,\n className,\n ...props\n}: DataTablePaginationProps<Row>) {\n const { pageIndex, pageSize } = table.state.pagination;\n const total = table.getRowCount();\n const pageCount = table.getPageCount();\n const from = total === 0 ? 0 : pageIndex * pageSize + 1;\n const to = Math.min(total, (pageIndex + 1) * pageSize);\n const sizes = pageSizeOptions.includes(pageSize)\n ? pageSizeOptions\n : [...pageSizeOptions, pageSize].sort((a, b) => a - b);\n const sizeItems = sizes.map((s) => ({ value: String(s), label: String(s) }));\n\n const navButton = (label: string, icon: React.ReactNode, onClick: () => void, disabled: boolean) => (\n <Button variant=\"outline\" size=\"icon-sm\" aria-label={label} onClick={onClick} disabled={disabled}>\n {icon}\n </Button>\n );\n\n return (\n <div\n data-slot=\"data-table-pagination\"\n className={cn(\n \"flex flex-wrap items-center justify-between gap-3\",\n \"text-[length:var(--ds-text-caption-lg)] text-[color:var(--ds-muted-foreground)]\",\n className,\n )}\n {...props}\n >\n <div className=\"flex items-center gap-2\">\n <span>Rows per page</span>\n <Select\n items={sizeItems}\n value={String(pageSize)}\n onValueChange={(value) => table.setPageSize(Number(value))}\n >\n <SelectTrigger size=\"sm\" aria-label=\"Rows per page\" className=\"min-w-0 w-[4.5rem]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {sizeItems.map((item) => (\n <SelectItem key={item.value} value={item.value}>\n {item.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"flex items-center gap-4\">\n <span data-slot=\"data-table-range\" aria-live=\"polite\">\n {from}–{to} of {total}\n </span>\n <div className=\"flex items-center gap-1\">\n {navButton(\"First page\", <ChevronsLeft />, () => table.firstPage(), !table.getCanPreviousPage())}\n {navButton(\"Previous page\", <ChevronLeft />, () => table.previousPage(), !table.getCanPreviousPage())}\n <span className=\"px-1 tabular-nums\">\n {pageCount === 0 ? 0 : pageIndex + 1} / {pageCount}\n </span>\n {navButton(\"Next page\", <ChevronRight />, () => table.nextPage(), !table.getCanNextPage())}\n {navButton(\"Last page\", <ChevronsRight />, () => table.lastPage(), !table.getCanNextPage())}\n </div>\n </div>\n </div>\n );\n}\n\nexport {\n DataTableColumnHeader,\n DataTableExportMenu,\n DataTableFacetedFilter,\n DataTablePagination,\n DataTableToolbar,\n DataTableViewOptions,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"parts.js","names":[],"sources":["../../../src/components/data-table/parts.tsx"],"sourcesContent":["\"use client\";\n\nimport type { RowData } from \"@tanstack/react-table\";\nimport {\n ArrowDown,\n ArrowUp,\n ChevronDown,\n ChevronLeft,\n ChevronRight,\n ChevronsLeft,\n ChevronsRight,\n ChevronsUpDown,\n Download,\n EyeOff,\n ListFilter,\n PinOff,\n RefreshCw,\n Search,\n Settings2,\n X,\n} from \"lucide-react\";\nimport * as React from \"react\";\nimport { Button } from \"@/components/button\";\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"@/components/dropdown-menu\";\nimport { Input } from \"@/components/input\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@/components/select\";\nimport { StatusDot } from \"@/components/status-dot\";\nimport { Switch } from \"@/components/switch\";\nimport { cn } from \"@/lib/cn\";\nimport { columnLabel, downloadCsv, downloadExcel } from \"./export\";\nimport type { DataTableAutoRefreshState, DataTableColumn, DataTableTable } from \"./types\";\n\n/* ------------------------------------------------------------------------ */\n/* Column header */\n/* ------------------------------------------------------------------------ */\n\nexport interface DataTableColumnHeaderProps<Row extends RowData>\n extends Omit<React.ComponentProps<\"div\">, \"title\"> {\n column: DataTableColumn<Row, any>;\n title: React.ReactNode;\n}\n\n/**\n * Header content for one column: the title as a sort toggle when the column\n * sorts, and a menu with sort, pin and hide when any of those apply. The\n * table renders it for every string `header` automatically; use it inside a\n * custom header function to keep the same affordances.\n */\nfunction DataTableColumnHeader<Row extends RowData>({\n column,\n title,\n className,\n ...props\n}: DataTableColumnHeaderProps<Row>) {\n const canSort = column.getCanSort();\n const canHide = column.getCanHide();\n const canPin = column.getCanPin();\n const sorted = column.getIsSorted();\n const align = column.columnDef.meta?.align ?? \"start\";\n\n if (!canSort && !canHide && !canPin) {\n return (\n <div data-slot=\"data-table-column-header\" className={cn(\"truncate\", className)} {...props}>\n {title}\n </div>\n );\n }\n\n const label = typeof title === \"string\" ? title : columnLabel(column);\n\n return (\n <div\n data-slot=\"data-table-column-header\"\n className={cn(\n \"group/header -mx-2 flex min-w-0 items-center\",\n // Leave room for the floating options trigger only while it shows;\n // anchored text does not move, it truncates if it must.\n align === \"end\" ? \"justify-end group-hover/head:pl-6\" : \"group-hover/head:pr-6\",\n align === \"center\" && \"justify-center\",\n className,\n )}\n {...props}\n >\n <button\n type=\"button\"\n data-slot=\"data-table-sort\"\n disabled={!canSort}\n onClick={canSort ? column.getToggleSortingHandler() : undefined}\n // Without a name of its own the button announces as just the column\n // title, which tells a screen-reader user the word \"Name\" and nothing\n // about there being a control or what it would do. The `aria-sort` on\n // the `<th>` (data-table.tsx) states the column's current order; this\n // states the action, and repeats the order because a button reached by\n // tabbing is not necessarily read with its header cell. Only when the\n // column sorts: on a column that only hides or pins, the button is\n // disabled and \"Sort by\" would be a lie.\n aria-label={\n canSort\n ? sorted\n ? `Sort by ${label}, sorted ${sorted === \"asc\" ? \"ascending\" : \"descending\"}`\n : `Sort by ${label}`\n : undefined\n }\n className={cn(\n \"inline-flex h-7 min-w-0 items-center gap-1 rounded-[var(--ds-radius-sm)] px-2 outline-none\",\n \"text-inherit transition-colors duration-[var(--ds-duration-fast)]\",\n canSort && \"hover:bg-[var(--ds-hover)] hover:text-[color:var(--ds-foreground)]\",\n \"focus-visible:ring-[3px] focus-visible:ring-[var(--ds-ring)]/40\",\n \"disabled:cursor-default\",\n )}\n >\n <span className=\"truncate\">{title}</span>\n {canSort ? (\n sorted === \"asc\" ? (\n <ArrowUp aria-hidden className=\"size-3.5 shrink-0 text-[color:var(--ds-primary)]\" />\n ) : sorted === \"desc\" ? (\n <ArrowDown aria-hidden className=\"size-3.5 shrink-0 text-[color:var(--ds-primary)]\" />\n ) : (\n // Out of flow until hover so narrow headers keep their full title.\n <ChevronsUpDown\n aria-hidden\n className=\"hidden size-3.5 shrink-0 opacity-60 group-hover/head:inline-block\"\n />\n )\n ) : null}\n </button>\n <DropdownMenu>\n <DropdownMenuTrigger\n aria-label={`${label} column options`}\n className={cn(\n // Floats over the cell edge so it costs the title no width.\n \"absolute top-1/2 z-[1] inline-flex size-6 -translate-y-1/2 items-center justify-center rounded-[var(--ds-radius-sm)] outline-none\",\n align === \"end\" ? \"left-1\" : \"right-1\",\n \"bg-[var(--ds-muted)] opacity-0 transition-opacity duration-[var(--ds-duration-fast)]\",\n \"group-hover/head:opacity-100 focus-visible:opacity-100 data-[popup-open]:opacity-100\",\n \"hover:bg-[var(--ds-hover)] focus-visible:ring-[3px] focus-visible:ring-[var(--ds-ring)]/40\",\n )}\n >\n <ChevronDown className=\"size-3.5\" />\n </DropdownMenuTrigger>\n <DropdownMenuContent align={align === \"end\" ? \"end\" : \"start\"}>\n {canSort ? (\n <DropdownMenuGroup>\n <DropdownMenuItem onClick={() => column.toggleSorting(false)}>\n <ArrowUp /> Sort ascending\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => column.toggleSorting(true)}>\n <ArrowDown /> Sort descending\n </DropdownMenuItem>\n {sorted ? (\n <DropdownMenuItem onClick={() => column.clearSorting()}>\n <ChevronsUpDown /> Clear sort\n </DropdownMenuItem>\n ) : null}\n </DropdownMenuGroup>\n ) : null}\n {canSort && (canPin || canHide) ? <DropdownMenuSeparator /> : null}\n {canPin ? (\n <DropdownMenuGroup>\n <DropdownMenuItem onClick={() => column.pin(\"start\")}>\n <ChevronsLeft /> Pin left\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => column.pin(\"end\")}>\n <ChevronsRight /> Pin right\n </DropdownMenuItem>\n {column.getIsPinned() ? (\n <DropdownMenuItem onClick={() => column.pin(false)}>\n <PinOff /> Unpin\n </DropdownMenuItem>\n ) : null}\n </DropdownMenuGroup>\n ) : null}\n {canPin && canHide ? <DropdownMenuSeparator /> : null}\n {canHide ? (\n <DropdownMenuItem onClick={() => column.toggleVisibility(false)}>\n <EyeOff /> Hide column\n </DropdownMenuItem>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n );\n}\n\n/* ------------------------------------------------------------------------ */\n/* Faceted filter */\n/* ------------------------------------------------------------------------ */\n\nexport interface DataTableFacetedFilterOption {\n value: string | number | boolean;\n label?: string;\n icon?: React.ComponentType<{ className?: string }>;\n}\n\nexport interface DataTableFacetedFilterProps<Row extends RowData> {\n column: DataTableColumn<Row, any>;\n title?: React.ReactNode;\n /** Fixed options; defaults to the column's distinct values with counts (client source only). */\n options?: DataTableFacetedFilterOption[];\n className?: string;\n}\n\n/**\n * A checkbox list of a column's values. The filter value is the array of\n * picked values; the column's `filterFn` (`oneOf` by default for\n * `meta.filter: \"faceted\"`) decides what that means, and a server source\n * receives it verbatim in `TableQuery.filters`.\n */\nfunction DataTableFacetedFilter<Row extends RowData>({\n column,\n title,\n options,\n className,\n}: DataTableFacetedFilterProps<Row>) {\n const raw = column.getFilterValue();\n const selected = new Set<unknown>(Array.isArray(raw) ? raw : raw == null ? [] : [raw]);\n const facets = options ? null : column.getFacetedUniqueValues();\n const items: DataTableFacetedFilterOption[] =\n options ??\n column.columnDef.meta?.filterOptions ??\n Array.from(facets?.keys() ?? [])\n .filter((v): v is string | number | boolean => v != null && typeof v !== \"object\")\n .sort((a, b) => String(a).localeCompare(String(b)))\n .map((value) => ({ value }));\n const label = title ?? columnLabel(column);\n\n const toggle = (value: unknown, checked: boolean) => {\n const next = new Set(selected);\n if (checked) next.add(value);\n else next.delete(value);\n column.setFilterValue(next.size ? Array.from(next) : undefined);\n };\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger\n data-slot=\"data-table-faceted-filter\"\n render={<Button variant=\"outline\" size=\"sm\" className={cn(\"border-dashed\", className)} />}\n >\n <ListFilter />\n {label}\n {selected.size ? (\n <span\n data-slot=\"data-table-filter-count\"\n className={cn(\n \"ml-1 inline-flex h-5 min-w-5 items-center justify-center rounded-[var(--ds-radius-sm)] px-1.5\",\n \"bg-[var(--ds-primary-soft)] text-[length:var(--ds-text-caption-sm)] font-medium text-[color:var(--ds-primary-soft-foreground)]\",\n )}\n >\n {selected.size}\n </span>\n ) : null}\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"max-h-80 min-w-48 overflow-y-auto\">\n <DropdownMenuGroup>\n <DropdownMenuLabel>{label}</DropdownMenuLabel>\n {items.length === 0 ? (\n <div className=\"px-2 py-1.5 text-[length:var(--ds-text-caption-lg)] text-[color:var(--ds-muted-foreground)]\">\n No values\n </div>\n ) : null}\n {items.map((item) => {\n const Icon = item.icon;\n const count = facets?.get(item.value);\n return (\n <DropdownMenuCheckboxItem\n key={String(item.value)}\n closeOnClick={false}\n checked={selected.has(item.value)}\n onCheckedChange={(checked) => toggle(item.value, checked)}\n >\n {Icon ? <Icon className=\"size-4 text-[color:var(--ds-muted-foreground)]\" /> : null}\n <span className=\"flex-1 truncate\">{item.label ?? String(item.value)}</span>\n {count !== undefined ? (\n <span className=\"ml-auto font-[family-name:var(--ds-font-mono)] text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]\">\n {count}\n </span>\n ) : null}\n </DropdownMenuCheckboxItem>\n );\n })}\n </DropdownMenuGroup>\n {selected.size ? (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem onClick={() => column.setFilterValue(undefined)} className=\"justify-center\">\n Clear filter\n </DropdownMenuItem>\n </>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\n/* ------------------------------------------------------------------------ */\n/* View options */\n/* ------------------------------------------------------------------------ */\n\nexport interface DataTableViewOptionsProps<Row extends RowData> {\n table: DataTableTable<Row>;\n className?: string;\n}\n\n/** Column visibility menu. */\nfunction DataTableViewOptions<Row extends RowData>({ table, className }: DataTableViewOptionsProps<Row>) {\n const columns = table.getAllLeafColumns().filter((c) => c.getCanHide());\n if (columns.length === 0) return null;\n return (\n <DropdownMenu>\n <DropdownMenuTrigger\n data-slot=\"data-table-view-options\"\n aria-label=\"Toggle columns\"\n render={<Button variant=\"outline\" size=\"sm\" className={className} />}\n >\n <Settings2 />\n View\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"max-h-80 min-w-40 overflow-y-auto\">\n <DropdownMenuGroup>\n <DropdownMenuLabel>Toggle columns</DropdownMenuLabel>\n {columns.map((column) => (\n <DropdownMenuCheckboxItem\n key={column.id}\n closeOnClick={false}\n checked={column.getIsVisible()}\n onCheckedChange={(checked) => column.toggleVisibility(checked)}\n >\n {columnLabel(column)}\n </DropdownMenuCheckboxItem>\n ))}\n </DropdownMenuGroup>\n {!table.getIsAllColumnsVisible() ? (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem onClick={() => table.toggleAllColumnsVisible(true)}>Show all</DropdownMenuItem>\n </>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\n/* ------------------------------------------------------------------------ */\n/* Export menu */\n/* ------------------------------------------------------------------------ */\n\nexport interface DataTableExportMenuProps<Row extends RowData> {\n table: DataTableTable<Row>;\n fileName?: string;\n className?: string;\n}\n\nfunction DataTableExportMenu<Row extends RowData>({ table, fileName, className }: DataTableExportMenuProps<Row>) {\n const selected = table.getSelectedRowModel().rows;\n return (\n <DropdownMenu>\n <DropdownMenuTrigger\n data-slot=\"data-table-export\"\n aria-label=\"Export\"\n render={<Button variant=\"outline\" size=\"sm\" className={className} />}\n >\n <Download />\n Export\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onClick={() => downloadCsv(table, fileName)}>CSV</DropdownMenuItem>\n <DropdownMenuItem onClick={() => void downloadExcel(table, fileName)}>Excel</DropdownMenuItem>\n {selected.length ? (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem onClick={() => downloadCsv(table, fileName, selected)}>\n CSV of {selected.length} selected\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => void downloadExcel(table, fileName, selected)}>\n Excel of {selected.length} selected\n </DropdownMenuItem>\n </>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\n/* ------------------------------------------------------------------------ */\n/* Toolbar */\n/* ------------------------------------------------------------------------ */\n\nexport interface DataTableToolbarProps<Row extends RowData>\n extends Omit<React.ComponentProps<\"div\">, \"children\"> {\n table: DataTableTable<Row>;\n /** Placeholder for the search box. @default \"Search…\" */\n searchPlaceholder?: string;\n /** Rendered while rows are selected, in place of the filters. */\n bulkActions?: (ctx: { rows: Row[]; table: DataTableTable<Row> }) => React.ReactNode;\n /** Extra controls on the right, before the view and export menus. */\n children?: React.ReactNode;\n /** @default true */\n enableExport?: boolean;\n exportFileName?: string;\n /**\n * Freshness controls beside the search box. `DataTable` fills this in for\n * every server source; a hand-built toolbar must pass it too (P-39).\n */\n refresh?: DataTableRefreshProps;\n}\n\n/**\n * Search box, one faceted filter per `meta.filter: \"faceted\"` column, a reset\n * when anything is filtered, and the view / export menus. While rows are\n * selected the left side becomes the selection summary and `bulkActions`.\n */\nfunction DataTableToolbar<Row extends RowData>({\n table,\n searchPlaceholder = \"Search…\",\n bulkActions,\n children,\n enableExport = true,\n exportFileName,\n refresh,\n className,\n ...props\n}: DataTableToolbarProps<Row>) {\n const globalFilter = (table.state.globalFilter as string | undefined) ?? \"\";\n const canSearch = table.options.enableGlobalFilter !== false;\n const facetedColumns = table\n .getAllLeafColumns()\n .filter((c) => c.columnDef.meta?.filter === \"faceted\" && c.getCanFilter());\n const isFiltered = table.state.columnFilters.length > 0 || globalFilter !== \"\";\n const selectedRows = table.options.enableRowSelection ? table.getSelectedRowModel().rows : [];\n\n return (\n <div\n data-slot=\"data-table-toolbar\"\n className={cn(\"flex flex-wrap items-center gap-2\", className)}\n {...props}\n >\n <div className=\"flex min-w-0 flex-1 flex-wrap items-center gap-2\">\n {selectedRows.length ? (\n <div data-slot=\"data-table-bulk-actions\" className=\"flex min-w-0 flex-wrap items-center gap-2\">\n <span className=\"text-[length:var(--ds-text-caption-lg)] text-[color:var(--ds-muted-foreground)]\">\n <span className=\"font-medium text-[color:var(--ds-foreground)]\">{selectedRows.length}</span> selected\n </span>\n {bulkActions?.({ rows: selectedRows.map((r) => r.original), table })}\n <Button variant=\"ghost\" size=\"sm\" onClick={() => table.resetRowSelection()}>\n Clear\n <X />\n </Button>\n </div>\n ) : (\n <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n {canSearch ? (\n <div className=\"relative w-64 max-w-full\">\n <Search\n aria-hidden\n className=\"pointer-events-none absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-[color:var(--ds-text-tertiary)]\"\n />\n <Input\n data-slot=\"data-table-search\"\n type=\"search\"\n aria-label=\"Search\"\n placeholder={searchPlaceholder}\n value={globalFilter}\n onChange={(e) => table.setGlobalFilter(e.target.value)}\n className=\"h-8 pl-8 text-[length:var(--ds-text-caption-lg)]\"\n />\n </div>\n ) : null}\n {facetedColumns.map((column) => (\n <DataTableFacetedFilter key={column.id} column={column} />\n ))}\n {isFiltered ? (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => {\n table.resetColumnFilters();\n table.setGlobalFilter(\"\");\n }}\n >\n Reset\n <X />\n </Button>\n ) : null}\n </div>\n )}\n {/* Beside the search box, and still there while a selection is up: the\n stamp answers \"how old is this?\", which a selection does not change. */}\n {refresh ? <DataTableRefresh {...refresh} /> : null}\n </div>\n <div className=\"flex items-center gap-2\">\n {children}\n <DataTableViewOptions table={table} />\n {enableExport ? <DataTableExportMenu table={table} fileName={exportFileName} /> : null}\n </div>\n </div>\n );\n}\n\n/* ------------------------------------------------------------------------ */\n/* Refresh + freshness stamp */\n/* ------------------------------------------------------------------------ */\n\n/** A failed background refresh says so beside the stamp, then gets out of the way. */\nconst ERROR_VISIBLE_MS = 10_000;\n/** Under a minute the stamp is relative; past it, the clock time it was taken. */\nconst RELATIVE_WINDOW_MS = 60_000;\n\n/**\n * Re-renders once, at a moment named in advance.\n *\n * The stamp has exactly two of them — the minute at which \"just now\" stops\n * being true, and the ten seconds a failure is worth mentioning — and neither\n * is a good reason to tick on a timer: a clock time, once shown, never changes\n * again, so a periodic re-render would be one every fifteen seconds forever to\n * paint the same eight characters.\n */\nfunction useDeadline(force: () => void, at: number | null, after: number): void {\n React.useEffect(() => {\n if (at === null) return;\n const left = after - (Date.now() - at);\n if (left <= 0) return;\n const id = setTimeout(force, left);\n return () => clearTimeout(id);\n }, [force, at, after]);\n}\n\nfunction formatClock(at: number): string {\n return new Date(at).toLocaleTimeString(undefined, { hour: \"2-digit\", minute: \"2-digit\" });\n}\n\nfunction pauseNote(pausedBy: DataTableAutoRefreshState[\"pausedBy\"]): string | null {\n switch (pausedBy) {\n case \"hidden\":\n return \"Paused while this tab is in the background\";\n case \"editing\":\n return \"Paused while a cell is being edited\";\n case \"selection\":\n return \"Paused while rows are selected\";\n case \"mutation\":\n return \"Paused while a change is saving\";\n case \"app\":\n return \"Paused\";\n default:\n return null;\n }\n}\n\ninterface StampProps {\n lastUpdatedAt: number | null;\n lastErrorAt: number | null;\n}\n\n/**\n * \"Updated 14:32\", or \"Updated just now\" for the first minute. A server table\n * is a photograph of a moment, and this is the only thing on screen that says\n * which moment. Its own component so a re-render at the minute boundary costs\n * eight words rather than the whole toolbar.\n */\nfunction DataTableUpdatedStamp({ lastUpdatedAt, lastErrorAt }: StampProps) {\n const [, force] = React.useReducer((n: number) => n + 1, 0);\n useDeadline(force, lastUpdatedAt, RELATIVE_WINDOW_MS);\n useDeadline(force, lastErrorAt, ERROR_VISIBLE_MS);\n const now = Date.now();\n\n if (lastErrorAt !== null && now - lastErrorAt < ERROR_VISIBLE_MS) {\n return (\n <span\n data-slot=\"data-table-updated\"\n data-failed=\"\"\n className=\"whitespace-nowrap text-[length:var(--ds-text-caption)] text-[color:var(--ds-destructive)]\"\n >\n Update failed\n </span>\n );\n }\n if (lastUpdatedAt === null) return null;\n return (\n <span\n data-slot=\"data-table-updated\"\n title={new Date(lastUpdatedAt).toLocaleString()}\n className=\"whitespace-nowrap text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]\"\n >\n {now - lastUpdatedAt < RELATIVE_WINDOW_MS\n ? \"Updated just now\"\n : `Updated ${formatClock(lastUpdatedAt)}`}\n </span>\n );\n}\n\nexport interface DataTableRefreshProps extends Omit<React.ComponentProps<\"div\">, \"children\"> {\n /** Refetch the page on screen. The same query the interval drives. */\n onRefresh: () => void;\n /** Spins the icon and holds the button while a request is in flight. */\n isFetching?: boolean;\n /** `Date.now()` of the last successful response. */\n lastUpdatedAt?: number | null;\n /** `Date.now()` of the last failure; shown for ten seconds, then dropped. */\n lastErrorAt?: number | null;\n /** Renders the on/off switch and the live indicator. Omit for a table without `autoRefresh`. */\n autoRefresh?: DataTableAutoRefreshState | null;\n}\n\n/**\n * The freshness controls: Refresh, the last-updated stamp, and — only where a\n * table opted in — the auto-refresh switch with its live indicator. Mandatory\n * on every server-backed table (P-39); `DataTable` renders it beside the\n * search box on its own.\n */\nfunction DataTableRefresh({\n onRefresh,\n isFetching,\n lastUpdatedAt = null,\n lastErrorAt = null,\n autoRefresh,\n className,\n ...props\n}: DataTableRefreshProps) {\n const note = autoRefresh ? pauseNote(autoRefresh.pausedBy) : null;\n return (\n <div\n data-slot=\"data-table-refresh\"\n className={cn(\"flex items-center gap-2\", className)}\n {...props}\n >\n <Button\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label=\"Refresh\"\n onClick={onRefresh}\n disabled={isFetching}\n >\n <RefreshCw className={cn(isFetching && \"animate-spin\")} />\n </Button>\n <DataTableUpdatedStamp lastUpdatedAt={lastUpdatedAt} lastErrorAt={lastErrorAt} />\n {autoRefresh ? (\n <span data-slot=\"data-table-auto-refresh\" className=\"flex items-center gap-1.5\">\n <Switch\n aria-label=\"Auto refresh\"\n checked={autoRefresh.enabled}\n onCheckedChange={autoRefresh.setEnabled}\n data-backing-off={autoRefresh.backingOff ? \"\" : undefined}\n />\n {autoRefresh.active ? (\n <StatusDot data-slot=\"data-table-live\" tone=\"success\" live label=\"Live\" />\n ) : (\n <span\n data-slot=\"data-table-auto-label\"\n title={note ?? undefined}\n className=\"whitespace-nowrap text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]\"\n >\n {autoRefresh.enabled ? (note ? \"Paused\" : \"Auto\") : \"Auto\"}\n </span>\n )}\n </span>\n ) : null}\n </div>\n );\n}\n\n/* ------------------------------------------------------------------------ */\n/* Pagination */\n/* ------------------------------------------------------------------------ */\n\nexport interface DataTablePaginationProps<Row extends RowData> extends React.ComponentProps<\"div\"> {\n table: DataTableTable<Row>;\n /** @default [10, 25, 50, 100] */\n pageSizeOptions?: number[];\n}\n\nconst DEFAULT_PAGE_SIZES = [10, 25, 50, 100];\n\nfunction DataTablePagination<Row extends RowData>({\n table,\n pageSizeOptions = DEFAULT_PAGE_SIZES,\n className,\n ...props\n}: DataTablePaginationProps<Row>) {\n const { pageIndex, pageSize } = table.state.pagination;\n const total = table.getRowCount();\n const pageCount = table.getPageCount();\n const from = total === 0 ? 0 : pageIndex * pageSize + 1;\n const to = Math.min(total, (pageIndex + 1) * pageSize);\n const sizes = pageSizeOptions.includes(pageSize)\n ? pageSizeOptions\n : [...pageSizeOptions, pageSize].sort((a, b) => a - b);\n const sizeItems = sizes.map((s) => ({ value: String(s), label: String(s) }));\n\n const navButton = (label: string, icon: React.ReactNode, onClick: () => void, disabled: boolean) => (\n <Button variant=\"outline\" size=\"icon-sm\" aria-label={label} onClick={onClick} disabled={disabled}>\n {icon}\n </Button>\n );\n\n return (\n <div\n data-slot=\"data-table-pagination\"\n className={cn(\n \"flex flex-wrap items-center justify-between gap-3\",\n \"text-[length:var(--ds-text-caption-lg)] text-[color:var(--ds-muted-foreground)]\",\n className,\n )}\n {...props}\n >\n <div className=\"flex items-center gap-2\">\n <span>Rows per page</span>\n <Select\n items={sizeItems}\n value={String(pageSize)}\n onValueChange={(value) => table.setPageSize(Number(value))}\n >\n <SelectTrigger size=\"sm\" aria-label=\"Rows per page\" className=\"min-w-0 w-[4.5rem]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {sizeItems.map((item) => (\n <SelectItem key={item.value} value={item.value}>\n {item.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"flex items-center gap-4\">\n <span data-slot=\"data-table-range\" aria-live=\"polite\">\n {from}–{to} of {total}\n </span>\n <div className=\"flex items-center gap-1\">\n {navButton(\"First page\", <ChevronsLeft />, () => table.firstPage(), !table.getCanPreviousPage())}\n {navButton(\"Previous page\", <ChevronLeft />, () => table.previousPage(), !table.getCanPreviousPage())}\n <span className=\"px-1 tabular-nums\">\n {pageCount === 0 ? 0 : pageIndex + 1} / {pageCount}\n </span>\n {navButton(\"Next page\", <ChevronRight />, () => table.nextPage(), !table.getCanNextPage())}\n {navButton(\"Last page\", <ChevronsRight />, () => table.lastPage(), !table.getCanNextPage())}\n </div>\n </div>\n </div>\n );\n}\n\nexport {\n DataTableColumnHeader,\n DataTableExportMenu,\n DataTableFacetedFilter,\n DataTablePagination,\n DataTableRefresh,\n DataTableToolbar,\n DataTableViewOptions,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAyDA,SAAS,sBAA2C,EAClD,QACA,OACA,WACA,GAAG,SAC+B;CAClC,MAAM,UAAU,OAAO,WAAW;CAClC,MAAM,UAAU,OAAO,WAAW;CAClC,MAAM,SAAS,OAAO,UAAU;CAChC,MAAM,SAAS,OAAO,YAAY;CAClC,MAAM,QAAQ,OAAO,UAAU,MAAM,SAAS;CAE9C,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,QAC3B,OACE,oBAAC,OAAD;EAAK,aAAU;EAA2B,WAAW,GAAG,YAAY,SAAS;EAAG,GAAI;EACjF,UAAA;CACE,CAAA;CAIT,MAAM,QAAQ,OAAO,UAAU,WAAW,QAAQ,YAAY,MAAM;CAEpE,OACE,qBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,gDAGA,UAAU,QAAQ,sCAAsC,yBACxD,UAAU,YAAY,kBACtB,SACF;EACA,GAAI;EAVN,UAAA,CAYE,qBAAC,UAAD;GACE,MAAK;GACL,aAAU;GACV,UAAU,CAAC;GACX,SAAS,UAAU,OAAO,wBAAwB,IAAI,KAAA;GAStD,cACE,UACI,SACE,WAAW,MAAM,WAAW,WAAW,QAAQ,cAAc,iBAC7D,WAAW,UACb,KAAA;GAEN,WAAW,GACT,8FACA,qEACA,WAAW,sEACX,mEACA,yBACF;GA1BF,UAAA,CA4BE,oBAAC,QAAD;IAAM,WAAU;IAAY,UAAA;GAAY,CAAA,GACvC,UACC,WAAW,QACT,oBAAC,SAAD;IAAS,eAAA;IAAY,WAAU;GAAoD,CAAA,IACjF,WAAW,SACb,oBAAC,WAAD;IAAW,eAAA;IAAY,WAAU;GAAoD,CAAA,IAGrF,oBAAC,gBAAD;IACE,eAAA;IACA,WAAU;GACX,CAAA,IAED,IACE;EACR,CAAA,GAAA,qBAAC,cAAD,EAAA,UAAA,CACE,oBAAC,qBAAD;GACE,cAAY,GAAG,MAAM;GACrB,WAAW,GAET,qIACA,UAAU,QAAQ,WAAW,WAC7B,wFACA,wFACA,4FACF;GAEA,UAAA,oBAAC,aAAD,EAAa,WAAU,WAAY,CAAA;EAChB,CAAA,GACrB,qBAAC,qBAAD;GAAqB,OAAO,UAAU,QAAQ,QAAQ;GAAtD,UAAA;IACG,UACC,qBAAC,mBAAD,EAAA,UAAA;KACE,qBAAC,kBAAD;MAAkB,eAAe,OAAO,cAAc,KAAK;MAA3D,UAAA,CACE,oBAAC,SAAD,CAAU,CAAA,GAAC,iBACK;;KAClB,qBAAC,kBAAD;MAAkB,eAAe,OAAO,cAAc,IAAI;MAA1D,UAAA,CACE,oBAAC,WAAD,CAAY,CAAA,GAAC,kBACG;;KACjB,SACC,qBAAC,kBAAD;MAAkB,eAAe,OAAO,aAAa;MAArD,UAAA,CACE,oBAAC,gBAAD,CAAiB,CAAA,GAAC,aACF;KAChB,CAAA,IAAA;IACa,EAAA,CAAA,IACjB;IACH,YAAY,UAAU,WAAW,oBAAC,uBAAD,CAAwB,CAAA,IAAI;IAC7D,SACC,qBAAC,mBAAD,EAAA,UAAA;KACE,qBAAC,kBAAD;MAAkB,eAAe,OAAO,IAAI,OAAO;MAAnD,UAAA,CACE,oBAAC,cAAD,CAAe,CAAA,GAAC,WACA;;KAClB,qBAAC,kBAAD;MAAkB,eAAe,OAAO,IAAI,KAAK;MAAjD,UAAA,CACE,oBAAC,eAAD,CAAgB,CAAA,GAAC,YACD;;KACjB,OAAO,YAAY,IAClB,qBAAC,kBAAD;MAAkB,eAAe,OAAO,IAAI,KAAK;MAAjD,UAAA,CACE,oBAAC,QAAD,CAAS,CAAA,GAAC,QACM;KAChB,CAAA,IAAA;IACa,EAAA,CAAA,IACjB;IACH,UAAU,UAAU,oBAAC,uBAAD,CAAwB,CAAA,IAAI;IAChD,UACC,qBAAC,kBAAD;KAAkB,eAAe,OAAO,iBAAiB,KAAK;KAA9D,UAAA,CACE,oBAAC,QAAD,CAAS,CAAA,GAAC,cACM;IAChB,CAAA,IAAA;GACe;EACT,CAAA,CAAA,EAAA,CAAA,CACX;;AAET;;;;;;;AA0BA,SAAS,uBAA4C,EACnD,QACA,OACA,SACA,aACmC;CACnC,MAAM,MAAM,OAAO,eAAe;CAClC,MAAM,WAAW,IAAI,IAAa,MAAM,QAAQ,GAAG,IAAI,MAAM,OAAO,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC;CACrF,MAAM,SAAS,UAAU,OAAO,OAAO,uBAAuB;CAC9D,MAAM,QACJ,WACA,OAAO,UAAU,MAAM,iBACvB,MAAM,KAAK,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,CAC7B,QAAQ,MAAsC,KAAK,QAAQ,OAAO,MAAM,QAAQ,CAAC,CACjF,MAAM,GAAG,MAAM,OAAO,CAAC,CAAC,CAAC,cAAc,OAAO,CAAC,CAAC,CAAC,CAAC,CAClD,KAAK,WAAW,EAAE,MAAM,EAAE;CAC/B,MAAM,QAAQ,SAAS,YAAY,MAAM;CAEzC,MAAM,UAAU,OAAgB,YAAqB;EACnD,MAAM,OAAO,IAAI,IAAI,QAAQ;EAC7B,IAAI,SAAS,KAAK,IAAI,KAAK;OACtB,KAAK,OAAO,KAAK;EACtB,OAAO,eAAe,KAAK,OAAO,MAAM,KAAK,IAAI,IAAI,KAAA,CAAS;CAChE;CAEA,OACE,qBAAC,cAAD,EAAA,UAAA,CACE,qBAAC,qBAAD;EACE,aAAU;EACV,QAAQ,oBAAC,QAAD;GAAQ,SAAQ;GAAU,MAAK;GAAK,WAAW,GAAG,iBAAiB,SAAS;EAAI,CAAA;EAF1F,UAAA;GAIE,oBAAC,YAAD,CAAa,CAAA;GACZ;GACA,SAAS,OACR,oBAAC,QAAD;IACE,aAAU;IACV,WAAW,GACT,iGACA,gIACF;IAEC,UAAA,SAAS;GACN,CAAA,IACJ;EACe;CACrB,CAAA,GAAA,qBAAC,qBAAD;EAAqB,OAAM;EAAQ,WAAU;EAA7C,UAAA,CACE,qBAAC,mBAAD,EAAA,UAAA;GACE,oBAAC,mBAAD,EAAA,UAAoB,MAAyB,CAAA;GAC5C,MAAM,WAAW,IAChB,oBAAC,OAAD;IAAK,WAAU;IAA8F,UAAA;GAExG,CAAA,IACH;GACH,MAAM,KAAK,SAAS;IACnB,MAAM,OAAO,KAAK;IAClB,MAAM,QAAQ,QAAQ,IAAI,KAAK,KAAK;IACpC,OACE,qBAAC,0BAAD;KAEE,cAAc;KACd,SAAS,SAAS,IAAI,KAAK,KAAK;KAChC,kBAAkB,YAAY,OAAO,KAAK,OAAO,OAAO;KAJ1D,UAAA;MAMG,OAAO,oBAAC,MAAD,EAAM,WAAU,iDAAkD,CAAA,IAAI;MAC9E,oBAAC,QAAD;OAAM,WAAU;OAAmB,UAAA,KAAK,SAAS,OAAO,KAAK,KAAK;MAAQ,CAAA;MACzE,UAAU,KAAA,IACT,oBAAC,QAAD;OAAM,WAAU;OACb,UAAA;MACG,CAAA,IACJ;KACoB;IAZnB,GAAA,OAAO,KAAK,KAAK,CAYE;GAE9B,CAAC;EACgB,EAAA,CAAA,GAClB,SAAS,OACR,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,uBAAD,CAAwB,CAAA,GACxB,oBAAC,kBAAD;GAAkB,eAAe,OAAO,eAAe,KAAA,CAAS;GAAG,WAAU;GAAiB,UAAA;EAE5E,CAAA,CAClB,EAAA,CAAA,IACA,IACe;CACT,CAAA,CAAA,EAAA,CAAA;AAElB;;AAYA,SAAS,qBAA0C,EAAE,OAAO,aAA6C;CACvG,MAAM,UAAU,MAAM,kBAAkB,CAAC,CAAC,QAAQ,MAAM,EAAE,WAAW,CAAC;CACtE,IAAI,QAAQ,WAAW,GAAG,OAAO;CACjC,OACE,qBAAC,cAAD,EAAA,UAAA,CACE,qBAAC,qBAAD;EACE,aAAU;EACV,cAAW;EACX,QAAQ,oBAAC,QAAD;GAAQ,SAAQ;GAAU,MAAK;GAAgB;EAAY,CAAA;EAHrE,UAAA,CAKE,oBAAC,WAAD,CAAY,CAAA,GAAC,MAEM;CACrB,CAAA,GAAA,qBAAC,qBAAD;EAAqB,OAAM;EAAM,WAAU;EAA3C,UAAA,CACE,qBAAC,mBAAD,EAAA,UAAA,CACE,oBAAC,mBAAD,EAAA,UAAmB,iBAAiC,CAAA,GACnD,QAAQ,KAAK,WACZ,oBAAC,0BAAD;GAEE,cAAc;GACd,SAAS,OAAO,aAAa;GAC7B,kBAAkB,YAAY,OAAO,iBAAiB,OAAO;GAE5D,UAAA,YAAY,MAAM;EACK,GANnB,OAAO,EAMY,CAC3B,CACgB,EAAA,CAAA,GAClB,CAAC,MAAM,uBAAuB,IAC7B,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,uBAAD,CAAwB,CAAA,GACxB,oBAAC,kBAAD;GAAkB,eAAe,MAAM,wBAAwB,IAAI;GAAG,UAAA;EAA0B,CAAA,CAChG,EAAA,CAAA,IACA,IACe;CACT,CAAA,CAAA,EAAA,CAAA;AAElB;AAYA,SAAS,oBAAyC,EAAE,OAAO,UAAU,aAA4C;CAC/G,MAAM,WAAW,MAAM,oBAAoB,CAAC,CAAC;CAC7C,OACE,qBAAC,cAAD,EAAA,UAAA,CACE,qBAAC,qBAAD;EACE,aAAU;EACV,cAAW;EACX,QAAQ,oBAAC,QAAD;GAAQ,SAAQ;GAAU,MAAK;GAAgB;EAAY,CAAA;EAHrE,UAAA,CAKE,oBAAC,UAAD,CAAW,CAAA,GAAC,QAEO;CACrB,CAAA,GAAA,qBAAC,qBAAD;EAAqB,OAAM;EAA3B,UAAA;GACE,oBAAC,kBAAD;IAAkB,eAAe,YAAY,OAAO,QAAQ;IAAG,UAAA;GAAqB,CAAA;GACpF,oBAAC,kBAAD;IAAkB,eAAe,KAAK,cAAc,OAAO,QAAQ;IAAG,UAAA;GAAuB,CAAA;GAC5F,SAAS,SACR,qBAAA,UAAA,EAAA,UAAA;IACE,oBAAC,uBAAD,CAAwB,CAAA;IACxB,qBAAC,kBAAD;KAAkB,eAAe,YAAY,OAAO,UAAU,QAAQ;KAAtE,UAAA;MAAyE;MAC/D,SAAS;MAAO;KACR;;IAClB,qBAAC,kBAAD;KAAkB,eAAe,KAAK,cAAc,OAAO,UAAU,QAAQ;KAA7E,UAAA;MAAgF;MACpE,SAAS;MAAO;KACV;;GAClB,EAAA,CAAA,IACA;EACe;CACT,CAAA,CAAA,EAAA,CAAA;AAElB;;;;;;AA8BA,SAAS,iBAAsC,EAC7C,OACA,oBAAoB,WACpB,aACA,UACA,eAAe,MACf,gBACA,SACA,WACA,GAAG,SAC0B;CAC7B,MAAM,eAAgB,MAAM,MAAM,gBAAuC;CACzE,MAAM,YAAY,MAAM,QAAQ,uBAAuB;CACvD,MAAM,iBAAiB,MACpB,kBAAkB,CAAC,CACnB,QAAQ,MAAM,EAAE,UAAU,MAAM,WAAW,aAAa,EAAE,aAAa,CAAC;CAC3E,MAAM,aAAa,MAAM,MAAM,cAAc,SAAS,KAAK,iBAAiB;CAC5E,MAAM,eAAe,MAAM,QAAQ,qBAAqB,MAAM,oBAAoB,CAAC,CAAC,OAAO,CAAC;CAE5F,OACE,qBAAC,OAAD;EACE,aAAU;EACV,WAAW,GAAG,qCAAqC,SAAS;EAC5D,GAAI;EAHN,UAAA,CAKE,qBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACG,aAAa,SACZ,qBAAC,OAAD;IAAK,aAAU;IAA0B,WAAU;IAAnD,UAAA;KACE,qBAAC,QAAD;MAAM,WAAU;MAAhB,UAAA,CACE,oBAAC,QAAD;OAAM,WAAU;OAAiD,UAAA,aAAa;MAAa,CAAA,GAAC,WACxF;;KACL,cAAc;MAAE,MAAM,aAAa,KAAK,MAAM,EAAE,QAAQ;MAAG;KAAM,CAAC;KACnE,qBAAC,QAAD;MAAQ,SAAQ;MAAQ,MAAK;MAAK,eAAe,MAAM,kBAAkB;MAAzE,UAAA,CAA4E,SAE1E,oBAAC,GAAD,CAAI,CAAA,CACE;;IACL;GAEL,CAAA,IAAA,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACG,YACC,qBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,oBAAC,QAAD;OACE,eAAA;OACA,WAAU;MACX,CAAA,GACD,oBAAC,OAAD;OACE,aAAU;OACV,MAAK;OACL,cAAW;OACX,aAAa;OACb,OAAO;OACP,WAAW,MAAM,MAAM,gBAAgB,EAAE,OAAO,KAAK;OACrD,WAAU;MACX,CAAA,CACE;KACH,CAAA,IAAA;KACH,eAAe,KAAK,WACnB,oBAAC,wBAAD,EAAgD,OAAS,GAA5B,OAAO,EAAqB,CAC1D;KACA,aACC,qBAAC,QAAD;MACE,SAAQ;MACR,MAAK;MACL,eAAe;OACb,MAAM,mBAAmB;OACzB,MAAM,gBAAgB,EAAE;MAC1B;MANF,UAAA,CAOC,SAEC,oBAAC,GAAD,CAAI,CAAA,CACE;KACN,CAAA,IAAA;IACD;GAIN,CAAA,GAAA,UAAU,oBAAC,kBAAD,EAAkB,GAAI,QAAU,CAAA,IAAI,IAC5C;EACL,CAAA,GAAA,qBAAC,OAAD;GAAK,WAAU;GAAf,UAAA;IACG;IACD,oBAAC,sBAAD,EAA6B,MAAQ,CAAA;IACpC,eAAe,oBAAC,qBAAD;KAA4B;KAAO,UAAU;IAAiB,CAAA,IAAI;GAC/E;EACF,CAAA,CAAA;;AAET;;AAOA,MAAM,mBAAmB;;AAEzB,MAAM,qBAAqB;;;;;;;;;;AAW3B,SAAS,YAAY,OAAmB,IAAmB,OAAqB;CAC9E,MAAM,gBAAgB;EACpB,IAAI,OAAO,MAAM;EACjB,MAAM,OAAO,SAAS,KAAK,IAAI,IAAI;EACnC,IAAI,QAAQ,GAAG;EACf,MAAM,KAAK,WAAW,OAAO,IAAI;EACjC,aAAa,aAAa,EAAE;CAC9B,GAAG;EAAC;EAAO;EAAI;CAAK,CAAC;AACvB;AAEA,SAAS,YAAY,IAAoB;CACvC,OAAO,IAAI,KAAK,EAAE,CAAC,CAAC,mBAAmB,KAAA,GAAW;EAAE,MAAM;EAAW,QAAQ;CAAU,CAAC;AAC1F;AAEA,SAAS,UAAU,UAAgE;CACjF,QAAQ,UAAR;EACE,KAAK,UACH,OAAO;EACT,KAAK,WACH,OAAO;EACT,KAAK,aACH,OAAO;EACT,KAAK,YACH,OAAO;EACT,KAAK,OACH,OAAO;EACT,SACE,OAAO;CACX;AACF;;;;;;;AAaA,SAAS,sBAAsB,EAAE,eAAe,eAA2B;CACzE,MAAM,GAAG,SAAS,MAAM,YAAY,MAAc,IAAI,GAAG,CAAC;CAC1D,YAAY,OAAO,eAAe,kBAAkB;CACpD,YAAY,OAAO,aAAa,gBAAgB;CAChD,MAAM,MAAM,KAAK,IAAI;CAErB,IAAI,gBAAgB,QAAQ,MAAM,cAAc,kBAC9C,OACE,oBAAC,QAAD;EACE,aAAU;EACV,eAAY;EACZ,WAAU;EACX,UAAA;CAEK,CAAA;CAGV,IAAI,kBAAkB,MAAM,OAAO;CACnC,OACE,oBAAC,QAAD;EACE,aAAU;EACV,OAAO,IAAI,KAAK,aAAa,CAAC,CAAC,eAAe;EAC9C,WAAU;EAET,UAAA,MAAM,gBAAgB,qBACnB,qBACA,WAAW,YAAY,aAAa;CACpC,CAAA;AAEV;;;;;;;AAqBA,SAAS,iBAAiB,EACxB,WACA,YACA,gBAAgB,MAChB,cAAc,MACd,aACA,WACA,GAAG,SACqB;CACxB,MAAM,OAAO,cAAc,UAAU,YAAY,QAAQ,IAAI;CAC7D,OACE,qBAAC,OAAD;EACE,aAAU;EACV,WAAW,GAAG,2BAA2B,SAAS;EAClD,GAAI;EAHN,UAAA;GAKE,oBAAC,QAAD;IACE,SAAQ;IACR,MAAK;IACL,cAAW;IACX,SAAS;IACT,UAAU;IAEV,UAAA,oBAAC,WAAD,EAAW,WAAW,GAAG,cAAc,cAAc,EAAI,CAAA;GACnD,CAAA;GACR,oBAAC,uBAAD;IAAsC;IAA4B;GAAc,CAAA;GAC/E,cACC,qBAAC,QAAD;IAAM,aAAU;IAA0B,WAAU;IAApD,UAAA,CACE,oBAAC,QAAD;KACE,cAAW;KACX,SAAS,YAAY;KACrB,iBAAiB,YAAY;KAC7B,oBAAkB,YAAY,aAAa,KAAK,KAAA;IACjD,CAAA,GACA,YAAY,SACX,oBAAC,WAAD;KAAW,aAAU;KAAkB,MAAK;KAAU,MAAA;KAAK,OAAM;IAAQ,CAAA,IAEzE,oBAAC,QAAD;KACE,aAAU;KACV,OAAO,QAAQ,KAAA;KACf,WAAU;KAET,UAAA,YAAY,UAAW,OAAO,WAAW,SAAU;IAChD,CAAA,CAEJ;GACJ,CAAA,IAAA;EACD;;AAET;AAYA,MAAM,qBAAqB;CAAC;CAAI;CAAI;CAAI;AAAG;AAE3C,SAAS,oBAAyC,EAChD,OACA,kBAAkB,oBAClB,WACA,GAAG,SAC6B;CAChC,MAAM,EAAE,WAAW,aAAa,MAAM,MAAM;CAC5C,MAAM,QAAQ,MAAM,YAAY;CAChC,MAAM,YAAY,MAAM,aAAa;CACrC,MAAM,OAAO,UAAU,IAAI,IAAI,YAAY,WAAW;CACtD,MAAM,KAAK,KAAK,IAAI,QAAQ,YAAY,KAAK,QAAQ;CAIrD,MAAM,aAHQ,gBAAgB,SAAS,QAAQ,IAC3C,kBACA,CAAC,GAAG,iBAAiB,QAAQ,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC,EAAA,CAC/B,KAAK,OAAO;EAAE,OAAO,OAAO,CAAC;EAAG,OAAO,OAAO,CAAC;CAAE,EAAE;CAE3E,MAAM,aAAa,OAAe,MAAuB,SAAqB,aAC5E,oBAAC,QAAD;EAAQ,SAAQ;EAAU,MAAK;EAAU,cAAY;EAAgB;EAAmB;EACrF,UAAA;CACK,CAAA;CAGV,OACE,qBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,qDACA,mFACA,SACF;EACA,GAAI;EAPN,UAAA,CASE,qBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACE,oBAAC,QAAD,EAAA,UAAM,gBAAmB,CAAA,GACzB,qBAAC,QAAD;IACE,OAAO;IACP,OAAO,OAAO,QAAQ;IACtB,gBAAgB,UAAU,MAAM,YAAY,OAAO,KAAK,CAAC;IAH3D,UAAA,CAKE,oBAAC,eAAD;KAAe,MAAK;KAAK,cAAW;KAAgB,WAAU;KAC5D,UAAA,oBAAC,aAAD,CAAc,CAAA;IACD,CAAA,GACf,oBAAC,eAAD,EAAA,UACG,UAAU,KAAK,SACd,oBAAC,YAAD;KAA6B,OAAO,KAAK;KACtC,UAAA,KAAK;IACI,GAFK,KAAK,KAEV,CACb,EACY,CAAA,CACT;GACL,CAAA,CAAA;EACL,CAAA,GAAA,qBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACE,qBAAC,QAAD;IAAM,aAAU;IAAmB,aAAU;IAA7C,UAAA;KACG;KAAK;KAAE;KAAG;KAAK;IACZ;GACN,CAAA,GAAA,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACG,UAAU,cAAc,oBAAC,cAAD,CAAe,CAAA,SAAS,MAAM,UAAU,GAAG,CAAC,MAAM,mBAAmB,CAAC;KAC9F,UAAU,iBAAiB,oBAAC,aAAD,CAAc,CAAA,SAAS,MAAM,aAAa,GAAG,CAAC,MAAM,mBAAmB,CAAC;KACpG,qBAAC,QAAD;MAAM,WAAU;MAAhB,UAAA;OACG,cAAc,IAAI,IAAI,YAAY;OAAE;OAAI;MACrC;;KACL,UAAU,aAAa,oBAAC,cAAD,CAAe,CAAA,SAAS,MAAM,SAAS,GAAG,CAAC,MAAM,eAAe,CAAC;KACxF,UAAU,aAAa,oBAAC,eAAD,CAAgB,CAAA,SAAS,MAAM,SAAS,GAAG,CAAC,MAAM,eAAe,CAAC;IACvF;GACF,CAAA,CAAA;EACF,CAAA,CAAA;;AAET"}
@@ -99,6 +99,45 @@ export type DataTableState<Row extends RowData> = TableState<DataTableFeatures<R
99
99
  /** The TanStack table instance every part takes as `table`. */
100
100
  export type DataTableTable<Row extends RowData> = ReactTable<DataTableFeatures<Row>, Row>;
101
101
  export type DataTablePaginationMode = "pages" | "infinite" | "none";
102
+ /**
103
+ * Opt-in polling for a server source, for tables that are *watched* rather
104
+ * than edited — runs in progress, alert lists, a board left open on a wall
105
+ * screen. It renders an on/off switch beside the Refresh button, off until
106
+ * the user turns it on, and it is never a substitute for push: a table that
107
+ * subscribes to a notification stream should call `refresh()` from the stream
108
+ * and leave the interval as the fallback for when the stream is unavailable.
109
+ */
110
+ export interface DataTableAutoRefresh {
111
+ /**
112
+ * Poll interval while the switch is on. Clamped to a 30 s floor; anything
113
+ * shorter is a load problem wearing a freshness costume. @default 60000
114
+ */
115
+ intervalMs?: number;
116
+ /**
117
+ * An extra pause the app owns — a pending mutation, a wizard mid-flight.
118
+ * The tab being hidden, an open inline editor and a live selection are
119
+ * already handled by the component.
120
+ */
121
+ paused?: boolean;
122
+ }
123
+ /** Why the interval is not running even though the switch is on. */
124
+ export type DataTableRefreshPause = "hidden" | "editing" | "selection" | "mutation" | "app";
125
+ /** The auto-refresh half of the toolbar's freshness controls. */
126
+ export interface DataTableAutoRefreshState {
127
+ /** The user's switch, remembered per `stateKey`. False until they turn it on. */
128
+ enabled: boolean;
129
+ setEnabled: (on: boolean) => void;
130
+ /** `enabled` and nothing is pausing it: the interval is really running. */
131
+ active: boolean;
132
+ /** Set while `enabled` but paused, so the UI can say why. */
133
+ pausedBy: DataTableRefreshPause | null;
134
+ /** The interval in force, including error back-off. */
135
+ intervalMs: number;
136
+ /** The configured interval after the 30 s clamp. */
137
+ baseIntervalMs: number;
138
+ /** True while consecutive failures have stretched the interval. */
139
+ backingOff: boolean;
140
+ }
102
141
  export interface UseDataTableOptions<Row extends RowData> {
103
142
  columns: ReadonlyArray<DataTableColumnDef<Row, any>>;
104
143
  dataSource: DataSource<Row>;
@@ -137,6 +176,19 @@ export interface UseDataTableOptions<Row extends RowData> {
137
176
  onRowSelectionChange?: (rows: Row[]) => void;
138
177
  /** Debounce applied to the search box before it reaches a server source. @default 250 */
139
178
  searchDebounceMs?: number;
179
+ /**
180
+ * Turns on the auto-refresh switch beside the Refresh button (server
181
+ * sources only). Off by default; `stateKey` is required with it, because
182
+ * the user's choice is remembered per table.
183
+ */
184
+ autoRefresh?: DataTableAutoRefresh;
185
+ /**
186
+ * Identity of this table for anything remembered between visits. Required
187
+ * when `autoRefresh` is set: the choice is stored under
188
+ * `cortena.data-table.auto-refresh.<stateKey>`, so turning polling on for
189
+ * the runs list does not turn it on for every other table.
190
+ */
191
+ stateKey?: string;
140
192
  }
141
193
  export interface DataTableEditingCell {
142
194
  rowId: string;
@@ -161,6 +213,15 @@ export interface DataTableInstance<Row extends RowData> {
161
213
  hasNextPage: boolean;
162
214
  /** Search text the server last received (debounced), for consumers rendering their own toolbar. */
163
215
  searchQuery: string;
216
+ /**
217
+ * `Date.now()` of the last successful server response, for the "Updated
218
+ * hh:mm" stamp. Null before the first one, and for a client source.
219
+ */
220
+ lastUpdatedAt: number | null;
221
+ /** `Date.now()` of the last failed one; the toolbar shows it briefly. */
222
+ lastErrorAt: number | null;
223
+ /** Null unless `autoRefresh` was configured on a server source. */
224
+ autoRefresh: DataTableAutoRefreshState | null;
164
225
  editing: {
165
226
  cell: DataTableEditingCell | null;
166
227
  start: (cell: DataTableEditingCell) => void;