@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.
- package/README.md +5 -0
- package/bin/rsx.cjs +2868 -595
- package/package.json +5 -1
- package/{rs-x-vscode-extension-2.0.0-next.2.vsix → rs-x-vscode-extension-2.0.0-next.21.vsix} +0 -0
- package/scripts/prepare-local-rsx-packages.sh +20 -0
- package/scripts/verify-rsx-cli-mutations.sh +296 -0
- package/scripts/verify-rsx-projects.sh +220 -0
- package/scripts/verify-rsx-setup.sh +190 -0
- package/templates/angular-demo/README.md +115 -0
- package/templates/angular-demo/src/app/app.component.css +97 -0
- package/templates/angular-demo/src/app/app.component.html +58 -0
- package/templates/angular-demo/src/app/app.component.ts +52 -0
- package/templates/angular-demo/src/app/virtual-table/row-data.ts +35 -0
- package/templates/angular-demo/src/app/virtual-table/row-model.ts +45 -0
- package/templates/angular-demo/src/app/virtual-table/virtual-table-data.service.ts +136 -0
- package/templates/angular-demo/src/app/virtual-table/virtual-table-model.ts +224 -0
- package/templates/angular-demo/src/app/virtual-table/virtual-table.component.css +174 -0
- package/templates/angular-demo/src/app/virtual-table/virtual-table.component.html +50 -0
- package/templates/angular-demo/src/app/virtual-table/virtual-table.component.ts +83 -0
- package/templates/angular-demo/src/index.html +11 -0
- package/templates/angular-demo/src/main.ts +16 -0
- package/templates/angular-demo/src/styles.css +261 -0
- package/templates/next-demo/README.md +26 -0
- package/templates/next-demo/app/globals.css +431 -0
- package/templates/next-demo/app/layout.tsx +22 -0
- package/templates/next-demo/app/page.tsx +5 -0
- package/templates/next-demo/components/demo-app.tsx +114 -0
- package/templates/next-demo/components/virtual-table-row.tsx +40 -0
- package/templates/next-demo/components/virtual-table-shell.tsx +86 -0
- package/templates/next-demo/hooks/use-virtual-table-controller.ts +26 -0
- package/templates/next-demo/hooks/use-virtual-table-viewport.ts +41 -0
- package/templates/next-demo/lib/row-data.ts +35 -0
- package/templates/next-demo/lib/row-model.ts +45 -0
- package/templates/next-demo/lib/rsx-bootstrap.ts +46 -0
- package/templates/next-demo/lib/virtual-table-controller.ts +259 -0
- package/templates/next-demo/lib/virtual-table-data.service.ts +132 -0
- package/templates/react-demo/README.md +113 -0
- package/templates/react-demo/index.html +12 -0
- package/templates/react-demo/src/app/app.tsx +87 -0
- package/templates/react-demo/src/app/hooks/use-virtual-table-controller.ts +24 -0
- package/templates/react-demo/src/app/hooks/use-virtual-table-viewport.ts +39 -0
- package/templates/react-demo/src/app/virtual-table/row-data.ts +35 -0
- package/templates/react-demo/src/app/virtual-table/row-model.ts +45 -0
- package/templates/react-demo/src/app/virtual-table/virtual-table-controller.ts +259 -0
- package/templates/react-demo/src/app/virtual-table/virtual-table-data.service.ts +132 -0
- package/templates/react-demo/src/app/virtual-table/virtual-table-row.tsx +38 -0
- package/templates/react-demo/src/app/virtual-table/virtual-table-shell.tsx +84 -0
- package/templates/react-demo/src/main.tsx +24 -0
- package/templates/react-demo/src/rsx-bootstrap.ts +48 -0
- package/templates/react-demo/src/styles.css +422 -0
- package/templates/react-demo/tsconfig.json +17 -0
- package/templates/react-demo/vite.config.ts +6 -0
- package/templates/vue-demo/README.md +27 -0
- package/templates/vue-demo/src/App.vue +89 -0
- package/templates/vue-demo/src/components/VirtualTableRow.vue +33 -0
- package/templates/vue-demo/src/components/VirtualTableShell.vue +71 -0
- package/templates/vue-demo/src/composables/use-virtual-table-controller.ts +33 -0
- package/templates/vue-demo/src/composables/use-virtual-table-viewport.ts +40 -0
- package/templates/vue-demo/src/env.d.ts +10 -0
- package/templates/vue-demo/src/lib/row-data.ts +35 -0
- package/templates/vue-demo/src/lib/row-model.ts +45 -0
- package/templates/vue-demo/src/lib/rsx-bootstrap.ts +46 -0
- package/templates/vue-demo/src/lib/virtual-table-controller.ts +259 -0
- package/templates/vue-demo/src/lib/virtual-table-data.service.ts +132 -0
- package/templates/vue-demo/src/main.ts +13 -0
- package/templates/vue-demo/src/style.css +440 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--font-sans:
|
|
3
|
+
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
|
4
|
+
sans-serif;
|
|
5
|
+
--font-mono:
|
|
6
|
+
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Courier New',
|
|
7
|
+
monospace;
|
|
8
|
+
--bg: #f6f8fc;
|
|
9
|
+
--surface: rgba(255, 255, 255, 0.86);
|
|
10
|
+
--surface-2: rgba(255, 255, 255, 0.72);
|
|
11
|
+
--surface-solid: #ffffff;
|
|
12
|
+
--text: #0b1324;
|
|
13
|
+
--muted: rgba(11, 19, 36, 0.66);
|
|
14
|
+
--border: rgba(10, 25, 55, 0.14);
|
|
15
|
+
--border-soft: rgba(10, 25, 55, 0.1);
|
|
16
|
+
--brand: #0b66ff;
|
|
17
|
+
--brand-2: #2bb6a9;
|
|
18
|
+
--focus: rgba(11, 102, 255, 0.35);
|
|
19
|
+
--shadow-1:
|
|
20
|
+
0 1px 2px rgba(16, 24, 40, 0.06), 0 12px 34px rgba(16, 24, 40, 0.1);
|
|
21
|
+
--shadow-2:
|
|
22
|
+
0 2px 10px rgba(16, 24, 40, 0.08), 0 18px 52px rgba(16, 24, 40, 0.12);
|
|
23
|
+
--page-glow-a: rgba(11, 102, 255, 0.06);
|
|
24
|
+
--page-glow-b: rgba(43, 182, 169, 0.05);
|
|
25
|
+
--hero-section-bg: linear-gradient(
|
|
26
|
+
180deg,
|
|
27
|
+
rgba(255, 255, 255, 0.72),
|
|
28
|
+
rgba(255, 255, 255, 0.42)
|
|
29
|
+
);
|
|
30
|
+
--r-xl: 1.75rem;
|
|
31
|
+
--sp-2: 0.5rem;
|
|
32
|
+
--sp-3: 0.75rem;
|
|
33
|
+
--sp-4: 1rem;
|
|
34
|
+
--sp-5: 1.25rem;
|
|
35
|
+
--sp-6: 1.5rem;
|
|
36
|
+
--sp-7: 2rem;
|
|
37
|
+
--sp-8: 2.5rem;
|
|
38
|
+
--sp-9: 3rem;
|
|
39
|
+
--sp-10: 4rem;
|
|
40
|
+
--container: 1120px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
html[data-theme='dark'],
|
|
44
|
+
body[data-theme='dark'] {
|
|
45
|
+
--bg: #070b14;
|
|
46
|
+
--surface: rgba(12, 18, 35, 0.82);
|
|
47
|
+
--surface-2: rgba(12, 18, 35, 0.68);
|
|
48
|
+
--surface-solid: #0c1223;
|
|
49
|
+
--text: #edf2ff;
|
|
50
|
+
--muted: rgba(237, 242, 255, 0.7);
|
|
51
|
+
--border: rgba(237, 242, 255, 0.16);
|
|
52
|
+
--border-soft: rgba(237, 242, 255, 0.12);
|
|
53
|
+
--brand: #7ab6ff;
|
|
54
|
+
--brand-2: #49d9c8;
|
|
55
|
+
--focus: rgba(122, 182, 255, 0.35);
|
|
56
|
+
--shadow-1: 0 1px 2px rgba(0, 0, 0, 0.22), 0 12px 34px rgba(0, 0, 0, 0.32);
|
|
57
|
+
--shadow-2: 0 2px 10px rgba(0, 0, 0, 0.24), 0 18px 52px rgba(0, 0, 0, 0.34);
|
|
58
|
+
--page-glow-a: rgba(122, 182, 255, 0.1);
|
|
59
|
+
--page-glow-b: rgba(73, 217, 200, 0.08);
|
|
60
|
+
--hero-section-bg: linear-gradient(
|
|
61
|
+
180deg,
|
|
62
|
+
rgba(12, 18, 35, 0.56),
|
|
63
|
+
rgba(12, 18, 35, 0.24)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
*,
|
|
68
|
+
*::before,
|
|
69
|
+
*::after {
|
|
70
|
+
box-sizing: border-box;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
html,
|
|
74
|
+
body,
|
|
75
|
+
#root {
|
|
76
|
+
min-height: 100%;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
html {
|
|
80
|
+
color-scheme: dark;
|
|
81
|
+
background: var(--bg);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
html[data-theme='light'] {
|
|
85
|
+
color-scheme: light;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
body {
|
|
89
|
+
margin: 0;
|
|
90
|
+
font-family: var(--font-sans);
|
|
91
|
+
color: var(--text);
|
|
92
|
+
background-color: var(--bg);
|
|
93
|
+
background:
|
|
94
|
+
radial-gradient(circle at top, var(--page-glow-a), transparent 32%),
|
|
95
|
+
radial-gradient(circle at 85% 12%, var(--page-glow-b), transparent 28%),
|
|
96
|
+
var(--bg);
|
|
97
|
+
transition:
|
|
98
|
+
background 180ms ease,
|
|
99
|
+
color 180ms ease;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
button,
|
|
103
|
+
input,
|
|
104
|
+
select,
|
|
105
|
+
textarea {
|
|
106
|
+
font: inherit;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.container {
|
|
110
|
+
max-width: var(--container);
|
|
111
|
+
margin: 0 auto;
|
|
112
|
+
padding: 0 var(--sp-6);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.app-shell {
|
|
116
|
+
min-height: 100vh;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.hero {
|
|
120
|
+
position: relative;
|
|
121
|
+
padding: calc(var(--sp-10) + 1rem) 0 calc(var(--sp-8) + 0.5rem);
|
|
122
|
+
background: var(--hero-section-bg);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.heroGrid {
|
|
126
|
+
display: grid;
|
|
127
|
+
grid-template-columns: minmax(540px, 1.05fr) minmax(300px, 0.95fr);
|
|
128
|
+
column-gap: var(--sp-6);
|
|
129
|
+
row-gap: var(--sp-8);
|
|
130
|
+
align-items: center;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.heroLeft {
|
|
134
|
+
min-width: 0;
|
|
135
|
+
padding-top: var(--sp-4);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.app-eyebrow {
|
|
139
|
+
margin: 0 0 var(--sp-4);
|
|
140
|
+
letter-spacing: 0.2em;
|
|
141
|
+
text-transform: uppercase;
|
|
142
|
+
font-size: 0.82rem;
|
|
143
|
+
font-weight: 700;
|
|
144
|
+
color: var(--brand);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.hTitle {
|
|
148
|
+
margin: 0 0 var(--sp-5);
|
|
149
|
+
font-size: clamp(3.6rem, 5.2vw, 4.8rem);
|
|
150
|
+
line-height: 0.96;
|
|
151
|
+
letter-spacing: -0.052em;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.hSubhead {
|
|
155
|
+
margin: 0 0 var(--sp-2);
|
|
156
|
+
font-size: clamp(1.42rem, 2vw, 1.82rem);
|
|
157
|
+
font-weight: 640;
|
|
158
|
+
letter-spacing: -0.03em;
|
|
159
|
+
line-height: 1.08;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.hSub {
|
|
163
|
+
margin: var(--sp-5) 0 0;
|
|
164
|
+
color: var(--muted);
|
|
165
|
+
font-size: 1.1rem;
|
|
166
|
+
line-height: 1.72;
|
|
167
|
+
max-width: 31.5rem;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.heroActions {
|
|
171
|
+
display: flex;
|
|
172
|
+
flex-wrap: wrap;
|
|
173
|
+
gap: var(--sp-3);
|
|
174
|
+
margin-top: var(--sp-5);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.btn {
|
|
178
|
+
display: inline-flex;
|
|
179
|
+
align-items: center;
|
|
180
|
+
justify-content: center;
|
|
181
|
+
gap: var(--sp-2);
|
|
182
|
+
padding: 0.82rem 1.18rem;
|
|
183
|
+
border-radius: 14px;
|
|
184
|
+
border: 1px solid transparent;
|
|
185
|
+
font-weight: 800;
|
|
186
|
+
line-height: 1;
|
|
187
|
+
white-space: nowrap;
|
|
188
|
+
transition:
|
|
189
|
+
transform 160ms ease,
|
|
190
|
+
background 240ms ease,
|
|
191
|
+
border-color 240ms ease,
|
|
192
|
+
box-shadow 240ms ease;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.btn:active {
|
|
196
|
+
transform: translateY(1px);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.btnGhost {
|
|
200
|
+
background: var(--surface-2);
|
|
201
|
+
color: var(--text);
|
|
202
|
+
border-color: var(--border);
|
|
203
|
+
box-shadow: none;
|
|
204
|
+
text-decoration: none;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.btnGhost:hover {
|
|
208
|
+
box-shadow: var(--shadow-1);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.card {
|
|
212
|
+
position: relative;
|
|
213
|
+
min-width: 0;
|
|
214
|
+
border: 1px solid var(--border-soft);
|
|
215
|
+
background: linear-gradient(180deg, var(--surface), var(--surface-2));
|
|
216
|
+
border-radius: var(--r-xl);
|
|
217
|
+
padding: var(--sp-6);
|
|
218
|
+
box-shadow: var(--shadow-1);
|
|
219
|
+
display: flex;
|
|
220
|
+
flex-direction: column;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.cardTitle {
|
|
224
|
+
margin: 0 0 var(--sp-3);
|
|
225
|
+
font-weight: 700;
|
|
226
|
+
font-size: clamp(1.12rem, 0.96vw, 1.22rem);
|
|
227
|
+
letter-spacing: -0.02em;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.cardText {
|
|
231
|
+
margin: 0;
|
|
232
|
+
color: var(--muted);
|
|
233
|
+
line-height: 1.62;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.heroNote {
|
|
237
|
+
align-self: center;
|
|
238
|
+
margin-top: 0;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.section {
|
|
242
|
+
padding: var(--sp-7) 0 var(--sp-10);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.app-panel {
|
|
246
|
+
backdrop-filter: blur(12px);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.theme-toggle {
|
|
250
|
+
cursor: pointer;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.table-toolbar {
|
|
254
|
+
display: flex;
|
|
255
|
+
align-items: center;
|
|
256
|
+
justify-content: space-between;
|
|
257
|
+
gap: 16px;
|
|
258
|
+
padding-bottom: 16px;
|
|
259
|
+
border-bottom: 1px solid var(--border-soft);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.toolbar-left h2 {
|
|
263
|
+
margin: 0;
|
|
264
|
+
font-size: 20px;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.toolbar-left p {
|
|
268
|
+
margin: 4px 0 0;
|
|
269
|
+
color: var(--muted);
|
|
270
|
+
font-size: 13px;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.toolbar-right {
|
|
274
|
+
display: flex;
|
|
275
|
+
flex-wrap: wrap;
|
|
276
|
+
gap: 8px;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.toolbar-right button {
|
|
280
|
+
border: 1px solid var(--border);
|
|
281
|
+
background: color-mix(in srgb, var(--surface-solid) 88%, var(--brand) 12%);
|
|
282
|
+
color: var(--text);
|
|
283
|
+
padding: 6px 12px;
|
|
284
|
+
border-radius: 999px;
|
|
285
|
+
font-size: 12px;
|
|
286
|
+
cursor: pointer;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.toolbar-right button:hover {
|
|
290
|
+
border-color: var(--focus);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.table-header {
|
|
294
|
+
display: grid;
|
|
295
|
+
grid-template-columns: 80px 1.4fr 1fr 0.8fr 0.6fr 1fr 0.9fr;
|
|
296
|
+
gap: 8px;
|
|
297
|
+
padding: 12px 8px;
|
|
298
|
+
font-size: 12px;
|
|
299
|
+
font-weight: 600;
|
|
300
|
+
color: var(--muted);
|
|
301
|
+
text-transform: uppercase;
|
|
302
|
+
letter-spacing: 0.06em;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.table-viewport {
|
|
306
|
+
position: relative;
|
|
307
|
+
height: 520px;
|
|
308
|
+
overflow: auto;
|
|
309
|
+
border: 1px solid var(--border-soft);
|
|
310
|
+
border-radius: 12px;
|
|
311
|
+
background: color-mix(in srgb, var(--surface-solid) 94%, transparent);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.table-spacer {
|
|
315
|
+
width: 100%;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.table-row {
|
|
319
|
+
position: absolute;
|
|
320
|
+
top: 0;
|
|
321
|
+
left: 0;
|
|
322
|
+
right: 0;
|
|
323
|
+
height: 36px;
|
|
324
|
+
display: grid;
|
|
325
|
+
grid-template-columns: 80px 1.4fr 1fr 0.8fr 0.6fr 1fr 0.9fr;
|
|
326
|
+
align-items: center;
|
|
327
|
+
gap: 8px;
|
|
328
|
+
padding: 0 8px;
|
|
329
|
+
border-bottom: 1px solid var(--border-soft);
|
|
330
|
+
font-size: 13px;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.table-row:nth-of-type(odd) {
|
|
334
|
+
background: color-mix(in srgb, var(--surface-solid) 84%, transparent);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.table-row .total {
|
|
338
|
+
font-weight: 600;
|
|
339
|
+
color: var(--brand);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.table-footer {
|
|
343
|
+
display: flex;
|
|
344
|
+
justify-content: space-between;
|
|
345
|
+
margin-top: 12px;
|
|
346
|
+
font-size: 12px;
|
|
347
|
+
color: var(--muted);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
@media (max-width: 920px) {
|
|
351
|
+
.heroGrid {
|
|
352
|
+
grid-template-columns: 1fr;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
@media (max-width: 900px) {
|
|
357
|
+
.table-header,
|
|
358
|
+
.table-row {
|
|
359
|
+
grid-template-columns: 72px 1.3fr 1fr 0.8fr 0.7fr 1fr;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.table-header span:last-child,
|
|
363
|
+
.table-row span:last-child {
|
|
364
|
+
display: none;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
@media (max-width: 720px) {
|
|
369
|
+
.container {
|
|
370
|
+
padding: 0 var(--sp-4);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.hero {
|
|
374
|
+
padding: calc(var(--sp-8) + 0.5rem) 0 var(--sp-8);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.hTitle {
|
|
378
|
+
font-size: clamp(2.9rem, 14vw, 3.6rem);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.table-toolbar,
|
|
382
|
+
.table-footer {
|
|
383
|
+
flex-direction: column;
|
|
384
|
+
align-items: flex-start;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.table-header {
|
|
388
|
+
display: none;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.table-viewport {
|
|
392
|
+
height: 460px;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.table-row {
|
|
396
|
+
height: 168px;
|
|
397
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
398
|
+
align-content: start;
|
|
399
|
+
gap: 10px 16px;
|
|
400
|
+
padding: 14px 16px;
|
|
401
|
+
border: 1px solid var(--border-soft);
|
|
402
|
+
border-radius: 18px;
|
|
403
|
+
margin: 0 8px;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.table-row span {
|
|
407
|
+
display: flex;
|
|
408
|
+
flex-direction: column;
|
|
409
|
+
gap: 4px;
|
|
410
|
+
min-width: 0;
|
|
411
|
+
font-size: 0.95rem;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.table-row span::before {
|
|
415
|
+
content: attr(data-label);
|
|
416
|
+
color: var(--muted);
|
|
417
|
+
font-size: 0.72rem;
|
|
418
|
+
font-weight: 700;
|
|
419
|
+
letter-spacing: 0.06em;
|
|
420
|
+
text-transform: uppercase;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ES2022",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"baseUrl": "./",
|
|
10
|
+
"plugins": [
|
|
11
|
+
{
|
|
12
|
+
"name": "@rs-x/typescript-plugin"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts"]
|
|
17
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# rsx-vue-example
|
|
2
|
+
|
|
3
|
+
Website & docs: https://www.rsxjs.com/
|
|
4
|
+
|
|
5
|
+
This starter shows how to use RS-X in a Vue 3 application with a million-row
|
|
6
|
+
virtual table that keeps rendering and expression memory bounded.
|
|
7
|
+
|
|
8
|
+
## Scripts
|
|
9
|
+
|
|
10
|
+
- `npm run dev` runs the RS-X build step and starts Vite
|
|
11
|
+
- `npm run build` generates RS-X artifacts and builds the production app
|
|
12
|
+
- `npm run preview` previews the production build
|
|
13
|
+
|
|
14
|
+
## Structure
|
|
15
|
+
|
|
16
|
+
- `src/App.vue` contains the app shell and theme toggle
|
|
17
|
+
- `src/components/` contains UI components
|
|
18
|
+
- `src/composables/` contains reusable Vue composables
|
|
19
|
+
- `src/lib/` contains RS-X bootstrap and virtual-table state/data utilities
|
|
20
|
+
- `src/env.d.ts` declares Vue SFC modules for the RS-X build/typecheck pass
|
|
21
|
+
|
|
22
|
+
## Notes
|
|
23
|
+
|
|
24
|
+
- The demo defaults to dark mode.
|
|
25
|
+
- It uses the `useRsxExpression` composable from `@rs-x/vue`.
|
|
26
|
+
- The generated RS-X cache files in `src/rsx-generated` are created by
|
|
27
|
+
`npm run build:rsx`; they are not checked into the starter template.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onMounted, ref, watch } from 'vue';
|
|
3
|
+
|
|
4
|
+
import VirtualTableShell from './components/VirtualTableShell.vue';
|
|
5
|
+
|
|
6
|
+
type ThemeMode = 'light' | 'dark';
|
|
7
|
+
|
|
8
|
+
const theme = ref<ThemeMode>('dark');
|
|
9
|
+
|
|
10
|
+
onMounted(() => {
|
|
11
|
+
const storedTheme = window.localStorage.getItem('rsx-theme');
|
|
12
|
+
if (storedTheme === 'light' || storedTheme === 'dark') {
|
|
13
|
+
theme.value = storedTheme;
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
watch(
|
|
18
|
+
theme,
|
|
19
|
+
(nextTheme) => {
|
|
20
|
+
document.documentElement.setAttribute('data-theme', nextTheme);
|
|
21
|
+
document.body.setAttribute('data-theme', nextTheme);
|
|
22
|
+
window.localStorage.setItem('rsx-theme', nextTheme);
|
|
23
|
+
},
|
|
24
|
+
{ immediate: true },
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
function toggleTheme(): void {
|
|
28
|
+
theme.value = theme.value === 'dark' ? 'light' : 'dark';
|
|
29
|
+
}
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<main class="app-shell">
|
|
34
|
+
<section class="hero">
|
|
35
|
+
<div class="container">
|
|
36
|
+
<div class="heroGrid">
|
|
37
|
+
<div class="heroLeft">
|
|
38
|
+
<p class="app-eyebrow">RS-X Vue Demo</p>
|
|
39
|
+
<h1 class="hTitle">Virtual Table</h1>
|
|
40
|
+
<p class="hSubhead">
|
|
41
|
+
Million-row scrolling with a fixed RS-X expression pool.
|
|
42
|
+
</p>
|
|
43
|
+
<p class="hSub">
|
|
44
|
+
This demo keeps rendering bounded while streaming pages on demand,
|
|
45
|
+
so scrolling stays smooth without growing expression memory with
|
|
46
|
+
the dataset.
|
|
47
|
+
</p>
|
|
48
|
+
|
|
49
|
+
<div class="heroActions">
|
|
50
|
+
<a
|
|
51
|
+
class="btn btnGhost"
|
|
52
|
+
href="https://www.rsxjs.com/"
|
|
53
|
+
target="_blank"
|
|
54
|
+
rel="noreferrer"
|
|
55
|
+
>
|
|
56
|
+
rs-x
|
|
57
|
+
</a>
|
|
58
|
+
<button
|
|
59
|
+
type="button"
|
|
60
|
+
class="btn btnGhost theme-toggle"
|
|
61
|
+
:aria-label="`Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`"
|
|
62
|
+
@click="toggleTheme"
|
|
63
|
+
>
|
|
64
|
+
{{ theme === 'dark' ? 'Light mode' : 'Dark mode' }}
|
|
65
|
+
</button>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<aside class="card heroNote">
|
|
70
|
+
<h2 class="cardTitle">What This Shows</h2>
|
|
71
|
+
<p class="cardText">
|
|
72
|
+
Only a small row-model pool stays alive while pages stream in
|
|
73
|
+
around the viewport. That means one million logical rows without
|
|
74
|
+
one million live bindings.
|
|
75
|
+
</p>
|
|
76
|
+
</aside>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</section>
|
|
80
|
+
|
|
81
|
+
<section class="section">
|
|
82
|
+
<div class="container">
|
|
83
|
+
<section class="app-panel card">
|
|
84
|
+
<VirtualTableShell />
|
|
85
|
+
</section>
|
|
86
|
+
</div>
|
|
87
|
+
</section>
|
|
88
|
+
</main>
|
|
89
|
+
</template>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue';
|
|
3
|
+
|
|
4
|
+
import { useRsxExpression } from '@rs-x/vue';
|
|
5
|
+
|
|
6
|
+
import type { RowView } from '../lib/virtual-table-controller';
|
|
7
|
+
|
|
8
|
+
const props = defineProps<{ item: RowView }>();
|
|
9
|
+
|
|
10
|
+
const style = computed(() => ({
|
|
11
|
+
transform: `translateY(${props.item.top}px)`,
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
const id = useRsxExpression(props.item.row.idExpr);
|
|
15
|
+
const name = useRsxExpression(props.item.row.nameExpr);
|
|
16
|
+
const category = useRsxExpression(props.item.row.categoryExpr);
|
|
17
|
+
const price = useRsxExpression(props.item.row.priceExpr);
|
|
18
|
+
const quantity = useRsxExpression(props.item.row.quantityExpr);
|
|
19
|
+
const total = useRsxExpression(props.item.row.totalExpr);
|
|
20
|
+
const updatedAt = useRsxExpression(props.item.row.updatedAtExpr);
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<div class="table-row" :style="style">
|
|
25
|
+
<span data-label="ID">#{{ id ?? 0 }}</span>
|
|
26
|
+
<span data-label="Name">{{ name ?? '' }}</span>
|
|
27
|
+
<span data-label="Category">{{ category ?? '' }}</span>
|
|
28
|
+
<span data-label="Price">€{{ price ?? 0 }}</span>
|
|
29
|
+
<span data-label="Qty">{{ quantity ?? 0 }}</span>
|
|
30
|
+
<span data-label="Total" class="total">€{{ total ?? 0 }}</span>
|
|
31
|
+
<span data-label="Updated">{{ updatedAt ?? '--' }}</span>
|
|
32
|
+
</div>
|
|
33
|
+
</template>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, useTemplateRef } from 'vue';
|
|
3
|
+
|
|
4
|
+
import { useVirtualTableController } from '../composables/use-virtual-table-controller';
|
|
5
|
+
import { useVirtualTableViewport } from '../composables/use-virtual-table-viewport';
|
|
6
|
+
import VirtualTableRow from './VirtualTableRow.vue';
|
|
7
|
+
|
|
8
|
+
const { controller, snapshot } = useVirtualTableController();
|
|
9
|
+
const viewport = useTemplateRef<HTMLDivElement>('viewport');
|
|
10
|
+
useVirtualTableViewport(controller, viewport);
|
|
11
|
+
|
|
12
|
+
const visibleRows = computed(() => snapshot.value.visibleRows);
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<section class="table-toolbar">
|
|
17
|
+
<div class="toolbar-left">
|
|
18
|
+
<h2>Inventory Snapshot</h2>
|
|
19
|
+
<p>
|
|
20
|
+
{{ snapshot.totalRows }} rows • {{ snapshot.poolSize }} pre-wired models
|
|
21
|
+
</p>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="toolbar-right">
|
|
24
|
+
<button type="button" @click="controller.toggleSort('price')">
|
|
25
|
+
Sort by price
|
|
26
|
+
</button>
|
|
27
|
+
<button type="button" @click="controller.toggleSort('quantity')">
|
|
28
|
+
Sort by stock
|
|
29
|
+
</button>
|
|
30
|
+
<button type="button" @click="controller.toggleSort('name')">
|
|
31
|
+
Sort by name
|
|
32
|
+
</button>
|
|
33
|
+
</div>
|
|
34
|
+
</section>
|
|
35
|
+
|
|
36
|
+
<div class="table-header">
|
|
37
|
+
<span>ID</span>
|
|
38
|
+
<span>Name</span>
|
|
39
|
+
<span>Category</span>
|
|
40
|
+
<span>Price</span>
|
|
41
|
+
<span>Qty</span>
|
|
42
|
+
<span>Total</span>
|
|
43
|
+
<span>Updated</span>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div
|
|
47
|
+
ref="viewport"
|
|
48
|
+
class="table-viewport"
|
|
49
|
+
@scroll="
|
|
50
|
+
controller.setScrollTop(($event.target as HTMLDivElement).scrollTop)
|
|
51
|
+
"
|
|
52
|
+
>
|
|
53
|
+
<div
|
|
54
|
+
class="table-spacer"
|
|
55
|
+
:style="{ height: `${snapshot.spacerHeight}px` }"
|
|
56
|
+
/>
|
|
57
|
+
<VirtualTableRow
|
|
58
|
+
v-for="item in visibleRows"
|
|
59
|
+
:key="item.index"
|
|
60
|
+
:item="item"
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<div class="table-footer">
|
|
65
|
+
<div>
|
|
66
|
+
Rows in view: {{ snapshot.rowsInView }} • Loaded pages:
|
|
67
|
+
{{ snapshot.loadedPageCount }}
|
|
68
|
+
</div>
|
|
69
|
+
<div>Scroll to stream pages from a 1,000,000-row virtual dataset.</div>
|
|
70
|
+
</div>
|
|
71
|
+
</template>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getCurrentScope,
|
|
3
|
+
onScopeDispose,
|
|
4
|
+
type ShallowRef,
|
|
5
|
+
shallowRef,
|
|
6
|
+
} from 'vue';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
VirtualTableController,
|
|
10
|
+
type VirtualTableSnapshot,
|
|
11
|
+
} from '../lib/virtual-table-controller';
|
|
12
|
+
|
|
13
|
+
export function useVirtualTableController(): {
|
|
14
|
+
controller: VirtualTableController;
|
|
15
|
+
snapshot: ShallowRef<VirtualTableSnapshot>;
|
|
16
|
+
} {
|
|
17
|
+
const controller = new VirtualTableController();
|
|
18
|
+
const snapshot = shallowRef(controller.getSnapshot());
|
|
19
|
+
const unsubscribe = controller.subscribe(() => {
|
|
20
|
+
snapshot.value = controller.getSnapshot();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (getCurrentScope()) {
|
|
24
|
+
onScopeDispose(() => {
|
|
25
|
+
unsubscribe();
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
controller,
|
|
31
|
+
snapshot,
|
|
32
|
+
};
|
|
33
|
+
}
|