cortena-ui 1.3.0 → 1.4.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 (280) hide show
  1. package/README.md +74 -3
  2. package/dist/a2ui/catalogue-doc.d.ts +44 -0
  3. package/dist/a2ui/catalogue-doc.js +169 -0
  4. package/dist/a2ui/catalogue-doc.js.map +1 -0
  5. package/dist/a2ui/catalogue.d.ts +44 -0
  6. package/dist/a2ui/catalogue.js +942 -0
  7. package/dist/a2ui/catalogue.js.map +1 -0
  8. package/dist/a2ui/index.d.ts +7 -0
  9. package/dist/a2ui/message.d.ts +232 -0
  10. package/dist/a2ui/message.js +365 -0
  11. package/dist/a2ui/message.js.map +1 -0
  12. package/dist/a2ui/renderer.d.ts +46 -0
  13. package/dist/a2ui/renderer.js +252 -0
  14. package/dist/a2ui/renderer.js.map +1 -0
  15. package/dist/a2ui/views.d.ts +80 -0
  16. package/dist/a2ui/views.js +596 -0
  17. package/dist/a2ui/views.js.map +1 -0
  18. package/dist/a2ui.d.ts +8 -0
  19. package/dist/a2ui.js +7 -0
  20. package/dist/chart.d.ts +9 -0
  21. package/dist/chart.js +9 -0
  22. package/dist/components/accordion.d.ts +22 -0
  23. package/dist/components/accordion.js +63 -0
  24. package/dist/components/accordion.js.map +1 -0
  25. package/dist/components/alert.d.ts +15 -0
  26. package/dist/components/alert.js +64 -0
  27. package/dist/components/alert.js.map +1 -0
  28. package/dist/components/avatar.d.ts +18 -0
  29. package/dist/components/avatar.js +40 -0
  30. package/dist/components/avatar.js.map +1 -0
  31. package/dist/components/badge.d.ts +12 -0
  32. package/dist/components/badge.js +47 -0
  33. package/dist/components/badge.js.map +1 -0
  34. package/dist/components/breadcrumb.d.ts +71 -0
  35. package/dist/components/breadcrumb.js +123 -0
  36. package/dist/components/breadcrumb.js.map +1 -0
  37. package/dist/components/button-link.d.ts +28 -0
  38. package/dist/components/button-link.js +24 -0
  39. package/dist/components/button-link.js.map +1 -0
  40. package/dist/components/button.d.ts +24 -0
  41. package/dist/components/button.js +65 -0
  42. package/dist/components/button.js.map +1 -0
  43. package/dist/components/calendar.d.ts +28 -0
  44. package/dist/components/calendar.js +123 -0
  45. package/dist/components/calendar.js.map +1 -0
  46. package/dist/components/card.d.ts +32 -0
  47. package/dist/components/card.js +57 -0
  48. package/dist/components/card.js.map +1 -0
  49. package/dist/components/chart/chart.d.ts +71 -0
  50. package/dist/components/chart/chart.js +167 -0
  51. package/dist/components/chart/chart.js.map +1 -0
  52. package/dist/components/chart/container.d.ts +88 -0
  53. package/dist/components/chart/container.js +189 -0
  54. package/dist/components/chart/container.js.map +1 -0
  55. package/dist/components/chart/data.js +128 -0
  56. package/dist/components/chart/data.js.map +1 -0
  57. package/dist/components/chart/index.d.ts +9 -0
  58. package/dist/components/chart/index.js +10 -0
  59. package/dist/components/chart/nivo-charts.js +225 -0
  60. package/dist/components/chart/nivo-charts.js.map +1 -0
  61. package/dist/components/chart/nivo-lazy.js +50 -0
  62. package/dist/components/chart/nivo-lazy.js.map +1 -0
  63. package/dist/components/chart/nivo-theme.d.ts +156 -0
  64. package/dist/components/chart/nivo-theme.js +161 -0
  65. package/dist/components/chart/nivo-theme.js.map +1 -0
  66. package/dist/components/chart/recharts-charts.js +276 -0
  67. package/dist/components/chart/recharts-charts.js.map +1 -0
  68. package/dist/components/chart/recharts-lazy.js +58 -0
  69. package/dist/components/chart/recharts-lazy.js.map +1 -0
  70. package/dist/components/chart/recharts-parts.d.ts +10 -0
  71. package/dist/components/chart/recharts-parts.js +25 -0
  72. package/dist/components/chart/recharts-parts.js.map +1 -0
  73. package/dist/components/chart/tokens.d.ts +62 -0
  74. package/dist/components/chart/tokens.js +192 -0
  75. package/dist/components/chart/tokens.js.map +1 -0
  76. package/dist/components/chart/types.d.ts +114 -0
  77. package/dist/components/chart/types.js +27 -0
  78. package/dist/components/chart/types.js.map +1 -0
  79. package/dist/components/checkbox.d.ts +20 -0
  80. package/dist/components/checkbox.js +24 -0
  81. package/dist/components/checkbox.js.map +1 -0
  82. package/dist/components/chip.d.ts +17 -0
  83. package/dist/components/chip.js +47 -0
  84. package/dist/components/chip.js.map +1 -0
  85. package/dist/components/combobox.d.ts +76 -0
  86. package/dist/components/combobox.js +201 -0
  87. package/dist/components/combobox.js.map +1 -0
  88. package/dist/components/command.d.ts +50 -0
  89. package/dist/components/command.js +117 -0
  90. package/dist/components/command.js.map +1 -0
  91. package/dist/components/data-table/data-table.d.ts +65 -0
  92. package/dist/components/data-table/data-table.js +588 -0
  93. package/dist/components/data-table/data-table.js.map +1 -0
  94. package/dist/components/data-table/export.d.ts +23 -0
  95. package/dist/components/data-table/export.js +95 -0
  96. package/dist/components/data-table/export.js.map +1 -0
  97. package/dist/components/data-table/features.d.ts +66 -0
  98. package/dist/components/data-table/features.js +103 -0
  99. package/dist/components/data-table/features.js.map +1 -0
  100. package/dist/components/data-table/index.d.ts +8 -0
  101. package/dist/components/data-table/parts.d.ts +79 -0
  102. package/dist/components/data-table/parts.js +400 -0
  103. package/dist/components/data-table/parts.js.map +1 -0
  104. package/dist/components/data-table/system-columns.js +84 -0
  105. package/dist/components/data-table/system-columns.js.map +1 -0
  106. package/dist/components/data-table/types.d.ts +172 -0
  107. package/dist/components/data-table/use-data-table.d.ts +19 -0
  108. package/dist/components/data-table/use-data-table.js +171 -0
  109. package/dist/components/data-table/use-data-table.js.map +1 -0
  110. package/dist/components/data-table/use-server-source.js +200 -0
  111. package/dist/components/data-table/use-server-source.js.map +1 -0
  112. package/dist/components/date-field.d.ts +54 -0
  113. package/dist/components/date-field.js +235 -0
  114. package/dist/components/date-field.js.map +1 -0
  115. package/dist/components/date-picker.d.ts +42 -0
  116. package/dist/components/date-picker.js +125 -0
  117. package/dist/components/date-picker.js.map +1 -0
  118. package/dist/components/dialog.d.ts +36 -0
  119. package/dist/components/dialog.js +97 -0
  120. package/dist/components/dialog.js.map +1 -0
  121. package/dist/components/dropdown-menu.d.ts +54 -0
  122. package/dist/components/dropdown-menu.js +191 -0
  123. package/dist/components/dropdown-menu.js.map +1 -0
  124. package/dist/components/dropzone.d.ts +48 -0
  125. package/dist/components/dropzone.js +185 -0
  126. package/dist/components/dropzone.js.map +1 -0
  127. package/dist/components/empty-state.d.ts +22 -0
  128. package/dist/components/empty-state.js +46 -0
  129. package/dist/components/empty-state.js.map +1 -0
  130. package/dist/components/error-banner.d.ts +17 -0
  131. package/dist/components/error-banner.js +52 -0
  132. package/dist/components/error-banner.js.map +1 -0
  133. package/dist/components/field.d.ts +64 -0
  134. package/dist/components/field.js +133 -0
  135. package/dist/components/field.js.map +1 -0
  136. package/dist/components/form.d.ts +37 -0
  137. package/dist/components/form.js +103 -0
  138. package/dist/components/form.js.map +1 -0
  139. package/dist/components/input.d.ts +17 -0
  140. package/dist/components/input.js +26 -0
  141. package/dist/components/input.js.map +1 -0
  142. package/dist/components/kbd.d.ts +11 -0
  143. package/dist/components/kbd.js +20 -0
  144. package/dist/components/kbd.js.map +1 -0
  145. package/dist/components/label.d.ts +14 -0
  146. package/dist/components/label.js +28 -0
  147. package/dist/components/label.js.map +1 -0
  148. package/dist/components/loading-skeleton.d.ts +22 -0
  149. package/dist/components/loading-skeleton.js +123 -0
  150. package/dist/components/loading-skeleton.js.map +1 -0
  151. package/dist/components/markdown.d.ts +44 -0
  152. package/dist/components/markdown.js +231 -0
  153. package/dist/components/markdown.js.map +1 -0
  154. package/dist/components/page-header.d.ts +26 -0
  155. package/dist/components/page-header.js +53 -0
  156. package/dist/components/page-header.js.map +1 -0
  157. package/dist/components/popover.d.ts +29 -0
  158. package/dist/components/popover.js +74 -0
  159. package/dist/components/popover.js.map +1 -0
  160. package/dist/components/progress.d.ts +22 -0
  161. package/dist/components/progress.js +49 -0
  162. package/dist/components/progress.js.map +1 -0
  163. package/dist/components/radio-group.d.ts +20 -0
  164. package/dist/components/radio-group.js +29 -0
  165. package/dist/components/radio-group.js.map +1 -0
  166. package/dist/components/scroll-area.d.ts +25 -0
  167. package/dist/components/scroll-area.js +39 -0
  168. package/dist/components/scroll-area.js.map +1 -0
  169. package/dist/components/section-card.d.ts +29 -0
  170. package/dist/components/section-card.js +55 -0
  171. package/dist/components/section-card.js.map +1 -0
  172. package/dist/components/segmented.d.ts +27 -0
  173. package/dist/components/segmented.js +32 -0
  174. package/dist/components/segmented.js.map +1 -0
  175. package/dist/components/select.d.ts +62 -0
  176. package/dist/components/select.js +97 -0
  177. package/dist/components/select.js.map +1 -0
  178. package/dist/components/separator.d.ts +19 -0
  179. package/dist/components/separator.js +23 -0
  180. package/dist/components/separator.js.map +1 -0
  181. package/dist/components/sheet.d.ts +35 -0
  182. package/dist/components/sheet.js +131 -0
  183. package/dist/components/sheet.js.map +1 -0
  184. package/dist/components/skeleton.d.ts +12 -0
  185. package/dist/components/skeleton.js +22 -0
  186. package/dist/components/skeleton.js.map +1 -0
  187. package/dist/components/sortable-list.d.ts +67 -0
  188. package/dist/components/sortable-list.js +206 -0
  189. package/dist/components/sortable-list.js.map +1 -0
  190. package/dist/components/spinner.d.ts +29 -0
  191. package/dist/components/spinner.js +34 -0
  192. package/dist/components/spinner.js.map +1 -0
  193. package/dist/components/status-dot.d.ts +26 -0
  194. package/dist/components/status-dot.js +47 -0
  195. package/dist/components/status-dot.js.map +1 -0
  196. package/dist/components/switch.d.ts +21 -0
  197. package/dist/components/switch.js +21 -0
  198. package/dist/components/switch.js.map +1 -0
  199. package/dist/components/tabs.d.ts +24 -0
  200. package/dist/components/tabs.js +60 -0
  201. package/dist/components/tabs.js.map +1 -0
  202. package/dist/components/textarea.d.ts +19 -0
  203. package/dist/components/textarea.js +47 -0
  204. package/dist/components/textarea.js.map +1 -0
  205. package/dist/components/toast.d.ts +47 -0
  206. package/dist/components/toast.js +106 -0
  207. package/dist/components/toast.js.map +1 -0
  208. package/dist/components/toolbar.d.ts +19 -0
  209. package/dist/components/toolbar.js +45 -0
  210. package/dist/components/toolbar.js.map +1 -0
  211. package/dist/components/tooltip.d.ts +28 -0
  212. package/dist/components/tooltip.js +59 -0
  213. package/dist/components/tooltip.js.map +1 -0
  214. package/dist/core.d.ts +44 -0
  215. package/dist/core.js +44 -0
  216. package/dist/data-table.d.ts +9 -0
  217. package/dist/data-table.js +7 -0
  218. package/dist/form.d.ts +7 -0
  219. package/dist/form.js +7 -0
  220. package/dist/hooks/use-cortena-theme.d.ts +98 -0
  221. package/dist/hooks/use-cortena-theme.js +145 -0
  222. package/dist/hooks/use-cortena-theme.js.map +1 -0
  223. package/dist/index.d.ts +75 -2678
  224. package/dist/index.js +66 -8598
  225. package/dist/lib/cn.d.ts +10 -0
  226. package/dist/lib/cn.js +15 -0
  227. package/dist/lib/cn.js.map +1 -0
  228. package/dist/lib/render.d.ts +23 -0
  229. package/dist/lib/render.js +20 -0
  230. package/dist/lib/render.js.map +1 -0
  231. package/dist/lib/shakeable.js +39 -0
  232. package/dist/lib/shakeable.js.map +1 -0
  233. package/dist/markdown.d.ts +3 -0
  234. package/dist/markdown.js +3 -0
  235. package/dist/sortable-list.d.ts +3 -0
  236. package/dist/sortable-list.js +3 -0
  237. package/package.json +37 -14
  238. package/src/a2ui/catalogue.ts +140 -103
  239. package/src/a2ui/views.tsx +5 -2
  240. package/src/components/alert.tsx +1 -1
  241. package/src/components/badge.tsx +1 -1
  242. package/src/components/button.tsx +1 -1
  243. package/src/components/chart/chart.tsx +72 -25
  244. package/src/components/chart/container.tsx +24 -39
  245. package/src/components/chart/index.tsx +4 -2
  246. package/src/components/chart/nivo-charts.tsx +41 -13
  247. package/src/components/chart/nivo-lazy.tsx +52 -0
  248. package/src/components/chart/recharts-charts.tsx +98 -3
  249. package/src/components/chart/recharts-lazy.tsx +72 -0
  250. package/src/components/chart/recharts-parts.tsx +34 -0
  251. package/src/components/chart/tokens.ts +1 -1
  252. package/src/components/chart/types.ts +14 -0
  253. package/src/components/chip.tsx +1 -1
  254. package/src/components/combobox.tsx +2 -2
  255. package/src/components/data-table/export.ts +18 -2
  256. package/src/components/data-table/features.ts +77 -45
  257. package/src/components/data-table/parts.tsx +15 -0
  258. package/src/components/data-table/use-data-table.ts +7 -2
  259. package/src/components/data-table/use-server-source.ts +17 -4
  260. package/src/components/date-field.tsx +1 -1
  261. package/src/components/error-banner.tsx +1 -1
  262. package/src/components/field.tsx +2 -2
  263. package/src/components/form.tsx +2 -2
  264. package/src/components/input.tsx +1 -1
  265. package/src/components/label.tsx +1 -1
  266. package/src/components/markdown.tsx +5 -4
  267. package/src/components/sheet.tsx +2 -2
  268. package/src/components/sortable-list.tsx +2 -2
  269. package/src/components/textarea.tsx +1 -1
  270. package/src/components/toast.tsx +1 -1
  271. package/src/entries/a2ui.ts +11 -0
  272. package/src/entries/chart.ts +10 -0
  273. package/src/entries/core.ts +91 -0
  274. package/src/entries/data-table.ts +9 -0
  275. package/src/entries/form.ts +24 -0
  276. package/src/entries/markdown.ts +9 -0
  277. package/src/entries/sortable-list.ts +13 -0
  278. package/src/index.ts +32 -98
  279. package/src/lib/shakeable.ts +33 -0
  280. package/dist/index.js.map +0 -1
