@rozie-ui/data-table-lit 0.7.1 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/DataTable.ts CHANGED
@@ -46,63 +46,167 @@ import { columnSpecsEquivalent } from './helpers/columnDefUtils';
46
46
 
47
47
  // The registry API handed to <Column> children (whole-object-replace — T-48-PP guard).
48
48
 
49
+ import type { Column as TableColumn, ColumnDef, ColumnFiltersState, ColumnOrderState, ColumnPinningState, ColumnSizingState, ExpandedState, GroupingState, PaginationState, RowSelectionState, SortingState, VisibilityState } from '@tanstack/table-core';
50
+
51
+ /** A cell position as integers over the visible (display-order) model. */
52
+ export interface DataTableCellPosition {
53
+ rowIndex: number;
54
+ colIndex: number;
55
+ }
56
+ /** The active (roving-focus) cell. A header cell reports `rowIndex: null` and `isHeader: true`. */
57
+ export interface DataTableActiveCell {
58
+ rowIndex: number | null;
59
+ colIndex: number;
60
+ isHeader: boolean;
61
+ }
62
+ /** The rectangular cell-range selection. Both corners are `null` when there is no range. */
63
+ export interface DataTableRange {
64
+ anchor: DataTableCellPosition | null;
65
+ focus: DataTableCellPosition | null;
66
+ }
67
+ /** One data column offered to the `#groupBar` slot. */
68
+ export interface DataTableGroupableColumn {
69
+ id: string;
70
+ label: string;
71
+ }
72
+ /** One changed cell inside a row edit commit. */
73
+ export interface DataTableCellChange {
74
+ columnId: string;
75
+ oldValue: any;
76
+ newValue: any;
77
+ }
78
+
79
+ /** The `activecell-change` payload. `isHeader` is set when the move landed on or left a header cell. */
80
+ export interface DataTableActiveCellChangePayload {
81
+ rowIndex: number | null;
82
+ colIndex: number;
83
+ isHeader?: boolean;
84
+ }
85
+ /** The `cell-edit-commit` payload: one committed cell. */
86
+ export interface DataTableCellEditCommitPayload {
87
+ rowId: string;
88
+ columnId: string;
89
+ oldValue: any;
90
+ newValue: any;
91
+ }
92
+ /** The `row-edit-commit` payload: the row and every cell the save changed. */
93
+ export interface DataTableRowEditCommitPayload {
94
+ rowId: string;
95
+ changes: DataTableCellChange[];
96
+ }
97
+ /** The `filter-change` payload: `globalFilter` for the search box, `columnFilters` for a per-column filter. Exactly one key is set. */
98
+ export interface DataTableFilterChangePayload {
99
+ globalFilter?: string;
100
+ columnFilters?: ColumnFiltersState;
101
+ }
102
+ /** The `history-change` payload: undo/redo availability. */
103
+ export interface DataTableHistoryChangePayload {
104
+ canUndo: boolean;
105
+ canRedo: boolean;
106
+ }
107
+ /** The `range-change` payload: the new range corners (both `null` when the range cleared). */
108
+ export type DataTableRangeChangePayload = DataTableRange;
109
+ /** The `row-activate` payload. `row` is the original data object and `index` its position in the rendered model. */
110
+ export interface DataTableRowActivatePayload {
111
+ row: any;
112
+ index: number;
113
+ trigger: 'keyboard' | 'click';
114
+ }
115
+ /** The `visible-range-change` payload: the rendered row window, `end` exclusive. */
116
+ export interface DataTableVisibleRangeChangePayload {
117
+ start: number;
118
+ end: number;
119
+ }
120
+
121
+ export interface RozieDataTableEventMap extends Omit<HTMLElementEventMap, 'activecell-change' | 'cell-edit-commit' | 'expand-change' | 'filter-change' | 'group-change' | 'history-change' | 'page-change' | 'pin-change' | 'range-change' | 'reorder-change' | 'resize-change' | 'row-activate' | 'row-edit-commit' | 'selection-change' | 'sort-change' | 'visibility-change' | 'visible-range-change' | 'data-change' | 'sorting-change' | 'global-filter-change' | 'column-filters-change' | 'pagination-change' | 'expanded-change' | 'grouping-change' | 'row-selection-change' | 'column-visibility-change' | 'column-sizing-change' | 'column-order-change' | 'column-pinning-change'> {
122
+ 'activecell-change': CustomEvent<DataTableActiveCellChangePayload>;
123
+ 'cell-edit-commit': CustomEvent<DataTableCellEditCommitPayload>;
124
+ 'expand-change': CustomEvent<ExpandedState>;
125
+ 'filter-change': CustomEvent<DataTableFilterChangePayload>;
126
+ 'group-change': CustomEvent<GroupingState>;
127
+ 'history-change': CustomEvent<DataTableHistoryChangePayload>;
128
+ 'page-change': CustomEvent<PaginationState>;
129
+ 'pin-change': CustomEvent<ColumnPinningState>;
130
+ 'range-change': CustomEvent<DataTableRangeChangePayload>;
131
+ 'reorder-change': CustomEvent<ColumnOrderState>;
132
+ 'resize-change': CustomEvent<ColumnSizingState>;
133
+ 'row-activate': CustomEvent<DataTableRowActivatePayload>;
134
+ 'row-edit-commit': CustomEvent<DataTableRowEditCommitPayload>;
135
+ 'selection-change': CustomEvent<RowSelectionState>;
136
+ 'sort-change': CustomEvent<SortingState>;
137
+ 'visibility-change': CustomEvent<VisibilityState>;
138
+ 'visible-range-change': CustomEvent<DataTableVisibleRangeChangePayload>;
139
+ 'data-change': CustomEvent<any[]>;
140
+ 'sorting-change': CustomEvent<any[]>;
141
+ 'global-filter-change': CustomEvent<string>;
142
+ 'column-filters-change': CustomEvent<any[]>;
143
+ 'pagination-change': CustomEvent<any>;
144
+ 'expanded-change': CustomEvent<any | boolean>;
145
+ 'grouping-change': CustomEvent<any[]>;
146
+ 'row-selection-change': CustomEvent<any>;
147
+ 'column-visibility-change': CustomEvent<any>;
148
+ 'column-sizing-change': CustomEvent<any>;
149
+ 'column-order-change': CustomEvent<any[]>;
150
+ 'column-pinning-change': CustomEvent<any>;
151
+ }
152
+
49
153
  const __rozieCtx_data_table_columns = createContext(Symbol.for("rozie:data-table:columns"));
50
154
 
51
155
  interface RozieGroupBarSlotCtx {
52
- grouping: any;
53
- groupableColumns: any;
54
- applyGrouping: any;
55
- clearGrouping: any;
156
+ grouping: string[];
157
+ groupableColumns: DataTableGroupableColumn[];
158
+ applyGrouping: (cols: string[]) => void;
159
+ clearGrouping: () => void;
56
160
  }
57
161
 
58
162
  interface RozieSelectAllSlotCtx {
59
- checked: any;
60
- indeterminate: any;
61
- toggle: any;
163
+ checked: boolean;
164
+ indeterminate: boolean;
165
+ toggle: (event: any) => void;
62
166
  }
63
167
 
64
168
  interface RozieDynamicColHeaderSlotCtx {
65
- columnId: any;
66
- column: any;
67
- label: any;
169
+ columnId: string;
170
+ column: TableColumn<any, unknown>;
171
+ label: string;
68
172
  }
69
173
 
70
174
  interface RozieDynamicFilterSlotCtx {
71
- columnId: any;
175
+ columnId: string;
72
176
  value: any;
73
- uniqueValues: any;
74
- minMax: any;
75
- columnLabel: any;
76
- setFilter: any;
177
+ uniqueValues: any[];
178
+ minMax: [number, number] | null;
179
+ columnLabel: string;
180
+ setFilter: (columnId: string, value: any) => void;
77
181
  }
78
182
 
79
183
  interface RoziePlaceholderSlotCtx {
80
- index: any;
81
- columnId: any;
184
+ index: number;
185
+ columnId: string;
82
186
  }
83
187
 
84
188
  interface RozieSelectCellSlotCtx {
85
189
  row: any;
86
- checked: any;
87
- toggle: any;
190
+ checked: boolean;
191
+ toggle: (event: any) => void;
88
192
  }
89
193
 
