@helix-x/datagrid-ui 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -2,75 +2,284 @@ import * as react from 'react';
2
2
  import { ReactNode, Ref } from 'react';
3
3
 
4
4
  /**
5
- * The wire shapes below intentionally mirror what ag-grid's server-side row
6
- * model posts, because most existing backends that speak "grid" already parse
7
- * them. Keeping them identical is what lets a screen swap grids without a
8
- * backend change.
5
+ * Every public type in `@helix-x/datagrid-ui`.
6
+ *
7
+ * The filter, sort and row-request shapes deliberately mirror what ag-grid's
8
+ * server-side row model posts, so a backend written for that model accepts this
9
+ * grid's requests unchanged.
10
+ */
11
+
12
+ /**
13
+ * Comparison operators available on a `text` column filter.
14
+ *
15
+ * `blank` and `notBlank` take no operand — see {@link isUnaryFilter}.
16
+ *
17
+ * @see {@link TEXT_FILTER_TYPES} for the same list as a runtime array.
9
18
  */
10
19
  type TextFilterType = 'contains' | 'notContains' | 'equals' | 'notEqual' | 'startsWith' | 'endsWith' | 'blank' | 'notBlank';
20
+ /**
21
+ * Comparison operators available on a `number` column filter.
22
+ *
23
+ * `inRange` uses both `filter` and `filterTo`; `blank` and `notBlank` take no
24
+ * operand at all.
25
+ *
26
+ * @see {@link NUMBER_FILTER_TYPES} for the same list as a runtime array.
27
+ */
11
28
  type NumberFilterType = 'equals' | 'notEqual' | 'lessThan' | 'lessThanOrEqual' | 'greaterThan' | 'greaterThanOrEqual' | 'inRange' | 'blank' | 'notBlank';
29
+ /**
30
+ * Comparison operators available on a `date` column filter.
31
+ *
32
+ * `inRange` uses both `dateFrom` and `dateTo`; `blank` and `notBlank` take no
33
+ * operand.
34
+ *
35
+ * @see {@link DATE_FILTER_TYPES} for the same list as a runtime array.
36
+ */
12
37
  type DateFilterType = 'equals' | 'notEqual' | 'before' | 'after' | 'inRange' | 'blank' | 'notBlank';
38
+ /**
39
+ * A text filter as sent to the server.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * const model: TextFilterModel = {
44
+ * filterType: 'text',
45
+ * type: 'contains',
46
+ * filter: 'acme',
47
+ * };
48
+ * ```
49
+ */
13
50
  interface TextFilterModel {
51
+ /** Discriminant. Always `'text'`. */
14
52
  filterType: 'text';
53
+ /** The comparison to apply. */
15
54
  type: TextFilterType;
55
+ /** The search term. Absent for `blank` and `notBlank`. */
16
56
  filter?: string;
17
57
  }
58
+ /**
59
+ * A numeric filter as sent to the server.
60
+ *
61
+ * @example Between two values
62
+ * ```ts
63
+ * const model: NumberFilterModel = {
64
+ * filterType: 'number',
65
+ * type: 'inRange',
66
+ * filter: 10,
67
+ * filterTo: 100,
68
+ * };
69
+ * ```
70
+ */
18
71
  interface NumberFilterModel {
72
+ /** Discriminant. Always `'number'`. */
19
73
  filterType: 'number';
74
+ /** The comparison to apply. */
20
75
  type: NumberFilterType;
76
+ /** The operand, or the lower bound when `type` is `'inRange'`. */
21
77
  filter?: number;
78
+ /** The upper bound. Only meaningful when `type` is `'inRange'`. */
22
79
  filterTo?: number;
23
80
  }
81
+ /**
82
+ * A date filter as sent to the server.
83
+ *
84
+ * Dates are calendar days, not instants — compare on the day, or a same-day
85
+ * timestamp will never match its own date.
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * const model: DateFilterModel = {
90
+ * filterType: 'date',
91
+ * type: 'after',
92
+ * dateFrom: '2026-01-01',
93
+ * };
94
+ * ```
95
+ */
24
96
  interface DateFilterModel {
97
+ /** Discriminant. Always `'date'`. */
25
98
  filterType: 'date';
99
+ /** The comparison to apply. */
26
100
  type: DateFilterType;
27
- /** `YYYY-MM-DD` */
101
+ /** Lower bound, formatted `YYYY-MM-DD`. */
28
102
  dateFrom?: string;
103
+ /** Upper bound, formatted `YYYY-MM-DD`. Only used by `'inRange'`. */
29
104
  dateTo?: string;
30
105
  }
106
+ /**
107
+ * A set (multi-select) filter as sent to the server.
108
+ *
109
+ * An empty `values` array means "no constraint" — that is how the grid clears
110
+ * a set filter. {@link buildSetFilter} returns `null` rather than an empty set.
111
+ *
112
+ * @example
113
+ * ```ts
114
+ * const model: SetFilterModel = {
115
+ * filterType: 'set',
116
+ * values: ['ACTIVE', 'PENDING'],
117
+ * };
118
+ * ```
119
+ */
31
120
  interface SetFilterModel {
121
+ /** Discriminant. Always `'set'`. */
32
122
  filterType: 'set';
123
+ /** The selected values. Compared as strings. */
33
124
  values: string[];
34
125
  }
126
+ /**
127
+ * Any one column filter. Discriminate on `filterType`.
128
+ *
129
+ * @example
130
+ * ```ts
131
+ * function describe(filter: HxFilterModel) {
132
+ * switch (filter.filterType) {
133
+ * case 'text': return filter.filter;
134
+ * case 'number': return filter.filter;
135
+ * case 'date': return filter.dateFrom;
136
+ * case 'set': return filter.values.join(', ');
137
+ * }
138
+ * }
139
+ * ```
140
+ */
35
141
  type HxFilterModel = TextFilterModel | NumberFilterModel | DateFilterModel | SetFilterModel;
142
+ /**
143
+ * Every active filter, keyed by {@link ColumnDef.colId | column id}.
144
+ *
145
+ * A column with no entry is unfiltered. Use {@link withFilter} to add or remove
146
+ * one immutably.
147
+ */
36
148
  type FilterModelMap = Record<string, HxFilterModel>;
149
+ /**
150
+ * Which filter UI a column offers, set via {@link ColumnDef.filter}.
151
+ *
152
+ * Pass `false` on the column instead to disable filtering entirely.
153
+ */
37
154
  type FilterKind = 'text' | 'number' | 'date' | 'set';
155
+ /** Sort direction for one column. */
38
156
  type SortDirection = 'asc' | 'desc';
157
+ /**
158
+ * One entry in the sort model.
159
+ *
160
+ * The model is an ordered array: the first entry is the primary sort, and later
161
+ * entries break ties in the ones before them.
162
+ *
163
+ * @example Sort by status, then by newest first
164
+ * ```ts
165
+ * const sortModel: SortModelItem[] = [
166
+ * { colId: 'status', sort: 'asc' },
167
+ * { colId: 'createdAt', sort: 'desc' },
168
+ * ];
169
+ * ```
170
+ */
39
171
  interface SortModelItem {
172
+ /** The {@link ColumnDef.colId | column id} to sort on. */
40
173
  colId: string;
174
+ /** Direction to sort in. */
41
175
  sort: SortDirection;
42
176
  }
43
177
  /**
178
+ * What the grid asks the server for: one page, with the active sort and
179
+ * filters.
180
+ *
44
181
  * Structurally compatible with ag-grid's `IServerSideGetRowsRequest`, so an
45
- * endpoint written for that model accepts this verbatim. The grouping/pivot
182
+ * endpoint written for that model accepts this verbatim. The grouping and pivot
46
183
  * fields are always sent empty — this grid does not implement them — but they
47
184
  * are present so servers that destructure them do not fall over.
185
+ *
186
+ * @example Handling the request on the server
187
+ * ```ts
188
+ * app.post('/api/rows', (req, res) => {
189
+ * const { startRow, endRow, sortModel, filterModel } = req.body;
190
+ * const filtered = applyFilters(allRows, filterModel);
191
+ * const sorted = applySort(filtered, sortModel);
192
+ * res.json({ rows: sorted.slice(startRow, endRow), lastRow: filtered.length });
193
+ * });
194
+ * ```
48
195
  */
49
196
  interface HxRowsRequest {
197
+ /** Zero-based index of the first row wanted, inclusive. */
50
198
  startRow: number;
199
+ /** Index one past the last row wanted, exclusive. */
51
200
  endRow: number;
201
+ /** Active sorts, primary first. Empty when unsorted. */
52
202
  sortModel: SortModelItem[];
203
+ /** Active filters, keyed by column id. Empty when unfiltered. */
53
204
  filterModel: FilterModelMap;
205
+ /** Always empty. Present only for ag-grid wire compatibility. */
54
206
  rowGroupCols: never[];
207
+ /** Always empty. Present only for ag-grid wire compatibility. */
55
208
  valueCols: never[];
209
+ /** Always empty. Present only for ag-grid wire compatibility. */
56
210
  pivotCols: never[];
211
+ /** Always `false`. Present only for ag-grid wire compatibility. */
57
212
  pivotMode: false;
213
+ /** Always empty. Present only for ag-grid wire compatibility. */
58
214
  groupKeys: never[];
59
215
  }
216
+ /**
217
+ * What the server returns for one {@link HxRowsRequest}.
218
+ *
219
+ * @typeParam T - The row type.
220
+ */
60
221
  interface HxRowsResponse<T> {
222
+ /** The rows for the requested page only, not the whole result set. */
61
223
  rows: T[];
62
- /** Total row count across all pages, or -1 when unknown. */
224
+ /**
225
+ * Total row count across **all** pages — not `rows.length`. This is what
226
+ * drives the pager. Return `-1` when the total is genuinely unknown.
227
+ */
63
228
  lastRow: number;
64
229
  }
