@rs-x/cli 2.0.0-next.2 → 2.0.0-next.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +5 -0
  2. package/bin/rsx.cjs +2868 -595
  3. package/package.json +5 -1
  4. package/{rs-x-vscode-extension-2.0.0-next.2.vsix → rs-x-vscode-extension-2.0.0-next.21.vsix} +0 -0
  5. package/scripts/prepare-local-rsx-packages.sh +20 -0
  6. package/scripts/verify-rsx-cli-mutations.sh +296 -0
  7. package/scripts/verify-rsx-projects.sh +220 -0
  8. package/scripts/verify-rsx-setup.sh +190 -0
  9. package/templates/angular-demo/README.md +115 -0
  10. package/templates/angular-demo/src/app/app.component.css +97 -0
  11. package/templates/angular-demo/src/app/app.component.html +58 -0
  12. package/templates/angular-demo/src/app/app.component.ts +52 -0
  13. package/templates/angular-demo/src/app/virtual-table/row-data.ts +35 -0
  14. package/templates/angular-demo/src/app/virtual-table/row-model.ts +45 -0
  15. package/templates/angular-demo/src/app/virtual-table/virtual-table-data.service.ts +136 -0
  16. package/templates/angular-demo/src/app/virtual-table/virtual-table-model.ts +224 -0
  17. package/templates/angular-demo/src/app/virtual-table/virtual-table.component.css +174 -0
  18. package/templates/angular-demo/src/app/virtual-table/virtual-table.component.html +50 -0
  19. package/templates/angular-demo/src/app/virtual-table/virtual-table.component.ts +83 -0
  20. package/templates/angular-demo/src/index.html +11 -0
  21. package/templates/angular-demo/src/main.ts +16 -0
  22. package/templates/angular-demo/src/styles.css +261 -0
  23. package/templates/next-demo/README.md +26 -0
  24. package/templates/next-demo/app/globals.css +431 -0
  25. package/templates/next-demo/app/layout.tsx +22 -0
  26. package/templates/next-demo/app/page.tsx +5 -0
  27. package/templates/next-demo/components/demo-app.tsx +114 -0
  28. package/templates/next-demo/components/virtual-table-row.tsx +40 -0
  29. package/templates/next-demo/components/virtual-table-shell.tsx +86 -0
  30. package/templates/next-demo/hooks/use-virtual-table-controller.ts +26 -0
  31. package/templates/next-demo/hooks/use-virtual-table-viewport.ts +41 -0
  32. package/templates/next-demo/lib/row-data.ts +35 -0
  33. package/templates/next-demo/lib/row-model.ts +45 -0
  34. package/templates/next-demo/lib/rsx-bootstrap.ts +46 -0
  35. package/templates/next-demo/lib/virtual-table-controller.ts +259 -0
  36. package/templates/next-demo/lib/virtual-table-data.service.ts +132 -0
  37. package/templates/react-demo/README.md +113 -0
  38. package/templates/react-demo/index.html +12 -0
  39. package/templates/react-demo/src/app/app.tsx +87 -0
  40. package/templates/react-demo/src/app/hooks/use-virtual-table-controller.ts +24 -0
  41. package/templates/react-demo/src/app/hooks/use-virtual-table-viewport.ts +39 -0
  42. package/templates/react-demo/src/app/virtual-table/row-data.ts +35 -0
  43. package/templates/react-demo/src/app/virtual-table/row-model.ts +45 -0
  44. package/templates/react-demo/src/app/virtual-table/virtual-table-controller.ts +259 -0
  45. package/templates/react-demo/src/app/virtual-table/virtual-table-data.service.ts +132 -0
  46. package/templates/react-demo/src/app/virtual-table/virtual-table-row.tsx +38 -0
  47. package/templates/react-demo/src/app/virtual-table/virtual-table-shell.tsx +84 -0
  48. package/templates/react-demo/src/main.tsx +24 -0
  49. package/templates/react-demo/src/rsx-bootstrap.ts +48 -0
  50. package/templates/react-demo/src/styles.css +422 -0
  51. package/templates/react-demo/tsconfig.json +17 -0
  52. package/templates/react-demo/vite.config.ts +6 -0
  53. package/templates/vue-demo/README.md +27 -0
  54. package/templates/vue-demo/src/App.vue +89 -0
  55. package/templates/vue-demo/src/components/VirtualTableRow.vue +33 -0
  56. package/templates/vue-demo/src/components/VirtualTableShell.vue +71 -0
  57. package/templates/vue-demo/src/composables/use-virtual-table-controller.ts +33 -0
  58. package/templates/vue-demo/src/composables/use-virtual-table-viewport.ts +40 -0
  59. package/templates/vue-demo/src/env.d.ts +10 -0
  60. package/templates/vue-demo/src/lib/row-data.ts +35 -0
  61. package/templates/vue-demo/src/lib/row-model.ts +45 -0
  62. package/templates/vue-demo/src/lib/rsx-bootstrap.ts +46 -0
  63. package/templates/vue-demo/src/lib/virtual-table-controller.ts +259 -0
  64. package/templates/vue-demo/src/lib/virtual-table-data.service.ts +132 -0
  65. package/templates/vue-demo/src/main.ts +13 -0
  66. package/templates/vue-demo/src/style.css +440 -0
