@ornery/ui-grid-vanilla 0.1.10 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/AGENTS.md +70 -0
  2. package/README.md +196 -0
  3. package/demo/main.ts +0 -1
  4. package/dist/index.d.mts +582 -55
  5. package/dist/index.d.ts +582 -55
  6. package/dist/index.js +4959 -1429
  7. package/dist/index.mjs +5137 -1560
  8. package/package.json +11 -8
  9. package/scss-plugin.ts +65 -0
  10. package/src/adopt-styles.ts +39 -0
  11. package/src/attribute-bridge.ts +291 -0
  12. package/src/auto-height.test.ts +142 -0
  13. package/src/auto-resize.test.ts +207 -0
  14. package/src/cell-nav.test.ts +262 -0
  15. package/src/components/grid-body-cell/grid-body-cell.ts +23 -0
  16. package/src/components/grid-body-cell/index.ts +1 -0
  17. package/src/components/grid-cell-editor/grid-cell-editor.ts +104 -0
  18. package/src/components/grid-cell-editor/index.ts +1 -0
  19. package/src/components/grid-filter-cell/grid-filter-cell.scss +14 -0
  20. package/src/components/grid-filter-cell/grid-filter-cell.template.ts +20 -0
  21. package/src/components/grid-filter-cell/grid-filter-cell.ts +121 -0
  22. package/src/components/grid-filter-cell/index.ts +1 -0
  23. package/src/components/grid-group-row/grid-group-row.scss +24 -0
  24. package/src/components/grid-group-row/grid-group-row.template.ts +23 -0
  25. package/src/components/grid-group-row/grid-group-row.ts +94 -0
  26. package/src/components/grid-group-row/index.ts +1 -0
  27. package/src/components/grid-header-cell/grid-header-cell.ts +17 -0
  28. package/src/components/grid-header-cell/index.ts +1 -0
  29. package/src/components/grid-pagination/grid-pagination.scss +39 -0
  30. package/src/components/grid-pagination/grid-pagination.template.ts +40 -0
  31. package/src/components/grid-pagination/grid-pagination.ts +204 -0
  32. package/src/components/grid-pagination/index.ts +1 -0
  33. package/src/components/grid-template/grid-template.ts +166 -0
  34. package/src/components/grid-template/index.ts +1 -0
  35. package/src/events.ts +824 -0
  36. package/src/exporter.test.ts +186 -0
  37. package/src/focus.ts +379 -0
  38. package/src/framework-slots.ts +440 -0
  39. package/src/grid-controller.ts +1532 -21
  40. package/src/icons.ts +88 -0
  41. package/src/importer.test.ts +147 -0
  42. package/src/index.integration.test.ts +2 -2
  43. package/src/index.ts +9 -0
  44. package/src/infinite-scroll.test.ts +220 -0
  45. package/src/keyboard-navigation.test.ts +579 -0
  46. package/src/patch.ts +722 -0
  47. package/src/render.ts +465 -0
  48. package/src/row-edit.test.ts +191 -0
  49. package/src/row-selection.test.ts +550 -0
  50. package/src/save-state.test.ts +235 -0
  51. package/src/scss.d.ts +4 -0
  52. package/src/templates/index.ts +62 -0
  53. package/src/test-setup.ts +23 -0
  54. package/src/ui-grid-empty.template.ts +23 -0
  55. package/src/ui-grid-shell.scss +25 -0
  56. package/src/ui-grid-shell.template.ts +45 -0
  57. package/src/ui-grid-standalone.element.ts +751 -1112
  58. package/src/utils/cell-class.ts +70 -0
  59. package/src/utils/display-items.ts +15 -0
  60. package/src/utils/dom.ts +64 -0
  61. package/src/validate.test.ts +170 -0
  62. package/tsup.config.ts +10 -0
  63. package/vite.config.ts +10 -1
package/dist/index.d.mts CHANGED
@@ -1,14 +1,217 @@
1
- import { GridOptions, GridLabels, GridColumnDef, PipelineResult, SortState, PinnedColumnState, GridCellPosition, UiGridApi, GridRecord, DisplayItem, SortDirection, PinDirection, GridRow, GroupItem, BuildGridPipelineContext } from '@ornery/ui-grid-core';
1
+ import { GridCellTemplateContext, GridLabels, GridExpandableTemplateContext, GridColumnDef, GridHeaderTemplateContext, GridRow, GroupItem, GridOptions, PipelineResult, SortState, PinnedColumnState, GridCellPosition, UiGridApi, GridRecord, DisplayItem, SortDirection, PinDirection, GridSelectionState, resolveGridSelectionOptions, GridInfiniteScrollState, GridValidatorRegistry, BuildGridPipelineContext } from '@ornery/ui-grid-core';
2
2
  export { GridOptions, UiGridApi } from '@ornery/ui-grid-core';
3
3
 