230
+ /**
231
+ * The grid's one required dependency: something that turns a request into rows.
232
+ *
233
+ * @typeParam T - The row type.
234
+ *
235
+ * @example
236
+ * ```tsx
237
+ * const dataSource: HxDataSource<Person> = {
238
+ * getRows: async (request, signal) => {
239
+ * const response = await fetch('/api/people', {
240
+ * method: 'POST',
241
+ * headers: { 'content-type': 'application/json' },
242
+ * body: JSON.stringify(request),
243
+ * signal,
244
+ * });
245
+ * return response.json();
246
+ * },
247
+ * };
248
+ * ```
249
+ *
250
+ * @remarks
251
+ * Memoise the object (or define it outside the component). A new identity on
252
+ * every render causes a refetch.
253
+ */
65
254
  interface HxDataSource<T> {
255
+ /**
256
+ * Fetch one page of rows.
257
+ *
258
+ * @param request - The page, sort and filters being asked for.
259
+ * @param signal - Aborts when the grid supersedes this request. Pass it to
260
+ * `fetch` so a slow response for an old sort can never overwrite a newer one.
261
+ * @returns The page of rows plus the total row count.
262
+ */
66
263
  getRows(request: HxRowsRequest, signal: AbortSignal): Promise<HxRowsResponse<T>>;
67
264
  }
265
+ /** Which edge a pinned column sticks to. */
68
266
  type Pinned = 'left' | 'right';
267
+ /**
268
+ * What {@link ColumnDef.cellRenderer} receives.
269
+ *
270
+ * @typeParam T - The row type.
271
+ * @typeParam C - The {@link DataGridProps.context | context} type.
272
+ */
69
273
  interface CellRendererParams<T, C = unknown> {
274
+ /** The whole row, for cells that need more than their own field. */
70
275
  row: T;
276
+ /** Index within the currently loaded page, not the full result set. */
71
277
  rowIndex: number;
278
+ /** The raw value, after {@link ColumnDef.valueGetter}. */
72
279
  value: unknown;
280
+ /** The display string, after {@link ColumnDef.valueFormatter}. */
73
281
  formatted: string;
282
+ /** The column being rendered. */
74
283
  column: ColumnDef<T, C>;
75
284
  /**
76
285
  * Arbitrary app state handed to every renderer. Put volatile things here
@@ -78,425 +287,1393 @@ interface CellRendererParams<T, C = unknown> {
78
287
  * memoised on `[]` and never rebuild.
79
288
  */
80
289
  context: C;
290
+ /** The live imperative API, for renderers that act on the grid. */
81
291
  api: GridApi<T>;
82
292
  }
293
+ /**
294
+ * What a cell editor receives — both the built-ins and your own.
295
+ *
296
+ * @typeParam T - The row type.
297
+ * @typeParam C - The {@link DataGridProps.context | context} type.
298
+ */
83
299
  interface EditorParams<T, C = unknown> {
300
+ /** Current draft value for this cell. */
84
301
  value: unknown;
302
+ /** The row being edited, as a draft copy. */
85
303
  row: T;
304
+ /** The column being edited. */
86
305
  column: ColumnDef<T, C>;
306
+ /** The grid's {@link DataGridProps.context | context}. */
87
307
  context: C;
88
308
  /** Writes into the row draft. Never hits the network. */
89
309
  onChange: (value: unknown) => void;
90
- /** Commit the whole row. */
310
+ /** Commit the whole row, invoking {@link DataGridProps.onRowCommit}. */
91
311
  onCommit: () => void;
312
+ /** Abandon the edit and restore the original row. */
92
313
  onCancel: () => void;
314
+ /** Server-supplied message for this field, if the last commit was rejected. */
93
315
  error?: string;
316
+ /** True for the first editable cell in the row. */
94
317
  autoFocus?: boolean;
95
318
  }
319
+ /**
320
+ * A custom cell editor.
321
+ *
322
+ * @typeParam T - The row type.
323
+ * @typeParam C - The context type.
324
+ *
325
+ * @example A trimming text editor
326
+ * ```tsx
327
+ * const TrimmedEditor: EditorComponent<Person> = ({ value, onChange, onCommit, onCancel }) => (
328
+ * <input
329
+ * autoFocus
330
+ * value={String(value ?? '')}
331
+ * onChange={(e) => onChange(e.target.value.trim())}
332
+ * onKeyDown={(e) => {
333
+ * if (e.key === 'Enter') onCommit();
334
+ * if (e.key === 'Escape') onCancel();
335
+ * }}
336
+ * />
337
+ * );
338
+ * ```
339
+ */
96
340
  type EditorComponent<T, C = unknown> = (params: EditorParams<T, C>) => ReactNode;
341
+ /**
342
+ * Names of the editors that ship with the grid.
343
+ *
344
+ * @see {@link BUILTIN_EDITORS} for the components these map to.
345
+ */
97
346
  type BuiltinEditor = 'text' | 'number' | 'select' | 'date' | 'checkbox';
347
+ /** One choice offered by the `select` editor. */
98
348
  interface SelectOption {
349
+ /** Shown to the user. */
99
350
  label: string;
351
+ /** Written into the row draft. */
100
352
  value: unknown;
101
353
  }
354
+ /**
355
+ * One column. This is the main thing you write when using the grid.
356
+ *
357
+ * @typeParam T - The row type.
358
+ * @typeParam C - The {@link DataGridProps.context | context} type.
359
+ *
360
+ * @example A representative set of columns
361
+ * ```tsx
362
+ * const columns: ColumnDef<Person>[] = [
363
+ * { field: 'id', header: 'ID', width: 90, filter: 'number' },
364
+ * { field: 'name', header: 'Name', flex: 1, filter: 'text', editable: true },
365
+ * {
366
+ * field: 'status',
367
+ * header: 'Status',
368
+ * filter: 'set',
369
+ * filterParams: { values: () => fetch('/api/statuses').then((r) => r.json()) },
370
+ * editable: true,
371
+ * editor: 'select',
372
+ * editorParams: { options: [{ label: 'Active', value: 'ACTIVE' }] },
373
+ * },
374
+ * {
375
+ * colId: 'fullName',
376
+ * header: 'Full name',
377
+ * sortable: false,
378
+ * valueGetter: (row) => `${row.first} ${row.last}`,
379
+ * },
380
+ * ];
381
+ * ```
382
+ *
383
+ * @remarks
384
+ * Define columns as a module constant or memoise on `[]`. Anything volatile
385
+ * belongs in {@link DataGridProps.context} instead, which reaches every
386
+ * renderer without rebuilding a single definition.
387
+ */
102
388
  interface ColumnDef<T, C = unknown> {
103
- /** Stable identity. Falls back to `field` when omitted. */
389
+ /**
390
+ * Stable identity, used as the key in the sort and filter models and in
391
+ * persisted layout. Falls back to {@link ColumnDef.field | field} when
392
+ * omitted; one of the two is required.
393
+ */
104
394
  colId?: string;
105
- /** Dotted path into the row. Also used as the server-side filter/sort key. */
395
+ /**
396
+ * Dotted path into the row (`'customer.name'`), and the key sent to the
397
+ * server for sorting and filtering.
398
+ *
399
+ * Omit it for purely derived columns and supply `colId` plus
400
+ * {@link ColumnDef.valueGetter | valueGetter} instead.
401
+ */
106
402
  field?: string;
403
+ /** Header content. Any node — an icon, a tooltip wrapper, anything. */
107
404
  header: ReactNode;
108
- /** Plain-text header, used for exports when `header` is a node. */
405
+ /**
406
+ * Plain-text header for exports and aria labels. Required in practice
407
+ * whenever `header` is a node rather than a string.
408
+ */
109
409
  headerName?: string;
410
+ /**
411
+ * Starting width in pixels.
412
+ * @defaultValue 150
413
+ */
110
414
  width?: number;
415
+ /**
416
+ * Floor for both `flex` and user resizing.
417
+ * @defaultValue 60
418
+ */
111
419
  minWidth?: number;
420
+ /** Ceiling for user resizing. */
112
421
  maxWidth?: number;
113
- /** Share of the leftover horizontal space. */
422
+ /**
423
+ * Share of the leftover horizontal space, like `flex-grow`. A column with
424
+ * `flex: 2` takes twice the slack of one with `flex: 1`.
425
+ */
114
426
  flex?: number;
427
+ /** Stick this column to an edge while the rest scroll horizontally. */
115
428
  pinned?: Pinned;
429
+ /** Start hidden. The user can still reveal it from the columns panel. */
116
430
  hide?: boolean;
431
+ /**
432
+ * Allow click-to-sort on the header.
433
+ * @defaultValue true
434
+ */
117
435
  sortable?: boolean;
436
+ /**
437
+ * Allow drag-to-resize.
438
+ * @defaultValue true
439
+ */
118
440
  resizable?: boolean;
119
441
  /** Prevent the user dragging this column out of position. */
120
442
  lockPosition?: boolean;
443
+ /**
444
+ * Which filter UI to offer, or `false` for none.
445
+ *
446
+ * @defaultValue undefined (no filter)
447
+ */
121
448
  filter?: FilterKind | false;
449
+ /** Extra options for the filter named by {@link ColumnDef.filter}. */
122
450
  filterParams?: {
123
- /** Static values, or a loader for a set filter whose options come from an API. */
451
+ /**
452
+ * Options for a `set` filter: a static list, or a function called the first
453
+ * time the popover opens so the list can come from an API.
454
+ */
124
455
  values?: string[] | (() => Promise<string[]>);
125
456
  /** Hide the always-visible filter input under the header for this column. */
126
457
  suppressFloatingFilter?: boolean;
127
458
  };
128
- /** Alternative flat keys to try when the server flattens nested fields. */
459
+ /**
460
+ * Alternative flat keys to try when the server flattens nested fields — some
461
+ * backends return `{"customer.name": x}` rather than a nested object, and
462
+ * change the prefix when grouping is on.
463
+ */
129
464
  fieldAliases?: string[];
465
+ /**
466
+ * Compute the cell value instead of reading `field`. Takes precedence over
467
+ * every other lookup.
468
+ */
130
469
  valueGetter?: (row: T) => unknown;
470
+ /**
471
+ * Turn the raw value into its display string. Also used for exports unless
472
+ * {@link ColumnDef.exportValue} is given.
473
+ */
131
474
  valueFormatter?: (value: unknown, row: T) => string;
475
+ /**
476
+ * Render the cell as markup. Presentation only — sorting and filtering still
477
+ * use `field`, so a decorated cell keeps behaving like a plain one.
478
+ */
132
479
  cellRenderer?: (params: CellRendererParams<T, C>) => ReactNode;
480
+ /** Whether the cell can be edited, optionally per row. */
133
481
  editable?: boolean | ((row: T) => boolean);
482
+ /**
483
+ * Which editor to use: a {@link BuiltinEditor} name or your own component.
484
+ * @defaultValue 'text'
485
+ */
134
486
  editor?: BuiltinEditor | EditorComponent<T, C>;
487
+ /** Extra options for the editor named by {@link ColumnDef.editor}. */
135
488
  editorParams?: {
489
+ /** Choices for the `select` editor. */
136
490
  options?: SelectOption[];
491
+ /** Placeholder for the `text` and `number` editors. */
137
492
  placeholder?: string;
138
493
  };
494
+ /**
495
+ * The flat string written to CSV and the clipboard. Supply this whenever
496
+ * {@link ColumnDef.cellRenderer} produces markup.
497
+ */
139
498
  exportValue?: (row: T) => string;
499
+ /** Leave this column out of CSV and clipboard output entirely. */
140
500
  suppressExport?: boolean;
501
+ /** Extra classes on every cell, optionally per row. */
141
502
  cellClassName?: string | ((row: T) => string);
503
+ /** Extra classes on the header cell. */
142
504
  headerClassName?: string;
143
- /** Horizontal alignment of the cell content. */
505
+ /**
506
+ * Horizontal alignment of the cell content.
507
+ * @defaultValue 'left'
508
+ */
144
509
  align?: 'left' | 'center' | 'right';
145
510
  }
