@bmsuisse/datagrid 0.23.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/LICENSE +21 -0
- package/README.md +36 -0
- package/dist/index.d.ts +1027 -0
- package/dist/index.js +2494 -0
- package/dist/index.js.map +1 -0
- package/package.json +78 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1027 @@
|
|
|
1
|
+
import { ReactNode, ReactElement, ComponentType } from 'react';
|
|
2
|
+
import { ColumnSizingState, RowData } from '@tanstack/react-table';
|
|
3
|
+
export { ColumnSizingState } from '@tanstack/react-table';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shared filter/sort contract for @bmsuisse/datagrid.
|
|
7
|
+
*
|
|
8
|
+
* This shape is intentionally mirrored byte-for-byte (field names and operator
|
|
9
|
+
* set) by the Python package `bmsdna.datagrid` (see `bmsdna/datagrid/filters.py`)
|
|
10
|
+
* so that a `GridState` produced by the grid in "server" mode can be serialized
|
|
11
|
+
* as JSON and handed directly to a FastAPI endpoint that turns it into SQL or a
|
|
12
|
+
* Meilisearch filter string.
|
|
13
|
+
*/
|
|
14
|
+
/** The full set of operators supported by any FilterDescriptor. */
|
|
15
|
+
type FilterOperator = "eq" | "neq" | "lt" | "lte" | "gt" | "gte" | "in" | "notIn" | "contains" | "doesNotContain" | "startsWith" | "endsWith" | "isEmpty" | "isNotEmpty" | "isNull" | "isNotNull" | "between";
|
|
16
|
+
/**
|
|
17
|
+
* A single leaf filter condition.
|
|
18
|
+
*
|
|
19
|
+
* `field` is either a plain field name, or a pre-qualified path (e.g.
|
|
20
|
+
* `["c", "customer_name"]` for `c.customer_name` in a join) — the grid never
|
|
21
|
+
* infers joins, callers pre-qualify ambiguous fields themselves.
|
|
22
|
+
*
|
|
23
|
+
* `value` is an array for `in`/`notIn`/`between`, and is omitted entirely for
|
|
24
|
+
* unary operators like `isNull`/`isNotNull`/`isEmpty`/`isNotEmpty`.
|
|
25
|
+
*/
|
|
26
|
+
interface FilterDescriptor {
|
|
27
|
+
field: string | string[];
|
|
28
|
+
operator: FilterOperator;
|
|
29
|
+
value?: unknown;
|
|
30
|
+
ignoreCase?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/** Combines two or more filters (leaf or nested composite) with AND/OR logic. */
|
|
33
|
+
interface CompositeFilterDescriptor {
|
|
34
|
+
logic: "and" | "or";
|
|
35
|
+
filters: (FilterDescriptor | CompositeFilterDescriptor)[];
|
|
36
|
+
}
|
|
37
|
+
interface SortDescriptor {
|
|
38
|
+
field: string;
|
|
39
|
+
dir: "asc" | "desc";
|
|
40
|
+
}
|
|
41
|
+
/** The full sort/filter/pagination state a <DataGrid> tracks and can emit. */
|
|
42
|
+
interface GridState {
|
|
43
|
+
filter: CompositeFilterDescriptor | null;
|
|
44
|
+
sort: SortDescriptor[];
|
|
45
|
+
page: number;
|
|
46
|
+
pageSize: number;
|
|
47
|
+
}
|
|
48
|
+
/** True for a leaf `FilterDescriptor`, false for a `CompositeFilterDescriptor`. */
|
|
49
|
+
declare function isFilterDescriptor(node: FilterDescriptor | CompositeFilterDescriptor): node is FilterDescriptor;
|
|
50
|
+
/** True for a `CompositeFilterDescriptor`, false for a leaf `FilterDescriptor`. */
|
|
51
|
+
declare function isCompositeFilterDescriptor(node: FilterDescriptor | CompositeFilterDescriptor): node is CompositeFilterDescriptor;
|
|
52
|
+
/** Operators that never carry a `value` (unary presence/absence checks). */
|
|
53
|
+
declare const UNARY_OPERATORS: ReadonlySet<FilterOperator>;
|
|
54
|
+
/** Renders a `field` (string or pre-qualified path) as a single dotted key, for use as a stable id/key. */
|
|
55
|
+
declare function fieldKey(field: string | string[]): string;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Evaluates a filter tree (leaf or composite, or `null`/`undefined` for "no
|
|
59
|
+
* filter") against a single row. Used by `<DataGrid>` in `"client"` mode;
|
|
60
|
+
* `"server"` mode never calls this — it trusts the caller already applied
|
|
61
|
+
* the equivalent filter server-side.
|
|
62
|
+
*/
|
|
63
|
+
declare function evaluateFilter<TRow>(row: TRow, filter: CompositeFilterDescriptor | FilterDescriptor | null | undefined): boolean;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Contract every built-in filter widget implements. `TColumn` is narrowed to
|
|
67
|
+
* the specific column shape the widget knows how to render (e.g. `EnumColumn`
|
|
68
|
+
* for `EnumFilter`) — the grid picks the right widget by `column.type` before
|
|
69
|
+
* handing it a column, so the widget itself never has to branch on `type`.
|
|
70
|
+
*
|
|
71
|
+
* `value` is the current FilterDescriptor for this column, or undefined when
|
|
72
|
+
* no filter is applied. `onChange(undefined)` clears the filter entirely.
|
|
73
|
+
*/
|
|
74
|
+
interface FilterWidgetProps<TColumn> {
|
|
75
|
+
column: TColumn;
|
|
76
|
+
value: FilterDescriptor | undefined;
|
|
77
|
+
onChange: (next: FilterDescriptor | undefined) => void;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Horizontal alignment used for a column's cell content and header. */
|
|
81
|
+
type ColumnAlign = "left" | "center" | "right";
|
|
82
|
+
/** Fields shared by every column type, independent of `type`. */
|
|
83
|
+
interface BaseColumn<TRow> {
|
|
84
|
+
id: string;
|
|
85
|
+
accessorKey?: keyof TRow & string;
|
|
86
|
+
accessorFn?: (row: TRow) => unknown;
|
|
87
|
+
header: string;
|
|
88
|
+
width?: number;
|
|
89
|
+
/** Opt-in: defaults to false — must be explicitly set to true to show a sort toggle in the header. */
|
|
90
|
+
sortable?: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Opt-in: defaults to false, meaning a first click sorts ascending. Set true for a
|
|
93
|
+
* column whose "interesting" direction is descending — revenue, risk scores, backlog —
|
|
94
|
+
* so a first click surfaces the highest/most-urgent values first instead of the
|
|
95
|
+
* lowest/safest ones. Cycles desc -> asc -> none instead of the default asc -> desc -> none.
|
|
96
|
+
*/
|
|
97
|
+
sortDescFirst?: boolean;
|
|
98
|
+
/** Opt-in: defaults to false — must be explicitly set to true to show a filter icon/widget in the header. */
|
|
99
|
+
filterable?: boolean;
|
|
100
|
+
pinned?: "left" | "right";
|
|
101
|
+
/** Overrides the default type-based renderer. */
|
|
102
|
+
cell?: (value: unknown, row: TRow) => ReactNode;
|
|
103
|
+
/** Free-text group label for <ColumnSelector>; columns sharing a group are shown together. Ungrouped when omitted. */
|
|
104
|
+
group?: string;
|
|
105
|
+
/**
|
|
106
|
+
* Free-text label for a spanning header cell in `<DataGrid>`'s own thead —
|
|
107
|
+
* unlike `group` above (which only affects `<ColumnSelector>`), this
|
|
108
|
+
* changes the grid's rendered markup. Contiguous *visible* columns sharing
|
|
109
|
+
* the same `headerGroup` string get merged under one `colSpan`'d label
|
|
110
|
+
* cell in an extra row above the normal per-column header row; a column
|
|
111
|
+
* with no `headerGroup` instead spans both header rows itself (via
|
|
112
|
+
* `rowSpan`), so it reads as a single ungrouped column rather than leaving
|
|
113
|
+
* a blank cell above it. Two columns with the same `headerGroup` that
|
|
114
|
+
* AREN'T adjacent in the visible column order (e.g. a differently-grouped
|
|
115
|
+
* or ungrouped column sits between them) form two separate spanning cells
|
|
116
|
+
* with the same label, rather than one — grouping is purely positional,
|
|
117
|
+
* matching how a caller would read the columns left-to-right.
|
|
118
|
+
*/
|
|
119
|
+
headerGroup?: string;
|
|
120
|
+
/**
|
|
121
|
+
* Overrides the default type-based filter widget (see
|
|
122
|
+
* `renderDefaultFilterWidget`) for this column. Receives the same
|
|
123
|
+
* `(value, onChange)` pair the default widget would, plus the grid's full
|
|
124
|
+
* current `GridState.filter` (every column's combined filter) as a third
|
|
125
|
+
* argument — needed for a widget like `NumberHistogramFilter`, whose
|
|
126
|
+
* `allValues` prop must be computed via `facetedNumberValues(data, column,
|
|
127
|
+
* filter)` to respect every OTHER active filter while excluding this
|
|
128
|
+
* column's own (see `facetedNumberValues`'s own doc for why). Set this to
|
|
129
|
+
* close over whatever extra data (e.g. the full unfiltered `data`) the
|
|
130
|
+
* custom widget needs.
|
|
131
|
+
*/
|
|
132
|
+
renderFilter?: (value: FilterDescriptor | undefined, onChange: (next: FilterDescriptor | undefined) => void, filter: CompositeFilterDescriptor | null) => ReactNode;
|
|
133
|
+
/**
|
|
134
|
+
* Where this column's filter control renders. Defaults to `"popover"`: a
|
|
135
|
+
* filter-icon button in the header that opens the widget in a popover,
|
|
136
|
+
* same as every column gets automatically today. `"row"` renders the
|
|
137
|
+
* widget's content directly, inline, in an additional filter row under
|
|
138
|
+
* the header instead — needed for a wide control (e.g.
|
|
139
|
+
* `NumberHistogramFilter`'s slider/histogram) that doesn't fit well
|
|
140
|
+
* collapsed into the header's compact trigger button. The filter row
|
|
141
|
+
* itself is entirely opt-in: it only renders at all if at least one
|
|
142
|
+
* visible, filterable column sets `filterDisplay: "row"`.
|
|
143
|
+
*/
|
|
144
|
+
filterDisplay?: "popover" | "row";
|
|
145
|
+
/**
|
|
146
|
+
* Overrides the plain-text `header` rendering *inside* the header cell's
|
|
147
|
+
* sort toggle `<button>` (the sort caret still renders alongside it, and
|
|
148
|
+
* still inside that same button). Use this for a header that needs a
|
|
149
|
+
* tooltip, an info icon, a loading spinner tied to a separate
|
|
150
|
+
* batch-hydration fetch, or any other rich content — `header` itself
|
|
151
|
+
* stays a plain string (used for the filter icon's `aria-label` and by
|
|
152
|
+
* `<ColumnSelector>`, neither of which can work off a ReactNode).
|
|
153
|
+
*
|
|
154
|
+
* Whatever this returns is nested inside a real `<button>` — don't return
|
|
155
|
+
* another focusable/interactive element (a nested `<button>`, a link, a
|
|
156
|
+
* tooltip trigger that itself renders as a `<button>`) or the browser will
|
|
157
|
+
* split the outer button apart, breaking click-to-sort for that column.
|
|
158
|
+
* If the return value is non-textual (an icon with no visible label), the
|
|
159
|
+
* button's accessible name is whatever text content survives inside it —
|
|
160
|
+
* add your own `aria-label` inside the returned content if there isn't
|
|
161
|
+
* any, since `<DataGrid>` has no way to tell your content lacks one.
|
|
162
|
+
*/
|
|
163
|
+
renderHeader?: (column: BaseColumn<TRow>) => ReactNode;
|
|
164
|
+
}
|
|
165
|
+
type StringColumn<TRow> = BaseColumn<TRow> & {
|
|
166
|
+
type: "string";
|
|
167
|
+
};
|
|
168
|
+
type NumberColumn<TRow> = BaseColumn<TRow> & {
|
|
169
|
+
type: "number" | "currency";
|
|
170
|
+
currency?: string;
|
|
171
|
+
};
|
|
172
|
+
type DateColumn<TRow> = BaseColumn<TRow> & {
|
|
173
|
+
type: "date" | "datetime";
|
|
174
|
+
};
|
|
175
|
+
type BooleanColumn<TRow> = BaseColumn<TRow> & {
|
|
176
|
+
type: "boolean";
|
|
177
|
+
};
|
|
178
|
+
interface EnumOption {
|
|
179
|
+
value: string;
|
|
180
|
+
label: string;
|
|
181
|
+
}
|
|
182
|
+
type EnumColumn<TRow> = BaseColumn<TRow> & {
|
|
183
|
+
type: "enum";
|
|
184
|
+
options: EnumOption[];
|
|
185
|
+
};
|
|
186
|
+
/** Discriminated union of every supported column shape, keyed on `type`. */
|
|
187
|
+
type ColumnDef<TRow> = StringColumn<TRow> | NumberColumn<TRow> | DateColumn<TRow> | BooleanColumn<TRow> | EnumColumn<TRow>;
|
|
188
|
+
/** Resolves a column's raw cell value from a row, via `accessorFn` or `accessorKey`. */
|
|
189
|
+
declare function getColumnValue<TRow>(column: ColumnDef<TRow>, row: TRow): unknown;
|
|
190
|
+
/** True only if a column explicitly opts in (`sortable: true`) — the default is false. */
|
|
191
|
+
declare function isSortable<TRow>(column: ColumnDef<TRow>): boolean;
|
|
192
|
+
/** True only if a column explicitly opts in (`filterable: true`) — the default is false. */
|
|
193
|
+
declare function isFilterable<TRow>(column: ColumnDef<TRow>): boolean;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Default filter widget for `type: "boolean"` columns: a 3-way All/Yes/No
|
|
197
|
+
* select.
|
|
198
|
+
*
|
|
199
|
+
* `bare` (default `false`) skips this component's own Popover/trigger
|
|
200
|
+
* Button, rendering just the select directly — see `EnumFilter`'s identical
|
|
201
|
+
* `bare` doc for why (`renderDefaultFilterWidget` passes this automatically
|
|
202
|
+
* outside `filterDisplay: "row"`).
|
|
203
|
+
*/
|
|
204
|
+
declare function BooleanFilter<TRow>({ column, value, onChange, bare, }: FilterWidgetProps<BooleanColumn<TRow>> & {
|
|
205
|
+
bare?: boolean;
|
|
206
|
+
}): ReactElement;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Default filter widget for `type: "date"` / `type: "datetime"` columns: a
|
|
210
|
+
* date-range picker with Today / Last 7 days / This month presets, plus a
|
|
211
|
+
* calendar for a custom range. Always emits the `between` operator
|
|
212
|
+
* (inclusive both ends, matching evaluateFilter/sql.py/meili.py semantics).
|
|
213
|
+
*
|
|
214
|
+
* `today` defaults to the real current date; it's exposed as a prop purely
|
|
215
|
+
* so tests can pin preset computation to a fixed date without mocking the
|
|
216
|
+
* system clock (and the timer machinery that comes with that).
|
|
217
|
+
*
|
|
218
|
+
* `bare` (default `false`) skips this component's own Popover/trigger
|
|
219
|
+
* Button, rendering just the presets + calendar directly — see
|
|
220
|
+
* `EnumFilter`'s identical `bare` doc for why (`renderDefaultFilterWidget`
|
|
221
|
+
* passes this automatically outside `filterDisplay: "row"`).
|
|
222
|
+
*/
|
|
223
|
+
declare function DateRangeFilter<TRow>({ column, value, onChange, today, bare, }: FilterWidgetProps<DateColumn<TRow>> & {
|
|
224
|
+
today?: Date;
|
|
225
|
+
bare?: boolean;
|
|
226
|
+
}): ReactElement;
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Default filter widget for `type: "enum"` columns: a single Excel-style
|
|
230
|
+
* checkbox list, regardless of how many `column.options` there are (no
|
|
231
|
+
* short-list/long-list branch) —
|
|
232
|
+
*
|
|
233
|
+
* - a search input filters which options *render* below via a plain
|
|
234
|
+
* substring match against each option's label; it never touches the
|
|
235
|
+
* committed filter on its own.
|
|
236
|
+
* - a tri-state "Select all" checkbox acts on whatever subset is currently
|
|
237
|
+
* visible after that search, not the full option set — matching Excel's
|
|
238
|
+
* own column-filter behavior ("select all of what's shown").
|
|
239
|
+
* - every visible option gets its own plain checkbox + label row (a real
|
|
240
|
+
* shadcn `Checkbox`, not a menu item), inside a scrollable list so long
|
|
241
|
+
* option lists don't blow up the popover's height.
|
|
242
|
+
* - every toggle emits the updated `FilterDescriptor` immediately — there's
|
|
243
|
+
* no OK/Cancel step, matching every other filter widget in this package.
|
|
244
|
+
*
|
|
245
|
+
* `bare` (default `false`) skips this component's own Popover/trigger
|
|
246
|
+
* Button, rendering just the search input + option list directly — for a
|
|
247
|
+
* caller that already provides its own popover/collapsible (this is what
|
|
248
|
+
* `renderDefaultFilterWidget` passes automatically outside `filterDisplay:
|
|
249
|
+
* "row"`, so `<DataGrid>`'s own header-icon popover isn't nested inside a
|
|
250
|
+
* second one of this component's own).
|
|
251
|
+
*/
|
|
252
|
+
declare function EnumFilter<TRow>({ column, value, onChange, bare, }: FilterWidgetProps<EnumColumn<TRow>> & {
|
|
253
|
+
bare?: boolean;
|
|
254
|
+
}): ReactElement;
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* An Excel-style alternative to the default `NumberRangeFilter`: an operator
|
|
258
|
+
* dropdown (Greater than / Less than / Equals / ... / Between) plus one
|
|
259
|
+
* number input (two for `Between`), instead of a plain always-visible
|
|
260
|
+
* min/max range. Not wired up as any column type's automatic default —
|
|
261
|
+
* `NumberRangeFilter` still is, so existing columns keep their current
|
|
262
|
+
* behavior. Use this directly as a column's custom filter UI (see
|
|
263
|
+
* `BaseColumn.renderFilter`) for a numeric column where users think in
|
|
264
|
+
* terms of "greater than X" rather than "between X and Y".
|
|
265
|
+
*
|
|
266
|
+
* Local `operator`/text state (rather than deriving straight from `value`
|
|
267
|
+
* on every render, the way `StringFilter` does) is what lets `Between` hold
|
|
268
|
+
* a partially-typed pair — deriving directly would wipe whatever's in the
|
|
269
|
+
* OTHER box the instant the first box's own edit clears `value` back to
|
|
270
|
+
* `undefined` (no filter is emitted until both bounds parse), and would
|
|
271
|
+
* reset the operator dropdown back to the default the same way. The effect
|
|
272
|
+
* below only resyncs from `value` when it changed for some reason other
|
|
273
|
+
* than this widget's own last `onChange` call (an external clear-all, or
|
|
274
|
+
* restoring a saved view) — the same pattern `NumberRangeFilter` uses for
|
|
275
|
+
* the same reason.
|
|
276
|
+
*
|
|
277
|
+
* `bare` (default `true`) skips this component's own Popover/trigger
|
|
278
|
+
* Button, rendering just the operator + input(s) directly — correct under
|
|
279
|
+
* the default `filterDisplay: "popover"`, where `<DataGrid>` already
|
|
280
|
+
* supplies the header filter icon and popover; passing `false` there would
|
|
281
|
+
* nest a second, redundant one inside it. Pass `bare={false}` only when the
|
|
282
|
+
* column also sets `filterDisplay: "row"`, where nothing else provides a
|
|
283
|
+
* trigger — see `NumberHistogramFilter`'s identical `bare` doc for why.
|
|
284
|
+
*/
|
|
285
|
+
declare function NumberComparisonFilter<TRow>({ column, value, onChange, bare, }: FilterWidgetProps<NumberColumn<TRow>> & {
|
|
286
|
+
bare?: boolean;
|
|
287
|
+
}): ReactElement;
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Default filter widget for `type: "number"` / `type: "currency"` columns: a
|
|
291
|
+
* min/max range. Emits `between` when both bounds are set (reordered so the
|
|
292
|
+
* lower bound is always <= the upper bound), or `gte`/`lte` when only one
|
|
293
|
+
* is — all three are evaluated identically by evaluateFilter and sql.py/
|
|
294
|
+
* meili.py.
|
|
295
|
+
*
|
|
296
|
+
* The two input boxes track their own local text rather than deriving
|
|
297
|
+
* straight from `value` on every render. If they derived from `value`
|
|
298
|
+
* directly, reordering an inverted min/max (see above) would relocate
|
|
299
|
+
* whatever the user just typed into the *other* box on the very next
|
|
300
|
+
* render — so typing "99" into Min then "9" then "9" into Max (intending
|
|
301
|
+
* "99") would see Max flip to "99" after the first "9" (since a swap makes
|
|
302
|
+
* it the new max), and the second "9" would land in the now-relocated box,
|
|
303
|
+
* producing "909" instead of "99". Keeping local text means what's on
|
|
304
|
+
* screen always matches exactly what was typed, independent of how the
|
|
305
|
+
* emitted FilterDescriptor's bounds get reordered.
|
|
306
|
+
*
|
|
307
|
+
* `bare` (default `false`) skips this component's own Popover/trigger
|
|
308
|
+
* Button, rendering just the two inputs directly — see `EnumFilter`'s
|
|
309
|
+
* identical `bare` doc for why (`renderDefaultFilterWidget` passes this
|
|
310
|
+
* automatically outside `filterDisplay: "row"`). Under `filterDisplay:
|
|
311
|
+
* "row"`, the min/max boxes are always visible at their natural width
|
|
312
|
+
* regardless of a narrower column — wrapping them in a compact trigger
|
|
313
|
+
* (bare: false) is what keeps a plain numeric column from forcing its whole
|
|
314
|
+
* column wider than its data ever needs.
|
|
315
|
+
*/
|
|
316
|
+
declare function NumberRangeFilter<TRow>({ column, value, onChange, bare, }: FilterWidgetProps<NumberColumn<TRow>> & {
|
|
317
|
+
bare?: boolean;
|
|
318
|
+
}): ReactElement;
|
|
319
|
+
|
|
320
|
+
type NumberHistogramValuesProps = {
|
|
321
|
+
/**
|
|
322
|
+
* The full set of values to compute the histogram and slider domain
|
|
323
|
+
* from.
|
|
324
|
+
*
|
|
325
|
+
* MUST be computed excluding this column's OWN active filter (every
|
|
326
|
+
* other active filter is fine, even expected — see
|
|
327
|
+
* `facetedNumberValues`, which computes exactly this). Passing values
|
|
328
|
+
* already narrowed by this same filter reproduces the bug this
|
|
329
|
+
* component's design exists to prevent: the domain shrinks every time
|
|
330
|
+
* the user adjusts the range, so they can never widen it back out, and
|
|
331
|
+
* (if the same "all rows" array is reused across multiple numeric
|
|
332
|
+
* columns) filtering one column warps every other numeric column's
|
|
333
|
+
* histogram too.
|
|
334
|
+
*
|
|
335
|
+
* Use this (not `loadValues`) whenever the full, facet-safe row set is
|
|
336
|
+
* already in memory — e.g. `<DataGrid>` in `"client"` mode.
|
|
337
|
+
*/
|
|
338
|
+
allValues: (number | null | undefined)[];
|
|
339
|
+
loadValues?: never;
|
|
340
|
+
} | {
|
|
341
|
+
allValues?: never;
|
|
342
|
+
/**
|
|
343
|
+
* Fetches the histogram/slider domain values on demand, called every
|
|
344
|
+
* time the popover opens (not cached across opens, since every OTHER
|
|
345
|
+
* active filter — which the fetch should itself respect — may have
|
|
346
|
+
* changed since the last time it was open).
|
|
347
|
+
*
|
|
348
|
+
* Use this instead of `allValues` whenever the facet-safe row set
|
|
349
|
+
* ISN'T already in memory — the standard case for `<DataGrid>` in
|
|
350
|
+
* `"server"` mode, where the rows the grid holds are just the current
|
|
351
|
+
* page, already narrowed by every active filter (including this
|
|
352
|
+
* column's own), and therefore never safe to pass as `allValues`
|
|
353
|
+
* directly. Typically this closes over a caller's own "fetch facet
|
|
354
|
+
* values for this column, given every other active filter" endpoint.
|
|
355
|
+
*/
|
|
356
|
+
loadValues: () => Promise<(number | null | undefined)[]>;
|
|
357
|
+
};
|
|
358
|
+
type NumberHistogramFilterProps<TRow> = FilterWidgetProps<NumberColumn<TRow>> & NumberHistogramValuesProps & {
|
|
359
|
+
/** Formats a bound for display. Defaults to `toLocaleString()`. */
|
|
360
|
+
format?: (value: number) => string;
|
|
361
|
+
/** Number of histogram bars. Defaults to 24. */
|
|
362
|
+
buckets?: number;
|
|
363
|
+
/**
|
|
364
|
+
* Set this to `false` only when the column also sets `filterDisplay:
|
|
365
|
+
* "row"` — there, nothing else provides a trigger, so this needs its
|
|
366
|
+
* own Popover + Button to avoid permanently expanding the histogram
|
|
367
|
+
* inline in the filter row. Defaults to `true` (bare: no own Popover/
|
|
368
|
+
* Button, just the histogram/slider/inputs/clear content) because a
|
|
369
|
+
* custom `BaseColumn.renderFilter` is normally reached for under the
|
|
370
|
+
* *default* `filterDisplay: "popover"`, where `<DataGrid>` already
|
|
371
|
+
* renders a header filter icon that opens a popover around whatever
|
|
372
|
+
* `renderFilter` returns — passing `false` there nests a second,
|
|
373
|
+
* redundant Popover+Button inside the first, so opening the header
|
|
374
|
+
* icon shows nothing but another filter icon to click, not the
|
|
375
|
+
* histogram. There's no `open`/`onOpenChange` state at this level in
|
|
376
|
+
* bare mode — the component simply renders as always-open; the
|
|
377
|
+
* caller's own popover is what mounts/unmounts it.
|
|
378
|
+
*/
|
|
379
|
+
bare?: boolean;
|
|
380
|
+
};
|
|
381
|
+
/**
|
|
382
|
+
* A richer alternative to the default `NumberRangeFilter`: a log-scale
|
|
383
|
+
* histogram behind a dual-thumb range slider, plus min/max text inputs.
|
|
384
|
+
* Not wired up as any column type's automatic default (unlike
|
|
385
|
+
* `NumberRangeFilter`) — it needs `allValues`/`loadValues` (see above),
|
|
386
|
+
* which the grid itself doesn't have a way to supply automatically yet, so
|
|
387
|
+
* use it directly as a column's custom filter UI (see `BaseColumn.renderFilter`).
|
|
388
|
+
* By default (`bare` unset) this renders bare — just the histogram/slider/
|
|
389
|
+
* inputs, no own trigger/popover — correct under the default `filterDisplay:
|
|
390
|
+
* "popover"`, where `<DataGrid>` already supplies the header filter icon and
|
|
391
|
+
* popover. Pass `bare={false}` when the column also sets `filterDisplay:
|
|
392
|
+
* "row"`, where nothing else provides a trigger. Compute `allValues` via
|
|
393
|
+
* `facetedNumberValues(data, column, filter)` in client mode, or supply
|
|
394
|
+
* `loadValues` to fetch the facet from the server in server mode.
|
|
395
|
+
*/
|
|
396
|
+
declare function NumberHistogramFilter<TRow>({ column, value, onChange, allValues, loadValues, format, buckets, bare, }: NumberHistogramFilterProps<TRow>): ReactElement;
|
|
397
|
+
|
|
398
|
+
type FilterNode = FilterDescriptor | CompositeFilterDescriptor;
|
|
399
|
+
/**
|
|
400
|
+
* Computes the correct `allValues` input for `NumberHistogramFilter` bound
|
|
401
|
+
* to `column`: `data` filtered by every OTHER currently-active filter, with
|
|
402
|
+
* `column`'s own filter excluded, then that column's values extracted.
|
|
403
|
+
*
|
|
404
|
+
* This exists to prevent a specific, easy-to-reach bug: computing a filter
|
|
405
|
+
* widget's own histogram/slider domain from a row set that's already been
|
|
406
|
+
* narrowed BY that same filter means the domain shrinks every time the user
|
|
407
|
+
* adjusts the range — they can never widen it back out — and if one "all
|
|
408
|
+
* values" computation is naively reused across multiple numeric columns
|
|
409
|
+
* (e.g. from `table.getFilteredRowModel()`), setting a filter on column A
|
|
410
|
+
* also warps column B's histogram, since B's domain was computed from rows
|
|
411
|
+
* A already excluded. This is the standard "faceted search" fix: a filter's
|
|
412
|
+
* own facet should reflect "what could I select if I changed just this
|
|
413
|
+
* filter," never a result set that filter has already narrowed.
|
|
414
|
+
*
|
|
415
|
+
* `filter` is the grid's full `GridState.filter` (every column's combined
|
|
416
|
+
* filter) — pass it as-is; this function does the excluding.
|
|
417
|
+
*/
|
|
418
|
+
declare function facetedNumberValues<TRow>(data: readonly TRow[], column: ColumnDef<TRow>, filter: FilterNode | null | undefined): (number | null)[];
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Default filter widget for `type: "string"` columns: a text input plus an
|
|
422
|
+
* operator dropdown.
|
|
423
|
+
*
|
|
424
|
+
* `bare` (default `false`) skips this component's own Popover/trigger
|
|
425
|
+
* Button, rendering just the operator + input directly — see `EnumFilter`'s
|
|
426
|
+
* identical `bare` doc for why (`renderDefaultFilterWidget` passes this
|
|
427
|
+
* automatically outside `filterDisplay: "row"`).
|
|
428
|
+
*/
|
|
429
|
+
declare function StringFilter<TRow>({ column, value, onChange, bare, }: FilterWidgetProps<StringColumn<TRow>> & {
|
|
430
|
+
bare?: boolean;
|
|
431
|
+
}): ReactElement;
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Picks and renders the default filter widget for a column, based purely on
|
|
435
|
+
* `column.type`. This is what makes filtering "just work" without a column
|
|
436
|
+
* having to configure anything — the discriminated union on `type` lets each
|
|
437
|
+
* branch narrow `column` to the exact widget it needs.
|
|
438
|
+
*
|
|
439
|
+
* Every default widget has its own self-contained Popover+trigger Button
|
|
440
|
+
* (needed under `filterDisplay: "row"`, where nothing else provides one) —
|
|
441
|
+
* everywhere else, `<DataGrid>`'s own header filter icon already opens a
|
|
442
|
+
* popover around whatever this function returns, so passing `bare: true`
|
|
443
|
+
* there (this function is `<DataGrid>`'s only caller, and always knows
|
|
444
|
+
* `column.filterDisplay` already) is what keeps that from nesting a second
|
|
445
|
+
* popover inside the first.
|
|
446
|
+
*/
|
|
447
|
+
declare function renderDefaultFilterWidget<TRow>(column: ColumnDef<TRow>, value: FilterDescriptor | undefined, onChange: (next: FilterDescriptor | undefined) => void): ReactElement;
|
|
448
|
+
|
|
449
|
+
/** The default horizontal alignment for a column, based purely on its `type`. */
|
|
450
|
+
declare function defaultAlign<TRow>(column: ColumnDef<TRow>): ColumnAlign;
|
|
451
|
+
/** The literal Tailwind class for a column's default alignment; see `defaultAlign`. */
|
|
452
|
+
declare function alignClassName<TRow>(column: ColumnDef<TRow>): string;
|
|
453
|
+
/**
|
|
454
|
+
* Renders a raw cell value as a display string, using the type-appropriate
|
|
455
|
+
* default formatter for `column.type`. Columns with a custom `cell` render
|
|
456
|
+
* function bypass this entirely — it only backs the built-in renderer.
|
|
457
|
+
*/
|
|
458
|
+
declare function defaultFormat<TRow>(column: ColumnDef<TRow>, value: unknown): string;
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Column visibility keyed by `ColumnDef.id`. A missing key means visible —
|
|
462
|
+
* this matches TanStack Table's own `VisibilityState` convention, so a
|
|
463
|
+
* `ColumnVisibility` value can be handed straight to a TanStack table's
|
|
464
|
+
* `state.columnVisibility` if a consumer ever needs to.
|
|
465
|
+
*/
|
|
466
|
+
type ColumnVisibility = Record<string, boolean>;
|
|
467
|
+
|
|
468
|
+
interface ColumnSelectorProps<TRow> {
|
|
469
|
+
columns: ColumnDef<TRow>[];
|
|
470
|
+
visibility: ColumnVisibility;
|
|
471
|
+
onVisibilityChange: (visibility: ColumnVisibility) => void;
|
|
472
|
+
/** If set: restore from localStorage on mount, and persist on every subsequent change. */
|
|
473
|
+
persistKey?: string;
|
|
474
|
+
/** Optional custom trigger; defaults to a "Columns" icon button. */
|
|
475
|
+
trigger?: ReactNode;
|
|
476
|
+
/** Optional controlled open state, for callers that want their own trigger entirely. */
|
|
477
|
+
open?: boolean;
|
|
478
|
+
onOpenChange?: (open: boolean) => void;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Controlled column-visibility selector: a modal listing every column,
|
|
482
|
+
* grouped by `column.group` into side-by-side columns (ungrouped columns
|
|
483
|
+
* first, unlabeled, matching `groupColumns`' own ordering), wrapping onto a
|
|
484
|
+
* new row rather than growing the dialog past a reasonable width. Each named
|
|
485
|
+
* group's header is a plain muted label — no bulk-select affordance of any
|
|
486
|
+
* kind lives there (an earlier version had a tri-state checkbox, then an
|
|
487
|
+
* "All"/"None" link pair, both dropped; see AGENTS.md for why). Purely
|
|
488
|
+
* controlled by `visibility`/`onVisibilityChange` — `persistKey`
|
|
489
|
+
* layers an optional localStorage sync on top (restore once on mount, write
|
|
490
|
+
* on every change), it never becomes the source of truth in place of the
|
|
491
|
+
* `visibility` prop.
|
|
492
|
+
*
|
|
493
|
+
* Not rendered inside `<DataGrid>` automatically — wire it up next to the
|
|
494
|
+
* grid yourself, e.g.:
|
|
495
|
+
*
|
|
496
|
+
* ```tsx
|
|
497
|
+
* const [visibility, setVisibility] = useState<ColumnVisibility>({});
|
|
498
|
+
* <ColumnSelector columns={columns} visibility={visibility} onVisibilityChange={setVisibility} persistKey="orders" />
|
|
499
|
+
* <DataGrid columns={columns} columnVisibility={visibility} onColumnVisibilityChange={setVisibility} ... />
|
|
500
|
+
* ```
|
|
501
|
+
*/
|
|
502
|
+
declare function ColumnSelector<TRow>({ columns, visibility, onVisibilityChange, persistKey, trigger, open, onOpenChange, }: ColumnSelectorProps<TRow>): ReactElement;
|
|
503
|
+
|
|
504
|
+
/** A missing key means visible, matching TanStack's own VisibilityState convention. */
|
|
505
|
+
declare function isColumnVisible(visibility: ColumnVisibility, columnId: string): boolean;
|
|
506
|
+
/** How many of `columns` are currently visible under `visibility`. */
|
|
507
|
+
declare function countVisible<TRow>(columns: readonly ColumnDef<TRow>[], visibility: ColumnVisibility): number;
|
|
508
|
+
interface ColumnGroup<TRow> {
|
|
509
|
+
/** undefined for the ungrouped section, which is always listed first. */
|
|
510
|
+
group: string | undefined;
|
|
511
|
+
columns: ColumnDef<TRow>[];
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Groups columns by `column.group`, preserving each group's first-seen order
|
|
515
|
+
* and each column's original order within its group. The ungrouped section
|
|
516
|
+
* (columns with no `group`) is always returned first, regardless of where
|
|
517
|
+
* ungrouped columns actually fall in the input order.
|
|
518
|
+
*/
|
|
519
|
+
declare function groupColumns<TRow>(columns: readonly ColumnDef<TRow>[]): ColumnGroup<TRow>[];
|
|
520
|
+
/** Whether toggling `columnId` off is currently allowed (must leave at least one column visible). */
|
|
521
|
+
declare function canHideColumn<TRow>(columns: readonly ColumnDef<TRow>[], visibility: ColumnVisibility, columnId: string): boolean;
|
|
522
|
+
/** Whether hiding every column in `groupColumnIds` at once is currently allowed. */
|
|
523
|
+
declare function canHideGroup<TRow>(columns: readonly ColumnDef<TRow>[], visibility: ColumnVisibility, groupColumnIds: readonly string[]): boolean;
|
|
524
|
+
|
|
525
|
+
declare function storageKeyFor(persistKey: string): string;
|
|
526
|
+
|
|
527
|
+
/** The row/selection context a menu item's visible/disabled/onSelect handlers are evaluated against. */
|
|
528
|
+
interface MenuItemContext<TRow> {
|
|
529
|
+
row?: TRow;
|
|
530
|
+
selectedRows?: TRow[];
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* A single entry in a row-actions (per-row kebab menu) or header-actions
|
|
534
|
+
* (toolbar) menu. `rowActions` items are evaluated with `ctx.row` set;
|
|
535
|
+
* `headerActions` items are evaluated with `ctx.selectedRows` set (whatever
|
|
536
|
+
* the grid's current selection is, possibly empty).
|
|
537
|
+
*/
|
|
538
|
+
interface MenuItem<TRow> {
|
|
539
|
+
id: string;
|
|
540
|
+
label: string;
|
|
541
|
+
icon?: ComponentType;
|
|
542
|
+
danger?: boolean;
|
|
543
|
+
/** Defaults to always visible when omitted. */
|
|
544
|
+
visible?: (ctx: MenuItemContext<TRow>) => boolean;
|
|
545
|
+
/** Defaults to always enabled when omitted. */
|
|
546
|
+
disabled?: (ctx: MenuItemContext<TRow>) => boolean;
|
|
547
|
+
onSelect: (ctx: MenuItemContext<TRow>) => void;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/** A `MenuItem` with its `visible`/`disabled` predicates already evaluated against a context. */
|
|
551
|
+
interface ResolvedMenuItem<TRow> extends MenuItem<TRow> {
|
|
552
|
+
isDisabled: boolean;
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Evaluates each item's `visible`/`disabled` predicates against `ctx` (both
|
|
556
|
+
* default to "always" when omitted), dropping items that aren't visible and
|
|
557
|
+
* annotating the rest with `isDisabled`. Pure and UI-free on purpose, so the
|
|
558
|
+
* gating logic itself — as opposed to the DropdownMenu rendering built on
|
|
559
|
+
* top of it — can be unit-tested directly.
|
|
560
|
+
*/
|
|
561
|
+
declare function resolveMenuItems<TRow>(items: readonly MenuItem<TRow>[], ctx: MenuItemContext<TRow>): ResolvedMenuItem<TRow>[];
|
|
562
|
+
|
|
563
|
+
interface ActionsMenuProps<TRow> {
|
|
564
|
+
items: readonly MenuItem<TRow>[];
|
|
565
|
+
ctx: MenuItemContext<TRow>;
|
|
566
|
+
/** Accessible label for the trigger button, e.g. "Row actions" or "Bulk actions". */
|
|
567
|
+
triggerLabel: string;
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Shared rendering for both `rowActions` (per-row kebab menu) and
|
|
571
|
+
* `headerActions` (toolbar menu) — they differ only in what `ctx` they're
|
|
572
|
+
* evaluated against (`{ row }` vs `{ selectedRows }`) and the trigger's
|
|
573
|
+
* accessible label, so <DataGrid> passes those in rather than this module
|
|
574
|
+
* having two near-identical components.
|
|
575
|
+
*/
|
|
576
|
+
declare function ActionsMenu<TRow>({ items, ctx, triggerLabel }: ActionsMenuProps<TRow>): ReactElement | null;
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* `"client"`: the grid owns filtering/sorting/pagination and evaluates them
|
|
580
|
+
* in-memory against the full `data` array (via `evaluateFilter`).
|
|
581
|
+
*
|
|
582
|
+
* `"server"`: `data` is just the current page, already filtered/sorted by
|
|
583
|
+
* the caller; the grid calls `onStateChange` on every sort/filter/page
|
|
584
|
+
* change (debounced ~300ms for filter changes) and trusts the result — it
|
|
585
|
+
* never re-filters/re-sorts server-mode data itself.
|
|
586
|
+
*/
|
|
587
|
+
type DataSource<TRow> = {
|
|
588
|
+
mode: "client";
|
|
589
|
+
data: TRow[];
|
|
590
|
+
} | {
|
|
591
|
+
mode: "server";
|
|
592
|
+
data: TRow[];
|
|
593
|
+
rowCount: number;
|
|
594
|
+
loading?: boolean;
|
|
595
|
+
onStateChange: (state: GridState) => void;
|
|
596
|
+
};
|
|
597
|
+
interface DataGridVirtualizeOptions {
|
|
598
|
+
/** Enables row virtualization once the row count exceeds this threshold. Defaults to 100. Set to 0 to always virtualize, or Infinity to never. */
|
|
599
|
+
threshold?: number;
|
|
600
|
+
/** Fixed row height estimate in pixels, used by the virtualizer. Defaults to 40. */
|
|
601
|
+
estimatedRowHeight?: number;
|
|
602
|
+
/** Max height of the scrollable table body in pixels. Required for virtualization to have a viewport to scroll within. Defaults to 480. */
|
|
603
|
+
maxBodyHeight?: number;
|
|
604
|
+
/** Extra rows rendered beyond the viewport on each side. Defaults to 10. */
|
|
605
|
+
overscan?: number;
|
|
606
|
+
/**
|
|
607
|
+
* Called once when scrolling reaches the last currently loaded row — the
|
|
608
|
+
* caller's job to fetch more and grow `dataSource.data`; `<DataGrid>`
|
|
609
|
+
* doesn't own pagination state in this mode (works with `"client"` and
|
|
610
|
+
* `"server"` `DataSource` alike, since it's purely about *rendering*, not
|
|
611
|
+
* fetching). Won't fire again for the same `data` length — only once
|
|
612
|
+
* `data` actually grows (or shrinks and regrows) does another scroll to
|
|
613
|
+
* the end re-fire it.
|
|
614
|
+
*/
|
|
615
|
+
onEndReached?: () => void;
|
|
616
|
+
/** Whether more rows exist beyond what's currently loaded. `onEndReached` never fires while this is `false`. Defaults to `true`. */
|
|
617
|
+
hasMore?: boolean;
|
|
618
|
+
}
|
|
619
|
+
interface DataGridProps<TRow> {
|
|
620
|
+
columns: ColumnDef<TRow>[];
|
|
621
|
+
dataSource: DataSource<TRow>;
|
|
622
|
+
getRowId: (row: TRow) => string;
|
|
623
|
+
/**
|
|
624
|
+
* Shows a full-overlay spinner over the grid's scrollable body, positioned as a
|
|
625
|
+
* sibling of it (not a child) so it stays fixed over the visible viewport instead of
|
|
626
|
+
* scrolling away with the table content. Independent of `dataSource.mode === "server"`'s
|
|
627
|
+
* own `loading` flag (which only affects the empty-state text) — this works for
|
|
628
|
+
* `"client"` mode too, for a caller whose own `data` array is still being fetched.
|
|
629
|
+
* The two are OR'd together if both happen to be set. Defaults to `false`.
|
|
630
|
+
*/
|
|
631
|
+
loading?: boolean;
|
|
632
|
+
/** Per-row kebab menu. Evaluated with `ctx.row` set to that row. */
|
|
633
|
+
rowActions?: MenuItem<TRow>[];
|
|
634
|
+
/** Toolbar menu above the table. Evaluated with `ctx.selectedRows` set. */
|
|
635
|
+
headerActions?: MenuItem<TRow>[];
|
|
636
|
+
/** Allows shift-click to add a secondary sort column instead of replacing. Defaults to false. */
|
|
637
|
+
enableMultiSort?: boolean;
|
|
638
|
+
/** Initial sort/filter/page/pageSize, e.g. restored from a URL. Defaults to no filter, no sort, page 0, pageSize 20. Only read once, at mount — see `gridState` to push updates in afterward. */
|
|
639
|
+
initialState?: Partial<GridState>;
|
|
640
|
+
/**
|
|
641
|
+
* Pushes a full `GridState` into the grid from outside, overriding
|
|
642
|
+
* whatever it currently has — the one way to change sort/filter/page
|
|
643
|
+
* programmatically after mount without remounting `<DataGrid>` (e.g. via
|
|
644
|
+
* a changing `key`). Omit entirely for the grid to fully own its own
|
|
645
|
+
* state, seeded once from `initialState`. Typical server-mode usage
|
|
646
|
+
* mirrors every `dataSource.onStateChange` call into your own state and
|
|
647
|
+
* passes that same value back in here — a fresh object literal recreated
|
|
648
|
+
* on every render instead re-triggers this resync every render, so don't
|
|
649
|
+
* inline a new one; pass the state variable you're already keeping in
|
|
650
|
+
* sync from `onStateChange`. Like a controlled `<input>`: decide once
|
|
651
|
+
* whether a given `<DataGrid>` is controlled this way and stay there for
|
|
652
|
+
* its whole lifetime — toggling this to `undefined` later doesn't hand
|
|
653
|
+
* control back, it just stops resyncing.
|
|
654
|
+
*/
|
|
655
|
+
gridState?: GridState;
|
|
656
|
+
/**
|
|
657
|
+
* Controlled column visibility, e.g. driven by a `<ColumnSelector>` next
|
|
658
|
+
* to the grid. Both must be supplied together to actually hide columns —
|
|
659
|
+
* <DataGrid> never owns this state itself, matching how it never owns
|
|
660
|
+
* GridState's persistence either.
|
|
661
|
+
*/
|
|
662
|
+
columnVisibility?: ColumnVisibility;
|
|
663
|
+
onColumnVisibilityChange?: (visibility: ColumnVisibility) => void;
|
|
664
|
+
/**
|
|
665
|
+
* Enables an expand/collapse chevron in a dedicated leftmost column; when
|
|
666
|
+
* a row is expanded, its result is rendered directly below that row, in
|
|
667
|
+
* its own full-width `<tr>`. Omit entirely to disable — no chevron column
|
|
668
|
+
* renders at all. Multiple rows can be expanded simultaneously (there's
|
|
669
|
+
* no single-row-at-a-time constraint). Combined with `virtualize`: only
|
|
670
|
+
* the row's own height is dynamically measured, not the expanded detail
|
|
671
|
+
* panel's — size `virtualize.estimatedRowHeight` generously if rows are
|
|
672
|
+
* commonly expanded in a virtualized grid, or expect some visual overlap.
|
|
673
|
+
*/
|
|
674
|
+
renderDetail?: (row: TRow) => ReactNode;
|
|
675
|
+
/**
|
|
676
|
+
* Enables row virtualization (`@tanstack/react-virtual`) once the row
|
|
677
|
+
* count crosses `threshold`. Omit entirely for a plain, fully-rendered
|
|
678
|
+
* `<table>` (the default) — most grids never need this; it's for the ones
|
|
679
|
+
* with hundreds+ of rows where rendering every `<tr>` becomes the
|
|
680
|
+
* bottleneck.
|
|
681
|
+
*/
|
|
682
|
+
virtualize?: DataGridVirtualizeOptions;
|
|
683
|
+
/**
|
|
684
|
+
* Opt-in: defaults to false. When true, every column gets a drag handle
|
|
685
|
+
* on its trailing edge to resize it, and every column (not just ones with
|
|
686
|
+
* an explicit `width`) gets a concrete pixel width — TanStack Table's own
|
|
687
|
+
* `size: 150` default for any column that doesn't set one, same as if you
|
|
688
|
+
* typed `width: 150` yourself. That's an intentional trade of "natural,
|
|
689
|
+
* content-driven column widths" for "every column resizable," so only
|
|
690
|
+
* turn this on for a grid that actually needs manual column resizing.
|
|
691
|
+
*/
|
|
692
|
+
enableColumnResizing?: boolean;
|
|
693
|
+
/** `"onChange"` (default) commits width live while dragging; `"onEnd"` commits once on release. */
|
|
694
|
+
columnResizeMode?: "onChange" | "onEnd";
|
|
695
|
+
/**
|
|
696
|
+
* Controlled column widths, e.g. persisted the same way `columnVisibility`
|
|
697
|
+
* might be. Both must be supplied together to actually drive sizing —
|
|
698
|
+
* omit both to let `<DataGrid>` manage resize state internally.
|
|
699
|
+
*/
|
|
700
|
+
columnSizing?: ColumnSizingState;
|
|
701
|
+
onColumnSizingChange?: (sizing: ColumnSizingState) => void;
|
|
702
|
+
/**
|
|
703
|
+
* Renders the built-in Previous/Next pagination footer. Defaults to true.
|
|
704
|
+
* Set to false when a caller renders its own pagination UI instead (e.g.
|
|
705
|
+
* one with specific `data-testid`s an existing test suite depends on) —
|
|
706
|
+
* `<DataGrid>` itself never conditions any other behavior on this, it just
|
|
707
|
+
* stops rendering that one footer.
|
|
708
|
+
*/
|
|
709
|
+
showPagination?: boolean;
|
|
710
|
+
/**
|
|
711
|
+
* Controlled row selection, e.g. so a caller can build its own toolbar
|
|
712
|
+
* (a bulk-export button, a "N selected" count) elsewhere on the page
|
|
713
|
+
* instead of only reacting to selection via `headerActions`' `onSelect`
|
|
714
|
+
* closure. Both must be supplied together to actually control it — omit
|
|
715
|
+
* both to let `<DataGrid>` manage selection state internally (the
|
|
716
|
+
* default, unchanged from before this existed).
|
|
717
|
+
*/
|
|
718
|
+
selectedIds?: ReadonlySet<string>;
|
|
719
|
+
onSelectedIdsChange?: (ids: ReadonlySet<string>) => void;
|
|
720
|
+
/**
|
|
721
|
+
* Extra props (className, onClick, data-* attributes, etc.) spread onto a
|
|
722
|
+
* row's own `<tr>`. Applied AFTER `<DataGrid>`'s own `data-testid`/
|
|
723
|
+
* `data-index`/`ref`, so those stay intact even if this returns keys that
|
|
724
|
+
* would otherwise collide — use it to make a whole row clickable (an
|
|
725
|
+
* `onClick` `<DataGrid>` has no other way to express — `rowActions` is a
|
|
726
|
+
* kebab menu, not a row-wide click target), or to attach a `className`/
|
|
727
|
+
* other `data-*` attribute an existing test suite already depends on. A
|
|
728
|
+
* `data-testid` returned here specifically is NOT one of those — see
|
|
729
|
+
* `getRowTestId` below for overriding that one. Typed
|
|
730
|
+
* as a plain `Record` (not `HTMLAttributes<HTMLTableRowElement>`) since
|
|
731
|
+
* that interface has no index signature — a return value with only
|
|
732
|
+
* `data-*` keys and no standard HTML attributes would fail TypeScript's
|
|
733
|
+
* "no properties in common" excess-property check against it.
|
|
734
|
+
*
|
|
735
|
+
* `onClick` specifically is safe to combine with `renderDetail`'s expand
|
|
736
|
+
* chevron, a `rowActions` kebab, or `selectedIds`' checkbox — each of
|
|
737
|
+
* those controls stops its own click from bubbling up to this `onClick`.
|
|
738
|
+
* That guard only covers `click`, though: an `onMouseDown`/`onDoubleClick`/
|
|
739
|
+
* `onContextMenu` returned here would still fire from inside any of those
|
|
740
|
+
* controls too, since nothing stops those event types from bubbling.
|
|
741
|
+
*/
|
|
742
|
+
getRowProps?: (row: TRow) => Record<string, unknown>;
|
|
743
|
+
/**
|
|
744
|
+
* Overrides the per-row `data-testid` (default `row-${getRowId(row)}`).
|
|
745
|
+
* Note this is NOT something `getRowProps` above can do despite what its
|
|
746
|
+
* own doc says about "a caller-specific data-testid" — `<DataGrid>` always
|
|
747
|
+
* re-applies its own `data-testid` after spreading `getRowProps`'s result,
|
|
748
|
+
* specifically so a colliding key there can't accidentally break e2e
|
|
749
|
+
* selectors depending on the default `row-${id}` convention. Use this prop
|
|
750
|
+
* instead when a consumer's existing test suite selects rows by a
|
|
751
|
+
* different, entity-specific convention (e.g. `import-row-${id}`) that
|
|
752
|
+
* predates adopting `<DataGrid>` — lets that suite need zero changes.
|
|
753
|
+
*/
|
|
754
|
+
getRowTestId?: (row: TRow) => string;
|
|
755
|
+
/**
|
|
756
|
+
* `data-testid` for the table's own scrollable container (the element
|
|
757
|
+
* `virtualize.maxBodyHeight` actually applies to, and — with or without
|
|
758
|
+
* virtualization — the element horizontal overflow scrolls within). Omit
|
|
759
|
+
* for no attribute at all. Useful for an e2e test that needs to scroll
|
|
760
|
+
* this specific grid or assert on its scroll position; the outer
|
|
761
|
+
* `data-testid="datagrid"` wrapper isn't it, and isn't unique per grid on
|
|
762
|
+
* a page with more than one.
|
|
763
|
+
*/
|
|
764
|
+
testId?: string;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* A single React Table component covering both client- and server-driven
|
|
769
|
+
* data: filtering, sorting, and pagination all flow through one `GridState`,
|
|
770
|
+
* with `dataSource.mode` deciding whether the grid computes the visible page
|
|
771
|
+
* itself (`"client"`) or trusts a page the caller already computed
|
|
772
|
+
* (`"server"`). Column filter UI defaults to the type-appropriate widget
|
|
773
|
+
* from `src/filter` unless a column supplies its own `cell` renderer.
|
|
774
|
+
*/
|
|
775
|
+
declare function DataGrid<TRow extends RowData>({ columns, dataSource, getRowId, rowActions, headerActions, enableMultiSort, initialState, columnVisibility, renderDetail, enableColumnResizing, columnResizeMode, columnSizing, onColumnSizingChange, virtualize, showPagination, getRowProps, getRowTestId, gridState, selectedIds: controlledSelectedIds, onSelectedIdsChange, testId, loading: loadingProp, }: DataGridProps<TRow>): ReactElement;
|
|
776
|
+
|
|
777
|
+
interface GridStateController {
|
|
778
|
+
state: GridState;
|
|
779
|
+
/** Per-column FilterDescriptor, keyed by column id. */
|
|
780
|
+
filtersByColumn: ReadonlyMap<string, FilterDescriptor>;
|
|
781
|
+
setColumnFilter: (columnId: string, next: FilterDescriptor | undefined) => void;
|
|
782
|
+
/**
|
|
783
|
+
* Cycles a column's sort: none -> asc -> desc -> none, or none -> desc -> asc -> none
|
|
784
|
+
* when `descFirst` is true (see `BaseColumn.sortDescFirst`) — for a column whose
|
|
785
|
+
* "interesting" direction is descending (revenue, risk scores, backlog), so a first
|
|
786
|
+
* click surfaces the highest/most-urgent values instead of the lowest/safest ones.
|
|
787
|
+
* Adds/replaces per `enableMultiSort`.
|
|
788
|
+
*/
|
|
789
|
+
toggleSort: (columnId: string, additive: boolean, descFirst?: boolean) => void;
|
|
790
|
+
setPage: (page: number) => void;
|
|
791
|
+
setPageSize: (pageSize: number) => void;
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Owns `GridState` (filter/sort/page/pageSize) for `<DataGrid>` and, in
|
|
795
|
+
* `"server"` mode, calls `dataSource.onStateChange` on every change —
|
|
796
|
+
* debounced ~300ms for filter changes (typing), immediately for sort/page
|
|
797
|
+
* changes. In `"client"` mode `onStateChange` doesn't exist and is never
|
|
798
|
+
* called; the grid re-filters/re-sorts the in-memory array itself instead.
|
|
799
|
+
*
|
|
800
|
+
* Deliberately takes no `columns` and enforces no `sortable`/`filterable`
|
|
801
|
+
* policy itself — `toggleSort`/`setColumnFilter` will happily act on any
|
|
802
|
+
* column id passed to them. `<DataGrid>` is the only place that consults
|
|
803
|
+
* `isSortable`/`isFilterable` (in its rendered header, gating the sort
|
|
804
|
+
* button's `onClick` and whether a filter icon renders at all), because this
|
|
805
|
+
* hook is a generic state container, not a per-column-policy enforcer. A
|
|
806
|
+
* caller that imports this hook directly (bypassing <DataGrid>'s UI
|
|
807
|
+
* entirely) is responsible for applying that same policy itself before
|
|
808
|
+
* calling these setters, the same way it's responsible for its own markup.
|
|
809
|
+
*
|
|
810
|
+
* `externalState`, when given (from the very first render on), makes this
|
|
811
|
+
* fully caller-controlled: whenever it changes identity, internal `state`
|
|
812
|
+
* is overwritten to match (restoring a saved filter, clearing everything at
|
|
813
|
+
* once, applying a URL-restored state — all without remounting
|
|
814
|
+
* `<DataGrid>`). Pass the *same* value back in that a server-mode
|
|
815
|
+
* `dataSource.onStateChange` call just handed you (mirror it into your own
|
|
816
|
+
* state and pass that straight through) — a fresh object literal recreated
|
|
817
|
+
* every render re-triggers this resync every render too. Like a controlled
|
|
818
|
+
* `<input>`, don't switch between passing `externalState` and omitting it
|
|
819
|
+
* across the component's lifetime — start controlled and stay controlled,
|
|
820
|
+
* or start uncontrolled and stay uncontrolled; toggling to `undefined`
|
|
821
|
+
* later does not hand ownership back, it just stops resyncing.
|
|
822
|
+
*/
|
|
823
|
+
declare function useGridState<TRow>(dataSource: DataSource<TRow>, initialState?: Partial<GridState>, externalState?: GridState): GridStateController;
|
|
824
|
+
|
|
825
|
+
interface ClientProcessingResult<TRow> {
|
|
826
|
+
rows: TRow[];
|
|
827
|
+
rowCount: number;
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Applies a `GridState` to an in-memory row array: filter (via
|
|
831
|
+
* `evaluateFilter`, matching sql.py/meili.py semantics), then multi-column
|
|
832
|
+
* sort, then pagination. This is the entire implementation of `"client"`
|
|
833
|
+
* DataSource mode — `"server"` mode skips this and trusts the caller.
|
|
834
|
+
*/
|
|
835
|
+
declare function processClientData<TRow>(data: readonly TRow[], state: GridState): ClientProcessingResult<TRow>;
|
|
836
|
+
|
|
837
|
+
interface DebouncedCallback<TArgs extends unknown[]> {
|
|
838
|
+
/** Schedules `callback`, resetting the timer if one is already pending. */
|
|
839
|
+
run: (...args: TArgs) => void;
|
|
840
|
+
/**
|
|
841
|
+
* Discards any pending scheduled call without invoking it. Callers that
|
|
842
|
+
* also have an immediate, non-debounced path to the same effect (see
|
|
843
|
+
* `<DataGrid>`'s `useGridState`, where sort/page changes call the
|
|
844
|
+
* underlying callback immediately) need this to prevent an
|
|
845
|
+
* already-in-flight debounced call from firing later with stale state and
|
|
846
|
+
* overwriting the immediate one.
|
|
847
|
+
*/
|
|
848
|
+
cancel: () => void;
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Returns a stable `{ run, cancel }` pair. `run` delays invoking `callback`
|
|
852
|
+
* until `delayMs` has elapsed since the last call, resetting the timer on
|
|
853
|
+
* every call in between (standard trailing-edge debounce). Used by
|
|
854
|
+
* `<DataGrid>` in `"server"` mode to avoid firing `onStateChange` on every
|
|
855
|
+
* keystroke while a filter widget is being typed into.
|
|
856
|
+
*/
|
|
857
|
+
declare function useDebouncedCallback<TArgs extends unknown[]>(callback: (...args: TArgs) => void, delayMs: number): DebouncedCallback<TArgs>;
|
|
858
|
+
|
|
859
|
+
/**
|
|
860
|
+
* Accessor-based node shape, matching `<DataGrid>`'s `getRowId` convention
|
|
861
|
+
* rather than requiring `TRow` to carry specific field names.
|
|
862
|
+
*
|
|
863
|
+
* `hasChildren` and `getChildren` are deliberately independent, mirroring
|
|
864
|
+
* a consuming app's contract tree model (`has_children: boolean` distinct
|
|
865
|
+
* from an optional `children` array): a node can know it *has* children the backend
|
|
866
|
+
* hasn't sent yet. A node only needs lazy-loading (`onLoadChildren` gets
|
|
867
|
+
* called) when `hasChildren(row)` is true AND `getChildren(row)` returns
|
|
868
|
+
* nothing usable yet.
|
|
869
|
+
*/
|
|
870
|
+
interface TreeAccessors<TRow> {
|
|
871
|
+
getRowId: (row: TRow) => string;
|
|
872
|
+
/** Already-loaded children, if any — from eager data or a prior `onLoadChildren` call. */
|
|
873
|
+
getChildren: (row: TRow) => TRow[] | undefined;
|
|
874
|
+
/**
|
|
875
|
+
* Whether this node has children at all, independent of whether they're
|
|
876
|
+
* loaded yet. Defaults to `(row) => (getChildren(row)?.length ?? 0) > 0`
|
|
877
|
+
* when omitted — i.e. purely eager trees (no lazy loading) don't need to
|
|
878
|
+
* supply this.
|
|
879
|
+
*/
|
|
880
|
+
hasChildren?: (row: TRow) => boolean;
|
|
881
|
+
}
|
|
882
|
+
interface TreeDataGridProps<TRow> extends TreeAccessors<TRow> {
|
|
883
|
+
columns: ColumnDef<TRow>[];
|
|
884
|
+
/** Root-level rows. */
|
|
885
|
+
data: TRow[];
|
|
886
|
+
/**
|
|
887
|
+
* The column whose cell gets the depth indentation + expand/collapse
|
|
888
|
+
* chevron prepended. Defaults to the first column in `columns`.
|
|
889
|
+
*/
|
|
890
|
+
treeColumnId?: string;
|
|
891
|
+
/**
|
|
892
|
+
* Fetches a node's children on demand. Omit this entirely for a fully
|
|
893
|
+
* eager tree (all children already present via `getChildren`) — a node
|
|
894
|
+
* with `hasChildren(row)` true but no `onLoadChildren` is treated as a
|
|
895
|
+
* dead end (chevron renders, but expanding it just shows "No children.").
|
|
896
|
+
* Results are cached per node id for the component's lifetime: collapsing
|
|
897
|
+
* and re-expanding a node never refetches.
|
|
898
|
+
*/
|
|
899
|
+
onLoadChildren?: (row: TRow) => Promise<TRow[]>;
|
|
900
|
+
/** How many levels to auto-expand on first render (0 = collapsed root only). Defaults to 0. */
|
|
901
|
+
initialExpandedLevel?: number;
|
|
902
|
+
/** Controlled expanded state, keyed by node id. Omit for the grid to own this itself. */
|
|
903
|
+
expanded?: Record<string, boolean>;
|
|
904
|
+
onExpandedChange?: (expanded: Record<string, boolean>) => void;
|
|
905
|
+
/** Pixels of left padding added per depth level to the tree column's cell. Defaults to 20. */
|
|
906
|
+
indentSize?: number;
|
|
907
|
+
/**
|
|
908
|
+
* Extra props (className, onClick, data-* attributes, etc.) spread onto a
|
|
909
|
+
* row's own `<tr>`. Applied AFTER `<TreeDataGrid>`'s own `data-testid`/
|
|
910
|
+
* `ref`, so those stay intact even if this returns keys that would
|
|
911
|
+
* otherwise collide — use it to make a whole row clickable (an `onClick`
|
|
912
|
+
* `<TreeDataGrid>` has no other way to express — `rowActions` is a kebab
|
|
913
|
+
* menu, not a row-wide click target), or to attach a `className`/other
|
|
914
|
+
* `data-*` attribute an existing test suite already depends on. A
|
|
915
|
+
* `data-testid` returned here specifically is NOT one of those — see
|
|
916
|
+
* `getRowTestId` below for overriding that one. `depth` and `index` are
|
|
917
|
+
* the row's tree depth and its position among the currently visible
|
|
918
|
+
* (flattened) rows, e.g. for depth-aware indentation or index-aware
|
|
919
|
+
* striping. Typed as a plain `Record` (not `HTMLAttributes<HTMLTableRowElement>`)
|
|
920
|
+
* since that interface has no index signature — a return value with only
|
|
921
|
+
* `data-*` keys and no standard HTML attributes would fail TypeScript's
|
|
922
|
+
* "no properties in common" excess-property check against it.
|
|
923
|
+
*
|
|
924
|
+
* `onClick` specifically is safe to combine with the expand/collapse
|
|
925
|
+
* chevron or a `rowActions` kebab — each of those controls stops its own
|
|
926
|
+
* click from bubbling up to this `onClick`. That guard only covers
|
|
927
|
+
* `click`, though: an `onMouseDown`/`onDoubleClick`/`onContextMenu`
|
|
928
|
+
* returned here would still fire from inside either control too, since
|
|
929
|
+
* nothing stops those event types from bubbling.
|
|
930
|
+
*/
|
|
931
|
+
getRowProps?: (row: TRow, depth: number, index: number) => Record<string, unknown>;
|
|
932
|
+
/**
|
|
933
|
+
* Overrides the per-row `data-testid` (default `tree-row-${getRowId(row)}`).
|
|
934
|
+
* Note this is NOT something `getRowProps` above can do despite what its
|
|
935
|
+
* own doc says about "a caller-specific data-testid" — `<TreeDataGrid>`
|
|
936
|
+
* always re-applies its own `data-testid` after spreading `getRowProps`'s
|
|
937
|
+
* result, specifically so a colliding key there can't accidentally break
|
|
938
|
+
* e2e selectors depending on the default convention. Use this prop instead
|
|
939
|
+
* when a consumer's existing test suite selects rows by a different,
|
|
940
|
+
* entity-specific convention that predates adopting `<TreeDataGrid>`.
|
|
941
|
+
*/
|
|
942
|
+
getRowTestId?: (row: TRow) => string;
|
|
943
|
+
/** Per-row kebab menu — same `MenuItem` contract as `<DataGrid>`'s `rowActions`. */
|
|
944
|
+
rowActions?: MenuItem<TRow>[];
|
|
945
|
+
/**
|
|
946
|
+
* Enables row virtualization once the flattened (visible) row count
|
|
947
|
+
* exceeds this threshold. Defaults to 100 — small trees just render every
|
|
948
|
+
* row directly (simpler DOM, no virtualizer overhead); large/deep trees
|
|
949
|
+
* switch to `@tanstack/react-virtual`. Set to `0` to always virtualize, or
|
|
950
|
+
* `Infinity` to never.
|
|
951
|
+
*/
|
|
952
|
+
virtualizeThreshold?: number;
|
|
953
|
+
/** Fixed row height estimate in pixels, used by the virtualizer. Defaults to 40. */
|
|
954
|
+
estimatedRowHeight?: number;
|
|
955
|
+
/** Max height of the scrollable table body in pixels. Required for virtualization to have a viewport to scroll within. Defaults to 480. */
|
|
956
|
+
maxBodyHeight?: number;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/**
|
|
960
|
+
* A lazy-loading tree grid: generalizes a consuming app's contract tree list
|
|
961
|
+
* (a from-scratch hand-rolled hierarchy table with no shared abstraction)
|
|
962
|
+
* into a reusable component on top of the same `ColumnDef<TRow>` system
|
|
963
|
+
* `<DataGrid>` uses. Deliberately does NOT support sorting or client-side
|
|
964
|
+
* filtering — that original hierarchy table has neither (hierarchy order
|
|
965
|
+
* comes from the backend; "filtering" there means asking the server for a
|
|
966
|
+
* differently-shaped tree, which is an app-level concern, not something
|
|
967
|
+
* this component can generalize).
|
|
968
|
+
*
|
|
969
|
+
* Row indentation + the expand/collapse chevron are prepended to whichever
|
|
970
|
+
* column's cell matches `treeColumnId` (defaults to `columns[0]`); every
|
|
971
|
+
* other column renders exactly like it would in `<DataGrid>`.
|
|
972
|
+
*/
|
|
973
|
+
declare function TreeDataGrid<TRow>({ columns, data, treeColumnId, getRowId, getChildren, hasChildren, onLoadChildren, initialExpandedLevel, expanded: controlledExpanded, onExpandedChange, indentSize, getRowProps, getRowTestId, rowActions, virtualizeThreshold, estimatedRowHeight, maxBodyHeight, }: TreeDataGridProps<TRow>): ReactElement;
|
|
974
|
+
|
|
975
|
+
interface FlatTreeRow<TRow> {
|
|
976
|
+
row: TRow;
|
|
977
|
+
id: string;
|
|
978
|
+
depth: number;
|
|
979
|
+
hasChildren: boolean;
|
|
980
|
+
isExpanded: boolean;
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Depth-first flatten of `roots` into the currently *visible* rows only —
|
|
984
|
+
* a collapsed node's descendants never enter the output at all, matching
|
|
985
|
+
* `ContractTreelist`'s `flattenContractTree` (the function this generalizes).
|
|
986
|
+
*
|
|
987
|
+
* `childrenOverride` (a node id -> loaded children map) takes priority over
|
|
988
|
+
* `getChildren(row)` when both exist, since it reflects the most recently
|
|
989
|
+
* fetched batch for a lazily-loaded node — `getChildren` may still return a
|
|
990
|
+
* stale/partial eager batch for the same node.
|
|
991
|
+
*/
|
|
992
|
+
declare function flattenTree<TRow>(roots: TRow[], accessors: TreeAccessors<TRow>, expanded: Record<string, boolean>, childrenOverride: ReadonlyMap<string, TRow[]>): FlatTreeRow<TRow>[];
|
|
993
|
+
|
|
994
|
+
interface TreeStateController<TRow> {
|
|
995
|
+
expanded: Record<string, boolean>;
|
|
996
|
+
childrenMap: ReadonlyMap<string, TRow[]>;
|
|
997
|
+
loadingIds: ReadonlySet<string>;
|
|
998
|
+
/** Node id -> the message from its most recent failed `onLoadChildren` call. */
|
|
999
|
+
errorIds: ReadonlyMap<string, string>;
|
|
1000
|
+
toggleExpand: (row: TRow) => void;
|
|
1001
|
+
/** Re-attempts `onLoadChildren` for a node currently in `errorIds`. */
|
|
1002
|
+
retry: (row: TRow) => void;
|
|
1003
|
+
/** Expands every node from the roots down to (but not including) `level`, fetching lazy children along the way. */
|
|
1004
|
+
expandToLevel: (level: number) => Promise<void>;
|
|
1005
|
+
}
|
|
1006
|
+
interface UseTreeStateOptions<TRow> extends TreeAccessors<TRow> {
|
|
1007
|
+
data: TRow[];
|
|
1008
|
+
onLoadChildren?: (row: TRow) => Promise<TRow[]>;
|
|
1009
|
+
expanded?: Record<string, boolean>;
|
|
1010
|
+
onExpandedChange?: (expanded: Record<string, boolean>) => void;
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Owns everything `<TreeDataGrid>` needs beyond plain rendering: expand
|
|
1014
|
+
* state (controlled or internal, same pattern as `<ColumnSelector>`'s
|
|
1015
|
+
* `visibility`/`onVisibilityChange`), a per-node lazy-children cache, and
|
|
1016
|
+
* per-node loading/error tracking.
|
|
1017
|
+
*
|
|
1018
|
+
* Deliberately does NOT reset any of this when `data` changes identity —
|
|
1019
|
+
* unlike `ContractTreelist`, which resets `expanded`/`childrenMap` on every
|
|
1020
|
+
* filter change because filtering there reshapes the whole tree server-side.
|
|
1021
|
+
* This hook has no opinion on why `data` changed; a caller that wants a full
|
|
1022
|
+
* reset (e.g. switching to a different root entity entirely) should remount
|
|
1023
|
+
* `<TreeDataGrid>` via a `key` prop rather than rely on implicit magic here.
|
|
1024
|
+
*/
|
|
1025
|
+
declare function useTreeState<TRow>({ data, getRowId, getChildren, hasChildren, onLoadChildren, expanded: controlledExpanded, onExpandedChange, }: UseTreeStateOptions<TRow>): TreeStateController<TRow>;
|
|
1026
|
+
|
|
1027
|
+
export { ActionsMenu, type BaseColumn, type BooleanColumn, BooleanFilter, type ClientProcessingResult, type ColumnAlign, type ColumnDef, type ColumnGroup, ColumnSelector, type ColumnSelectorProps, type ColumnVisibility, type CompositeFilterDescriptor, DataGrid, type DataGridProps, type DataSource, type DateColumn, DateRangeFilter, type DebouncedCallback, type EnumColumn, EnumFilter, type EnumOption, type FilterDescriptor, type FilterOperator, type FilterWidgetProps, type FlatTreeRow, type GridState, type GridStateController, type MenuItem, type MenuItemContext, type NumberColumn, NumberComparisonFilter, NumberHistogramFilter, type NumberHistogramFilterProps, NumberRangeFilter, type ResolvedMenuItem, type SortDescriptor, type StringColumn, StringFilter, type TreeAccessors, TreeDataGrid, type TreeDataGridProps, type TreeStateController, UNARY_OPERATORS, type UseTreeStateOptions, alignClassName, canHideColumn, canHideGroup, countVisible, defaultAlign, defaultFormat, evaluateFilter, facetedNumberValues, fieldKey, flattenTree, getColumnValue, groupColumns, isColumnVisible, isCompositeFilterDescriptor, isFilterDescriptor, isFilterable, isSortable, processClientData, renderDefaultFilterWidget, resolveMenuItems, storageKeyFor, useDebouncedCallback, useGridState, useTreeState };
|