aplance-ui 1.5.1 → 1.6.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.
- package/README.md +1 -1
- package/dist/api.d.ts +2 -1
- package/dist/api.js +103 -68
- package/dist/api.js.map +1 -1
- package/dist/frameworks/vite-react/vite.config.ts.template +1 -1
- package/dist/index.js +103 -68
- package/dist/index.js.map +1 -1
- package/package.json +12 -6
- package/registry/components/primitives/modal/modal.module.css +9 -3
- package/registry/components/primitives/popover/popover-context.ts +2 -0
- package/registry/components/primitives/popover/popover.tsx +47 -2
- package/registry/components/ui/chart/chart.tsx +51 -30
- package/registry/components/ui/chart/types.ts +5 -1
- package/registry/components/ui/data-table/checkbox-column.tsx +12 -8
- package/registry/components/ui/data-table/data-table.module.css +41 -19
- package/registry/components/ui/data-table/data-table.tsx +1285 -1238
- package/registry/components/ui/data-table/filter-bar.tsx +56 -13
- package/registry/components/ui/data-table/filter-modal.tsx +16 -82
- package/registry/components/ui/data-table/filter-sheet.tsx +97 -0
- package/registry/components/ui/data-table/filters.tsx +21 -21
- package/registry/components/ui/data-table/pagination-bar.tsx +4 -6
- package/registry/components/ui/data-table/types.ts +149 -80
- package/registry/components/ui/data-table/useDataTable.tsx +35 -16
- package/registry/components/ui/data-table/utils.ts +691 -471
- package/registry/components/ui/input-fields/date-picker/calendar.tsx +7 -15
- package/registry/components/ui/input-fields/date-picker/date-picker.tsx +1 -1
- package/registry/components/ui/input-fields/date-picker/types.ts +3 -3
- package/registry/components/ui/input-fields/emoji-picker/emoji-palette.tsx +1 -1
- package/registry/registry.json +17 -12
- package/registry/styles/media.css +1 -1
|
@@ -4,55 +4,99 @@ import type {
|
|
|
4
4
|
ColumnOrderState,
|
|
5
5
|
ColumnPinningState,
|
|
6
6
|
ColumnSizingState,
|
|
7
|
+
ColumnVisibilityState,
|
|
7
8
|
ExpandedState,
|
|
8
9
|
GroupingState,
|
|
9
10
|
Header,
|
|
10
11
|
OnChangeFn,
|
|
11
|
-
|
|
12
|
+
PaginationState,
|
|
12
13
|
Row,
|
|
14
|
+
RowData,
|
|
13
15
|
RowSelectionState,
|
|
14
16
|
SortingState,
|
|
15
17
|
Table,
|
|
16
|
-
TableMeta as TanstackTableMeta,
|
|
17
|
-
VisibilityState,
|
|
18
18
|
} from "@tanstack/react-table";
|
|
19
19
|
import type { ReactNode } from "react";
|
|
20
|
+
import type { dataTableFeatures } from "./utils";
|
|
21
|
+
|
|
22
|
+
// ─── Features & columns ───────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The TanStack Table feature set every DataTable registers. Pass it where
|
|
26
|
+
* TanStack asks for a table's features, e.g.
|
|
27
|
+
* `createColumnHelper<DataTableFeatures, Invoice>()`.
|
|
28
|
+
*/
|
|
29
|
+
export type DataTableFeatures = typeof dataTableFeatures;
|
|
20
30
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
cellClassName?: string;
|
|
40
|
-
}
|
|
41
|
-
/* eslint-enable @typescript-eslint/no-unused-vars */
|
|
31
|
+
/**
|
|
32
|
+
* Presentation hints a column carries in `meta`, so consumers stay in
|
|
33
|
+
* TanStack's idiomatic API surface (`columns: [{ ..., meta: {...} }]`)
|
|
34
|
+
* instead of inventing parallel prop maps on the table.
|
|
35
|
+
*/
|
|
36
|
+
export interface DataTableColumnMeta {
|
|
37
|
+
/** Horizontal alignment of cell + header content. Default: `'left'`. */
|
|
38
|
+
align?: "left" | "center" | "right";
|
|
39
|
+
/**
|
|
40
|
+
* Whether to truncate overflowing text with an ellipsis. Default: `true`.
|
|
41
|
+
* Set to `false` for cells that host interactive controls (inputs,
|
|
42
|
+
* selects, buttons) so focus rings and dropdowns are not clipped.
|
|
43
|
+
*/
|
|
44
|
+
truncate?: boolean;
|
|
45
|
+
/** Extra class merged onto the `<th>` element. */
|
|
46
|
+
headerClassName?: string;
|
|
47
|
+
/** Extra class merged onto the `<td>` element. */
|
|
48
|
+
cellClassName?: string;
|
|
42
49
|
}
|
|
43
50
|
|
|
51
|
+
/**
|
|
52
|
+
* One DataTable column. TanStack's `ColumnDef` is invariant on its value type
|
|
53
|
+
* parameter: `ColumnDef<F, Email, string>` is NOT assignable to
|
|
54
|
+
* `ColumnDef<F, Email, unknown>`, because `footer`/`header` templates place the
|
|
55
|
+
* value param in a contravariant position. `any` is the correct default - it
|
|
56
|
+
* is bivariant (simultaneously supertype and subtype of every type), which is
|
|
57
|
+
* the only param that lets columns of any concrete value type share one
|
|
58
|
+
* array. This matches how TanStack Table itself types its own internal APIs.
|
|
59
|
+
*
|
|
60
|
+
* In consumers: do NOT write `useMemo<DataTableColumnDef<X, unknown>[]>(...)`.
|
|
61
|
+
* Just write `useMemo(() => [...], [deps])` - let TypeScript infer the
|
|
62
|
+
* column types. The inferred union widens correctly at the prop boundary.
|
|
63
|
+
*/
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
65
|
+
export type DataTableColumnDef<TData extends RowData, TValue = any> = ColumnDef<
|
|
66
|
+
DataTableFeatures,
|
|
67
|
+
TData,
|
|
68
|
+
TValue
|
|
69
|
+
>;
|
|
70
|
+
|
|
44
71
|
// ─── Filter fields ────────────────────────────────────────────────────────────
|
|
45
72
|
// Two headless primitives - nothing else. Every concrete filter (search,
|
|
46
73
|
// select, date picker, custom slider, etc.) plugs in through the same
|
|
47
74
|
// `value`/`onChange` render-prop contract, so the data-table has zero
|
|
48
75
|
// compile-time dependency on any specific input component.
|
|
49
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Where a filter sits. `auto`: in the row while it fits, behind the Filters
|
|
79
|
+
* button on a compact screen. `inline`: in the row at every width, as a search
|
|
80
|
+
* people reach for first. `modal`: behind the Filters button at every width,
|
|
81
|
+
* for the filters a row has no room for.
|
|
82
|
+
*/
|
|
83
|
+
export type FilterPlacement = "auto" | "inline" | "modal";
|
|
84
|
+
|
|
50
85
|
/** Any input-like field with a string value. Render anything that fulfils the API. */
|
|
51
86
|
export interface FilterInputField {
|
|
52
87
|
type: "field";
|
|
53
88
|
key: string;
|
|
54
89
|
/** Grid column width template. Default: `'1fr'` (shares remaining space). */
|
|
55
90
|
width?: string;
|
|
91
|
+
/** Where it sits. Default: `'auto'`. */
|
|
92
|
+
placement?: FilterPlacement;
|
|
93
|
+
/**
|
|
94
|
+
* What it holds until someone sets it: the control shows it, it does not
|
|
95
|
+
* count as in use, and Clear all returns it here. With `useDataTable`, give
|
|
96
|
+
* the same value in `defaultFilterValues` so the table's state, its URL or
|
|
97
|
+
* storage, and `resetFilters` start from it too. Default: `''`.
|
|
98
|
+
*/
|
|
99
|
+
defaultValue?: string;
|
|
56
100
|
render: (api: {
|
|
57
101
|
value: string;
|
|
58
102
|
onChange: (value: string) => void;
|
|
@@ -64,6 +108,8 @@ export interface FilterAction {
|
|
|
64
108
|
type: "action";
|
|
65
109
|
/** Grid column width template. Default: `'auto'` (hugs content). */
|
|
66
110
|
width?: string;
|
|
111
|
+
/** Where it sits. Default: `'auto'`. */
|
|
112
|
+
placement?: FilterPlacement;
|
|
67
113
|
render: () => ReactNode;
|
|
68
114
|
}
|
|
69
115
|
|
|
@@ -75,9 +121,18 @@ export interface FilterBarProps {
|
|
|
75
121
|
onChange: (key: string, value: string) => void;
|
|
76
122
|
}
|
|
77
123
|
|
|
124
|
+
export interface FilterSheetProps extends FilterBarProps {
|
|
125
|
+
/**
|
|
126
|
+
* The button shows only its icon, for a row whose own fields, as a search,
|
|
127
|
+
* already say what it is. Default: `false`.
|
|
128
|
+
*/
|
|
129
|
+
iconOnly?: boolean;
|
|
130
|
+
}
|
|
131
|
+
|
|
78
132
|
// ─── Table meta ───────────────────────────────────────────────────────────────
|
|
79
133
|
|
|
80
|
-
|
|
134
|
+
/** What a DataTable puts in `table.options.meta`, for cells to read. */
|
|
135
|
+
export interface DataTableMeta {
|
|
81
136
|
editingRowId: string | null;
|
|
82
137
|
updateData: (rowId: string, columnId: string, value: unknown) => void;
|
|
83
138
|
}
|
|
@@ -94,7 +149,7 @@ export interface PersistedState {
|
|
|
94
149
|
pagination?: PaginationState;
|
|
95
150
|
sorting?: SortingState;
|
|
96
151
|
columnOrder?: ColumnOrderState;
|
|
97
|
-
columnVisibility?:
|
|
152
|
+
columnVisibility?: ColumnVisibilityState;
|
|
98
153
|
columnPinning?: ColumnPinningState;
|
|
99
154
|
columnSizing?: ColumnSizingState;
|
|
100
155
|
grouping?: GroupingState;
|
|
@@ -143,13 +198,8 @@ export type PersistState =
|
|
|
143
198
|
|
|
144
199
|
// ─── Pagination ───────────────────────────────────────────────────────────────
|
|
145
200
|
|
|
146
|
-
export interface
|
|
147
|
-
|
|
148
|
-
pageSize: number;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export interface PaginationBarProps<TData> {
|
|
152
|
-
table: Table<TData>;
|
|
201
|
+
export interface PaginationBarProps<TData extends RowData> {
|
|
202
|
+
table: Table<DataTableFeatures, TData>;
|
|
153
203
|
pagination: PaginationState;
|
|
154
204
|
onPaginationChange: (next: PaginationState) => void;
|
|
155
205
|
pageSizeOptions: number[];
|
|
@@ -163,37 +213,24 @@ export interface PaginationBarProps<TData> {
|
|
|
163
213
|
extra: ReactNode;
|
|
164
214
|
}
|
|
165
215
|
|
|
166
|
-
// ─── Re-exports for
|
|
216
|
+
// ─── Re-exports for table state ───────────────────────────────────────────────
|
|
167
217
|
// Re-exposed from tanstack so consumers do not need to import two modules.
|
|
168
218
|
export type {
|
|
169
219
|
ColumnOrderState,
|
|
170
220
|
ColumnPinningState,
|
|
171
221
|
ColumnSizingState,
|
|
222
|
+
ColumnVisibilityState,
|
|
172
223
|
ExpandedState,
|
|
173
224
|
GroupingState,
|
|
174
|
-
|
|
225
|
+
PaginationState,
|
|
175
226
|
};
|
|
176
227
|
|
|
177
228
|
// ─── Main DataTable props ─────────────────────────────────────────────────────
|
|
178
229
|
|
|
179
230
|
export interface DataTableProps<TData extends { id: string }> {
|
|
180
231
|
data: TData[];
|
|
181
|
-
/**
|
|
182
|
-
|
|
183
|
-
* `ColumnDef<Email, string>` is NOT assignable to `ColumnDef<Email, unknown>`
|
|
184
|
-
* because `footer`/`header` templates place the value param in a contravariant
|
|
185
|
-
* position. `any` is the correct solution - it is bivariant (simultaneously
|
|
186
|
-
* supertype and subtype of every type), which is the only param that lets
|
|
187
|
-
* columns of any concrete value type pass here without errors.
|
|
188
|
-
* This matches how TanStack Table itself types its own internal APIs.
|
|
189
|
-
*
|
|
190
|
-
* In consumers: do NOT write `useMemo<ColumnDef<X, unknown>[]>(...)`.
|
|
191
|
-
* Just write `useMemo(() => [...], [deps])` - let TypeScript infer the
|
|
192
|
-
* column types. The inferred union widens correctly into `ColumnDef<X, any>`
|
|
193
|
-
* at the prop boundary with zero errors.
|
|
194
|
-
*/
|
|
195
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
196
|
-
columns: ColumnDef<TData, any>[];
|
|
232
|
+
/** Column definitions - see `DataTableColumnDef` for how to type them. */
|
|
233
|
+
columns: DataTableColumnDef<TData>[];
|
|
197
234
|
|
|
198
235
|
isLoading?: boolean;
|
|
199
236
|
emptyMessage?: string;
|
|
@@ -215,7 +252,20 @@ export interface DataTableProps<TData extends { id: string }> {
|
|
|
215
252
|
showPagination?: boolean;
|
|
216
253
|
pagination?: PaginationState;
|
|
217
254
|
onPaginationChange?: (next: PaginationState) => void;
|
|
255
|
+
/**
|
|
256
|
+
* When true, `data` is one page (from a server, or sliced by you) and the
|
|
257
|
+
* table shows it as is. Set it to false to pass every row: the table then
|
|
258
|
+
* sorts and groups all of them before it pages them. `useDataTable` sets it
|
|
259
|
+
* from `clientSidePagination`. Default: `true`.
|
|
260
|
+
*/
|
|
261
|
+
manualPagination?: boolean;
|
|
262
|
+
/**
|
|
263
|
+
* Page count when `manualPagination` is on. Without it, pages are counted
|
|
264
|
+
* from `totalCount`, and with neither the total is unknown: "next" stays
|
|
265
|
+
* open and the page controls are hidden.
|
|
266
|
+
*/
|
|
218
267
|
totalPages?: number;
|
|
268
|
+
/** Total row count, shown in the pagination bar. */
|
|
219
269
|
totalCount?: number;
|
|
220
270
|
pageSizeOptions?: number[];
|
|
221
271
|
showPageSizeSelect?: boolean;
|
|
@@ -229,7 +279,7 @@ export interface DataTableProps<TData extends { id: string }> {
|
|
|
229
279
|
* all). Returning false disables the per-row checkbox via the standard
|
|
230
280
|
* `row.getCanSelect()` flow.
|
|
231
281
|
*/
|
|
232
|
-
getRowCanSelect?: (row: Row<TData>) => boolean;
|
|
282
|
+
getRowCanSelect?: (row: Row<DataTableFeatures, TData>) => boolean;
|
|
233
283
|
rowSelection?: RowSelectionState;
|
|
234
284
|
onRowSelectionChange?: OnChangeFn<RowSelectionState>;
|
|
235
285
|
|
|
@@ -263,17 +313,23 @@ export interface DataTableProps<TData extends { id: string }> {
|
|
|
263
313
|
onColumnSizingChange?: OnChangeFn<ColumnSizingState>;
|
|
264
314
|
|
|
265
315
|
/**
|
|
266
|
-
* Sticky column pinning
|
|
267
|
-
* during horizontal scroll. Per-column opt-out:
|
|
316
|
+
* Sticky column pinning to the row's `start` or `end`. When enabled, pinned
|
|
317
|
+
* columns stay visible during horizontal scroll. Per-column opt-out:
|
|
318
|
+
* `enablePinning: false`.
|
|
268
319
|
*/
|
|
269
320
|
enableColumnPinning?: boolean;
|
|
270
321
|
columnPinning?: ColumnPinningState;
|
|
271
322
|
onColumnPinningChange?: OnChangeFn<ColumnPinningState>;
|
|
272
|
-
|
|
323
|
+
/**
|
|
324
|
+
* Columns pinned from the start, e.g. `{ end: ['actions'] }`. They also
|
|
325
|
+
* anchor the order: they stay on the outer edges, with pins made later
|
|
326
|
+
* placed inside them.
|
|
327
|
+
*/
|
|
328
|
+
defaultColumnPinning?: Partial<ColumnPinningState>;
|
|
273
329
|
|
|
274
330
|
/** Show/hide columns. Pass with a UI to let users toggle visibility. */
|
|
275
|
-
columnVisibility?:
|
|
276
|
-
onColumnVisibilityChange?: OnChangeFn<
|
|
331
|
+
columnVisibility?: ColumnVisibilityState;
|
|
332
|
+
onColumnVisibilityChange?: OnChangeFn<ColumnVisibilityState>;
|
|
277
333
|
|
|
278
334
|
/**
|
|
279
335
|
* Row grouping by column id. Renders a collapsible group row per
|
|
@@ -328,8 +384,8 @@ export interface HeaderActionButtonProps {
|
|
|
328
384
|
children: ReactNode;
|
|
329
385
|
}
|
|
330
386
|
|
|
331
|
-
export interface HeaderCellProps<TData> {
|
|
332
|
-
header: Header<TData, unknown>;
|
|
387
|
+
export interface HeaderCellProps<TData extends RowData> {
|
|
388
|
+
header: Header<DataTableFeatures, TData, unknown>;
|
|
333
389
|
extraWidth: number;
|
|
334
390
|
enableColumnOrdering: boolean;
|
|
335
391
|
enableColumnResizing: boolean;
|
|
@@ -344,7 +400,7 @@ export interface HeaderCellProps<TData> {
|
|
|
344
400
|
}
|
|
345
401
|
|
|
346
402
|
export interface BodyRowProps<TData extends { id: string }> {
|
|
347
|
-
row: Row<TData>;
|
|
403
|
+
row: Row<DataTableFeatures, TData>;
|
|
348
404
|
editingRowId: string | null;
|
|
349
405
|
onRow?: (row: TData) => void;
|
|
350
406
|
enableGrouping: boolean;
|
|
@@ -358,17 +414,25 @@ export interface BodyRowProps<TData extends { id: string }> {
|
|
|
358
414
|
onDropRow?: (draggedId: string, targetId: string) => void;
|
|
359
415
|
}
|
|
360
416
|
|
|
361
|
-
export interface
|
|
362
|
-
|
|
417
|
+
export interface FooterCellProps<TData extends RowData> {
|
|
418
|
+
header: Header<DataTableFeatures, TData, unknown>;
|
|
363
419
|
extraWidth: number;
|
|
364
|
-
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export interface BodyCellProps<TData extends RowData> {
|
|
423
|
+
cell: Cell<DataTableFeatures, TData, unknown>;
|
|
424
|
+
extraWidth: number;
|
|
425
|
+
groupSourceCell?: Cell<DataTableFeatures, TData, unknown>;
|
|
365
426
|
isGroupAnchorCell: boolean;
|
|
366
427
|
}
|
|
367
428
|
|
|
368
429
|
// ─── useDataTable hook ────────────────────────────────────────────────────────
|
|
369
430
|
|
|
370
431
|
export interface UseDataTableOptions<TData extends { id: string }> {
|
|
371
|
-
/**
|
|
432
|
+
/**
|
|
433
|
+
* Full dataset. Client-side, the table sorts and groups all of it, then
|
|
434
|
+
* shows one page at a time.
|
|
435
|
+
*/
|
|
372
436
|
data: TData[];
|
|
373
437
|
|
|
374
438
|
/** Initial page size. Default: 10. */
|
|
@@ -424,9 +488,9 @@ export interface UseDataTableOptions<TData extends { id: string }> {
|
|
|
424
488
|
onDataChange?: (data: TData[]) => void;
|
|
425
489
|
|
|
426
490
|
/**
|
|
427
|
-
* If true (default), the
|
|
428
|
-
* Set to false when using server-side pagination - you
|
|
429
|
-
* and just want the hook to manage state.
|
|
491
|
+
* If true (default), the table pages `data` itself, after sorting and
|
|
492
|
+
* grouping every row. Set to false when using server-side pagination - you
|
|
493
|
+
* control `data` (one page) externally and just want the hook to manage state.
|
|
430
494
|
*/
|
|
431
495
|
clientSidePagination?: boolean;
|
|
432
496
|
|
|
@@ -447,9 +511,13 @@ export interface UseDataTableOptions<TData extends { id: string }> {
|
|
|
447
511
|
/** Initial drag-drop column order. */
|
|
448
512
|
defaultColumnOrder?: ColumnOrderState;
|
|
449
513
|
/** Initial column visibility map. */
|
|
450
|
-
defaultColumnVisibility?:
|
|
451
|
-
/**
|
|
452
|
-
|
|
514
|
+
defaultColumnVisibility?: ColumnVisibilityState;
|
|
515
|
+
/**
|
|
516
|
+
* Initial column pinning, e.g. `{ end: ['actions'] }`. These columns also
|
|
517
|
+
* anchor the order: they stay on the outer edges, with pins made later
|
|
518
|
+
* placed inside them.
|
|
519
|
+
*/
|
|
520
|
+
defaultColumnPinning?: Partial<ColumnPinningState>;
|
|
453
521
|
/** Initial column sizing state. */
|
|
454
522
|
defaultColumnSizing?: ColumnSizingState;
|
|
455
523
|
/** Initial grouping state (column ids to group by). */
|
|
@@ -472,14 +540,12 @@ export interface UseDataTableState<TData extends { id: string }> {
|
|
|
472
540
|
rowSelection: RowSelectionState;
|
|
473
541
|
editingRowId: string | null;
|
|
474
542
|
columnOrder: ColumnOrderState;
|
|
475
|
-
columnVisibility:
|
|
543
|
+
columnVisibility: ColumnVisibilityState;
|
|
476
544
|
columnPinning: ColumnPinningState;
|
|
477
545
|
columnSizing: ColumnSizingState;
|
|
478
546
|
grouping: GroupingState;
|
|
479
547
|
expanded: ExpandedState;
|
|
480
|
-
/**
|
|
481
|
-
pageData: TData[];
|
|
482
|
-
/** All rows after filtering (before paging) */
|
|
548
|
+
/** All rows after filtering (before sorting and paging) */
|
|
483
549
|
filteredData: TData[];
|
|
484
550
|
/** IDs of selected rows */
|
|
485
551
|
selectedIds: string[];
|
|
@@ -492,7 +558,10 @@ export interface UseDataTableActions {
|
|
|
492
558
|
setFilter: (key: string, value: string) => void;
|
|
493
559
|
/** Set multiple filter values at once (resets page to 0 automatically) */
|
|
494
560
|
setFilters: (values: Record<string, string>) => void;
|
|
495
|
-
/**
|
|
561
|
+
/**
|
|
562
|
+
* Reset all filters to `defaultFilterValues`. Every value that changes is
|
|
563
|
+
* reported through `onFilterChange`, like a single `setFilter`.
|
|
564
|
+
*/
|
|
496
565
|
resetFilters: () => void;
|
|
497
566
|
/** Go to a specific page index (0-based) */
|
|
498
567
|
goToPage: (index: number) => void;
|
|
@@ -511,7 +580,7 @@ export interface UseDataTableActions {
|
|
|
511
580
|
/** Toggle visibility of a single column. */
|
|
512
581
|
toggleColumnVisibility: (columnId: string) => void;
|
|
513
582
|
/** Replace the visibility map. */
|
|
514
|
-
setColumnVisibility: (next:
|
|
583
|
+
setColumnVisibility: (next: ColumnVisibilityState) => void;
|
|
515
584
|
/** Replace the column order. */
|
|
516
585
|
setColumnOrder: (next: ColumnOrderState) => void;
|
|
517
586
|
/** Replace the column pinning state. */
|
|
@@ -556,8 +625,8 @@ export interface PersistArgs {
|
|
|
556
625
|
setSorting: (next: SortingState) => void;
|
|
557
626
|
columnOrder: ColumnOrderState;
|
|
558
627
|
setColumnOrder: (next: ColumnOrderState) => void;
|
|
559
|
-
columnVisibility:
|
|
560
|
-
setColumnVisibility: (next:
|
|
628
|
+
columnVisibility: ColumnVisibilityState;
|
|
629
|
+
setColumnVisibility: (next: ColumnVisibilityState) => void;
|
|
561
630
|
columnPinning: ColumnPinningState;
|
|
562
631
|
setColumnPinning: (next: ColumnPinningState) => void;
|
|
563
632
|
columnSizing: ColumnSizingState;
|
|
@@ -580,7 +649,7 @@ export interface SnapshotInput {
|
|
|
580
649
|
defaultPageSize: number;
|
|
581
650
|
sorting: SortingState;
|
|
582
651
|
columnOrder: ColumnOrderState;
|
|
583
|
-
columnVisibility:
|
|
652
|
+
columnVisibility: ColumnVisibilityState;
|
|
584
653
|
columnPinning: ColumnPinningState;
|
|
585
654
|
columnSizing: ColumnSizingState;
|
|
586
655
|
grouping: GroupingState;
|
|
@@ -601,7 +670,7 @@ export interface ApplyPersistedTarget {
|
|
|
601
670
|
setPagination: (next: PaginationState) => void;
|
|
602
671
|
setSorting: (next: SortingState) => void;
|
|
603
672
|
setColumnOrder: (next: ColumnOrderState) => void;
|
|
604
|
-
setColumnVisibility: (next:
|
|
673
|
+
setColumnVisibility: (next: ColumnVisibilityState) => void;
|
|
605
674
|
setColumnPinning: (next: ColumnPinningState) => void;
|
|
606
675
|
setColumnSizing: (next: ColumnSizingState) => void;
|
|
607
676
|
setGrouping: (next: GroupingState) => void;
|
|
@@ -3,16 +3,16 @@ import type {
|
|
|
3
3
|
ColumnOrderState,
|
|
4
4
|
ColumnPinningState,
|
|
5
5
|
ColumnSizingState,
|
|
6
|
+
ColumnVisibilityState,
|
|
6
7
|
ExpandedState,
|
|
7
8
|
GroupingState,
|
|
8
9
|
OnChangeFn,
|
|
10
|
+
PaginationState,
|
|
9
11
|
RowSelectionState,
|
|
10
12
|
SortingState,
|
|
11
|
-
VisibilityState,
|
|
12
13
|
} from "@tanstack/react-table";
|
|
13
14
|
import type {
|
|
14
15
|
DataTableProps,
|
|
15
|
-
PaginationState,
|
|
16
16
|
PersistArgs,
|
|
17
17
|
PersistedState,
|
|
18
18
|
UseDataTableOptions,
|
|
@@ -69,9 +69,12 @@ export function useDataTable<TData extends { id: string }>({
|
|
|
69
69
|
defaultColumnOrder ?? [],
|
|
70
70
|
);
|
|
71
71
|
const [columnVisibility, setColumnVisibilityState] =
|
|
72
|
-
useState<
|
|
72
|
+
useState<ColumnVisibilityState>(defaultColumnVisibility ?? {});
|
|
73
73
|
const [columnPinning, setColumnPinningState] = useState<ColumnPinningState>(
|
|
74
|
-
|
|
74
|
+
() => ({
|
|
75
|
+
start: defaultColumnPinning?.start ?? [],
|
|
76
|
+
end: defaultColumnPinning?.end ?? [],
|
|
77
|
+
}),
|
|
75
78
|
);
|
|
76
79
|
const [columnSizing, setColumnSizingState] = useState<ColumnSizingState>(
|
|
77
80
|
defaultColumnSizing ?? {},
|
|
@@ -93,6 +96,10 @@ export function useDataTable<TData extends { id: string }>({
|
|
|
93
96
|
// updater. Firing consumer callbacks inside an updater causes double-fires
|
|
94
97
|
// under React strict/concurrent rendering (updaters may run twice) which
|
|
95
98
|
// would trigger duplicate API calls for server-side sort/filter consumers.
|
|
99
|
+
// Each handler also assigns its ref as soon as it computes `next`, so several
|
|
100
|
+
// calls in one event (Clear all resets each filter in turn) build on each
|
|
101
|
+
// other instead of on the last render. The effect below covers state set
|
|
102
|
+
// any other way (hydration from the URL or storage).
|
|
96
103
|
const sortingRef = useRef(sorting);
|
|
97
104
|
const rowSelectionRef = useRef(rowSelection);
|
|
98
105
|
const filterValuesRef = useRef(filterValues);
|
|
@@ -140,6 +147,7 @@ export function useDataTable<TData extends { id: string }>({
|
|
|
140
147
|
(updater) => {
|
|
141
148
|
const next =
|
|
142
149
|
typeof updater === "function" ? updater(sortingRef.current) : updater;
|
|
150
|
+
sortingRef.current = next;
|
|
143
151
|
setSorting(next);
|
|
144
152
|
externalOnSortingChange?.(next);
|
|
145
153
|
},
|
|
@@ -149,6 +157,7 @@ export function useDataTable<TData extends { id: string }>({
|
|
|
149
157
|
// ── Pagination handler ──────────────────────────────────────────────────────
|
|
150
158
|
const handlePaginationChange = useCallback(
|
|
151
159
|
(next: PaginationState) => {
|
|
160
|
+
paginationRef.current = next;
|
|
152
161
|
setPagination(next);
|
|
153
162
|
externalOnPaginationChange?.(next);
|
|
154
163
|
},
|
|
@@ -162,6 +171,7 @@ export function useDataTable<TData extends { id: string }>({
|
|
|
162
171
|
typeof updater === "function"
|
|
163
172
|
? updater(rowSelectionRef.current)
|
|
164
173
|
: updater;
|
|
174
|
+
rowSelectionRef.current = next;
|
|
165
175
|
setRowSelection(next);
|
|
166
176
|
externalOnRowSelectionChange?.(next);
|
|
167
177
|
},
|
|
@@ -176,6 +186,7 @@ export function useDataTable<TData extends { id: string }>({
|
|
|
176
186
|
const setFilter = useCallback(
|
|
177
187
|
(key: string, value: string) => {
|
|
178
188
|
const next = { ...filterValuesRef.current, [key]: value };
|
|
189
|
+
filterValuesRef.current = next;
|
|
179
190
|
setFilterValues(next);
|
|
180
191
|
onFilterChange?.(key, value, next);
|
|
181
192
|
|
|
@@ -190,6 +201,7 @@ export function useDataTable<TData extends { id: string }>({
|
|
|
190
201
|
const setFilters = useCallback(
|
|
191
202
|
(values: Record<string, string>) => {
|
|
192
203
|
const next = { ...filterValuesRef.current, ...values };
|
|
204
|
+
filterValuesRef.current = next;
|
|
193
205
|
setFilterValues(next);
|
|
194
206
|
for (const [k, v] of Object.entries(values)) {
|
|
195
207
|
onFilterChange?.(k, v, next);
|
|
@@ -204,13 +216,22 @@ export function useDataTable<TData extends { id: string }>({
|
|
|
204
216
|
);
|
|
205
217
|
|
|
206
218
|
const resetFilters = useCallback(() => {
|
|
207
|
-
|
|
208
|
-
|
|
219
|
+
const prev = filterValuesRef.current;
|
|
220
|
+
const next = defaultFilterValues;
|
|
221
|
+
filterValuesRef.current = next;
|
|
222
|
+
setFilterValues(next);
|
|
223
|
+
// Same contract as setFilter: every value that changes is reported, so a
|
|
224
|
+
// server-side consumer refetches after a reset too.
|
|
225
|
+
for (const key of new Set([...Object.keys(prev), ...Object.keys(next)])) {
|
|
226
|
+
const value = next[key] ?? "";
|
|
227
|
+
if ((prev[key] ?? "") !== value) onFilterChange?.(key, value, next);
|
|
228
|
+
}
|
|
229
|
+
|
|
209
230
|
const p = paginationRef.current;
|
|
210
231
|
if (p.pageIndex !== 0) {
|
|
211
232
|
handlePaginationChange({ ...p, pageIndex: 0 });
|
|
212
233
|
}
|
|
213
|
-
}, [defaultFilterValues, handlePaginationChange]);
|
|
234
|
+
}, [defaultFilterValues, onFilterChange, handlePaginationChange]);
|
|
214
235
|
|
|
215
236
|
// ── Inline editing actions ──────────────────────────────────────────────────
|
|
216
237
|
const startEdit = useCallback((rowId: string) => {
|
|
@@ -281,6 +302,7 @@ export function useDataTable<TData extends { id: string }>({
|
|
|
281
302
|
);
|
|
282
303
|
|
|
283
304
|
const clearSelection = useCallback(() => {
|
|
305
|
+
rowSelectionRef.current = {};
|
|
284
306
|
setRowSelection({});
|
|
285
307
|
}, []);
|
|
286
308
|
|
|
@@ -342,12 +364,6 @@ export function useDataTable<TData extends { id: string }>({
|
|
|
342
364
|
return filterFn(localData, filterValues);
|
|
343
365
|
}, [localData, filterFn, filterValues]);
|
|
344
366
|
|
|
345
|
-
const pageData = useMemo<TData[]>(() => {
|
|
346
|
-
if (!clientSidePagination) return filteredData;
|
|
347
|
-
const { pageIndex, pageSize } = pagination;
|
|
348
|
-
return filteredData.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize);
|
|
349
|
-
}, [filteredData, pagination, clientSidePagination]);
|
|
350
|
-
|
|
351
367
|
const totalCount = externalTotalCount ?? filteredData.length;
|
|
352
368
|
const totalPages =
|
|
353
369
|
externalTotalPages ?? Math.ceil(filteredData.length / pagination.pageSize);
|
|
@@ -364,7 +380,9 @@ export function useDataTable<TData extends { id: string }>({
|
|
|
364
380
|
"columns" | "filters" | "className"
|
|
365
381
|
> = useMemo(
|
|
366
382
|
() => ({
|
|
367
|
-
|
|
383
|
+
// Client-side, the table gets every filtered row and sorts, groups and
|
|
384
|
+
// pages them itself; server-side, `data` is already the page.
|
|
385
|
+
data: filteredData,
|
|
368
386
|
sorting,
|
|
369
387
|
onSortingChange: handleSortingChange,
|
|
370
388
|
manualSorting,
|
|
@@ -373,6 +391,7 @@ export function useDataTable<TData extends { id: string }>({
|
|
|
373
391
|
showPagination: true,
|
|
374
392
|
pagination,
|
|
375
393
|
onPaginationChange: handlePaginationChange,
|
|
394
|
+
manualPagination: !clientSidePagination,
|
|
376
395
|
totalPages,
|
|
377
396
|
totalCount,
|
|
378
397
|
rowSelection,
|
|
@@ -395,7 +414,7 @@ export function useDataTable<TData extends { id: string }>({
|
|
|
395
414
|
onRowOrderChange: reorderRow,
|
|
396
415
|
}),
|
|
397
416
|
[
|
|
398
|
-
|
|
417
|
+
filteredData,
|
|
399
418
|
sorting,
|
|
400
419
|
handleSortingChange,
|
|
401
420
|
manualSorting,
|
|
@@ -403,6 +422,7 @@ export function useDataTable<TData extends { id: string }>({
|
|
|
403
422
|
setFilter,
|
|
404
423
|
pagination,
|
|
405
424
|
handlePaginationChange,
|
|
425
|
+
clientSidePagination,
|
|
406
426
|
totalPages,
|
|
407
427
|
totalCount,
|
|
408
428
|
rowSelection,
|
|
@@ -479,7 +499,6 @@ export function useDataTable<TData extends { id: string }>({
|
|
|
479
499
|
columnSizing,
|
|
480
500
|
grouping,
|
|
481
501
|
expanded,
|
|
482
|
-
pageData,
|
|
483
502
|
filteredData,
|
|
484
503
|
selectedIds,
|
|
485
504
|
selectedRows,
|