146
- /** A column with every default resolved. Internal, but exported for renderers. */
511
+ /**
512
+ * A {@link ColumnDef} with every optional default filled in.
513
+ *
514
+ * Produced by {@link resolveColumn}. Mostly internal, but exported because the
515
+ * exporter helpers and the columns panel take it.
516
+ *
517
+ * @typeParam T - The row type.
518
+ * @typeParam C - The context type.
519
+ */
147
520
  interface ResolvedColumn<T, C = unknown> extends ColumnDef<T, C> {
521
+ /** Always present — `colId`, or `field` when `colId` was omitted. */
148
522
  colId: string;
523
+ /** Resolved width, never below `minWidth`. */
149
524
  width: number;
525
+ /** Resolved minimum width. */
150
526
  minWidth: number;
527
+ /** Resolved sortability. */
151
528
  sortable: boolean;
529
+ /** Resolved resizability. */
152
530
  resizable: boolean;
153
531
  }
154
- /** Geometry for one visible column, computed once per layout change. */
532
+ /**
533
+ * Geometry for one visible column, computed once per layout change.
534
+ *
535
+ * @typeParam T - The row type.
536
+ * @typeParam C - The context type.
537
+ */
155
538
  interface ColumnLayoutItem<T, C = unknown> {
539
+ /** The column this geometry belongs to. */
156
540
  column: ResolvedColumn<T, C>;
541
+ /** Convenience copy of `column.colId`. */
157
542
  colId: string;
543
+ /** Final pixel width, after `flex` distribution and user resizing. */
158
544
  width: number;
159
545
  /** Offset from the left edge of the full (unscrolled) column strip. */
160
546
  left: number;
547
+ /** Which edge this column is pinned to, if any. */
161
548
  pinned?: Pinned;
162
549
  /** `left`/`right` offset to use for `position: sticky` on pinned columns. */
163
550
  stickyOffset: number;
164
551
  }
552
+ /**
553
+ * The measured layout of every visible column.
554
+ *
555
+ * @typeParam T - The row type.
556
+ * @typeParam C - The context type.
557
+ */
165
558
  interface ColumnLayout<T, C = unknown> {
559
+ /** Visible columns in display order, each with its geometry. */
166
560
  items: ColumnLayoutItem<T, C>[];
561
+ /** Combined width of all visible columns. */
167
562
  totalWidth: number;
563
+ /** Combined width of the left-pinned columns. */
168
564
  leftPinnedWidth: number;
565
+ /** Combined width of the right-pinned columns. */
169
566
  rightPinnedWidth: number;
170
567
  }
568
+ /**
569
+ * The outcome of {@link DataGridProps.onRowCommit}.
570
+ *
571
+ * Returning `ok: false` keeps the row in edit mode and paints each message onto
572
+ * the cell whose column id it is keyed by — which is how server-side validation
573
+ * reaches the user without being mirrored in the client.
574
+ *
575
+ * @example
576
+ * ```ts
577
+ * async function onRowCommit(draft: Person): Promise<RowCommitResult> {
578
+ * const response = await fetch(`/api/people/${draft.id}`, {
579
+ * method: 'PATCH',
580
+ * body: JSON.stringify(draft),
581
+ * });
582
+ * if (response.status === 422) {
583
+ * const { errors, message } = await response.json();
584
+ * return { ok: false, errors, message };
585
+ * }
586
+ * return { ok: true, row: await response.json() };
587
+ * }
588
+ * ```
589
+ */
171
590
  type RowCommitResult = {
591
+ /** Discriminant: the row was saved. */
172
592
  ok: true;
593
+ /**
594
+ * The server's canonical copy, which replaces the row in place. Useful
595
+ * when the server fills in derived fields.
596
+ */
173
597
  row?: unknown;
174
598
  } | {
599
+ /** Discriminant: the row was rejected and stays open. */
175
600
  ok: false;
601
+ /** Messages keyed by column id, painted onto the offending cells. */
176
602
  errors: Record<string, string>;
603
+ /** A single message for the whole row. */
177
604
  message?: string;
178
605
  };
606
+ /**
607
+ * Schema version for {@link PersistedGridState}.
608
+ *
609
+ * Saved state carrying a different version is discarded rather than migrated:
610
+ * the layout is a convenience, and a bad restore is worse than starting from
611
+ * the column defaults.
612
+ */
179
613
  declare const GRID_STATE_VERSION = 1;
614
+ /**
615
+ * What the grid writes to `localStorage` under
616
+ * {@link DataGridProps.storageKey}.
617
+ *
618
+ * Stored under the key `hxg:<storageKey>`.
619
+ */
180
620
  interface PersistedGridState {
621
+ /** Schema version. Compared against {@link GRID_STATE_VERSION} on read. */
181
622
  v: number;
623
+ /** Column layout the user arranged. */
182
624
  columns: {
625
+ /** Column ids in display order. */
183
626
  order: string[];
627
+ /** Ids of columns the user hid. */
184
628
  hidden: string[];
629
+ /** User-resized widths, by column id. */
185
630
  widths: Record<string, number>;
631
+ /** User-pinned columns, by column id. */
186
632
  pinned: Record<string, Pinned>;
187
633
  };
634
+ /** The sort model at the time of saving. */
188
635
  sort: SortModelItem[];
636
+ /** The filter model at the time of saving. */
189
637
  filters: FilterModelMap;
638
+ /** Paging preferences. */
190
639
  pagination: {
640
+ /** Rows per page the user chose. */
191
641
  pageSize: number;
192
642
  };
193
643
  }
644
+ /** Options for {@link GridApi.exportCsv}. */
194
645
  interface ExportCsvOptions {
646
+ /**
647
+ * Export only the selected rows rather than the whole loaded page.
648
+ * @defaultValue false
649
+ */
195
650
  onlySelected?: boolean;
651
+ /**
652
+ * File name, with or without the `.csv` suffix.
653
+ * @defaultValue the grid's `exportFileName` prop
654
+ */
196
655
  fileName?: string;
197
- /** Field separator. Defaults to `,`. */
656
+ /**
657
+ * Field separator.
658
+ * @defaultValue ','
659
+ */
198
660
  separator?: string;
199
661
  }
662
+ /**
663
+ * The imperative handle onto a live grid.
664
+ *
665
+ * Reach it either through {@link DataGridProps.apiRef} or as
666
+ * {@link DataGridProps.toolbar}'s argument.
667
+ *
668
+ * @typeParam T - The row type.
669
+ *
670
+ * @example
671
+ * ```tsx
672
+ * const apiRef = useRef<GridApi<Person>>(null);
673
+ *
674
+ * <DataGrid
675
+ * apiRef={apiRef}
676
+ * toolbar={(api) => (
677
+ * <button onClick={() => api.exportCsv({ onlySelected: true })}>
678
+ * Export selection
679
+ * </button>
680
+ * )}
681
+ * {...rest}
682
+ * />
683
+ * ```
684
+ */
200
685
  interface GridApi<T> {
201
- /** Re-fetch. `purge` drops every cached block first. */
686
+ /**
687
+ * Re-fetch the current page.
688
+ *
689
+ * @param options - `purge: true` drops every cached block first, so nothing
690
+ * stale can survive; otherwise cached neighbouring pages are kept.
691
+ */
202
692
  refresh(options?: {
203
693
  purge?: boolean;
204
694
  }): void;
695
+ /** The rows currently loaded for this page, in display order. */
205
696
  getDisplayedRows(): T[];
697
+ /** The selected rows that are on the current page. */
206
698
  getSelectedRows(): T[];
699
+ /** Ids of every selected row, including those on other pages. */
207
700
  getSelectedIds(): Array<string | number>;
701
+ /** Deselect everything. */
208
702
  clearSelection(): void;
703
+ /** Select every row on the current page. */
209
704
  selectAll(): void;
705
+ /** Download the rows as CSV, honouring each column's `exportValue`. */
210
706
  exportCsv(options?: ExportCsvOptions): void;
707
+ /** Copy the selection to the clipboard as TSV, which spreadsheets expect. */
211
708
  copySelectionToClipboard(): Promise<void>;
709
+ /** The active filters. */
212
710
  getFilterModel(): FilterModelMap;
711
+ /** Replace every filter at once, triggering one refetch. */
213
712
  setFilterModel(model: FilterModelMap): void;
713
+ /** The active sorts, primary first. */
214
714
  getSortModel(): SortModelItem[];
715
+ /** Replace the sort model, triggering one refetch. */
215
716
  setSortModel(model: SortModelItem[]): void;
717
+ /** Discard the user's column layout and return to the definitions' defaults. */
216
718
  resetColumns(): void;
217
- /** Patch rows already on screen without a round trip. */
719
+ /**
720
+ * Patch rows already on screen, matched by `getRowId`, without a round trip.
721
+ *
722
+ * Ids that are not on the current page are ignored, so a live feed can push
723
+ * everything it has without the client filtering first. Scroll position,
724
+ * selection, sort and any open editor are all preserved.
725
+ *
726
+ * @param rows - Whole replacement rows, not partials.
727
+ */
218
728
  updateRows(rows: T[]): void;
729
+ /**
730
+ * Open the row editor programmatically.
731
+ * @param rowId - The id, as returned by `getRowId`.
732
+ */
219
733
  startEditing(rowId: string | number): void;
734
+ /** Close the editor, abandoning any uncommitted draft. */
220
735
  stopEditing(): void;
221
736
  }
