@ds-mo/ui 14.9.0 → 14.10.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.
@@ -242,7 +242,8 @@
242
242
 
243
243
  :where(.ds-table__table--native-group-sticky .ds-table__group-row),
244
244
  :where(.ds-table__table--native-group-sticky .ds-table__state-row),
245
- :where(.ds-table__table--native-group-sticky .ds-table__load-row) {
245
+ :where(.ds-table__table--native-group-sticky .ds-table__load-row),
246
+ :where(.ds-table__table--native-group-sticky .ds-table__virtual-spacer-row) {
246
247
  display: block;
247
248
  inline-size: 100%;
248
249
  }
@@ -251,7 +252,8 @@
251
252
  :where(.ds-table__table--native-group-sticky .ds-table__cell),
252
253
  :where(.ds-table__table--native-group-sticky .ds-table__group-cell),
253
254
  :where(.ds-table__table--native-group-sticky .ds-table__state-cell),
254
- :where(.ds-table__table--native-group-sticky .ds-table__load-cell) {
255
+ :where(.ds-table__table--native-group-sticky .ds-table__load-cell),
256
+ :where(.ds-table__table--native-group-sticky .ds-table__virtual-spacer-cell) {
255
257
  display: block;
256
258
  inline-size: auto;
257
259
  width: auto;
@@ -261,11 +263,28 @@
261
263
 
262
264
  :where(.ds-table__table--native-group-sticky .ds-table__group-cell),
263
265
  :where(.ds-table__table--native-group-sticky .ds-table__state-cell),
264
- :where(.ds-table__table--native-group-sticky .ds-table__load-cell) {
266
+ :where(.ds-table__table--native-group-sticky .ds-table__load-cell),
267
+ :where(.ds-table__table--native-group-sticky .ds-table__virtual-spacer-cell) {
265
268
  inline-size: 100%;
266
269
  width: 100%;
267
270
  }
268
271
 
272
+ :where(.ds-table__virtual-spacer-row) {
273
+ border: 0;
274
+ }
275
+
276
+ :where(.ds-table__virtual-spacer-cell) {
277
+ box-sizing: border-box;
278
+ padding: 0;
279
+ border: 0;
280
+ overflow: hidden;
281
+ line-height: 0;
282
+ font-size: 0;
283
+ background: transparent;
284
+ block-size: var(--_table-virtual-spacer-block-size, 0);
285
+ min-block-size: var(--_table-virtual-spacer-block-size, 0);
286
+ }
287
+
269
288
  :where(.ds-table__caption-bar),
270
289
  :where(.ds-table__footer) {
271
290
  box-sizing: border-box;
@@ -31,12 +31,15 @@ export declare class Table {
31
31
  columnOrder: string[];
32
32
  /**
33
33
  * Optional result summary footer. When both `displayedCount` and `totalCount`
34
- * are finite numbers, the table shows “Displaying {displayed} of {total}”.
34
+ * are finite numbers, infinite mode shows “Displaying {displayed} of {total}”.
35
+ * Virtual mode ignores `displayedCount` and shows a total-only summary.
35
36
  */
36
37
  displayedCount: number | undefined;
37
38
  totalCount: number | undefined;
38
39
  /** Supports {displayed} and {total} placeholders. */
39
40
  resultSummaryLabel: string;
41
+ /** Supports the {total} placeholder. Used when dataMode is virtual. */
42
+ resultTotalSummaryLabel: string;
40
43
  stickyHeader: boolean;
41
44
  /** Maximum scroll-region height. Numbers resolve to CSS pixels. */
42
45
  maxHeight: string | number | undefined;
@@ -64,7 +67,7 @@ export declare class Table {
64
67
  errorHeading: string;
65
68
  errorBody: string;
66
69
  emptyCellLabel: string;
67
- /** Top-level data-window strategy. Group member loading remains group-owned. */
70
+ /** Top-level data-window strategy. Virtual mode recycles row DOM only. */
68
71
  dataMode: TableDataMode;
69
72
  /** Opt in to the table-owned control for choosing between supported data modes. */
70
73
  dataModeSwitcher: boolean;
@@ -124,12 +127,15 @@ export declare class Table {
124
127
  private columnCustomizerInitialFocusVisible;
125
128
  private dataModeSwitcherOpen;
126
129
  private dataModeSwitcherInitialFocusVisible;
130
+ private virtualWindow;
127
131
  private readonly actionMenuElementId;
128
132
  private readonly columnCustomizerElementId;
129
133
  private readonly dataModeSwitcherElementId;
130
134
  private truncateTooltipEl?;
131
135
  private truncateAnchor;
132
136
  private truncateTooltipBound;
137
+ private focusedRowId;
138
+ private virtualItems;
133
139
  private rootEl;
134
140
  private viewportEl;
135
141
  private frameEl;
@@ -152,6 +158,7 @@ export declare class Table {
152
158
  private readonly layoutController;
153
159
  private readonly loadController;
154
160
  private readonly groupLoadController;
161
+ private readonly virtualController;
155
162
  private readonly viewportFitController;
156
163
  componentWillLoad(): void;
157
164
  componentDidLoad(): void;
@@ -167,6 +174,7 @@ export declare class Table {
167
174
  handleStructureChange(): void;
168
175
  handleDataChange(): void;
169
176
  handleLoadIdentityChange(): void;
177
+ handleVirtualIndexReset(): void;
170
178
  handleLazyConfigurationChange(): void;
171
179
  handleDataModeChange(): void;
172
180
  handlePaginationChange(pagination: TablePaginationState | null, previous: TablePaginationState | null): void;
@@ -193,6 +201,12 @@ export declare class Table {
193
201
  private currentLoadedRows;
194
202
  private currentLoadedRowCount;
195
203
  private createRenderModel;
204
+ private get virtualizationEnabled();
205
+ private get virtualViewportMissing();
206
+ private estimateVirtualViewportSize;
207
+ private virtualPinnedRowIds;
208
+ private syncVirtualItems;
209
+ private shouldVirtualize;
196
210
  private get resolvedMaxHeight();
197
211
  private get resolvedHeight();
198
212
  private warnModelIssues;
@@ -219,6 +233,7 @@ export declare class Table {
219
233
  private renderTruncateTooltip;
220
234
  private truncateAttr;
221
235
  private handleRowKeydown;
236
+ private onVirtualFocusIn;
222
237
  private renderSelectionControl;
223
238
  private renderStickyEdge;
224
239
  private renderCollapseAllButton;
@@ -236,6 +251,10 @@ export declare class Table {
236
251
  private formatGroupLoadLabel;
237
252
  private renderGroupLoadRow;
238
253
  private renderStickyGroup;
254
+ private renderVirtualSpacer;
255
+ private renderVirtualRow;
256
+ private renderVirtualGroup;
257
+ private renderVirtualBodies;
239
258
  private renderDataBodies;
240
259
  private renderSkeletonBody;
241
260
  private renderSkeletonCell;
@@ -1,6 +1,7 @@
1
1
  import type { MenuItemData } from '../Menu/menu-types';
2
2
  import type { TableDataMode } from './table-types';
3
3
  export declare function nextTableDataModeSwitcherElementId(): string;
4
+ /** Menu of supported data-mode choices. Virtual is a controlled prop, not a switcher item. */
4
5
  export declare function tableDataModeMenuItems(dataMode: TableDataMode, labels: {
5
6
  infinite: string;
6
7
  pagination: string;
@@ -50,5 +50,7 @@ export declare function tableGroupIntentClass(intent: (typeof TABLE_GROUP_INTENT
50
50
  export declare function tableGroupLabelColor(intent: (typeof TABLE_GROUP_INTENTS)[number] | undefined): 'primary' | 'brand' | 'negative' | 'warning' | 'caution' | 'positive' | `var(--${string})`;
51
51
  /** Formats the optional table result footer. Returns null when either count is missing. */
52
52
  export declare function formatTableResultSummary(displayed: number | null | undefined, total: number | null | undefined, label?: string, locale?: string): string | null;
53
+ /** Formats the virtual-mode total-only result footer. */
54
+ export declare function formatTableTotalSummary(total: number | null | undefined, label?: string, locale?: string): string | null;
53
55
  export declare function tableModelIssues(columns: TableColumn[], rows: TableRow[], groups: TableGroup[], grouped: boolean): string[];
54
56
  //# sourceMappingURL=table-model.d.ts.map
@@ -1,9 +1,11 @@
1
1
  import { tableGroupLabelColor, type TableCollapseAllHost, type TableSelectionState } from './table-model';
2
2
  import type { TableColumn, TableGroup, TableGroupIntent, TableRow, TableSelectionMode } from './table-types';
3
+ export type TableGroupCountPresentation = 'loaded-progress' | 'total';
3
4
  export interface TableGroupRenderModel {
4
5
  group: TableGroup;
5
6
  count: number;
6
7
  loadedCount: number;
8
+ visibleCountText: string;
7
9
  countLabel: string;
8
10
  intent: TableGroupIntent | undefined;
9
11
  intentClass: string | undefined;
@@ -35,6 +37,7 @@ export interface TableRenderModelInput {
35
37
  selectionMode: TableSelectionMode;
36
38
  selectedRowIds: string[];
37
39
  collapsedGroupIds: string[];
40
+ groupCountPresentation?: TableGroupCountPresentation;
38
41
  }
39
42
  /** Create one immutable snapshot of all derived state consumed during a render. */
40
43
  export declare function createTableRenderModel(input: TableRenderModelInput): TableRenderModel;
@@ -9,7 +9,7 @@ export type TableCellLinkTarget = '_self' | '_blank';
9
9
  export type TableColumnSticky = 'start' | 'end';
10
10
  export type TableCaptionVisibility = 'visible' | 'hidden';
11
11
  export type TableLoadMoreMode = 'auto' | 'manual';
12
- export type TableDataMode = 'infinite' | 'pagination';
12
+ export type TableDataMode = 'infinite' | 'pagination' | 'virtual';
13
13
  export interface TableDataModeChangeDetail {
14
14
  dataMode: TableDataMode;
15
15
  }
@@ -0,0 +1,48 @@
1
+ import { type TableVirtualItem, type TableVirtualPlan } from './table-virtual-model';
2
+ export interface TableVirtualControllerState {
3
+ enabled: boolean;
4
+ items: readonly TableVirtualItem[];
5
+ pinnedRowIds: ReadonlySet<string>;
6
+ viewport: HTMLElement | null;
7
+ viewportSize: number;
8
+ }
9
+ export interface TableVirtualControllerOptions {
10
+ state: () => TableVirtualControllerState;
11
+ windowChanged: (plan: TableVirtualPlan | null) => void;
12
+ requestAnimationFrame?: (callback: FrameRequestCallback) => number;
13
+ cancelAnimationFrame?: (handle: number) => void;
14
+ }
15
+ /** Binds viewport scroll/resize to a recycled row window. Scroll offset stays off render state. */
16
+ export declare class TableVirtualController {
17
+ private readonly options;
18
+ private connected;
19
+ private connectedViewport;
20
+ private resizeObserver;
21
+ private frame;
22
+ private measures;
23
+ private plan;
24
+ private scrollOffset;
25
+ private pendingReset;
26
+ private readonly raf;
27
+ private readonly caf;
28
+ constructor(options: TableVirtualControllerOptions);
29
+ connect(): void;
30
+ disconnect(): void;
31
+ refresh(): void;
32
+ schedule: () => void;
33
+ /** Drop cached heights after column, density, or wrap geometry changes. */
34
+ invalidateMeasures(): void;
35
+ /** Rebuild from a new index and pin the viewport to the start. */
36
+ resetToTop(): void;
37
+ sizeFor(item: TableVirtualItem): number;
38
+ currentPlan(): TableVirtualPlan | null;
39
+ /** Measure painted rows and correct scroll when estimates were wrong. */
40
+ collectMeasurements(root: ParentNode | null): void;
41
+ private prefixForCurrentItems;
42
+ private readonly handleScroll;
43
+ private sync;
44
+ private emit;
45
+ private unbindViewport;
46
+ private cancelFrame;
47
+ }
48
+ //# sourceMappingURL=table-virtual-controller.d.ts.map
@@ -0,0 +1,77 @@
1
+ import type { TableColumn, TableGroup, TableRow } from './table-types';
2
+ /**
3
+ * Token-aligned body-row track contracts: 40 / 62 / 84, plus wrapping-secondary
4
+ * 106. Estimates only; measured heights replace these after paint.
5
+ */
6
+ export declare const TABLE_VIRTUAL_ROW_TRACK_SIZE: {
7
+ readonly 1: 40;
8
+ readonly 2: 62;
9
+ readonly 3: 84;
10
+ readonly 4: 106;
11
+ };
12
+ export declare const TABLE_VIRTUAL_GROUP_HEADER_SIZE: 40;
13
+ export declare const TABLE_VIRTUAL_MIN_OVERSCAN_ROWS = 8;
14
+ export declare const TABLE_VIRTUAL_VIEWPORT_REQUIRED_HEADING = "Bounded height required";
15
+ export declare const TABLE_VIRTUAL_VIEWPORT_REQUIRED_BODY = "Virtual mode requires height, maxHeight, or fitViewport.";
16
+ export declare const TABLE_VIRTUAL_TOTAL_SUMMARY_LABEL = "{total} items";
17
+ export type TableVirtualItemKind = 'group' | 'row';
18
+ export interface TableVirtualItem {
19
+ kind: TableVirtualItemKind;
20
+ id: string;
21
+ groupId?: string;
22
+ rowId?: string;
23
+ estimatedSize: number;
24
+ }
25
+ export type TableVirtualNode = {
26
+ kind: 'spacer';
27
+ size: number;
28
+ key: string;
29
+ } | {
30
+ kind: 'row';
31
+ index: number;
32
+ item: TableVirtualItem;
33
+ } | {
34
+ kind: 'group';
35
+ groupId: string;
36
+ headerIndex: number;
37
+ nodes: Array<Extract<TableVirtualNode, {
38
+ kind: 'spacer' | 'row';
39
+ }>>;
40
+ };
41
+ export interface TableVirtualPlan {
42
+ totalSize: number;
43
+ itemCount: number;
44
+ start: number;
45
+ end: number;
46
+ nodes: TableVirtualNode[];
47
+ mountedIds: ReadonlySet<string>;
48
+ mountedIndexes: ReadonlySet<number>;
49
+ }
50
+ export interface FlattenTableVirtualItemsInput {
51
+ grouped: boolean;
52
+ rows: TableRow[];
53
+ groups: TableGroup[];
54
+ collapsedGroupIds: ReadonlySet<string> | readonly string[];
55
+ columns: TableColumn[];
56
+ }
57
+ export interface ResolveTableVirtualPlanInput {
58
+ items: readonly TableVirtualItem[];
59
+ sizes: readonly number[];
60
+ scrollOffset: number;
61
+ viewportSize: number;
62
+ pinnedRowIds?: ReadonlySet<string>;
63
+ }
64
+ /** Map a cell recipe onto the named track stack used for first-paint estimates. */
65
+ export declare function estimateTableCellTrackCount(value: TableRow['cells'][string], column: TableColumn): number;
66
+ /** Tallest named track stack across the row's visible cells. */
67
+ export declare function estimateTableRowBlockSize(row: TableRow, columns: readonly TableColumn[]): number;
68
+ export declare function flattenTableVirtualItems(input: FlattenTableVirtualItemsInput): TableVirtualItem[];
69
+ export declare function tableVirtualOverscanPx(viewportSize: number): number;
70
+ export declare function buildTableVirtualPrefixSums(sizes: readonly number[]): number[];
71
+ /** Largest item index whose start offset is <= `offset`. */
72
+ export declare function findTableVirtualIndexAtOffset(prefix: readonly number[], offset: number): number;
73
+ export declare function tableVirtualItemSizes(items: readonly TableVirtualItem[], measures: ReadonlyMap<string, number>): number[];
74
+ export declare function sameTableVirtualPlan(left: TableVirtualPlan | null | undefined, right: TableVirtualPlan | null | undefined): boolean;
75
+ /** Resolve the mounted slice, sticky headers, and in-flow spacers for one scroll offset. */
76
+ export declare function resolveTableVirtualPlan(input: ResolveTableVirtualPlanInput): TableVirtualPlan;
77
+ //# sourceMappingURL=table-virtual-model.d.ts.map
@@ -2877,7 +2877,7 @@ export namespace Components {
2877
2877
  */
2878
2878
  "columns": TableColumn[];
2879
2879
  /**
2880
- * Top-level data-window strategy. Group member loading remains group-owned.
2880
+ * Top-level data-window strategy. Virtual mode recycles row DOM only.
2881
2881
  * @default 'infinite'
2882
2882
  */
2883
2883
  "dataMode": TableDataMode;
@@ -2895,7 +2895,7 @@ export namespace Components {
2895
2895
  */
2896
2896
  "dataModeSwitcherLabel": string;
2897
2897
  /**
2898
- * Optional result summary footer. When both `displayedCount` and `totalCount` are finite numbers, the table shows “Displaying {displayed} of {total}”.
2898
+ * Optional result summary footer. When both `displayedCount` and `totalCount` are finite numbers, infinite mode shows “Displaying {displayed} of {total}”. Virtual mode ignores `displayedCount` and shows a total-only summary.
2899
2899
  */
2900
2900
  "displayedCount": number | undefined;
2901
2901
  /**
@@ -3038,6 +3038,11 @@ export namespace Components {
3038
3038
  * @default 'Displaying {displayed} of {total}'
3039
3039
  */
3040
3040
  "resultSummaryLabel": string;
3041
+ /**
3042
+ * Supports the {total} placeholder. Used when dataMode is virtual.
3043
+ * @default '{total} items'
3044
+ */
3045
+ "resultTotalSummaryLabel": string;
3041
3046
  /**
3042
3047
  * @default 'Retry'
3043
3048
  */
@@ -7794,7 +7799,7 @@ declare namespace LocalJSX {
7794
7799
  */
7795
7800
  "columns"?: TableColumn[];
7796
7801
  /**
7797
- * Top-level data-window strategy. Group member loading remains group-owned.
7802
+ * Top-level data-window strategy. Virtual mode recycles row DOM only.
7798
7803
  * @default 'infinite'
7799
7804
  */
7800
7805
  "dataMode"?: TableDataMode;
@@ -7812,7 +7817,7 @@ declare namespace LocalJSX {
7812
7817
  */
7813
7818
  "dataModeSwitcherLabel"?: string;
7814
7819
  /**
7815
- * Optional result summary footer. When both `displayedCount` and `totalCount` are finite numbers, the table shows “Displaying {displayed} of {total}”.
7820
+ * Optional result summary footer. When both `displayedCount` and `totalCount` are finite numbers, infinite mode shows “Displaying {displayed} of {total}”. Virtual mode ignores `displayedCount` and shows a total-only summary.
7816
7821
  */
7817
7822
  "displayedCount"?: number | undefined;
7818
7823
  /**
@@ -7965,6 +7970,11 @@ declare namespace LocalJSX {
7965
7970
  * @default 'Displaying {displayed} of {total}'
7966
7971
  */
7967
7972
  "resultSummaryLabel"?: string;
7973
+ /**
7974
+ * Supports the {total} placeholder. Used when dataMode is virtual.
7975
+ * @default '{total} items'
7976
+ */
7977
+ "resultTotalSummaryLabel"?: string;
7968
7978
  /**
7969
7979
  * @default 'Retry'
7970
7980
  */
@@ -9000,6 +9010,7 @@ declare namespace LocalJSX {
9000
9010
  "displayedCount": number | undefined;
9001
9011
  "totalCount": number | undefined;
9002
9012
  "resultSummaryLabel": string;
9013
+ "resultTotalSummaryLabel": string;
9003
9014
  "stickyHeader": boolean;
9004
9015
  "maxHeight": string;
9005
9016
  "height": string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ds-mo/ui",
3
- "version": "14.9.0",
3
+ "version": "14.10.0",
4
4
  "description": "CompoMo — composable web components styled with TokoMo design tokens",
5
5
  "keywords": [
6
6
  "web-components",