4
+ /**
5
+ * Framework-rendered slot bridge for the vanilla grid element.
6
+ *
7
+ * The vanilla grid renders every cell / header / filter / group / expandable
8
+ * / empty-state via string-interpolated templates by default. When a
9
+ * framework wrapper (Angular, React, or any other consumer) wants to own
10
+ * rendering of a slot with its own templating engine, it calls
11
+ * `setFrameworkRenderedSlots()` on the element to flip slot kinds into
12
+ * "slot placeholder" mode.
13
+ *
14
+ * In that mode the element emits `<slot name="…">` placeholders at the
15
+ * usual render sites. The wrapper listens for the `*SlotsChanged` event
16
+ * this module dispatches and inserts light-DOM nodes with matching `slot`
17
+ * attributes carrying its framework-rendered content. Slot projection then
18
+ * composes the wrapper's view back into the shadow tree without the
19
+ * wrapper ever touching the shadow DOM directly.
20
+ *
21
+ * Slot naming conventions:
22
+ * - `cell-<columnName>-<rowId>` per-cell (one slot per rendered body cell)
23
+ * - `header-<columnName>` per-column, singleton
24
+ * - `filter-<columnName>` per-column, singleton
25
+ * - `group-row-<groupId>` per-group
26
+ * - `expandable-row-<rowId>` per-row
27
+ * - `empty` singleton
28
+ */
29
+
30
+ /** Which slot kinds the element should render as `<slot>` placeholders. */
31
+ interface FrameworkRenderedSlotsConfig {
32
+ /** Column names whose body cells are framework-rendered. Everything else
33
+ * continues to use the vanilla interpolation path. */
34
+ cells?: readonly string[];
35
+ /** Column names whose header cells are framework-rendered. */
36
+ headers?: readonly string[];
37
+ /** Column names whose filter cells are framework-rendered. */
38
+ filters?: readonly string[];
39
+ /** When true, group rows are emitted as slot placeholders. */
40
+ groupRow?: boolean;
41
+ /** When true, expandable detail rows are emitted as slot placeholders. */
42
+ expandableRow?: boolean;
43
+ /** When true, the empty-state panel is emitted as a slot placeholder. */
44
+ emptyState?: boolean;
45
+ }
46
+ interface FrameworkCellSlot {
47
+ slotName: string;
48
+ columnName: string;
49
+ rowId: string;
50
+ rowIndex: number;
51
+ context: GridCellTemplateContext;
52
+ }
53
+ interface FrameworkHeaderSlot {
54
+ slotName: string;
55
+ columnName: string;
56
+ context: GridHeaderTemplateContext;
57
+ }
58
+ interface FrameworkFilterSlot {
59
+ slotName: string;
60
+ columnName: string;
61
+ value: string;
62
+ placeholder: string;
63
+ disabled: boolean;
64
+ column: GridColumnDef;
65
+ }
66
+ interface FrameworkGroupRowSlot {
67
+ slotName: string;
68
+ groupId: string;
69
+ field: string;
70
+ label: string;
71
+ count: number;
72
+ depth: number;
73
+ collapsed: boolean;
74
+ }
75
+ interface FrameworkExpandableRowSlot {
76
+ slotName: string;
77
+ rowId: string;
78
+ rowIndex: number;
79
+ context: GridExpandableTemplateContext;
80
+ }
81
+ interface FrameworkEmptyStateSlot {
82
+ slotName: 'empty';
83
+ heading: string;
84
+ description: string;
85
+ labels: GridLabels;
86
+ }
87
+ interface FrameworkSlotDelta<TSlot> {
88
+ added: TSlot[];
89
+ removed: TSlot[];
90
+ }
91
+ /**
92
+ * Bridge state + behavior. The element creates one instance, reads the
93
+ * `has*` methods during render to decide which path to take, calls the
94
+ * `stage*` methods as it emits slot placeholders, and calls `flush()` at
95
+ * the end of each render pass to emit the add/remove delta.
96
+ */
97
+ declare class FrameworkSlotBridge {
98
+ private readonly host;
99
+ private flaggedCells;
100
+ private flaggedHeaders;
101
+ private flaggedFilters;
102
+ private flaggedGroupRow;
103
+ private flaggedExpandableRow;
104
+ private flaggedEmptyState;
105
+ private lastCellSlots;
106
+ private lastExpandableRowSlots;
107
+ private lastGroupRowSlots;
108
+ private lastHeaderSlots;
109
+ private lastFilterSlots;
110
+ private lastEmptyStateSlot;
111
+ private pendingCellSlots;
112
+ private pendingExpandableRowSlots;
113
+ private pendingGroupRowSlots;
114
+ private pendingHeaderSlots;
115
+ private pendingFilterSlots;
116
+ private pendingEmptyStateSlot;
117
+ constructor(host: EventTarget);
118
+ /**
119
+ * Apply a slots config. Returns true when the structural set changed (and
120
+ * therefore a re-render is required), false when the config is a no-op.
121
+ */
122
+ configure(config: FrameworkRenderedSlotsConfig): boolean;
123
+ hasCell(columnName: string): boolean;
124
+ hasHeader(columnName: string): boolean;
125
+ hasFilter(columnName: string): boolean;
126
+ hasGroupRow(): boolean;
127
+ hasExpandableRow(): boolean;
128
+ hasEmptyState(): boolean;
129
+ stageCell(column: GridColumnDef, row: GridRow, rowIndex: number): void;
130
+ stageExpandableRow(row: GridRow, rowIndex: number): void;
131
+ stageGroupRow(group: GroupItem): void;
132
+ stageHeader(column: GridColumnDef): void;
133
+ stageFilter(column: GridColumnDef, value: string, placeholder: string, disabled: boolean): void;
134
+ stageEmptyState(heading: string, description: string, labels: GridLabels): void;
135
+ /**
136
+ * Diff pending vs. last, dispatch `*SlotsChanged` events for every kind
137
+ * that moved, then swap last = pending and reset pending. Called at the
138
+ * end of every render pass.
139
+ */
140
+ flush(): void;
141
+ /**
142
+ * Data-only patch variant: merge pending entries into last without firing
143
+ * events, since the slot structure hasn't changed (only cell contexts
144
+ * refreshed). Wrappers that need context updates subscribe to
145
+ * `rowsVisibleChanged`.
146
+ */
147
+ mergePendingIntoLast(): void;
148
+ /**
149
+ * On element teardown, emit `removed` for every remaining slot so wrappers
150
+ * can destroy their views.
151
+ */
152
+ flushRemovals(): void;
153
+ private diffAndEmit;
154
+ private diffAndEmitSingleton;
155
+ }
156
+
157
+ /**
158
+ * Icon registry for the vanilla grid element.
159
+ *
160
+ * The grid draws its own sort / group / tree / expand / pin / pagination
161
+ * icons via inline SVGs. Consumers can override any individual icon by
162
+ * passing an `iconOverrides` map in GridOptions or by writing to the
163
+ * element's `controlIcons` property at runtime. This module owns the
164
+ * icon registry class plus the SVG markup helpers so the element file
165
+ * doesn't have to.
166
+ */
167
+ type UiGridControlIconKey = 'sortNone' | 'sortAsc' | 'sortDesc' | 'group' | 'groupExpanded' | 'groupCollapsed' | 'treeExpanded' | 'treeCollapsed' | 'expandExpanded' | 'expandCollapsed' | 'pin' | 'pinLeft' | 'pinRight' | 'paginationPrev' | 'paginationNext';
168
+ interface UiGridIconDefinition {
169
+ viewBox?: string;
170
+ path: string;
171
+ }
172
+ type UiGridIconOverrides = Partial<Record<UiGridControlIconKey, UiGridIconDefinition>>;
173
+ /**
174
+ * Holds the active set of icons (defaults merged with consumer overrides)
175
+ * and renders the inline SVG markup for each one. A single instance lives
176
+ * on the element.
177
+ */
178
+ declare class IconRegistry {
179
+ private overrides;
180
+ private resolved;
181
+ getOverrides(): UiGridIconOverrides;
182
+ setOverrides(value: UiGridIconOverrides): void;
183
+ resolve(key: UiGridControlIconKey): UiGridIconDefinition;
184
+ /** Render the SVG markup for a control icon (sort, group, pin, …). */
185
+ renderControlIcon(key: UiGridControlIconKey): string;
186
+ /** Render the SVG markup with an arbitrary wrapper class. */
187
+ renderIconWithClass(svgClass: string, key: UiGridControlIconKey): string;
188
+ }
189
+
190
+ /**
191
+ * Complete grid save state — parity with the old `ui.grid.saveState`
192
+ * module. Every field is optional so partial saves / restores work too.
193
+ *
194
+ * Opt-in/out behaviour mirrors the old module: by default save() serialises
195
+ * everything; consumers can disable per-field capture via
196
+ * `saveSort`/`saveFilter`/... options on `GridOptions`. Restore tolerates
197
+ * missing fields.
198
+ */
4
199
  interface GridSaveState {
5
- sortState: SortState;
6
- activeFilters: Record<string, string>;
7
- groupByColumns: string[];
8
- pinnedColumns: PinnedColumnState;
9
- columnOrder: string[];
10
- currentPage: number;
11
- pageSize: number;
200
+ sortState?: SortState;
201
+ activeFilters?: Record<string, string>;
202
+ groupByColumns?: string[];
203
+ collapsedGroups?: Record<string, boolean>;
204
+ pinnedColumns?: PinnedColumnState;
205
+ columnOrder?: string[];
206
+ columnWidthOverrides?: Record<string, string>;
207
+ currentPage?: number;
208
+ pageSize?: number;
209
+ selectedRowIds?: string[];
210
+ focusedCell?: GridCellPosition | null;
211
+ expandedRows?: Record<string, boolean>;
212
+ expandedTreeRows?: Record<string, boolean>;
213
+ scrollTop?: number;
214
+ scrollLeft?: number;
12
215
  }