222
737
 
738
+ /**
739
+ * A row's stable identity, as returned by `getRowId`.
740
+ *
741
+ * Selection, editing and {@link GridApi.updateRows} all key off this.
742
+ */
223
743
  type RowId = string | number;
744
+ /**
745
+ * What {@link useSelectionModel} returns.
746
+ *
747
+ * @typeParam T - The row type.
748
+ */
224
749
  interface UseSelectionModelResult<T> {
750
+ /** Every selected id, including rows on pages that are not loaded. */
225
751
  selectedIds: Set<RowId>;
752
+ /** Whether one id is selected. */
226
753
  isSelected: (id: RowId) => boolean;
227
754
  /** True when every row currently on screen is selected. */
228
755
  allVisibleSelected: boolean;
756
+ /** True when some, but not all, visible rows are selected. */
229
757
  someVisibleSelected: boolean;
758
+ /**
759
+ * Toggle one row.
760
+ * @param id - The row to toggle.
761
+ * @param index - Its index in the loaded page, used as the shift anchor.
762
+ * @param shiftKey - Extend from the last toggled row instead of toggling one.
763
+ */
230
764
  toggleRow: (id: RowId, index: number, shiftKey: boolean) => void;
765
+ /** Select every visible row, or deselect them if all are already selected. */
231
766
  toggleAllVisible: () => void;
767
+ /** Deselect everything, including rows on other pages. */
232
768
  clear: () => void;
769
+ /** Select every row on the current page. */
233
770
  selectAllVisible: () => void;
771
+ /** The selected rows that are currently loaded. */
234
772
  getSelectedRows: () => T[];
235
773
  }
236
774
  /**
237
- * Selection lives here as a real React model rather than being read back out of
238
- * persisted grid state -- that indirection is what made the previous
239
- * implementation's "is anything selected?" check unreliable.
775
+ * Multi-row selection with shift-range support.
776
+ *
777
+ * Selection survives paging: ids stay selected even when their rows are not
778
+ * loaded, which is why `getSelectedRows` returns only the loaded subset while
779
+ * `selectedIds` holds everything.
780
+ *
781
+ * @typeParam T - The row type.
782
+ * @param rows - The currently loaded page.
783
+ * @param getRowId - Stable identity for a row.
784
+ * @param onSelectionChanged - Called with every selected id after each change.
785
+ * @returns The selection state and its mutators.
240
786
  */
241
787
  declare function useSelectionModel<T>(rows: T[], getRowId: (row: T) => RowId, onSelectionChanged?: (ids: RowId[]) => void): UseSelectionModelResult<T>;
242
788
 
789
+ /**
790
+ * Props for {@link DataGrid}.
791
+ *
792
+ * @typeParam T - The row type.
793
+ * @typeParam C - The {@link DataGridProps.context | context} type. Inferred
794
+ * from `context`; defaults to `unknown`.
795
+ */
243
796
  interface DataGridProps<T, C = unknown> {
797
+ /**
798
+ * The columns to render.
799
+ *
800
+ * @remarks
801
+ * Define these as a module constant or memoise on `[]` — anything volatile
802
+ * belongs in {@link DataGridProps.context} instead.
803
+ */
244
804
  columns: ColumnDef<T, C>[];
805
+ /**
806
+ * Where rows come from. Memoise it; a new identity causes a refetch.
807
+ *
808
+ * @see {@link HxDataSource}
809
+ */
245
810
  dataSource: HxDataSource<T>;
811
+ /**
812
+ * Stable identity for a row, used for selection, editing and
813
+ * {@link GridApi.updateRows}. Must be stable across refetches.
814
+ */
246
815
  getRowId: (row: T) => RowId;
247
- /** Volatile app state handed to every cell renderer. */
816
+ /**
817
+ * Volatile app state handed to every cell renderer.
818
+ *
819
+ * This is the escape hatch that keeps `columns` static: put in-flight ids,
820
+ * permission checks and event handlers here, and changing them re-renders
821
+ * cells without rebuilding a single column definition.
822
+ */
248
823
  context?: C;
249
- /** localStorage key for column/sort/filter/page-size preferences. */
824
+ /**
825
+ * `localStorage` key for column layout, sort, filters and page size.
826
+ *
827
+ * Omit it and nothing is persisted. Stored under `hxg:<storageKey>`.
828
+ *
829
+ * @see {@link PersistedGridState}
830
+ */
250
831
  storageKey?: string;
832
+ /**
833
+ * Row height in pixels. Fixed for every row — that is what keeps
834
+ * virtualization O(1).
835
+ * @defaultValue 36
836
+ */
251
837
  rowHeight?: number;
838
+ /**
839
+ * Header height in pixels.
840
+ * @defaultValue 36
841
+ */
252
842
  headerHeight?: number;
253
- /** Show the always-visible filter inputs under the header. */
843
+ /**
844
+ * Show the always-visible filter inputs under the header.
845
+ * @defaultValue true
846
+ */
254
847
  floatingFilter?: boolean;
848
+ /**
849
+ * Show the checkbox selection column.
850
+ * @defaultValue true
851
+ */
255
852
  selectable?: boolean;
853
+ /**
854
+ * Rows per page before the user changes it. A persisted preference wins.
855
+ * @defaultValue 20
856
+ */
256
857
  defaultPageSize?: number;
858
+ /**
859
+ * Page sizes offered in the pager.
860
+ * @defaultValue [10, 20, 50, 100]
861
+ */
257
862
  pageSizeOptions?: number[];
258
- /** Enables row editing. Return `{ ok:false, errors }` to keep the row open. */
863
+ /**
864
+ * Enables row editing. Called when the user commits a row.
865
+ *
866
+ * Return `{ ok: false, errors }` to keep the row open and paint each message
867
+ * onto the cell whose column id it is keyed by.
868
+ *
869
+ * @param draft - The edited copy.
870
+ * @param original - The row as it was before editing.
871
+ * @see {@link RowCommitResult}
872
+ */
259
873
  onRowCommit?: (draft: T, original: T) => Promise<RowCommitResult> | RowCommitResult;
874
+ /**
875
+ * Called whenever the selection changes.
876
+ * @param ids - Every selected row id, including rows on other pages.
877
+ */
260
878
  onSelectionChanged?: (ids: RowId[]) => void;
261
879
  /** Enables dropping files onto a row (e.g. to attach documents to it). */
262
880
  onRowFilesDropped?: (row: T, files: File[]) => void;
881
+ /**
882
+ * Called when a fetch or a commit throws. Aborted requests are not reported.
883
+ */
263
884
  onError?: (error: unknown) => void;
264
- /** Rendered above the header; receives the live api. */
885
+ /**
886
+ * Rendered into the toolbar strip above the header, left of the built-in
887
+ * Columns and Export CSV buttons.
888
+ *
889
+ * @param api - The live imperative API.
890
+ */
265
891
  toolbar?: (api: GridApi<T>) => ReactNode;
892
+ /**
893
+ * Shown when a query returns no rows.
894
+ * @defaultValue 'No rows'
895
+ */
266
896
  emptyMessage?: ReactNode;
897
+ /**
898
+ * Base file name for CSV export, without the extension.
899
+ * @defaultValue 'export'
900
+ */
267
901
  exportFileName?: string;
902
+ /** Extra classes on the grid's outer frame. */
268
903
  className?: string;
269
- /** Height of the scrolling area. Defaults to `70vh`. */
904
+ /**
905
+ * Height of the whole grid. Any CSS length; a number is treated as pixels.
906
+ * @defaultValue '70vh'
907
+ */
270
908
  height?: number | string;
909
+ /**
910
+ * Receives the imperative {@link GridApi}. Accepts a ref object or a
911
+ * callback ref.
912
+ */
271
913
  apiRef?: Ref<GridApi<T>>;
272
914
  }
915
+ /**
916
+ * A virtualized, server-driven data grid.
917
+ *
918
+ * Paging, sorting and filtering are all resolved by your
919
+ * {@link HxDataSource | data source} — the grid holds one page at a time and
920
+ * never filters or sorts locally. Rows are windowed at a fixed height, so the
921
+ * DOM stays small regardless of the result set.
922
+ *
923
+ * @typeParam T - The row type.
924
+ * @typeParam C - The context type, inferred from the `context` prop.
925
+ *
926
+ * @example Minimal usage
927
+ * ```tsx
928
+ * const columns: ColumnDef<Person>[] = [
929
+ * { field: 'name', header: 'Name', flex: 1, filter: 'text' },
930
+ * { field: 'email', header: 'Email', flex: 1 },
931
+ * ];
932
+ *
933
+ * function People() {
934
+ * const dataSource = useMemo(
935
+ * () => ({ getRows: (request, signal) => api.list(request, signal) }),
936
+ * []
937
+ * );
938
+ *
939
+ * return (
940
+ * <DataGrid
941
+ * columns={columns}
942
+ * dataSource={dataSource}
943
+ * getRowId={(row) => row.id}
944
+ * />
945
+ * );
946
+ * }
947
+ * ```
948
+ *
949
+ * @example With editing, persistence and the imperative API
950
+ * ```tsx
951
+ * const apiRef = useRef<GridApi<Person>>(null);
952
+ *
953
+ * <DataGrid
954
+ * columns={columns}
955
+ * dataSource={dataSource}
956
+ * getRowId={(row) => row.id}
957
+ * storageKey="people"
958
+ * apiRef={apiRef}
959
+ * onRowCommit={async (draft) => {
960
+ * const result = await api.save(draft);
961
+ * return result.ok ? { ok: true } : { ok: false, errors: result.errors };
962
+ * }}
963
+ * toolbar={(api) => (
964
+ * <button onClick={() => api.refresh({ purge: true })}>Refresh</button>
965
+ * )}
966
+ * />
967
+ * ```
968
+ *
969
+ * @remarks
970
+ * Styling is plain Tailwind utility classes — there is no stylesheet to import.
971
+ * Tailwind skips `node_modules` when detecting content, so point it at the
972
+ * shipped bundle explicitly:
973
+ *
974
+ * ```css
975
+ * @import "tailwindcss";
976
+ * @source "../node_modules/@helix-x/datagrid-ui/dist/index.js";
977
+ * ```
978
+ *
979
+ * @see {@link DataGridProps} for every option.
980
+ * @see {@link GridApi} for what `apiRef` and `toolbar` receive.
981
+ */
273
982
  declare function DataGrid<T, C = unknown>({ columns, dataSource, getRowId, context, storageKey, rowHeight, headerHeight, floatingFilter, selectable, defaultPageSize, pageSizeOptions, onRowCommit, onSelectionChanged, onRowFilesDropped, onError, toolbar, emptyMessage, exportFileName, className, height, apiRef, }: DataGridProps<T, C>): react.JSX.Element;
