@nuvui/table 0.0.0-stage → 0.1.1

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 (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -2
  3. package/dist/components/data-table/content.d.ts +67 -0
  4. package/dist/components/data-table/content.js +278 -0
  5. package/dist/components/data-table/context.d.ts +23 -0
  6. package/dist/components/data-table/context.js +51 -0
  7. package/dist/components/data-table/create-data-table-hook.d.ts +78 -0
  8. package/dist/components/data-table/create-data-table-hook.js +103 -0
  9. package/dist/components/data-table/data-table.d.ts +76 -0
  10. package/dist/components/data-table/data-table.js +178 -0
  11. package/dist/components/data-table/header.d.ts +23 -0
  12. package/dist/components/data-table/header.js +120 -0
  13. package/dist/components/data-table/index.d.ts +11 -0
  14. package/dist/components/data-table/index.js +10 -0
  15. package/dist/components/data-table/labels.d.ts +50 -0
  16. package/dist/components/data-table/labels.js +35 -0
  17. package/dist/components/data-table/pagination.d.ts +18 -0
  18. package/dist/components/data-table/pagination.js +126 -0
  19. package/dist/components/data-table/selection.d.ts +48 -0
  20. package/dist/components/data-table/selection.js +153 -0
  21. package/dist/components/data-table/toolbar.d.ts +51 -0
  22. package/dist/components/data-table/toolbar.js +123 -0
  23. package/dist/components/data-table/use-data-table-request.d.ts +44 -0
  24. package/dist/components/data-table/use-data-table-request.js +72 -0
  25. package/dist/components/table/index.d.ts +2 -0
  26. package/dist/components/table/index.js +2 -0
  27. package/dist/components/table/table.d.ts +100 -0
  28. package/dist/components/table/table.js +130 -0
  29. package/dist/css/data-table.css +278 -0
  30. package/dist/css/table.css +164 -0
  31. package/dist/full.d.ts +264 -0
  32. package/dist/full.js +44 -0
  33. package/dist/index.d.ts +14 -0
  34. package/dist/index.js +11 -0
  35. package/dist/styles.css +441 -0
  36. package/dist/utils/cx.js +6 -0
  37. package/dist/utils/use-shift-click.js +32 -0
  38. package/dist/virtual.d.ts +42 -0
  39. package/dist/virtual.js +133 -0
  40. package/package.json +101 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nuvraxis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,58 @@
1
- # Temporary Holding Version
1
+ # @nuvui/table
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Tables for [`@nuvui/react`](https://www.npmjs.com/package/@nuvui/react): the elements of a table, styled, and a data table drawn from a [TanStack Table](https://tanstack.com/table) 9 instance, with the controls that go with it.
4
+
5
+ This is early: the package is on npm as a 0.x version, and before 1.0 a minor version may rename or remove things. The [changelog](https://nuvui.nuvraxis.com/docs/changelog) says where one does, and the [repository README](https://github.com/Nuvraxis/nuvui#readme) says where things stand.
6
+
7
+ ```sh
8
+ pnpm add @nuvui/table @nuvui/react @tanstack/react-table
9
+ ```
10
+
11
+ ```tsx
12
+ "use client";
13
+
14
+ import "@nuvui/react/styles.css";
15
+ import "@nuvui/table/styles.css";
16
+ import { DataTable } from "@nuvui/table";
17
+ import { createColumnHelper, useDataTable } from "@nuvui/table/full";
18
+
19
+ type Person = { id: string; name: string; email: string };
20
+
21
+ const helper = createColumnHelper<Person>();
22
+ const columns = helper.columns([
23
+ helper.accessor("name", { header: "Name" }),
24
+ helper.accessor("email", { header: "Email" }),
25
+ ]);
26
+
27
+ export function People({ people }: { people: Person[] }) {
28
+ const table = useDataTable({ columns, data: people });
29
+ return <DataTable table={table} caption="People" rowHeader="name" />;
30
+ }
31
+ ```
32
+
33
+ ## What's in it
34
+
35
+ - `Table`, `TableContainer`, `TableHeader`, `TableBody`, `TableFooter`, `TableRow`, `TableHead`, `TableCell` and `TableCaption`: a real `table`, styled, for a table written out by hand. A sticky header, a pinned column, and a box that scrolls sideways and can be reached from the keyboard.
36
+ - `DataTable`: a finished table from a TanStack table. Headings that sort, a search field, a column chooser, checkboxes with a bar for the selected rows, a pagination bar, pinned and resizable columns, rows that open, and loading, empty and error states. Each is there when the table was made with the feature for it.
37
+ - `createDataTableHook`: makes the hook your tables are created with, from the TanStack features you list. Only those end up in your bundle.
38
+ - `@nuvui/table/full`: that hook, ready made, with all nine features.
39
+ - `useDataTableRequest`: for a table whose sorting, filtering and paging are done by a server.
40
+ - `@nuvui/table/virtual`: the data table with only the rows in view drawn. It needs `@tanstack/react-virtual`.
41
+
42
+ TanStack Table keeps a table's state and works out its rows. The markup, the styles, the controls and the accessibility are this package's: a caption or label, `scope` on headings, `aria-sort`, named checkboxes, and a live region that reads out what a sort, a filter or a change of page did.
43
+
44
+ ## Peer dependencies
45
+
46
+ `@nuvui/react` and `@tanstack/react-table` 9 are peer dependencies. Your columns are written with TanStack's own helpers and types, so your app owns that version. `@tanstack/react-virtual` is an optional peer, needed only for `@nuvui/table/virtual`.
47
+
48
+ TanStack Table 9 is a different API from version 8. The docs have a page for people coming from version 8.
49
+
50
+ The package is ES modules only, as `@tanstack/react-table` is.
51
+
52
+ It's a package of its own so that an app which only wants buttons and dialogs doesn't have to install a table engine.
53
+
54
+ The docs are at https://nuvui.nuvraxis.com/docs/table.
55
+
56
+ ## License
57
+
58
+ MIT
@@ -0,0 +1,67 @@
1
+ import "./context.js";
2
+ import { Row, RowData, TableFeatures } from "@tanstack/react-table";
3
+ import { HTMLAttributes, ReactNode } from "react";
4
+ //#region src/components/data-table/content.d.ts
5
+ /** A table has to have a name. One of these gives it one. */
6
+ export type DataTableName = {
7
+ /** The table's name, shown under it. */
8
+ caption: ReactNode;
9
+ "aria-label"?: undefined;
10
+ "aria-labelledby"?: undefined;
11
+ } | {
12
+ caption?: undefined;
13
+ "aria-label": string;
14
+ "aria-labelledby"?: undefined;
15
+ } | {
16
+ caption?: undefined;
17
+ "aria-label"?: undefined;
18
+ /** The id of a heading that's already on the page. */
19
+ "aria-labelledby": string;
20
+ };
21
+ export interface DataTableContentOwnProps<TFeatures extends TableFeatures = any, TData extends RowData = any> {
22
+ /**
23
+ * Keep the caption for screen readers and show nothing.
24
+ * @default false
25
+ */
26
+ captionHidden?: boolean;
27
+ /**
28
+ * Keep the header row in view while the rows scroll. Give the table a
29
+ * height to scroll in with `--nuv-table-container-max-height`.
30
+ * @default false
31
+ */
32
+ stickyHeader?: boolean;
33
+ /**
34
+ * Shade every second row.
35
+ * @default false
36
+ */
37
+ striped?: boolean;
38
+ /**
39
+ * `"auto"` sizes each column to what's in it. `"fixed"` gives each column
40
+ * the width TanStack holds for it, which needs `columnSizingFeature`, and
41
+ * puts a grip on every column that can be resized.
42
+ * @default "auto"
43
+ */
44
+ layout?: "auto" | "fixed";
45
+ /**
46
+ * What a row shows under itself when it's open. With this set, a row can
47
+ * be opened if `getRowCanExpand` says so.
48
+ */
49
+ renderDetail?: (row: Row<TFeatures, TData>) => ReactNode;
50
+ /** Shown in place of rows when there are none. */
51
+ empty?: ReactNode;
52
+ /**
53
+ * How many placeholder rows to show while the first rows load.
54
+ * @default the page size, up to 10
55
+ */
56
+ loadingRows?: number;
57
+ className?: string;
58
+ }
59
+ export type DataTableContentProps<TFeatures extends TableFeatures = any, TData extends RowData = any> = DataTableContentOwnProps<TFeatures, TData> & DataTableName;
60
+ /**
61
+ * The table itself: the headings, the rows, and what's shown when there
62
+ * are no rows. `DataTable` puts this between the toolbar and the
63
+ * pagination bar. Use it yourself, inside `DataTableRoot`, to lay those
64
+ * out differently.
65
+ */
66
+ export declare function DataTableContent<TFeatures extends TableFeatures = any, TData extends RowData = any>(props: DataTableContentProps<TFeatures, TData>): import("react").JSX.Element;
67
+ //#endregion
@@ -0,0 +1,278 @@
1
+ "use client";
2
+ import { anyFilter, canResize, columnLabel, columnMeta, expanded, leafColumns, pinned, selected, sorted, supports, tableContexts, useDataTableView } from "./context.js";
3
+ import { cx } from "../../utils/cx.js";
4
+ import { DataTableColumnHeader, DataTableResizeHandle } from "./header.js";
5
+ import { Table, TableBody, TableCaption, TableCell, TableContainer, TableFooter, TableHead, TableHeader, TableRow } from "../table/table.js";
6
+ import { Fragment, useEffect, useId, useLayoutEffect, useRef } from "react";
7
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
8
+ import { Button } from "@nuvui/react/button";
9
+ import { Skeleton } from "@nuvui/react/skeleton";
10
+ //#region src/components/data-table/content.tsx
11
+ const useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
12
+ function pins(columns) {
13
+ const result = /* @__PURE__ */ new Map();
14
+ const start = columns.filter((column) => pinned(column) === "start");
15
+ const end = columns.filter((column) => pinned(column) === "end").reverse();
16
+ start.forEach((column, index) => {
17
+ result.set(column.id, {
18
+ side: "start",
19
+ index,
20
+ edge: index === start.length - 1
21
+ });
22
+ });
23
+ end.forEach((column, index) => {
24
+ result.set(column.id, {
25
+ side: "end",
26
+ index,
27
+ edge: index === end.length - 1
28
+ });
29
+ });
30
+ return result;
31
+ }
32
+ function headerPin(column, pinMap) {
33
+ const leaves = column.getLeafColumns();
34
+ const first = pinMap.get(leaves[0]?.id ?? "");
35
+ if (first?.side !== "end") return first;
36
+ return pinMap.get(leaves[leaves.length - 1]?.id ?? "");
37
+ }
38
+ const pinStyle = (pin) => pin ? { [pin.side === "start" ? "insetInlineStart" : "insetInlineEnd"]: `var(--nuv-data-table-pin-${pin.side}-${pin.index}, 0px)` } : void 0;
39
+ function usePinOffsets(table, key) {
40
+ useIsomorphicLayoutEffect(() => {
41
+ const element = table.current;
42
+ if (!element || !key) return;
43
+ const cells = [...element.querySelectorAll("thead [data-pin]")];
44
+ const measure = () => {
45
+ for (const side of ["start", "end"]) {
46
+ const ordered = cells.filter((cell) => cell.dataset.pin === side).sort((a, b) => Number(a.dataset.pinIndex) - Number(b.dataset.pinIndex));
47
+ let offset = 0;
48
+ for (const cell of ordered) {
49
+ element.style.setProperty(`--nuv-data-table-pin-${side}-${cell.dataset.pinIndex}`, `${offset}px`);
50
+ offset += cell.getBoundingClientRect().width;
51
+ }
52
+ }
53
+ };
54
+ measure();
55
+ if (typeof ResizeObserver === "undefined") return;
56
+ const observer = new ResizeObserver(measure);
57
+ for (const cell of cells) observer.observe(cell);
58
+ return () => observer.disconnect();
59
+ }, [key]);
60
+ }
61
+ const ariaSort = (column) => {
62
+ const direction = sorted(column);
63
+ if (!direction || column.getSortIndex() > 0) return void 0;
64
+ return direction === "asc" ? "ascending" : "descending";
65
+ };
66
+ function HeaderCell({ header, table, pin, resizable, footer = false }) {
67
+ const { column } = header;
68
+ const leaf = column.columns.length === 0;
69
+ const meta = columnMeta(column);
70
+ const template = footer ? column.columnDef.footer : column.columnDef.header;
71
+ let content = null;
72
+ if (footer) content = header.isPlaceholder ? null : /* @__PURE__ */ jsx(table.FlexRender, { footer: header });
73
+ else if (typeof template === "string") content = template ? /* @__PURE__ */ jsx(DataTableColumnHeader, {
74
+ column,
75
+ children: template
76
+ }) : null;
77
+ else if (template) content = /* @__PURE__ */ jsx(table.FlexRender, { header });
78
+ const shared = {
79
+ colSpan: header.colSpan > 1 ? header.colSpan : void 0,
80
+ align: meta.align,
81
+ pinned: pin?.side,
82
+ pinnedEdge: pin?.edge,
83
+ style: pinStyle(pin)
84
+ };
85
+ let cell;
86
+ if (footer) cell = /* @__PURE__ */ jsx(TableCell, {
87
+ ...shared,
88
+ children: content
89
+ });
90
+ else if (content === null || meta.control) cell = /* @__PURE__ */ jsx(TableCell, {
91
+ ...shared,
92
+ rowSpan: header.rowSpan > 1 ? header.rowSpan : void 0,
93
+ className: cx("nuv-table__head", meta.control && "nuv-data-table__cell--control"),
94
+ "data-pin": leaf ? pin?.side : void 0,
95
+ "data-pin-index": leaf ? pin?.index : void 0,
96
+ children: content
97
+ });
98
+ else {
99
+ const handle = leaf && resizable && canResize(column);
100
+ cell = /* @__PURE__ */ jsxs(TableHead, {
101
+ ...shared,
102
+ rowSpan: header.rowSpan > 1 ? header.rowSpan : void 0,
103
+ "aria-sort": leaf ? ariaSort(column) : void 0,
104
+ "aria-label": handle ? columnLabel(column) : void 0,
105
+ className: cx(handle && !pin && "nuv-data-table__head--resizable"),
106
+ "data-pin": leaf ? pin?.side : void 0,
107
+ "data-pin-index": leaf ? pin?.index : void 0,
108
+ children: [content, handle ? /* @__PURE__ */ jsx(DataTableResizeHandle, { header }) : null]
109
+ });
110
+ }
111
+ if (table.AppHeader) {
112
+ const Wrapper = footer && table.AppFooter || table.AppHeader;
113
+ return /* @__PURE__ */ jsx(Wrapper, {
114
+ header,
115
+ children: () => cell
116
+ });
117
+ }
118
+ return /* @__PURE__ */ jsx(tableContexts.headerContext.Provider, {
119
+ value: header,
120
+ children: cell
121
+ });
122
+ }
123
+ function BodyCell({ row, column, table, pin, rowHeader }) {
124
+ const cell = row.getAllCellsByColumnId()[column.id];
125
+ if (!cell) return null;
126
+ const meta = columnMeta(column);
127
+ const element = /* @__PURE__ */ jsx(TableCell, {
128
+ rowHeader,
129
+ align: meta.align,
130
+ pinned: pin?.side,
131
+ pinnedEdge: pin?.edge,
132
+ style: pinStyle(pin),
133
+ className: cx(meta.control && "nuv-data-table__cell--control"),
134
+ children: /* @__PURE__ */ jsx(table.FlexRender, { cell })
135
+ });
136
+ if (table.AppCell) return /* @__PURE__ */ jsx(table.AppCell, {
137
+ cell,
138
+ children: () => element
139
+ });
140
+ return /* @__PURE__ */ jsx(tableContexts.cellContext.Provider, {
141
+ value: cell,
142
+ children: element
143
+ });
144
+ }
145
+ /**
146
+ * What the two tables share: the box, the headings, the footer, and what's
147
+ * shown when there are no rows. The rows themselves are drawn by `children`,
148
+ * which is where a plain table and a virtual one differ.
149
+ */
150
+ function ContentFrame({ caption, captionHidden = false, stickyHeader = false, striped = false, layout = "auto", renderDetail, empty, loadingRows, className, containerRef, containerProps, tableProps, children, ...name }) {
151
+ const { table, labels, rowHeader, loading, error, onRetry } = useDataTableView("DataTableContent");
152
+ const element = useRef(null);
153
+ const captionId = useId();
154
+ const headerGroups = table.getHeaderGroups();
155
+ const columns = leafColumns(table);
156
+ const pinMap = supports.pinning(table) ? pins(columns) : /* @__PURE__ */ new Map();
157
+ usePinOffsets(element, [...pinMap].map(([id, pin]) => `${id}:${pin.side}`).join(","));
158
+ const sized = layout === "fixed" && supports.sizing(table);
159
+ const resizable = sized && supports.resizing(table);
160
+ const rows = table.getRowModel().rows;
161
+ const hasFooter = table.getAllLeafColumns().some((column) => column.columnDef.footer);
162
+ const renderRow = (row, options = {}) => {
163
+ const open = renderDetail !== void 0 && expanded(row);
164
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(TableRow, {
165
+ selected: selected(row),
166
+ ...options.rowProps,
167
+ children: columns.map((column) => /* @__PURE__ */ jsx(BodyCell, {
168
+ row,
169
+ column,
170
+ table,
171
+ pin: pinMap.get(column.id),
172
+ rowHeader: column.id === rowHeader
173
+ }, column.id))
174
+ }), open ? /* @__PURE__ */ jsx(TableRow, {
175
+ className: "nuv-data-table__detail-row",
176
+ children: /* @__PURE__ */ jsx(TableCell, {
177
+ colSpan: columns.length,
178
+ className: "nuv-data-table__detail",
179
+ children: renderDetail(row)
180
+ })
181
+ }) : null] }, row.id);
182
+ };
183
+ const message = (content, alert = false) => /* @__PURE__ */ jsx(TableRow, {
184
+ className: "nuv-data-table__message-row",
185
+ children: /* @__PURE__ */ jsx(TableCell, {
186
+ colSpan: columns.length,
187
+ className: "nuv-data-table__message",
188
+ role: alert ? "alert" : void 0,
189
+ children: content
190
+ })
191
+ });
192
+ let body;
193
+ if (error) body = message(/* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", { children: error === true ? labels.error : error }), onRetry ? /* @__PURE__ */ jsx(Button, {
194
+ intent: "secondary",
195
+ size: "sm",
196
+ onClick: onRetry,
197
+ children: labels.retry
198
+ }) : null] }), true);
199
+ else if (rows.length > 0) body = children(rows, renderRow, headerGroups.length);
200
+ else if (loading) {
201
+ const pageSize = table.atoms.pagination?.get().pageSize;
202
+ const count = loadingRows ?? Math.min(pageSize ?? 5, 10);
203
+ body = Array.from({ length: count }, (_, index) => /* @__PURE__ */ jsx(TableRow, {
204
+ className: "nuv-data-table__loading-row",
205
+ children: columns.map((column) => /* @__PURE__ */ jsx(TableCell, {
206
+ className: cx(columnMeta(column).control && "nuv-data-table__cell--control"),
207
+ children: /* @__PURE__ */ jsx(Skeleton, { shape: "text" })
208
+ }, column.id))
209
+ }, index));
210
+ } else if (anyFilter(table)) body = message(/* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", { children: labels.noMatches }), /* @__PURE__ */ jsx(Button, {
211
+ intent: "secondary",
212
+ size: "sm",
213
+ onClick: () => {
214
+ if (supports.columnFilters(table)) table.resetColumnFilters(true);
215
+ if (supports.globalFilter(table)) table.setGlobalFilter("");
216
+ },
217
+ children: labels.clearFilters
218
+ })] }));
219
+ else body = message(empty ?? labels.empty);
220
+ const labelledBy = caption != null ? captionId : name["aria-labelledby"];
221
+ return /* @__PURE__ */ jsx(TableContainer, {
222
+ ref: containerRef,
223
+ "aria-label": name["aria-label"],
224
+ "aria-labelledby": labelledBy,
225
+ ...containerProps,
226
+ className: cx("nuv-data-table__container", className, containerProps?.className),
227
+ children: /* @__PURE__ */ jsxs(Table, {
228
+ ref: element,
229
+ stickyHeader,
230
+ striped,
231
+ layout,
232
+ "aria-label": name["aria-label"],
233
+ "aria-labelledby": caption != null ? void 0 : name["aria-labelledby"],
234
+ "aria-busy": loading || void 0,
235
+ style: sized ? { inlineSize: table.getTotalSize() } : void 0,
236
+ ...tableProps,
237
+ children: [
238
+ caption != null ? /* @__PURE__ */ jsx(TableCaption, {
239
+ id: captionId,
240
+ visuallyHidden: captionHidden,
241
+ children: caption
242
+ }) : null,
243
+ sized ? /* @__PURE__ */ jsx("colgroup", { children: columns.map((column) => /* @__PURE__ */ jsx("col", { style: { inlineSize: column.getSize() } }, column.id)) }) : null,
244
+ /* @__PURE__ */ jsx(TableHeader, { children: headerGroups.map((group, index) => /* @__PURE__ */ jsx(TableRow, {
245
+ "aria-rowindex": tableProps?.["aria-rowcount"] !== void 0 ? index + 1 : void 0,
246
+ children: group.headers.map((header) => header.rowSpan === 0 ? null : /* @__PURE__ */ jsx(HeaderCell, {
247
+ header,
248
+ table,
249
+ pin: headerPin(header.column, pinMap),
250
+ resizable
251
+ }, header.id))
252
+ }, group.id)) }),
253
+ /* @__PURE__ */ jsx(TableBody, { children: body }),
254
+ hasFooter ? /* @__PURE__ */ jsx(TableFooter, { children: /* @__PURE__ */ jsx(TableRow, { children: (table.getFooterGroups()[0]?.headers ?? []).map((header) => /* @__PURE__ */ jsx(HeaderCell, {
255
+ header,
256
+ table,
257
+ pin: pinMap.get(header.column.id),
258
+ resizable: false,
259
+ footer: true
260
+ }, header.id)) }) }) : null
261
+ ]
262
+ })
263
+ });
264
+ }
265
+ /**
266
+ * The table itself: the headings, the rows, and what's shown when there
267
+ * are no rows. `DataTable` puts this between the toolbar and the
268
+ * pagination bar. Use it yourself, inside `DataTableRoot`, to lay those
269
+ * out differently.
270
+ */
271
+ function DataTableContent(props) {
272
+ return /* @__PURE__ */ jsx(ContentFrame, {
273
+ ...props,
274
+ children: (rows, renderRow) => rows.map((row) => renderRow(row))
275
+ });
276
+ }
277
+ //#endregion
278
+ export { ContentFrame, DataTableContent };
@@ -0,0 +1,23 @@
1
+ import { AppReactTable, Column, ReactTable, Row } from "@tanstack/react-table";
2
+ import { ReactNode } from "react";
3
+ //#region src/components/data-table/context.d.ts
4
+ /**
5
+ * What a column can say about itself for the controls. Register it with
6
+ * `columnMeta: metaHelper<DataTableColumnMeta>()` in `tableFeatures` and
7
+ * a column's `meta` is typed.
8
+ */
9
+ export interface DataTableColumnMeta {
10
+ /**
11
+ * The column's name in the column chooser and for screen readers. A
12
+ * column whose `header` is a string doesn't need one.
13
+ */
14
+ label?: string;
15
+ /** Which side the column's cells and heading sit on. */
16
+ align?: "start" | "center" | "end";
17
+ /**
18
+ * The column only holds a control, such as a checkbox. It's kept as
19
+ * narrow as the control.
20
+ */
21
+ control?: boolean;
22
+ }
23
+ //#endregion
@@ -0,0 +1,51 @@
1
+ "use client";
2
+ import { createTableHookContexts } from "@tanstack/react-table";
3
+ import { createContext, useContext } from "react";
4
+ //#region src/components/data-table/context.tsx
5
+ const tableContexts = createTableHookContexts();
6
+ const ViewContext = createContext(null);
7
+ function useDataTableView(part) {
8
+ const view = useContext(ViewContext);
9
+ if (!view) throw new Error(`${part} has to be inside a DataTable or DataTableRoot.`);
10
+ return view;
11
+ }
12
+ const useHeader = () => useContext(tableContexts.headerContext);
13
+ const useCell = () => useContext(tableContexts.cellContext);
14
+ const is = (value) => typeof value === "function";
15
+ const supports = {
16
+ sorting: (table) => is(table.setSorting),
17
+ columnFilters: (table) => is(table.setColumnFilters),
18
+ globalFilter: (table) => is(table.setGlobalFilter),
19
+ pagination: (table) => is(table.setPagination),
20
+ selection: (table) => is(table.setRowSelection),
21
+ visibility: (table) => is(table.setColumnVisibility),
22
+ pinning: (table) => is(table.setColumnPinning),
23
+ sizing: (table) => is(table.setColumnSizing),
24
+ resizing: (table) => is(table.setColumnResizing),
25
+ expanding: (table) => is(table.setExpanded)
26
+ };
27
+ const columnMeta = (column) => column.columnDef.meta ?? {};
28
+ function columnLabel(column) {
29
+ const { label } = columnMeta(column);
30
+ if (label) return label;
31
+ const { header } = column.columnDef;
32
+ return typeof header === "string" && header ? header : column.id;
33
+ }
34
+ const canSort = (column) => is(column.getCanSort) && column.getCanSort();
35
+ const sorted = (column) => is(column.getIsSorted) ? column.getIsSorted() : false;
36
+ const pinned = (column) => is(column.getIsPinned) ? column.getIsPinned() : false;
37
+ const canResize = (column) => is(column.getCanResize) && is(column.getSize) && column.getCanResize();
38
+ const canHide = (column) => is(column.getCanHide) && column.getCanHide();
39
+ const canExpand = (row) => is(row.getCanExpand) && row.getCanExpand();
40
+ const expanded = (row) => is(row.getIsExpanded) && row.getIsExpanded();
41
+ const selected = (row) => is(row.getIsSelected) && row.getIsSelected();
42
+ function leafColumns(table) {
43
+ return table.getHeaderGroups().flatMap((group) => group.headers).filter((header) => header.subHeaders.length === 0).map((header) => header.column);
44
+ }
45
+ function anyFilter(table) {
46
+ const columnFilters = table.atoms.columnFilters?.get() ?? [];
47
+ const globalFilter = table.atoms.globalFilter?.get();
48
+ return columnFilters.length > 0 || globalFilter != null && globalFilter !== "";
49
+ }
50
+ //#endregion
51
+ export { ViewContext, anyFilter, canExpand, canHide, canResize, canSort, columnLabel, columnMeta, expanded, leafColumns, pinned, selected, sorted, supports, tableContexts, useCell, useDataTableView, useHeader };
@@ -0,0 +1,78 @@
1
+ import { DataTableColumnHeader } from "./header.js";
2
+ import { DataTablePagination } from "./pagination.js";
3
+ import { DataTableBulkActions, DataTableExpandToggle, DataTableSelectAll, DataTableSelectRow } from "./selection.js";
4
+ import { DataTableColumnChooser, DataTableColumnFilter, DataTableFilter, DataTableToolbar } from "./toolbar.js";
5
+ import { AppColumnHelper, AppDisplayColumnDef, AppReactTable, CreateTableHookOptions, DisplayColumnDef, RowData, TableFeatures, TableOptions, TableState } from "@tanstack/react-table";
6
+ import { ComponentType } from "react";
7
+ //#region src/components/data-table/create-data-table-hook.d.ts
8
+ declare const tableComponents: {
9
+ Toolbar: typeof DataTableToolbar;
10
+ Filter: typeof DataTableFilter;
11
+ ColumnChooser: typeof DataTableColumnChooser;
12
+ BulkActions: typeof DataTableBulkActions;
13
+ Pagination: typeof DataTablePagination;
14
+ };
15
+ declare const headerComponents: {
16
+ ColumnHeader: typeof DataTableColumnHeader;
17
+ ColumnFilter: typeof DataTableColumnFilter;
18
+ SelectAll: typeof DataTableSelectAll;
19
+ };
20
+ declare const cellComponents: {
21
+ SelectRow: typeof DataTableSelectRow;
22
+ ExpandToggle: typeof DataTableExpandToggle;
23
+ };
24
+ type Components = Record<string, ComponentType<any>>;
25
+ type None = {};
26
+ type TableComponents<T extends Components> = typeof tableComponents & T;
27
+ type HeaderComponents<T extends Components> = typeof headerComponents & T;
28
+ type CellComponents<T extends Components> = typeof cellComponents & T;
29
+ type ControlColumn<TFeatures extends TableFeatures, TData extends RowData, TCell extends Components, THeader extends Components> = Partial<AppDisplayColumnDef<TFeatures, TData, CellComponents<TCell>, HeaderComponents<THeader>>>;
30
+ export type DataTableColumnHelper<TFeatures extends TableFeatures, TData extends RowData, TCell extends Components = None, THeader extends Components = None> = AppColumnHelper<TFeatures, TData, CellComponents<TCell>, HeaderComponents<THeader>> & {
31
+ /**
32
+ * A column of checkboxes: one a row, and one in the heading for all of
33
+ * them. Needs `rowSelectionFeature`.
34
+ */
35
+ select: (column?: ControlColumn<TFeatures, TData, TCell, THeader>) => DisplayColumnDef<TFeatures, TData, unknown>;
36
+ /**
37
+ * A column of buttons that open and close a row. Needs
38
+ * `rowExpandingFeature`.
39
+ */
40
+ expand: (column?: ControlColumn<TFeatures, TData, TCell, THeader>) => DisplayColumnDef<TFeatures, TData, unknown>;
41
+ };
42
+ export type DataTableHookOptions<TFeatures extends TableFeatures, TTable extends Components = None, TCell extends Components = None, THeader extends Components = None> = Omit<CreateTableHookOptions<TFeatures, TTable, TCell, THeader>, "tableContext" | "cellContext" | "headerContext">;
43
+ /**
44
+ * Makes the hook an app's tables are created with. Call it once, in a
45
+ * module of its own, with the features your tables use. Only those end up
46
+ * in your bundle.
47
+ *
48
+ * It's TanStack's `createTableHook` with this package's controls already
49
+ * registered, and takes the same options. What it returns:
50
+ *
51
+ * - `useDataTable`: `useTable`, bound to the features. Its table has the
52
+ * controls on it: `table.Pagination`, `table.Filter` and so on.
53
+ * - `createColumnHelper`: TanStack's column helper, bound to the features,
54
+ * with `select()` and `expand()` added. In a column's `header` and `cell`
55
+ * the controls are on what it's given: `header.ColumnHeader`,
56
+ * `cell.SelectRow`.
57
+ */
58
+ export declare function createDataTableHook<TFeatures extends TableFeatures, const TTable extends Components = None, const TCell extends Components = None, const THeader extends Components = None>(options: DataTableHookOptions<TFeatures, TTable, TCell, THeader>): {
59
+ /** The features object that was passed in. */
60
+ features: TFeatures;
61
+ useDataTable: <TData extends RowData, TSelected = TableState<TFeatures>>(tableOptions: Omit<TableOptions<TFeatures, TData>, "features">, selector?: (state: TableState<TFeatures>) => TSelected) => AppReactTable<TFeatures, TData, TSelected, TableComponents<TTable>, CellComponents<TCell>, HeaderComponents<THeader>>;
62
+ createColumnHelper: <TData extends RowData>() => DataTableColumnHelper<TFeatures, TData, TCell, THeader>;
63
+ useTableContext: <TData extends RowData = RowData, TSelected = import("@tanstack/react-table").ExtractFeatureMapTypes<TFeatures, import("@tanstack/react-table").TableState_FeatureMap>>() => AppReactTable<TFeatures, TData, TSelected, TableComponents<TTable>, CellComponents<TCell>, HeaderComponents<THeader>>;
64
+ useHeaderContext: <TValue extends import("@tanstack/react-table").CellData = unknown>() => import("@tanstack/react-table").Header_Core<TFeatures, any, TValue> & import("@tanstack/react-table").ExtractFeatureMapTypes<TFeatures, import("@tanstack/react-table").Header_FeatureMap> & {
65
+ ColumnHeader: typeof DataTableColumnHeader;
66
+ ColumnFilter: typeof DataTableColumnFilter;
67
+ SelectAll: typeof DataTableSelectAll;
68
+ } & THeader & {
69
+ FlexRender: () => import("react").ReactNode;
70
+ };
71
+ useCellContext: <TValue extends import("@tanstack/react-table").CellData = unknown>() => import("@tanstack/react-table").Cell_Core<TFeatures, any, TValue> & import("@tanstack/react-table").ExtractFeatureMapTypes<TFeatures, import("@tanstack/react-table").Cell_FeatureMap> & {
72
+ SelectRow: typeof DataTableSelectRow;
73
+ ExpandToggle: typeof DataTableExpandToggle;
74
+ } & TCell & {
75
+ FlexRender: () => import("react").ReactNode;
76
+ };
77
+ };
78
+ //#endregion