13
216
  interface GridControllerSnapshot {
14
217
  options: GridOptions;
@@ -28,6 +231,13 @@ interface GridControllerSnapshot {
28
231
  pageSize: number;
29
232
  editingCell: GridCellPosition | null;
30
233
  editingValue: string;
234
+ /** Row ids currently selected. A ReadonlySet for surgical DOM updates —
235
+ * the renderer toggles the .ui-grid-row-selected class on every cell
236
+ * whose data-row matches one of these ids. */
237
+ selectedRowIds: ReadonlySet<string>;
238
+ selectAll: boolean;
239
+ focusedRowId: string | null;
240
+ selectedCount: number;
31
241
  }
32
242
  type GridControllerSubscriber = (snapshot: GridControllerSnapshot) => void;
33
243
  declare class VanillaGridController {
@@ -35,6 +245,10 @@ declare class VanillaGridController {
35
245
  private activeFilters;
36
246
  private sortState;
37
247
  private groupByColumns;
248
+ private lastOptionsGroupBy;
249
+ private lastOptionsPinnedState;
250
+ private lastOptionsInfiniteScrollUp;
251
+ private lastOptionsInfiniteScrollDown;
38
252
  private collapsedGroups;
39
253
  private hiddenRowReasons;
40
254
  private expandedRows;
@@ -42,10 +256,26 @@ declare class VanillaGridController {
42
256
  private pinnedColumns;
43
257
  private columnOrder;
44
258
  private columnWidthOverrides;
259
+ private viewportWidth;
45
260
  private currentPage;
46
261
  private pageSize;
47
262
  private editingCell;
48
263
  private editingValue;
264
+ private exporterOverrides;
265
+ private validatorRegistry;
266
+ private rowEditState;
267
+ private rowEditTimers;
268
+ private rowEditSavePromiseOverrides;
269
+ private selectionState;
270
+ /** cellNav state. Persists the last focused cell so the public API's
271
+ * getFocusedCell returns the right value across re-renders, and tracks
272
+ * the focused-cells history for getCurrentSelection. */
273
+ private cellNavLastRowCol;
274
+ private cellNavFocusedCells;
275
+ /** Infinite-scroll state. Direction flags seed from options; dataLoading
276
+ * + previousVisibleRows are runtime bookkeeping for saveScrollPercentage /
277
+ * dataRemovedTop / dataRemovedBottom. */
278
+ private infiniteScrollState;
49
279
  private pipeline;
50
280
  private labels;
51
281
  private visibleColumns;
@@ -53,6 +283,11 @@ declare class VanillaGridController {
53
283
  private readonly subscribers;
54
284
  readonly gridApi: UiGridApi;
55
285
  constructor(options: GridOptions);
286
+ /** Disposer for the i18n language-change listener — cleared when the
287
+ * controller is torn down (matches the pattern used for other global
288
+ * subscriptions). */
289
+ private disposeLanguageListener;
290
+ dispose(): void;
56
291
  setOptions(options: GridOptions): void;
57
292
  /**
58
293
  * Fast-path: swap only the row data and rebuild the pipeline without
@@ -81,11 +316,84 @@ declare class VanillaGridController {
81
316
  expandAllTreeRows(): void;
82
317
  collapseAllTreeRows(): void;
83
318
  setTreeRowExpanded(row: GridRow | GridRecord | string, expanded: boolean): void;
84
- beginCellEdit(row: GridRow | GridRecord | string, columnName: string, triggerEvent?: Event | KeyboardEvent | null): void;
319
+ beginCellEdit(row: GridRow | GridRecord | string, columnName: string, triggerEvent?: Event | KeyboardEvent | null, initialValue?: string): void;
85
320
  updateEditingValue(value: string): void;
86
321
  commitCellEdit(): void;
87
322
  cancelCellEdit(): void;
323
+ /** Internal: run a core selection mutation, raise events, trigger a render. */
324
+ private applySelectionChange;
325
+ private allGridRows;
326
+ toggleRowSelectionByEntity(rowEntity: GridRecord, evt?: Event | null): void;
327
+ selectRow(rowEntity: GridRecord, evt?: Event | null): void;
328
+ selectRowByVisibleIndex(rowNum: number, evt?: Event | null): void;
329
+ selectRowByKey(isInEntity: boolean, key: string, comparator: unknown, evt?: Event | null, lookInRows?: readonly GridRow[]): void;
330
+ unSelectRow(rowEntity: GridRecord, evt?: Event | null): void;
331
+ unSelectRowByVisibleIndex(rowNum: number, evt?: Event | null): void;
332
+ unSelectRowByKey(isInEntity: boolean, key: string, comparator: unknown, evt?: Event | null, lookInRows?: readonly GridRow[]): void;
333
+ selectAllRows(evt?: Event | null): void;
334
+ selectAllVisibleRows(evt?: Event | null): void;
335
+ clearSelectedRows(evt?: Event | null): void;
336
+ shiftSelectRow(rowEntity: GridRecord, evt?: Event | null): void;
337
+ getSelectedRows(): GridRecord[];
338
+ getUnSelectedRows(): GridRecord[];
339
+ getSelectedGridRows(): GridRow[];
340
+ getUnSelectedGridRows(): GridRow[];
341
+ setMultiSelect(multiSelect: boolean): void;
342
+ setModifierKeysToMultiSelect(value: boolean): void;
343
+ /** Focus a row by id — the old module's `row.setFocused(true)` with all
344
+ * the book-keeping. Raises rowFocusChanged and refreshes. */
345
+ setRowFocused(rowId: string, focused: boolean, evt?: Event | null): void;
346
+ getRowSelectionState(): Readonly<GridSelectionState>;
347
+ /** Resolved/defaulted selection options — exposed for the element layer
348
+ * so it can decide whether to wire pointer handlers without duplicating
349
+ * default logic. */
350
+ getResolvedSelectionOptions(): ReturnType<typeof resolveGridSelectionOptions>;
351
+ /** Public analogue of findRowById — the element uses this during drag
352
+ * painting to resolve a row-id back to its entity without calling the
353
+ * selection API methods that would double-raise events. */
354
+ findRowByIdPublic(rowId: string): GridRow | null;
355
+ /** Record a navigation event. Pushes to the focused-cells history,
356
+ * updates lastRowCol, and raises cellNav.navigate on the public API. */
357
+ setCellNavFocus(rowId: string | null, columnName: string | null, opts?: {
358
+ appendToSelection?: boolean;
359
+ }): void;
360
+ /** Raise viewPortKeyDown / viewPortKeyPress on behalf of the element.
361
+ * Element gates this with `options.keyDownOverrides`. */
362
+ raiseCellNavKeyEvent(type: 'keydown' | 'keypress', event: KeyboardEvent): void;
363
+ private cellNavGetFocusedCell;
364
+ private cellNavGetCurrentSelection;
365
+ private cellNavRowColSelectIndex;
366
+ private resolveCellNavRowCol;
367
+ /** cellNav.scrollToFocus — resolves rowEntity + colDef into a logical
368
+ * focus and delegates to the element (if attached) for actual scrolling.
369
+ * When the element isn't wired (e.g. bare controller in tests), the
370
+ * state still updates and the promise resolves immediately. */
371
+ private cellNavScrollToFocus;
372
+ /** Element registers its scroll-and-focus handler here so the API
373
+ * binding above can delegate DOM work. Controller stays DOM-free. */
374
+ private cellNavScrollRequest;
375
+ setCellNavScrollHandler(handler: ((rowId: string | null, columnName: string | null) => void) | null): void;
376
+ /** Read-only accessor for the current options. Used by the element to
377
+ * inspect keyDownOverrides / modifierKeysToMultiSelectCells without
378
+ * coupling it to a private field. */
379
+ getOptions(): GridOptions;
380
+ /** Evaluate whether the current scroll position should request more
381
+ * data at the top or bottom. Called from the element's scroll frame. */
382
+ evaluateInfiniteScroll(startIndex: number, visibleRows: number, viewportRows: number): void;
383
+ private infiniteScrollDataLoaded;
384
+ private infiniteScrollResetScroll;
385
+ private infiniteScrollSavePercentage;
386
+ private infiniteScrollDataRemovedTop;
387
+ private infiniteScrollDataRemovedBottom;
388
+ private infiniteScrollSetDirections;
389
+ /** Element registers a scroll-to-top handler here so resetScroll can
390
+ * bring the viewport back to 0. Keeps the controller DOM-free. */
391
+ private infiniteScrollScrollToTopRequest;
392
+ setInfiniteScrollResetHandler(handler: (() => void) | null): void;
393
+ getInfiniteScrollState(): Readonly<GridInfiniteScrollState>;
88
394
  canResizeColumns(): boolean;
395
+ setViewportWidth(width: number): void;
396
+ getViewportWidth(): number;
89
397
  setColumnWidthOverride(columnName: string, widthPx: number): void;
90
398
  /** Returns the grid-template-columns string as if `columnName` had the given width,
91
399
  * without triggering a full refresh. Used for smooth drag-resize DOM updates. */
@@ -106,6 +414,12 @@ declare class VanillaGridController {
106
414
  getSortDirection(column: GridColumnDef): SortDirection;
107
415
  isPinningEnabled(): boolean;
108
416
  isColumnPinnable(column: GridColumnDef): boolean;
417
+ /** True when the given column is editable in principle — does NOT evaluate
418
+ * a per-row `cellEditableCondition`. Use isCellEditable for that. */
419
+ isColumnEditable(column: GridColumnDef): boolean;
420
+ /** True when the (row, column) cell is editable, respecting both the
421
+ * column's `enableCellEdit` flag and any `cellEditableCondition`. */
422
+ isCellEditable(row: GridRow, column: GridColumnDef): boolean;
109
423
  isPinned(column: GridColumnDef): boolean;
110
424
  isPinnedLeftLast(column: GridColumnDef): boolean;
111
425
  isPinnedRightFirst(column: GridColumnDef): boolean;
@@ -124,8 +438,26 @@ declare class VanillaGridController {
124
438
  expandToggleLabel(row: GridRow): string;
125
439
  editorInputType(column: GridColumnDef): string;
126
440
  isEditingCell(rowId: string, columnName: string): boolean;
441
+ /**
442
+ * Serialise the current grid state. Mirrors `ui.grid.saveState.save`:
443
+ * every field is gated by an `options.save*` flag with the old grid's
444
+ * defaults. A missing flag keeps the matching field out of the result.
445
+ */
127
446
  getState(): GridSaveState;
447
+ /**
448
+ * Apply a previously saved state. All fields are optional — missing
449
+ * fields are left at whatever the controller currently has. Matches the
450
+ * old grid's `ui.grid.saveState.restore`.
451
+ */
128
452
  setState(state: Partial<GridSaveState>): void;
453
+ /** Element injects its scroll accessor so save() can capture scrollTop/
454
+ * scrollLeft. Controller stays DOM-free. */
455
+ private saveStateScrollSnapshot;
456
+ private saveStateScrollRestore;
457
+ setSaveStateScrollHandlers(snapshot: (() => {
458
+ scrollTop: number;
459
+ scrollLeft: number;
460
+ }) | null, restore: ((scrollTop: number, scrollLeft: number) => void) | null): void;
129
461
  private refresh;
130
462
  private emit;
131
463
  private findRowById;
@@ -141,6 +473,67 @@ declare class VanillaGridController {
141
473
  private setRowInvisible;
142
474
  private clearRowInvisible;
143
475
  private benchmark;
476
+ /** Returns the rows that match the requested exporter row-type. Mirrors
477
+ * the old grid's row selection logic: `all` uses the full data set (or
478
+ * `exporterAllDataFn` when provided), `visible` uses the current pipeline
479
+ * output, `selected` uses the selection state. */
480
+ private exporterRowsFor;
481
+ private resolveExporterOptions;
482
+ private buildCsv;
483
+ private exportCsv;
484
+ private buildPdfDocDefinition;
485
+ private exportPdf;
486
+ private buildExcelSheetData;
487
+ private exportExcel;
488
+ /** Runs the validators declared on `colDef.validators` against the new
489
+ * value and flips `$$invalid<col>` / `$$errors<col>` on the entity. On
490
+ * failure raises `validate.validationFailed`. Async validators are
491
+ * awaited so the caller can `await` for complete results. */
492
+ private validateRunValidators;
493
+ private validateGetFormattedErrors;
494
+ private validateGetTitleFormattedErrors;
495
+ private validateGetInvalidRows;
496
+ /** Test hook — exposes the validator registry so consumers can inspect or
497
+ * extend it outside of the gridApi surface (e.g. from a controller-level
498
+ * plugin). */
499
+ getValidatorRegistry(): GridValidatorRegistry;
500
+ /** Set by the element so `importAFile()` can trigger its file-picker flow.
501
+ * Controller stays DOM-free: it just invokes whatever handler was wired. */
502
+ private importerFilePickerRequest;
503
+ setImporterFilePickerHandler(handler: (() => void) | null): void;
504
+ private resolveImporterOptions;
505
+ private importerRequestFile;
506
+ private importerDispatchObjects;
507
+ private importerImportText;
508
+ private importerImportThisFile;
509
+ private buildExporterMenuItems;
510
+ /** Menu items the importer contributes. Just the single "Import"
511
+ * entry when the feature is enabled; returns an empty array otherwise. */
512
+ private buildImporterMenuItems;
513
+ /** Menu items row-edit contributes: "Save changes" + "Retry errored
514
+ * rows". Each entry is gated by both a gridOption flag and a runtime
515
+ * predicate (`hasDirtyRows` / `hasErrorRows`) so the menu only surfaces
516
+ * actions that have something to do. */
517
+ private buildRowEditMenuItems;
518
+ private rowEditSetSavePromise;
519
+ private rowEditGetDirtyRows;
520
+ private rowEditGetErrorRows;
521
+ private rowEditFlushDirtyRows;
522
+ /** Re-fires `saveRow` for every row currently in the error state. The
523
+ * "Retry errored rows" menu entry funnels through here. Returns a
524
+ * promise that resolves once every retry promise has settled. */
525
+ private rowEditRetryErroredRows;
526
+ private rowEditSetRowsDirty;
527
+ private rowEditSetRowsClean;
528
+ private considerSetRowEditTimer;
529
+ private cancelRowEditTimer;
530
+ private runRowEditSave;
531
+ /** Test hook — reports the current row-edit dirty / error / saving state. */
532
+ getRowEditState(): {
533
+ dirtyRowIds: string[];
534
+ errorRowIds: string[];
535
+ savingRowIds: string[];
536
+ };
144
537
  }
145
538
  declare function createVanillaGridController(options: GridOptions): VanillaGridController;
146
539
 
@@ -155,49 +548,86 @@ type VanillaUiGridElement = HTMLElement & {
155
548
  */
156
549
  setData(rows: GridRecord[]): void;
157
550
  };
158
- type UiGridControlIconKey = 'sortNone' | 'sortAsc' | 'sortDesc' | 'group' | 'groupExpanded' | 'groupCollapsed' | 'treeExpanded' | 'treeCollapsed' | 'expandExpanded' | 'expandCollapsed' | 'pin' | 'pinLeft' | 'pinRight' | 'paginationPrev' | 'paginationNext';
159
- interface UiGridIconDefinition {
160
- viewBox?: string;
161
- path: string;
162
- }
163
- type UiGridIconOverrides = Partial<Record<UiGridControlIconKey, UiGridIconDefinition>>;
551
+ type VanillaGridOptions = GridOptions & {
552
+ iconOverrides?: UiGridIconOverrides;
553
+ };
164
554
  declare class UiGridStandaloneElement extends HTMLElement {
165
- private controller;
166
- private snapshot;
167
- private unsubscribe;
168
- private activeOptions;
169
- private attributeOptions;
170
- private attributeSyncScheduled;
171
- private iconOverrides;
172
- private templateObserver;
173
- private openPinMenuColumn;
174
- private focusedCell;
175
- private draggedColumnName;
176
- private dropTargetColumnName;
177
- private scrollPosition;
178
- private horizontalScrollPosition;
179
- private scrollFrame;
180
- private suppressScrollEvent;
181
- private lastVirtualStartIndex;
182
- private measuredHeaderStickyHeight;
183
- private measuredFilterStickyHeight;
184
- private stickyHeightRelayoutQueued;
185
- private benchmarkAverage;
186
- private skipNextRender;
187
- private dataFrame;
188
- private pendingPatchedRowIds;
189
- private pendingDataRefreshMode;
190
- private lastScrollActivityAt;
555
+ /** @internal */ controller: VanillaGridController | null;
556
+ /** @internal */ snapshot: GridControllerSnapshot | null;
557
+ /** @internal */ unsubscribe: (() => void) | null;
558
+ /** @internal */ activeOptions: VanillaGridOptions | null;
559
+ /** @internal */ attributeOptions: Partial<GridOptions>;
560
+ /** @internal */ attributeSyncScheduled: boolean;
561
+ /** @internal Owns the SVG-icon registry: defaults merged with consumer
562
+ * overrides plus rendering helpers. See `./icons.ts`. */
563
+ icons: IconRegistry;
564
+ /** @internal */ templateObserver: MutationObserver | null;
565
+ /** @internal */ openPinMenuColumn: string | null;
566
+ /** @internal */ focusedCell: {
567
+ rowId: string;
568
+ columnName: string;
569
+ } | null;
570
+ /** @internal */ draggedColumnName: string | null;
571
+ /** @internal */ dropTargetColumnName: string | null;
572
+ /** @internal */ scrollPosition: number;
573
+ /** @internal */ horizontalScrollPosition: number;
574
+ /** @internal */ scrollFrame: number | null;
575
+ /** @internal */ suppressScrollEvent: boolean;
576
+ /** @internal */ lastVirtualStartIndex: number;
577
+ /** @internal */ measuredHeaderStickyHeight: number;
578
+ /** @internal */ measuredFilterStickyHeight: number;
579
+ /** @internal */ stickyHeightRelayoutQueued: boolean;
580
+ /** @internal */ benchmarkAverage: string;
581
+ /** @internal */ skipNextRender: boolean;
582
+ /** @internal */ autoResizeObserver: ResizeObserver | null;
583
+ /** @internal */ autoResizeDebounceHandle: number | null;
584
+ gridTitle: string;
585
+ gridTableStyle: string;
586
+ bodyViewportStyle: string;
587
+ templateColumns: string;
588
+ slotRegistry: string;
589
+ headerContent: string;
590
+ filterRowContent: string;
591
+ bodyContent: string;
592
+ paginationContent: string;
593
+ /** @internal */ dataFrame: number | null;
594
+ /** @internal */ pendingPatchedRowIds: Set<string> | null;
595
+ /** @internal */ pendingDataRefreshMode: 'patch' | 'virtual' | 'full' | null;
596
+ /** @internal */ lastScrollActivityAt: number;
597
+ /** @internal */ lastStructureKey: string | null;
598
+ /** @internal */ lastItemsFingerprint: string | null;
599
+ /** @internal */ lastVirtualOffset: number;
600
+ /** @internal */ lastTotalVirtualHeight: number;
601
+ /**
602
+ * @internal Framework-rendered slot bridge. See `./framework-slots.ts`.
603
+ */
604
+ frameworkSlots: FrameworkSlotBridge;
191
605
  static get observedAttributes(): string[];
192
606
  get options(): GridOptions;
193
607
  set options(value: GridOptions);
608
+ /**
609
+ * Declare which slots are framework-rendered. When a slot is flagged, the
610
+ * element renders a `<slot name="…">` placeholder at the usual site instead
611
+ * of running its string-interpolation template, and dispatches
612
+ * `*SlotsChanged` events so a wrapper can insert light-DOM content with a
613
+ * matching `slot` attribute.
614
+ *
615
+ * Pass an empty object (or `{}`) to revert every slot kind back to the
616
+ * default interpolation path. Individual fields behave the same way:
617
+ * omitting `cells` means the cells list is unchanged; passing
618
+ * `cells: []` clears it.
619
+ *
620
+ * This is the bridge the Angular / React wrappers use to project native
621
+ * templates (ng-template, JSX render props) into the grid. Consumers of
622
+ * the web component directly can ignore this method — the default
623
+ * `<template slot="cell-columnName">` workflow still works.
624
+ */
625
+ setFrameworkRenderedSlots(config: FrameworkRenderedSlotsConfig): void;
194
626
  attributeChangedCallback(_name: string, _oldValue: string | null, _newValue: string | null): void;
195
627
  private syncAttributesToOptions;
196
- private parseBooleanAttribute;
197
- private parseNumberAttribute;
198
- private parseJsonAttribute;
199
628
  get controlIcons(): UiGridIconOverrides;
200
629
  set controlIcons(value: UiGridIconOverrides);
630
+ private resolveIcon;
201
631
  get gridId(): string;
202
632
  set gridId(value: string);
203
633
  get title(): string;
@@ -206,8 +636,6 @@ declare class UiGridStandaloneElement extends HTMLElement {
206
636
  set rowHeight(value: number);
207
637
  get headerRowHeight(): number;
208
638
  set headerRowHeight(value: number);
209
- get viewportHeight(): number;
210
- set viewportHeight(value: number);
211
639
  get paginationPageSize(): number;
212
640
  set paginationPageSize(value: number);
213
641
  get paginationCurrentPage(): number;
@@ -289,21 +717,87 @@ declare class UiGridStandaloneElement extends HTMLElement {
289
717
  private patchCells;
290
718
  connectedCallback(): void;
291
719
  disconnectedCallback(): void;
292
- private ensureShadowRoot;
293
- private ensureController;
720
+ /** @internal */
721
+ ensureShadowRoot(): ShadowRoot;
722
+ /** @internal */
723
+ ensureController(options: VanillaGridOptions): void;
724
+ /** Hidden file input used by `gridApi.importer.importAFile()`. Lazily
725
+ * created on first use and reused thereafter. */
726
+ private importerFileInput;
727
+ private openImporterFilePicker;
294
728
  private observeTemplateSlots;
295
- private buildEffectiveOptions;
296
729
  private bindEvents;
730
+ private syncHeaderHorizontalScroll;
731
+ private maybeTriggerInfiniteScroll;
732
+ /** @internal */
733
+ buildEffectiveOptions(options: VanillaGridOptions): VanillaGridOptions;
734
+ /**
735
+ * Ask the controller to evaluate whether the current scroll position
736
+ * should request more data at the top or bottom — ports the old grid's
737
+ * handleScroll → loadData check. The controller does the actual
738
+ * needLoadMoreData / needLoadMoreDataTop raise.
739
+ */
740
+ /**
741
+ * Mirror horizontal scroll from the body viewport onto the header + filter
742
+ * strips by setting `scrollLeft` on them. Because those strips are real
743
+ * scroll containers (with their scrollbars hidden), `position: sticky` on
744
+ * pinned header / filter cells anchors to the strip's scroll position the
745
+ * same way body cells anchor to `.grid-body-viewport`. This is the
746
+ * imperative sync strategy the old ui-grid used in ui-grid-viewport.js.
747
+ */
297
748
  /**
298
749
  * Targeted update for virtual scroll: replaces only the rendered rows inside
299
750
  * the existing `.grid-virtual-body` without touching the scroll container.
300
751
  * This preserves momentum/inertia scrolling because the `.grid-table` element
301
752
  * is never destroyed.
302
753
  */
303
- private renderVirtualBody;
304
- private render;
754
+ /** @internal */
755
+ renderVirtualBody(): void;
756
+ /** @internal */
757
+ render(): void;
758
+ /** @internal */
759
+ private inAutoAdjust;
760
+ private lastAutoHeight;
761
+ private autoAdjustHeight;
762
+ measuredPaginationHeight(): number;
763
+ /** @internal */
764
+ setupAutoResize(): void;
765
+ /** @internal */
766
+ teardownAutoResize(): void;
767
+ private buildRenderPlan;
768
+ private renderFull;
769
+ private renderPatch;
770
+ private reconcileBodyRoot;
771
+ private reconcilePagination;
772
+ private patchFilterCells;
773
+ private fingerprintItems;
774
+ private patchExistingRows;
775
+ private patchGroupRow;
776
+ private patchBodyCell;
777
+ private patchPagination;
305
778
  private renderHeaderCell;
306
779
  private renderFilterCell;
780
+ /**
781
+ * Commit the in-flight edit, then move keyboard focus relative to the
782
+ * committed cell. Splits commit + focus across two paint frames: commit
783
+ * runs synchronously (editor unmounts, cell re-renders with the new value),
784
+ * then focus moves to the adjacent cell. Scheduling the focus in a
785
+ * microtask avoids racing the blur handler's re-entry guard and ensures
786
+ * the destination cell is fully reattached in the DOM.
787
+ *
788
+ * If the source cell was being edited, the destination cell enters edit
789
+ * mode too (provided the destination column is editable). This keeps
790
+ * Tab/Enter nav inside an edit session continuous — you keep editing as
791
+ * you move. Arrow nav outside an edit session does NOT auto-enter edit.
792
+ */
793
+ private commitAndMove;
794
+ private moveGridFocus;
795
+ private scrollFocusedRowIntoView;
796
+ private handleRowSelectionClick;
797
+ private handleRowHeaderCheckboxClick;
798
+ private matchesKeyOverride;
799
+ private applyFocusedCellClass;
800
+ private focusCellElement;
307
801
  private measureAutoColumnWidth;
308
802
  private renderDisplayItem;
309
803
  private renderBodyCell;
@@ -313,18 +807,51 @@ declare class UiGridStandaloneElement extends HTMLElement {
313
807
  private renderCellTemplateFromMarkup;
314
808
  private renderExpandableTemplate;
315
809
  private getTemplateMarkup;
316
- private interpolateTemplate;
317
- private resolveTemplateValue;
810
+ /** @internal Used by the render module to interpolate consumer templates. */
811
+ interpolateTemplate(templateMarkup: string, context: Record<string, unknown>): string;
318
812
  private rowIndexFor;
319
813
  private classifyDataRefresh;
320
814
  private getRenderedDisplayWindow;
321
815
  private cellSlotName;
322
- private downloadCsv;
816
+ private renderEmptyState;
817
+ /** @internal */
818
+ downloadCsv(rowType?: 'all' | 'visible' | 'selected', colType?: 'all' | 'visible'): void;
323
819
  private renderControlIcon;
324
820
  private renderIconWithClass;
325
821
  }
326
822
  declare function defineStandaloneUiGridElement(tagName?: string): Promise<void>;
327
823
 
824
+ /**
825
+ * A declarative template element for defining reusable cell/row templates
826
+ * without writing JavaScript.
827
+ *
828
+ * Supports both `${this.prop}` and `{{prop}}` binding syntax.
829
+ *
830
+ * @example
831
+ * ```html
832
+ * <template is="ui-grid-template" name="ui-status-badge" status="unknown">
833
+ * <style>
834
+ * :host { display: inline-block; }
835
+ * .badge { padding: 2px 8px; border-radius: 4px; font-size: 0.8em; }
836
+ * .badge-active { background: #d1fae5; color: #065f46; }
837
+ * .badge-inactive { background: #fee2e2; color: #991b1b; }
838
+ * </style>
839
+ * <span class="badge badge-${this.status}">${this.status}</span>
840
+ * </template>
841
+ *
842
+ * <ui-grid-element>
843
+ * <template slot="cell-status">
844
+ * <ui-status-badge status="{{value}}"></ui-status-badge>
845
+ * </template>
846
+ * </ui-grid-element>
847
+ * ```
848
+ */
849
+ declare class UIGridTemplate extends HTMLTemplateElement {
850
+ static readonly TAG = "ui-grid-template";
851
+ connectedCallback(): void;
852
+ static define(tagName?: string): void;
853
+ }
854
+
328
855
  interface UiGridRustWebModule {
329
856
  default(input?: unknown): Promise<unknown>;
330
857
  build_pipeline_js(context: BuildGridPipelineContext): PipelineResult;
@@ -333,4 +860,4 @@ interface UiGridRustWebModule {
333
860
  declare function registerVanillaUiGridRustModule(module: UiGridRustWebModule, input?: unknown): Promise<void>;
334
861
  declare function mountVanillaUiGrid(target: Element, options: GridOptions, rustModule?: UiGridRustWebModule, tagName?: string): Promise<VanillaUiGridElement>;
335
862
 
336
- export { type GridControllerSnapshot, type GridSaveState, type UiGridControlIconKey, type UiGridIconDefinition, type UiGridIconOverrides, type UiGridRustWebModule, UiGridStandaloneElement, VanillaGridController, type VanillaUiGridElement, createVanillaGridController, defineStandaloneUiGridElement, defineStandaloneUiGridElement as defineUiGridElement, mountVanillaUiGrid, registerVanillaUiGridRustModule };
863
+ export { type FrameworkCellSlot, type FrameworkEmptyStateSlot, type FrameworkExpandableRowSlot, type FrameworkFilterSlot, type FrameworkGroupRowSlot, type FrameworkHeaderSlot, type FrameworkRenderedSlotsConfig, type FrameworkSlotDelta, type GridControllerSnapshot, type GridSaveState, UIGridTemplate, type UiGridControlIconKey, type UiGridIconDefinition, type UiGridIconOverrides, type UiGridRustWebModule, UiGridStandaloneElement, VanillaGridController, type VanillaUiGridElement, createVanillaGridController, defineStandaloneUiGridElement, defineStandaloneUiGridElement as defineUiGridElement, mountVanillaUiGrid, registerVanillaUiGridRustModule };