274
983
 
984
+ /** Props for {@link GridPagination}. */
275
985
  interface GridPaginationProps {
986
+ /** Zero-based current page. */
276
987
  page: number;
988
+ /** Rows per page. */
277
989
  pageSize: number;
990
+ /** Total across all pages. `-1` when the server reported it as unknown. */
278
991
  totalRows: number;
992
+ /** Page sizes to offer in the selector. */
279
993
  pageSizeOptions: number[];
994
+ /** Disables the controls while a fetch is in flight. */
280
995
  isLoading: boolean;
996
+ /** Called with the new zero-based page index. */
281
997
  onPageChange: (page: number) => void;
998
+ /** Called with the new page size. */
282
999
  onPageSizeChange: (pageSize: number) => void;
283
1000
  }
1001
+ /**
1002
+ * The grid's pager: range summary, page size selector and navigation.
1003
+ *
1004
+ * {@link DataGrid} renders this itself. It is exported for apps that build a
1005
+ * custom surface on the same hooks.
1006
+ */
284
1007
  declare function GridPagination({ page, pageSize, totalRows, pageSizeOptions, isLoading, onPageChange, onPageSizeChange, }: GridPaginationProps): react.JSX.Element;
285
1008
 
1009
+ /** Props for {@link GridOverlay}. */
286
1010
  interface GridOverlayProps {
1011
+ /** Which state to show. */
287
1012
  kind: 'loading' | 'empty' | 'error';
1013
+ /** Message body. Falls back to a default per `kind`. */
288
1014
  message?: ReactNode;
289
1015
  }
290
1016
  /**
291
- * Sits over the row area rather than replacing it, so the header and column
292
- * widths stay put while a refetch is in flight.
1017
+ * The loading, empty and error states shown over the row area.
1018
+ *
1019
+ * Overlays rather than replaces, so the header and column widths stay put while
1020
+ * a refetch is in flight.
1021
+ *
1022
+ * @param props - Which state to show, and the message.
293
1023
  */
294
1024
  declare function GridOverlay({ kind, message }: GridOverlayProps): react.JSX.Element;
295
1025
 
1026
+ /**
1027
+ * Props for {@link ColumnsPanel}.
1028
+ *
1029
+ * @typeParam T - The row type.
1030
+ * @typeParam C - The context type.
1031
+ */
296
1032
  interface ColumnsPanelProps<T, C> {
1033
+ /** Every column, including hidden ones. */
297
1034
  columns: ResolvedColumn<T, C>[];
1035
+ /** Whether a column is currently hidden. */
298
1036
  isHidden: (colId: string) => boolean;
1037
+ /** Show or hide one column. */
299
1038
  onToggle: (colId: string, hidden: boolean) => void;
1039
+ /** Move a column to a new index. */
300
1040
  onMove: (colId: string, toIndex: number) => void;
1041
+ /** Pin a column, or `undefined` to unpin. */
301
1042
  onPin: (colId: string, pinned: Pinned | undefined) => void;
1043
+ /** Discard the user's layout. */
302
1044
  onReset: () => void;
1045
+ /** Dismiss the panel. */
303
1046
  onClose: () => void;
304
1047
  }
305
- /** Show/hide, reorder and pin, plus the "reset my preferences" escape hatch. */
1048
+ /**
1049
+ * The side panel for showing, hiding, reordering and pinning columns, with the
1050
+ * "reset my preferences" escape hatch.
1051
+ *
1052
+ * {@link DataGrid} renders this from its toolbar; exported for custom surfaces.
1053
+ */
306
1054
  declare function ColumnsPanel<T, C>({ columns, isHidden, onToggle, onMove, onPin, onReset, onClose, }: ColumnsPanelProps<T, C>): react.JSX.Element;
307
1055
 
1056
+ /** Props for {@link FilterPopover}. */
308
1057
  interface FilterPopoverProps {
1058
+ /** Which filter UI to render. */
309
1059
  kind: FilterKind;
1060
+ /** The column's current filter, or `undefined` when unfiltered. */
310
1061
  value: HxFilterModel | undefined;
1062
+ /**
1063
+ * Options for a `set` filter: a static list, or a loader called the first
1064
+ * time the popover opens.
1065
+ */
311
1066
  setValues?: string[] | (() => Promise<string[]>);
1067
+ /** Called with the new filter, or `null` to clear the column's filter. */
312
1068
  onApply: (filter: HxFilterModel | null) => void;
1069
+ /** Dismiss the popover. */
313
1070
  onClose: () => void;
314
1071
  }
315
1072
  /** Header filter menu. Emits the wire-format model directly. */
1073
+ /**
1074
+ * The per-column filter editor, covering all four
1075
+ * {@link FilterKind | filter kinds}.
1076
+ *
1077
+ * {@link DataGrid} opens this from the header; exported for custom surfaces.
1078
+ */
316
1079
  declare function FilterPopover({ kind, value, setValues, onApply, onClose, }: FilterPopoverProps): react.JSX.Element;
317
1080
 
1081
+ /**
1082
+ * Single-line text input. The default editor when a column sets
1083
+ * `editable: true` without naming one.
1084
+ *
1085
+ * Reads `editorParams.placeholder`.
1086
+ */
318
1087
  declare function TextEditor<T, C>({ value, onChange, onCommit, onCancel, error, autoFocus, column, }: EditorParams<T, C>): react.JSX.Element;
1088
+ /**
1089
+ * Numeric input. Writes a `number`, or `null` when the field is cleared, so an
1090
+ * empty cell is never stored as `NaN` or `''`.
1091
+ *
1092
+ * Reads `editorParams.placeholder`.
1093
+ */
319
1094
  declare function NumberEditor<T, C>({ value, onChange, onCommit, onCancel, error, autoFocus, }: EditorParams<T, C>): react.JSX.Element;
1095
+ /**
1096
+ * Native date input. Reads and writes `YYYY-MM-DD` strings, matching
1097
+ * {@link DateFilterModel}.
1098
+ */
320
1099
  declare function DateEditor<T, C>({ value, onChange, onCommit, onCancel, error, autoFocus, }: EditorParams<T, C>): react.JSX.Element;
1100
+ /**
1101
+ * Dropdown over `editorParams.options`. Values round-trip by identity, so a
1102
+ * non-string option value is preserved rather than stringified.
1103
+ */
321
1104
  declare function SelectEditor<T, C>({ value, onChange, onCommit, onCancel, error, autoFocus, column, }: EditorParams<T, C>): react.JSX.Element;
1105
+ /** Boolean checkbox. Writes `true` or `false`, never `undefined`. */
322
1106
  declare function CheckboxEditor<T, C>({ value, onChange, onCommit, onCancel, autoFocus, }: EditorParams<T, C>): react.JSX.Element;
323
1107
 
324
- /** Maps a `ColumnDef.editor` string to its component. */
1108
+ /**
1109
+ * Maps a {@link BuiltinEditor} name to its component.
1110
+ *
1111
+ * Look one up to reuse it inside a custom editor, or to check which names are
1112
+ * available.
1113
+ *
1114
+ * @example
1115
+ * ```tsx
1116
+ * const Base = BUILTIN_EDITORS.text;
1117
+ * const UppercaseEditor: EditorComponent<Row> = (params) => (
1118
+ * <Base {...params} onChange={(v) => params.onChange(String(v).toUpperCase())} />
1119
+ * );
1120
+ * ```
1121
+ */
325
1122
  declare const BUILTIN_EDITORS: {
1123
+ /** Single-line text input. See {@link TextEditor}. */
326
1124
  readonly text: typeof TextEditor;
1125
+ /** Numeric input that writes `null` when cleared. See {@link NumberEditor}. */
327
1126
  readonly number: typeof NumberEditor;
1127
+ /** Native date input over `YYYY-MM-DD`. See {@link DateEditor}. */
328
1128
  readonly date: typeof DateEditor;
1129
+ /** Dropdown over `editorParams.options`. See {@link SelectEditor}. */
329
1130
  readonly select: typeof SelectEditor;
1131
+ /** Boolean checkbox. See {@link CheckboxEditor}. */
330
1132
  readonly checkbox: typeof CheckboxEditor;
331
1133
  };
332
1134
 
