@ornery/ui-grid-vanilla 0.1.9 → 1.0.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 (63) hide show
  1. package/README.md +196 -0
  2. package/demo/index.html +3 -3
  3. package/demo/main.ts +1 -1
  4. package/dist/index.d.mts +584 -55
  5. package/dist/index.d.ts +584 -55
  6. package/dist/index.js +4887 -1404
  7. package/dist/index.mjs +5056 -1526
  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 +206 -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 +815 -0
  36. package/src/exporter.test.ts +186 -0
  37. package/src/focus.ts +353 -0
  38. package/src/framework-slots.ts +440 -0
  39. package/src/grid-controller.ts +1431 -20
  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 -1032
  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 {
@@ -41,10 +251,26 @@ declare class VanillaGridController {
41
251
  private expandedTreeRows;
42
252
  private pinnedColumns;
43
253
  private columnOrder;
254
+ private columnWidthOverrides;
44
255
  private currentPage;
45
256
  private pageSize;
46
257
  private editingCell;
47
258
  private editingValue;
259
+ private exporterOverrides;
260
+ private validatorRegistry;
261
+ private rowEditState;
262
+ private rowEditTimers;
263
+ private rowEditSavePromiseOverrides;
264
+ private selectionState;
265
+ /** cellNav state. Persists the last focused cell so the public API's
266
+ * getFocusedCell returns the right value across re-renders, and tracks
267
+ * the focused-cells history for getCurrentSelection. */
268
+ private cellNavLastRowCol;
269
+ private cellNavFocusedCells;
270
+ /** Infinite-scroll state. Direction flags seed from options; dataLoading
271
+ * + previousVisibleRows are runtime bookkeeping for saveScrollPercentage /
272
+ * dataRemovedTop / dataRemovedBottom. */
273
+ private infiniteScrollState;
48
274
  private pipeline;
49
275
  private labels;
50
276
  private visibleColumns;
@@ -52,6 +278,11 @@ declare class VanillaGridController {
52
278
  private readonly subscribers;
53
279
  readonly gridApi: UiGridApi;
54
280
  constructor(options: GridOptions);
281
+ /** Disposer for the i18n language-change listener — cleared when the
282
+ * controller is torn down (matches the pattern used for other global
283
+ * subscriptions). */
284
+ private disposeLanguageListener;
285
+ dispose(): void;
55
286
  setOptions(options: GridOptions): void;
56
287
  /**
57
288
  * Fast-path: swap only the row data and rebuild the pipeline without
@@ -80,10 +311,86 @@ declare class VanillaGridController {
80
311
  expandAllTreeRows(): void;
81
312
  collapseAllTreeRows(): void;
82
313
  setTreeRowExpanded(row: GridRow | GridRecord | string, expanded: boolean): void;
83
- beginCellEdit(row: GridRow | GridRecord | string, columnName: string, triggerEvent?: Event | KeyboardEvent | null): void;
314
+ beginCellEdit(row: GridRow | GridRecord | string, columnName: string, triggerEvent?: Event | KeyboardEvent | null, initialValue?: string): void;
84
315
  updateEditingValue(value: string): void;
85
316
  commitCellEdit(): void;
86
317
  cancelCellEdit(): void;
318
+ /** Internal: run a core selection mutation, raise events, trigger a render. */
319
+ private applySelectionChange;
320
+ private allGridRows;
321
+ toggleRowSelectionByEntity(rowEntity: GridRecord, evt?: Event | null): void;
322
+ selectRow(rowEntity: GridRecord, evt?: Event | null): void;
323
+ selectRowByVisibleIndex(rowNum: number, evt?: Event | null): void;
324
+ selectRowByKey(isInEntity: boolean, key: string, comparator: unknown, evt?: Event | null, lookInRows?: readonly GridRow[]): void;
325
+ unSelectRow(rowEntity: GridRecord, evt?: Event | null): void;
326
+ unSelectRowByVisibleIndex(rowNum: number, evt?: Event | null): void;
327
+ unSelectRowByKey(isInEntity: boolean, key: string, comparator: unknown, evt?: Event | null, lookInRows?: readonly GridRow[]): void;
328
+ selectAllRows(evt?: Event | null): void;
329
+ selectAllVisibleRows(evt?: Event | null): void;
330
+ clearSelectedRows(evt?: Event | null): void;
331
+ shiftSelectRow(rowEntity: GridRecord, evt?: Event | null): void;
332
+ getSelectedRows(): GridRecord[];
333
+ getUnSelectedRows(): GridRecord[];
334
+ getSelectedGridRows(): GridRow[];
335
+ getUnSelectedGridRows(): GridRow[];
336
+ setMultiSelect(multiSelect: boolean): void;
337
+ setModifierKeysToMultiSelect(value: boolean): void;
338
+ /** Focus a row by id — the old module's `row.setFocused(true)` with all
339
+ * the book-keeping. Raises rowFocusChanged and refreshes. */
340
+ setRowFocused(rowId: string, focused: boolean, evt?: Event | null): void;
341
+ getRowSelectionState(): Readonly<GridSelectionState>;
342
+ /** Resolved/defaulted selection options — exposed for the element layer
343
+ * so it can decide whether to wire pointer handlers without duplicating
344
+ * default logic. */
345
+ getResolvedSelectionOptions(): ReturnType<typeof resolveGridSelectionOptions>;
346
+ /** Public analogue of findRowById — the element uses this during drag
347
+ * painting to resolve a row-id back to its entity without calling the
348
+ * selection API methods that would double-raise events. */
349
+ findRowByIdPublic(rowId: string): GridRow | null;
350
+ /** Record a navigation event. Pushes to the focused-cells history,
351
+ * updates lastRowCol, and raises cellNav.navigate on the public API. */
352
+ setCellNavFocus(rowId: string | null, columnName: string | null, opts?: {
353
+ appendToSelection?: boolean;
354
+ }): void;
355
+ /** Raise viewPortKeyDown / viewPortKeyPress on behalf of the element.
356
+ * Element gates this with `options.keyDownOverrides`. */
357
+ raiseCellNavKeyEvent(type: 'keydown' | 'keypress', event: KeyboardEvent): void;
358
+ private cellNavGetFocusedCell;
359
+ private cellNavGetCurrentSelection;
360
+ private cellNavRowColSelectIndex;
361
+ private resolveCellNavRowCol;
362
+ /** cellNav.scrollToFocus — resolves rowEntity + colDef into a logical
363
+ * focus and delegates to the element (if attached) for actual scrolling.
364
+ * When the element isn't wired (e.g. bare controller in tests), the
365
+ * state still updates and the promise resolves immediately. */
366
+ private cellNavScrollToFocus;
367
+ /** Element registers its scroll-and-focus handler here so the API
368
+ * binding above can delegate DOM work. Controller stays DOM-free. */
369
+ private cellNavScrollRequest;
370
+ setCellNavScrollHandler(handler: ((rowId: string | null, columnName: string | null) => void) | null): void;
371
+ /** Read-only accessor for the current options. Used by the element to
372
+ * inspect keyDownOverrides / modifierKeysToMultiSelectCells without
373
+ * coupling it to a private field. */
374
+ getOptions(): GridOptions;
375
+ /** Evaluate whether the current scroll position should request more
376
+ * data at the top or bottom. Called from the element's scroll frame. */
377
+ evaluateInfiniteScroll(startIndex: number, visibleRows: number, viewportRows: number): void;
378
+ private infiniteScrollDataLoaded;
379
+ private infiniteScrollResetScroll;
380
+ private infiniteScrollSavePercentage;
381
+ private infiniteScrollDataRemovedTop;
382
+ private infiniteScrollDataRemovedBottom;
383
+ private infiniteScrollSetDirections;
384
+ /** Element registers a scroll-to-top handler here so resetScroll can
385
+ * bring the viewport back to 0. Keeps the controller DOM-free. */
386
+ private infiniteScrollScrollToTopRequest;
387
+ setInfiniteScrollResetHandler(handler: (() => void) | null): void;
388
+ getInfiniteScrollState(): Readonly<GridInfiniteScrollState>;
389
+ canResizeColumns(): boolean;
390
+ setColumnWidthOverride(columnName: string, widthPx: number): void;
391
+ /** Returns the grid-template-columns string as if `columnName` had the given width,
392
+ * without triggering a full refresh. Used for smooth drag-resize DOM updates. */
393
+ buildTemplateColumnsWithOverride(columnName: string, widthPx: number): string;
87
394
  isGroupingEnabled(): boolean;
88
395
  isSortingEnabled(): boolean;
89
396
  isFilteringEnabled(): boolean;
@@ -100,6 +407,12 @@ declare class VanillaGridController {
100
407
  getSortDirection(column: GridColumnDef): SortDirection;
101
408
  isPinningEnabled(): boolean;
102
409
  isColumnPinnable(column: GridColumnDef): boolean;
410
+ /** True when the given column is editable in principle — does NOT evaluate
411
+ * a per-row `cellEditableCondition`. Use isCellEditable for that. */
412
+ isColumnEditable(column: GridColumnDef): boolean;
413
+ /** True when the (row, column) cell is editable, respecting both the
414
+ * column's `enableCellEdit` flag and any `cellEditableCondition`. */
415
+ isCellEditable(row: GridRow, column: GridColumnDef): boolean;
103
416
  isPinned(column: GridColumnDef): boolean;
104
417
  isPinnedLeftLast(column: GridColumnDef): boolean;
105
418
  isPinnedRightFirst(column: GridColumnDef): boolean;
@@ -118,8 +431,26 @@ declare class VanillaGridController {
118
431
  expandToggleLabel(row: GridRow): string;
119
432
  editorInputType(column: GridColumnDef): string;
120
433
  isEditingCell(rowId: string, columnName: string): boolean;
434
+ /**
435
+ * Serialise the current grid state. Mirrors `ui.grid.saveState.save`:
436
+ * every field is gated by an `options.save*` flag with the old grid's
437
+ * defaults. A missing flag keeps the matching field out of the result.
438
+ */
121
439
  getState(): GridSaveState;
440
+ /**
441
+ * Apply a previously saved state. All fields are optional — missing
442
+ * fields are left at whatever the controller currently has. Matches the
443
+ * old grid's `ui.grid.saveState.restore`.
444
+ */
122
445
  setState(state: Partial<GridSaveState>): void;
446
+ /** Element injects its scroll accessor so save() can capture scrollTop/
447
+ * scrollLeft. Controller stays DOM-free. */
448
+ private saveStateScrollSnapshot;
449
+ private saveStateScrollRestore;
450
+ setSaveStateScrollHandlers(snapshot: (() => {
451
+ scrollTop: number;
452
+ scrollLeft: number;
453
+ }) | null, restore: ((scrollTop: number, scrollLeft: number) => void) | null): void;
123
454
  private refresh;
124
455
  private emit;
125
456
  private findRowById;
@@ -135,6 +466,67 @@ declare class VanillaGridController {
135
466
  private setRowInvisible;
136
467
  private clearRowInvisible;
137
468
  private benchmark;
469
+ /** Returns the rows that match the requested exporter row-type. Mirrors
470
+ * the old grid's row selection logic: `all` uses the full data set (or
471
+ * `exporterAllDataFn` when provided), `visible` uses the current pipeline
472
+ * output, `selected` uses the selection state. */
473
+ private exporterRowsFor;
474
+ private resolveExporterOptions;
475
+ private buildCsv;
476
+ private exportCsv;
477
+ private buildPdfDocDefinition;
478
+ private exportPdf;
479
+ private buildExcelSheetData;
480
+ private exportExcel;
481
+ /** Runs the validators declared on `colDef.validators` against the new
482
+ * value and flips `$$invalid<col>` / `$$errors<col>` on the entity. On
483
+ * failure raises `validate.validationFailed`. Async validators are
484
+ * awaited so the caller can `await` for complete results. */
485
+ private validateRunValidators;
486
+ private validateGetFormattedErrors;
487
+ private validateGetTitleFormattedErrors;
488
+ private validateGetInvalidRows;
489
+ /** Test hook — exposes the validator registry so consumers can inspect or
490
+ * extend it outside of the gridApi surface (e.g. from a controller-level
491
+ * plugin). */
492
+ getValidatorRegistry(): GridValidatorRegistry;
493
+ /** Set by the element so `importAFile()` can trigger its file-picker flow.
494
+ * Controller stays DOM-free: it just invokes whatever handler was wired. */
495
+ private importerFilePickerRequest;
496
+ setImporterFilePickerHandler(handler: (() => void) | null): void;
497
+ private resolveImporterOptions;
498
+ private importerRequestFile;
499
+ private importerDispatchObjects;
500
+ private importerImportText;
501
+ private importerImportThisFile;
502
+ private buildExporterMenuItems;
503
+ /** Menu items the importer contributes. Just the single "Import"
504
+ * entry when the feature is enabled; returns an empty array otherwise. */
505
+ private buildImporterMenuItems;
506
+ /** Menu items row-edit contributes: "Save changes" + "Retry errored
507
+ * rows". Each entry is gated by both a gridOption flag and a runtime
508
+ * predicate (`hasDirtyRows` / `hasErrorRows`) so the menu only surfaces
509
+ * actions that have something to do. */
510
+ private buildRowEditMenuItems;
511
+ private rowEditSetSavePromise;
512
+ private rowEditGetDirtyRows;
513
+ private rowEditGetErrorRows;
514
+ private rowEditFlushDirtyRows;
515
+ /** Re-fires `saveRow` for every row currently in the error state. The
516
+ * "Retry errored rows" menu entry funnels through here. Returns a
517
+ * promise that resolves once every retry promise has settled. */
518
+ private rowEditRetryErroredRows;
519
+ private rowEditSetRowsDirty;
520
+ private rowEditSetRowsClean;
521
+ private considerSetRowEditTimer;
522
+ private cancelRowEditTimer;
523
+ private runRowEditSave;
524
+ /** Test hook — reports the current row-edit dirty / error / saving state. */
525
+ getRowEditState(): {
526
+ dirtyRowIds: string[];
527
+ errorRowIds: string[];
528
+ savingRowIds: string[];
529
+ };
138
530
  }
139
531
  declare function createVanillaGridController(options: GridOptions): VanillaGridController;
140
532
 
@@ -149,49 +541,86 @@ type VanillaUiGridElement = HTMLElement & {
149
541
  */
150
542
  setData(rows: GridRecord[]): void;
151
543
  };
152
- type UiGridControlIconKey = 'sortNone' | 'sortAsc' | 'sortDesc' | 'group' | 'groupExpanded' | 'groupCollapsed' | 'treeExpanded' | 'treeCollapsed' | 'expandExpanded' | 'expandCollapsed' | 'pin' | 'pinLeft' | 'pinRight' | 'paginationPrev' | 'paginationNext';
153
- interface UiGridIconDefinition {
154
- viewBox?: string;
155
- path: string;
156
- }
157
- type UiGridIconOverrides = Partial<Record<UiGridControlIconKey, UiGridIconDefinition>>;
544
+ type VanillaGridOptions = GridOptions & {
545
+ iconOverrides?: UiGridIconOverrides;
546
+ };
158
547
  declare class UiGridStandaloneElement extends HTMLElement {
159
- private controller;
160
- private snapshot;
161
- private unsubscribe;
162
- private activeOptions;
163
- private attributeOptions;
164
- private attributeSyncScheduled;
165
- private iconOverrides;
166
- private templateObserver;
167
- private openPinMenuColumn;
168
- private focusedCell;
169
- private draggedColumnName;
170
- private dropTargetColumnName;
171
- private scrollPosition;
172
- private horizontalScrollPosition;
173
- private scrollFrame;
174
- private suppressScrollEvent;
175
- private lastVirtualStartIndex;
176
- private measuredHeaderStickyHeight;
177
- private measuredFilterStickyHeight;
178
- private stickyHeightRelayoutQueued;
179
- private benchmarkAverage;
180
- private skipNextRender;
181
- private dataFrame;
182
- private pendingPatchedRowIds;
183
- private pendingDataRefreshMode;
184
- private lastScrollActivityAt;
548
+ /** @internal */ controller: VanillaGridController | null;
549
+ /** @internal */ snapshot: GridControllerSnapshot | null;
550
+ /** @internal */ unsubscribe: (() => void) | null;
551
+ /** @internal */ activeOptions: VanillaGridOptions | null;
552
+ /** @internal */ attributeOptions: Partial<GridOptions>;
553
+ /** @internal */ attributeSyncScheduled: boolean;
554
+ /** @internal Owns the SVG-icon registry: defaults merged with consumer
555
+ * overrides plus rendering helpers. See `./icons.ts`. */
556
+ icons: IconRegistry;
557
+ /** @internal */ templateObserver: MutationObserver | null;
558
+ /** @internal */ openPinMenuColumn: string | null;
559
+ /** @internal */ focusedCell: {
560
+ rowId: string;
561
+ columnName: string;
562
+ } | null;
563
+ /** @internal */ draggedColumnName: string | null;
564
+ /** @internal */ dropTargetColumnName: string | null;
565
+ /** @internal */ scrollPosition: number;
566
+ /** @internal */ horizontalScrollPosition: number;
567
+ /** @internal */ scrollFrame: number | null;
568
+ /** @internal */ suppressScrollEvent: boolean;
569
+ /** @internal */ lastVirtualStartIndex: number;
570
+ /** @internal */ measuredHeaderStickyHeight: number;
571
+ /** @internal */ measuredFilterStickyHeight: number;
572
+ /** @internal */ stickyHeightRelayoutQueued: boolean;
573
+ /** @internal */ benchmarkAverage: string;
574
+ /** @internal */ skipNextRender: boolean;
575
+ /** @internal */ autoResizeObserver: ResizeObserver | null;
576
+ /** @internal */ autoResizeDebounceHandle: number | null;
577
+ gridTitle: string;
578
+ gridTableStyle: string;
579
+ bodyViewportStyle: string;
580
+ templateColumns: string;
581
+ slotRegistry: string;
582
+ headerContent: string;
583
+ filterRowContent: string;
584
+ bodyContent: string;
585
+ paginationContent: string;
586
+ /** @internal */ dataFrame: number | null;
587
+ /** @internal */ pendingPatchedRowIds: Set<string> | null;
588
+ /** @internal */ pendingDataRefreshMode: 'patch' | 'virtual' | 'full' | null;
589
+ /** @internal */ lastScrollActivityAt: number;
590
+ /** @internal */ lastStructureKey: string | null;
591
+ /** @internal */ lastItemsFingerprint: string | null;
592
+ /** @internal */ lastVirtualOffset: number;
593
+ /** @internal */ lastTotalVirtualHeight: number;
594
+ /**
595
+ * @internal Framework-rendered slot bridge. See `./framework-slots.ts`.
596
+ */
597
+ frameworkSlots: FrameworkSlotBridge;
185
598
  static get observedAttributes(): string[];
186
599
  get options(): GridOptions;
187
600
  set options(value: GridOptions);
601
+ /**
602
+ * Declare which slots are framework-rendered. When a slot is flagged, the
603
+ * element renders a `<slot name="…">` placeholder at the usual site instead
604
+ * of running its string-interpolation template, and dispatches
605
+ * `*SlotsChanged` events so a wrapper can insert light-DOM content with a
606
+ * matching `slot` attribute.
607
+ *
608
+ * Pass an empty object (or `{}`) to revert every slot kind back to the
609
+ * default interpolation path. Individual fields behave the same way:
610
+ * omitting `cells` means the cells list is unchanged; passing
611
+ * `cells: []` clears it.
612
+ *
613
+ * This is the bridge the Angular / React wrappers use to project native
614
+ * templates (ng-template, JSX render props) into the grid. Consumers of
615
+ * the web component directly can ignore this method — the default
616
+ * `<template slot="cell-columnName">` workflow still works.
617
+ */
618
+ setFrameworkRenderedSlots(config: FrameworkRenderedSlotsConfig): void;
188
619
  attributeChangedCallback(_name: string, _oldValue: string | null, _newValue: string | null): void;
189
620
  private syncAttributesToOptions;
190
- private parseBooleanAttribute;
191
- private parseNumberAttribute;
192
- private parseJsonAttribute;
193
621
  get controlIcons(): UiGridIconOverrides;
194
622
  set controlIcons(value: UiGridIconOverrides);
623
+ private resolveIcon;
195
624
  get gridId(): string;
196
625
  set gridId(value: string);
197
626
  get title(): string;
@@ -200,8 +629,6 @@ declare class UiGridStandaloneElement extends HTMLElement {
200
629
  set rowHeight(value: number);
201
630
  get headerRowHeight(): number;
202
631
  set headerRowHeight(value: number);
203
- get viewportHeight(): number;
204
- set viewportHeight(value: number);
205
632
  get paginationPageSize(): number;
206
633
  set paginationPageSize(value: number);
207
634
  get paginationCurrentPage(): number;
@@ -236,6 +663,8 @@ declare class UiGridStandaloneElement extends HTMLElement {
236
663
  set enablePinning(value: boolean);
237
664
  get enableColumnMoving(): boolean;
238
665
  set enableColumnMoving(value: boolean);
666
+ get enableColumnResizing(): boolean;
667
+ set enableColumnResizing(value: boolean);
239
668
  get enableCellEdit(): boolean;
240
669
  set enableCellEdit(value: boolean);
241
670
  get enableCellEditOnFocus(): boolean;
@@ -281,21 +710,88 @@ declare class UiGridStandaloneElement extends HTMLElement {
281
710
  private patchCells;
282
711
  connectedCallback(): void;
283
712
  disconnectedCallback(): void;
284
- private ensureShadowRoot;
285
- private ensureController;
713
+ /** @internal */
714
+ ensureShadowRoot(): ShadowRoot;
715
+ /** @internal */
716
+ ensureController(options: VanillaGridOptions): void;
717
+ /** Hidden file input used by `gridApi.importer.importAFile()`. Lazily
718
+ * created on first use and reused thereafter. */
719
+ private importerFileInput;
720
+ private openImporterFilePicker;
286
721
  private observeTemplateSlots;
287
- private buildEffectiveOptions;
288
722
  private bindEvents;
723
+ private syncHeaderHorizontalScroll;
724
+ private maybeTriggerInfiniteScroll;
725
+ /** @internal */
726
+ buildEffectiveOptions(options: VanillaGridOptions): VanillaGridOptions;
727
+ /**
728
+ * Ask the controller to evaluate whether the current scroll position
729
+ * should request more data at the top or bottom — ports the old grid's
730
+ * handleScroll → loadData check. The controller does the actual
731
+ * needLoadMoreData / needLoadMoreDataTop raise.
732
+ */
733
+ /**
734
+ * Mirror horizontal scroll from the body viewport onto the header + filter
735
+ * strips by setting `scrollLeft` on them. Because those strips are real
736
+ * scroll containers (with their scrollbars hidden), `position: sticky` on
737
+ * pinned header / filter cells anchors to the strip's scroll position the
738
+ * same way body cells anchor to `.grid-body-viewport`. This is the
739
+ * imperative sync strategy the old ui-grid used in ui-grid-viewport.js.
740
+ */
289
741
  /**
290
742
  * Targeted update for virtual scroll: replaces only the rendered rows inside
291
743
  * the existing `.grid-virtual-body` without touching the scroll container.
292
744
  * This preserves momentum/inertia scrolling because the `.grid-table` element
293
745
  * is never destroyed.
294
746
  */
295
- private renderVirtualBody;
296
- private render;
747
+ /** @internal */
748
+ renderVirtualBody(): void;
749
+ /** @internal */
750
+ render(): void;
751
+ /** @internal */
752
+ private inAutoAdjust;
753
+ private lastAutoHeight;
754
+ private autoAdjustHeight;
755
+ measuredPaginationHeight(): number;
756
+ /** @internal */
757
+ setupAutoResize(): void;
758
+ /** @internal */
759
+ teardownAutoResize(): void;
760
+ private buildRenderPlan;
761
+ private renderFull;
762
+ private renderPatch;
763
+ private reconcileBodyRoot;
764
+ private reconcilePagination;
765
+ private patchFilterCells;
766
+ private fingerprintItems;
767
+ private patchExistingRows;
768
+ private patchGroupRow;
769
+ private patchBodyCell;
770
+ private patchPagination;
297
771
  private renderHeaderCell;
298
772
  private renderFilterCell;
773
+ /**
774
+ * Commit the in-flight edit, then move keyboard focus relative to the
775
+ * committed cell. Splits commit + focus across two paint frames: commit
776
+ * runs synchronously (editor unmounts, cell re-renders with the new value),
777
+ * then focus moves to the adjacent cell. Scheduling the focus in a
778
+ * microtask avoids racing the blur handler's re-entry guard and ensures
779
+ * the destination cell is fully reattached in the DOM.
780
+ *
781
+ * If the source cell was being edited, the destination cell enters edit
782
+ * mode too (provided the destination column is editable). This keeps
783
+ * Tab/Enter nav inside an edit session continuous — you keep editing as
784
+ * you move. Arrow nav outside an edit session does NOT auto-enter edit.
785
+ */
786
+ private commitAndMove;
787
+ private moveGridFocus;
788
+ private scrollFocusedRowIntoView;
789
+ private handleRowSelectionClick;
790
+ private handleRowHeaderCheckboxClick;
791
+ private matchesKeyOverride;
792
+ private applyFocusedCellClass;
793
+ private focusCellElement;
794
+ private measureAutoColumnWidth;
299
795
  private renderDisplayItem;
300
796
  private renderBodyCell;
301
797
  private renderPagination;
@@ -304,18 +800,51 @@ declare class UiGridStandaloneElement extends HTMLElement {
304
800
  private renderCellTemplateFromMarkup;
305
801
  private renderExpandableTemplate;
306
802
  private getTemplateMarkup;
307
- private interpolateTemplate;
308
- private resolveTemplateValue;
803
+ /** @internal Used by the render module to interpolate consumer templates. */
804
+ interpolateTemplate(templateMarkup: string, context: Record<string, unknown>): string;
309
805
  private rowIndexFor;
310
806
  private classifyDataRefresh;
311
807
  private getRenderedDisplayWindow;
312
808
  private cellSlotName;
313
- private downloadCsv;
809
+ private renderEmptyState;
810
+ /** @internal */
811
+ downloadCsv(rowType?: 'all' | 'visible' | 'selected', colType?: 'all' | 'visible'): void;
314
812
  private renderControlIcon;
315
813
  private renderIconWithClass;
316
814
  }
317
815
  declare function defineStandaloneUiGridElement(tagName?: string): Promise<void>;
318
816
 
817
+ /**
818
+ * A declarative template element for defining reusable cell/row templates
819
+ * without writing JavaScript.
820
+ *
821
+ * Supports both `${this.prop}` and `{{prop}}` binding syntax.
822
+ *
823
+ * @example
824
+ * ```html
825
+ * <template is="ui-grid-template" name="ui-status-badge" status="unknown">
826
+ * <style>
827
+ * :host { display: inline-block; }
828
+ * .badge { padding: 2px 8px; border-radius: 4px; font-size: 0.8em; }
829
+ * .badge-active { background: #d1fae5; color: #065f46; }
830
+ * .badge-inactive { background: #fee2e2; color: #991b1b; }
831
+ * </style>
832
+ * <span class="badge badge-${this.status}">${this.status}</span>
833
+ * </template>
834
+ *
835
+ * <ui-grid-element>
836
+ * <template slot="cell-status">
837
+ * <ui-status-badge status="{{value}}"></ui-status-badge>
838
+ * </template>
839
+ * </ui-grid-element>
840
+ * ```
841
+ */
842
+ declare class UIGridTemplate extends HTMLTemplateElement {
843
+ static readonly TAG = "ui-grid-template";
844
+ connectedCallback(): void;
845
+ static define(tagName?: string): void;
846
+ }
847
+
319
848
  interface UiGridRustWebModule {
320
849
  default(input?: unknown): Promise<unknown>;
321
850
  build_pipeline_js(context: BuildGridPipelineContext): PipelineResult;
@@ -324,4 +853,4 @@ interface UiGridRustWebModule {
324
853
  declare function registerVanillaUiGridRustModule(module: UiGridRustWebModule, input?: unknown): Promise<void>;
325
854
  declare function mountVanillaUiGrid(target: Element, options: GridOptions, rustModule?: UiGridRustWebModule, tagName?: string): Promise<VanillaUiGridElement>;
326
855
 
327
- export { type GridControllerSnapshot, type GridSaveState, type UiGridControlIconKey, type UiGridIconDefinition, type UiGridIconOverrides, type UiGridRustWebModule, UiGridStandaloneElement, VanillaGridController, type VanillaUiGridElement, createVanillaGridController, defineStandaloneUiGridElement, defineStandaloneUiGridElement as defineUiGridElement, mountVanillaUiGrid, registerVanillaUiGridRustModule };
856
+ 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 };