@svgrid/grid 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/FlexRender.svelte +96 -96
  2. package/dist/GridFooter.svelte +178 -178
  3. package/dist/SvGrid.css +2377 -2376
  4. package/dist/SvGrid.svelte +2764 -2764
  5. package/dist/SvGridBoard.svelte +2281 -2281
  6. package/dist/SvGridDropdown.svelte +666 -666
  7. package/dist/cdn/svgrid.js +3 -3
  8. package/dist/cdn/svgrid.svelte-external.js +3 -3
  9. package/dist/themes/index.d.ts +45 -0
  10. package/dist/themes/index.js +132 -0
  11. package/package.json +92 -85
  12. package/src/FlexRender.svelte +96 -96
  13. package/src/GridFooter.svelte +178 -178
  14. package/src/SvGrid.controller.svelte.ts +2553 -2553
  15. package/src/SvGrid.css +2377 -2376
  16. package/src/SvGrid.svelte +2764 -2764
  17. package/src/SvGrid.types.ts +944 -944
  18. package/src/SvGridBoard.svelte +2281 -2281
  19. package/src/SvGridDropdown.svelte +666 -666
  20. package/src/a11y.contract.test.ts +49 -49
  21. package/src/a11y.test.ts +59 -59
  22. package/src/a11y.ts +61 -61
  23. package/src/build-api.ts +798 -798
  24. package/src/cell-formatting.ts +169 -169
  25. package/src/cell-render.ts +469 -469
  26. package/src/collaboration.test.ts +104 -104
  27. package/src/collaboration.ts +167 -167
  28. package/src/core.performance.test.ts +30 -30
  29. package/src/core.ts +1111 -1111
  30. package/src/createGrid.svelte.ts +42 -42
  31. package/src/createGrid.test.ts +10 -10
  32. package/src/createGridState.svelte.ts +17 -17
  33. package/src/editing.test.ts +859 -859
  34. package/src/editing.ts +675 -675
  35. package/src/export-data-api.test.ts +126 -126
  36. package/src/export-format.test.ts +107 -107
  37. package/src/export-format.ts +598 -598
  38. package/src/flex-render.ts +3 -3
  39. package/src/index.ts +463 -463
  40. package/src/keyboard.test.ts +59 -59
  41. package/src/keyboard.ts +97 -97
  42. package/src/menus.ts +582 -582
  43. package/src/merge-objects.ts +48 -48
  44. package/src/render-component.ts +28 -28
  45. package/src/selection.test.ts +754 -754
  46. package/src/selection.ts +600 -600
  47. package/src/server-data-source.test.ts +289 -289
  48. package/src/server-data-source.ts +413 -413
  49. package/src/sparkline.test.ts +68 -68
  50. package/src/sparkline.ts +169 -169
  51. package/src/spreadsheet.test.ts +489 -489
  52. package/src/spreadsheet.ts +304 -304
  53. package/src/static-functions.ts +11 -11
  54. package/src/subscribe.ts +38 -38
  55. package/src/svgrid-wrapper.types.ts +439 -439
  56. package/src/svgrid.behavior.test.ts +706 -706
  57. package/src/svgrid.features.test.ts +157 -157
  58. package/src/svgrid.new-features.wrapper.test.ts +251 -251
  59. package/src/svgrid.wrapper.test.ts +40 -40
  60. package/src/themes/index.ts +159 -0
  61. package/src/virtualization/column-virtualizer.test.ts +27 -27
  62. package/src/virtualization/column-virtualizer.ts +30 -30
  63. package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
  64. package/src/virtualization/types.ts +30 -30
  65. package/src/virtualization/virtualizer.test.ts +47 -47
  66. package/src/virtualization/virtualizer.ts +296 -296
  67. package/themes/ag-alpine.css +59 -0
  68. package/themes/antd.css +59 -0
  69. package/themes/atlassian.css +59 -0
  70. package/themes/bootstrap.css +59 -0
  71. package/themes/carbon.css +59 -0
  72. package/themes/catppuccin.css +59 -0
  73. package/themes/dracula.css +59 -0
  74. package/themes/excel.css +59 -0
  75. package/themes/fluent.css +59 -0
  76. package/themes/github.css +59 -0
  77. package/themes/linear.css +59 -0
  78. package/themes/material.css +59 -0
  79. package/themes/nord.css +59 -0
  80. package/themes/notion.css +59 -0
  81. package/themes/salesforce.css +59 -0
  82. package/themes/sap.css +59 -0
  83. package/themes/shadcn.css +59 -0
  84. package/themes/tailwind.css +59 -0
  85. package/themes/vercel.css +59 -0