1135
+ /** What {@link useGridState} returns. */
333
1136
  interface UseGridStateResult {
334
- /** Read once on mount; never re-read, so it is safe as an initial value. */
1137
+ /**
1138
+ * The restored state, read once on mount and never re-read — so it is safe
1139
+ * to use as a `useState` initial value. `undefined` when nothing was saved
1140
+ * or the saved schema version did not match.
1141
+ */
335
1142
  initial: PersistedGridState | undefined;
1143
+ /** Persist the column layout. */
336
1144
  saveColumns: (columns: PersistedGridState['columns']) => void;
1145
+ /** Persist the sort model. */
337
1146
  saveSort: (sort: SortModelItem[]) => void;
1147
+ /** Persist the filter model. */
338
1148
  saveFilters: (filters: FilterModelMap) => void;
1149
+ /** Persist the page size. */
339
1150
  savePageSize: (pageSize: number) => void;
1151
+ /** Delete the saved state entirely. */
340
1152
  clear: () => void;
1153
+ /** Whether anything was restored on mount. */
341
1154
  hasSavedState: boolean;
342
1155
  }
343
- /** Versioned, lean localStorage persistence for one grid. */
1156
+ /**
1157
+ * Versioned `localStorage` persistence for one grid's layout.
1158
+ *
1159
+ * Writes go through a ref, so saving never causes a re-render. Every read and
1160
+ * write is wrapped: a quota error or a privacy mode degrades to not persisting
1161
+ * rather than throwing, and state saved under a different
1162
+ * {@link GRID_STATE_VERSION} is discarded rather than migrated.
1163
+ *
1164
+ * @param storageKey - Key to store under, namespaced as `hxg:<storageKey>`.
1165
+ * Pass `undefined` to disable persistence entirely.
1166
+ * @returns The restored state and its setters.
1167
+ */
344
1168
  declare function useGridState(storageKey: string | undefined): UseGridStateResult;
345
1169
 
1170
+ /**
1171
+ * Options for {@link useServerDataSource}.
1172
+ *
1173
+ * @typeParam T - The row type.
1174
+ */
346
1175
  interface UseServerDataSourceOptions<T> {
1176
+ /** Where rows come from. Memoise it, or every render refetches. */
347
1177
  dataSource: HxDataSource<T>;
1178
+ /** Rows per page. */
348
1179
  pageSize: number;
1180
+ /** Zero-based page index. */
349
1181
  page: number;
1182
+ /** Active sorts, primary first. */
350
1183
  sortModel: SortModelItem[];
1184
+ /** Active filters, keyed by column id. */
351
1185
  filterModel: FilterModelMap;
352
- /** Blocks to keep around so paging back and forth does not refetch. */
1186
+ /**
1187
+ * Blocks to keep around so paging back and forth does not refetch.
1188
+ * @defaultValue 3
1189
+ */
353
1190
  maxCachedBlocks?: number;
1191
+ /** Called when a fetch throws. Aborted requests are not reported. */
354
1192
  onError?: (error: unknown) => void;
355
1193
  }
1194
+ /**
1195
+ * What {@link useServerDataSource} returns.
1196
+ *
1197
+ * @typeParam T - The row type.
1198
+ */
356
1199
  interface UseServerDataSourceResult<T> {
1200
+ /** Rows for the current page. Empty while the first fetch is in flight. */
357
1201
  rows: T[];
1202
+ /** Total across all pages, from the response's `lastRow`. */
358
1203
  totalRows: number;
1204
+ /** True while a fetch is in flight. */
359
1205
  isLoading: boolean;
1206
+ /** The last error, or `null`. */
360
1207
  error: unknown;
1208
+ /**
1209
+ * Re-fetch the current page.
1210
+ * @param options - `purge: true` drops every cached block first.
1211
+ */
361
1212
  refresh: (options?: {
362
1213
  purge?: boolean;
363
1214
  }) => void;
364
- /** Patch loaded rows in place, keyed by the grid's row id. */
1215
+ /**
1216
+ * Patch loaded rows in place, matched by id. Ids that are not loaded are
1217
+ * ignored. No network request is made.
1218
+ */
365
1219
  patchRows: (rows: T[], getRowId: (row: T) => string | number) => void;
366
1220
  }
367
1221
  /**
368
1222
  * Server-side paging with a small block cache.
369
1223
  *
370
- * Two things here that the ag-grid screen this replaces did not do: every
371
- * request carries an AbortController and a sequence number, so a slow response
372
- * for an old sort/filter can never overwrite a newer one; and the cache is
373
- * dropped wholesale when the query changes, so stale blocks are never mixed
1224
+ * {@link DataGrid} uses this internally; call it directly only when building a
1225
+ * custom surface on top of the same data contract.
1226
+ *
1227
+ * @typeParam T - The row type.
1228
+ * @param options - Data source, page, sort and filters.
1229
+ * @returns The current page plus loading state and imperative helpers.
1230
+ *
1231
+ * @remarks
1232
+ * Every request carries an `AbortController` and a sequence number, so a slow
1233
+ * response for an old sort or filter can never overwrite a newer one. The cache
1234
+ * is dropped wholesale when the query changes, so stale blocks are never mixed
374
1235
  * with fresh ones.
1236
+ *
1237
+ * @example
1238
+ * ```ts
1239
+ * const { rows, totalRows, isLoading, refresh } = useServerDataSource({
1240
+ * dataSource,
1241
+ * page: 0,
1242
+ * pageSize: 50,
1243
+ * sortModel: [{ colId: 'name', sort: 'asc' }],
1244
+ * filterModel: {},
1245
+ * });
1246
+ * ```
375
1247
  */
376
1248
  declare function useServerDataSource<T>({ dataSource, pageSize, page, sortModel, filterModel, maxCachedBlocks, onError, }: UseServerDataSourceOptions<T>): UseServerDataSourceResult<T>;
377
1249
 
1250
+ /** The slice of rows currently worth rendering. */
378
1251
  interface VirtualWindow {
1252
+ /** First row index to render, inclusive. Includes overscan. */
379
1253
  startIndex: number;
1254
+ /** Index one past the last row to render, exclusive. */
380
1255
  endIndex: number;
381
1256
  }
1257
+ /** Options for {@link useVirtualRows}. */
382
1258
  interface UseVirtualRowsOptions {
1259
+ /** How many rows exist in the current page. */
383
1260
  rowCount: number;
1261
+ /** Fixed height of every row, in pixels. */
384
1262
  rowHeight: number;
1263
+ /**
1264
+ * Extra rows rendered above and below the viewport, so fast scrolling does
1265
+ * not show blank space.
1266
+ * @defaultValue 6
1267
+ */
385
1268
  overscan?: number;
386
1269
  }
1270
+ /** What {@link useVirtualRows} returns. */
387
1271
  interface UseVirtualRowsResult {
1272
+ /** The rows to render right now. */
388
1273
  window: VirtualWindow;
1274
+ /** `rowCount * rowHeight` — the spacer height that gives the correct scrollbar. */
389
1275
  totalHeight: number;
390
- /** Attach to the scrolling element. */
1276
+ /** Attach to the scrolling element's `onScroll`. */
391
1277
  onScroll: (event: {
392
1278
  currentTarget: HTMLElement;
393
1279
  }) => void;
394
1280
  /** Call when the viewport is measured or resized. */
395
1281
  setViewportHeight: (height: number) => void;
1282
+ /** Live scroll offset, readable without causing a re-render. */
396
1283
  scrollTopRef: React.RefObject<number>;
397
1284
  }
398
1285
  /**
399
1286
  * Fixed-height row windowing.
400
1287
  *
1288
+ * @param options - Row count, row height and overscan.
1289
+ * @returns The visible window plus the handlers that maintain it.
1290
+ *
1291
+ * @remarks
401
1292
  * Fixed heights are a deliberate constraint: they make the visible range O(1)
402
1293
  * to compute and remove the measure-then-reflow pass that variable heights
403
1294
  * force. Scroll position is tracked in a ref and only promoted to state when
404
- * the computed window actually changes, so scrolling within a row does not
405
- * re-render anything.
1295
+ * the computed window actually changes, so scrolling within a single row
1296
+ * re-renders nothing.
1297
+ *
1298
+ * @example
1299
+ * ```tsx
1300
+ * const { window, totalHeight, onScroll, setViewportHeight } = useVirtualRows({
1301
+ * rowCount: rows.length,
1302
+ * rowHeight: 36,
1303
+ * });
1304
+ *
1305
+ * <div onScroll={onScroll} style={{ overflow: 'auto' }}>
1306
+ * <div style={{ height: totalHeight }}>
1307
+ * {rows.slice(window.startIndex, window.endIndex).map(renderRow)}
1308
+ * </div>
1309
+ * </div>
1310
+ * ```
406
1311
  */
407
1312
  declare function useVirtualRows({ rowCount, rowHeight, overscan, }: UseVirtualRowsOptions): UseVirtualRowsResult;
408
1313
 
1314
+ /** The user's column layout, as persisted. */
409
1315
  interface ColumnStateValue {
1316
+ /** Column ids in display order. */
410
1317
  order: string[];
1318
+ /** Ids of hidden columns. */
411
1319
  hidden: string[];
1320
+ /** Widths in pixels, by column id. */
412
1321
  widths: Record<string, number>;
1322
+ /** Pinned edge, by column id. */
413
1323
  pinned: Record<string, Pinned>;
414
1324
  }
1325
+ /**
1326
+ * What {@link useColumnState} returns.
1327
+ *
1328
+ * @typeParam T - The row type.
1329
+ * @typeParam C - The context type.
1330
+ */
415
1331
  interface UseColumnStateResult<T, C> {
416
1332
  /** Every column, in user order, including hidden ones (for the columns panel). */
417
1333
  allColumns: ResolvedColumn<T, C>[];
1334
+ /** Just the visible columns, in display order. */
418
1335
  visibleColumns: ResolvedColumn<T, C>[];
1336
+ /** Measured geometry for the visible columns. */
419
1337
  layout: ColumnLayout<T, C>;
1338
+ /** The raw layout state, ready to persist. */
420
1339
  state: ColumnStateValue;
1340
+ /** Whether a column is hidden. */
421
1341
  isHidden: (colId: string) => boolean;
1342
+ /** Show or hide one column. */
422
1343
  setHidden: (colId: string, hidden: boolean) => void;
1344
+ /** Resize one column. Clamped to its `minWidth` and `maxWidth`. */
423
1345
  setWidth: (colId: string, width: number) => void;
1346
+ /** Pin one column to an edge, or pass `undefined` to unpin it. */
424
1347
  setPinned: (colId: string, pinned: Pinned | undefined) => void;
1348
+ /** Move a column to a new index in the display order. */
425
1349
  moveColumn: (colId: string, toIndex: number) => void;
1350
+ /** Discard the user's layout and return to the column definitions' defaults. */
426
1351
  reset: () => void;
427
1352
  }
