ripple 0.2.208 → 0.2.210
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/CHANGELOG.md +43 -0
- package/README.md +2 -1
- package/package.json +2 -6
- package/shims/rollup-estree-types.d.ts +1 -1
- package/src/compiler/index.d.ts +1 -0
- package/src/compiler/index.js +7 -1
- package/src/compiler/phases/1-parse/index.js +15 -6
- package/src/compiler/phases/2-analyze/css-analyze.js +100 -104
- package/src/compiler/phases/2-analyze/index.js +215 -2
- package/src/compiler/phases/3-transform/client/index.js +388 -50
- package/src/compiler/phases/3-transform/segments.js +123 -39
- package/src/compiler/phases/3-transform/server/index.js +266 -13
- package/src/compiler/types/index.d.ts +16 -3
- package/src/compiler/utils.js +1 -15
- package/src/constants.js +0 -2
- package/src/helpers.d.ts +4 -0
- package/src/html-tree-validation.js +211 -0
- package/src/jsx-runtime.d.ts +260 -259
- package/src/jsx-runtime.js +12 -12
- package/src/runtime/array.js +17 -17
- package/src/runtime/create-subscriber.js +1 -1
- package/src/runtime/index-client.js +1 -5
- package/src/runtime/index-server.js +15 -0
- package/src/runtime/internal/client/compat.js +3 -3
- package/src/runtime/internal/client/composite.js +6 -1
- package/src/runtime/internal/client/head.js +50 -4
- package/src/runtime/internal/client/html.js +73 -12
- package/src/runtime/internal/client/hydration.js +12 -0
- package/src/runtime/internal/client/index.js +1 -1
- package/src/runtime/internal/client/portal.js +54 -29
- package/src/runtime/internal/client/rpc.js +3 -1
- package/src/runtime/internal/client/switch.js +5 -0
- package/src/runtime/internal/client/template.js +117 -11
- package/src/runtime/internal/client/try.js +1 -0
- package/src/runtime/internal/server/index.js +113 -1
- package/src/runtime/internal/server/rpc.js +4 -4
- package/src/runtime/map.js +2 -2
- package/src/runtime/object.js +6 -6
- package/src/runtime/proxy.js +12 -11
- package/src/runtime/reactive-value.js +9 -1
- package/src/runtime/set.js +12 -7
- package/src/runtime/url-search-params.js +0 -1
- package/src/server/index.js +4 -0
- package/src/utils/hashing.js +15 -0
- package/src/utils/normalize_css_property_name.js +1 -1
- package/tests/client/array/array.mutations.test.ripple +8 -8
- package/tests/client/basic/basic.errors.test.ripple +28 -0
- package/tests/client/basic/basic.events.test.ripple +6 -3
- package/tests/client/basic/basic.utilities.test.ripple +1 -1
- package/tests/client/compiler/compiler.regex.test.ripple +10 -8
- package/tests/client/composite/composite.generics.test.ripple +5 -2
- package/tests/client/dynamic-elements.test.ripple +30 -1
- package/tests/client/function-overload-import.ripple +6 -7
- package/tests/client/html.test.ripple +0 -1
- package/tests/client/object.test.ripple +2 -2
- package/tests/client/portal.test.ripple +3 -3
- package/tests/client/return.test.ripple +2500 -0
- package/tests/client/try.test.ripple +69 -0
- package/tests/client/typescript-generics.test.ripple +1 -1
- package/tests/client/url/url.derived.test.ripple +1 -1
- package/tests/client/url/url.parsing.test.ripple +3 -3
- package/tests/client/url/url.partial-removal.test.ripple +7 -7
- package/tests/client/url/url.reactivity.test.ripple +15 -15
- package/tests/client/url/url.serialization.test.ripple +2 -2
- package/tests/hydration/basic.test.js +23 -0
- package/tests/hydration/build-components.js +10 -4
- package/tests/hydration/compiled/client/basic.js +165 -3
- package/tests/hydration/compiled/client/for.js +1140 -23
- package/tests/hydration/compiled/client/head.js +234 -0
- package/tests/hydration/compiled/client/html.js +135 -0
- package/tests/hydration/compiled/client/portal.js +172 -0
- package/tests/hydration/compiled/client/reactivity.js +3 -1
- package/tests/hydration/compiled/client/return.js +1976 -0
- package/tests/hydration/compiled/client/switch.js +162 -0
- package/tests/hydration/compiled/server/basic.js +249 -0
- package/tests/hydration/compiled/server/events.js +1 -1
- package/tests/hydration/compiled/server/for.js +891 -1
- package/tests/hydration/compiled/server/head.js +291 -0
- package/tests/hydration/compiled/server/html.js +133 -0
- package/tests/hydration/compiled/server/if.js +1 -1
- package/tests/hydration/compiled/server/portal.js +250 -0
- package/tests/hydration/compiled/server/reactivity.js +1 -1
- package/tests/hydration/compiled/server/return.js +1969 -0
- package/tests/hydration/compiled/server/switch.js +130 -0
- package/tests/hydration/components/basic.ripple +55 -0
- package/tests/hydration/components/for.ripple +403 -0
- package/tests/hydration/components/head.ripple +111 -0
- package/tests/hydration/components/html.ripple +38 -0
- package/tests/hydration/components/portal.ripple +49 -0
- package/tests/hydration/components/return.ripple +564 -0
- package/tests/hydration/components/switch.ripple +51 -0
- package/tests/hydration/for.test.js +363 -0
- package/tests/hydration/head.test.js +105 -0
- package/tests/hydration/html.test.js +46 -0
- package/tests/hydration/portal.test.js +71 -0
- package/tests/hydration/return.test.js +544 -0
- package/tests/hydration/switch.test.js +42 -0
- package/tests/server/basic.attributes.test.ripple +1 -1
- package/tests/server/compiler.test.ripple +22 -0
- package/tests/server/composite.test.ripple +5 -2
- package/tests/server/html-nesting-validation.test.ripple +237 -0
- package/tests/server/return.test.ripple +1379 -0
- package/tests/setup-hydration.js +6 -1
- package/tests/utils/escaping.test.js +3 -1
- package/tests/utils/normalize_css_property_name.test.js +0 -1
- package/tests/utils/patterns.test.js +6 -2
- package/tests/utils/sanitize_template_string.test.js +3 -2
- package/types/server.d.ts +16 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import * as _$_ from 'ripple/internal/server';
|
|
2
|
+
|
|
3
|
+
import { track } from 'ripple/server';
|
|
4
|
+
|
|
5
|
+
export function SwitchStatic(__output) {
|
|
6
|
+
_$_.push_component();
|
|
7
|
+
|
|
8
|
+
const status = 'success';
|
|
9
|
+
|
|
10
|
+
__output.push('<!--[-->');
|
|
11
|
+
|
|
12
|
+
switch (status) {
|
|
13
|
+
case 'success':
|
|
14
|
+
__output.push('<div');
|
|
15
|
+
__output.push(' class="status-success"');
|
|
16
|
+
__output.push('>');
|
|
17
|
+
{
|
|
18
|
+
__output.push('Success');
|
|
19
|
+
}
|
|
20
|
+
__output.push('</div>');
|
|
21
|
+
break;
|
|
22
|
+
|
|
23
|
+
case 'error':
|
|
24
|
+
__output.push('<div');
|
|
25
|
+
__output.push(' class="status-error"');
|
|
26
|
+
__output.push('>');
|
|
27
|
+
{
|
|
28
|
+
__output.push('Error');
|
|
29
|
+
}
|
|
30
|
+
__output.push('</div>');
|
|
31
|
+
break;
|
|
32
|
+
|
|
33
|
+
default:
|
|
34
|
+
__output.push('<div');
|
|
35
|
+
__output.push(' class="status-unknown"');
|
|
36
|
+
__output.push('>');
|
|
37
|
+
{
|
|
38
|
+
__output.push('Unknown');
|
|
39
|
+
}
|
|
40
|
+
__output.push('</div>');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
__output.push('<!--]-->');
|
|
44
|
+
_$_.pop_component();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function SwitchReactive(__output) {
|
|
48
|
+
_$_.push_component();
|
|
49
|
+
|
|
50
|
+
let status = track('a');
|
|
51
|
+
|
|
52
|
+
__output.push('<button');
|
|
53
|
+
__output.push(' class="toggle"');
|
|
54
|
+
__output.push('>');
|
|
55
|
+
|
|
56
|
+
{
|
|
57
|
+
__output.push('Toggle');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
__output.push('</button>');
|
|
61
|
+
__output.push('<!--[-->');
|
|
62
|
+
|
|
63
|
+
switch (_$_.get(status)) {
|
|
64
|
+
case 'a':
|
|
65
|
+
__output.push('<div');
|
|
66
|
+
__output.push(' class="case-a"');
|
|
67
|
+
__output.push('>');
|
|
68
|
+
{
|
|
69
|
+
__output.push('Case A');
|
|
70
|
+
}
|
|
71
|
+
__output.push('</div>');
|
|
72
|
+
break;
|
|
73
|
+
|
|
74
|
+
case 'b':
|
|
75
|
+
__output.push('<div');
|
|
76
|
+
__output.push(' class="case-b"');
|
|
77
|
+
__output.push('>');
|
|
78
|
+
{
|
|
79
|
+
__output.push('Case B');
|
|
80
|
+
}
|
|
81
|
+
__output.push('</div>');
|
|
82
|
+
break;
|
|
83
|
+
|
|
84
|
+
default:
|
|
85
|
+
__output.push('<div');
|
|
86
|
+
__output.push(' class="case-c"');
|
|
87
|
+
__output.push('>');
|
|
88
|
+
{
|
|
89
|
+
__output.push('Case C');
|
|
90
|
+
}
|
|
91
|
+
__output.push('</div>');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
__output.push('<!--]-->');
|
|
95
|
+
_$_.pop_component();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function SwitchFallthrough(__output) {
|
|
99
|
+
_$_.push_component();
|
|
100
|
+
|
|
101
|
+
const val = 1;
|
|
102
|
+
|
|
103
|
+
__output.push('<!--[-->');
|
|
104
|
+
|
|
105
|
+
switch (val) {
|
|
106
|
+
case 1:
|
|
107
|
+
|
|
108
|
+
case 2:
|
|
109
|
+
__output.push('<div');
|
|
110
|
+
__output.push(' class="case-1-2"');
|
|
111
|
+
__output.push('>');
|
|
112
|
+
{
|
|
113
|
+
__output.push('1 or 2');
|
|
114
|
+
}
|
|
115
|
+
__output.push('</div>');
|
|
116
|
+
break;
|
|
117
|
+
|
|
118
|
+
default:
|
|
119
|
+
__output.push('<div');
|
|
120
|
+
__output.push(' class="case-other"');
|
|
121
|
+
__output.push('>');
|
|
122
|
+
{
|
|
123
|
+
__output.push('Other');
|
|
124
|
+
}
|
|
125
|
+
__output.push('</div>');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
__output.push('<!--]-->');
|
|
129
|
+
_$_.pop_component();
|
|
130
|
+
}
|
|
@@ -63,3 +63,58 @@ export component ExpressionContent() {
|
|
|
63
63
|
<div>{value}</div>
|
|
64
64
|
<span>{text.toUpperCase()}</span>
|
|
65
65
|
}
|
|
66
|
+
|
|
67
|
+
// Test for static content in child component followed by sibling content
|
|
68
|
+
component StaticHeader() {
|
|
69
|
+
<h1 class="sr-only">{'heading'}</h1>
|
|
70
|
+
<p class="subtitle">{'first paragraph'}</p>
|
|
71
|
+
<p class="subtitle">{'second paragraph'}</p>
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export component StaticChildWithSiblings() {
|
|
75
|
+
const foo = 'bar';
|
|
76
|
+
<StaticHeader />
|
|
77
|
+
<span class="sibling1">{foo}</span>
|
|
78
|
+
<span class="sibling2">{foo}</span>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Website-like components for testing complex hydration scenarios
|
|
82
|
+
|
|
83
|
+
component Header() {
|
|
84
|
+
<h1 class="sr-only">{'Ripple'}</h1>
|
|
85
|
+
<img src="/images/logo.png" alt="Logo" class="logo" />
|
|
86
|
+
<p class="subtitle">{'the elegant TypeScript UI framework'}</p>
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
component Actions({ playgroundVisible = false }: { playgroundVisible: boolean }) {
|
|
90
|
+
<div class="social-links">
|
|
91
|
+
<a href="https://github.com" class="github-link">{'GitHub'}</a>
|
|
92
|
+
<a href="https://discord.com" class="discord-link">{'Discord'}</a>
|
|
93
|
+
if (playgroundVisible) {
|
|
94
|
+
<a href="/playground" class="playground-link">{'Playground'}</a>
|
|
95
|
+
}
|
|
96
|
+
</div>
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
component Layout({ children }) {
|
|
100
|
+
<main>
|
|
101
|
+
<div class="container">
|
|
102
|
+
<children />
|
|
103
|
+
</div>
|
|
104
|
+
</main>
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
component Content() {
|
|
108
|
+
<div class="content">
|
|
109
|
+
<p>{'Some content here'}</p>
|
|
110
|
+
</div>
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export component WebsiteIndex() {
|
|
114
|
+
<Layout>
|
|
115
|
+
<Header />
|
|
116
|
+
<Actions playgroundVisible={true} />
|
|
117
|
+
<Content />
|
|
118
|
+
<Actions playgroundVisible={false} />
|
|
119
|
+
</Layout>
|
|
120
|
+
}
|
|
@@ -126,3 +126,406 @@ export component ForLoopComplexObjects() {
|
|
|
126
126
|
}
|
|
127
127
|
</div>
|
|
128
128
|
}
|
|
129
|
+
|
|
130
|
+
// Test reordering items in a keyed for loop
|
|
131
|
+
export component KeyedForLoopReorder() {
|
|
132
|
+
let items = track([
|
|
133
|
+
{ id: 1, name: 'First' },
|
|
134
|
+
{ id: 2, name: 'Second' },
|
|
135
|
+
{ id: 3, name: 'Third' },
|
|
136
|
+
]);
|
|
137
|
+
<button
|
|
138
|
+
class="reorder"
|
|
139
|
+
onClick={() => {
|
|
140
|
+
@items = [@items[2], @items[0], @items[1]];
|
|
141
|
+
}}
|
|
142
|
+
>
|
|
143
|
+
{'Reorder'}
|
|
144
|
+
</button>
|
|
145
|
+
<ul>
|
|
146
|
+
for (const item of @items; key item.id) {
|
|
147
|
+
<li class={`item-${item.id}`}>{item.name}</li>
|
|
148
|
+
}
|
|
149
|
+
</ul>
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Test for loop with item property updates (keyed)
|
|
153
|
+
export component KeyedForLoopUpdate() {
|
|
154
|
+
let items = track([
|
|
155
|
+
{ id: 1, name: 'Item 1' },
|
|
156
|
+
{ id: 2, name: 'Item 2' },
|
|
157
|
+
]);
|
|
158
|
+
<button
|
|
159
|
+
class="update"
|
|
160
|
+
onClick={() => {
|
|
161
|
+
@items = @items.map((item) => (item.id === 1 ? { ...item, name: 'Updated' } : item));
|
|
162
|
+
}}
|
|
163
|
+
>
|
|
164
|
+
{'Update'}
|
|
165
|
+
</button>
|
|
166
|
+
<ul>
|
|
167
|
+
for (const item of @items; key item.id) {
|
|
168
|
+
<li class={`item-${item.id}`}>{item.name}</li>
|
|
169
|
+
}
|
|
170
|
+
</ul>
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Test for loop with combined add/remove/reorder
|
|
174
|
+
export component ForLoopMixedOperations() {
|
|
175
|
+
let items = track(['A', 'B', 'C', 'D']);
|
|
176
|
+
<button
|
|
177
|
+
class="shuffle"
|
|
178
|
+
onClick={() => {
|
|
179
|
+
// Remove B, add E, reorder to D, C, A, E
|
|
180
|
+
@items = ['D', 'C', 'A', 'E'];
|
|
181
|
+
}}
|
|
182
|
+
>
|
|
183
|
+
{'Shuffle'}
|
|
184
|
+
</button>
|
|
185
|
+
<ul>
|
|
186
|
+
for (const item of @items) {
|
|
187
|
+
<li class={`item-${item}`}>{item}</li>
|
|
188
|
+
}
|
|
189
|
+
</ul>
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Test for loop inside if block (combined control flow)
|
|
193
|
+
export component ForLoopInsideIf() {
|
|
194
|
+
let showList = track(true);
|
|
195
|
+
let items = track(['X', 'Y', 'Z']);
|
|
196
|
+
<button
|
|
197
|
+
class="toggle"
|
|
198
|
+
onClick={() => {
|
|
199
|
+
@showList = !@showList;
|
|
200
|
+
}}
|
|
201
|
+
>
|
|
202
|
+
{'Toggle List'}
|
|
203
|
+
</button>
|
|
204
|
+
<button
|
|
205
|
+
class="add"
|
|
206
|
+
onClick={() => {
|
|
207
|
+
@items = [...@items, 'W'];
|
|
208
|
+
}}
|
|
209
|
+
>
|
|
210
|
+
{'Add Item'}
|
|
211
|
+
</button>
|
|
212
|
+
if (@showList) {
|
|
213
|
+
<ul class="list">
|
|
214
|
+
for (const item of @items) {
|
|
215
|
+
<li>{item}</li>
|
|
216
|
+
}
|
|
217
|
+
</ul>
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Test for loop that transitions from empty to populated
|
|
222
|
+
export component ForLoopEmptyToPopulated() {
|
|
223
|
+
let items = track<string[]>([]);
|
|
224
|
+
<button
|
|
225
|
+
class="populate"
|
|
226
|
+
onClick={() => {
|
|
227
|
+
@items = ['One', 'Two', 'Three'];
|
|
228
|
+
}}
|
|
229
|
+
>
|
|
230
|
+
{'Populate'}
|
|
231
|
+
</button>
|
|
232
|
+
<ul class="list">
|
|
233
|
+
for (const item of @items) {
|
|
234
|
+
<li>{item}</li>
|
|
235
|
+
}
|
|
236
|
+
</ul>
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Test for loop that transitions from populated to empty
|
|
240
|
+
export component ForLoopPopulatedToEmpty() {
|
|
241
|
+
let items = track(['One', 'Two', 'Three']);
|
|
242
|
+
<button
|
|
243
|
+
class="clear"
|
|
244
|
+
onClick={() => {
|
|
245
|
+
@items = [];
|
|
246
|
+
}}
|
|
247
|
+
>
|
|
248
|
+
{'Clear'}
|
|
249
|
+
</button>
|
|
250
|
+
<ul class="list">
|
|
251
|
+
for (const item of @items) {
|
|
252
|
+
<li>{item}</li>
|
|
253
|
+
}
|
|
254
|
+
</ul>
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Test nested for loops with reactivity
|
|
258
|
+
export component NestedForLoopReactive() {
|
|
259
|
+
let grid = track([
|
|
260
|
+
[1, 2],
|
|
261
|
+
[3, 4],
|
|
262
|
+
]);
|
|
263
|
+
<button
|
|
264
|
+
class="add-row"
|
|
265
|
+
onClick={() => {
|
|
266
|
+
@grid = [...@grid, [5, 6]];
|
|
267
|
+
}}
|
|
268
|
+
>
|
|
269
|
+
{'Add Row'}
|
|
270
|
+
</button>
|
|
271
|
+
<button
|
|
272
|
+
class="update-cell"
|
|
273
|
+
onClick={() => {
|
|
274
|
+
const newGrid = @grid.map((row) => [...row]);
|
|
275
|
+
newGrid[0][0] = 99;
|
|
276
|
+
@grid = newGrid;
|
|
277
|
+
}}
|
|
278
|
+
>
|
|
279
|
+
{'Update Cell'}
|
|
280
|
+
</button>
|
|
281
|
+
<div class="grid">
|
|
282
|
+
for (const row of @grid; index rowIndex) {
|
|
283
|
+
<div class={`row-${rowIndex}`}>
|
|
284
|
+
for (const cell of row; index colIndex) {
|
|
285
|
+
<span class={`cell-${rowIndex}-${colIndex}`}>{cell}</span>
|
|
286
|
+
}
|
|
287
|
+
</div>
|
|
288
|
+
}
|
|
289
|
+
</div>
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Test for loop with deeply nested data
|
|
293
|
+
export component ForLoopDeeplyNested() {
|
|
294
|
+
const departments = [
|
|
295
|
+
{
|
|
296
|
+
id: 'd1',
|
|
297
|
+
name: 'Engineering',
|
|
298
|
+
teams: [
|
|
299
|
+
{ id: 't1', name: 'Frontend', members: ['Alice', 'Bob'] },
|
|
300
|
+
{ id: 't2', name: 'Backend', members: ['Charlie'] },
|
|
301
|
+
],
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
id: 'd2',
|
|
305
|
+
name: 'Design',
|
|
306
|
+
teams: [
|
|
307
|
+
{ id: 't3', name: 'UX', members: ['Diana', 'Eve', 'Frank'] },
|
|
308
|
+
],
|
|
309
|
+
},
|
|
310
|
+
];
|
|
311
|
+
<div class="org">
|
|
312
|
+
for (const dept of departments; key dept.id) {
|
|
313
|
+
<div class={`dept-${dept.id}`}>
|
|
314
|
+
<h2 class="dept-name">{dept.name}</h2>
|
|
315
|
+
for (const team of dept.teams; key team.id) {
|
|
316
|
+
<div class={`team-${team.id}`}>
|
|
317
|
+
<h3 class="team-name">{team.name}</h3>
|
|
318
|
+
<ul>
|
|
319
|
+
for (const member of team.members) {
|
|
320
|
+
<li class="member">{member}</li>
|
|
321
|
+
}
|
|
322
|
+
</ul>
|
|
323
|
+
</div>
|
|
324
|
+
}
|
|
325
|
+
</div>
|
|
326
|
+
}
|
|
327
|
+
</div>
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Test for loop with index that gets updated
|
|
331
|
+
export component ForLoopIndexUpdate() {
|
|
332
|
+
let items = track(['First', 'Second', 'Third']);
|
|
333
|
+
<button
|
|
334
|
+
class="prepend"
|
|
335
|
+
onClick={() => {
|
|
336
|
+
@items = ['Zeroth', ...@items];
|
|
337
|
+
}}
|
|
338
|
+
>
|
|
339
|
+
{'Prepend'}
|
|
340
|
+
</button>
|
|
341
|
+
<ul>
|
|
342
|
+
for (const item of @items; index i) {
|
|
343
|
+
<li class={`item-${i}`}>{`[${i}] ${item}`}</li>
|
|
344
|
+
}
|
|
345
|
+
</ul>
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Test keyed for loop with index
|
|
349
|
+
export component KeyedForLoopWithIndex() {
|
|
350
|
+
let items = track([
|
|
351
|
+
{ id: 'a', value: 'Alpha' },
|
|
352
|
+
{ id: 'b', value: 'Beta' },
|
|
353
|
+
{ id: 'c', value: 'Gamma' },
|
|
354
|
+
]);
|
|
355
|
+
<button
|
|
356
|
+
class="reorder"
|
|
357
|
+
onClick={() => {
|
|
358
|
+
@items = [@items[1], @items[2], @items[0]];
|
|
359
|
+
}}
|
|
360
|
+
>
|
|
361
|
+
{'Rotate'}
|
|
362
|
+
</button>
|
|
363
|
+
<ul>
|
|
364
|
+
for (const item of @items; index i; key item.id) {
|
|
365
|
+
<li class={`item-${item.id}`} data-index={i}>{`[${i}] ${item.id}: ${item.value}`}</li>
|
|
366
|
+
}
|
|
367
|
+
</ul>
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Test for loop with sibling elements
|
|
371
|
+
export component ForLoopWithSiblings() {
|
|
372
|
+
let items = track(['A', 'B']);
|
|
373
|
+
<div class="wrapper">
|
|
374
|
+
<header class="before">{'Before'}</header>
|
|
375
|
+
for (const item of @items) {
|
|
376
|
+
<div class={`item-${item}`}>{item}</div>
|
|
377
|
+
}
|
|
378
|
+
<footer class="after">{'After'}</footer>
|
|
379
|
+
</div>
|
|
380
|
+
<button
|
|
381
|
+
class="add"
|
|
382
|
+
onClick={() => {
|
|
383
|
+
@items = [...@items, 'C'];
|
|
384
|
+
}}
|
|
385
|
+
>
|
|
386
|
+
{'Add'}
|
|
387
|
+
</button>
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Test for loop items with their own reactive state
|
|
391
|
+
export component ForLoopItemState() {
|
|
392
|
+
const initialItems = [
|
|
393
|
+
{ id: 1, text: 'Todo 1' },
|
|
394
|
+
{ id: 2, text: 'Todo 2' },
|
|
395
|
+
{ id: 3, text: 'Todo 3' },
|
|
396
|
+
];
|
|
397
|
+
<div>
|
|
398
|
+
for (const item of initialItems; key item.id) {
|
|
399
|
+
<TodoItem id={item.id} text={item.text} />
|
|
400
|
+
}
|
|
401
|
+
</div>
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
component TodoItem(props: { id: number; text: string }) {
|
|
405
|
+
let done = track(false);
|
|
406
|
+
<div class={`todo-${props.id}`}>
|
|
407
|
+
<input
|
|
408
|
+
type="checkbox"
|
|
409
|
+
class="checkbox"
|
|
410
|
+
checked={@done}
|
|
411
|
+
onChange={(e) => {
|
|
412
|
+
@done = (e.target as HTMLInputElement).checked;
|
|
413
|
+
}}
|
|
414
|
+
/>
|
|
415
|
+
<span class={@done ? 'completed' : 'pending'}>{props.text}</span>
|
|
416
|
+
</div>
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Test for loop with single item
|
|
420
|
+
export component ForLoopSingleItem() {
|
|
421
|
+
const items = ['Only'];
|
|
422
|
+
<ul>
|
|
423
|
+
for (const item of items) {
|
|
424
|
+
<li class="single">{item}</li>
|
|
425
|
+
}
|
|
426
|
+
</ul>
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Test for loop adding at beginning
|
|
430
|
+
export component ForLoopAddAtBeginning() {
|
|
431
|
+
let items = track(['B', 'C']);
|
|
432
|
+
<button
|
|
433
|
+
class="prepend"
|
|
434
|
+
onClick={() => {
|
|
435
|
+
@items = ['A', ...@items];
|
|
436
|
+
}}
|
|
437
|
+
>
|
|
438
|
+
{'Prepend A'}
|
|
439
|
+
</button>
|
|
440
|
+
<ul>
|
|
441
|
+
for (const item of @items) {
|
|
442
|
+
<li class={`item-${item}`}>{item}</li>
|
|
443
|
+
}
|
|
444
|
+
</ul>
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Test for loop adding in the middle
|
|
448
|
+
export component ForLoopAddInMiddle() {
|
|
449
|
+
let items = track(['A', 'C']);
|
|
450
|
+
<button
|
|
451
|
+
class="insert"
|
|
452
|
+
onClick={() => {
|
|
453
|
+
const copy = [...@items];
|
|
454
|
+
copy.splice(1, 0, 'B');
|
|
455
|
+
@items = copy;
|
|
456
|
+
}}
|
|
457
|
+
>
|
|
458
|
+
{'Insert B'}
|
|
459
|
+
</button>
|
|
460
|
+
<ul>
|
|
461
|
+
for (const item of @items) {
|
|
462
|
+
<li class={`item-${item}`}>{item}</li>
|
|
463
|
+
}
|
|
464
|
+
</ul>
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Test for loop removing from the middle
|
|
468
|
+
export component ForLoopRemoveFromMiddle() {
|
|
469
|
+
let items = track(['A', 'B', 'C']);
|
|
470
|
+
<button
|
|
471
|
+
class="remove-middle"
|
|
472
|
+
onClick={() => {
|
|
473
|
+
@items = @items.filter((item) => item !== 'B');
|
|
474
|
+
}}
|
|
475
|
+
>
|
|
476
|
+
{'Remove B'}
|
|
477
|
+
</button>
|
|
478
|
+
<ul>
|
|
479
|
+
for (const item of @items) {
|
|
480
|
+
<li class={`item-${item}`}>{item}</li>
|
|
481
|
+
}
|
|
482
|
+
</ul>
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Test for loop with large list
|
|
486
|
+
export component ForLoopLargeList() {
|
|
487
|
+
const items = Array.from({ length: 50 }, (_, i) => `Item ${i + 1}`);
|
|
488
|
+
<ul class="large-list">
|
|
489
|
+
for (const item of items; index i) {
|
|
490
|
+
<li class={`item-${i}`}>{item}</li>
|
|
491
|
+
}
|
|
492
|
+
</ul>
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Test for loop with swap operation
|
|
496
|
+
export component ForLoopSwap() {
|
|
497
|
+
let items = track(['A', 'B', 'C', 'D']);
|
|
498
|
+
<button
|
|
499
|
+
class="swap"
|
|
500
|
+
onClick={() => {
|
|
501
|
+
const copy = [...@items];
|
|
502
|
+
[copy[0], copy[3]] = [copy[3], copy[0]];
|
|
503
|
+
@items = copy;
|
|
504
|
+
}}
|
|
505
|
+
>
|
|
506
|
+
{'Swap First and Last'}
|
|
507
|
+
</button>
|
|
508
|
+
<ul>
|
|
509
|
+
for (const item of @items) {
|
|
510
|
+
<li class={`item-${item}`}>{item}</li>
|
|
511
|
+
}
|
|
512
|
+
</ul>
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Test for loop with reverse operation
|
|
516
|
+
export component ForLoopReverse() {
|
|
517
|
+
let items = track(['A', 'B', 'C', 'D']);
|
|
518
|
+
<button
|
|
519
|
+
class="reverse"
|
|
520
|
+
onClick={() => {
|
|
521
|
+
@items = [...@items].reverse();
|
|
522
|
+
}}
|
|
523
|
+
>
|
|
524
|
+
{'Reverse'}
|
|
525
|
+
</button>
|
|
526
|
+
<ul>
|
|
527
|
+
for (const item of @items) {
|
|
528
|
+
<li class={`item-${item}`}>{item}</li>
|
|
529
|
+
}
|
|
530
|
+
</ul>
|
|
531
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { track } from 'ripple';
|
|
2
|
+
|
|
3
|
+
// Static title
|
|
4
|
+
export component StaticTitle() {
|
|
5
|
+
<head>
|
|
6
|
+
<title>{'Static Test Title'}</title>
|
|
7
|
+
</head>
|
|
8
|
+
|
|
9
|
+
<div>{'Content'}</div>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Reactive title
|
|
13
|
+
export component ReactiveTitle() {
|
|
14
|
+
let title = track('Initial Title');
|
|
15
|
+
|
|
16
|
+
<head>
|
|
17
|
+
<title>{@title}</title>
|
|
18
|
+
</head>
|
|
19
|
+
<div>
|
|
20
|
+
<span>{@title}</span>
|
|
21
|
+
</div>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Multiple head elements
|
|
25
|
+
export component MultipleHeadElements() {
|
|
26
|
+
<head>
|
|
27
|
+
<title>{'Page Title'}</title>
|
|
28
|
+
<meta name="description" content="Page description" />
|
|
29
|
+
<link rel="stylesheet" href="/styles.css" />
|
|
30
|
+
</head>
|
|
31
|
+
|
|
32
|
+
<div>{'Page content'}</div>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Head with reactive meta tags
|
|
36
|
+
export component ReactiveMetaTags() {
|
|
37
|
+
let description = track('Initial description');
|
|
38
|
+
|
|
39
|
+
<head>
|
|
40
|
+
<title>{'My Page'}</title>
|
|
41
|
+
<meta name="description" content={@description} />
|
|
42
|
+
</head>
|
|
43
|
+
|
|
44
|
+
<div>{@description}</div>
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Title with template literal
|
|
48
|
+
export component TitleWithTemplate() {
|
|
49
|
+
let name = track('World');
|
|
50
|
+
|
|
51
|
+
<head>
|
|
52
|
+
<title>{`Hello ${@name}!`}</title>
|
|
53
|
+
</head>
|
|
54
|
+
|
|
55
|
+
<div>{@name}</div>
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Empty title
|
|
59
|
+
export component EmptyTitle() {
|
|
60
|
+
<head>
|
|
61
|
+
<title>{''}</title>
|
|
62
|
+
</head>
|
|
63
|
+
<div>{'Empty title test'}</div>
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Title with conditional content
|
|
67
|
+
export component ConditionalTitle() {
|
|
68
|
+
let showPrefix = track(true);
|
|
69
|
+
let title = track('Main Page');
|
|
70
|
+
|
|
71
|
+
<head>
|
|
72
|
+
<title>{@showPrefix ? 'App - ' + @title : @title}</title>
|
|
73
|
+
</head>
|
|
74
|
+
|
|
75
|
+
<div>{@title}</div>
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Title with computed value
|
|
79
|
+
export component ComputedTitle() {
|
|
80
|
+
let count = track(0);
|
|
81
|
+
let prefix = 'Count: ';
|
|
82
|
+
|
|
83
|
+
<head>
|
|
84
|
+
<title>{prefix + @count}</title>
|
|
85
|
+
</head>
|
|
86
|
+
<div>
|
|
87
|
+
<span>{@count}</span>
|
|
88
|
+
</div>
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Multiple head blocks (edge case)
|
|
92
|
+
export component MultipleHeadBlocks() {
|
|
93
|
+
<head>
|
|
94
|
+
<title>{'First Head'}</title>
|
|
95
|
+
</head>
|
|
96
|
+
|
|
97
|
+
<div>{'Content'}</div>
|
|
98
|
+
|
|
99
|
+
<head>
|
|
100
|
+
<meta name="author" content="Test Author" />
|
|
101
|
+
</head>
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Head with style tag
|
|
105
|
+
export component HeadWithStyle() {
|
|
106
|
+
<head>
|
|
107
|
+
<title>{'Styled Page'}</title>
|
|
108
|
+
</head>
|
|
109
|
+
|
|
110
|
+
<div>{'Styled content'}</div>
|
|
111
|
+
}
|