@happyvertical/smrt-ui 0.42.3 → 0.42.5
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/README.md +275 -0
- package/dist/components/data/DataTable.svelte +1444 -172
- package/dist/components/data/DataTable.svelte.d.ts +1 -1
- package/dist/components/data/DataTable.svelte.d.ts.map +1 -1
- package/dist/components/data/DataTableController.d.ts +235 -0
- package/dist/components/data/DataTableController.d.ts.map +1 -0
- package/dist/components/data/DataTableController.js +792 -0
- package/dist/components/data/DataTableIdentity.d.ts +21 -0
- package/dist/components/data/DataTableIdentity.d.ts.map +1 -0
- package/dist/components/data/DataTableIdentity.js +28 -0
- package/dist/components/data/DataTableLayout.d.ts +37 -0
- package/dist/components/data/DataTableLayout.d.ts.map +1 -0
- package/dist/components/data/DataTableLayout.js +135 -0
- package/dist/components/data/DataTablePerformance.d.ts +31 -0
- package/dist/components/data/DataTablePerformance.d.ts.map +1 -0
- package/dist/components/data/DataTablePerformance.js +46 -0
- package/dist/components/data/DataTableVirtualization.d.ts +71 -0
- package/dist/components/data/DataTableVirtualization.d.ts.map +1 -0
- package/dist/components/data/DataTableVirtualization.js +100 -0
- package/dist/components/data/__benchmarks__/DataTable.bench.d.ts +2 -0
- package/dist/components/data/__benchmarks__/DataTable.bench.d.ts.map +1 -0
- package/dist/components/data/__benchmarks__/DataTable.bench.js +53 -0
- package/dist/components/data/__fixtures__/DataTableConformanceFixture.d.ts +27 -0
- package/dist/components/data/__fixtures__/DataTableConformanceFixture.d.ts.map +1 -0
- package/dist/components/data/__fixtures__/DataTableConformanceFixture.js +141 -0
- package/dist/components/data/__fixtures__/DataTablePerformanceFixture.d.ts +10 -0
- package/dist/components/data/__fixtures__/DataTablePerformanceFixture.d.ts.map +1 -0
- package/dist/components/data/__fixtures__/DataTablePerformanceFixture.js +23 -0
- package/dist/components/data/__tests__/DataTable.test.js +644 -21
- package/dist/components/data/__tests__/DataTableConformance.test.js +212 -0
- package/dist/components/data/__tests__/DataTableController.test.js +336 -0
- package/dist/components/data/__tests__/DataTableIdentity.test.js +29 -0
- package/dist/components/data/__tests__/DataTableLayout.test.js +195 -0
- package/dist/components/data/__tests__/DataTablePerformance.test.js +21 -0
- package/dist/components/data/__tests__/DataTableVirtualization.test.js +80 -0
- package/dist/components/data/__tests__/DataTableVirtualizationComponent.test.js +255 -0
- package/dist/components/data/__tests__/data-surface.test.js +675 -0
- package/dist/components/data/data-surface.d.ts +246 -0
- package/dist/components/data/data-surface.d.ts.map +1 -0
- package/dist/components/data/data-surface.js +1102 -0
- package/dist/components/data/index.d.ts +5 -0
- package/dist/components/data/index.d.ts.map +1 -1
- package/dist/components/data/index.js +5 -0
- package/dist/components/data/types.d.ts +110 -2
- package/dist/components/data/types.d.ts.map +1 -1
- package/dist/i18n/strings.d.ts +26 -0
- package/dist/i18n/strings.d.ts.map +1 -1
- package/dist/i18n/strings.js +28 -2
- package/dist/svelte/playground/DataTablePreview.svelte +400 -10
- package/dist/svelte/playground/DataTablePreview.svelte.d.ts.map +1 -1
- package/dist/svelte/playground.js +2 -2
- package/package.json +3 -2
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type DataTableRowId } from './DataTableController.js';
|
|
2
|
+
/** A stable row identity accessor. It must not depend on display position. */
|
|
3
|
+
export type DataTableRowKey<T> = keyof T | ((row: T) => DataTableRowId);
|
|
4
|
+
export interface DataTableResolvedRow<T> {
|
|
5
|
+
row: T;
|
|
6
|
+
/** Index in the `data` array supplied to DataTable before local transforms. */
|
|
7
|
+
sourceIndex: number;
|
|
8
|
+
/** Canonical key used by Svelte, selection, and expansion. */
|
|
9
|
+
rowId: DataTableRowId;
|
|
10
|
+
}
|
|
11
|
+
export interface ResolveDataTableRowsOptions {
|
|
12
|
+
/** Durable table features may not use the historical source-index fallback. */
|
|
13
|
+
requireStableIdentity?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the renderer's source rows once and fail closed for missing or
|
|
17
|
+
* duplicate durable identities. The index fallback only exists for
|
|
18
|
+
* presentational local tables that have no durable row state.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveDataTableRows<T>(rows: readonly T[], rowKey: DataTableRowKey<T> | undefined, options?: ResolveDataTableRowsOptions): DataTableResolvedRow<T>[];
|
|
21
|
+
//# sourceMappingURL=DataTableIdentity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DataTableIdentity.d.ts","sourceRoot":"","sources":["../../../src/components/data/DataTableIdentity.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,EAEpB,MAAM,0BAA0B,CAAC;AAElC,8EAA8E;AAC9E,MAAM,MAAM,eAAe,CAAC,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,KAAK,cAAc,CAAC,CAAC;AAExE,MAAM,WAAW,oBAAoB,CAAC,CAAC;IACrC,GAAG,EAAE,CAAC,CAAC;IACP,+EAA+E;IAC/E,WAAW,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,KAAK,EAAE,cAAc,CAAC;CACvB;AAED,MAAM,WAAW,2BAA2B;IAC1C,+EAA+E;IAC/E,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAQD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,IAAI,EAAE,SAAS,CAAC,EAAE,EAClB,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,SAAS,EACtC,OAAO,GAAE,2BAAgC,GACxC,oBAAoB,CAAC,CAAC,CAAC,EAAE,CAqB3B"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { assertDataTableRowId, dataTableRowIdKey, } from './DataTableController.js';
|
|
2
|
+
function readRowKey(row, rowKey) {
|
|
3
|
+
return typeof rowKey === 'function'
|
|
4
|
+
? rowKey(row)
|
|
5
|
+
: row[rowKey];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Resolve the renderer's source rows once and fail closed for missing or
|
|
9
|
+
* duplicate durable identities. The index fallback only exists for
|
|
10
|
+
* presentational local tables that have no durable row state.
|
|
11
|
+
*/
|
|
12
|
+
export function resolveDataTableRows(rows, rowKey, options = {}) {
|
|
13
|
+
if (options.requireStableIdentity && !rowKey) {
|
|
14
|
+
throw new TypeError('DataTable rowKey is required for selectable, expandable, manual, or agent-addressable tables');
|
|
15
|
+
}
|
|
16
|
+
const seen = new Set();
|
|
17
|
+
return rows.map((row, sourceIndex) => {
|
|
18
|
+
const rowId = rowKey
|
|
19
|
+
? assertDataTableRowId(readRowKey(row, rowKey))
|
|
20
|
+
: sourceIndex;
|
|
21
|
+
const key = dataTableRowIdKey(rowId);
|
|
22
|
+
if (seen.has(key)) {
|
|
23
|
+
throw new TypeError(`DataTable rowKey must resolve to unique row ids; duplicate ${key}`);
|
|
24
|
+
}
|
|
25
|
+
seen.add(key);
|
|
26
|
+
return { row, sourceIndex, rowId };
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { DataTableColumnPinning, DataTableViewState } from './DataTableController.js';
|
|
2
|
+
import type { DataTableColumn } from './types.js';
|
|
3
|
+
export interface DataTableResolvedColumn<T> {
|
|
4
|
+
column: DataTableColumn<T>;
|
|
5
|
+
pin?: DataTableColumnPinning['position'];
|
|
6
|
+
/** A persisted pixel width supersedes the column's presentational default. */
|
|
7
|
+
width?: number;
|
|
8
|
+
/** CSS-safe width used for a sticky offset when one is available. */
|
|
9
|
+
offsetWidth: string;
|
|
10
|
+
stickyOffset?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface DataTableHeaderGroupCell {
|
|
13
|
+
kind: 'group';
|
|
14
|
+
key: string;
|
|
15
|
+
label: string;
|
|
16
|
+
colspan: number;
|
|
17
|
+
rowspan: number;
|
|
18
|
+
pin?: DataTableColumnPinning['position'];
|
|
19
|
+
stickyOffset?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface DataTableHeaderLeafCell<T> {
|
|
22
|
+
kind: 'leaf';
|
|
23
|
+
column: DataTableResolvedColumn<T>;
|
|
24
|
+
rowspan: number;
|
|
25
|
+
}
|
|
26
|
+
export type DataTableHeaderCell<T> = DataTableHeaderGroupCell | DataTableHeaderLeafCell<T>;
|
|
27
|
+
export interface DataTableLayout<T> {
|
|
28
|
+
columns: DataTableResolvedColumn<T>[];
|
|
29
|
+
headerRows: DataTableHeaderCell<T>[][];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Resolve a DOM-safe table layout from canonical controller state. Pinning is
|
|
33
|
+
* a partition of the configured order so the visual and assistive orders stay
|
|
34
|
+
* aligned. Header groups are then rebuilt from the final visible leaf order.
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolveDataTableLayout<T>(columns: readonly DataTableColumn<T>[], state: Pick<DataTableViewState, 'columnOrder' | 'columnVisibility' | 'columnWidths' | 'columnPinning'>, measuredWidths?: Readonly<Record<string, number>>): DataTableLayout<T>;
|
|
37
|
+
//# sourceMappingURL=DataTableLayout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DataTableLayout.d.ts","sourceRoot":"","sources":["../../../src/components/data/DataTableLayout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,kBAAkB,EACnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,eAAe,EAA8B,MAAM,YAAY,CAAC;AAE9E,MAAM,WAAW,uBAAuB,CAAC,CAAC;IACxC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC;IAC3B,GAAG,CAAC,EAAE,sBAAsB,CAAC,UAAU,CAAC,CAAC;IACzC,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,sBAAsB,CAAC,UAAU,CAAC,CAAC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,uBAAuB,CAAC,CAAC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,uBAAuB,CAAC,CAAC,CAAC,CAAC;IACnC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,mBAAmB,CAAC,CAAC,IAC7B,wBAAwB,GACxB,uBAAuB,CAAC,CAAC,CAAC,CAAC;AAE/B,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,OAAO,EAAE,uBAAuB,CAAC,CAAC,CAAC,EAAE,CAAC;IACtC,UAAU,EAAE,mBAAmB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;CACxC;AA4GD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,EACtC,OAAO,EAAE,SAAS,eAAe,CAAC,CAAC,CAAC,EAAE,EACtC,KAAK,EAAE,IAAI,CACT,kBAAkB,EAClB,aAAa,GAAG,kBAAkB,GAAG,cAAc,GAAG,eAAe,CACtE,EACD,cAAc,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,GACpD,eAAe,CAAC,CAAC,CAAC,CA2DpB"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
function compareOrder(left, right, order) {
|
|
2
|
+
const leftOrder = order.get(left.id) ?? Number.MAX_SAFE_INTEGER;
|
|
3
|
+
const rightOrder = order.get(right.id) ?? Number.MAX_SAFE_INTEGER;
|
|
4
|
+
return leftOrder === rightOrder ? 0 : leftOrder - rightOrder;
|
|
5
|
+
}
|
|
6
|
+
function headerPath(column) {
|
|
7
|
+
const path = column.headerPath ?? [];
|
|
8
|
+
for (const segment of path) {
|
|
9
|
+
if (!segment ||
|
|
10
|
+
typeof segment.id !== 'string' ||
|
|
11
|
+
segment.id.length === 0 ||
|
|
12
|
+
typeof segment.label !== 'string' ||
|
|
13
|
+
segment.label.length === 0) {
|
|
14
|
+
throw new TypeError(`DataTable headerPath entries for ${column.id} require non-empty id and label`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return path;
|
|
18
|
+
}
|
|
19
|
+
function offsetWidth(column, width, measuredWidth) {
|
|
20
|
+
if (measuredWidth !== undefined && measuredWidth > 0) {
|
|
21
|
+
return `${measuredWidth}px`;
|
|
22
|
+
}
|
|
23
|
+
if (width !== undefined)
|
|
24
|
+
return `${width}px`;
|
|
25
|
+
return column.width ?? column.minWidth ?? '0px';
|
|
26
|
+
}
|
|
27
|
+
function resolveHeaderRows(columns) {
|
|
28
|
+
const paths = columns.map(({ column }) => headerPath(column));
|
|
29
|
+
const groupDepth = Math.max(0, ...paths.map((path) => path.length));
|
|
30
|
+
if (groupDepth === 0) {
|
|
31
|
+
return [columns.map((column) => ({ kind: 'leaf', column, rowspan: 1 }))];
|
|
32
|
+
}
|
|
33
|
+
const rows = Array.from({ length: groupDepth + 1 }, () => []);
|
|
34
|
+
for (let level = 0; level <= groupDepth; level += 1) {
|
|
35
|
+
let index = 0;
|
|
36
|
+
while (index < columns.length) {
|
|
37
|
+
const path = paths[index];
|
|
38
|
+
if (path.length < level) {
|
|
39
|
+
index += 1;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (path.length === level) {
|
|
43
|
+
rows[level].push({
|
|
44
|
+
kind: 'leaf',
|
|
45
|
+
column: columns[index],
|
|
46
|
+
rowspan: groupDepth - level + 1,
|
|
47
|
+
});
|
|
48
|
+
index += 1;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const segment = path[level];
|
|
52
|
+
let end = index + 1;
|
|
53
|
+
while (end < columns.length &&
|
|
54
|
+
paths[end][level]?.id === segment.id &&
|
|
55
|
+
paths[end][level]?.label === segment.label) {
|
|
56
|
+
end += 1;
|
|
57
|
+
}
|
|
58
|
+
rows[level].push({
|
|
59
|
+
kind: 'group',
|
|
60
|
+
key: `${level}:${index}:${segment.id}`,
|
|
61
|
+
label: segment.label,
|
|
62
|
+
colspan: end - index,
|
|
63
|
+
rowspan: 1,
|
|
64
|
+
...resolveGroupPin(columns.slice(index, end)),
|
|
65
|
+
});
|
|
66
|
+
index = end;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return rows;
|
|
70
|
+
}
|
|
71
|
+
function resolveGroupPin(columns) {
|
|
72
|
+
const pin = columns[0]?.pin;
|
|
73
|
+
if (!pin || !columns.every((column) => column.pin === pin))
|
|
74
|
+
return {};
|
|
75
|
+
const edge = pin === 'start' ? columns[0] : columns.at(-1);
|
|
76
|
+
return { pin, stickyOffset: edge?.stickyOffset };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Resolve a DOM-safe table layout from canonical controller state. Pinning is
|
|
80
|
+
* a partition of the configured order so the visual and assistive orders stay
|
|
81
|
+
* aligned. Header groups are then rebuilt from the final visible leaf order.
|
|
82
|
+
*/
|
|
83
|
+
export function resolveDataTableLayout(columns, state, measuredWidths = {}) {
|
|
84
|
+
const order = new Map(state.columnOrder.map((id, index) => [id, index]));
|
|
85
|
+
const visibility = new Map(state.columnVisibility.map((entry) => [entry.columnId, entry.visible]));
|
|
86
|
+
const widths = new Map(state.columnWidths.map((entry) => [entry.columnId, entry.width]));
|
|
87
|
+
const pinning = new Map(state.columnPinning.map((entry) => [entry.columnId, entry.position]));
|
|
88
|
+
const ordered = columns
|
|
89
|
+
.filter((column) => !column.hidden && visibility.get(column.id) !== false)
|
|
90
|
+
.slice()
|
|
91
|
+
.sort((left, right) => compareOrder(left, right, order));
|
|
92
|
+
const partitions = {
|
|
93
|
+
start: ordered.filter((column) => pinning.get(column.id) === 'start'),
|
|
94
|
+
center: ordered.filter((column) => !pinning.has(column.id)),
|
|
95
|
+
end: ordered.filter((column) => pinning.get(column.id) === 'end'),
|
|
96
|
+
};
|
|
97
|
+
const resolved = [];
|
|
98
|
+
let startOffset = '0px';
|
|
99
|
+
for (const column of partitions.start) {
|
|
100
|
+
const width = widths.get(column.id);
|
|
101
|
+
const offset = offsetWidth(column, width, measuredWidths[column.id]);
|
|
102
|
+
resolved.push({
|
|
103
|
+
column,
|
|
104
|
+
pin: 'start',
|
|
105
|
+
width,
|
|
106
|
+
offsetWidth: offset,
|
|
107
|
+
stickyOffset: startOffset,
|
|
108
|
+
});
|
|
109
|
+
startOffset = `calc(${startOffset} + ${offset})`;
|
|
110
|
+
}
|
|
111
|
+
for (const column of partitions.center) {
|
|
112
|
+
const width = widths.get(column.id);
|
|
113
|
+
resolved.push({
|
|
114
|
+
column,
|
|
115
|
+
width,
|
|
116
|
+
offsetWidth: offsetWidth(column, width, measuredWidths[column.id]),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
let endOffset = '0px';
|
|
120
|
+
const endColumns = [];
|
|
121
|
+
for (const column of partitions.end.slice().reverse()) {
|
|
122
|
+
const width = widths.get(column.id);
|
|
123
|
+
const offset = offsetWidth(column, width, measuredWidths[column.id]);
|
|
124
|
+
endColumns.unshift({
|
|
125
|
+
column,
|
|
126
|
+
pin: 'end',
|
|
127
|
+
width,
|
|
128
|
+
offsetWidth: offset,
|
|
129
|
+
stickyOffset: endOffset,
|
|
130
|
+
});
|
|
131
|
+
endOffset = `calc(${endOffset} + ${offset})`;
|
|
132
|
+
}
|
|
133
|
+
resolved.push(...endColumns);
|
|
134
|
+
return { columns: resolved, headerRows: resolveHeaderRows(resolved) };
|
|
135
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Published, testable scale guidance for the DataTable pipeline.
|
|
3
|
+
*
|
|
4
|
+
* These are interaction thresholds, not hard data limits. Use the benchmark
|
|
5
|
+
* fixture before raising them for a new renderer, browser target, or cell shape.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DATA_TABLE_SCALE_THRESHOLDS: {
|
|
8
|
+
readonly localRender: {
|
|
9
|
+
readonly maxRows: 250;
|
|
10
|
+
readonly maxCells: 5000;
|
|
11
|
+
readonly recommendation: "Use normal semantic rendering for ordinary, bounded result sets.";
|
|
12
|
+
};
|
|
13
|
+
readonly clientTransforms: {
|
|
14
|
+
readonly maxRows: 1000;
|
|
15
|
+
readonly maxCells: 20000;
|
|
16
|
+
readonly recommendation: "Use manual filtering, sorting, or paging once a client transform exceeds this budget.";
|
|
17
|
+
};
|
|
18
|
+
readonly manualPaging: {
|
|
19
|
+
readonly recommendedPageSize: 100;
|
|
20
|
+
readonly maxRenderedRows: 250;
|
|
21
|
+
readonly recommendation: "Keep remote pages bounded and pass totalRows only for a known server total.";
|
|
22
|
+
};
|
|
23
|
+
readonly virtualization: {
|
|
24
|
+
readonly minRows: 250;
|
|
25
|
+
readonly recommendation: "Use fixed-height virtualization for continuous browsing above the local render budget; preserve stable rowKey values and retain remote paging for unbounded data.";
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export type DataTableScaleStrategy = 'local' | 'manual-paging' | 'virtualization';
|
|
29
|
+
/** Returns the recommended rendering strategy for a known row and column count. */
|
|
30
|
+
export declare function recommendDataTableScaleStrategy(rowCount: number, columnCount: number): DataTableScaleStrategy;
|
|
31
|
+
//# sourceMappingURL=DataTablePerformance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DataTablePerformance.d.ts","sourceRoot":"","sources":["../../../src/components/data/DataTablePerformance.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;CAwB9B,CAAC;AAEX,MAAM,MAAM,sBAAsB,GAC9B,OAAO,GACP,eAAe,GACf,gBAAgB,CAAC;AAErB,mFAAmF;AACnF,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAClB,sBAAsB,CAwBxB"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Published, testable scale guidance for the DataTable pipeline.
|
|
3
|
+
*
|
|
4
|
+
* These are interaction thresholds, not hard data limits. Use the benchmark
|
|
5
|
+
* fixture before raising them for a new renderer, browser target, or cell shape.
|
|
6
|
+
*/
|
|
7
|
+
export const DATA_TABLE_SCALE_THRESHOLDS = {
|
|
8
|
+
localRender: {
|
|
9
|
+
maxRows: 250,
|
|
10
|
+
maxCells: 5_000,
|
|
11
|
+
recommendation: 'Use normal semantic rendering for ordinary, bounded result sets.',
|
|
12
|
+
},
|
|
13
|
+
clientTransforms: {
|
|
14
|
+
maxRows: 1_000,
|
|
15
|
+
maxCells: 20_000,
|
|
16
|
+
recommendation: 'Use manual filtering, sorting, or paging once a client transform exceeds this budget.',
|
|
17
|
+
},
|
|
18
|
+
manualPaging: {
|
|
19
|
+
recommendedPageSize: 100,
|
|
20
|
+
maxRenderedRows: 250,
|
|
21
|
+
recommendation: 'Keep remote pages bounded and pass totalRows only for a known server total.',
|
|
22
|
+
},
|
|
23
|
+
virtualization: {
|
|
24
|
+
minRows: 250,
|
|
25
|
+
recommendation: 'Use fixed-height virtualization for continuous browsing above the local render budget; preserve stable rowKey values and retain remote paging for unbounded data.',
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
/** Returns the recommended rendering strategy for a known row and column count. */
|
|
29
|
+
export function recommendDataTableScaleStrategy(rowCount, columnCount) {
|
|
30
|
+
if (!Number.isInteger(rowCount) ||
|
|
31
|
+
!Number.isInteger(columnCount) ||
|
|
32
|
+
rowCount < 0 ||
|
|
33
|
+
columnCount < 0) {
|
|
34
|
+
throw new TypeError('DataTable scale counts must be non-negative integers');
|
|
35
|
+
}
|
|
36
|
+
const cells = rowCount * columnCount;
|
|
37
|
+
if (rowCount <= DATA_TABLE_SCALE_THRESHOLDS.localRender.maxRows &&
|
|
38
|
+
cells <= DATA_TABLE_SCALE_THRESHOLDS.localRender.maxCells) {
|
|
39
|
+
return 'local';
|
|
40
|
+
}
|
|
41
|
+
if (rowCount > DATA_TABLE_SCALE_THRESHOLDS.clientTransforms.maxRows ||
|
|
42
|
+
cells > DATA_TABLE_SCALE_THRESHOLDS.clientTransforms.maxCells) {
|
|
43
|
+
return 'manual-paging';
|
|
44
|
+
}
|
|
45
|
+
return 'virtualization';
|
|
46
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { DataTableRowId } from './DataTableController.js';
|
|
2
|
+
/**
|
|
3
|
+
* Fixed-height virtualization configuration for the DataTable body.
|
|
4
|
+
*
|
|
5
|
+
* Header groups and summary rows remain normal table sections and are not
|
|
6
|
+
* included in the virtual row count. Body rows must have a fixed height.
|
|
7
|
+
*/
|
|
8
|
+
export interface DataTableVirtualizationOptions {
|
|
9
|
+
/** The measured, fixed height of each data row in CSS pixels. */
|
|
10
|
+
rowHeight: number;
|
|
11
|
+
/**
|
|
12
|
+
* The visible height of the scrolling data viewport in CSS pixels. The
|
|
13
|
+
* component adds measured caption and header height around this body region.
|
|
14
|
+
*/
|
|
15
|
+
viewportHeight: number;
|
|
16
|
+
/** Extra rows rendered before and after the visible range. Defaults to 3. */
|
|
17
|
+
overscan?: number;
|
|
18
|
+
/** Controlled scroll position, used to restore a table after remounting. */
|
|
19
|
+
scrollTop?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Receives the current virtual scroll position. When a footer is present,
|
|
22
|
+
* its measured height extends the range so it remains reachable.
|
|
23
|
+
*/
|
|
24
|
+
onScrollTopChange?: (scrollTop: number) => void;
|
|
25
|
+
/**
|
|
26
|
+
* A stable row id that must remain visible. Use this with
|
|
27
|
+
* `onFocusedRowIdChange` to restore keyboard focus after a data refresh.
|
|
28
|
+
*/
|
|
29
|
+
focusedRowId?: DataTableRowId | null;
|
|
30
|
+
/** Receives the stable id of a row when focus enters that row. */
|
|
31
|
+
onFocusedRowIdChange?: (rowId: DataTableRowId) => void;
|
|
32
|
+
}
|
|
33
|
+
export type DataTableVirtualizationReason = 'disabled' | 'variable-row-height';
|
|
34
|
+
export interface DataTableVirtualizationContext {
|
|
35
|
+
options?: DataTableVirtualizationOptions;
|
|
36
|
+
rowCount: number;
|
|
37
|
+
scrollTop: number;
|
|
38
|
+
/** Expansion and body group rows produce variable heights and disable the window. */
|
|
39
|
+
hasVariableRowHeight?: boolean;
|
|
40
|
+
/** Structural header rows, including grouped headers. Excluded from the window. */
|
|
41
|
+
headerRowCount?: number;
|
|
42
|
+
/** Structural summary rows. Excluded from the window. */
|
|
43
|
+
summaryRowCount?: number;
|
|
44
|
+
}
|
|
45
|
+
export interface DataTableVirtualWindow {
|
|
46
|
+
enabled: boolean;
|
|
47
|
+
reason?: DataTableVirtualizationReason;
|
|
48
|
+
startIndex: number;
|
|
49
|
+
endIndex: number;
|
|
50
|
+
topSpacerHeight: number;
|
|
51
|
+
bottomSpacerHeight: number;
|
|
52
|
+
totalBodyHeight: number;
|
|
53
|
+
headerRowCount: number;
|
|
54
|
+
summaryRowCount: number;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Returns the maximum scroll position for a virtual body and an optional
|
|
58
|
+
* measured summary footer. Header and caption height are outside this range.
|
|
59
|
+
*/
|
|
60
|
+
export declare function maximumDataTableVirtualScrollTop(rowCount: number, options: Pick<DataTableVirtualizationOptions, 'rowHeight' | 'viewportHeight'>, footerHeight?: number): number;
|
|
61
|
+
/**
|
|
62
|
+
* Resolves the renderable data-row window without using source order as an
|
|
63
|
+
* identity. The caller retains the rows and slices by the returned indexes.
|
|
64
|
+
*/
|
|
65
|
+
export declare function resolveDataTableVirtualWindow(context: DataTableVirtualizationContext): DataTableVirtualWindow;
|
|
66
|
+
/**
|
|
67
|
+
* Returns the smallest fixed-height scroll position that reveals a stable row
|
|
68
|
+
* index, clamped to the body extent. Callers resolve the index from `rowKey`.
|
|
69
|
+
*/
|
|
70
|
+
export declare function scrollTopForDataTableRow(rowIndex: number, rowCount: number, options: Pick<DataTableVirtualizationOptions, 'rowHeight' | 'viewportHeight'>, currentScrollTop?: number): number;
|
|
71
|
+
//# sourceMappingURL=DataTableVirtualization.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DataTableVirtualization.d.ts","sourceRoot":"","sources":["../../../src/components/data/DataTableVirtualization.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,WAAW,8BAA8B;IAC7C,iEAAiE;IACjE,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD;;;OAGG;IACH,YAAY,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACrC,kEAAkE;IAClE,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;CACxD;AAED,MAAM,MAAM,6BAA6B,GAAG,UAAU,GAAG,qBAAqB,CAAC;AAE/E,MAAM,WAAW,8BAA8B;IAC7C,OAAO,CAAC,EAAE,8BAA8B,CAAC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,qFAAqF;IACrF,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,mFAAmF;IACnF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yDAAyD;IACzD,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,6BAA6B,CAAC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;CACzB;AAkCD;;;GAGG;AACH,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,IAAI,CAAC,8BAA8B,EAAE,WAAW,GAAG,gBAAgB,CAAC,EAC7E,YAAY,SAAI,GACf,MAAM,CAaR;AAqBD;;;GAGG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,8BAA8B,GACtC,sBAAsB,CAyDxB;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,IAAI,CAAC,8BAA8B,EAAE,WAAW,GAAG,gBAAgB,CAAC,EAC7E,gBAAgB,SAAI,GACnB,MAAM,CA0BR"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
function assertNonNegativeInteger(value, label) {
|
|
2
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
3
|
+
throw new TypeError(`DataTable virtualization ${label} must be a non-negative integer`);
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
function assertPositiveFinite(value, label) {
|
|
7
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
8
|
+
throw new TypeError(`DataTable virtualization ${label} must be a positive finite number`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function clampScrollTop(scrollTop, totalBodyHeight, viewportHeight) {
|
|
12
|
+
if (!Number.isFinite(scrollTop)) {
|
|
13
|
+
throw new TypeError('DataTable virtualization scrollTop must be a finite number');
|
|
14
|
+
}
|
|
15
|
+
return Math.min(Math.max(0, scrollTop), Math.max(0, totalBodyHeight - viewportHeight));
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Returns the maximum scroll position for a virtual body and an optional
|
|
19
|
+
* measured summary footer. Header and caption height are outside this range.
|
|
20
|
+
*/
|
|
21
|
+
export function maximumDataTableVirtualScrollTop(rowCount, options, footerHeight = 0) {
|
|
22
|
+
assertNonNegativeInteger(rowCount, 'rowCount');
|
|
23
|
+
assertPositiveFinite(options.rowHeight, 'rowHeight');
|
|
24
|
+
assertPositiveFinite(options.viewportHeight, 'viewportHeight');
|
|
25
|
+
if (!Number.isFinite(footerHeight) || footerHeight < 0) {
|
|
26
|
+
throw new TypeError('DataTable virtualization footerHeight must be a non-negative finite number');
|
|
27
|
+
}
|
|
28
|
+
return Math.max(0, rowCount * options.rowHeight - options.viewportHeight + footerHeight);
|
|
29
|
+
}
|
|
30
|
+
function staticWindow(rowCount, reason, headerRowCount, summaryRowCount) {
|
|
31
|
+
return {
|
|
32
|
+
enabled: false,
|
|
33
|
+
reason,
|
|
34
|
+
startIndex: 0,
|
|
35
|
+
endIndex: rowCount,
|
|
36
|
+
topSpacerHeight: 0,
|
|
37
|
+
bottomSpacerHeight: 0,
|
|
38
|
+
totalBodyHeight: 0,
|
|
39
|
+
headerRowCount,
|
|
40
|
+
summaryRowCount,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Resolves the renderable data-row window without using source order as an
|
|
45
|
+
* identity. The caller retains the rows and slices by the returned indexes.
|
|
46
|
+
*/
|
|
47
|
+
export function resolveDataTableVirtualWindow(context) {
|
|
48
|
+
const { options, rowCount, scrollTop, hasVariableRowHeight = false, headerRowCount = 0, summaryRowCount = 0, } = context;
|
|
49
|
+
assertNonNegativeInteger(rowCount, 'rowCount');
|
|
50
|
+
assertNonNegativeInteger(headerRowCount, 'headerRowCount');
|
|
51
|
+
assertNonNegativeInteger(summaryRowCount, 'summaryRowCount');
|
|
52
|
+
if (!options) {
|
|
53
|
+
return staticWindow(rowCount, 'disabled', headerRowCount, summaryRowCount);
|
|
54
|
+
}
|
|
55
|
+
const overscan = options.overscan ?? 3;
|
|
56
|
+
assertPositiveFinite(options.rowHeight, 'rowHeight');
|
|
57
|
+
assertPositiveFinite(options.viewportHeight, 'viewportHeight');
|
|
58
|
+
assertNonNegativeInteger(overscan, 'overscan');
|
|
59
|
+
if (hasVariableRowHeight) {
|
|
60
|
+
return staticWindow(rowCount, 'variable-row-height', headerRowCount, summaryRowCount);
|
|
61
|
+
}
|
|
62
|
+
const totalBodyHeight = rowCount * options.rowHeight;
|
|
63
|
+
const currentScrollTop = clampScrollTop(scrollTop, totalBodyHeight, options.viewportHeight);
|
|
64
|
+
const startIndex = Math.max(0, Math.floor(currentScrollTop / options.rowHeight) - overscan);
|
|
65
|
+
const endIndex = Math.min(rowCount, Math.ceil((currentScrollTop + options.viewportHeight) / options.rowHeight) +
|
|
66
|
+
overscan);
|
|
67
|
+
return {
|
|
68
|
+
enabled: true,
|
|
69
|
+
startIndex,
|
|
70
|
+
endIndex,
|
|
71
|
+
topSpacerHeight: startIndex * options.rowHeight,
|
|
72
|
+
bottomSpacerHeight: (rowCount - endIndex) * options.rowHeight,
|
|
73
|
+
totalBodyHeight,
|
|
74
|
+
headerRowCount,
|
|
75
|
+
summaryRowCount,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Returns the smallest fixed-height scroll position that reveals a stable row
|
|
80
|
+
* index, clamped to the body extent. Callers resolve the index from `rowKey`.
|
|
81
|
+
*/
|
|
82
|
+
export function scrollTopForDataTableRow(rowIndex, rowCount, options, currentScrollTop = 0) {
|
|
83
|
+
assertNonNegativeInteger(rowIndex, 'rowIndex');
|
|
84
|
+
assertNonNegativeInteger(rowCount, 'rowCount');
|
|
85
|
+
if (rowIndex >= rowCount) {
|
|
86
|
+
throw new RangeError('DataTable virtualization rowIndex must reference a data row');
|
|
87
|
+
}
|
|
88
|
+
assertPositiveFinite(options.rowHeight, 'rowHeight');
|
|
89
|
+
assertPositiveFinite(options.viewportHeight, 'viewportHeight');
|
|
90
|
+
const totalBodyHeight = rowCount * options.rowHeight;
|
|
91
|
+
const current = clampScrollTop(currentScrollTop, totalBodyHeight, options.viewportHeight);
|
|
92
|
+
const rowTop = rowIndex * options.rowHeight;
|
|
93
|
+
const rowBottom = rowTop + options.rowHeight;
|
|
94
|
+
const next = rowTop < current
|
|
95
|
+
? rowTop
|
|
96
|
+
: rowBottom > current + options.viewportHeight
|
|
97
|
+
? rowBottom - options.viewportHeight
|
|
98
|
+
: current;
|
|
99
|
+
return clampScrollTop(next, totalBodyHeight, options.viewportHeight);
|
|
100
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DataTable.bench.d.ts","sourceRoot":"","sources":["../../../../src/components/data/__benchmarks__/DataTable.bench.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { render } from '@testing-library/svelte';
|
|
2
|
+
import { bench, describe } from 'vitest';
|
|
3
|
+
import { createDataTablePerformanceColumns, createDataTablePerformanceRows, } from '../__fixtures__/DataTablePerformanceFixture.js';
|
|
4
|
+
import DataTable from '../DataTable.svelte';
|
|
5
|
+
import { createDataTableController } from '../DataTableController.js';
|
|
6
|
+
const localRows = createDataTablePerformanceRows(250, 18);
|
|
7
|
+
const localColumns = createDataTablePerformanceColumns(18);
|
|
8
|
+
const transformRows = createDataTablePerformanceRows(1_000, 18);
|
|
9
|
+
const manualPageRows = createDataTablePerformanceRows(100, 18);
|
|
10
|
+
const BenchmarkDataTable = DataTable;
|
|
11
|
+
describe('DataTable scale thresholds', () => {
|
|
12
|
+
bench('local render: 250 rows and 20 columns', () => {
|
|
13
|
+
const table = render(BenchmarkDataTable, {
|
|
14
|
+
props: { data: localRows, columns: localColumns, rowKey: 'id' },
|
|
15
|
+
});
|
|
16
|
+
table.unmount();
|
|
17
|
+
});
|
|
18
|
+
bench('client transforms: 1,000 rows and 20 columns', () => {
|
|
19
|
+
const controller = createDataTableController({
|
|
20
|
+
columnIds: localColumns.map((column) => column.id),
|
|
21
|
+
initialState: {
|
|
22
|
+
search: 'record 00',
|
|
23
|
+
sorting: [{ columnId: 'value0', direction: 'desc' }],
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
const table = render(BenchmarkDataTable, {
|
|
27
|
+
props: {
|
|
28
|
+
data: transformRows,
|
|
29
|
+
columns: localColumns,
|
|
30
|
+
rowKey: 'id',
|
|
31
|
+
controller,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
table.unmount();
|
|
35
|
+
});
|
|
36
|
+
bench('manual paging: 100 supplied rows from a 100,000-row remote result', () => {
|
|
37
|
+
const controller = createDataTableController({
|
|
38
|
+
columnIds: localColumns.map((column) => column.id),
|
|
39
|
+
modes: { filtering: 'manual', sorting: 'manual', pagination: 'manual' },
|
|
40
|
+
initialState: { page: 500, pageSize: 100 },
|
|
41
|
+
});
|
|
42
|
+
const table = render(BenchmarkDataTable, {
|
|
43
|
+
props: {
|
|
44
|
+
data: manualPageRows,
|
|
45
|
+
columns: localColumns,
|
|
46
|
+
rowKey: 'id',
|
|
47
|
+
controller,
|
|
48
|
+
totalRows: 100_000,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
table.unmount();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { DataTableColumn, DataTableStructuralRow } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Shared, domain-neutral data used to exercise the documented DataTable
|
|
4
|
+
* contracts in component tests and the interactive workbench preview.
|
|
5
|
+
*/
|
|
6
|
+
export interface DataTableConformanceRow {
|
|
7
|
+
id: string;
|
|
8
|
+
account: string;
|
|
9
|
+
team: string;
|
|
10
|
+
actual: number;
|
|
11
|
+
forecast: number;
|
|
12
|
+
variance: number;
|
|
13
|
+
status: 'On track' | 'Watch';
|
|
14
|
+
action: string;
|
|
15
|
+
}
|
|
16
|
+
export declare const dataTableConformanceRows: DataTableConformanceRow[];
|
|
17
|
+
export declare const dataTableConformanceColumns: DataTableColumn<DataTableConformanceRow>[];
|
|
18
|
+
export declare const dataTableConformanceStructuralRows: DataTableStructuralRow<DataTableConformanceRow>[];
|
|
19
|
+
/** The release-level scenarios that must remain represented in tests and the playground. */
|
|
20
|
+
export interface DataTableConformanceScenario {
|
|
21
|
+
id: 'semantic-interaction' | 'manual-query' | 'async-lifecycle' | 'responsive-overflow' | 'report-layout' | 'scale-virtualization';
|
|
22
|
+
title: string;
|
|
23
|
+
contracts: readonly string[];
|
|
24
|
+
}
|
|
25
|
+
export declare const dataTableConformanceScenarios: readonly DataTableConformanceScenario[];
|
|
26
|
+
export declare function createDataTableConformanceRows(rowCount: number): DataTableConformanceRow[];
|
|
27
|
+
//# sourceMappingURL=DataTableConformanceFixture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DataTableConformanceFixture.d.ts","sourceRoot":"","sources":["../../../../src/components/data/__fixtures__/DataTableConformanceFixture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAE3E;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,wBAAwB,EAAE,uBAAuB,EAqB7D,CAAC;AAEF,eAAO,MAAM,2BAA2B,EAAE,eAAe,CAAC,uBAAuB,CAAC,EAgD/E,CAAC;AAEJ,eAAO,MAAM,kCAAkC,EAAE,sBAAsB,CAAC,uBAAuB,CAAC,EAc7F,CAAC;AAEJ,4FAA4F;AAC5F,MAAM,WAAW,4BAA4B;IAC3C,EAAE,EACE,sBAAsB,GACtB,cAAc,GACd,iBAAiB,GACjB,qBAAqB,GACrB,eAAe,GACf,sBAAsB,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9B;AAED,eAAO,MAAM,6BAA6B,EAAE,SAAS,4BAA4B,EA8C9E,CAAC;AAEJ,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,MAAM,GACf,uBAAuB,EAAE,CAW3B"}
|