1353
+ /**
1354
+ * Column order, visibility, width and pinning, merged over the definitions'
1355
+ * defaults.
1356
+ *
1357
+ * Persisted state is merged key by key rather than replacing the defaults
1358
+ * wholesale — so a layout saved before a new column was added does not hide
1359
+ * that column, and does not discard defaults it says nothing about.
1360
+ *
1361
+ * @typeParam T - The row type.
1362
+ * @typeParam C - The context type.
1363
+ * @returns The resolved columns, their layout, and the mutators.
1364
+ */
428
1365
  declare function useColumnState<T, C>(columns: ColumnDef<T, C>[], persisted: PersistedGridState['columns'] | undefined, onChange: (state: ColumnStateValue) => void, availableWidth: number): UseColumnStateResult<T, C>;
429
1366
 
1367
+ /**
1368
+ * The row currently being edited.
1369
+ *
1370
+ * @typeParam T - The row type.
1371
+ */
430
1372
  interface EditState<T> {
1373
+ /** Which row is open. */
431
1374
  rowId: RowId;
1375
+ /** The working copy, updated as the user types. */
432
1376
  draft: T;
1377
+ /** The row as it was when editing started, for cancel and for the commit callback. */
433
1378
  original: T;
1379
+ /** Server-supplied messages from the last rejected commit, keyed by column id. */
434
1380
  errors: Record<string, string>;
1381
+ /** True while a commit is in flight. */
435
1382
  isSaving: boolean;
436
1383
  }
1384
+ /**
1385
+ * What {@link useEditModel} returns.
1386
+ *
1387
+ * @typeParam T - The row type.
1388
+ */
437
1389
  interface UseEditModelResult<T> {
1390
+ /** The open row, or `null` when nothing is being edited. */
438
1391
  edit: EditState<T> | null;
1392
+ /** Whether a given row is the one currently open. */
439
1393
  isEditing: (rowId: RowId) => boolean;
1394
+ /** Open a row for editing, replacing any row already open. */
440
1395
  start: (rowId: RowId, row: T) => void;
1396
+ /**
1397
+ * Write one field into the draft. Accepts a dotted path, creating
1398
+ * intermediate objects as needed.
1399
+ */
441
1400
  setField: (field: string, value: unknown) => void;
1401
+ /** Close without saving, discarding the draft. */
442
1402
  cancel: () => void;
1403
+ /** Run the commit callback. Keeps the row open if it returns `ok: false`. */
443
1404
  commit: () => Promise<void>;
444
1405
  }
445
1406
  /**
446
1407
  * Row-level editing against a draft copy.
447
1408
  *
448
- * The draft never touches the data source, and the editors never call an API --
449
- * the single `onCommit` callback owns validation and persistence, so the app can
450
- * plug in whatever schema library it uses without the grid knowing about it.
1409
+ * @typeParam T - The row type.
1410
+ * @param onCommit - Owns validation and persistence. Return
1411
+ * `{ ok: false, errors }` to keep the row open with per-field messages.
1412
+ * @returns The edit state and its mutators.
1413
+ *
1414
+ * @remarks
1415
+ * The draft never touches the data source, and editors never call an API — the
1416
+ * single commit callback owns both, so an app can plug in whatever schema
1417
+ * library it uses without the grid knowing about it.
1418
+ *
1419
+ * @see {@link RowCommitResult}
451
1420
  */
452
1421
  declare function useEditModel<T>(onCommit: (draft: T, original: T) => Promise<RowCommitResult> | RowCommitResult): UseEditModelResult<T>;
453
1422
 
454
1423
  /**
455
- * Resolves a cell value.
1424
+ * Resolves a cell's raw value from its row.
1425
+ *
1426
+ * Tries, in order: {@link ColumnDef.valueGetter}, the dotted `field` path, the
1427
+ * literal flat key, then each {@link ColumnDef.fieldAliases | alias}.
1428
+ *
1429
+ * @remarks
1430
+ * The fallbacks exist because servers that flatten joined relations hand back
1431
+ * `{"customer.businessName": x}` rather than a nested object — and some change
1432
+ * the prefix when grouping is on.
456
1433
  *
457
- * Servers that flatten joined relations hand back `{"customer.businessName": x}`
458
- * rather than a nested object -- and some flatten with a different prefix when
459
- * grouping is on. So: explicit getter, then the dotted path, then the literal
460
- * flat key, then any declared aliases.
1434
+ * @param row - The row to read from.
1435
+ * @param column - The column describing what to read.
1436
+ * @returns The raw value, or `undefined` if nothing matched.
1437
+ *
1438
+ * @example
1439
+ * ```ts
1440
+ * resolveValue({ customer: { name: 'Acme' } }, { field: 'customer.name', header: 'C' });
1441
+ * // 'Acme'
1442
+ * ```
461
1443
  */
462
1444
  declare function resolveValue<T>(row: T, column: ColumnDef<T, never>): unknown;
463
- /** The display string for a cell, independent of any custom renderer. */
1445
+ /**
1446
+ * The display string for a cell, independent of any custom renderer.
1447
+ *
1448
+ * Uses {@link ColumnDef.valueFormatter} when present. Otherwise `null` and
1449
+ * `undefined` become `''`, `Date` becomes `YYYY-MM-DD`, booleans become
1450
+ * `Yes`/`No`, and everything else is stringified.
1451
+ *
1452
+ * @param value - The raw value, usually from {@link resolveValue}.
1453
+ * @param row - The row it came from, passed to the formatter.
1454
+ * @param column - The column being formatted.
1455
+ * @returns The display string.
1456
+ */
464
1457
  declare function formatValue<T>(value: unknown, row: T, column: ColumnDef<T, never>): string;
465
- /** The string written to CSV / clipboard for a cell. */
1458
+ /**
1459
+ * The string written to CSV and the clipboard for a cell.
1460
+ *
1461
+ * Prefers {@link ColumnDef.exportValue}, falling back to {@link formatValue} —
1462
+ * which is why a column with a markup `cellRenderer` should supply one.
1463
+ *
1464
+ * @param row - The row to export.
1465
+ * @param column - The column being exported.
1466
+ * @returns The flat string for this cell.
1467
+ */
466
1468
  declare function exportValue<T>(row: T, column: ColumnDef<T, never>): string;
467
- /** Plain-text header, for exports and aria labels. */
1469
+ /**
1470
+ * Plain-text header, for exports and aria labels.
1471
+ *
1472
+ * Prefers {@link ColumnDef.headerName}, then a string or numeric `header`, then
1473
+ * falls back to the column id.
1474
+ *
1475
+ * @param column - The column to label.
1476
+ * @returns The header as plain text, or `''` if nothing usable was found.
1477
+ */
468
1478
  declare function headerText<T>(column: ColumnDef<T, never>): string;
1479
+ /**
1480
+ * The stable id for a column: `colId`, or `field` when `colId` was omitted.
1481
+ *
1482
+ * @param column - The column to identify.
1483
+ * @returns The column id.
1484
+ * @throws If the column has neither `colId` nor `field`.
1485
+ */
469
1486
  declare function columnId<T, C>(column: ColumnDef<T, C>): string;
470
- /** Fills in the defaults every other module assumes are present. */
1487
+ /**
1488
+ * Fills in the defaults every other module assumes are present.
1489
+ *
1490
+ * Width defaults to 150 (never below `minWidth`), `minWidth` to 60, and both
1491
+ * `sortable` and `resizable` to `true`.
1492
+ *
1493
+ * @param column - The column as authored.
1494
+ * @returns The same column with required fields resolved.
1495
+ */
471
1496
  declare function resolveColumn<T, C>(column: ColumnDef<T, C>): ResolvedColumn<T, C>;
1497
+ /**
1498
+ * Whether a specific row's cell is editable in this column.
1499
+ *
1500
+ * @param column - The column to test.
1501
+ * @param row - The row to test, for a predicate `editable`.
1502
+ * @returns `true` when the cell can be edited.
1503
+ */
472
1504
  declare function isEditable<T, C>(column: ColumnDef<T, C>, row: T): boolean;
473
1505
 
474
1506
  /**
475
- * Every wire-format filter shape is built here, so the contract with the server
476
- * is defined in exactly one place.
1507
+ * Builders and helpers for the wire-format filter shapes.
1508
+ *
1509
+ * Every filter the grid sends is constructed here, so the contract with the
1510
+ * server is defined in exactly one place.
477
1511
  */
1512
+ /** Every text operator, in the order the filter UI lists them. */
478
1513
  declare const TEXT_FILTER_TYPES: TextFilterType[];
1514
+ /** Every numeric operator, in the order the filter UI lists them. */
479
1515
  declare const NUMBER_FILTER_TYPES: NumberFilterType[];
1516
+ /** Every date operator, in the order the filter UI lists them. */
480
1517
  declare const DATE_FILTER_TYPES: DateFilterType[];
1518
+ /**
1519
+ * Human-readable label for each operator, e.g. `notEqual` → `'Not equal'`.
1520
+ *
1521
+ * Replace an entry to relabel an operator throughout the filter UI.
1522
+ */
481
1523
  declare const FILTER_TYPE_LABELS: Record<string, string>;
482
- /** Filter types that need no operand. */
1524
+ /**
1525
+ * Whether an operator takes no operand — `blank` and `notBlank`.
1526
+ *
1527
+ * @param type - The operator name.
1528
+ * @returns `true` when no value is needed.
1529
+ */
483
1530
  declare function isUnaryFilter(type: string): boolean;
1531
+ /**
1532
+ * Whether an operator takes two operands — currently only `inRange`.
1533
+ *
1534
+ * @param type - The operator name.
1535
+ * @returns `true` when both bounds are needed.
1536
+ */
484
1537
  declare function isRangeFilter(type: string): boolean;
1538
+ /**
1539
+ * The operator a filter starts on when its popover is first opened.
1540
+ *
1541
+ * `contains` for text, `equals` for numbers and dates.
1542
+ *
1543
+ * @param kind - The filter kind.
1544
+ * @returns The default operator name.
1545
+ */
485
1546
  declare function defaultFilterType(kind: FilterKind): string;