@@ -1,944 +1,944 @@
1
- // Type definitions extracted from SvGrid.svelte. These are compile-time
2
- // only - moving them out keeps the component's <script> focused on logic.
3
- import type { Snippet } from "svelte";
4
- import type {
5
- CellEditorType,
6
- ColumnDef,
7
- RowData,
8
- SvGridApi,
9
- TableFeatures,
10
- } from "./index";
11
- import type { ConditionalFormat } from "./conditional-formatting";
12
- import type { MenuItem } from "./SvMenuList.svelte";
13
-
14
- /**
15
- * The built-in card detail drawer. Set `board.drawer` (`true` for all fields,
16
- * or this object to customize) and opening a card shows a drawer with an
17
- * `SvForm` of its fields, rendered with the UI-kit editors.
18
- */
19
- export type BoardDrawerConfig<TData extends RowData = RowData> = {
20
- /** Fields to show, in order. Omit for every column that has a `field`. */
21
- fields?: ReadonlyArray<keyof TData & string>;
22
- /** Drawer title - a string or derived from the row. Defaults to the title field. */
23
- title?: string | ((row: TData) => string);
24
- /** Which edge the drawer opens from. Defaults to `'right'`. */
25
- side?: "right" | "left" | "top" | "bottom";
26
- /** Drawer size (any CSS length). Defaults to `'380px'`. */
27
- size?: string;
28
- /** Save button label. Defaults to `'Save'`. */
29
- submitLabel?: string;
30
- /** Form columns inside the drawer. Defaults to `1`. */
31
- columns?: number;
32
- };
33
-
34
- /** Board-native helpers passed to `board.cardMenu` for building context items. */
35
- export type BoardMenuContext<TData extends RowData = RowData> = {
36
- /** The board's lanes (id + title). */
37
- lanes: BoardLane[];
38
- /** Move this card to a lane (board-native; fires onCardMove). */
39
- moveTo: (laneId: string) => void;
40
- /** Open this card's editor (inline, or your `onCardEdit`). */
41
- edit: () => void;
42
- };
43
-
44
- /** Board-native helpers passed to `board.laneMenu` for building lane context items. */
45
- export type BoardLaneMenuContext<TData extends RowData = RowData> = {
46
- /** The lane's id. */
47
- laneId: string;
48
- /** The lane's current cards (this swimlane's, if any). */
49
- cards: TData[];
50
- /** Whether the lane is collapsed. */
51
- collapsed: boolean;
52
- /** Collapse / expand the lane (needs `collapsibleLanes`). */
53
- toggleCollapse: () => void;
54
- /** Add a card to the lane (fires `onCardAdd`). */
55
- addCard: (title?: string) => void;
56
- };
57
-
58
- /** The cell a context menu was opened on. Passed to every item's callbacks. */
59
- export type ContextMenuTarget<TData extends RowData = RowData> = {
60
- rowIndex: number;
61
- colIndex: number;
62
- columnId: string;
63
- /** Stable row id (from getRowId / row model), for keying notes etc. */
64
- rowId: string;
65
- row: TData | null;
66
- };
67
-
68
- /**
69
- * A context-menu entry. Either a built-in action key, the `"separator"`
70
- * divider, or a custom item. Built-in keys: `"copy" | "cut" | "paste" |
71
- * "clear" | "row_above" | "row_below" | "remove_row" | "remove_col"`.
72
- */
73
- export type ContextMenuItem<TData extends RowData = RowData> =
74
- | string
75
- | {
76
- key: string;
77
- label: string;
78
- /** Hide the item entirely for this target. */
79
- hidden?: (target: ContextMenuTarget<TData>) => boolean;
80
- /** Render the item greyed-out and non-clickable for this target. */
81
- disabled?: (target: ContextMenuTarget<TData>) => boolean;
82
- /** Invoked on click. The menu closes afterwards. */
83
- action: (target: ContextMenuTarget<TData>) => void;
84
- };
85
-
86
- /** A single Kanban lane (board column). {@link BoardConfig}. */
87
- export type BoardLane = {
88
- /** Lane id - equals the `groupBy` field value of the cards it holds. */
89
- id: string;
90
- /** Header title. Defaults to the id (or `(empty)` for a blank value). */
91
- title?: string;
92
- /** Accent color (any CSS color) for the lane header bar + card ring. */
93
- color?: string;
94
- /**
95
- * Soft max number of cards. When exceeded the lane header is flagged
96
- * over-limit. Enforcement/styling is an enterprise concern; the count is
97
- * always shown.
98
- */
99
- wipLimit?: number;
100
- };
101
-
102
- /** A checklist item inside a card. See {@link BoardConfig.subtasks}. */
103
- export type BoardSubtask = {
104
- id: string | number;
105
- title: string;
106
- done: boolean;
107
- };
108
-
109
- /** A comment on a card. See {@link BoardConfig.commentsField}. */
110
- export type BoardComment = {
111
- id: string | number;
112
- text: string;
113
- author?: string;
114
- /** Timestamp label (any string). */
115
- at?: string;
116
- };
117
-
118
- /**
119
- * The serializable board layout - the per-card position/edit overlay plus
120
- * collapsed lanes. Persisted by `board.persistKey` and passed to
121
- * `board.onLayoutChange`.
122
- */
123
- export type BoardLayout = {
124
- /** rowId -> lane id. */
125
- laneOf?: Record<string, string>;
126
- /** rowId -> sort order within its lane. */
127
- orderOf?: Record<string, number>;
128
- /** rowId -> swimlane id. */
129
- swimOf?: Record<string, string>;
130
- /** rowId -> edited field values. */
131
- edits?: Record<string, Record<string, unknown>>;
132
- /** lane id -> collapsed. */
133
- collapsed?: Record<string, boolean>;
134
- /** swimlane id -> collapsed. */
135
- collapsedSwim?: Record<string, boolean>;
136
- /** Explicit lane order (ids), when lanes have been drag-reordered. */
137
- laneOrder?: string[];
138
- /** rowId -> subtaskId -> done override. */
139
- subDone?: Record<string, Record<string, boolean>>;
140
- /** rowId -> subtasks added on the card. */
141
- subAdded?: Record<string, BoardSubtask[]>;
142
- };
143
-
144
- /** Emitted when a card's built-in editor is saved. */
145
- export type BoardCardCommitEvent<TData extends RowData = RowData> = {
146
- /** The edited row. */
147
- row: TData;
148
- /** Only the fields that changed, `{ field: newValue }`. */
149
- changes: Record<string, unknown>;
150
- /** The full set of edited field values. */
151
- values: Record<string, unknown>;
152
- };
153
-
154
- /** Emitted when a card is dragged to a new lane and/or position. */
155
- export type BoardCardMoveEvent<TData extends RowData = RowData> = {
156
- /** The dragged row. */
157
- row: TData;
158
- /** Lane the card came from. */
159
- fromLane: string;
160
- /** Lane the card was dropped on. */
161
- toLane: string;
162
- /** Insertion index within the destination lane. */
163
- toIndex: number;
164
- /** Swimlane the card came from (only when `swimlaneBy` is set). */
165
- fromSwimlane?: string;
166
- /** Swimlane the card was dropped on (only when `swimlaneBy` is set). */
167
- toSwimlane?: string;
168
- };
169
-
170
- /**
171
- * Turns the grid into a Kanban board. Set `board` and the grid renders its
172
- * rows as cards in horizontal lanes (bucketed by the `groupBy` field) instead
173
- * of a table. Dragging a card between lanes fires {@link BoardConfig.onCardMove}
174
- * where you reassign the `groupBy` field on your own data.
175
- */
176
- export type BoardConfig<TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData> = {
177
- /** Field whose value buckets each row into a lane. */
178
- groupBy: keyof TData & string;
179
- /**
180
- * Explicit, ordered lanes. Omit to derive lanes from the distinct
181
- * `groupBy` values found in the data (first-seen order).
182
- */
183
- lanes?: ReadonlyArray<BoardLane>;
184
- /** Custom card body. Receives the row. Omit for the built-in default card. */
185
- card?: Snippet<[TData]>;
186
- /**
187
- * Fired when a card is dragged to a new lane/position. Update your data
188
- * here (reassign the `groupBy` field, and optionally reorder). Without it
189
- * the board is display-only.
190
- */
191
- onCardMove?: (event: BoardCardMoveEvent<TData>) => void;
192
- /**
193
- * Add-card affordance. A `+` in the lane header (or, with `composer`, a
194
- * type-a-title box at the bottom of the lane) fires this with the lane id
195
- * and - from the composer - the typed title.
196
- */
197
- onCardAdd?: (laneId: string, title?: string) => void;
198
- /**
199
- * Show a quick-add composer at the bottom of each lane (a "+ Add a card"
200
- * button that opens an inline title input; Enter calls `onCardAdd(lane,
201
- * title)`). Without it, `onCardAdd` shows a plain `+` in the header.
202
- */
203
- composer?: boolean;
204
- /**
205
- * Field holding a card's checklist of sub-tasks (`BoardSubtask[]`). The card
206
- * shows a progress chip + bar and expands to an inline checklist with a
207
- * quick-add. Toggles/adds run through an overlay (never mutating your data)
208
- * and fire the callbacks below.
209
- */
210
- subtasks?: keyof TData & string;
211
- /** Fired when a sub-task checkbox is toggled. */
212
- onSubtaskToggle?: (row: TData, subtaskId: string | number, done: boolean) => void;
213
- /** Fired when a sub-task is added from the card. */
214
- onSubtaskAdd?: (row: TData, title: string) => void;
215
- /**
216
- * Field holding label/tag strings (or `{ text, color }`) shown as chips on
217
- * the default card's badge row.
218
- */
219
- labelsField?: keyof TData & string;
220
- /** Field holding a due date (Date | ISO string); shown as a badge, red when overdue. */
221
- dueField?: keyof TData & string;
222
- /**
223
- * Field holding one or more assignee names (string | string[]); shown as
224
- * avatar(s) on the default card's badge row.
225
- */
226
- assigneesField?: keyof TData & string;
227
- /**
228
- * Enable the built-in card editor: double-click a card (or press F2 while
229
- * it is focused) to edit its fields inline. Fields are rendered from the
230
- * columns that declare an `editorType` (or all fields if none do). Ignored
231
- * when `onCardEdit` is set.
232
- */
233
- editable?: boolean;
234
- /**
235
- * Open your own editor instead of the built-in one. Called on double-click /
236
- * F2 with the row; you might open a drawer or route to a detail screen.
237
- */
238
- onCardEdit?: (row: TData) => void;
239
- /**
240
- * Built-in **detail drawer**: opening a card (double-click / F2) shows an
241
- * `SvDrawer` with an `SvForm` of its fields (kit editors). `true` = all
242
- * fields; pass a {@link BoardDrawerConfig} to pick a subset / customize.
243
- * Ignored when `onCardEdit` is set.
244
- */
245
- drawer?: boolean | BoardDrawerConfig<TData>;
246
- /** Fired when the built-in editor is saved. Persist / mirror the changes. */
247
- onCardCommit?: (event: BoardCardCommitEvent<TData>) => void;
248
- /** Field used as the card title. Defaults to the first column's field. */
249
- titleField?: keyof TData & string;
250
- /**
251
- * Column fields shown as label:value meta lines on the default card
252
- * (max 4). Defaults to the first few non-title, non-groupBy columns.
253
- */
254
- cardFields?: ReadonlyArray<keyof TData & string>;
255
- /**
256
- * Second grouping axis: split the board into horizontal **swimlanes** by
257
- * this field. Each swimlane band shows the full lane set with only its own
258
- * cards. Dragging a card into another band reassigns this field too.
259
- */
260
- swimlaneBy?: keyof TData & string;
261
- /** Let users collapse a swimlane band via its header chevron. */
262
- collapsibleSwimlanes?: boolean;
263
- /**
264
- * Field holding a card's comments - `BoardComment[]` (or plain strings, or a
265
- * number for a count only). Renders a comment-count badge that expands to a
266
- * thread; provide the callbacks below to let users write / delete comments.
267
- */
268
- commentsField?: keyof TData & string;
269
- /** Fired when a comment is added on the card; append it to your data. */
270
- onCommentAdd?: (row: TData, text: string) => void;
271
- /** Fired when a comment's delete button is pressed; remove it from your data. */
272
- onCommentDelete?: (row: TData, commentId: string | number) => void;
273
- /**
274
- * Field holding a card's attachments - an array or a number. Renders an
275
- * attachment-count badge on the default card.
276
- */
277
- attachmentsField?: keyof TData & string;
278
- /**
279
- * Field holding a cover colour (any CSS colour string) shown as a strip
280
- * across the top of the default card.
281
- */
282
- coverField?: keyof TData & string;
283
- /**
284
- * Enforce `wipLimit`: reject a drop / keyboard move that would push a lane
285
- * past its limit (the card snaps back). Off by default (the limit is a
286
- * soft, visual flag only).
287
- */
288
- enforceWip?: boolean;
289
- /** Let users collapse a lane to a slim strip by clicking its header. */
290
- collapsibleLanes?: boolean;
291
- /** Let users drag lane headers to reorder lanes (persisted with `persistKey`). */
292
- reorderableLanes?: boolean;
293
- /** Fired after a lane drag-reorder, with the new ordered lane ids. */
294
- onLaneReorder?: (orderedIds: string[]) => void;
295
- /**
296
- * Enable inline lane rename: double-click a lane title to edit it. Fires
297
- * with the lane id and new title - update your own lane config / data.
298
- */
299
- onLaneRename?: (laneId: string, title: string) => void;
300
- /**
301
- * Per-swimlane summary shown in the band header (e.g. a points/$ total or a
302
- * WIP count). Receives the swimlane's cards and its id; return a short string.
303
- */
304
- swimlaneSummary?: (rows: TData[], swimId: string) => string;
305
- /**
306
- * Right-click (or long-press) a card to open a context menu. Return the
307
- * menu items for that card; `ctx` gives board-native `moveTo(laneId)` and
308
- * `edit()` helpers plus the lane list. Return empty/undefined for no menu.
309
- */
310
- cardMenu?: (row: TData, ctx: BoardMenuContext<TData>) => ReadonlyArray<MenuItem> | undefined;
311
- /** Right-click a lane header for a lane context menu (rename / clear / add / collapse). */
312
- laneMenu?: (laneId: string, ctx: BoardLaneMenuContext<TData>) => ReadonlyArray<MenuItem> | undefined;
313
- /**
314
- * Truthy field marks a card as **blocked** (waiting on a dependency): a red
315
- * corner + a "Blocked" badge on the card. If the value is a non-empty string
316
- * it is used as the blocked **reason** (shown in the badge tooltip). A blocked
317
- * card is **locked from moving** (drag + keyboard) until it is unblocked - set
318
- * {@link BoardConfig.flagBlocksMoves} `false` for a purely visual flag.
319
- */
320
- flagField?: keyof TData & string;
321
- /** Whether a blocked (`flagField`) card is locked from moving. Defaults to `true`. */
322
- flagBlocksMoves?: boolean;
323
- /**
324
- * Field holding a created/started date (Date | ISO string); shown as a
325
- * relative **age** badge (e.g. `3d`).
326
- */
327
- ageField?: keyof TData & string;
328
- /**
329
- * Fields to expose as a **facet filter bar** above the board - a chip per
330
- * distinct value; selecting chips filters the visible cards.
331
- */
332
- facets?: ReadonlyArray<keyof TData & string>;
333
- /**
334
- * Enable card **multi-select**: click to select, Ctrl/Cmd-click to toggle,
335
- * and dragging a selected card moves the whole selection. Fires
336
- * `onSelectionChange`.
337
- */
338
- selectable?: boolean;
339
- /** Fired when the selection changes, with the selected rows. */
340
- onSelectionChange?: (rows: TData[]) => void;
341
- /**
342
- * Field holding a card's **child rows** (`TData[]`) - the card gets a
343
- * children count and expands to show them as nested mini-cards (epic ->
344
- * stories). Each child renders its title and, if present, its `groupBy` value.
345
- */
346
- childrenField?: keyof TData & string;
347
- /**
348
- * Optional per-lane summary shown in the lane header (e.g. a story-point or
349
- * dollar total). Receives the lane's cards (the current swimlane's, when
350
- * `swimlaneBy` is set) and the lane id; return a short string.
351
- */
352
- laneSummary?: (rows: TData[], laneId: string) => string;
353
- /** Show the built-in card search box (default `true`). */
354
- searchable?: boolean;
355
- /** Placeholder for the search box. Defaults to `"Search cards..."`. */
356
- searchPlaceholder?: string;
357
- /**
358
- * Virtualize each lane so only the cards in view are in the DOM - for boards
359
- * with thousands of cards per lane. Assumes roughly uniform card height; set
360
- * `cardHeight` to match your cards.
361
- */
362
- virtualized?: boolean;
363
- /** Estimated card height in px used by `virtualized` windowing (default 76). */
364
- cardHeight?: number;
365
- /**
366
- * Persist the board layout (card positions, edits, collapsed lanes) to
367
- * `localStorage` under this key, restored on load. Requires `getRowId` for
368
- * stable identity across reloads.
369
- */
370
- persistKey?: string;
371
- /** Called whenever the board layout changes, with the serializable state. */
372
- onLayoutChange?: (layout: BoardLayout) => void;
373
- };
374
-
375
- export type Props<TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData> = {
376
- data: ReadonlyArray<TData>;
377
- columns: Array<ColumnDef<TFeatures, TData>>;
378
- /**
379
- * Kanban board mode. When set, the grid renders its rows as cards in
380
- * horizontal lanes (bucketed by `board.groupBy`) instead of a table. See
381
- * {@link BoardConfig}.
382
- */
383
- board?: BoardConfig<TFeatures, TData>;
384
- /**
385
- * Right-click context menu. `true` shows the default item set (copy, cut,
386
- * paste, clear, insert row above/below, remove row, remove column). Pass an
387
- * array to customize: strings are built-in keys, `"separator"` is a divider,
388
- * and objects are custom items. Omitted/`false` disables the menu (native
389
- * browser menu shows instead).
390
- */
391
- contextMenu?: boolean | ReadonlyArray<ContextMenuItem<TData>>;
392
- /**
393
- * The feature set built with `tableFeatures({ ... })`. Optional - the
394
- * `sortable` / `filterable` / `groupable` shortcuts below inject the
395
- * matching feature for you, so a grid can be configured entirely from
396
- * the boolean shortcuts without importing the feature constants.
397
- */
398
- features?: TFeatures;
399
- /**
400
- * Convenience shortcuts to switch a whole capability on without wiring
401
- * `features` or the finer-grained props by hand. Every capability is OFF
402
- * by default - a bare grid is a plain read-only table - so set the
403
- * shortcut `true` to opt in. (`false` / omitted both leave it off; the
404
- * shortcut is mainly there to turn things ON.)
405
- *
406
- * `sortable` - column sorting (injects `rowSortingFeature`)
407
- * `filterable` - column filtering (injects `columnFilteringFeature`)
408
- * `editable` - inline cell editing (alias of `enableInlineEditing`)
409
- * `groupable` - row grouping controls(alias of `showGroupingControls`,
410
- * also injects `columnGroupingFeature`)
411
- * `pageable` - pagination footer (alias of `showPagination`)
412
- *
413
- * Fine-grained props (`enableInlineEditing`, `showPagination`, ...) still
414
- * work; the shortcut wins only when it is explicitly set.
415
- */
416
- sortable?: boolean;
417
- filterable?: boolean;
418
- editable?: boolean;
419
- groupable?: boolean;
420
- pageable?: boolean;
421
- loading?: boolean;
422
- /**
423
- * Render `loading` as a non-blocking overlay instead of replacing the
424
- * whole grid: the current rows stay visible (dimmed, with a top progress
425
- * bar) during a refetch, and the first load shows shimmer skeleton rows.
426
- * Ideal for server-paged grids so paging/sorting doesn't flash. Defaults
427
- * to `false` (the classic full "Loading..." replacement).
428
- */
429
- loadingOverlay?: boolean;
430
- /** Skeleton placeholder rows to show on first load. Defaults to 8. */
431
- loadingSkeletonRows?: number;
432
- error?: string | null;
433
- emptyMessage?: string;
434
- showGlobalFilter?: boolean;
435
- showColumnFilters?: boolean;
436
- /**
437
- * Quick way to pick a single filtering UI. When set it controls which of
438
- * the three filter surfaces appears (and is overridden per-surface by the
439
- * `showGlobalFilter` / `showColumnFilters` / `showFilterRow` props).
440
- * Defaults to `'menu'` (only the column menu's filter section is shown).
441
- */
442
- filterMode?: "row" | "menu" | "global" | "none";
443
- showGroupingControls?: boolean;
444
- showRowSelection?: boolean;
445
- showPagination?: boolean;
446
- /** Initial page size when pagination is enabled. Defaults to 10. */
447
- pageSize?: number;
448
- /** Page-size choices shown in the footer's selector. Defaults to `[10, 25, 50, 100]`. */
449
- pageSizeOptions?: number[];
450
- /**
451
- * Where the pagination footer sits: `'bottom'` (default), `'top'`, or `'both'`.
452
- * The status bar (when enabled) always stays at the bottom.
453
- */
454
- paginationPosition?: 'top' | 'bottom' | 'both';
455
- /**
456
- * Server-side pagination. When `true`, the grid renders its native
457
- * pagination footer from the `rowCount` / `pageIndex` you provide (rather
458
- * than counting the local rows), does NOT slice `data` (the rows you pass are
459
- * treated as the current page), and emits `onPaginationChange` when the user
460
- * pages or changes page size. Pair with `externalSort` / `externalFilter`
461
- * and a server data source. Requires `showPagination` / `pageable` to show
462
- * the footer. Controlled: you own `pageIndex` + `pageSize` + `rowCount`.
463
- */
464
- externalPagination?: boolean;
465
- /** Total rows on the server, for the footer's range + page count. Used with `externalPagination`. */
466
- rowCount?: number;
467
- /** Current 0-based page index. Used with `externalPagination` (controlled). */
468
- pageIndex?: number;
469
- /**
470
- * Fires when the user changes page or page size while `externalPagination`
471
- * is on. Fetch that page and update `data` / `rowCount` / `pageIndex`.
472
- */
473
- onPaginationChange?: (pagination: { pageIndex: number; pageSize: number }) => void;
474
- virtualization?: boolean;
475
- /** Row height in pixels. Pass a function `(rowIndex) => px` for
476
- * per-row variable heights (e.g. an interactive row-resize feature).
477
- * Defaults to 30. */
478
- rowHeight?: number | ((rowIndex: number) => number);
479
- /**
480
- * Height (px) of a single column-header level row. With multi-level
481
- * (grouped) headers the total header height is `levels * headerHeight`,
482
- * since each level renders as its own row. When omitted, header rows size
483
- * to their content (the default). Does not affect the filter row.
484
- */
485
- headerHeight?: number;
486
- overscan?: number;
487
- /**
488
- * Height of the grid's scrollable shell. A number is treated as pixels;
489
- * a string is used as-is, so callers can pass `'100%'` or `'auto'` to
490
- * make the grid fill its parent. Defaults to 520 px.
491
- */
492
- containerHeight?: number | string;
493
- columnVirtualization?: boolean;
494
- columnOverscan?: number;
495
- columnWidth?: number;
496
- /**
497
- * Columns pinned to the left/right edge on mount. Each entry is a
498
- * column id (matches `ColumnDef.field` when no explicit id is set).
499
- * The internal pinning state is seeded once at mount; user-driven
500
- * pinning via the column menu still works and overrides this default.
501
- * Requires `columnVirtualization={false}` to be visible in the menu
502
- * UI (sticky positioning can't co-exist with column virtualization
503
- * since the virtualizer recycles DOM nodes).
504
- */
505
- initialColumnPinning?: {
506
- left?: ReadonlyArray<string>;
507
- right?: ReadonlyArray<string>;
508
- };
509
- /**
510
- * When `true`, columns are scaled proportionally so their total width
511
- * fills the viewport (no empty space on the right). Disabled by
512
- * default - explicit `width` values are used as-is. User resizes still
513
- * win once they happen.
514
- */
515
- fitColumns?: boolean;
516
- /**
517
- * Make the grid usable on narrow screens. When the grid's own width drops
518
- * below the breakpoint (default `640`px), pinned columns are un-pinned so the
519
- * whole grid pans, `fitColumns` scaling is suspended (columns keep their
520
- * natural widths and scroll), touch panning is smoothed, and columns whose
521
- * `hideBelow` exceeds the width are hidden. `true` uses the default
522
- * breakpoint; pass `{ breakpoint }` to change it. Off by default.
523
- */
524
- responsive?: boolean | { breakpoint?: number };
525
- showFilterMenu?: boolean;
526
- showFilterRow?: boolean;
527
- enableCellSelection?: boolean;
528
- /**
529
- * Highlight the row under the pointer. Default **false** - the hover tint can
530
- * compete with the cell selection / fill marquee. Set `true` to opt in; when
531
- * on, the hover no longer paints over selected cells so the selection stays
532
- * visible.
533
- */
534
- enableRowHover?: boolean;
535
- /**
536
- * Prepend a header row (the column labels) to copied cell ranges, so pasting
537
- * into Excel / Sheets includes the headers. Applies per copied range.
538
- */
539
- copyHeadersToClipboard?: boolean;
540
- /**
541
- * Transform each cell value on its way to the clipboard - e.g. strip
542
- * currency symbols, expand codes to labels, or redact. Receives the display
543
- * value plus the row/column context; return the string (or value) to copy.
544
- */
545
- processCellForClipboard?: (params: {
546
- value: unknown;
547
- column: unknown;
548
- row: TData;
549
- rowIndex: number;
550
- columnId: string;
551
- }) => unknown;
552
- enableInlineEditing?: boolean;
553
- /**
554
- * Full-row editing. When `true`, starting an edit puts the WHOLE row into
555
- * edit mode - every editable cell shows an inline editor at once - and a
556
- * single Enter (or focus leaving the row) commits all of them; Esc cancels
557
- * the whole row. Requires `enableInlineEditing`. The full-row editor covers
558
- * text / number / date / datetime / checkbox / list-select editor types.
559
- */
560
- fullRowEditing?: boolean;
561
- enableRowSummaries?: boolean;
562
- /**
563
- * Excel-style status bar under the grid showing live aggregates of the
564
- * selected cell range (count, numeric count, sum, average, min, max).
565
- * `true` shows the default set; pass `{ aggregates: [...] }` to choose
566
- * which. Requires `enableCellSelection`.
567
- */
568
- statusBar?:
569
- | boolean
570
- | {
571
- aggregates?: ReadonlyArray<
572
- "count" | "numericCount" | "sum" | "avg" | "min" | "max"
573
- >;
574
- };
575
- /**
576
- * Show the docked tool panel - the enterprise sidebar with Columns and
577
- * Filters tabs. A "Columns & Filters" button appears in a toolbar above the
578
- * grid; the panel docks on the right edge.
579
- */
580
- toolPanel?: boolean;
581
- /**
582
- * Render the header column menu (⋮) as a tabbed popover - **General**,
583
- * **Filter**, and **Columns** tabs (the AG-Grid layout). Defaults to `false`,
584
- * which keeps the flat menu (actions list + "Choose columns" submenu).
585
- */
586
- columnMenuTabs?: boolean;
587
- /** Open the tool panel on first render (instead of collapsed). */
588
- toolPanelDefaultOpen?: boolean;
589
- /** Which tab the tool panel starts on. Defaults to `'columns'`. */
590
- toolPanelDefaultTab?: "columns" | "filters";
591
- /**
592
- * Quick way to pick which selection surfaces are active. `'row'` shows the
593
- * selection checkbox column only, `'cell'` allows rectangle/range cell
594
- * selection only, `'both'` (default) enables both, `'none'` disables both.
595
- * Overridden per-surface by `showRowSelection` / `enableCellSelection`.
596
- */
597
- selectionMode?: "row" | "cell" | "both" | "none";
598
- /**
599
- * Render a leading row-number column (1-based) before any selection
600
- * column. Useful as a permanent anchor when scrolling wide grids.
601
- */
602
- showRowNumbers?: boolean;
603
- /**
604
- * Paint alternating data rows with the `--sg-row-alt-bg` color (zebra
605
- * striping). Only data rows stripe - pinned, group, detail, and summary
606
- * rows keep their single background. Defaults to `false`.
607
- */
608
- zebraRows?: boolean;
609
- /**
610
- * Width (px) of the row-number column. Defaults to 56, which fits up
611
- * to "99,999"; bump this when the dataset crosses six digits so the
612
- * largest row number stays fully visible at the bottom of a scroll.
613
- */
614
- rowNumberWidth?: number;
615
- /** Receives the imperative grid API once the component has mounted. */
616
- onApiReady?: (api: SvGridApi<TFeatures, TData>) => void;
617
- /**
618
- * Fires whenever the row-selection state changes. The first argument
619
- * is the new selection record `{ [rowId]: true }`; the second is the
620
- * array of selected `TData` rows.
621
- */
622
- onRowSelectionChange?: (
623
- selection: Record<string, boolean>,
624
- rows: TData[],
625
- ) => void;
626
- /**
627
- * Fires whenever the cell-selection rectangle changes (mouse, keyboard,
628
- * or `api.selectCells()`). `ranges` matches `api.getSelected()` -
629
- * `[rowStart, colStart, rowEnd, colEnd]` rectangles in grid coords.
630
- * Empty array when the user clears the selection.
631
- */
632
- onCellSelectionChange?: (
633
- ranges: Array<[number, number, number, number]>,
634
- ) => void;
635
- /**
636
- * When `true`, the grid records sort state (and renders sort indicators
637
- * + cycling on headers) but does NOT actually re-order the rows. The
638
- * consumer is expected to sort `data` themselves - typically via
639
- * `onSortingChange`. Use this for tree/hierarchical data, where a flat
640
- * global sort would break parent-child adjacency. Defaults to `false`.
641
- */
642
- externalSort?: boolean;
643
- /**
644
- * Fires whenever the sort clauses change. Receives the new array of
645
- * `{ id, desc }` entries. Pair with `externalSort={true}` when the
646
- * consumer wants to own the row ordering.
647
- */
648
- onSortingChange?: (sorting: Array<{ id: string; desc: boolean }>) => void;
649
- /**
650
- * When `true`, the grid still records column-filter / global-filter /
651
- * facet state (so the menu UI works and indicators light up) but does
652
- * NOT actually filter the rows. The consumer is expected to fetch / sort
653
- * / filter the data themselves - typically via `onFiltersChange`. Used
654
- * by server-side data sources. Defaults to `false`.
655
- */
656
- externalFilter?: boolean;
657
- /**
658
- * Fires whenever any of the in-grid filter state changes - global
659
- * search, per-column operator filters, or facet (value-checklist)
660
- * filters. Pair with `externalFilter={true}` when the consumer wants to
661
- * push the query to the server.
662
- */
663
- onFiltersChange?: (filters: {
664
- global: string;
665
- columns: Array<{
666
- id: string;
667
- operator: FilterOperator;
668
- value: string;
669
- /**
670
- * Upper bound for the `between` operator. Only set when
671
- * `operator === 'between'` AND the user has typed both values.
672
- */
673
- valueTo?: string;
674
- selectedValues?: Array<string>;
675
- }>;
676
- }) => void;
677
- /**
678
- * Fires when an inline edit is committed (Enter / Tab / blur). Useful
679
- * for cascade-recompute pipelines: a parent listens, then refreshes
680
- * derived columns or aggregates. The wrapper has already written the
681
- * parsed value back into the row by the time this fires.
682
- */
683
- /**
684
- * Resolve a stable id per row. Drives selection, expansion, edit,
685
- * and active-cell state. When omitted, the row's array index is
686
- * used - fine for read-only views but the wrong choice if `data`
687
- * gets reordered or filtered outside the grid (selection would
688
- * follow positions, not actual rows). Use a database PK, a UUID,
689
- * or any stable string.
690
- */
691
- getRowId?: (row: TData, index: number) => string;
692
- /**
693
- * Conditional class(es) added to every `<tr>` body row. Receives
694
- * the row's `original` data + its data-array index. Return a
695
- * string, an array of strings, or an object mapping class names to
696
- * booleans. Useful for "highlight overdue rows", "tint cancelled
697
- * orders", and similar row-level state mappings.
698
- */
699
- rowClass?: (ctx: {
700
- row: TData;
701
- rowIndex: number;
702
- }) => string | ReadonlyArray<string> | Record<string, boolean> | undefined | null;
703
- /**
704
- * Per-cell notes - longer free-form comments shown as a corner
705
- * indicator + tooltip on hover. Keyed by row id then column id;
706
- * empty / missing entries mean "no note". The grid owns rendering;
707
- * you own storage (write your own callbacks to add / edit notes).
708
- */
709
- notes?: Record<string, Record<string, string>>;
710
- /**
711
- * Allow editing per-cell notes/comments through the UI: the context menu
712
- * gains an "Edit comment" item that opens a popover editor. Edits are
713
- * applied to an internal overlay for immediate feedback and emitted via
714
- * `onNoteChange` so you can persist them back into `notes`.
715
- */
716
- editableComments?: boolean;
717
- /** Fires when a comment is saved or removed (removed = empty `note`). */
718
- onNoteChange?: (event: { rowId: string; columnId: string; note: string }) => void;
719
- /**
720
- * Excel-style conditional formatting. A list of value-driven rules that
721
- * color cells: `colorScale` (gradient across the column range),
722
- * `dataBar` (in-cell proportional bar), `iconSet` (arrows / traffic /
723
- * triangles by threshold), and `rule` (apply a style when a predicate
724
- * matches). Scope a format to specific columns with `columns: [...]`,
725
- * or omit it to apply to every column. Later entries win on conflict.
726
- */
727
- conditionalFormats?: ReadonlyArray<ConditionalFormat<TData>>;
728
- onCellValueChange?: (event: {
729
- rowIndex: number;
730
- columnId: string;
731
- oldValue: unknown;
732
- newValue: unknown;
733
- row: TData;
734
- }) => void;
735
- /**
736
- * Fires whenever the active cell changes - click, keyboard move,
737
- * tab, page-up/down, etc. Consumers (toolbars, ribbon UIs) use this
738
- * to stay synced with the grid's selection without polling the DOM.
739
- */
740
- onActiveCellChange?: (cell: {
741
- rowIndex: number;
742
- colIndex: number;
743
- columnId: string;
744
- }) => void;
745
- /**
746
- * Fires when a data cell (and therefore a row) is single-clicked.
747
- * Group-header rows are excluded. `value` is the displayed cell value.
748
- */
749
- onCellClick?: (event: {
750
- rowIndex: number;
751
- colIndex: number;
752
- columnId: string;
753
- value: unknown;
754
- row: TData;
755
- }) => void;
756
- /** Fires when a data row is single-clicked (any cell). Group rows excluded. */
757
- onRowClick?: (event: {
758
- rowIndex: number;
759
- columnId: string;
760
- row: TData;
761
- }) => void;
762
- /**
763
- * Fires when a data cell is double-clicked - independent of whether the
764
- * cell is editable, so it fires even on read-only grids. Group rows excluded.
765
- */
766
- onCellDoubleClick?: (event: {
767
- rowIndex: number;
768
- colIndex: number;
769
- columnId: string;
770
- value: unknown;
771
- row: TData;
772
- }) => void;
773
- /** Fires when a data row is double-clicked (any cell). Group rows excluded. */
774
- onRowDoubleClick?: (event: {
775
- rowIndex: number;
776
- columnId: string;
777
- row: TData;
778
- }) => void;
779
- /**
780
- * Fires once each time the body is scrolled to (within ~32px of) the
781
- * bottom. Re-arms after the user scrolls back up. The canonical hook for
782
- * infinite / lazy loading - append more rows to `data` when it fires.
783
- */
784
- onScrollBottomReached?: (event: {
785
- scrollTop: number;
786
- scrollHeight: number;
787
- clientHeight: number;
788
- }) => void;
789
- /**
790
- * Marks a row as an expandable "detail row". When this returns true the
791
- * grid renders that row as a SINGLE full-width cell (colspan across every
792
- * column) using `renderDetailRow`, instead of the normal per-column cells
793
- * - the canonical Stripe / GitHub "expand a rich panel beneath the row"
794
- * pattern. Insert the detail rows into `data` yourself (typically right
795
- * after the row they belong to) and toggle them with your own expanded
796
- * state. Pair with `virtualization={false}` so the variable-height detail
797
- * isn't clipped by the fixed-row-height virtualizer.
798
- */
799
- isDetailRow?: (row: TData, rowIndex: number) => boolean;
800
- /**
801
- * Snippet rendered inside the full-width detail cell for rows where
802
- * `isDetailRow` is true. Receives the row's data and its index.
803
- */
804
- renderDetailRow?: Snippet<[{ row: TData; rowIndex: number }]>;
805
- /**
806
- * Server-side group / tree keyboard + accessibility, built into the grid. When
807
- * set, the grid uses the treegrid role and marks matching rows with
808
- * `aria-level` / `aria-expanded`, and ArrowRight / ArrowLeft expand / collapse
809
- * the focused group row (no app-level key handling). Pair with `serverGroupRows`
810
- * + `SvGroupCell` for the visual expander. Every accessor receives the row data.
811
- */
812
- serverGroup?: {
813
- /** Whether a row is an expandable group / branch. */
814
- isGroup: (row: TData) => boolean;
815
- /** Tree depth of the row (0 = top level), for `aria-level`. */
816
- level: (row: TData) => number;
817
- /** Whether an expandable row is currently expanded. */
818
- expanded?: (row: TData) => boolean;
819
- /** Expand / collapse a group row. Called on ArrowRight / ArrowLeft. */
820
- onToggle: (row: TData) => void;
821
- };
822
- /**
823
- * Server-side set-filter values. When set, a column's filter checklist is
824
- * populated by fetching the distinct values from the server (once per column,
825
- * cached) instead of deriving them from the loaded page - so the checklist
826
- * shows every value, not just those on screen. Return the distinct string
827
- * values for the column id.
828
- */
829
- serverFilterValues?: (columnId: string) => Promise<string[]>;
830
- /**
831
- * Rows to pin to the TOP of the grid - rendered above the regular
832
- * rows and sticky-positioned so they stay visible while the user
833
- * scrolls. Typical use: a "totals" or "headline" row that should
834
- * always be in view. Rows are read-only (no inline editing, no
835
- * row-selection checkbox). They share the column schema with the
836
- * main grid; field/format/cell/cellClass all apply.
837
- */
838
- pinnedTopRows?: ReadonlyArray<TData>;
839
- /**
840
- * Rows to pin to the BOTTOM of the grid - rendered below the regular
841
- * rows and sticky-positioned (sticks to the bottom of the viewport
842
- * while the user scrolls). Typical use: a "page totals" or "grand
843
- * total" row computed from `getDisplayedRows()`.
844
- */
845
- pinnedBottomRows?: ReadonlyArray<TData>;
846
- /**
847
- * Enables drag-to-reorder on the grid's column headers. When `true`,
848
- * every header gets `draggable=true` and a drop indicator paints
849
- * between headers during a drag. On drop the grid mutates its
850
- * internal column order and fires `onColumnOrderChange` with the
851
- * new order. Defaults to `false`.
852
- */
853
- enableColumnReorder?: boolean;
854
- /**
855
- * Initial column order, by `id` (falls back to `field`). When the
856
- * user reorders columns, this is the starting state. After mount,
857
- * the grid owns the order internally and emits `onColumnOrderChange`
858
- * on every change - persist that to `localStorage` to restore.
859
- */
860
- columnOrder?: ReadonlyArray<string>;
861
- /** Fires every time the column order changes (drag or `api.setColumnOrder`). */
862
- onColumnOrderChange?: (order: ReadonlyArray<string>) => void;
863
- /**
864
- * Infer each column's data type (number / boolean / date / ISO date-string /
865
- * text) from the first data row, for columns that declare neither an
866
- * explicit `editorType` nor a `cellDataType`. Sets the matching editor,
867
- * alignment, date format, and filter operators automatically. Explicit
868
- * column config always wins. Defaults to `false`.
869
- */
870
- inferColumnTypes?: boolean;
871
- /**
872
- * Enables managed row dragging. When `true`, every row becomes a drag
873
- * source (grab cursor + a grip in the row-number cell) and a drop
874
- * indicator paints between rows during a drag. On drop the grid mutates
875
- * its own internal data - reordering within the grid, or moving the row
876
- * across grids that share the same {@link rowDragGroup}. Defaults to
877
- * `false`.
878
- */
879
- rowDragManaged?: boolean;
880
- /**
881
- * Connection group for cross-grid row dragging. Grids that share the same
882
- * non-empty `rowDragGroup` string (and have `rowDragManaged` on) can
883
- * exchange rows: dragging a row out of one and dropping it into another
884
- * removes it from the source and inserts it into the target. Omit to keep
885
- * dragging confined to reordering within a single grid.
886
- */
887
- rowDragGroup?: string;
888
- /**
889
- * Fires on the TARGET grid after a managed row drag settles, with the
890
- * moved row, its landing index, whether it stayed in the same grid, and
891
- * the source / target grid ids. Use it to mirror the move into your own
892
- * state (persistence, server sync). The grid has already applied the
893
- * change to its internal data by the time this fires.
894
- */
895
- onRowDragEnd?: (event: {
896
- row: TData;
897
- toIndex: number;
898
- sameGrid: boolean;
899
- fromGridId: number;
900
- toGridId: number;
901
- }) => void;
902
- /**
903
- * Align this grid with others that share the same non-empty
904
- * `alignedGridGroup` string: horizontal scroll and column-resize widths are
905
- * kept in lockstep across every grid in the group. Use for a totals/header
906
- * grid above a body grid, or side-by-side comparison grids that must line up.
907
- * The grids should declare the same columns (matched by id) for widths to map.
908
- */
909
- alignedGridGroup?: string;
910
- /**
911
- * BCP-47 locale tag (or array of fallbacks) used for accent- and
912
- * case-insensitive text filtering / sorting / search. Powered by
913
- * `Intl.Collator` with `sensitivity: 'base'`, so "cafe", "Café"
914
- * and "CAFÉ" all match "cafe" without strain. Defaults to the
915
- * browser's locale.
916
- */
917
- filterLocale?: string | ReadonlyArray<string>;
918
- };
919
-
920
- export type SelectionPoint = { rowIndex: number; colIndex: number };
921
- export type SelectionRange = {
922
- anchor: SelectionPoint | null;
923
- focus: SelectionPoint | null;
924
- };
925
- export type CellEditState = {
926
- rowId: string;
927
- columnId: string;
928
- editorType: CellEditorType;
929
- value: unknown;
930
- } | null;
931
- export type FilterOperator =
932
- | "contains"
933
- | "equals"
934
- | "startsWith"
935
- | "greaterThan"
936
- | "lessThan"
937
- | "between"
938
- | "isBlank";
939
- export type FilterOption = {
940
- value: FilterOperator;
941
- label: string;
942
- iconName: string;
943
- };
944
- export type MenuPosition = { x: number; y: number };
1
+ // Type definitions extracted from SvGrid.svelte. These are compile-time
2
+ // only - moving them out keeps the component's <script> focused on logic.
3
+ import type { Snippet } from "svelte";
4
+ import type {
5
+ CellEditorType,
6
+ ColumnDef,
7
+ RowData,
8
+ SvGridApi,
9
+ TableFeatures,
10
+ } from "./index";
11
+ import type { ConditionalFormat } from "./conditional-formatting";
12
+ import type { MenuItem } from "./SvMenuList.svelte";
13
+
14
+ /**
15
+ * The built-in card detail drawer. Set `board.drawer` (`true` for all fields,
16
+ * or this object to customize) and opening a card shows a drawer with an
17
+ * `SvForm` of its fields, rendered with the UI-kit editors.
18
+ */
19
+ export type BoardDrawerConfig<TData extends RowData = RowData> = {
20
+ /** Fields to show, in order. Omit for every column that has a `field`. */
21
+ fields?: ReadonlyArray<keyof TData & string>;
22
+ /** Drawer title - a string or derived from the row. Defaults to the title field. */
23
+ title?: string | ((row: TData) => string);
24
+ /** Which edge the drawer opens from. Defaults to `'right'`. */
25
+ side?: "right" | "left" | "top" | "bottom";
26
+ /** Drawer size (any CSS length). Defaults to `'380px'`. */
27
+ size?: string;
28
+ /** Save button label. Defaults to `'Save'`. */
29
+ submitLabel?: string;
30
+ /** Form columns inside the drawer. Defaults to `1`. */
31
+ columns?: number;
32
+ };
33
+
34
+ /** Board-native helpers passed to `board.cardMenu` for building context items. */
35
+ export type BoardMenuContext<TData extends RowData = RowData> = {
36
+ /** The board's lanes (id + title). */
37
+ lanes: BoardLane[];
38
+ /** Move this card to a lane (board-native; fires onCardMove). */
39
+ moveTo: (laneId: string) => void;
40
+ /** Open this card's editor (inline, or your `onCardEdit`). */
41
+ edit: () => void;
42
+ };
43
+
44
+ /** Board-native helpers passed to `board.laneMenu` for building lane context items. */
45
+ export type BoardLaneMenuContext<TData extends RowData = RowData> = {
46
+ /** The lane's id. */
47
+ laneId: string;
48
+ /** The lane's current cards (this swimlane's, if any). */
49
+ cards: TData[];
50
+ /** Whether the lane is collapsed. */
51
+ collapsed: boolean;
52
+ /** Collapse / expand the lane (needs `collapsibleLanes`). */
53
+ toggleCollapse: () => void;
54
+ /** Add a card to the lane (fires `onCardAdd`). */
55
+ addCard: (title?: string) => void;
56
+ };
57
+
58
+ /** The cell a context menu was opened on. Passed to every item's callbacks. */
59
+ export type ContextMenuTarget<TData extends RowData = RowData> = {
60
+ rowIndex: number;
61
+ colIndex: number;
62
+ columnId: string;
63
+ /** Stable row id (from getRowId / row model), for keying notes etc. */
64
+ rowId: string;
65
+ row: TData | null;
66
+ };
67
+
68
+ /**
69
+ * A context-menu entry. Either a built-in action key, the `"separator"`
70
+ * divider, or a custom item. Built-in keys: `"copy" | "cut" | "paste" |
71
+ * "clear" | "row_above" | "row_below" | "remove_row" | "remove_col"`.
72
+ */
73
+ export type ContextMenuItem<TData extends RowData = RowData> =
74
+ | string
75
+ | {
76
+ key: string;
77
+ label: string;
78
+ /** Hide the item entirely for this target. */
79
+ hidden?: (target: ContextMenuTarget<TData>) => boolean;
80
+ /** Render the item greyed-out and non-clickable for this target. */
81
+ disabled?: (target: ContextMenuTarget<TData>) => boolean;
82
+ /** Invoked on click. The menu closes afterwards. */
83
+ action: (target: ContextMenuTarget<TData>) => void;
84
+ };
85
+
86
+ /** A single Kanban lane (board column). {@link BoardConfig}. */
87
+ export type BoardLane = {
88
+ /** Lane id - equals the `groupBy` field value of the cards it holds. */
89
+ id: string;
90
+ /** Header title. Defaults to the id (or `(empty)` for a blank value). */
91
+ title?: string;
92
+ /** Accent color (any CSS color) for the lane header bar + card ring. */
93
+ color?: string;
94
+ /**
95
+ * Soft max number of cards. When exceeded the lane header is flagged
96
+ * over-limit. Enforcement/styling is an enterprise concern; the count is
97
+ * always shown.
98
+ */
99
+ wipLimit?: number;
100
+ };
101
+
102
+ /** A checklist item inside a card. See {@link BoardConfig.subtasks}. */
103
+ export type BoardSubtask = {
104
+ id: string | number;
105
+ title: string;
106
+ done: boolean;
107
+ };
108
+
109
+ /** A comment on a card. See {@link BoardConfig.commentsField}. */
110
+ export type BoardComment = {
111
+ id: string | number;
112
+ text: string;
113
+ author?: string;
114
+ /** Timestamp label (any string). */
115
+ at?: string;
116
+ };
117
+
118
+ /**
119
+ * The serializable board layout - the per-card position/edit overlay plus
120
+ * collapsed lanes. Persisted by `board.persistKey` and passed to
121
+ * `board.onLayoutChange`.
122
+ */
123
+ export type BoardLayout = {
124
+ /** rowId -> lane id. */
125
+ laneOf?: Record<string, string>;
126
+ /** rowId -> sort order within its lane. */
127
+ orderOf?: Record<string, number>;
128
+ /** rowId -> swimlane id. */
129
+ swimOf?: Record<string, string>;
130
+ /** rowId -> edited field values. */
131
+ edits?: Record<string, Record<string, unknown>>;
132
+ /** lane id -> collapsed. */
133
+ collapsed?: Record<string, boolean>;
134
+ /** swimlane id -> collapsed. */
135
+ collapsedSwim?: Record<string, boolean>;
136
+ /** Explicit lane order (ids), when lanes have been drag-reordered. */
137
+ laneOrder?: string[];
138
+ /** rowId -> subtaskId -> done override. */
139
+ subDone?: Record<string, Record<string, boolean>>;
140
+ /** rowId -> subtasks added on the card. */
141
+ subAdded?: Record<string, BoardSubtask[]>;
142
+ };
143
+
144
+ /** Emitted when a card's built-in editor is saved. */
145
+ export type BoardCardCommitEvent<TData extends RowData = RowData> = {
146
+ /** The edited row. */
147
+ row: TData;
148
+ /** Only the fields that changed, `{ field: newValue }`. */
149
+ changes: Record<string, unknown>;
150
+ /** The full set of edited field values. */
151
+ values: Record<string, unknown>;
152
+ };
153
+
154
+ /** Emitted when a card is dragged to a new lane and/or position. */
155
+ export type BoardCardMoveEvent<TData extends RowData = RowData> = {
156
+ /** The dragged row. */
157
+ row: TData;
158
+ /** Lane the card came from. */
159
+ fromLane: string;
160
+ /** Lane the card was dropped on. */
161
+ toLane: string;
162
+ /** Insertion index within the destination lane. */
163
+ toIndex: number;
164
+ /** Swimlane the card came from (only when `swimlaneBy` is set). */
165
+ fromSwimlane?: string;
166
+ /** Swimlane the card was dropped on (only when `swimlaneBy` is set). */
167
+ toSwimlane?: string;
168
+ };
169
+
170
+ /**
171
+ * Turns the grid into a Kanban board. Set `board` and the grid renders its
172
+ * rows as cards in horizontal lanes (bucketed by the `groupBy` field) instead
173
+ * of a table. Dragging a card between lanes fires {@link BoardConfig.onCardMove}
174
+ * where you reassign the `groupBy` field on your own data.
175
+ */
176
+ export type BoardConfig<TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData> = {
177
+ /** Field whose value buckets each row into a lane. */
178
+ groupBy: keyof TData & string;
179
+ /**
180
+ * Explicit, ordered lanes. Omit to derive lanes from the distinct
181
+ * `groupBy` values found in the data (first-seen order).
182
+ */
183
+ lanes?: ReadonlyArray<BoardLane>;
184
+ /** Custom card body. Receives the row. Omit for the built-in default card. */
185
+ card?: Snippet<[TData]>;
186
+ /**
187
+ * Fired when a card is dragged to a new lane/position. Update your data
188
+ * here (reassign the `groupBy` field, and optionally reorder). Without it
189
+ * the board is display-only.
190
+ */
191
+ onCardMove?: (event: BoardCardMoveEvent<TData>) => void;
192
+ /**
193
+ * Add-card affordance. A `+` in the lane header (or, with `composer`, a
194
+ * type-a-title box at the bottom of the lane) fires this with the lane id
195
+ * and - from the composer - the typed title.
196
+ */
197
+ onCardAdd?: (laneId: string, title?: string) => void;
198
+ /**
199
+ * Show a quick-add composer at the bottom of each lane (a "+ Add a card"
200
+ * button that opens an inline title input; Enter calls `onCardAdd(lane,
201
+ * title)`). Without it, `onCardAdd` shows a plain `+` in the header.
202
+ */
203
+ composer?: boolean;
204
+ /**
205
+ * Field holding a card's checklist of sub-tasks (`BoardSubtask[]`). The card
206
+ * shows a progress chip + bar and expands to an inline checklist with a
207
+ * quick-add. Toggles/adds run through an overlay (never mutating your data)
208
+ * and fire the callbacks below.
209
+ */
210
+ subtasks?: keyof TData & string;
211
+ /** Fired when a sub-task checkbox is toggled. */
212
+ onSubtaskToggle?: (row: TData, subtaskId: string | number, done: boolean) => void;
213
+ /** Fired when a sub-task is added from the card. */
214
+ onSubtaskAdd?: (row: TData, title: string) => void;
215
+ /**
216
+ * Field holding label/tag strings (or `{ text, color }`) shown as chips on
217
+ * the default card's badge row.
218
+ */
219
+ labelsField?: keyof TData & string;
220
+ /** Field holding a due date (Date | ISO string); shown as a badge, red when overdue. */
221
+ dueField?: keyof TData & string;
222
+ /**
223
+ * Field holding one or more assignee names (string | string[]); shown as
224
+ * avatar(s) on the default card's badge row.
225
+ */
226
+ assigneesField?: keyof TData & string;
227
+ /**
228
+ * Enable the built-in card editor: double-click a card (or press F2 while
229
+ * it is focused) to edit its fields inline. Fields are rendered from the
230
+ * columns that declare an `editorType` (or all fields if none do). Ignored
231
+ * when `onCardEdit` is set.
232
+ */
233
+ editable?: boolean;
234
+ /**
235
+ * Open your own editor instead of the built-in one. Called on double-click /
236
+ * F2 with the row; you might open a drawer or route to a detail screen.
237
+ */
238
+ onCardEdit?: (row: TData) => void;
239
+ /**
240
+ * Built-in **detail drawer**: opening a card (double-click / F2) shows an
241
+ * `SvDrawer` with an `SvForm` of its fields (kit editors). `true` = all
242
+ * fields; pass a {@link BoardDrawerConfig} to pick a subset / customize.
243
+ * Ignored when `onCardEdit` is set.
244
+ */
245
+ drawer?: boolean | BoardDrawerConfig<TData>;
246
+ /** Fired when the built-in editor is saved. Persist / mirror the changes. */
247
+ onCardCommit?: (event: BoardCardCommitEvent<TData>) => void;
248
+ /** Field used as the card title. Defaults to the first column's field. */
249
+ titleField?: keyof TData & string;
250
+ /**
251
+ * Column fields shown as label:value meta lines on the default card
252
+ * (max 4). Defaults to the first few non-title, non-groupBy columns.
253
+ */
254
+ cardFields?: ReadonlyArray<keyof TData & string>;
255
+ /**
256
+ * Second grouping axis: split the board into horizontal **swimlanes** by
257
+ * this field. Each swimlane band shows the full lane set with only its own
258
+ * cards. Dragging a card into another band reassigns this field too.
259
+ */
260
+ swimlaneBy?: keyof TData & string;
261
+ /** Let users collapse a swimlane band via its header chevron. */
262
+ collapsibleSwimlanes?: boolean;
263
+ /**
264
+ * Field holding a card's comments - `BoardComment[]` (or plain strings, or a
265
+ * number for a count only). Renders a comment-count badge that expands to a
266
+ * thread; provide the callbacks below to let users write / delete comments.
267
+ */
268
+ commentsField?: keyof TData & string;
269
+ /** Fired when a comment is added on the card; append it to your data. */
270
+ onCommentAdd?: (row: TData, text: string) => void;
271
+ /** Fired when a comment's delete button is pressed; remove it from your data. */
272
+ onCommentDelete?: (row: TData, commentId: string | number) => void;
273
+ /**
274
+ * Field holding a card's attachments - an array or a number. Renders an
275
+ * attachment-count badge on the default card.
276
+ */
277
+ attachmentsField?: keyof TData & string;
278
+ /**
279
+ * Field holding a cover colour (any CSS colour string) shown as a strip
280
+ * across the top of the default card.
281
+ */
282
+ coverField?: keyof TData & string;
283
+ /**
284
+ * Enforce `wipLimit`: reject a drop / keyboard move that would push a lane
285
+ * past its limit (the card snaps back). Off by default (the limit is a
286
+ * soft, visual flag only).
287
+ */
288
+ enforceWip?: boolean;
289
+ /** Let users collapse a lane to a slim strip by clicking its header. */
290
+ collapsibleLanes?: boolean;
291
+ /** Let users drag lane headers to reorder lanes (persisted with `persistKey`). */
292
+ reorderableLanes?: boolean;
293
+ /** Fired after a lane drag-reorder, with the new ordered lane ids. */
294
+ onLaneReorder?: (orderedIds: string[]) => void;
295
+ /**
296
+ * Enable inline lane rename: double-click a lane title to edit it. Fires
297
+ * with the lane id and new title - update your own lane config / data.
298
+ */
299
+ onLaneRename?: (laneId: string, title: string) => void;
300
+ /**
301
+ * Per-swimlane summary shown in the band header (e.g. a points/$ total or a
302
+ * WIP count). Receives the swimlane's cards and its id; return a short string.
303
+ */
304
+ swimlaneSummary?: (rows: TData[], swimId: string) => string;
305
+ /**
306
+ * Right-click (or long-press) a card to open a context menu. Return the
307
+ * menu items for that card; `ctx` gives board-native `moveTo(laneId)` and
308
+ * `edit()` helpers plus the lane list. Return empty/undefined for no menu.
309
+ */
310
+ cardMenu?: (row: TData, ctx: BoardMenuContext<TData>) => ReadonlyArray<MenuItem> | undefined;
311
+ /** Right-click a lane header for a lane context menu (rename / clear / add / collapse). */
312
+ laneMenu?: (laneId: string, ctx: BoardLaneMenuContext<TData>) => ReadonlyArray<MenuItem> | undefined;
313
+ /**
314
+ * Truthy field marks a card as **blocked** (waiting on a dependency): a red
315
+ * corner + a "Blocked" badge on the card. If the value is a non-empty string
316
+ * it is used as the blocked **reason** (shown in the badge tooltip). A blocked
317
+ * card is **locked from moving** (drag + keyboard) until it is unblocked - set
318
+ * {@link BoardConfig.flagBlocksMoves} `false` for a purely visual flag.
319
+ */
320
+ flagField?: keyof TData & string;
321
+ /** Whether a blocked (`flagField`) card is locked from moving. Defaults to `true`. */
322
+ flagBlocksMoves?: boolean;
323
+ /**
324
+ * Field holding a created/started date (Date | ISO string); shown as a
325
+ * relative **age** badge (e.g. `3d`).
326
+ */
327
+ ageField?: keyof TData & string;
328
+ /**
329
+ * Fields to expose as a **facet filter bar** above the board - a chip per
330
+ * distinct value; selecting chips filters the visible cards.
331
+ */
332
+ facets?: ReadonlyArray<keyof TData & string>;
333
+ /**
334
+ * Enable card **multi-select**: click to select, Ctrl/Cmd-click to toggle,
335
+ * and dragging a selected card moves the whole selection. Fires
336
+ * `onSelectionChange`.
337
+ */
338
+ selectable?: boolean;
339
+ /** Fired when the selection changes, with the selected rows. */
340
+ onSelectionChange?: (rows: TData[]) => void;
341
+ /**
342
+ * Field holding a card's **child rows** (`TData[]`) - the card gets a
343
+ * children count and expands to show them as nested mini-cards (epic ->
344
+ * stories). Each child renders its title and, if present, its `groupBy` value.
345
+ */
346
+ childrenField?: keyof TData & string;
347
+ /**
348
+ * Optional per-lane summary shown in the lane header (e.g. a story-point or
349
+ * dollar total). Receives the lane's cards (the current swimlane's, when
350
+ * `swimlaneBy` is set) and the lane id; return a short string.
351
+ */
352
+ laneSummary?: (rows: TData[], laneId: string) => string;
353
+ /** Show the built-in card search box (default `true`). */
354
+ searchable?: boolean;
355
+ /** Placeholder for the search box. Defaults to `"Search cards..."`. */
356
+ searchPlaceholder?: string;
357
+ /**
358
+ * Virtualize each lane so only the cards in view are in the DOM - for boards
359
+ * with thousands of cards per lane. Assumes roughly uniform card height; set
360
+ * `cardHeight` to match your cards.
361
+ */
362
+ virtualized?: boolean;
363
+ /** Estimated card height in px used by `virtualized` windowing (default 76). */
364
+ cardHeight?: number;
365
+ /**
366
+ * Persist the board layout (card positions, edits, collapsed lanes) to
367
+ * `localStorage` under this key, restored on load. Requires `getRowId` for
368
+ * stable identity across reloads.
369
+ */
370
+ persistKey?: string;
371
+ /** Called whenever the board layout changes, with the serializable state. */
372
+ onLayoutChange?: (layout: BoardLayout) => void;
373
+ };
374
+
375
+ export type Props<TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData> = {
376
+ data: ReadonlyArray<TData>;
377
+ columns: Array<ColumnDef<TFeatures, TData>>;
378
+ /**
379
+ * Kanban board mode. When set, the grid renders its rows as cards in
380
+ * horizontal lanes (bucketed by `board.groupBy`) instead of a table. See
381
+ * {@link BoardConfig}.
382
+ */
383
+ board?: BoardConfig<TFeatures, TData>;
384
+ /**
385
+ * Right-click context menu. `true` shows the default item set (copy, cut,
386
+ * paste, clear, insert row above/below, remove row, remove column). Pass an
387
+ * array to customize: strings are built-in keys, `"separator"` is a divider,
388
+ * and objects are custom items. Omitted/`false` disables the menu (native
389
+ * browser menu shows instead).
390
+ */
391
+ contextMenu?: boolean | ReadonlyArray<ContextMenuItem<TData>>;
392
+ /**
393
+ * The feature set built with `tableFeatures({ ... })`. Optional - the
394
+ * `sortable` / `filterable` / `groupable` shortcuts below inject the
395
+ * matching feature for you, so a grid can be configured entirely from
396
+ * the boolean shortcuts without importing the feature constants.
397
+ */
398
+ features?: TFeatures;
399
+ /**
400
+ * Convenience shortcuts to switch a whole capability on without wiring
401
+ * `features` or the finer-grained props by hand. Every capability is OFF
402
+ * by default - a bare grid is a plain read-only table - so set the
403
+ * shortcut `true` to opt in. (`false` / omitted both leave it off; the
404
+ * shortcut is mainly there to turn things ON.)
405
+ *
406
+ * `sortable` - column sorting (injects `rowSortingFeature`)
407
+ * `filterable` - column filtering (injects `columnFilteringFeature`)
408
+ * `editable` - inline cell editing (alias of `enableInlineEditing`)
409
+ * `groupable` - row grouping controls(alias of `showGroupingControls`,
410
+ * also injects `columnGroupingFeature`)
411
+ * `pageable` - pagination footer (alias of `showPagination`)
412
+ *
413
+ * Fine-grained props (`enableInlineEditing`, `showPagination`, ...) still
414
+ * work; the shortcut wins only when it is explicitly set.
415
+ */
416
+ sortable?: boolean;
417
+ filterable?: boolean;
418
+ editable?: boolean;
419
+ groupable?: boolean;
420
+ pageable?: boolean;
421
+ loading?: boolean;
422
+ /**
423
+ * Render `loading` as a non-blocking overlay instead of replacing the
424
+ * whole grid: the current rows stay visible (dimmed, with a top progress
425
+ * bar) during a refetch, and the first load shows shimmer skeleton rows.
426
+ * Ideal for server-paged grids so paging/sorting doesn't flash. Defaults
427
+ * to `false` (the classic full "Loading..." replacement).
428
+ */
429
+ loadingOverlay?: boolean;
430
+ /** Skeleton placeholder rows to show on first load. Defaults to 8. */
431
+ loadingSkeletonRows?: number;
432
+ error?: string | null;
433
+ emptyMessage?: string;
434
+ showGlobalFilter?: boolean;
435
+ showColumnFilters?: boolean;
436
+ /**
437
+ * Quick way to pick a single filtering UI. When set it controls which of
438
+ * the three filter surfaces appears (and is overridden per-surface by the
439
+ * `showGlobalFilter` / `showColumnFilters` / `showFilterRow` props).
440
+ * Defaults to `'menu'` (only the column menu's filter section is shown).
441
+ */
442
+ filterMode?: "row" | "menu" | "global" | "none";
443
+ showGroupingControls?: boolean;
444
+ showRowSelection?: boolean;
445
+ showPagination?: boolean;
446
+ /** Initial page size when pagination is enabled. Defaults to 10. */
447
+ pageSize?: number;
448
+ /** Page-size choices shown in the footer's selector. Defaults to `[10, 25, 50, 100]`. */
449
+ pageSizeOptions?: number[];
450
+ /**
451
+ * Where the pagination footer sits: `'bottom'` (default), `'top'`, or `'both'`.
452
+ * The status bar (when enabled) always stays at the bottom.
453
+ */
454
+ paginationPosition?: 'top' | 'bottom' | 'both';
455
+ /**
456
+ * Server-side pagination. When `true`, the grid renders its native
457
+ * pagination footer from the `rowCount` / `pageIndex` you provide (rather
458
+ * than counting the local rows), does NOT slice `data` (the rows you pass are
459
+ * treated as the current page), and emits `onPaginationChange` when the user
460
+ * pages or changes page size. Pair with `externalSort` / `externalFilter`
461
+ * and a server data source. Requires `showPagination` / `pageable` to show
462
+ * the footer. Controlled: you own `pageIndex` + `pageSize` + `rowCount`.
463
+ */
464
+ externalPagination?: boolean;
465
+ /** Total rows on the server, for the footer's range + page count. Used with `externalPagination`. */
466
+ rowCount?: number;
467
+ /** Current 0-based page index. Used with `externalPagination` (controlled). */
468
+ pageIndex?: number;
469
+ /**
470
+ * Fires when the user changes page or page size while `externalPagination`
471
+ * is on. Fetch that page and update `data` / `rowCount` / `pageIndex`.
472
+ */
473
+ onPaginationChange?: (pagination: { pageIndex: number; pageSize: number }) => void;
474
+ virtualization?: boolean;
475
+ /** Row height in pixels. Pass a function `(rowIndex) => px` for
476
+ * per-row variable heights (e.g. an interactive row-resize feature).
477
+ * Defaults to 30. */
478
+ rowHeight?: number | ((rowIndex: number) => number);
479
+ /**
480
+ * Height (px) of a single column-header level row. With multi-level
481
+ * (grouped) headers the total header height is `levels * headerHeight`,
482
+ * since each level renders as its own row. When omitted, header rows size
483
+ * to their content (the default). Does not affect the filter row.
484
+ */
485
+ headerHeight?: number;
486
+ overscan?: number;
487
+ /**
488
+ * Height of the grid's scrollable shell. A number is treated as pixels;
489
+ * a string is used as-is, so callers can pass `'100%'` or `'auto'` to
490
+ * make the grid fill its parent. Defaults to 520 px.
491
+ */
492
+ containerHeight?: number | string;
493
+ columnVirtualization?: boolean;
494
+ columnOverscan?: number;
495
+ columnWidth?: number;
496
+ /**
497
+ * Columns pinned to the left/right edge on mount. Each entry is a
498
+ * column id (matches `ColumnDef.field` when no explicit id is set).
499
+ * The internal pinning state is seeded once at mount; user-driven
500
+ * pinning via the column menu still works and overrides this default.
501
+ * Requires `columnVirtualization={false}` to be visible in the menu
502
+ * UI (sticky positioning can't co-exist with column virtualization
503
+ * since the virtualizer recycles DOM nodes).
504
+ */
505
+ initialColumnPinning?: {
506
+ left?: ReadonlyArray<string>;
507
+ right?: ReadonlyArray<string>;
508
+ };
509
+ /**
510
+ * When `true`, columns are scaled proportionally so their total width
511
+ * fills the viewport (no empty space on the right). Disabled by
512
+ * default - explicit `width` values are used as-is. User resizes still
513
+ * win once they happen.
514
+ */
515
+ fitColumns?: boolean;
516
+ /**
517
+ * Make the grid usable on narrow screens. When the grid's own width drops
518
+ * below the breakpoint (default `640`px), pinned columns are un-pinned so the
519
+ * whole grid pans, `fitColumns` scaling is suspended (columns keep their
520
+ * natural widths and scroll), touch panning is smoothed, and columns whose
521
+ * `hideBelow` exceeds the width are hidden. `true` uses the default
522
+ * breakpoint; pass `{ breakpoint }` to change it. Off by default.
523
+ */
524
+ responsive?: boolean | { breakpoint?: number };
525
+ showFilterMenu?: boolean;
526
+ showFilterRow?: boolean;
527
+ enableCellSelection?: boolean;
528
+ /**
529
+ * Highlight the row under the pointer. Default **false** - the hover tint can
530
+ * compete with the cell selection / fill marquee. Set `true` to opt in; when
531
+ * on, the hover no longer paints over selected cells so the selection stays
532
+ * visible.
533
+ */
534
+ enableRowHover?: boolean;
535
+ /**
536
+ * Prepend a header row (the column labels) to copied cell ranges, so pasting
537
+ * into Excel / Sheets includes the headers. Applies per copied range.
538
+ */
539
+ copyHeadersToClipboard?: boolean;
540
+ /**
541
+ * Transform each cell value on its way to the clipboard - e.g. strip
542
+ * currency symbols, expand codes to labels, or redact. Receives the display
543
+ * value plus the row/column context; return the string (or value) to copy.
544
+ */
545
+ processCellForClipboard?: (params: {
546
+ value: unknown;
547
+ column: unknown;
548
+ row: TData;
549
+ rowIndex: number;
550
+ columnId: string;
551
+ }) => unknown;
552
+ enableInlineEditing?: boolean;
553
+ /**
554
+ * Full-row editing. When `true`, starting an edit puts the WHOLE row into
555
+ * edit mode - every editable cell shows an inline editor at once - and a
556
+ * single Enter (or focus leaving the row) commits all of them; Esc cancels
557
+ * the whole row. Requires `enableInlineEditing`. The full-row editor covers
558
+ * text / number / date / datetime / checkbox / list-select editor types.
559
+ */
560
+ fullRowEditing?: boolean;
561
+ enableRowSummaries?: boolean;
562
+ /**
563
+ * Excel-style status bar under the grid showing live aggregates of the
564
+ * selected cell range (count, numeric count, sum, average, min, max).
565
+ * `true` shows the default set; pass `{ aggregates: [...] }` to choose
566
+ * which. Requires `enableCellSelection`.
567
+ */
568
+ statusBar?:
569
+ | boolean
570
+ | {
571
+ aggregates?: ReadonlyArray<
572
+ "count" | "numericCount" | "sum" | "avg" | "min" | "max"
573
+ >;
574
+ };
575
+ /**
576
+ * Show the docked tool panel - the enterprise sidebar with Columns and
577
+ * Filters tabs. A "Columns & Filters" button appears in a toolbar above the
578
+ * grid; the panel docks on the right edge.
579
+ */
580
+ toolPanel?: boolean;
581
+ /**
582
+ * Render the header column menu (⋮) as a tabbed popover - **General**,
583
+ * **Filter**, and **Columns** tabs (the AG-Grid layout). Defaults to `false`,
584
+ * which keeps the flat menu (actions list + "Choose columns" submenu).
585
+ */
586
+ columnMenuTabs?: boolean;
587
+ /** Open the tool panel on first render (instead of collapsed). */
588
+ toolPanelDefaultOpen?: boolean;
589
+ /** Which tab the tool panel starts on. Defaults to `'columns'`. */
590
+ toolPanelDefaultTab?: "columns" | "filters";
591
+ /**
592
+ * Quick way to pick which selection surfaces are active. `'row'` shows the
593
+ * selection checkbox column only, `'cell'` allows rectangle/range cell
594
+ * selection only, `'both'` (default) enables both, `'none'` disables both.
595
+ * Overridden per-surface by `showRowSelection` / `enableCellSelection`.
596
+ */
597
+ selectionMode?: "row" | "cell" | "both" | "none";
598
+ /**
599
+ * Render a leading row-number column (1-based) before any selection
600
+ * column. Useful as a permanent anchor when scrolling wide grids.
601
+ */
602
+ showRowNumbers?: boolean;
603
+ /**
604
+ * Paint alternating data rows with the `--sg-row-alt-bg` color (zebra
605
+ * striping). Only data rows stripe - pinned, group, detail, and summary
606
+ * rows keep their single background. Defaults to `false`.
607
+ */
608
+ zebraRows?: boolean;
609
+ /**
610
+ * Width (px) of the row-number column. Defaults to 56, which fits up
611
+ * to "99,999"; bump this when the dataset crosses six digits so the
612
+ * largest row number stays fully visible at the bottom of a scroll.
613
+ */
614
+ rowNumberWidth?: number;
615
+ /** Receives the imperative grid API once the component has mounted. */
616
+ onApiReady?: (api: SvGridApi<TFeatures, TData>) => void;
617
+ /**
618
+ * Fires whenever the row-selection state changes. The first argument
619
+ * is the new selection record `{ [rowId]: true }`; the second is the
620
+ * array of selected `TData` rows.
621
+ */
622
+ onRowSelectionChange?: (
623
+ selection: Record<string, boolean>,
624
+ rows: TData[],
625
+ ) => void;
626
+ /**
627
+ * Fires whenever the cell-selection rectangle changes (mouse, keyboard,
628
+ * or `api.selectCells()`). `ranges` matches `api.getSelected()` -
629
+ * `[rowStart, colStart, rowEnd, colEnd]` rectangles in grid coords.
630
+ * Empty array when the user clears the selection.
631
+ */
632
+ onCellSelectionChange?: (
633
+ ranges: Array<[number, number, number, number]>,
634
+ ) => void;
635
+ /**
636
+ * When `true`, the grid records sort state (and renders sort indicators
637
+ * + cycling on headers) but does NOT actually re-order the rows. The
638
+ * consumer is expected to sort `data` themselves - typically via
639
+ * `onSortingChange`. Use this for tree/hierarchical data, where a flat
640
+ * global sort would break parent-child adjacency. Defaults to `false`.
641
+ */
642
+ externalSort?: boolean;
643
+ /**
644
+ * Fires whenever the sort clauses change. Receives the new array of
645
+ * `{ id, desc }` entries. Pair with `externalSort={true}` when the
646
+ * consumer wants to own the row ordering.
647
+ */
648
+ onSortingChange?: (sorting: Array<{ id: string; desc: boolean }>) => void;
649
+ /**
650
+ * When `true`, the grid still records column-filter / global-filter /
651
+ * facet state (so the menu UI works and indicators light up) but does
652
+ * NOT actually filter the rows. The consumer is expected to fetch / sort
653
+ * / filter the data themselves - typically via `onFiltersChange`. Used
654
+ * by server-side data sources. Defaults to `false`.
655
+ */
656
+ externalFilter?: boolean;
657
+ /**
658
+ * Fires whenever any of the in-grid filter state changes - global
659
+ * search, per-column operator filters, or facet (value-checklist)
660
+ * filters. Pair with `externalFilter={true}` when the consumer wants to
661
+ * push the query to the server.
662
+ */
663
+ onFiltersChange?: (filters: {
664
+ global: string;
665
+ columns: Array<{
666
+ id: string;
667
+ operator: FilterOperator;
668
+ value: string;
669
+ /**
670
+ * Upper bound for the `between` operator. Only set when
671
+ * `operator === 'between'` AND the user has typed both values.
672
+ */
673
+ valueTo?: string;
674
+ selectedValues?: Array<string>;
675
+ }>;
676
+ }) => void;
677
+ /**
678
+ * Fires when an inline edit is committed (Enter / Tab / blur). Useful
679
+ * for cascade-recompute pipelines: a parent listens, then refreshes
680
+ * derived columns or aggregates. The wrapper has already written the
681
+ * parsed value back into the row by the time this fires.
682
+ */
683
+ /**
684
+ * Resolve a stable id per row. Drives selection, expansion, edit,
685
+ * and active-cell state. When omitted, the row's array index is
686
+ * used - fine for read-only views but the wrong choice if `data`
687
+ * gets reordered or filtered outside the grid (selection would
688
+ * follow positions, not actual rows). Use a database PK, a UUID,
689
+ * or any stable string.
690
+ */
691
+ getRowId?: (row: TData, index: number) => string;
692
+ /**
693
+ * Conditional class(es) added to every `<tr>` body row. Receives
694
+ * the row's `original` data + its data-array index. Return a
695
+ * string, an array of strings, or an object mapping class names to
696
+ * booleans. Useful for "highlight overdue rows", "tint cancelled
697
+ * orders", and similar row-level state mappings.
698
+ */
699
+ rowClass?: (ctx: {
700
+ row: TData;
701
+ rowIndex: number;
702
+ }) => string | ReadonlyArray<string> | Record<string, boolean> | undefined | null;
703
+ /**
704
+ * Per-cell notes - longer free-form comments shown as a corner
705
+ * indicator + tooltip on hover. Keyed by row id then column id;
706
+ * empty / missing entries mean "no note". The grid owns rendering;
707
+ * you own storage (write your own callbacks to add / edit notes).
708
+ */
709
+ notes?: Record<string, Record<string, string>>;
710
+ /**
711
+ * Allow editing per-cell notes/comments through the UI: the context menu
712
+ * gains an "Edit comment" item that opens a popover editor. Edits are
713
+ * applied to an internal overlay for immediate feedback and emitted via
714
+ * `onNoteChange` so you can persist them back into `notes`.
715
+ */
716
+ editableComments?: boolean;
717
+ /** Fires when a comment is saved or removed (removed = empty `note`). */
718
+ onNoteChange?: (event: { rowId: string; columnId: string; note: string }) => void;
719
+ /**
720
+ * Excel-style conditional formatting. A list of value-driven rules that
721
+ * color cells: `colorScale` (gradient across the column range),
722
+ * `dataBar` (in-cell proportional bar), `iconSet` (arrows / traffic /
723
+ * triangles by threshold), and `rule` (apply a style when a predicate
724
+ * matches). Scope a format to specific columns with `columns: [...]`,
725
+ * or omit it to apply to every column. Later entries win on conflict.
726
+ */
727
+ conditionalFormats?: ReadonlyArray<ConditionalFormat<TData>>;
728
+ onCellValueChange?: (event: {
729
+ rowIndex: number;
730
+ columnId: string;
731
+ oldValue: unknown;
732
+ newValue: unknown;
733
+ row: TData;
734
+ }) => void;
735
+ /**
736
+ * Fires whenever the active cell changes - click, keyboard move,
737
+ * tab, page-up/down, etc. Consumers (toolbars, ribbon UIs) use this
738
+ * to stay synced with the grid's selection without polling the DOM.
739
+ */
740
+ onActiveCellChange?: (cell: {
741
+ rowIndex: number;
742
+ colIndex: number;
743
+ columnId: string;
744
+ }) => void;
745
+ /**
746
+ * Fires when a data cell (and therefore a row) is single-clicked.
747
+ * Group-header rows are excluded. `value` is the displayed cell value.
748
+ */
749
+ onCellClick?: (event: {
750
+ rowIndex: number;
751
+ colIndex: number;
752
+ columnId: string;
753
+ value: unknown;
754
+ row: TData;
755
+ }) => void;
756
+ /** Fires when a data row is single-clicked (any cell). Group rows excluded. */
757
+ onRowClick?: (event: {
758
+ rowIndex: number;
759
+ columnId: string;
760
+ row: TData;
761
+ }) => void;
762
+ /**
763
+ * Fires when a data cell is double-clicked - independent of whether the
764
+ * cell is editable, so it fires even on read-only grids. Group rows excluded.
765
+ */
766
+ onCellDoubleClick?: (event: {
767
+ rowIndex: number;
768
+ colIndex: number;
769
+ columnId: string;
770
+ value: unknown;
771
+ row: TData;
772
+ }) => void;
773
+ /** Fires when a data row is double-clicked (any cell). Group rows excluded. */
774
+ onRowDoubleClick?: (event: {
775
+ rowIndex: number;
776
+ columnId: string;
777
+ row: TData;
778
+ }) => void;
779
+ /**
780
+ * Fires once each time the body is scrolled to (within ~32px of) the
781
+ * bottom. Re-arms after the user scrolls back up. The canonical hook for
782
+ * infinite / lazy loading - append more rows to `data` when it fires.
783
+ */
784
+ onScrollBottomReached?: (event: {
785
+ scrollTop: number;
786
+ scrollHeight: number;
787
+ clientHeight: number;
788
+ }) => void;
789
+ /**
790
+ * Marks a row as an expandable "detail row". When this returns true the
791
+ * grid renders that row as a SINGLE full-width cell (colspan across every
792
+ * column) using `renderDetailRow`, instead of the normal per-column cells
793
+ * - the canonical Stripe / GitHub "expand a rich panel beneath the row"
794
+ * pattern. Insert the detail rows into `data` yourself (typically right
795
+ * after the row they belong to) and toggle them with your own expanded
796
+ * state. Pair with `virtualization={false}` so the variable-height detail
797
+ * isn't clipped by the fixed-row-height virtualizer.
798
+ */
799
+ isDetailRow?: (row: TData, rowIndex: number) => boolean;
800
+ /**
801
+ * Snippet rendered inside the full-width detail cell for rows where
802
+ * `isDetailRow` is true. Receives the row's data and its index.
803
+ */
804
+ renderDetailRow?: Snippet<[{ row: TData; rowIndex: number }]>;
805
+ /**
806
+ * Server-side group / tree keyboard + accessibility, built into the grid. When
807
+ * set, the grid uses the treegrid role and marks matching rows with
808
+ * `aria-level` / `aria-expanded`, and ArrowRight / ArrowLeft expand / collapse
809
+ * the focused group row (no app-level key handling). Pair with `serverGroupRows`
810
+ * + `SvGroupCell` for the visual expander. Every accessor receives the row data.
811
+ */
812
+ serverGroup?: {
813
+ /** Whether a row is an expandable group / branch. */
814
+ isGroup: (row: TData) => boolean;
815
+ /** Tree depth of the row (0 = top level), for `aria-level`. */
816
+ level: (row: TData) => number;
817
+ /** Whether an expandable row is currently expanded. */
818
+ expanded?: (row: TData) => boolean;
819
+ /** Expand / collapse a group row. Called on ArrowRight / ArrowLeft. */
820
+ onToggle: (row: TData) => void;
821
+ };
822
+ /**
823
+ * Server-side set-filter values. When set, a column's filter checklist is
824
+ * populated by fetching the distinct values from the server (once per column,
825
+ * cached) instead of deriving them from the loaded page - so the checklist
826
+ * shows every value, not just those on screen. Return the distinct string
827
+ * values for the column id.
828
+ */
829
+ serverFilterValues?: (columnId: string) => Promise<string[]>;
830
+ /**
831
+ * Rows to pin to the TOP of the grid - rendered above the regular
832
+ * rows and sticky-positioned so they stay visible while the user
833
+ * scrolls. Typical use: a "totals" or "headline" row that should
834
+ * always be in view. Rows are read-only (no inline editing, no
835
+ * row-selection checkbox). They share the column schema with the
836
+ * main grid; field/format/cell/cellClass all apply.
837
+ */
838
+ pinnedTopRows?: ReadonlyArray<TData>;
839
+ /**
840
+ * Rows to pin to the BOTTOM of the grid - rendered below the regular
841
+ * rows and sticky-positioned (sticks to the bottom of the viewport
842
+ * while the user scrolls). Typical use: a "page totals" or "grand
843
+ * total" row computed from `getDisplayedRows()`.
844
+ */
845
+ pinnedBottomRows?: ReadonlyArray<TData>;
846
+ /**
847
+ * Enables drag-to-reorder on the grid's column headers. When `true`,
848
+ * every header gets `draggable=true` and a drop indicator paints
849
+ * between headers during a drag. On drop the grid mutates its
850
+ * internal column order and fires `onColumnOrderChange` with the
851
+ * new order. Defaults to `false`.
852
+ */
853
+ enableColumnReorder?: boolean;
854
+ /**
855
+ * Initial column order, by `id` (falls back to `field`). When the
856
+ * user reorders columns, this is the starting state. After mount,
857
+ * the grid owns the order internally and emits `onColumnOrderChange`
858
+ * on every change - persist that to `localStorage` to restore.
859
+ */
860
+ columnOrder?: ReadonlyArray<string>;
861
+ /** Fires every time the column order changes (drag or `api.setColumnOrder`). */
862
+ onColumnOrderChange?: (order: ReadonlyArray<string>) => void;
863
+ /**
864
+ * Infer each column's data type (number / boolean / date / ISO date-string /
865
+ * text) from the first data row, for columns that declare neither an
866
+ * explicit `editorType` nor a `cellDataType`. Sets the matching editor,
867
+ * alignment, date format, and filter operators automatically. Explicit
868
+ * column config always wins. Defaults to `false`.
869
+ */
870
+ inferColumnTypes?: boolean;
871
+ /**
872
+ * Enables managed row dragging. When `true`, every row becomes a drag
873
+ * source (grab cursor + a grip in the row-number cell) and a drop
874
+ * indicator paints between rows during a drag. On drop the grid mutates
875
+ * its own internal data - reordering within the grid, or moving the row
876
+ * across grids that share the same {@link rowDragGroup}. Defaults to
877
+ * `false`.
878
+ */
879
+ rowDragManaged?: boolean;
880
+ /**
881
+ * Connection group for cross-grid row dragging. Grids that share the same
882
+ * non-empty `rowDragGroup` string (and have `rowDragManaged` on) can
883
+ * exchange rows: dragging a row out of one and dropping it into another
884
+ * removes it from the source and inserts it into the target. Omit to keep
885
+ * dragging confined to reordering within a single grid.
886
+ */
887
+ rowDragGroup?: string;
888
+ /**
889
+ * Fires on the TARGET grid after a managed row drag settles, with the
890
+ * moved row, its landing index, whether it stayed in the same grid, and
891
+ * the source / target grid ids. Use it to mirror the move into your own
892
+ * state (persistence, server sync). The grid has already applied the
893
+ * change to its internal data by the time this fires.
894
+ */
895
+ onRowDragEnd?: (event: {
896
+ row: TData;
897
+ toIndex: number;
898
+ sameGrid: boolean;
899
+ fromGridId: number;
900
+ toGridId: number;
901
+ }) => void;
902
+ /**
903
+ * Align this grid with others that share the same non-empty
904
+ * `alignedGridGroup` string: horizontal scroll and column-resize widths are
905
+ * kept in lockstep across every grid in the group. Use for a totals/header
906
+ * grid above a body grid, or side-by-side comparison grids that must line up.
907
+ * The grids should declare the same columns (matched by id) for widths to map.
908
+ */
909
+ alignedGridGroup?: string;
910
+ /**
911
+ * BCP-47 locale tag (or array of fallbacks) used for accent- and
912
+ * case-insensitive text filtering / sorting / search. Powered by
913
+ * `Intl.Collator` with `sensitivity: 'base'`, so "cafe", "Café"
914
+ * and "CAFÉ" all match "cafe" without strain. Defaults to the
915
+ * browser's locale.
916
+ */
917
+ filterLocale?: string | ReadonlyArray<string>;
918
+ };
919
+
920
+ export type SelectionPoint = { rowIndex: number; colIndex: number };
921
+ export type SelectionRange = {
922
+ anchor: SelectionPoint | null;
923
+ focus: SelectionPoint | null;
924
+ };
925
+ export type CellEditState = {
926
+ rowId: string;
927
+ columnId: string;
928
+ editorType: CellEditorType;
929
+ value: unknown;
930
+ } | null;
931
+ export type FilterOperator =
932
+ | "contains"
933
+ | "equals"
934
+ | "startsWith"
935
+ | "greaterThan"
936
+ | "lessThan"
937
+ | "between"
938
+ | "isBlank";
939
+ export type FilterOption = {
940
+ value: FilterOperator;
941
+ label: string;
942
+ iconName: string;
943
+ };
944
+ export type MenuPosition = { x: number; y: number };