@@ -0,0 +1,40 @@
1
+ import { getCurrentScope, onMounted, onScopeDispose, type Ref } from 'vue';
2
+
3
+ import type { VirtualTableController } from '../lib/virtual-table-controller';
4
+
5
+ const COMPACT_BREAKPOINT_PX = 720;
6
+ const DEFAULT_ROW_HEIGHT = 36;
7
+ const COMPACT_ROW_HEIGHT = 168;
8
+
9
+ export function useVirtualTableViewport(
10
+ controller: VirtualTableController,
11
+ viewportRef: Ref<HTMLDivElement | null>,
12
+ ): void {
13
+ let observer: ResizeObserver | undefined;
14
+
15
+ onMounted(() => {
16
+ const viewport = viewportRef.value;
17
+ if (!viewport) {
18
+ return;
19
+ }
20
+
21
+ const syncMetrics = (): void => {
22
+ controller.setViewportHeight(viewport.clientHeight);
23
+ controller.setRowHeight(
24
+ viewport.clientWidth <= COMPACT_BREAKPOINT_PX
25
+ ? COMPACT_ROW_HEIGHT
26
+ : DEFAULT_ROW_HEIGHT,
27
+ );
28
+ };
29
+
30
+ syncMetrics();
31
+ observer = new ResizeObserver(syncMetrics);
32
+ observer.observe(viewport);
33
+ });
34
+
35
+ if (getCurrentScope()) {
36
+ onScopeDispose(() => {
37
+ observer?.disconnect();
38
+ });
39
+ }
40
+ }
@@ -0,0 +1,10 @@
1
+ declare module '*.vue' {
2
+ import type { DefineComponent } from 'vue';
3
+
4
+ const component: DefineComponent<
5
+ Record<string, never>,
6
+ Record<string, never>,
7
+ unknown
8
+ >;
9
+ export default component;
10
+ }
@@ -0,0 +1,35 @@
1
+ export type SortKey = 'id' | 'name' | 'price' | 'quantity' | 'category';
2
+ export type SortDirection = 'asc' | 'desc';
3
+
4
+ export type RowData = {
5
+ id: number;
6
+ name: string;
7
+ price: number;
8
+ quantity: number;
9
+ category: string;
10
+ updatedAt: string;
11
+ };
12
+
13
+ const categories = ['Hardware', 'Software', 'Design', 'Ops'];
14
+
15
+ function pad(value: number): string {
16
+ return value.toString().padStart(2, '0');
17
+ }
18
+
19
+ export function createRowData(id: number): RowData {
20
+ const zeroBasedId = id - 1;
21
+ const price = 25 + (zeroBasedId % 1000) / 10;
22
+ const quantity = 1 + (Math.floor(zeroBasedId / 1000) % 100);
23
+ const category = categories[zeroBasedId % categories.length] ?? 'General';
24
+ const month = pad(((zeroBasedId * 7) % 12) + 1);
25
+ const day = pad(((zeroBasedId * 11) % 28) + 1);
26
+
27
+ return {
28
+ id,
29
+ name: `Product ${id.toString().padStart(7, '0')}`,
30
+ price,
31
+ quantity,
32
+ category,
33
+ updatedAt: `2026-${month}-${day}`,
34
+ };
35
+ }
@@ -0,0 +1,45 @@
1
+ import { type IExpression, rsx } from '@rs-x/expression-parser';
2
+
3
+ import type { RowData } from './row-data';
4
+
5
+ export type RowModel = {
6
+ model: RowData;
7
+ idExpr: IExpression<number>;
8
+ nameExpr: IExpression<string>;
9
+ categoryExpr: IExpression<string>;
10
+ priceExpr: IExpression<number>;
11
+ quantityExpr: IExpression<number>;
12
+ updatedAtExpr: IExpression<string>;
13
+ totalExpr: IExpression<number>;
14
+ };
15
+
16
+ export function createRowModel(): RowModel {
17
+ const model: RowData = {
18
+ id: 0,
19
+ name: '',
20
+ price: 0,
21
+ quantity: 0,
22
+ category: 'General',
23
+ updatedAt: '2026-01-01',
24
+ };
25
+
26
+ return {
27
+ model,
28
+ idExpr: rsx<number>('id')(model),
29
+ nameExpr: rsx<string>('name')(model),
30
+ categoryExpr: rsx<string>('category')(model),
31
+ priceExpr: rsx<number>('price')(model),
32
+ quantityExpr: rsx<number>('quantity')(model),
33
+ updatedAtExpr: rsx<string>('updatedAt')(model),
34
+ totalExpr: rsx<number>('price * quantity')(model),
35
+ };
36
+ }
37
+
38
+ export function updateRowModel(target: RowModel, data: RowData): void {
39
+ target.model.id = data.id;
40
+ target.model.name = data.name;
41
+ target.model.price = data.price;
42
+ target.model.quantity = data.quantity;
43
+ target.model.category = data.category;
44
+ target.model.updatedAt = data.updatedAt;
45
+ }
@@ -0,0 +1,46 @@
1
+ import { InjectionContainer } from '@rs-x/core';
2
+ import { RsXExpressionParserModule } from '@rs-x/expression-parser';
3
+
4
+ let initialized = false;
5
+
6
+ type RsxCompiledModule = {
7
+ registerRsxAotCompiledExpressions?: () => void;
8
+ };
9
+
10
+ type RsxPreparsedModule = {
11
+ registerRsxAotParsedExpressionCache?: () => void;
12
+ };
13
+
14
+ async function loadCompiledModule(): Promise<RsxCompiledModule> {
15
+ try {
16
+ return (await import(
17
+ '../rsx-generated/' + 'rsx-aot-compiled.generated'
18
+ )) as RsxCompiledModule;
19
+ } catch {
20
+ return {};
21
+ }
22
+ }
23
+
24
+ async function loadPreparsedModule(): Promise<RsxPreparsedModule> {
25
+ try {
26
+ return (await import(
27
+ '../rsx-generated/' + 'rsx-aot-preparsed.generated'
28
+ )) as RsxPreparsedModule;
29
+ } catch {
30
+ return {};
31
+ }
32
+ }
33
+
34
+ export async function initRsx(): Promise<void> {
35
+ if (initialized) {
36
+ return;
37
+ }
38
+
39
+ const preparsedModule = await loadPreparsedModule();
40
+ const compiledModule = await loadCompiledModule();
41
+
42
+ preparsedModule.registerRsxAotParsedExpressionCache?.();
43
+ compiledModule.registerRsxAotCompiledExpressions?.();
44
+ await InjectionContainer.load(RsXExpressionParserModule);
45
+ initialized = true;
46
+ }
@@ -0,0 +1,259 @@
1
+ import { type RowData, type SortDirection, type SortKey } from './row-data';
2
+ import { createRowModel, type RowModel, updateRowModel } from './row-model';
3
+ import { VirtualTableDataService } from './virtual-table-data.service';
4
+
5
+ export type RowView = {
6
+ index: number;
7
+ top: number;
8
+ row: RowModel;
9
+ };
10
+
11
+ export type VirtualTableSnapshot = {
12
+ visibleRows: RowView[];
13
+ totalRows: number;
14
+ poolSize: number;
15
+ rowsInView: number;
16
+ loadedPageCount: number;
17
+ spacerHeight: number;
18
+ sortKey: SortKey;
19
+ sortDirection: SortDirection;
20
+ };
21
+
22
+ const ROW_HEIGHT = 36;
23
+ const PAGE_SIZE = 50;
24
+ const POOL_PAGES = 4;
25
+ const CACHE_PADDING_PAGES = 2;
26
+ const RETAIN_PADDING_PAGES = 4;
27
+
28
+ export class VirtualTableController {
29
+ public rowHeight = ROW_HEIGHT;
30
+ public readonly pageSize = PAGE_SIZE;
31
+ public readonly poolSize = PAGE_SIZE * POOL_PAGES;
32
+ public readonly totalRows: number;
33
+
34
+ private scrollTop = 0;
35
+ private viewportHeight = 520;
36
+ private sortKey: SortKey = 'id';
37
+ private sortDirection: SortDirection = 'asc';
38
+ private spacerHeight: number;
39
+ private rowsInView = Math.max(
40
+ 1,
41
+ Math.ceil(this.viewportHeight / this.rowHeight),
42
+ );
43
+ private visibleRows: RowView[] = [];
44
+ private snapshot: VirtualTableSnapshot;
45
+
46
+ private readonly listeners = new Set<() => void>();
47
+ private readonly pool = Array.from({ length: PAGE_SIZE * POOL_PAGES }, () =>
48
+ createRowModel(),
49
+ );
50
+ private readonly dataByIndex = new Map<number, RowData>();
51
+ private readonly loadedPages = new Set<number>();
52
+ private readonly pageLoading = new Map<number, Promise<void>>();
53
+ private readonly dataService = new VirtualTableDataService();
54
+
55
+ public constructor() {
56
+ this.totalRows = this.dataService.totalRows;
57
+ this.spacerHeight = this.totalRows * this.rowHeight;
58
+ this.snapshot = {
59
+ visibleRows: this.visibleRows,
60
+ totalRows: this.totalRows,
61
+ poolSize: this.poolSize,
62
+ rowsInView: this.rowsInView,
63
+ loadedPageCount: this.loadedPages.size,
64
+ spacerHeight: this.spacerHeight,
65
+ sortKey: this.sortKey,
66
+ sortDirection: this.sortDirection,
67
+ };
68
+ this.refresh();
69
+ }
70
+
71
+ public subscribe = (listener: () => void): (() => void) => {
72
+ this.listeners.add(listener);
73
+ return () => {
74
+ this.listeners.delete(listener);
75
+ };
76
+ };
77
+
78
+ public getSnapshot = (): VirtualTableSnapshot => {
79
+ return this.snapshot;
80
+ };
81
+
82
+ public setViewportHeight(height: number): void {
83
+ this.viewportHeight = height;
84
+ this.rowsInView = Math.max(
85
+ 1,
86
+ Math.ceil(this.viewportHeight / this.rowHeight),
87
+ );
88
+ this.refresh();
89
+ }
90
+
91
+ public setRowHeight(height: number): void {
92
+ if (this.rowHeight === height) {
93
+ return;
94
+ }
95
+
96
+ this.rowHeight = height;
97
+ this.spacerHeight = this.totalRows * this.rowHeight;
98
+ this.rowsInView = Math.max(
99
+ 1,
100
+ Math.ceil(this.viewportHeight / this.rowHeight),
101
+ );
102
+ this.refresh();
103
+ }
104
+
105
+ public setScrollTop(value: number): void {
106
+ this.scrollTop = value;
107
+ this.refresh();
108
+ }
109
+
110
+ public toggleSort(nextKey: SortKey): void {
111
+ if (this.sortKey === nextKey) {
112
+ this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
113
+ } else {
114
+ this.sortKey = nextKey;
115
+ this.sortDirection = 'asc';
116
+ }
117
+
118
+ this.resetLoadedData();
119
+ this.refresh();
120
+ }
121
+
122
+ private refresh(): void {
123
+ const scrollIndex = Math.floor(this.scrollTop / this.rowHeight);
124
+ const bufferTop = Math.max(
125
+ 0,
126
+ Math.floor((this.poolSize - this.rowsInView) / 2),
127
+ );
128
+ const maxStart = Math.max(0, this.totalRows - this.poolSize);
129
+ const startIndex = Math.min(Math.max(scrollIndex - bufferTop, 0), maxStart);
130
+ const endIndex = Math.min(startIndex + this.poolSize, this.totalRows);
131
+ const startPage = Math.max(
132
+ 0,
133
+ Math.floor(startIndex / this.pageSize) - CACHE_PADDING_PAGES,
134
+ );
135
+ const endPage = Math.min(
136
+ Math.floor((endIndex - 1) / this.pageSize) + CACHE_PADDING_PAGES,
137
+ Math.floor((this.totalRows - 1) / this.pageSize),
138
+ );
139
+
140
+ this.ensurePages(startPage, endPage);
141
+ this.pruneCachedPages(startPage, endPage);
142
+
143
+ const nextRows: RowView[] = [];
144
+ const length = endIndex - startIndex;
145
+
146
+ for (let offset = 0; offset < length; offset += 1) {
147
+ const index = startIndex + offset;
148
+ const target = this.pool[offset];
149
+ updateRowModel(target, this.getRowData(index));
150
+
151
+ nextRows.push({
152
+ index,
153
+ top: index * this.rowHeight,
154
+ row: target,
155
+ });
156
+ }
157
+
158
+ this.visibleRows = nextRows;
159
+ this.snapshot = {
160
+ visibleRows: this.visibleRows,
161
+ totalRows: this.totalRows,
162
+ poolSize: this.poolSize,
163
+ rowsInView: this.rowsInView,
164
+ loadedPageCount: this.loadedPages.size,
165
+ spacerHeight: this.spacerHeight,
166
+ sortKey: this.sortKey,
167
+ sortDirection: this.sortDirection,
168
+ };
169
+ this.emit();
170
+ }
171
+
172
+ private emit(): void {
173
+ for (const listener of this.listeners) {
174
+ listener();
175
+ }
176
+ }
177
+
178
+ private ensurePages(startPage: number, endPage: number): void {
179
+ for (let pageIndex = startPage; pageIndex <= endPage; pageIndex += 1) {
180
+ this.ensurePageLoaded(pageIndex);
181
+ }
182
+ }
183
+
184
+ private ensurePageLoaded(pageIndex: number): void {
185
+ if (this.loadedPages.has(pageIndex) || this.pageLoading.has(pageIndex)) {
186
+ return;
187
+ }
188
+
189
+ const task = this.loadPageAsync(pageIndex).finally(() => {
190
+ this.pageLoading.delete(pageIndex);
191
+ this.loadedPages.add(pageIndex);
192
+ this.refresh();
193
+ });
194
+
195
+ this.pageLoading.set(pageIndex, task);
196
+ }
197
+
198
+ private async loadPageAsync(pageIndex: number): Promise<void> {
199
+ const page = await this.dataService.fetchPage(
200
+ pageIndex,
201
+ this.pageSize,
202
+ this.sortKey,
203
+ this.sortDirection,
204
+ );
205
+ const startIndex = pageIndex * this.pageSize;
206
+
207
+ for (let offset = 0; offset < page.items.length; offset += 1) {
208
+ const item = page.items[offset];
209
+ if (!item) {
210
+ continue;
211
+ }
212
+
213
+ this.dataByIndex.set(startIndex + offset, item);
214
+ }
215
+ }
216
+
217
+ private getRowData(index: number): RowData {
218
+ const cached = this.dataByIndex.get(index);
219
+ if (cached) {
220
+ return cached;
221
+ }
222
+
223
+ return {
224
+ id: index + 1,
225
+ name: 'Loading...',
226
+ price: 0,
227
+ quantity: 0,
228
+ category: 'Pending',
229
+ updatedAt: '--',
230
+ };
231
+ }
232
+
233
+ private resetLoadedData(): void {
234
+ this.dataByIndex.clear();
235
+ this.loadedPages.clear();
236
+ this.pageLoading.clear();
237
+ }
238
+
239
+ private pruneCachedPages(startPage: number, endPage: number): void {
240
+ const minPage = Math.max(0, startPage - RETAIN_PADDING_PAGES);
241
+ const maxPage = Math.min(
242
+ Math.floor((this.totalRows - 1) / this.pageSize),
243
+ endPage + RETAIN_PADDING_PAGES,
244
+ );
245
+
246
+ for (const pageIndex of Array.from(this.loadedPages)) {
247
+ if (pageIndex >= minPage && pageIndex <= maxPage) {
248
+ continue;
249
+ }
250
+
251
+ this.loadedPages.delete(pageIndex);
252
+ const pageStart = pageIndex * this.pageSize;
253
+ const pageEnd = Math.min(pageStart + this.pageSize, this.totalRows);
254
+ for (let index = pageStart; index < pageEnd; index += 1) {
255
+ this.dataByIndex.delete(index);
256
+ }
257
+ }
258
+ }
259
+ }
@@ -0,0 +1,132 @@
1
+ import {
2
+ createRowData,
3
+ type RowData,
4
+ type SortDirection,
5
+ type SortKey,
6
+ } from './row-data';
7
+
8
+ export type VirtualTablePage = {
9
+ total: number;
10
+ items: RowData[];
11
+ };
12
+
13
+ const TOTAL_ROWS = 1_000_000;
14
+ const REQUEST_DELAY_MS = 120;
15
+ const CATEGORY_COUNT = 4;
16
+ const PRICE_BUCKET_COUNT = 1_000;
17
+ const QUANTITY_BUCKET_COUNT = 100;
18
+ const MAX_CACHED_PAGES = 24;
19
+
20
+ export class VirtualTableDataService {
21
+ private readonly pageCache = new Map<string, VirtualTablePage>();
22
+
23
+ public get totalRows(): number {
24
+ return TOTAL_ROWS;
25
+ }
26
+
27
+ public async fetchPage(
28
+ pageIndex: number,
29
+ pageSize: number,
30
+ sortKey: SortKey,
31
+ sortDirection: SortDirection,
32
+ ): Promise<VirtualTablePage> {
33
+ const cacheKey = `${sortKey}:${sortDirection}:${pageIndex}:${pageSize}`;
34
+ const cached = this.pageCache.get(cacheKey);
35
+ if (cached) {
36
+ return cached;
37
+ }
38
+
39
+ await this.delay(REQUEST_DELAY_MS + (pageIndex % 5) * 35);
40
+
41
+ const startIndex = pageIndex * pageSize;
42
+ const items: RowData[] = [];
43
+ const endIndex = Math.min(startIndex + pageSize, TOTAL_ROWS);
44
+
45
+ for (
46
+ let visualIndex = startIndex;
47
+ visualIndex < endIndex;
48
+ visualIndex += 1
49
+ ) {
50
+ const id = this.getIdAtVisualIndex(visualIndex, sortKey, sortDirection);
51
+ items.push(createRowData(id));
52
+ }
53
+
54
+ const page = { total: TOTAL_ROWS, items };
55
+ this.pageCache.set(cacheKey, page);
56
+ this.trimCache();
57
+ return page;
58
+ }
59
+
60
+ private getIdAtVisualIndex(
61
+ visualIndex: number,
62
+ sortKey: SortKey,
63
+ sortDirection: SortDirection,
64
+ ): number {
65
+ const normalizedIndex =
66
+ sortDirection === 'asc' ? visualIndex : TOTAL_ROWS - 1 - visualIndex;
67
+
68
+ if (sortKey === 'price') {
69
+ return this.getPriceSortedId(normalizedIndex);
70
+ }
71
+
72
+ if (sortKey === 'quantity') {
73
+ return this.getQuantitySortedId(normalizedIndex);
74
+ }
75
+
76
+ if (sortKey === 'category') {
77
+ return this.getCategorySortedId(normalizedIndex);
78
+ }
79
+
80
+ return normalizedIndex + 1;
81
+ }
82
+
83
+ private getPriceSortedId(visualIndex: number): number {
84
+ const groupSize = TOTAL_ROWS / PRICE_BUCKET_COUNT;
85
+ const priceBucket = Math.floor(visualIndex / groupSize);
86
+ const offsetInBucket = visualIndex % groupSize;
87
+
88
+ return priceBucket + offsetInBucket * PRICE_BUCKET_COUNT + 1;
89
+ }
90
+
91
+ private getQuantitySortedId(visualIndex: number): number {
92
+ const groupSize = TOTAL_ROWS / QUANTITY_BUCKET_COUNT;
93
+ const quantityBucket = Math.floor(visualIndex / groupSize);
94
+ const offsetInBucket = visualIndex % groupSize;
95
+ const quantityStride = PRICE_BUCKET_COUNT * QUANTITY_BUCKET_COUNT;
96
+ const quantityBlock = Math.floor(offsetInBucket / PRICE_BUCKET_COUNT);
97
+ const priceBucket = offsetInBucket % PRICE_BUCKET_COUNT;
98
+
99
+ return (
100
+ priceBucket +
101
+ quantityBucket * PRICE_BUCKET_COUNT +
102
+ quantityBlock * quantityStride +
103
+ 1
104
+ );
105
+ }
106
+
107
+ private getCategorySortedId(visualIndex: number): number {
108
+ const groupSize = TOTAL_ROWS / CATEGORY_COUNT;
109
+ const categoryBucket = Math.floor(visualIndex / groupSize);
110
+ const offsetInBucket = visualIndex % groupSize;
111
+
112
+ return categoryBucket + offsetInBucket * CATEGORY_COUNT + 1;
113
+ }
114
+
115
+ private delay(durationMs: number): Promise<void> {
116
+ return new Promise((resolve) => {
117
+ window.setTimeout(resolve, durationMs);
118
+ });
119
+ }
120
+
121
+ private trimCache(): void {
122
+ while (this.pageCache.size > MAX_CACHED_PAGES) {
123
+ const oldestKey = this.pageCache.keys().next().value as
124
+ | string
125
+ | undefined;
126
+ if (!oldestKey) {
127
+ return;
128
+ }
129
+ this.pageCache.delete(oldestKey);
130
+ }
131
+ }
132
+ }
@@ -0,0 +1,13 @@
1
+ import { createApp } from 'vue';
2
+
3
+ import { initRsx } from './lib/rsx-bootstrap';
4
+ import App from './App.vue';
5
+
6
+ import './style.css';
7
+
8
+ async function bootstrap(): Promise<void> {
9
+ await initRsx();
10
+ createApp(App).mount('#app');
11
+ }
12
+
13
+ void bootstrap();