@@ -0,0 +1,172 @@
1
+ "use client";
2
+ import { DataTableFeatures } from "./features.js";
3
+ import { Cell, Column, ColumnDef, ReactTable, Row, RowData, TableState } from "@tanstack/react-table";
4
+ //#region src/components/data-table/types.d.ts
5
+ /**
6
+ * Data sources (the contract in cortenaUI&Design/04-data-tables.md).
7
+ *
8
+ * A client source hands the table every row and lets it filter, sort and page
9
+ * in memory. A server source hands the table one page at a time: the table
10
+ * owns the search box, filter, sort and page state, turns it into a
11
+ * `TableQuery`, and the app answers with the already filtered, sorted page and
12
+ * the total. `TableQuery` / `TableResult` are also the backend contract every
13
+ * extension's list endpoint is expected to speak.
14
+ */
15
+ export type ClientSource<Row> = {
16
+ kind: "client";
17
+ rows: Row[];
18
+ };
19
+ export type ServerSource<Row> = {
20
+ kind: "server";
21
+ /**
22
+ * Fetch one page. The optional `signal` is aborted when the query changes
23
+ * before the request settles, so a fetch that forwards it to `fetch()` is
24
+ * cancelled rather than raced.
25
+ */
26
+ fetch: (query: TableQuery, signal?: AbortSignal) => Promise<TableResult<Row>>;
27
+ /**
28
+ * TanStack Query cache scope. Two tables that share a key share pages;
29
+ * defaults to a key private to the component instance.
30
+ */
31
+ queryKey?: readonly unknown[];
32
+ };
33
+ export type DataSource<Row> = ClientSource<Row> | ServerSource<Row>;
34
+ export type TableQuery = {
35
+ q?: string;
36
+ filters?: Array<{
37
+ id: string;
38
+ value: unknown;
39
+ }>;
40
+ sort?: Array<{
41
+ id: string;
42
+ desc: boolean;
43
+ }>;
44
+ page: number;
45
+ pageSize: number;
46
+ };
47
+ export type TableResult<Row> = {
48
+ rows: Row[];
49
+ total: number;
50
+ };
51
+ /** Inline-editing options for a column (`meta.editable`). */
52
+ export interface DataTableEditable {
53
+ /** Input type; `number` parses the draft with `Number()` before commit. */
54
+ type?: "text" | "number";
55
+ /** Turn the raw input string into the committed value. */
56
+ parse?: (raw: string) => unknown;
57
+ }
58
+ /**
59
+ * Per-column `meta`. Typed through the v9 `columnMeta` feature slot, so
60
+ * `helper.accessor("x", { meta: { ... } })` is checked without declaration
61
+ * merging.
62
+ */
63
+ export interface DataTableColumnMeta<Row = unknown> {
64
+ /**
65
+ * Toolbar filter for this column. `faceted` renders a checkbox list of the
66
+ * column's distinct values (or `filterOptions`) and filters with `oneOf`.
67
+ */
68
+ filter?: "faceted" | "text";
69
+ /** Fixed facet options; required for a server source, where only one page is loaded. */
70
+ filterOptions?: Array<{
71
+ value: string | number | boolean;
72
+ label?: string;
73
+ icon?: React.ComponentType<{
74
+ className?: string;
75
+ }>;
76
+ }>;
77
+ /** Text alignment of the header and cells. Numbers usually want `end`. */
78
+ align?: "start" | "center" | "end";
79
+ /** Enter or double-click opens an editor; Enter commits, Escape cancels. */
80
+ editable?: boolean | DataTableEditable;
81
+ /** Receives the committed value. Falls back to the table's `onCellCommit`. */
82
+ onCommit?: (row: Row, columnId: string, value: unknown) => void | Promise<void>;
83
+ /** Column label for the view menu and exports when `header` is not a string. */
84
+ label?: string;
85
+ /** Set `false` to leave the column out of CSV / Excel exports. */
86
+ exportable?: boolean;
87
+ /** Value written to exports instead of the accessor value. */
88
+ exportValue?: (row: Row) => string | number | boolean | Date | null | undefined;
89
+ /** Extra classes for every cell in this column. */
90
+ cellClassName?: string;
91
+ /** Extra classes for the header cell. */
92
+ headerClassName?: string;
93
+ }
94
+ export type DataTableColumnDef<Row extends RowData, Value = unknown> = ColumnDef<DataTableFeatures<Row>, Row, Value>;
95
+ export type DataTableColumn<Row extends RowData, Value = unknown> = Column<DataTableFeatures<Row>, Row, Value>;
96
+ export type DataTableRow<Row$1 extends RowData> = Row<DataTableFeatures<Row$1>, Row$1>;
97
+ export type DataTableCell<Row extends RowData, Value = unknown> = Cell<DataTableFeatures<Row>, Row, Value>;
98
+ export type DataTableState<Row extends RowData> = TableState<DataTableFeatures<Row>>;
99
+ /** The TanStack table instance every part takes as `table`. */
100
+ export type DataTableTable<Row extends RowData> = ReactTable<DataTableFeatures<Row>, Row>;
101
+ export type DataTablePaginationMode = "pages" | "infinite" | "none";
102
+ export interface UseDataTableOptions<Row extends RowData> {
103
+ columns: ReadonlyArray<DataTableColumnDef<Row, any>>;
104
+ dataSource: DataSource<Row>;
105
+ /** Stable row identity; defaults to the row index. Needed for selection and editing to survive re-sorts. */
106
+ getRowId?: (row: Row, index: number) => string;
107
+ /** Tree data: return a row's children. */
108
+ getSubRows?: (row: Row, index: number) => Row[] | undefined;
109
+ /** `pages` (default) renders a pager; `infinite` appends pages on scroll; `none` shows every row. */
110
+ pagination?: DataTablePaginationMode;
111
+ /** @default 25 */
112
+ pageSize?: number;
113
+ /** Adds a checkbox column and enables `bulkActions`. */
114
+ enableRowSelection?: boolean | ((row: DataTableRow<Row>) => boolean);
115
+ /** Adds an expander column; set automatically by `renderSubComponent`. */
116
+ enableExpanding?: boolean;
117
+ getRowCanExpand?: (row: DataTableRow<Row>) => boolean;
118
+ /** @default true */
119
+ enableSorting?: boolean;
120
+ /** @default false */
121
+ enableMultiSort?: boolean;
122
+ /** @default true */
123
+ enableGlobalFilter?: boolean;
124
+ /** @default true */
125
+ enableColumnFilters?: boolean;
126
+ /** @default true */
127
+ enableHiding?: boolean;
128
+ /** @default true */
129
+ enableColumnPinning?: boolean;
130
+ /** @default false */
131
+ enableColumnResizing?: boolean;
132
+ /** Starting sort, filters, visibility, pinning, sizing. */
133
+ initialState?: Partial<DataTableState<Row>>;
134
+ /** Table-wide commit handler for editable cells without their own `meta.onCommit`. */
135
+ onCellCommit?: (row: Row, columnId: string, value: unknown) => void | Promise<void>;
136
+ /** Fires with the selected original rows whenever selection changes. */
137
+ onRowSelectionChange?: (rows: Row[]) => void;
138
+ /** Debounce applied to the search box before it reaches a server source. @default 250 */
139
+ searchDebounceMs?: number;
140
+ }
141
+ export interface DataTableEditingCell {
142
+ rowId: string;
143
+ columnId: string;
144
+ }
145
+ export interface DataTableInstance<Row extends RowData> {
146
+ table: DataTableTable<Row>;
147
+ dataSource: DataSource<Row>;
148
+ paginationMode: DataTablePaginationMode;
149
+ /** Rows currently in the table (the page, for a server source). */
150
+ rows: Row[];
151
+ /** Total matching rows (server `total`, or the filtered count for a client source). */
152
+ total: number;
153
+ /** True until the first server page arrives; never true for a client source. */
154
+ isLoading: boolean;
155
+ /** True while any server request is in flight, including background refetches. */
156
+ isFetching: boolean;
157
+ error: Error | null;
158
+ refetch: () => void;
159
+ /** Infinite mode: load the next page. No-op elsewhere. */
160
+ fetchNextPage: () => void;
161
+ hasNextPage: boolean;
162
+ /** Search text the server last received (debounced), for consumers rendering their own toolbar. */
163
+ searchQuery: string;
164
+ editing: {
165
+ cell: DataTableEditingCell | null;
166
+ start: (cell: DataTableEditingCell) => void;
167
+ stop: () => void;
168
+ commit: (row: DataTableRow<Row>, columnId: string, value: unknown) => Promise<void>;
169
+ };
170
+ }
171
+ //#endregion
172
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,19 @@
1
+ "use client";
2
+ import { DataTableInstance, UseDataTableOptions } from "./types.js";
3
+ import { RowData } from "@tanstack/react-table";
4
+ //#region src/components/data-table/use-data-table.d.ts
5
+ /**
6
+ * Builds the table for a `dataSource`. This is where the design decision's
7
+ * "the component sets the manual flags itself" lives: a server source flips
8
+ * `manualFiltering`, `manualSorting` and `manualPagination` and feeds the
9
+ * table's own filter / sort / page state back out as a `TableQuery`; a client
10
+ * source leaves every row model in memory. Everything else (selection,
11
+ * expansion, visibility, pinning, sizing) is owned by the table.
12
+ *
13
+ * Use it directly when the default layout is not enough: pass the returned
14
+ * `table` to `DataTableToolbar`, `DataTablePagination` and friends, or the
15
+ * whole instance to `<DataTable instance={...} />`.
16
+ */
17
+ export declare function useDataTable<Row extends RowData>(options: UseDataTableOptions<Row>): DataTableInstance<Row>;
18
+ //#endregion
19
+ //# sourceMappingURL=use-data-table.d.ts.map
@@ -0,0 +1,171 @@
1
+ "use client";
2
+ "use client";
3
+ import { createColumnHelper as createColumnHelper$1, dataTableFeatures } from "./features.js";
4
+ import { expandColumn, selectColumn } from "./system-columns.js";
5
+ import { useServerSource } from "./use-server-source.js";
6
+ import * as React from "react";
7
+ import { useTable } from "@tanstack/react-table";
8
+ //#region src/components/data-table/use-data-table.ts
9
+ const EMPTY_ROWS = [];
10
+ function useDebounced(value, ms) {
11
+ const [debounced, setDebounced] = React.useState(value);
12
+ React.useEffect(() => {
13
+ if (ms <= 0) {
14
+ setDebounced(value);
15
+ return;
16
+ }
17
+ const id = setTimeout(() => setDebounced(value), ms);
18
+ return () => clearTimeout(id);
19
+ }, [value, ms]);
20
+ return ms <= 0 ? value : debounced;
21
+ }
22
+ /**
23
+ * Builds the table for a `dataSource`. This is where the design decision's
24
+ * "the component sets the manual flags itself" lives: a server source flips
25
+ * `manualFiltering`, `manualSorting` and `manualPagination` and feeds the
26
+ * table's own filter / sort / page state back out as a `TableQuery`; a client
27
+ * source leaves every row model in memory. Everything else (selection,
28
+ * expansion, visibility, pinning, sizing) is owned by the table.
29
+ *
30
+ * Use it directly when the default layout is not enough: pass the returned
31
+ * `table` to `DataTableToolbar`, `DataTablePagination` and friends, or the
32
+ * whole instance to `<DataTable instance={...} />`.
33
+ */
34
+ function useDataTable(options) {
35
+ const { columns, dataSource, getRowId, getSubRows, pagination: paginationMode = "pages", pageSize = 25, enableRowSelection = false, enableExpanding = false, getRowCanExpand, enableSorting = true, enableMultiSort = false, enableGlobalFilter = true, enableColumnFilters = true, enableHiding = true, enableColumnPinning = true, enableColumnResizing = false, initialState, onCellCommit, onRowSelectionChange, searchDebounceMs = 250 } = options;
36
+ const isServer = dataSource.kind === "server";
37
+ const [globalFilter, setGlobalFilter] = React.useState(initialState?.globalFilter ?? "");
38
+ const [columnFilters, setColumnFilters] = React.useState(initialState?.columnFilters ?? []);
39
+ const [sorting, setSorting] = React.useState(initialState?.sorting ?? []);
40
+ const [pagination, setPagination] = React.useState(initialState?.pagination ?? {
41
+ pageIndex: 0,
42
+ pageSize
43
+ });
44
+ const searchQuery = useDebounced(globalFilter, isServer ? searchDebounceMs : 0);
45
+ const scopeKey = JSON.stringify([
46
+ searchQuery,
47
+ columnFilters,
48
+ sorting
49
+ ]);
50
+ const lastScope = React.useRef(scopeKey);
51
+ React.useEffect(() => {
52
+ if (lastScope.current === scopeKey) return;
53
+ lastScope.current = scopeKey;
54
+ setPagination((p) => p.pageIndex === 0 ? p : {
55
+ ...p,
56
+ pageIndex: 0
57
+ });
58
+ }, [scopeKey]);
59
+ const query = React.useMemo(() => ({
60
+ q: searchQuery.trim() || void 0,
61
+ filters: columnFilters.length ? columnFilters : void 0,
62
+ sort: sorting.length ? sorting : void 0,
63
+ page: pagination.pageIndex,
64
+ pageSize: pagination.pageSize
65
+ }), [
66
+ searchQuery,
67
+ columnFilters,
68
+ sorting,
69
+ pagination.pageIndex,
70
+ pagination.pageSize
71
+ ]);
72
+ const server = useServerSource(isServer ? dataSource : void 0, query, paginationMode);
73
+ const data = isServer ? server.rows : dataSource.rows;
74
+ const resolvedColumns = React.useMemo(() => {
75
+ const helper = createColumnHelper$1();
76
+ const out = [];
77
+ if (enableRowSelection && !columns.some((c) => c.id === "select")) out.push(selectColumn(helper));
78
+ if (enableExpanding && !columns.some((c) => c.id === "expand")) out.push(expandColumn(helper));
79
+ for (const column of columns) {
80
+ const filter = column.meta?.filter;
81
+ if (filter === "faceted" && column.filterFn === void 0) out.push({
82
+ ...column,
83
+ filterFn: "oneOf"
84
+ });
85
+ else if (filter === "text" && column.filterFn === void 0) out.push({
86
+ ...column,
87
+ filterFn: "includesString"
88
+ });
89
+ else out.push(column);
90
+ }
91
+ return out;
92
+ }, [
93
+ columns,
94
+ enableRowSelection,
95
+ enableExpanding
96
+ ]);
97
+ const table = useTable({
98
+ features: dataTableFeatures(),
99
+ columns: resolvedColumns,
100
+ data: data.length ? data : EMPTY_ROWS,
101
+ getRowId,
102
+ getSubRows,
103
+ initialState,
104
+ state: {
105
+ globalFilter,
106
+ columnFilters,
107
+ sorting,
108
+ pagination
109
+ },
110
+ onGlobalFilterChange: setGlobalFilter,
111
+ onColumnFiltersChange: setColumnFilters,
112
+ onSortingChange: setSorting,
113
+ onPaginationChange: setPagination,
114
+ manualFiltering: isServer,
115
+ manualSorting: isServer,
116
+ manualPagination: isServer || paginationMode !== "pages",
117
+ rowCount: isServer ? server.total : void 0,
118
+ enableRowSelection,
119
+ enableExpanding,
120
+ getRowCanExpand: getRowCanExpand ?? (enableExpanding ? () => true : void 0),
121
+ enableSorting,
122
+ enableMultiSort,
123
+ enableGlobalFilter,
124
+ enableColumnFilters,
125
+ enableHiding,
126
+ enableColumnPinning,
127
+ enableColumnResizing,
128
+ columnResizeMode: "onChange",
129
+ globalFilterFn: "search"
130
+ });
131
+ const rowSelection = table.state.rowSelection;
132
+ const onSelectionRef = React.useRef(onRowSelectionChange);
133
+ onSelectionRef.current = onRowSelectionChange;
134
+ const firstSelection = React.useRef(true);
135
+ React.useEffect(() => {
136
+ if (firstSelection.current) {
137
+ firstSelection.current = false;
138
+ return;
139
+ }
140
+ onSelectionRef.current?.(table.getSelectedRowModel().rows.map((r) => r.original));
141
+ }, [rowSelection]);
142
+ const [editingCell, setEditingCell] = React.useState(null);
143
+ const commit = React.useCallback(async (row, columnId, value) => {
144
+ await ((table.getColumn(columnId)?.columnDef.meta)?.onCommit ?? onCellCommit)?.(row.original, columnId, value);
145
+ }, [table, onCellCommit]);
146
+ const editing = React.useMemo(() => ({
147
+ cell: editingCell,
148
+ start: (cell) => setEditingCell(cell),
149
+ stop: () => setEditingCell(null),
150
+ commit
151
+ }), [editingCell, commit]);
152
+ return {
153
+ table,
154
+ dataSource,
155
+ paginationMode,
156
+ rows: data,
157
+ total: isServer ? server.total ?? 0 : paginationMode === "pages" ? table.getPrePaginatedRowModel().rows.length : table.getRowModel().rows.length,
158
+ isLoading: server.isLoading,
159
+ isFetching: server.isFetching,
160
+ error: server.error,
161
+ refetch: server.refetch,
162
+ fetchNextPage: server.fetchNextPage,
163
+ hasNextPage: server.hasNextPage,
164
+ searchQuery,
165
+ editing
166
+ };
167
+ }
168
+ //#endregion
169
+ export { useDataTable };
170
+
171
+ //# sourceMappingURL=use-data-table.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-data-table.js","names":["createColumnHelper"],"sources":["../../../src/components/data-table/use-data-table.ts"],"sourcesContent":["\"use client\";\n\nimport {\n useTable,\n type ColumnFiltersState,\n type PaginationState,\n type RowData,\n type SortingState,\n} from \"@tanstack/react-table\";\nimport * as React from \"react\";\nimport { createColumnHelper, dataTableFeatures } from \"./features\";\nimport { expandColumn, selectColumn } from \"./system-columns\";\nimport type {\n DataSource,\n DataTableColumnDef,\n DataTableEditingCell,\n DataTableInstance,\n DataTableRow,\n DataTableTable,\n TableQuery,\n UseDataTableOptions,\n} from \"./types\";\nimport { useServerSource } from \"./use-server-source\";\n\nconst EMPTY_ROWS: never[] = [];\n\nfunction useDebounced<T>(value: T, ms: number): T {\n const [debounced, setDebounced] = React.useState(value);\n React.useEffect(() => {\n if (ms <= 0) {\n setDebounced(value);\n return;\n }\n const id = setTimeout(() => setDebounced(value), ms);\n return () => clearTimeout(id);\n }, [value, ms]);\n return ms <= 0 ? value : debounced;\n}\n\n/**\n * Builds the table for a `dataSource`. This is where the design decision's\n * \"the component sets the manual flags itself\" lives: a server source flips\n * `manualFiltering`, `manualSorting` and `manualPagination` and feeds the\n * table's own filter / sort / page state back out as a `TableQuery`; a client\n * source leaves every row model in memory. Everything else (selection,\n * expansion, visibility, pinning, sizing) is owned by the table.\n *\n * Use it directly when the default layout is not enough: pass the returned\n * `table` to `DataTableToolbar`, `DataTablePagination` and friends, or the\n * whole instance to `<DataTable instance={...} />`.\n */\nexport function useDataTable<Row extends RowData>(\n options: UseDataTableOptions<Row>,\n): DataTableInstance<Row> {\n const {\n columns,\n dataSource,\n getRowId,\n getSubRows,\n pagination: paginationMode = \"pages\",\n pageSize = 25,\n enableRowSelection = false,\n enableExpanding = false,\n getRowCanExpand,\n enableSorting = true,\n enableMultiSort = false,\n enableGlobalFilter = true,\n enableColumnFilters = true,\n enableHiding = true,\n enableColumnPinning = true,\n enableColumnResizing = false,\n initialState,\n onCellCommit,\n onRowSelectionChange,\n searchDebounceMs = 250,\n } = options;\n\n const isServer = dataSource.kind === \"server\";\n\n // Slices the server query is built from are React state; the table mirrors\n // them through `state` + `on*Change`, which v9 still supports alongside atoms.\n const [globalFilter, setGlobalFilter] = React.useState<string>(\n (initialState?.globalFilter as string | undefined) ?? \"\",\n );\n const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(\n initialState?.columnFilters ?? [],\n );\n const [sorting, setSorting] = React.useState<SortingState>(initialState?.sorting ?? []);\n const [pagination, setPagination] = React.useState<PaginationState>(\n initialState?.pagination ?? { pageIndex: 0, pageSize },\n );\n\n const searchQuery = useDebounced(globalFilter, isServer ? searchDebounceMs : 0);\n\n // A new search, filter or sort starts from page one again. Client sources\n // get this from `autoResetPageIndex`; manual pagination has to do it here.\n const scopeKey = JSON.stringify([searchQuery, columnFilters, sorting]);\n const lastScope = React.useRef(scopeKey);\n React.useEffect(() => {\n if (lastScope.current === scopeKey) return;\n lastScope.current = scopeKey;\n setPagination((p) => (p.pageIndex === 0 ? p : { ...p, pageIndex: 0 }));\n }, [scopeKey]);\n\n const query = React.useMemo<TableQuery>(\n () => ({\n // Trimmed for the same reason the client filter trims (features.ts): a\n // backend asked for `q: \" \"` returns nothing, and the user sees an empty\n // table with no explanation. Whitespace-only means no search term.\n q: searchQuery.trim() || undefined,\n filters: columnFilters.length ? columnFilters : undefined,\n sort: sorting.length ? sorting : undefined,\n page: pagination.pageIndex,\n pageSize: pagination.pageSize,\n }),\n [searchQuery, columnFilters, sorting, pagination.pageIndex, pagination.pageSize],\n );\n\n const server = useServerSource<Row>(isServer ? dataSource : undefined, query, paginationMode);\n\n const data: ReadonlyArray<Row> = isServer ? server.rows : dataSource.rows;\n\n // System columns first; then each consumer column with its meta defaults\n // applied (a faceted column filters with `oneOf` unless told otherwise).\n const resolvedColumns = React.useMemo(() => {\n const helper = createColumnHelper<Row>();\n const out: DataTableColumnDef<Row, any>[] = [];\n if (enableRowSelection && !columns.some((c) => c.id === \"select\")) out.push(selectColumn(helper));\n if (enableExpanding && !columns.some((c) => c.id === \"expand\")) out.push(expandColumn(helper));\n for (const column of columns) {\n const filter = column.meta?.filter;\n if (filter === \"faceted\" && column.filterFn === undefined) {\n out.push({ ...column, filterFn: \"oneOf\" });\n } else if (filter === \"text\" && column.filterFn === undefined) {\n out.push({ ...column, filterFn: \"includesString\" });\n } else {\n out.push(column);\n }\n }\n return out;\n }, [columns, enableRowSelection, enableExpanding]);\n\n const table: DataTableTable<Row> = useTable({\n features: dataTableFeatures<Row>(),\n columns: resolvedColumns,\n data: data.length ? data : EMPTY_ROWS,\n getRowId,\n getSubRows,\n initialState,\n state: { globalFilter, columnFilters, sorting, pagination },\n onGlobalFilterChange: setGlobalFilter,\n onColumnFiltersChange: setColumnFilters,\n onSortingChange: setSorting,\n onPaginationChange: setPagination,\n manualFiltering: isServer,\n manualSorting: isServer,\n manualPagination: isServer || paginationMode !== \"pages\",\n rowCount: isServer ? server.total : undefined,\n enableRowSelection,\n enableExpanding,\n getRowCanExpand: getRowCanExpand ?? (enableExpanding ? () => true : undefined),\n enableSorting,\n enableMultiSort,\n enableGlobalFilter,\n enableColumnFilters,\n enableHiding,\n enableColumnPinning,\n enableColumnResizing,\n columnResizeMode: \"onChange\",\n // `search`, not `includesString`: it trims the query, so a box holding only\n // whitespace filters nothing rather than emptying the grid. See features.ts.\n globalFilterFn: \"search\",\n });\n\n // Selection callback with original rows, for consumers that keep selection outside.\n const rowSelection = table.state.rowSelection;\n const onSelectionRef = React.useRef(onRowSelectionChange);\n onSelectionRef.current = onRowSelectionChange;\n const firstSelection = React.useRef(true);\n React.useEffect(() => {\n if (firstSelection.current) {\n firstSelection.current = false;\n return;\n }\n onSelectionRef.current?.(table.getSelectedRowModel().rows.map((r) => r.original));\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [rowSelection]);\n\n const [editingCell, setEditingCell] = React.useState<DataTableEditingCell | null>(null);\n const commit = React.useCallback(\n async (row: DataTableRow<Row>, columnId: string, value: unknown) => {\n const meta = table.getColumn(columnId)?.columnDef.meta;\n const handler = meta?.onCommit ?? onCellCommit;\n await handler?.(row.original, columnId, value);\n },\n [table, onCellCommit],\n );\n const editing = React.useMemo(\n () => ({\n cell: editingCell,\n start: (cell: DataTableEditingCell) => setEditingCell(cell),\n stop: () => setEditingCell(null),\n commit,\n }),\n [editingCell, commit],\n );\n\n const total = isServer\n ? (server.total ?? 0)\n : paginationMode === \"pages\"\n ? table.getPrePaginatedRowModel().rows.length\n : table.getRowModel().rows.length;\n\n return {\n table,\n dataSource: dataSource as DataSource<Row>,\n paginationMode,\n rows: data as Row[],\n total,\n isLoading: server.isLoading,\n isFetching: server.isFetching,\n error: server.error,\n refetch: server.refetch,\n fetchNextPage: server.fetchNextPage,\n hasNextPage: server.hasNextPage,\n searchQuery,\n editing,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,200 @@
1
+ "use client";
2
+ "use client";
3
+ import * as React from "react";
4
+ import { QueryClientContext, keepPreviousData, useInfiniteQuery, useQuery } from "@tanstack/react-query";
5
+ //#region src/components/data-table/use-server-source.ts
6
+ const EMPTY = [];
7
+ function toError(value) {
8
+ return value instanceof Error ? value : new Error(String(value));
9
+ }
10
+ /** The part of a query that identifies a result set; the page is separate so infinite mode can append. */
11
+ function scopeOf(query) {
12
+ const { page: _page, ...scope } = query;
13
+ return scope;
14
+ }
15
+ /**
16
+ * Runs a server source. With a `QueryClientProvider` above the table, pages
17
+ * go through TanStack Query: cached by query, previous page kept on screen
18
+ * while the next loads, and the in-flight request aborted through `signal`
19
+ * when the query changes. Without one, a small local fetch state does the
20
+ * same minus the cache, so the table never requires the optional peer to be
21
+ * wired up.
22
+ *
23
+ * The strategy is chosen once per mount: a provider appearing or vanishing
24
+ * mid-life would change the hook order, which React forbids, and is not a
25
+ * real scenario.
26
+ */
27
+ function useServerSource(source, query, mode) {
28
+ const client = React.useContext(QueryClientContext);
29
+ const [strategy] = React.useState(() => client ? "query" : "local");
30
+ return strategy === "query" ? useQueryStrategy(source, query, mode) : useLocalStrategy(source, query, mode);
31
+ }
32
+ function useQueryStrategy(source, query, mode) {
33
+ const id = React.useId();
34
+ const base = source?.queryKey ?? ["cortena-data-table", id];
35
+ const infinite = mode === "infinite";
36
+ const fetch = source?.fetch;
37
+ const page = useQuery({
38
+ queryKey: [
39
+ ...base,
40
+ "page",
41
+ query
42
+ ],
43
+ queryFn: ({ signal }) => fetch(query, signal),
44
+ enabled: Boolean(fetch) && !infinite,
45
+ placeholderData: keepPreviousData
46
+ });
47
+ const scope = scopeOf(query);
48
+ const pages = useInfiniteQuery({
49
+ queryKey: [
50
+ ...base,
51
+ "infinite",
52
+ scope
53
+ ],
54
+ queryFn: ({ pageParam, signal }) => fetch({
55
+ ...scope,
56
+ page: pageParam
57
+ }, signal),
58
+ initialPageParam: 0,
59
+ getNextPageParam: (last, all) => {
60
+ return all.reduce((n, p) => n + p.rows.length, 0) < last.total && last.rows.length > 0 ? all.length : void 0;
61
+ },
62
+ enabled: Boolean(fetch) && infinite,
63
+ placeholderData: keepPreviousData
64
+ });
65
+ const flatRows = React.useMemo(() => pages.data ? pages.data.pages.flatMap((p) => p.rows) : EMPTY, [pages.data]);
66
+ if (!fetch) return {
67
+ rows: EMPTY,
68
+ total: void 0,
69
+ isLoading: false,
70
+ isFetching: false,
71
+ error: null,
72
+ refetch: () => {},
73
+ fetchNextPage: () => {},
74
+ hasNextPage: false
75
+ };
76
+ if (infinite) return {
77
+ rows: flatRows,
78
+ total: pages.data?.pages.at(-1)?.total,
79
+ isLoading: pages.isPending,
80
+ isFetching: pages.isFetching,
81
+ error: pages.error ? toError(pages.error) : null,
82
+ refetch: () => void pages.refetch(),
83
+ fetchNextPage: () => {
84
+ if (pages.hasNextPage && !pages.isFetchingNextPage) pages.fetchNextPage();
85
+ },
86
+ hasNextPage: pages.hasNextPage
87
+ };
88
+ return {
89
+ rows: page.data?.rows ?? EMPTY,
90
+ total: page.data?.total,
91
+ isLoading: page.isPending,
92
+ isFetching: page.isFetching,
93
+ error: page.error ? toError(page.error) : null,
94
+ refetch: () => void page.refetch(),
95
+ fetchNextPage: () => {},
96
+ hasNextPage: false
97
+ };
98
+ }
99
+ function useLocalStrategy(source, query, mode) {
100
+ const infinite = mode === "infinite";
101
+ const fetch = source?.fetch;
102
+ const scope = scopeOf(query);
103
+ const scopeKey = JSON.stringify(scope);
104
+ const [tick, setTick] = React.useState(0);
105
+ const [wanted, setWanted] = React.useState({
106
+ scopeKey,
107
+ n: 1
108
+ });
109
+ const wantedPages = wanted.scopeKey === scopeKey ? wanted.n : 1;
110
+ const [state, setState] = React.useState({
111
+ pages: [],
112
+ scopeKey,
113
+ page: -1,
114
+ isFetching: false,
115
+ error: null
116
+ });
117
+ const loadedForScope = state.scopeKey === scopeKey ? state.pages : EMPTY;
118
+ const targetPage = infinite ? loadedForScope.length : query.page;
119
+ const needs = infinite ? loadedForScope.length < wantedPages : !(state.scopeKey === scopeKey && state.page === query.page && state.pages.length > 0);
120
+ const requestKey = `${scopeKey}|${targetPage}|${tick}`;
121
+ const fetchRef = React.useRef(fetch);
122
+ React.useEffect(() => {
123
+ fetchRef.current = fetch;
124
+ });
125
+ const hasFetch = Boolean(fetch);
126
+ React.useEffect(() => {
127
+ const run = fetchRef.current;
128
+ if (!run || !needs) return;
129
+ const controller = new AbortController();
130
+ setState((s) => ({
131
+ ...s,
132
+ isFetching: true
133
+ }));
134
+ run({
135
+ ...scope,
136
+ page: targetPage
137
+ }, controller.signal).then((result) => {
138
+ if (controller.signal.aborted) return;
139
+ setState((s) => ({
140
+ pages: infinite && s.scopeKey === scopeKey ? [...s.pages, result] : [result],
141
+ scopeKey,
142
+ page: targetPage,
143
+ isFetching: false,
144
+ error: null
145
+ }));
146
+ }, (err) => {
147
+ if (controller.signal.aborted) return;
148
+ setState((s) => ({
149
+ ...s,
150
+ isFetching: false,
151
+ error: toError(err)
152
+ }));
153
+ });
154
+ return () => controller.abort();
155
+ }, [
156
+ hasFetch,
157
+ requestKey,
158
+ needs
159
+ ]);
160
+ const rows = React.useMemo(() => infinite ? state.pages.flatMap((p) => p.rows) : state.pages[0]?.rows ?? EMPTY, [state.pages, infinite]);
161
+ const total = state.pages.at(-1)?.total;
162
+ const hasNextPage = infinite && total !== void 0 && rows.length < total;
163
+ if (!fetch) return {
164
+ rows: EMPTY,
165
+ total: void 0,
166
+ isLoading: false,
167
+ isFetching: false,
168
+ error: null,
169
+ refetch: () => {},
170
+ fetchNextPage: () => {},
171
+ hasNextPage: false
172
+ };
173
+ return {
174
+ rows,
175
+ total,
176
+ isLoading: state.pages.length === 0 && !state.error,
177
+ isFetching: state.isFetching,
178
+ error: state.error,
179
+ refetch: () => {
180
+ setState((s) => ({
181
+ ...s,
182
+ pages: [],
183
+ page: -1,
184
+ error: null
185
+ }));
186
+ setTick((t) => t + 1);
187
+ },
188
+ fetchNextPage: () => {
189
+ if (hasNextPage && !state.isFetching) setWanted({
190
+ scopeKey,
191
+ n: loadedForScope.length + 1
192
+ });
193
+ },
194
+ hasNextPage
195
+ };
196
+ }
197
+ //#endregion
198
+ export { useServerSource };
199
+
200
+ //# sourceMappingURL=use-server-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-server-source.js","names":[],"sources":["../../../src/components/data-table/use-server-source.ts"],"sourcesContent":["\"use client\";\n\nimport {\n keepPreviousData,\n QueryClientContext,\n useInfiniteQuery,\n useQuery,\n} from \"@tanstack/react-query\";\nimport * as React from \"react\";\nimport type { ServerSource, TableQuery, TableResult } from \"./types\";\n\nexport interface ServerSourceState<Row> {\n rows: Row[];\n total: number | undefined;\n isLoading: boolean;\n isFetching: boolean;\n error: Error | null;\n refetch: () => void;\n fetchNextPage: () => void;\n hasNextPage: boolean;\n}\n\nconst EMPTY: never[] = [];\n\nfunction toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value));\n}\n\n/** The part of a query that identifies a result set; the page is separate so infinite mode can append. */\nfunction scopeOf(query: TableQuery): Omit<TableQuery, \"page\"> {\n const { page: _page, ...scope } = query;\n return scope;\n}\n\n/**\n * Runs a server source. With a `QueryClientProvider` above the table, pages\n * go through TanStack Query: cached by query, previous page kept on screen\n * while the next loads, and the in-flight request aborted through `signal`\n * when the query changes. Without one, a small local fetch state does the\n * same minus the cache, so the table never requires the optional peer to be\n * wired up.\n *\n * The strategy is chosen once per mount: a provider appearing or vanishing\n * mid-life would change the hook order, which React forbids, and is not a\n * real scenario.\n */\nexport function useServerSource<Row>(\n source: ServerSource<Row> | undefined,\n query: TableQuery,\n mode: \"pages\" | \"infinite\" | \"none\",\n): ServerSourceState<Row> {\n const client = React.useContext(QueryClientContext);\n const [strategy] = React.useState<\"query\" | \"local\">(() => (client ? \"query\" : \"local\"));\n // Hook order is stable for the life of the component because `strategy` is.\n return strategy === \"query\"\n ? useQueryStrategy(source, query, mode)\n : useLocalStrategy(source, query, mode);\n}\n\nfunction useQueryStrategy<Row>(\n source: ServerSource<Row> | undefined,\n query: TableQuery,\n mode: \"pages\" | \"infinite\" | \"none\",\n): ServerSourceState<Row> {\n const id = React.useId();\n const base = source?.queryKey ?? [\"cortena-data-table\", id];\n const infinite = mode === \"infinite\";\n const fetch = source?.fetch;\n\n const page = useQuery({\n queryKey: [...base, \"page\", query],\n queryFn: ({ signal }) => fetch!(query, signal),\n enabled: Boolean(fetch) && !infinite,\n placeholderData: keepPreviousData,\n });\n\n const scope = scopeOf(query);\n const pages = useInfiniteQuery({\n queryKey: [...base, \"infinite\", scope],\n queryFn: ({ pageParam, signal }) => fetch!({ ...scope, page: pageParam }, signal),\n initialPageParam: 0,\n getNextPageParam: (last: TableResult<Row>, all: TableResult<Row>[]) => {\n const loaded = all.reduce((n, p) => n + p.rows.length, 0);\n return loaded < last.total && last.rows.length > 0 ? all.length : undefined;\n },\n enabled: Boolean(fetch) && infinite,\n placeholderData: keepPreviousData,\n });\n\n const flatRows = React.useMemo(\n () => (pages.data ? pages.data.pages.flatMap((p) => p.rows) : EMPTY),\n [pages.data],\n );\n\n if (!fetch) {\n return {\n rows: EMPTY,\n total: undefined,\n isLoading: false,\n isFetching: false,\n error: null,\n refetch: () => {},\n fetchNextPage: () => {},\n hasNextPage: false,\n };\n }\n\n if (infinite) {\n return {\n rows: flatRows,\n total: pages.data?.pages.at(-1)?.total,\n isLoading: pages.isPending,\n isFetching: pages.isFetching,\n error: pages.error ? toError(pages.error) : null,\n refetch: () => void pages.refetch(),\n fetchNextPage: () => {\n if (pages.hasNextPage && !pages.isFetchingNextPage) void pages.fetchNextPage();\n },\n hasNextPage: pages.hasNextPage,\n };\n }\n\n return {\n rows: page.data?.rows ?? EMPTY,\n total: page.data?.total,\n isLoading: page.isPending,\n isFetching: page.isFetching,\n error: page.error ? toError(page.error) : null,\n refetch: () => void page.refetch(),\n fetchNextPage: () => {},\n hasNextPage: false,\n };\n}\n\ninterface LocalState<Row> {\n pages: TableResult<Row>[];\n scopeKey: string;\n page: number;\n isFetching: boolean;\n error: Error | null;\n}\n\nfunction useLocalStrategy<Row>(\n source: ServerSource<Row> | undefined,\n query: TableQuery,\n mode: \"pages\" | \"infinite\" | \"none\",\n): ServerSourceState<Row> {\n const infinite = mode === \"infinite\";\n const fetch = source?.fetch;\n const scope = scopeOf(query);\n const scopeKey = JSON.stringify(scope);\n const [tick, setTick] = React.useState(0);\n // Pages wanted in infinite mode, scoped so a new query starts over at one.\n const [wanted, setWanted] = React.useState({ scopeKey, n: 1 });\n const wantedPages = wanted.scopeKey === scopeKey ? wanted.n : 1;\n const [state, setState] = React.useState<LocalState<Row>>({\n pages: [],\n scopeKey,\n page: -1,\n isFetching: false,\n error: null,\n });\n\n const loadedForScope = state.scopeKey === scopeKey ? state.pages : EMPTY;\n const targetPage = infinite ? loadedForScope.length : query.page;\n const needs = infinite\n ? loadedForScope.length < wantedPages\n : !(state.scopeKey === scopeKey && state.page === query.page && state.pages.length > 0);\n const requestKey = `${scopeKey}|${targetPage}|${tick}`;\n\n // The consumer's `fetch` is usually an inline arrow — `fetch: q => api.list(q)`\n // is the documented call — so it is a new function on every parent render.\n // Depending on it would abort and refire the request each time; the request's\n // identity is `requestKey`, so the latest function is read from a ref instead.\n const fetchRef = React.useRef(fetch);\n React.useEffect(() => {\n fetchRef.current = fetch;\n });\n\n const hasFetch = Boolean(fetch);\n\n React.useEffect(() => {\n const run = fetchRef.current;\n if (!run || !needs) return;\n const controller = new AbortController();\n setState((s) => ({ ...s, isFetching: true }));\n run({ ...scope, page: targetPage }, controller.signal).then(\n (result) => {\n if (controller.signal.aborted) return;\n setState((s) => ({\n pages: infinite && s.scopeKey === scopeKey ? [...s.pages, result] : [result],\n scopeKey,\n page: targetPage,\n isFetching: false,\n error: null,\n }));\n },\n (err: unknown) => {\n if (controller.signal.aborted) return;\n setState((s) => ({ ...s, isFetching: false, error: toError(err) }));\n },\n );\n return () => controller.abort();\n // `requestKey` is the identity of the request; `scope` and `targetPage` derive\n // from it. `fetch` itself is held in a ref, so only its presence is a dep.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hasFetch, requestKey, needs]);\n\n // Like keepPreviousData: the last result stays on screen while the next loads.\n const rows = React.useMemo(\n () => (infinite ? state.pages.flatMap((p) => p.rows) : (state.pages[0]?.rows ?? EMPTY)),\n [state.pages, infinite],\n );\n const total = state.pages.at(-1)?.total;\n const hasNextPage = infinite && total !== undefined && rows.length < total;\n\n if (!fetch) {\n return {\n rows: EMPTY,\n total: undefined,\n isLoading: false,\n isFetching: false,\n error: null,\n refetch: () => {},\n fetchNextPage: () => {},\n hasNextPage: false,\n };\n }\n\n return {\n rows,\n total,\n isLoading: state.pages.length === 0 && !state.error,\n isFetching: state.isFetching,\n error: state.error,\n refetch: () => {\n setState((s) => ({ ...s, pages: [], page: -1, error: null }));\n setTick((t) => t + 1);\n },\n fetchNextPage: () => {\n if (hasNextPage && !state.isFetching) setWanted({ scopeKey, n: loadedForScope.length + 1 });\n },\n hasNextPage,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ "use client";
2
+ import { CalendarProps } from "./calendar.js";
3
+ import * as React from "react";
4
+ //#region src/components/date-field.d.ts
5
+ /** `2026-01-05`. */
6
+ declare function formatDateISO(date: Date): string;
7
+ /** `5 Jan 2026` for en-GB; other locales via `Intl.DateTimeFormat`. */
8
+ declare function formatDateShort(date: Date, locale?: string): string;
9
+ /** `5 January 2026` for en-GB; other locales via `Intl.DateTimeFormat`. */
10
+ declare function formatDateLong(date: Date, locale?: string): string;
11
+ /**
12
+ * Calendar-day distance in words: `today`, `yesterday`, `tomorrow`, `3 days
13
+ * ago`, `in 2 weeks`, `4 months ago`, `in 2 years`. Compares local calendar
14
+ * days, so 23:59 to 00:01 is `tomorrow`. Other locales use
15
+ * `Intl.RelativeTimeFormat`.
16
+ */
17
+ declare function formatRelative(date: Date, options?: {
18
+ now?: Date;
19
+ locale?: string;
20
+ }): string;
21
+ /** Strict `yyyy-mm-dd`; rejects impossible days such as `2026-02-30`. */
22
+ declare function parseDateISO(text: string): Date | undefined;
23
+ /** ISO first, then the English `5 Jan 2026` / `5 January 2026` shapes. */
24
+ declare function parseDateDefault(text: string): Date | undefined;
25
+ type CalendarPassthrough = Omit<CalendarProps, "mode" | "selected" | "onSelect" | "required" | "disabled">;
26
+ export interface DateFieldProps extends Omit<React.ComponentProps<"input">, "value" | "defaultValue" | "onChange" | "min" | "max" | "type"> {
27
+ /** The date (controlled). */
28
+ value?: Date | undefined;
29
+ /** The initial date (uncontrolled). */
30
+ defaultValue?: Date | undefined;
31
+ /** Called with the parsed or picked date, or `undefined` when the text is cleared or invalid. */
32
+ onValueChange?: (date: Date | undefined) => void;
33
+ /** Input placeholder; defaults to the ISO shape. */
34
+ placeholder?: string;
35
+ /** Formats the date into the input; defaults to ISO `yyyy-mm-dd`. */
36
+ formatDate?: (date: Date) => string;
37
+ /** Parses typed text; defaults to ISO plus the English day-month-year shapes. */
38
+ parseDate?: (text: string) => Date | undefined;
39
+ /** Earliest selectable day. */
40
+ min?: Date;
41
+ /** Latest selectable day. */
42
+ max?: Date;
43
+ disabled?: boolean;
44
+ /** Force the invalid look (e.g. from a form validator). */
45
+ invalid?: boolean;
46
+ /** Extra props for the Calendar (e.g. `captionLayout`, `today`). */
47
+ calendarProps?: CalendarPassthrough;
48
+ /** Class name for the wrapper; use `className` for the input itself. */
49
+ wrapperClassName?: string;
50
+ }
51
+ declare function DateField(props: DateFieldProps): React.JSX.Element;
52
+ //#endregion
53
+ export { DateField, formatDateISO, formatDateLong, formatDateShort, formatRelative, parseDateDefault, parseDateISO };
54
+ //# sourceMappingURL=date-field.d.ts.map