90
194
  interface RozieDynamicCellSlotCtx {
91
- columnId: any;
92
- column: any;
195
+ columnId: string;
196
+ column: TableColumn<any, unknown>;
93
197
  row: any;
94
198
  value: any;
95
199
  }
96
200
 
97
201
  interface RozieDynamicEditorSlotCtx {
98
- columnId: any;
99
- column: any;
202
+ columnId: string;
203
+ column: TableColumn<any, unknown>;
100
204
  row: any;
101
205
  value: any;
102
- commit: any;
103
- cancel: any;
104
- columnLabel: any;
105
- autofocus: any;
206
+ commit: (value: any) => void;
207
+ cancel: () => void;
208
+ columnLabel: string;
209
+ autofocus: boolean;
106
210
  }
107
211
 
108
212
  interface RozieDetailSlotCtx {
@@ -110,36 +214,36 @@ interface RozieDetailSlotCtx {
110
214
  }
111
215
 
112
216
  interface RozieColHeaderSlotCtx {
113
- columnId: any;
114
- column: any;
115
- label: any;
217
+ columnId: string;
218
+ column: TableColumn<any, unknown>;
219
+ label: string;
116
220
  }
117
221
 
118
222
  interface RozieFilterSlotCtx {
119
- columnId: any;
223
+ columnId: string;
120
224
  value: any;
121
- uniqueValues: any;
122
- minMax: any;
123
- columnLabel: any;
124
- setFilter: any;
225
+ uniqueValues: any[];
226
+ minMax: [number, number] | null;
227
+ columnLabel: string;
228
+ setFilter: (columnId: string, value: any) => void;
125
229
  }
126
230
 
127
231
  interface RozieCellSlotCtx {
128
- columnId: any;
129
- column: any;
232
+ columnId: string;
233
+ column: TableColumn<any, unknown>;
130
234
  row: any;
131
235
  value: any;
132
236
  }
133
237
 
134
238
  interface RozieEditorSlotCtx {
135
- columnId: any;
136
- column: any;
239
+ columnId: string;
240
+ column: TableColumn<any, unknown>;
137
241
  row: any;
138
242
  value: any;
139
- commit: any;
140
- cancel: any;
141
- columnLabel: any;
142
- autofocus: any;
243
+ commit: (value: any) => void;
244
+ cancel: () => void;
245
+ columnLabel: string;
246
+ autofocus: boolean;
143
247
  }
144
248
 
145
249
  @customElement('rozie-data-table')
@@ -768,8 +872,10 @@ export default class DataTable extends SignalWatcher(LitElement) {
768
872
  private _liveAnnounce = signal('');
769
873
  @query('[data-rozie-ref="__rozieRoot"]') private _ref__rozieRoot!: HTMLElement;
770
874
  private __rozieWatchInitial_0 = true;
875
+ private __rozieWatchPrev_1: unknown;
771
876
  private __rozieWatchInitial_1 = true;
772
877
  private __rozieWatchInitial_2 = true;
878
+ private __rozieFirstUpdateDone = false;
773
879
  private __rozieCtxProvider_data_table_columns = new ContextProvider(this, { context: __rozieCtx_data_table_columns, initialValue: ((__rozieCtxHost) => ({
774
880
  registerColumn: (id: any, spec: any) => {
775
881
  if (id == null) return;
@@ -806,20 +912,20 @@ private __rozieCtxProvider_data_table_columns = new ContextProvider(this, { cont
806
912
  @queryAssignedElements({ flatten: true }) private _slotDefaultElements!: Element[];
807
913
  @state() private _hasSlotGroupBar = false;
808
914
  @queryAssignedElements({ slot: 'groupBar', flatten: true }) private _slotGroupBarElements!: Element[];
809
- @property({ attribute: false }) groupBar?: (scope: { grouping: any; groupableColumns: any; applyGrouping: any; clearGrouping: any }) => unknown;
915
+ @property({ attribute: false }) groupBar?: (scope: { grouping: string[]; groupableColumns: DataTableGroupableColumn[]; applyGrouping: (cols: string[]) => void; clearGrouping: () => void }) => unknown;
810
916
  @state() private _hasSlotSelectAll = false;
811
917
  @queryAssignedElements({ slot: 'selectAll', flatten: true }) private _slotSelectAllElements!: Element[];
812
- @property({ attribute: false }) selectAll?: (scope: { checked: any; indeterminate: any; toggle: any }) => unknown;
918
+ @property({ attribute: false }) selectAll?: (scope: { checked: boolean; indeterminate: boolean; toggle: (event: any) => void }) => unknown;
813
919
  @state() private _hasSlotDynamicColHeader = false;
814
920
  @queryAssignedElements({ flatten: true }) private _slotDynamicColHeaderElements!: Element[];
815
921
  @state() private _hasSlotDynamicFilter = false;
816
922
  @queryAssignedElements({ flatten: true }) private _slotDynamicFilterElements!: Element[];
817
923
  @state() private _hasSlotPlaceholder = false;
818
924
  @queryAssignedElements({ slot: 'placeholder', flatten: true }) private _slotPlaceholderElements!: Element[];
819
- @property({ attribute: false }) placeholder?: (scope: { index: any; columnId: any }) => unknown;
925
+ @property({ attribute: false }) placeholder?: (scope: { index: number; columnId: string }) => unknown;
820
926
  @state() private _hasSlotSelectCell = false;
821
927
  @queryAssignedElements({ slot: 'selectCell', flatten: true }) private _slotSelectCellElements!: Element[];
822
- @property({ attribute: false }) selectCell?: (scope: { row: any; checked: any; toggle: any }) => unknown;
928
+ @property({ attribute: false }) selectCell?: (scope: { row: any; checked: boolean; toggle: (event: any) => void }) => unknown;
823
929
  @state() private _hasSlotDynamicCell = false;
824
930
  @queryAssignedElements({ flatten: true }) private _slotDynamicCellElements!: Element[];
825
931
  @state() private _hasSlotDynamicEditor = false;
@@ -829,16 +935,16 @@ private __rozieCtxProvider_data_table_columns = new ContextProvider(this, { cont
829
935
  @property({ attribute: false }) detail?: (scope: { row: any }) => unknown;
830
936
  @state() private _hasSlotColHeader = false;
831
937
  @queryAssignedElements({ slot: 'colHeader', flatten: true }) private _slotColHeaderElements!: Element[];
832
- @property({ attribute: false }) colHeader?: (scope: { columnId: any; column: any; label: any }) => unknown;
938
+ @property({ attribute: false }) colHeader?: (scope: { columnId: string; column: TableColumn<any, unknown>; label: string }) => unknown;
833
939
  @state() private _hasSlotFilter = false;
834
940
  @queryAssignedElements({ slot: 'filter', flatten: true }) private _slotFilterElements!: Element[];
835
- @property({ attribute: false }) filter?: (scope: { columnId: any; value: any; uniqueValues: any; minMax: any; columnLabel: any; setFilter: any }) => unknown;
941
+ @property({ attribute: false }) filter?: (scope: { columnId: string; value: any; uniqueValues: any[]; minMax: [number, number] | null; columnLabel: string; setFilter: (columnId: string, value: any) => void }) => unknown;
836
942
  @state() private _hasSlotCell = false;
837
943
  @queryAssignedElements({ slot: 'cell', flatten: true }) private _slotCellElements!: Element[];
838
- @property({ attribute: false }) cell?: (scope: { columnId: any; column: any; row: any; value: any }) => unknown;
944
+ @property({ attribute: false }) cell?: (scope: { columnId: string; column: TableColumn<any, unknown>; row: any; value: any }) => unknown;
839
945
  @state() private _hasSlotEditor = false;
840
946
  @queryAssignedElements({ slot: 'editor', flatten: true }) private _slotEditorElements!: Element[];
841
- @property({ attribute: false }) editor?: (scope: { columnId: any; column: any; row: any; value: any; commit: any; cancel: any; columnLabel: any; autofocus: any }) => unknown;
947
+ @property({ attribute: false }) editor?: (scope: { columnId: string; column: TableColumn<any, unknown>; row: any; value: any; commit: (value: any) => void; cancel: () => void; columnLabel: string; autofocus: boolean }) => unknown;
842
948
  // Phase 79 Plan 08 (R4) contract for 79-09: the record intake for
843
949
  // record-routed slot fills. 79-09's consumer-side emitSlotFiller
844
950
  // accumulates an object literal onto the SAME `.rozieSlots=${{ ... }}`
@@ -848,7 +954,7 @@ private __rozieCtxProvider_data_table_columns = new ContextProvider(this, { cont
848
954
  // named function-prop / <slot> fallback (AC-9). Attribute
849
955
  // deserialization is disabled — this is a function-valued record,
850
956
  // never reflected to/from an HTML attribute.
851
- @property({ attribute: false }) rozieSlots?: { [key: `colHeader-${string}`]: (scope: { columnId: any; column: any; label: any }) => unknown; [key: `filter-${string}`]: (scope: { columnId: any; value: any; uniqueValues: any; minMax: any; columnLabel: any; setFilter: any }) => unknown; [key: `cell-${string}`]: (scope: { columnId: any; column: any; row: any; value: any }) => unknown; [key: `editor-${string}`]: (scope: { columnId: any; column: any; row: any; value: any; commit: any; cancel: any; columnLabel: any; autofocus: any }) => unknown; } & Record<string, (scope: any) => unknown>;
957
+ @property({ attribute: false }) rozieSlots?: { [key: `colHeader-${string}`]: (scope: { columnId: string; column: TableColumn<any, unknown>; label: string }) => unknown; [key: `filter-${string}`]: (scope: { columnId: string; value: any; uniqueValues: any[]; minMax: [number, number] | null; columnLabel: string; setFilter: (columnId: string, value: any) => void }) => unknown; [key: `cell-${string}`]: (scope: { columnId: string; column: TableColumn<any, unknown>; row: any; value: any }) => unknown; [key: `editor-${string}`]: (scope: { columnId: string; column: TableColumn<any, unknown>; row: any; value: any; commit: (value: any) => void; cancel: () => void; columnLabel: string; autofocus: boolean }) => unknown; } & Record<string, (scope: any) => unknown>;
852
958
 
853
959
  private _disconnectCleanups: Array<() => void> = [];
854
960
  // Re-parenting guard: set true once the deferred teardown has actually
@@ -1048,7 +1154,7 @@ private __rozieCtxProvider_data_table_columns = new ContextProvider(this, { cont
1048
1154
  // columnDefs()/tableColumns() build the UNION of both, and reFeed re-passes columns.
1049
1155
  // (Consumers memoize the array as with $props.data/$props.sorting; the uncontrolled
1050
1156
  // <Column>-children path leaves $props.columns undefined — a stable no-op getter.)
1051
- this.columns, this._colReg.value])(); untracked(() => { if (this.__rozieWatchInitial_1) { this.__rozieWatchInitial_1 = false; return; } (() => {
1157
+ this.columns, this._colReg.value])(); untracked(() => { this.__rozieWatchPrev_1 = __watchVal; if (this.__rozieWatchInitial_1) { this.__rozieWatchInitial_1 = false; return; } (() => {
1052
1158
  // Seed BEFORE the re-feed so the pinning this call may write is part of the state object
1053
1159
  // table-core receives on this same tick (rather than landing a frame late and re-ordering
1054
1160
  // getVisibleCells() a render after the columns appear). One-shot and self-latching — see
@@ -1104,15 +1210,8 @@ private __rozieCtxProvider_data_table_columns = new ContextProvider(this, { cont
1104
1210
  // re-ordering a render later. Covers the `:columns` config-array form, which is available
1105
1211
  // now; `<Column>` children register during mount and are covered by the re-feed watch's own
1106
1212
  // call. One-shot and self-latching — see seedColumnPinning() in stateAssembly.rzts.
1107
- // Apply any per-column `pinned` declaration as the INITIAL pinning state BEFORE createTable
1108
- // reads currentState() below, so the first row model table-core builds is already correctly
1109
- // ordered (getVisibleCells() returns [left-pinned, center, right-pinned]) rather than
1110
- // re-ordering a render later. Covers the `:columns` config-array form, which is available
1111
- // now; `<Column>` children register during mount and are covered by the re-feed watch's own
1112
- // call. One-shot and self-latching — see seedColumnPinning() in stateAssembly.rzts.
1113
1213
  this.seedColumnPinning();
1114
1214
  // Build the table instance HERE so the closures below capture the live `table`.
1115
- // Build the table instance HERE so the closures below capture the live `table`.
1116
1215
  this.table = createTable({
1117
1216
  // Plain value (NOT a `get data()` getter): an object-literal getter rebinds
1118
1217
  // `this` to the options object, and the Angular/Lit emitters resolve $props via
@@ -1295,14 +1394,9 @@ private __rozieCtxProvider_data_table_columns = new ContextProvider(this, { cont
1295
1394
  if (typeof queueMicrotask !== 'undefined') queueMicrotask(this.syncIndeterminate);else Promise.resolve().then(this.syncIndeterminate);
1296
1395
  };
1297
1396
 
1298
- // initial pull
1299
1397
  // initial pull
1300
1398
  this.refreshRowModel();
1301
1399
 
1302
- // ── Grid mode: capture the table root ──────────────────────────────────────────────
1303
- // $el is the component root; the <table class="rozie-data-table"> is the grid root the
1304
- // cell selectors hang off (the exact idiom proven ×6 by plan 01's probe). Captured here
1305
- // (post-mount) so it is non-null and ROZ123-clean.
1306
1400
  // ── Grid mode: capture the table root ──────────────────────────────────────────────
1307
1401
  // $el is the component root; the <table class="rozie-data-table"> is the grid root the
1308
1402
  // cell selectors hang off (the exact idiom proven ×6 by plan 01's probe). Captured here
@@ -1311,9 +1405,6 @@ private __rozieCtxProvider_data_table_columns = new ContextProvider(this, { cont
1311
1405
  // B-02: the click-away detector. `composedPath()` rather than `ev.target` because on Lit the
1312
1406
  // grid lives in a shadow root and a document-level listener sees the retargeted HOST element,
1313
1407
  // which is never `gridRoot.contains(...)` — every in-grid click would read as "outside".
1314
- // B-02: the click-away detector. `composedPath()` rather than `ev.target` because on Lit the
1315
- // grid lives in a shadow root and a document-level listener sees the retargeted HOST element,
1316
- // which is never `gridRoot.contains(...)` — every in-grid click would read as "outside".
1317
1408
  if (typeof document !== 'undefined') {
1318
1409
  this.docPointerDown = (ev: any) => {
1319
1410
  if (!this.gridRoot) {
@@ -1332,20 +1423,6 @@ private __rozieCtxProvider_data_table_columns = new ContextProvider(this, { cont
1332
1423
  // cell IS the first Tab-in target (matching the Wave-0 probe's "no auto-focus on
1333
1424
  // mount"); the consumer drives focus by Tabbing/clicking in, never the component.
1334
1425
 
1335
- // ── Windowing: capture the scroll element + construct the row virtualizer (req-1/2 — the
1336
- // row virtual-core instance ONLY when rows are windowed; the scroll-element capture whenever
1337
- // EITHER axis is windowed, since both axes observe the SAME .rdt-scroll element — D-03). Built
1338
- // HERE (post-mount) so getScrollElement resolves the rendered .rdt-scroll div and
1339
- // getPrePaginationRowModel reads the live table. ENTIRELY inside the isWindowed()/rowsWindowed()
1340
- // guards: when both axes are off, NO virtual-core runtime code executes (byte-identical-off).
1341
- // _didMount() registers the scroll-element ResizeObserver and returns the teardown stored for
1342
- // $onUnmount.
1343
- // WR-04: NO on-mount auto-focus of the entry cell. Auto-focusing here stole focus on
1344
- // page load AND was non-deterministic on React/Solid (the entry cell may not be
1345
- // committed to the DOM yet at the $onMount microtask). The roving tabindex="0" entry
1346
- // cell IS the first Tab-in target (matching the Wave-0 probe's "no auto-focus on
1347
- // mount"); the consumer drives focus by Tabbing/clicking in, never the component.
1348
-
1349
1426
  // ── Windowing: capture the scroll element + construct the row virtualizer (req-1/2 — the
1350
1427
  // row virtual-core instance ONLY when rows are windowed; the scroll-element capture whenever
1351
1428
  // EITHER axis is windowed, since both axes observe the SAME .rdt-scroll element — D-03). Built
@@ -1364,9 +1441,6 @@ private __rozieCtxProvider_data_table_columns = new ContextProvider(this, { cont
1364
1441
  // Phase 87 87-04: construct the SECOND, horizontal Virtualizer instance when the COLUMN
1365
1442
  // axis is windowed — entirely inside its own colsWindowed() guard, byte-identical-off for
1366
1443
  // 'rows'/true/false.
1367
- // Phase 87 87-04: construct the SECOND, horizontal Virtualizer instance when the COLUMN
1368
- // axis is windowed — entirely inside its own colsWindowed() guard, byte-identical-off for
1369
- // 'rows'/true/false.
1370
1444
  if (this.colsWindowed()) {
1371
1445
  this.colVirtualizer = new Virtualizer(this.columnVirtualizerOptions());
1372
1446
  this.colVirtualizerCleanup = this.colVirtualizer._didMount();
@@ -1389,24 +1463,6 @@ private __rozieCtxProvider_data_table_columns = new ContextProvider(this, { cont
1389
1463
  // idiom lowers to a functional React setState update and is safe regardless of ordering, the
1390
1464
  // same reasoning virtualizerOptions()'s onChange documents), but collapsing to one bump avoids
1391
1465
  // manufacturing the false-positive warning at all rather than accepting it.
1392
- // FINE-GRAINED FIRST-WINDOW KICK (Solid/Svelte), either axis: the windowed <For>/{#each}
1393
- // accessor was first evaluated at initial render — while `virtualizer`/`colVirtualizer` were
1394
- // still null — and (because windowedRows()/windowedColIndices() read $data.windowVer up top)
1395
- // subscribed to windowVer then returned []/the full set. Both `let`s are non-reactive, so
1396
- // their assignment above does NOT notify the accessor; we must bump the SIGNAL it subscribed
1397
- // to. _didMount() computes the first window synchronously but its onChange only fires on
1398
- // SUBSEQUENT scroll/resize, so without this explicit bump the first window would never paint
1399
- // on the fine-grained targets. Idempotent + harmless on the coarse targets (they re-render
1400
- // wholesale anyway). ONE bump here (not one per axis, Rule 1 — see the ROZ138 note below)
1401
- // covers both: windowedRows()/windowedColIndices() each simply re-derive against their own
1402
- // now-non-null virtualizer instance off the SAME counter (87-02 checkpoint decision (b): no
1403
- // separate $data.colWindowVer). Gated on isWindowed() (either axis) rather than two separate
1404
- // per-axis bumps: a second `$data.windowVer = $data.windowVer + 1` write later in this SAME
1405
- // $onMount body would dominate-flag ROZ138 (React stale-read) on its own right-hand-side read
1406
- // of the counter it had just written a few lines above — a false positive (this increment
1407
- // idiom lowers to a functional React setState update and is safe regardless of ordering, the
1408
- // same reasoning virtualizerOptions()'s onChange documents), but collapsing to one bump avoids
1409
- // manufacturing the false-positive warning at all rather than accepting it.
1410
1466
  if (this.isWindowed()) {
1411
1467
  this._windowVer.value = this._windowVer.value + 1;
1412
1468
  }
@@ -1416,12 +1472,6 @@ private __rozieCtxProvider_data_table_columns = new ContextProvider(this, { cont
1416
1472
  // row-specific measurement + warns stay further guarded by rowsWindowed() (byte-identical to
1417
1473
  // before this restructure); the column-axis warn (Phase 87 D-07(a)) is guarded by
1418
1474
  // colsWindowed() so it can fire ONLY on the 'columns'/'both' path, never on 'rows'/true/false.
1419
- // After the first window commits (next frame), refine heights + fire the dev-mode warns ONCE.
1420
- // Entirely inside the isWindowed() guard so the off (neither axis windowed) emitted path adds
1421
- // NO code and these warns can never fire there (req-1 byte-identical-off preserved). The
1422
- // row-specific measurement + warns stay further guarded by rowsWindowed() (byte-identical to
1423
- // before this restructure); the column-axis warn (Phase 87 D-07(a)) is guarded by
1424
- // colsWindowed() so it can fire ONLY on the 'columns'/'both' path, never on 'rows'/true/false.
1425
1475
  if (this.isWindowed()) {
1426
1476
  const afterFirstFrame = () => {
1427
1477
  if (this.rowsWindowed()) {
@@ -1488,9 +1538,6 @@ private __rozieCtxProvider_data_table_columns = new ContextProvider(this, { cont
1488
1538
  if (typeof requestAnimationFrame === 'function') requestAnimationFrame(() => requestAnimationFrame(afterFirstFrame));else setTimeout(afterFirstFrame, 0);
1489
1539
  }
1490
1540
 
1491
- // #14: seed the sort/filter announce baseline from the initial (post-mount) state so the LAZY
1492
- // watch's first fire — a real user sort/filter — compares against the true starting values and
1493
- // is classified correctly (a null sentinel would misread the first filter change as a sort change).
1494
1541
  // #14: seed the sort/filter announce baseline from the initial (post-mount) state so the LAZY
1495
1542
  // watch's first fire — a real user sort/filter — compares against the true starting values and
1496
1543
  // is classified correctly (a null sentinel would misread the first filter change as a sort change).
@@ -1500,41 +1547,55 @@ private __rozieCtxProvider_data_table_columns = new ContextProvider(this, { cont
1500
1547
  }
1501
1548
 
1502
1549
  updated(changedProperties: Map<string, unknown>): void {
1503
- this.maybeClearHistoryOnExternalSwap();
1504
- // visible-range-change backstop (quick 260925-dtl part 2): a re-render from new data can move
1505
- // the rendered window WITHOUT a window-version bump (measured on React: filling placeholders
1506
- // shifted the window by two rows and nothing was reported). Deduped, so a render that leaves
1507
- // the window where it was emits nothing.
1508
- // visible-range-change backstop (quick 260925-dtl part 2): a re-render from new data can move
1509
- // the rendered window WITHOUT a window-version bump (measured on React: filling placeholders
1510
- // shifted the window by two rows and nothing was reported). Deduped, so a render that leaves
1511
- // the window where it was emits nothing.
1512
- this.emitVisibleRangeIfChanged();
1513
- // A-02: the post-render hook is the one place that observes EVERY column-width change on all
1514
- // six targets — an interactive resize writes `columnSizing` through table-core under
1515
- // `columnResizeMode: 'onChange'` and never reaches the coarse re-feed watch (which keys on
1516
- // `$props.columnSizing`, undefined on the uncontrolled path). Self-guarded on the width
1517
- // signature, so a tick that changed no width costs one loop over the leaf columns.
1518
- // A-02: the post-render hook is the one place that observes EVERY column-width change on all
1519
- // six targets — an interactive resize writes `columnSizing` through table-core under
1520
- // `columnResizeMode: 'onChange'` and never reaches the coarse re-feed watch (which keys on
1521
- // `$props.columnSizing`, undefined on the uncontrolled path). Self-guarded on the width
1522
- // signature, so a tick that changed no width costs one loop over the leaf columns.
1523
- this.remeasureColumnSizes();
1524
- if (!this.table) return;
1525
- // Phase 51 req-4: track currentData() (the bound prop OR the uncontrolled
1526
- // $data.dataDefault) so a committed edit re-feeds on Lit whether or not the two-way
1527
- // `data` model is bound. Compare by reference AND length so a same-length single-cell edit (fresh array,
1528
- // identical length) still re-feeds.
1529
- // Phase 51 req-4: track currentData() (the bound prop OR the uncontrolled
1530
- // $data.dataDefault) so a committed edit re-feeds on Lit whether or not the two-way
1531
- // `data` model is bound. Compare by reference AND length so a same-length single-cell edit (fresh array,
1532
- // identical length) still re-feeds.
1533
- const d = this.currentData() || [];
1534
- if (d === this.lastData && d.length === this.lastDataLen) return;
1535
- this.lastData = d;
1536
- this.lastDataLen = d.length;
1537
- this.reFeed();
1550
+ if (this.__rozieFirstUpdateDone && (changedProperties.has('rowCount') || changedProperties.has('pageCount') || changedProperties.has('expandable') || changedProperties.has('groupable') || changedProperties.has('selectionMode') || changedProperties.has('columns'))) { const __watchVal = untracked(() => [this.sorting, this.globalFilter, this.columnFilters, this.pagination, // Server-side page-count sources (#2): re-feed when the consumer's rowCount/pageCount
1551
+ // changes at runtime (e.g. a server response updates the total) so getPageCount() and the
1552
+ // Next button availability track the new total.
1553
+ this.rowCount, this.pageCount, this.rowSelection, this.expanded, this.expandable, this.grouping, this.groupable, this.columnVisibility, this.columnSizing, this.columnOrder, this.columnPinning, this.selectionMode, (this.data || []).length, // Phase 51 req-4: key on the data REFERENCE (both sinks) so a committed edit re-feeds
1554
+ // even when the fresh array is the SAME length (a single-cell edit replaces one row
1555
+ // object → new array ref, identical length → the .length key alone would miss it). The
1556
+ // controlled path observes $props.data; the uncontrolled path observes $data.dataDefault.
1557
+ // writeData is echo-guarded (programmatic) and reFeed writes neither sink, so no loop.
1558
+ this.data, this._dataDefault.value, // Column CONFIG prop (the `:columns` array form) — the sibling column source to
1559
+ // $data.colReg (the `<Column>` children). Watch it so a runtime `:columns` swap re-feeds:
1560
+ // columnDefs()/tableColumns() build the UNION of both, and reFeed re-passes columns.
1561
+ // (Consumers memoize the array as with $props.data/$props.sorting; the uncontrolled
1562
+ // <Column>-children path leaves $props.columns undefined — a stable no-op getter.)
1563
+ this.columns, this._colReg.value]); if (!Object.is(__watchVal, this.__rozieWatchPrev_1)) { this.__rozieWatchPrev_1 = __watchVal; untracked(() => (() => {
1564
+ // Seed BEFORE the re-feed so the pinning this call may write is part of the state object
1565
+ // table-core receives on this same tick (rather than landing a frame late and re-ordering
1566
+ // getVisibleCells() a render after the columns appear). One-shot and self-latching — see
1567
+ // seedColumnPinning() in stateAssembly.rzts. This is the `<Column>`-children entry point:
1568
+ // those register during mount, so the $onMount call below can find zero columns.
1569
+ this.seedColumnPinning();
1570
+ this.reFeed();
1571
+ this.maybeClearHistoryOnExternalSwap();
1572
+ })()); } }
1573
+ this.__rozieFirstUpdateDone = true;
1574
+
1575
+ (() => {
1576
+ this.maybeClearHistoryOnExternalSwap();
1577
+ // visible-range-change backstop (quick 260925-dtl part 2): a re-render from new data can move
1578
+ // the rendered window WITHOUT a window-version bump (measured on React: filling placeholders
1579
+ // shifted the window by two rows and nothing was reported). Deduped, so a render that leaves
1580
+ // the window where it was emits nothing.
1581
+ this.emitVisibleRangeIfChanged();
1582
+ // A-02: the post-render hook is the one place that observes EVERY column-width change on all
1583
+ // six targets — an interactive resize writes `columnSizing` through table-core under
1584
+ // `columnResizeMode: 'onChange'` and never reaches the coarse re-feed watch (which keys on
1585
+ // `$props.columnSizing`, undefined on the uncontrolled path). Self-guarded on the width
1586
+ // signature, so a tick that changed no width costs one loop over the leaf columns.
1587
+ this.remeasureColumnSizes();
1588
+ if (!this.table) return;
1589
+ // Phase 51 req-4: track currentData() (the bound prop OR the uncontrolled
1590
+ // $data.dataDefault) so a committed edit re-feeds on Lit whether or not the two-way
1591
+ // `data` model is bound. Compare by reference AND length so a same-length single-cell edit (fresh array,
1592
+ // identical length) still re-feeds.
1593
+ const d = this.currentData() || [];
1594
+ if (d === this.lastData && d.length === this.lastDataLen) return;
1595
+ this.lastData = d;
1596
+ this.lastDataLen = d.length;
1597
+ this.reFeed();
1598
+ })();
1538
1599
  }
1539
1600
 
1540
1601
  disconnectedCallback(): void {
@@ -2458,7 +2519,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
2458
2519
  this.programmatic++;
2459
2520
  this._sortingDefault.value = next; // fresh array only (never in-place)
2460
2521
  this._sortingControllable.write(next); // two-way emit if bound (no-op-diff if not)
2461
- this.dispatchEvent(new CustomEvent("sort-change", {
2522
+ this.dispatchEvent(new CustomEvent<SortingState>("sort-change", {
2462
2523
  detail: next,
2463
2524
  bubbles: true,
2464
2525
  composed: true
@@ -2488,7 +2549,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
2488
2549
  // → duplicate-identifier TS2300 (the model-prop==emit-name collision class). Every
2489
2550
  // sibling slice avoids this by stemming the event off a DISTINCT name (sorting→
2490
2551
  // sort-change, rowSelection→selection-change); `expanded`→`expand-change` follows suit.
2491
- this.dispatchEvent(new CustomEvent("expand-change", {
2552
+ this.dispatchEvent(new CustomEvent<ExpandedState>("expand-change", {
2492
2553
  detail: next,
2493
2554
  bubbles: true,
2494
2555
  composed: true
@@ -2512,7 +2573,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
2512
2573
  this.programmatic++;
2513
2574
  this._groupingDefault.value = next; // fresh ordered array only (never in-place push)
2514
2575
  this._groupingControllable.write(next); // two-way emit if bound (no-op-diff if not)
2515
- this.dispatchEvent(new CustomEvent("group-change", {
2576
+ this.dispatchEvent(new CustomEvent<GroupingState>("group-change", {
2516
2577
  detail: next,
2517
2578
  bubbles: true,
2518
2579
  composed: true
@@ -2528,7 +2589,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
2528
2589
  this.programmatic++;
2529
2590
  this._globalFilterDefault.value = next;
2530
2591
  this._globalFilterControllable.write(next);
2531
- this.dispatchEvent(new CustomEvent("filter-change", {
2592
+ this.dispatchEvent(new CustomEvent<DataTableFilterChangePayload>("filter-change", {
2532
2593
  detail: {
2533
2594
  globalFilter: next
2534
2595
  },
@@ -2547,7 +2608,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
2547
2608
  this.programmatic++;
2548
2609
  this._columnFiltersDefault.value = next;
2549
2610
  this._columnFiltersControllable.write(next);
2550
- this.dispatchEvent(new CustomEvent("filter-change", {
2611
+ this.dispatchEvent(new CustomEvent<DataTableFilterChangePayload>("filter-change", {
2551
2612
  detail: {
2552
2613
  columnFilters: next
2553
2614
  },
@@ -2564,7 +2625,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
2564
2625
  this.programmatic++;
2565
2626
  this._paginationDefault.value = next;
2566
2627
  this._paginationControllable.write(next);
2567
- this.dispatchEvent(new CustomEvent("page-change", {
2628
+ this.dispatchEvent(new CustomEvent<PaginationState>("page-change", {
2568
2629
  detail: next,
2569
2630
  bubbles: true,
2570
2631
  composed: true
@@ -2580,7 +2641,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
2580
2641
  this.programmatic++;
2581
2642
  this._rowSelectionDefault.value = next;
2582
2643
  this._rowSelectionControllable.write(next);
2583
- this.dispatchEvent(new CustomEvent("selection-change", {
2644
+ this.dispatchEvent(new CustomEvent<RowSelectionState>("selection-change", {
2584
2645
  detail: next,
2585
2646
  bubbles: true,
2586
2647
  composed: true
@@ -2596,7 +2657,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
2596
2657
  this.programmatic++;
2597
2658
  this._columnVisibilityDefault.value = next;
2598
2659
  this._columnVisibilityControllable.write(next);
2599
- this.dispatchEvent(new CustomEvent("visibility-change", {
2660
+ this.dispatchEvent(new CustomEvent<VisibilityState>("visibility-change", {
2600
2661
  detail: next,
2601
2662
  bubbles: true,
2602
2663
  composed: true
@@ -2612,7 +2673,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
2612
2673
  this.programmatic++;
2613
2674
  this._columnSizingDefault.value = next;
2614
2675
  this._columnSizingControllable.write(next);
2615
- this.dispatchEvent(new CustomEvent("resize-change", {
2676
+ this.dispatchEvent(new CustomEvent<ColumnSizingState>("resize-change", {
2616
2677
  detail: next,
2617
2678
  bubbles: true,
2618
2679
  composed: true
@@ -2628,7 +2689,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
2628
2689
  this.programmatic++;
2629
2690
  this._columnOrderDefault.value = next;
2630
2691
  this._columnOrderControllable.write(next);
2631
- this.dispatchEvent(new CustomEvent("reorder-change", {
2692
+ this.dispatchEvent(new CustomEvent<ColumnOrderState>("reorder-change", {
2632
2693
  detail: next,
2633
2694
  bubbles: true,
2634
2695
  composed: true
@@ -2655,7 +2716,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
2655
2716
  this.programmatic++;
2656
2717
  this._columnPinningDefault.value = clean;
2657
2718
  this._columnPinningControllable.write(clean);
2658
- this.dispatchEvent(new CustomEvent("pin-change", {
2719
+ this.dispatchEvent(new CustomEvent<ColumnPinningState>("pin-change", {
2659
2720
  detail: clean,
2660
2721
  bubbles: true,
2661
2722
  composed: true
@@ -2767,13 +2828,13 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
2767
2828
  this.redoStack = [];
2768
2829
  };
2769
2830
 
2770
- canUndo = () => this.undoStack.length > 0;
2831
+ canUndo: () => boolean = () => this.undoStack.length > 0;
2771
2832
 
2772
- canRedo = () => this.redoStack.length > 0;
2833
+ canRedo: () => boolean = () => this.redoStack.length > 0;
2773
2834
 
2774
2835
  // Both stacks empty — the external-swap latch (DataTable.rozie reFeed) and the
2775
2836
  // clearHistory() $expose verb share this single implementation.
2776
- clearHistory = () => {
2837
+ clearHistory: () => void = () => {
2777
2838
  this.undoStack = [];
2778
2839
  this.redoStack = [];
2779
2840
  };
@@ -2783,7 +2844,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
2783
2844
  // per call per the design — NOT edge-gated there; only the writeData-triggered recording path
2784
2845
  // below is edge-gated, since a routine sequence of edits would otherwise spam the event).
2785
2846
  emitHistoryChange = () => {
2786
- this.dispatchEvent(new CustomEvent("history-change", {
2847
+ this.dispatchEvent(new CustomEvent<DataTableHistoryChangePayload>("history-change", {
2787
2848
  detail: {
2788
2849
  canUndo: this.canUndo(),
2789
2850
  canRedo: this.canRedo()
@@ -2809,7 +2870,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
2809
2870
  // re-capture this replay (which would corrupt the stack). Replaying through writeData
2810
2871
  // (rather than writing $data/$model directly) is deliberate: the two-way $model.data
2811
2872
  // writeback, the re-feed $watch, and the echo guard all keep working with zero new code.
2812
- undo = () => {
2873
+ undo: () => void = () => {
2813
2874
  if (!this.canUndo()) return;
2814
2875
  const prev = this.undoStack.pop();
2815
2876
  this.redoStack.push(this.currentData());
@@ -2821,7 +2882,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
2821
2882
 
2822
2883
  // redo(): symmetric — pop the redo stack, push the CURRENT data back onto the undo stack,
2823
2884
  // replay through the same guarded writeData seam.
2824
- redo = () => {
2885
+ redo: () => void = () => {
2825
2886
  if (!this.canRedo()) return;
2826
2887
  const next = this.redoStack.pop();
2827
2888
  this.undoStack.push(this.currentData());
@@ -3410,7 +3471,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
3410
3471
  if (start === this.lastRangeStart && end === this.lastRangeEnd) return;
3411
3472
  this.lastRangeStart = start;
3412
3473
  this.lastRangeEnd = end;
3413
- this.dispatchEvent(new CustomEvent("visible-range-change", {
3474
+ this.dispatchEvent(new CustomEvent<DataTableVisibleRangeChangePayload>("visible-range-change", {
3414
3475
  detail: {
3415
3476
  start,
3416
3477
  end
@@ -3447,7 +3508,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
3447
3508
  // into the internal `.rdt-scroll` class selector. Returns null when nothing is windowed:
3448
3509
  // gridScrollEl is only ever assigned inside the isWindowed() guard, so no extra gate is
3449
3510
  // needed here beyond that natural null.
3450
- getScrollElement = (): any => this.gridScrollEl;
3511
+ getScrollElement: () => HTMLElement | null = (): any => this.gridScrollEl;
3451
3512
 
3452
3513
  // ══ Generic vertical windowing math (Phase 64, D-04) — the target-agnostic virtual-core bridge ══
3453
3514
  // Lifted verbatim from the DataTable virtualization.rzts (the Phase 53/63 B13 baseline). This partial
@@ -5024,39 +5085,39 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
5024
5085
  // is dropped on ALL SIX targets, only the by-reference key survives → a
5025
5086
  // runtime ReferenceError at `defineExpose`/`useImperativeHandle`). Sorting verbs +
5026
5087
  // a fresh column-def readout, selection, pagination, and column-management verbs.
5027
- sortColumn = (colId: any, desc: any) => {
5088
+ sortColumn: (colId: string, desc?: boolean) => void = (colId: any, desc: any) => {
5028
5089
  if (this.table) this.table.getColumn(colId) && this.table.getColumn(colId).toggleSorting(desc, false);
5029
5090
  };
5030
5091
 
5031
- clearSorting = () => {
5092
+ clearSorting: () => void = () => {
5032
5093
  if (this.table) this.table.resetSorting(true);
5033
5094
  };
5034
5095
 
5035
- getColumnDefs = () => this.columnDefs();
5096
+ getColumnDefs: () => ColumnDef<any, any>[] = () => this.columnDefs();
5036
5097
 
5037
5098
  // selection verbs (req-7) — drive table-core so the onRowSelectionChange funnel
5038
5099
  // emits the fresh state + selection-change.
5039
- toggleAllRows = (value: any) => {
5100
+ toggleAllRows: (value?: boolean) => void = (value: any) => {
5040
5101
  if (this.table) this.table.toggleAllRowsSelected(value);
5041
5102
  };
5042
5103
 
5043
- clearSelection = () => {
5104
+ clearSelection: () => void = () => {
5044
5105
  if (this.table) this.table.resetRowSelection(true);
5045
5106
  };
5046
5107
 
5047
- getSelectedRows = () => this.table ? this.table.getSelectedRowModel().rows.map((r: any) => r.original) : [];
5108
+ getSelectedRows: () => any[] = () => this.table ? this.table.getSelectedRowModel().rows.map((r: any) => r.original) : [];
5048
5109
 
5049
5110
  // pagination verbs.
5050
- setPage = (idx: any) => {
5111
+ setPage: (idx: number) => void = (idx: any) => {
5051
5112
  if (this.table) this.table.setPageIndex(idx);
5052
5113
  };
5053
5114
 
5054
- setRowsPerPage = (size: any) => {
5115
+ setRowsPerPage: (size: number) => void = (size: any) => {
5055
5116
  if (this.table) this.table.setPageSize(size);
5056
5117
  };
5057
5118
 
5058
5119
  // column-management verbs (req-8/9/10/11) — drive table-core so the funnels fire.
5059
- toggleColumnVisibility = (colId: any) => {
5120
+ toggleColumnVisibility: (colId: string) => void = (colId: any) => {
5060
5121
  if (this.table) {
5061
5122
  const c = this.table.getColumn(colId);
5062
5123
  if (c && c.toggleVisibility) c.toggleVisibility();
@@ -5070,17 +5131,17 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
5070
5131
  // off-limits). So the public verb is `applyColumnOrder` (semantically: apply a
5071
5132
  // new column order). The other set* verbs (setPage/setRowsPerPage) do NOT match
5072
5133
  // any model prop's setter, so they are collision-free.
5073
- applyColumnOrder = (order: any) => {
5134
+ applyColumnOrder: (order: string[]) => void = (order: any) => {
5074
5135
  if (this.table) this.table.setColumnOrder(order);
5075
5136
  };
5076
5137
 
5077
- resetColumnSizing = () => {
5138
+ resetColumnSizing: () => void = () => {
5078
5139
  if (this.table) this.table.resetColumnSizing(true);
5079
5140
  };
5080
5141
 
5081
5142
  // pinColumn: the verb that drives column.pin; distinct from the template handler
5082
5143
  // onPinColumn (no shadow — the deferred-items finding #4 collision check).
5083
- pinColumn = (colId: any, side: any) => {
5144
+ pinColumn: (colId: string, side: 'left' | 'right' | false) => void = (colId: any, side: any) => {
5084
5145
  if (this.table) {
5085
5146
  const c = this.table.getColumn(colId);
5086
5147
  if (c && c.pin) c.pin(side);
@@ -5094,7 +5155,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
5094
5155
  // there is no page (windowing replaces pagination) → the windowed model IS the full model, so it
5095
5156
  // returns the absolute index unchanged. Collision-safe: no *-change event, prop, React auto-setter,
5096
5157
  // or inherited Lit DOM method named getRowIndexRelativeToPage (ROZ121/124/137 clear).
5097
- getRowIndexRelativeToPage = (absRow: any) => {
5158
+ getRowIndexRelativeToPage: (absRow?: number) => number = (absRow: any) => {
5098
5159
  const abs = absRow == null ? this.toAbsRow(this._activeRow.value) : Math.trunc(Number(absRow)) || 0;
5099
5160
  if (this.rowsWindowed()) return abs;
5100
5161
  return abs - this.pageRowOffset();
@@ -5106,7 +5167,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
5106
5167
  // active cell, so it cuts the current selection even when the call arrives off a control that
5107
5168
  // moved DOM focus off the grid. Collision-safe: no `cut` event / model prop / React auto-setter /
5108
5169
  // inherited Lit DOM method named `cut` (ROZ121/124/137 clear) — `cut` is not on HTMLElement.
5109
- cut = () => this.cutRange();
5170
+ cut: () => void = () => this.cutRange();
5110
5171
 
5111
5172
  // 260709-8ct (grid-wide undo/redo): NO pass-through wrapper lands here for
5112
5173
  // undo/redo/canUndo/canRedo/clearHistory. Unlike `cut` above (which delegates to a
@@ -5174,9 +5235,6 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
5174
5235
  // page-relative active row → absolute (display-order) index.
5175
5236
  toAbsRow = (localRow: any) => localRow + this.pageRowOffset();
5176
5237
 
5177
- // A body row's ABSOLUTE display-order index = its page-relative index + the page offset. Drives
5178
- // aria-rowindex on the non-virtual paginated body (B27); the virtual path uses wr.vi.index
5179
- // directly (already absolute). Reactive via rowIndexOf's tick().
5180
5238
  // Total filtered+sorted PRE-pagination row count — the clamp bound for an absolute focusCell.
5181
5239
  // In virtual mode $data.rows IS the full pre-pagination model (bodyRowCount suffices); in the
5182
5240
  // non-virtual paginated body $data.rows is only the page slice, so read the live model.
@@ -6117,7 +6175,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
6117
6175
  // SAME language): a header cell has no body-row index, so emit rowIndex:null + isHeader:true
6118
6176
  // rather than a bogus toAbsRow(nextRow) — which would compute a real body-row absolute index
6119
6177
  // for a HEADER move, misleading a consumer into thinking that body row is the active cell.
6120
- this.dispatchEvent(new CustomEvent("activecell-change", {
6178
+ this.dispatchEvent(new CustomEvent<DataTableActiveCellChangePayload>("activecell-change", {
6121
6179
  detail: nextIsHeader ? {
6122
6180
  rowIndex: null,
6123
6181
  colIndex: nextCol,
@@ -6327,7 +6385,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
6327
6385
  activateRowAt = (index: any, trigger: any) => {
6328
6386
  const row = (this._rows.value || [])[index];
6329
6387
  if (!row || this.rowIsGrouped(row) || this.rowIsLazyPlaceholder(row)) return;
6330
- this.dispatchEvent(new CustomEvent("row-activate", {
6388
+ this.dispatchEvent(new CustomEvent<DataTableRowActivatePayload>("row-activate", {
6331
6389
  detail: {
6332
6390
  row: row.original,
6333
6391
  index,
@@ -6734,7 +6792,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
6734
6792
  // { rowIndex, colIndex } pair (or null when no range). T-49-02: positions only, no row
6735
6793
  // data, no DOM node. Used by the getSelectedRange $expose verb AND every range-change emit
6736
6794
  // (the single payload source) AND copyRange/fillRange (the rectangle they operate over).
6737
- getSelectedRange = () => {
6795
+ getSelectedRange: () => DataTableRange = () => {
6738
6796
  // B8: clamp the corners to the CURRENT bounds ON READ so the verb (and the range-change emit
6739
6797
  // payload) never reports a corner past a shrunken model — React-stale-safe (the eager
6740
6798
  // refreshRowModel clamp is async-defeated on React; this read-time clamp is the guarantee).
@@ -6799,7 +6857,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
6799
6857
 
6800
6858
  emitRangeChange = (anchor: any, focus: any) => {
6801
6859
  this._rangeAnnounce.value = this.rangeSummary(anchor, focus);
6802
- this.dispatchEvent(new CustomEvent("range-change", {
6860
+ this.dispatchEvent(new CustomEvent<DataTableRangeChangePayload>("range-change", {
6803
6861
  detail: {
6804
6862
  anchor,
6805
6863
  focus
@@ -7242,7 +7300,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
7242
7300
  // is the first-party code that actually escapes.
7243
7301
  for (let i = 0; i < committed.length; i++) {
7244
7302
  try {
7245
- this.dispatchEvent(new CustomEvent("cell-edit-commit", {
7303
+ this.dispatchEvent(new CustomEvent<DataTableCellEditCommitPayload>("cell-edit-commit", {
7246
7304
  detail: committed[i],
7247
7305
  bubbles: true,
7248
7306
  composed: true
@@ -8241,7 +8299,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
8241
8299
  const next = replaceRowValue(this.currentData(), srcIndex, field, newValue);
8242
8300
  this.writeData(next);
8243
8301
  // Exactly one emit per commit, from this single call site (writeData does NOT emit).
8244
- this.dispatchEvent(new CustomEvent("cell-edit-commit", {
8302
+ this.dispatchEvent(new CustomEvent<DataTableCellEditCommitPayload>("cell-edit-commit", {
8245
8303
  detail: {
8246
8304
  rowId,
8247
8305
  columnId: colId,
@@ -8326,7 +8384,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
8326
8384
  this.writeData(replaceRowValue(this.currentData(), srcIndex, field, newValue));
8327
8385
  // Exactly one emit per toggle, from this single call site (writeData does NOT emit) —
8328
8386
  // mirrors commitEdit's D-07 single-emit discipline.
8329
- this.dispatchEvent(new CustomEvent("cell-edit-commit", {
8387
+ this.dispatchEvent(new CustomEvent<DataTableCellEditCommitPayload>("cell-edit-commit", {
8330
8388
  detail: {
8331
8389
  rowId,
8332
8390
  columnId: colId,
@@ -8562,7 +8620,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
8562
8620
  const next = replaceRowValues(this.currentData(), srcIndex, fieldValues);
8563
8621
  this.writeData(next);
8564
8622
  // EXACTLY ONE emit per row commit, from THIS single call site (React multi-emit dedup, D-07).
8565
- this.dispatchEvent(new CustomEvent("row-edit-commit", {
8623
+ this.dispatchEvent(new CustomEvent<DataTableRowEditCommitPayload>("row-edit-commit", {
8566
8624
  detail: {
8567
8625
  rowId,
8568
8626
  changes
@@ -9107,7 +9165,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
9107
9165
  // editCell(rowIndex, colIndex) — programmatic edit-entry ($expose, req-3). Coerces +
9108
9166
  // clamps indices, moves the active cell, and opens the editor (no-op on a non-editable
9109
9167
  // cell). Collision-clean (RESEARCH name-check): not a verb/event/prop/ROZ137 member.
9110
- editCell = (rowIndex: any, colIndex: any) => {
9168
+ editCell: (rowIndex: number, colIndex: number) => void = (rowIndex: any, colIndex: any) => {
9111
9169
  const lastRow = this.bodyRowCount() - 1;
9112
9170
  const maxRow = lastRow < 0 ? 0 : lastRow;
9113
9171
  const maxCol = this.visibleColCount() - 1;
@@ -9126,7 +9184,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
9126
9184
  // edit (editRow()/Shift+F2) drives editingRowIndex and leaves editingRow at -1, so the
9127
9185
  // single-cell commitEdit guard (editingRow >= 0) is false during a row edit — route to
9128
9186
  // commitRow() first so a programmatic commit of a row editor is not a silent no-op.
9129
- commitEditing = () => {
9187
+ commitEditing: () => void = () => {
9130
9188
  if (this.inRowEdit()) {
9131
9189
  this.commitRow();
9132
9190
  return;
@@ -9140,7 +9198,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
9140
9198
  // name-check): `editRow` is not in the 15 existing verbs, not a prop, not a *-change/commit
9141
9199
  // event, not a Lit ROZ137-reserved host member. Moves the active cell to the row first so the
9142
9200
  // commit/cancel focus-return lands in the right row.
9143
- editRow = (rowIndex: any) => {
9201
+ editRow: (rowIndex: number) => void = (rowIndex: any) => {
9144
9202
  const lastRow = this.bodyRowCount() - 1;
9145
9203
  const maxRow = lastRow < 0 ? 0 : lastRow;
9146
9204
  const r = clamp(Math.trunc(Number(rowIndex)) || 0, 0, maxRow);
@@ -9203,7 +9261,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
9203
9261
  // data-* selector is built (T-49-01/T-63-06-01: never interpolate a raw consumer string; clamp
9204
9262
  // the abs index into getPrePaginationRowModel bounds). The activecell-change payload + getActiveCell
9205
9263
  // speak the SAME absolute language (toAbsRow).
9206
- focusCell = (rowIndex: any, colIndex: any) => {
9264
+ focusCell: (rowIndex: number, colIndex: number) => void = (rowIndex: any, colIndex: any) => {
9207
9265
  // B16: isGrid()-gate the verb. In 'table' mode there is no roving active cell, so focusCell
9208
9266
  // is a NO-OP (never an activecell-change emit) — the keyboard path (onGridKeyDown) is already
9209
9267
  // isGrid-gated; the exposed verb must mirror that so a consumer's focusCell on a table-mode
@@ -9267,7 +9325,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
9267
9325
  }
9268
9326
  }
9269
9327
  if (absRow !== prevAbs || c !== prevCol || prevIsHeader) {
9270
- this.dispatchEvent(new CustomEvent("activecell-change", {
9328
+ this.dispatchEvent(new CustomEvent<DataTableActiveCellChangePayload>("activecell-change", {
9271
9329
  detail: {
9272
9330
  rowIndex: absRow,
9273
9331
  colIndex: c
@@ -9287,7 +9345,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
9287
9345
  // is unchanged for the body case).
9288
9346
  // C1: a body cell returns the ABSOLUTE display-order rowIndex (toAbsRow) — matching focusCell's
9289
9347
  // addressing + the activecell-change payload — in BOTH paginated and virtual modes.
9290
- getActiveCell = () => this._activeIsHeader.value ? {
9348
+ getActiveCell: () => DataTableActiveCell = () => this._activeIsHeader.value ? {
9291
9349
  rowIndex: null,
9292
9350
  colIndex: this._activeColIndex.value,
9293
9351
  isHeader: true
@@ -9301,7 +9359,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
9301
9359
  // exit interaction mode; the next Tab-in re-enters at the entry cell (D-01). Does NOT emit
9302
9360
  // (no move to a new addressable cell — a reset, not a navigation). B16: isGrid()-gated — a
9303
9361
  // table-mode instance has no roving active cell, so the verb is a no-op there.
9304
- clearActiveCell = () => {
9362
+ clearActiveCell: () => void = () => {
9305
9363
  if (!this.isGrid()) return;
9306
9364
  this._activeIsHeader.value = false;
9307
9365
  this._activeInControl.value = false;
@@ -9318,7 +9376,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
9318
9376
  // toggleRowExpanded(rowId) — toggle ONE row's expanded state, addressed by the consumer's
9319
9377
  // row id (the data `id` field) OR the table-core row id. Scans the core flat-row set (all
9320
9378
  // rows regardless of current expansion) so a collapsed parent is still resolvable.
9321
- toggleRowExpanded = (rowId: any) => {
9379
+ toggleRowExpanded: (rowId: string | number) => void = (rowId: any) => {
9322
9380
  if (!this.table) return;
9323
9381
  const target = String(rowId);
9324
9382
  const flat = this.table.getCoreRowModel().flatRows;
@@ -9332,14 +9390,14 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
9332
9390
 
9333
9391
  // expandAll() — open every expandable row (table-core sets ExpandedState to the `true`
9334
9392
  // literal under the hood → Pitfall 2: writeExpanded passes it through verbatim).
9335
- expandAll = () => {
9393
+ expandAll: () => void = () => {
9336
9394
  if (!this.table) return;
9337
9395
  this.table.toggleAllRowsExpanded(true);
9338
9396
  };
9339
9397
 
9340
9398
  // collapseAll() — reset to a blank expanded state ({}). resetExpanded(true) forces the
9341
9399
  // blank reset (NOT the initialState) and fires onExpandedChange → one expanded-change.
9342
- collapseAll = () => {
9400
+ collapseAll: () => void = () => {
9343
9401
  if (!this.table) return;
9344
9402
  this.table.resetExpanded(true);
9345
9403
  };
@@ -9347,7 +9405,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
9347
9405
  // getExpandedRows() — return the original row data for every currently-expanded row
9348
9406
  // (read-verb twin of expanded-change). Integers/data only — scans the core flat rows and
9349
9407
  // filters by getIsExpanded(). Empty when nothing is expanded.
9350
- getExpandedRows = () => {
9408
+ getExpandedRows: () => any[] = () => {
9351
9409
  if (!this.table) return [];
9352
9410
  const out = [];
9353
9411
  const flat = this.table.getCoreRowModel().flatRows;
@@ -9362,11 +9420,11 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
9362
9420
  // setColumnOrder→applyColumnOrder) + clearGrouping. Both drive @tanstack/table-core's
9363
9421
  // table.setGrouping so the onGroupingChange → writeGrouping funnel fires one group-change with
9364
9422
  // the fresh ordered key list. Also handed to the headless #groupBar slot as apply/clear helpers.
9365
- applyGrouping = (cols: any) => {
9423
+ applyGrouping: (cols: string[]) => void = (cols: any) => {
9366
9424
  if (this.table) this.table.setGrouping(cols);
9367
9425
  };
9368
9426
 
9369
- clearGrouping = () => {
9427
+ clearGrouping: () => void = () => {
9370
9428
  if (this.table) this.table.setGrouping([]);
9371
9429
  };
9372
9430
 
@@ -9384,7 +9442,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
9384
9442
  // column/table. NAMED to match the $expose verb exactly (the ExposedMethod.name shorthand
9385
9443
  // contract: an exposed verb lowers to `{ getFacetedUniqueValues }`, which must resolve to THIS
9386
9444
  // helper — the table-core factory was aliased to makeFacetedUniqueValues to free this name).
9387
- getFacetedUniqueValues = (colId: any) => {
9445
+ getFacetedUniqueValues: (colId: string) => any[] = (colId: any) => {
9388
9446
  if (this.tick() < 0 || !this.table) return [];
9389
9447
  const col = this.table.getColumn(colId);
9390
9448
  if (!col || !col.getFacetedUniqueValues) return [];
@@ -9394,7 +9452,7 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
9394
9452
 
9395
9453
  // getFacetedMinMaxValues: the column's [min, max] numeric range, or null when unavailable.
9396
9454
  // Named to match the $expose verb (same shorthand contract as getFacetedUniqueValues above).
9397
- getFacetedMinMaxValues = (colId: any) => {
9455
+ getFacetedMinMaxValues: (colId: string) => [number, number] | null = (colId: any) => {
9398
9456
  if (this.tick() < 0 || !this.table) return null;
9399
9457
  const col = this.table.getColumn(colId);
9400
9458
  if (!col || !col.getFacetedMinMaxValues) return null;
@@ -9425,4 +9483,15 @@ ${this.groupable ? html`<div class="rdt-group-bar-host" data-rozie-s-d5dcab4c>
9425
9483
  set columnOrder(v: any[]) { this._columnOrderControllable.notifyPropertyWrite(v); }
9426
9484
  get columnPinning(): any { return this._columnPinningControllable.read(); }
9427
9485
  set columnPinning(v: any) { this._columnPinningControllable.notifyPropertyWrite(v); }
9486
+
9487
+ addEventListener<K extends keyof RozieDataTableEventMap>(type: K, listener: (this: DataTable, ev: RozieDataTableEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
9488
+ addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
9489
+ addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void {
9490
+ super.addEventListener(type, listener, options);
9491
+ }
9492
+ removeEventListener<K extends keyof RozieDataTableEventMap>(type: K, listener: (this: DataTable, ev: RozieDataTableEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
9493
+ removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
9494
+ removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void {
9495
+ super.removeEventListener(type, listener, options);
9496
+ }
9428
9497
  }