1547
+ /**
1548
+ * Builds a text filter, or `null` when there is nothing to send.
1549
+ *
1550
+ * @param type - The operator.
1551
+ * @param filter - The search term. Ignored for unary operators.
1552
+ * @returns The filter model, or `null` if a non-unary operator got a blank term.
1553
+ *
1554
+ * @example
1555
+ * ```ts
1556
+ * buildTextFilter('contains', 'acme');
1557
+ * // { filterType: 'text', type: 'contains', filter: 'acme' }
1558
+ * buildTextFilter('contains', ' '); // null
1559
+ * ```
1560
+ */
486
1561
  declare function buildTextFilter(type: TextFilterType, filter: string): HxFilterModel | null;
1562
+ /**
1563
+ * Builds a numeric filter, or `null` when the input is incomplete.
1564
+ *
1565
+ * @param type - The operator.
1566
+ * @param filter - The operand, or lower bound for `inRange`, as typed.
1567
+ * @param filterTo - The upper bound. Required for `inRange`.
1568
+ * @returns The filter model, or `null` if a required operand is missing or not
1569
+ * a number.
1570
+ *
1571
+ * @example
1572
+ * ```ts
1573
+ * buildNumberFilter('inRange', '10', '100');
1574
+ * // { filterType: 'number', type: 'inRange', filter: 10, filterTo: 100 }
1575
+ * buildNumberFilter('inRange', '10'); // null — no upper bound
1576
+ * ```
1577
+ */
487
1578
  declare function buildNumberFilter(type: NumberFilterType, filter: string, filterTo?: string): HxFilterModel | null;
1579
+ /**
1580
+ * Builds a date filter, or `null` when the input is incomplete.
1581
+ *
1582
+ * @param type - The operator.
1583
+ * @param dateFrom - Lower bound, formatted `YYYY-MM-DD`.
1584
+ * @param dateTo - Upper bound. Required for `inRange`.
1585
+ * @returns The filter model, or `null` if a required bound is missing.
1586
+ */
488
1587
  declare function buildDateFilter(type: DateFilterType, dateFrom: string, dateTo?: string): HxFilterModel | null;
1588
+ /**
1589
+ * Builds a set filter, or `null` when nothing is selected.
1590
+ *
1591
+ * Returning `null` rather than an empty set is what clears the filter — see
1592
+ * {@link withFilter}.
1593
+ *
1594
+ * @param values - The selected values.
1595
+ * @returns The filter model, or `null` for an empty selection.
1596
+ */
489
1597
  declare function buildSetFilter(values: string[]): HxFilterModel | null;
490
- /** Sets or clears one column's entry, returning a new map. */
1598
+ /**
1599
+ * Sets or clears one column's filter, returning a new map.
1600
+ *
1601
+ * Never mutates the map it is given, so the result is safe to hand straight to
1602
+ * `setState`.
1603
+ *
1604
+ * @param model - The current filter model.
1605
+ * @param colId - The column to change.
1606
+ * @param filter - The new filter, or `null` to remove the column's entry.
1607
+ * @returns A new filter model.
1608
+ *
1609
+ * @example
1610
+ * ```ts
1611
+ * const next = withFilter(filterModel, 'status', buildSetFilter(['ACTIVE']));
1612
+ * const cleared = withFilter(filterModel, 'status', null);
1613
+ * ```
1614
+ */
491
1615
  declare function withFilter(model: FilterModelMap, colId: string, filter: HxFilterModel | null): FilterModelMap;
492
- /** A short label for the floating filter / header indicator. */
1616
+ /**
1617
+ * A short human-readable summary of a filter, for the floating filter input and
1618
+ * the header's active-filter indicator.
1619
+ *
1620
+ * @param filter - The filter to describe.
1621
+ * @returns A short label, e.g. `'Between 10 - 100'` or `'3 selected'`.
1622
+ */
493
1623
  declare function describeFilter(filter: HxFilterModel): string;
494
1624
 
1625
+ /**
1626
+ * Serialises rows to delimited text with a header line.
1627
+ *
1628
+ * Values are quoted per RFC 4180 when they contain the separator, a quote or a
1629
+ * newline. Columns marked {@link ColumnDef.suppressExport} are skipped, and
1630
+ * lines are joined with CRLF.
1631
+ *
1632
+ * @param rows - The rows to serialise.
1633
+ * @param columns - Columns to include, in output order.
1634
+ * @param separator - The field separator.
1635
+ * @returns The delimited document.
1636
+ */
495
1637
  declare function toDelimited<T>(rows: T[], columns: Array<ResolvedColumn<T, never> | ColumnDef<T, never>>, separator: string): string;
1638
+ /**
1639
+ * Serialises rows to CSV.
1640
+ *
1641
+ * @param rows - The rows to serialise.
1642
+ * @param columns - Columns to include, in output order.
1643
+ * @param separator - Field separator.
1644
+ * @returns The CSV document, without a BOM — {@link downloadCsv} adds one.
1645
+ *
1646
+ * @example
1647
+ * ```ts
1648
+ * const csv = toCsv(api.getSelectedRows(), columns);
1649
+ * ```
1650
+ */
496
1651
  declare function toCsv<T>(rows: T[], columns: Array<ResolvedColumn<T, never> | ColumnDef<T, never>>, separator?: string): string;
497
- /** Tab-separated, which is what spreadsheets expect from the clipboard. */
1652
+ /**
1653
+ * Serialises rows to tab-separated text, which is what spreadsheets expect to
1654
+ * receive from the clipboard.
1655
+ *
1656
+ * @param rows - The rows to serialise.
1657
+ * @param columns - Columns to include, in output order.
1658
+ * @returns The TSV document.
1659
+ */
498
1660
  declare function toTsv<T>(rows: T[], columns: Array<ResolvedColumn<T, never> | ColumnDef<T, never>>): string;
1661
+ /**
1662
+ * Triggers a browser download of CSV content.
1663
+ *
1664
+ * A UTF-8 BOM is prepended, which is what makes Excel read the file as UTF-8
1665
+ * rather than ANSI. The `.csv` extension is added if missing.
1666
+ *
1667
+ * @param content - The CSV document, e.g. from {@link toCsv}.
1668
+ * @param fileName - File name, with or without the extension.
1669
+ */
499
1670
  declare function downloadCsv(content: string, fileName: string): void;
1671
+ /**
1672
+ * Copies text to the clipboard, falling back to a hidden `textarea` on browsers
1673
+ * and non-secure origins without the async clipboard API.
1674
+ *
1675
+ * @param text - The text to copy.
1676
+ */
500
1677
  declare function copyToClipboard(text: string): Promise<void>;
501
1678
 
502
- export { BUILTIN_EDITORS, type BuiltinEditor, type CellRendererParams, CheckboxEditor, type ColumnDef, type ColumnLayout, type ColumnLayoutItem, ColumnsPanel, DATE_FILTER_TYPES, DataGrid, type DataGridProps, DateEditor, type DateFilterModel, type DateFilterType, type EditorComponent, type EditorParams, type ExportCsvOptions, FILTER_TYPE_LABELS, type FilterKind, type FilterModelMap, FilterPopover, GRID_STATE_VERSION, type GridApi, GridOverlay, GridPagination, type HxDataSource, type HxFilterModel, type HxRowsRequest, type HxRowsResponse, NUMBER_FILTER_TYPES, NumberEditor, type NumberFilterModel, type NumberFilterType, type PersistedGridState, type Pinned, type ResolvedColumn, type RowCommitResult, type RowId, SelectEditor, type SelectOption, type SetFilterModel, type SortDirection, type SortModelItem, TEXT_FILTER_TYPES, TextEditor, type TextFilterModel, type TextFilterType, buildDateFilter, buildNumberFilter, buildSetFilter, buildTextFilter, columnId, copyToClipboard, defaultFilterType, describeFilter, downloadCsv, exportValue, formatValue, headerText, isEditable, isRangeFilter, isUnaryFilter, resolveColumn, resolveValue, toCsv, toDelimited, toTsv, useColumnState, useEditModel, useGridState, useSelectionModel, useServerDataSource, useVirtualRows, withFilter };
1679
+ export { BUILTIN_EDITORS, type BuiltinEditor, type CellRendererParams, CheckboxEditor, type ColumnDef, type ColumnLayout, type ColumnLayoutItem, type ColumnStateValue, ColumnsPanel, type ColumnsPanelProps, DATE_FILTER_TYPES, DataGrid, type DataGridProps, DateEditor, type DateFilterModel, type DateFilterType, type EditState, type EditorComponent, type EditorParams, type ExportCsvOptions, FILTER_TYPE_LABELS, type FilterKind, type FilterModelMap, FilterPopover, type FilterPopoverProps, GRID_STATE_VERSION, type GridApi, GridOverlay, type GridOverlayProps, GridPagination, type GridPaginationProps, type HxDataSource, type HxFilterModel, type HxRowsRequest, type HxRowsResponse, NUMBER_FILTER_TYPES, NumberEditor, type NumberFilterModel, type NumberFilterType, type PersistedGridState, type Pinned, type ResolvedColumn, type RowCommitResult, type RowId, SelectEditor, type SelectOption, type SetFilterModel, type SortDirection, type SortModelItem, TEXT_FILTER_TYPES, TextEditor, type TextFilterModel, type TextFilterType, type UseColumnStateResult, type UseEditModelResult, type UseGridStateResult, type UseSelectionModelResult, type UseServerDataSourceOptions, type UseServerDataSourceResult, type UseVirtualRowsOptions, type UseVirtualRowsResult, type VirtualWindow, buildDateFilter, buildNumberFilter, buildSetFilter, buildTextFilter, columnId, copyToClipboard, defaultFilterType, describeFilter, downloadCsv, exportValue, formatValue, headerText, isEditable, isRangeFilter, isUnaryFilter, resolveColumn, resolveValue, toCsv, toDelimited, toTsv, useColumnState, useEditModel, useGridState, useSelectionModel, useServerDataSource, useVirtualRows, withFilter };