@rs-x/cli 2.0.0-next.6 → 2.0.0-next.7

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.
@@ -0,0 +1,174 @@
1
+ :host {
2
+ display: block;
3
+ }
4
+
5
+ .table-toolbar {
6
+ display: flex;
7
+ align-items: center;
8
+ justify-content: space-between;
9
+ gap: 16px;
10
+ padding-bottom: 16px;
11
+ border-bottom: 1px solid var(--border-soft);
12
+ }
13
+
14
+ .toolbar-left h2 {
15
+ margin: 0;
16
+ font-size: 20px;
17
+ }
18
+
19
+ .toolbar-left p {
20
+ margin: 4px 0 0;
21
+ color: var(--muted);
22
+ font-size: 13px;
23
+ }
24
+
25
+ .toolbar-right {
26
+ display: flex;
27
+ flex-wrap: wrap;
28
+ gap: 8px;
29
+ }
30
+
31
+ .toolbar-right button {
32
+ border: 1px solid var(--border);
33
+ background: color-mix(in srgb, var(--surface-solid) 88%, var(--brand) 12%);
34
+ color: var(--text);
35
+ padding: 6px 12px;
36
+ border-radius: 999px;
37
+ font-size: 12px;
38
+ cursor: pointer;
39
+ }
40
+
41
+ .toolbar-right button:hover {
42
+ border-color: var(--focus);
43
+ }
44
+
45
+ .table-header {
46
+ display: grid;
47
+ grid-template-columns: 80px 1.4fr 1fr 0.8fr 0.6fr 1fr 0.9fr;
48
+ gap: 8px;
49
+ padding: 12px 8px;
50
+ font-size: 12px;
51
+ font-weight: 600;
52
+ color: var(--muted);
53
+ text-transform: uppercase;
54
+ letter-spacing: 0.06em;
55
+ }
56
+
57
+ .table-viewport {
58
+ position: relative;
59
+ height: 520px;
60
+ overflow: auto;
61
+ border: 1px solid var(--border-soft);
62
+ border-radius: 12px;
63
+ background: color-mix(in srgb, var(--surface-solid) 94%, transparent);
64
+ }
65
+
66
+ .table-spacer {
67
+ width: 100%;
68
+ }
69
+
70
+ .table-row {
71
+ position: absolute;
72
+ top: 0;
73
+ left: 0;
74
+ right: 0;
75
+ height: 36px;
76
+ display: grid;
77
+ grid-template-columns: 80px 1.4fr 1fr 0.8fr 0.6fr 1fr 0.9fr;
78
+ align-items: center;
79
+ gap: 8px;
80
+ padding: 0 8px;
81
+ border-bottom: 1px solid var(--border-soft);
82
+ font-size: 13px;
83
+ }
84
+
85
+ .table-row:nth-child(odd) {
86
+ background: color-mix(in srgb, var(--surface-solid) 84%, transparent);
87
+ }
88
+
89
+ .table-row .total {
90
+ font-weight: 600;
91
+ color: var(--brand);
92
+ }
93
+
94
+ .table-footer {
95
+ display: flex;
96
+ justify-content: space-between;
97
+ margin-top: 12px;
98
+ font-size: 12px;
99
+ color: var(--muted);
100
+ }
101
+
102
+ :host-context(.theme-dark) .table-viewport {
103
+ background: rgba(12, 18, 35, 0.88);
104
+ }
105
+
106
+ :host-context(.theme-dark) .table-row:nth-child(odd) {
107
+ background: rgba(255, 255, 255, 0.03);
108
+ }
109
+
110
+ :host-context(.theme-dark) .toolbar-right button {
111
+ background: rgba(122, 182, 255, 0.12);
112
+ border-color: rgba(237, 242, 255, 0.14);
113
+ color: #edf2ff;
114
+ }
115
+
116
+ @media (max-width: 900px) {
117
+ .table-header,
118
+ .table-row {
119
+ grid-template-columns: 72px 1.3fr 1fr 0.8fr 0.7fr 1fr;
120
+ }
121
+
122
+ .table-header span:last-child,
123
+ .table-row span:last-child {
124
+ display: none;
125
+ }
126
+ }
127
+
128
+ @media (max-width: 720px) {
129
+ .table-toolbar,
130
+ .table-footer {
131
+ flex-direction: column;
132
+ align-items: flex-start;
133
+ }
134
+
135
+ .table-header {
136
+ display: none;
137
+ }
138
+
139
+ .table-viewport {
140
+ height: 460px;
141
+ }
142
+
143
+ .table-row {
144
+ height: 168px;
145
+ grid-template-columns: repeat(2, minmax(0, 1fr));
146
+ align-content: start;
147
+ gap: 10px 16px;
148
+ padding: 14px 16px;
149
+ border: 1px solid var(--border-soft);
150
+ border-radius: 18px;
151
+ margin: 0 8px;
152
+ }
153
+
154
+ .table-row span {
155
+ display: flex;
156
+ flex-direction: column;
157
+ gap: 4px;
158
+ min-width: 0;
159
+ font-size: 0.95rem;
160
+ }
161
+
162
+ .table-row span::before {
163
+ content: attr(data-label);
164
+ color: var(--muted);
165
+ font-size: 0.72rem;
166
+ font-weight: 700;
167
+ letter-spacing: 0.06em;
168
+ text-transform: uppercase;
169
+ }
170
+
171
+ .table-row .total {
172
+ color: var(--brand);
173
+ }
174
+ }
@@ -0,0 +1,50 @@
1
+ <section class="table-toolbar">
2
+ <div class="toolbar-left">
3
+ <h2>Inventory Snapshot</h2>
4
+ <p>{{ state.totalRows }} rows • {{ state.poolSize }} pre-wired models</p>
5
+ </div>
6
+ <div class="toolbar-right">
7
+ <button type="button" (click)="toggleSort('price')">Sort by price</button>
8
+ <button type="button" (click)="toggleSort('quantity')">
9
+ Sort by stock
10
+ </button>
11
+ <button type="button" (click)="toggleSort('name')">Sort by name</button>
12
+ </div>
13
+ </section>
14
+
15
+ <div class="table-header">
16
+ <span>ID</span>
17
+ <span>Name</span>
18
+ <span>Category</span>
19
+ <span>Price</span>
20
+ <span>Qty</span>
21
+ <span>Total</span>
22
+ <span>Updated</span>
23
+ </div>
24
+
25
+ <div #scrollViewport class="table-viewport" (scroll)="onScroll($event)">
26
+ <div class="table-spacer" [style.height.px]="state.spacerHeight"></div>
27
+ <div
28
+ class="table-row"
29
+ *ngFor="let item of state.rowsExpression | rsx; trackBy: trackByIndex"
30
+ [style.transform]="'translateY(' + item.top + 'px)'"
31
+ >
32
+ <span data-label="ID">#{{ item.row.idExpr | rsx }}</span>
33
+ <span data-label="Name">{{ item.row.nameExpr | rsx }}</span>
34
+ <span data-label="Category">{{ item.row.categoryExpr | rsx }}</span>
35
+ <span data-label="Price">€{{ item.row.priceExpr | rsx }}</span>
36
+ <span data-label="Qty">{{ item.row.quantityExpr | rsx }}</span>
37
+ <span data-label="Total" class="total"
38
+ >€{{ item.row.totalExpr | rsx }}</span
39
+ >
40
+ <span data-label="Updated">{{ item.row.updatedAtExpr | rsx }}</span>
41
+ </div>
42
+ </div>
43
+
44
+ <div class="table-footer">
45
+ <div>
46
+ Rows in view: {{ state.rowsInView }} • Loaded pages:
47
+ {{ state.loadedPageCount }}
48
+ </div>
49
+ <div>Scroll to stream pages from a 1,000,000-row virtual dataset.</div>
50
+ </div>
@@ -0,0 +1,83 @@
1
+ import { CommonModule } from '@angular/common';
2
+ import {
3
+ AfterViewInit,
4
+ ChangeDetectionStrategy,
5
+ Component,
6
+ ElementRef,
7
+ OnDestroy,
8
+ ViewChild,
9
+ } from '@angular/core';
10
+
11
+ import { RsxPipe } from '@rs-x/angular';
12
+
13
+ import { type RowView, VirtualTableModel } from './virtual-table-model';
14
+
15
+ @Component({
16
+ selector: 'rsx-virtual-table',
17
+ standalone: true,
18
+ imports: [CommonModule, RsxPipe],
19
+ templateUrl: './virtual-table.component.html',
20
+ styleUrls: ['./virtual-table.component.css'],
21
+ changeDetection: ChangeDetectionStrategy.OnPush,
22
+ })
23
+ export class VirtualTableComponent implements AfterViewInit, OnDestroy {
24
+ private static readonly COMPACT_BREAKPOINT_PX = 720;
25
+ private static readonly DEFAULT_ROW_HEIGHT = 36;
26
+ private static readonly COMPACT_ROW_HEIGHT = 168;
27
+
28
+ public readonly state = new VirtualTableModel();
29
+
30
+ @ViewChild('scrollViewport', { static: true })
31
+ private readonly scrollViewport?: ElementRef<HTMLDivElement>;
32
+
33
+ private resizeObserver?: ResizeObserver;
34
+
35
+ public ngAfterViewInit(): void {
36
+ const viewport = this.scrollViewport?.nativeElement;
37
+ if (!viewport) {
38
+ return;
39
+ }
40
+
41
+ this.syncViewportMetrics(viewport);
42
+ this.resizeObserver = new ResizeObserver((entries) => {
43
+ for (const entry of entries) {
44
+ this.state.setViewportHeight(entry.contentRect.height);
45
+ this.state.setRowHeight(
46
+ entry.contentRect.width <= VirtualTableComponent.COMPACT_BREAKPOINT_PX
47
+ ? VirtualTableComponent.COMPACT_ROW_HEIGHT
48
+ : VirtualTableComponent.DEFAULT_ROW_HEIGHT,
49
+ );
50
+ }
51
+ });
52
+ this.resizeObserver.observe(viewport);
53
+ }
54
+
55
+ public ngOnDestroy(): void {
56
+ this.resizeObserver?.disconnect();
57
+ }
58
+
59
+ public onScroll(event: Event): void {
60
+ const target = event.target as HTMLDivElement | null;
61
+ if (!target) {
62
+ return;
63
+ }
64
+ this.state.setScrollTop(target.scrollTop);
65
+ }
66
+
67
+ public toggleSort(key: 'id' | 'name' | 'price' | 'quantity'): void {
68
+ this.state.toggleSort(key);
69
+ }
70
+
71
+ public trackByIndex(_: number, item: RowView): number {
72
+ return item.index;
73
+ }
74
+
75
+ private syncViewportMetrics(viewport: HTMLDivElement): void {
76
+ this.state.setViewportHeight(viewport.clientHeight);
77
+ this.state.setRowHeight(
78
+ viewport.clientWidth <= VirtualTableComponent.COMPACT_BREAKPOINT_PX
79
+ ? VirtualTableComponent.COMPACT_ROW_HEIGHT
80
+ : VirtualTableComponent.DEFAULT_ROW_HEIGHT,
81
+ );
82
+ }
83
+ }
@@ -0,0 +1,11 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>RS-X Angular Virtual Table Demo</title>
7
+ </head>
8
+ <body>
9
+ <app-root></app-root>
10
+ </body>
11
+ </html>
@@ -0,0 +1,16 @@
1
+ import { enableProdMode, isDevMode } from '@angular/core';
2
+ import { bootstrapApplication } from '@angular/platform-browser';
3
+
4
+ import { providexRsx } from '@rs-x/angular';
5
+
6
+ import { AppComponent } from './app/app.component';
7
+
8
+ if (!isDevMode()) {
9
+ enableProdMode();
10
+ }
11
+
12
+ bootstrapApplication(AppComponent, {
13
+ providers: [...providexRsx()],
14
+ }).catch((error) => {
15
+ console.error(error);
16
+ });
@@ -0,0 +1,261 @@
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
+
9
+ --bg: #f6f8fc;
10
+ --surface: rgba(255, 255, 255, 0.86);
11
+ --surface-2: rgba(255, 255, 255, 0.72);
12
+ --surface-solid: #ffffff;
13
+ --text: #0b1324;
14
+ --muted: rgba(11, 19, 36, 0.66);
15
+ --border: rgba(10, 25, 55, 0.14);
16
+ --border-soft: rgba(10, 25, 55, 0.1);
17
+ --brand: #0b66ff;
18
+ --brand-2: #2bb6a9;
19
+ --focus: rgba(11, 102, 255, 0.35);
20
+ --shadow-1:
21
+ 0 1px 2px rgba(16, 24, 40, 0.06), 0 12px 34px rgba(16, 24, 40, 0.1);
22
+ --shadow-2:
23
+ 0 2px 10px rgba(16, 24, 40, 0.08), 0 18px 52px rgba(16, 24, 40, 0.12);
24
+ --page-glow-a: rgba(11, 102, 255, 0.06);
25
+ --page-glow-b: rgba(43, 182, 169, 0.05);
26
+ --code-surface: #f7f9ff;
27
+ --hero-section-bg: linear-gradient(
28
+ 180deg,
29
+ rgba(255, 255, 255, 0.72),
30
+ rgba(255, 255, 255, 0.42)
31
+ );
32
+ --r-xl: 1.75rem;
33
+ --sp-2: 0.5rem;
34
+ --sp-3: 0.75rem;
35
+ --sp-4: 1rem;
36
+ --sp-5: 1.25rem;
37
+ --sp-6: 1.5rem;
38
+ --sp-7: 2rem;
39
+ --sp-8: 2.5rem;
40
+ --sp-9: 3rem;
41
+ --sp-10: 4rem;
42
+ --container: 1120px;
43
+ }
44
+
45
+ html[data-theme='dark'],
46
+ body[data-theme='dark'] {
47
+ --bg: #070b14;
48
+ --surface: rgba(12, 18, 35, 0.82);
49
+ --surface-2: rgba(12, 18, 35, 0.68);
50
+ --surface-solid: #0c1223;
51
+ --text: #edf2ff;
52
+ --muted: rgba(237, 242, 255, 0.7);
53
+ --border: rgba(237, 242, 255, 0.16);
54
+ --border-soft: rgba(237, 242, 255, 0.12);
55
+ --brand: #7ab6ff;
56
+ --brand-2: #49d9c8;
57
+ --focus: rgba(122, 182, 255, 0.35);
58
+ --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.22), 0 12px 34px rgba(0, 0, 0, 0.32);
59
+ --shadow-2: 0 2px 10px rgba(0, 0, 0, 0.24), 0 18px 52px rgba(0, 0, 0, 0.34);
60
+ --page-glow-a: rgba(122, 182, 255, 0.1);
61
+ --page-glow-b: rgba(73, 217, 200, 0.08);
62
+ --code-surface: #0f182b;
63
+ --hero-section-bg: linear-gradient(
64
+ 180deg,
65
+ rgba(12, 18, 35, 0.56),
66
+ rgba(12, 18, 35, 0.24)
67
+ );
68
+ }
69
+
70
+ *,
71
+ *::before,
72
+ *::after {
73
+ box-sizing: border-box;
74
+ }
75
+
76
+ html,
77
+ body {
78
+ height: 100%;
79
+ }
80
+
81
+ html {
82
+ color-scheme: light;
83
+ background: var(--bg);
84
+ }
85
+
86
+ html[data-theme='dark'] {
87
+ color-scheme: dark;
88
+ background: var(--bg);
89
+ }
90
+
91
+ body {
92
+ margin: 0;
93
+ font-family: var(--font-sans);
94
+ color: var(--text);
95
+ background-color: var(--bg);
96
+ background:
97
+ radial-gradient(circle at top, var(--page-glow-a), transparent 32%),
98
+ radial-gradient(circle at 85% 12%, var(--page-glow-b), transparent 28%),
99
+ var(--bg);
100
+ transition:
101
+ background 180ms ease,
102
+ color 180ms ease;
103
+ }
104
+
105
+ html[data-theme='dark'] body,
106
+ body[data-theme='dark'] {
107
+ background-color: var(--bg);
108
+ background:
109
+ radial-gradient(circle at top, var(--page-glow-a), transparent 32%),
110
+ radial-gradient(circle at 85% 12%, var(--page-glow-b), transparent 28%),
111
+ var(--bg);
112
+ }
113
+
114
+ button,
115
+ input,
116
+ select,
117
+ textarea {
118
+ font: inherit;
119
+ }
120
+
121
+ .container {
122
+ max-width: var(--container);
123
+ margin: 0 auto;
124
+ padding: 0 var(--sp-6);
125
+ }
126
+
127
+ .hero {
128
+ position: relative;
129
+ padding: calc(var(--sp-10) + 1rem) 0 calc(var(--sp-8) + 0.5rem);
130
+ background: var(--hero-section-bg);
131
+ }
132
+
133
+ .heroGrid {
134
+ display: grid;
135
+ grid-template-columns: minmax(540px, 1.05fr) minmax(300px, 0.95fr);
136
+ column-gap: var(--sp-6);
137
+ row-gap: var(--sp-8);
138
+ align-items: center;
139
+ }
140
+
141
+ .heroLeft {
142
+ min-width: 0;
143
+ padding-top: var(--sp-4);
144
+ }
145
+
146
+ .hTitle {
147
+ margin: 0 0 var(--sp-5);
148
+ font-size: clamp(3.6rem, 5.2vw, 4.8rem);
149
+ line-height: 0.96;
150
+ letter-spacing: -0.052em;
151
+ }
152
+
153
+ .hSubhead {
154
+ margin: 0 0 var(--sp-2);
155
+ font-size: clamp(1.42rem, 2vw, 1.82rem);
156
+ font-weight: 640;
157
+ letter-spacing: -0.03em;
158
+ line-height: 1.08;
159
+ }
160
+
161
+ .hSub {
162
+ margin: var(--sp-5) 0 0;
163
+ color: var(--muted);
164
+ font-size: 1.1rem;
165
+ line-height: 1.72;
166
+ max-width: 31.5rem;
167
+ }
168
+
169
+ .heroActions {
170
+ display: flex;
171
+ flex-wrap: wrap;
172
+ gap: var(--sp-3);
173
+ margin-top: var(--sp-5);
174
+ }
175
+
176
+ .btn {
177
+ display: inline-flex;
178
+ align-items: center;
179
+ justify-content: center;
180
+ gap: var(--sp-2);
181
+ padding: 0.82rem 1.18rem;
182
+ border-radius: 14px;
183
+ border: 1px solid transparent;
184
+ font-weight: 800;
185
+ line-height: 1;
186
+ white-space: nowrap;
187
+ transition:
188
+ transform 160ms ease,
189
+ background 240ms ease,
190
+ border-color 240ms ease,
191
+ box-shadow 240ms ease;
192
+ }
193
+
194
+ .btn:active {
195
+ transform: translateY(1px);
196
+ }
197
+
198
+ .btnGhost {
199
+ background: var(--surface-2);
200
+ color: var(--text);
201
+ border-color: var(--border);
202
+ box-shadow: none;
203
+ }
204
+
205
+ .btnGhost:hover {
206
+ box-shadow: var(--shadow-1);
207
+ }
208
+
209
+ .card {
210
+ position: relative;
211
+ min-width: 0;
212
+ border: 1px solid var(--border-soft);
213
+ background: linear-gradient(180deg, var(--surface), var(--surface-2));
214
+ border-radius: var(--r-xl);
215
+ padding: var(--sp-6);
216
+ box-shadow: var(--shadow-1);
217
+ display: flex;
218
+ flex-direction: column;
219
+ }
220
+
221
+ .cardTitle {
222
+ margin: 0 0 var(--sp-3);
223
+ font-weight: 700;
224
+ font-size: clamp(1.12rem, 0.96vw, 1.22rem);
225
+ letter-spacing: -0.02em;
226
+ }
227
+
228
+ .cardText {
229
+ margin: 0;
230
+ color: var(--muted);
231
+ line-height: 1.62;
232
+ }
233
+
234
+ .heroNote {
235
+ align-self: center;
236
+ margin-top: 0;
237
+ }
238
+
239
+ .section {
240
+ padding: var(--sp-7) 0 var(--sp-10);
241
+ }
242
+
243
+ @media (max-width: 920px) {
244
+ .heroGrid {
245
+ grid-template-columns: 1fr;
246
+ }
247
+ }
248
+
249
+ @media (max-width: 720px) {
250
+ .container {
251
+ padding: 0 var(--sp-4);
252
+ }
253
+
254
+ .hero {
255
+ padding: calc(var(--sp-8) + 0.5rem) 0 var(--sp-8);
256
+ }
257
+
258
+ .hTitle {
259
+ font-size: clamp(2.9rem, 14vw, 3.6rem);
260
+ }
261
+ }