ripple 0.2.208 → 0.2.211
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 +57 -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 +24 -7
- 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 +15 -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 +119 -12
- 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/composite.js +139 -0
- 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/composite.js +176 -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/composite.ripple +37 -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/composite.test.js +42 -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
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export component Layout({ children }) {
|
|
2
|
+
<div class="layout">
|
|
3
|
+
<children />
|
|
4
|
+
</div>
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export component SingleChild() {
|
|
8
|
+
<div class="single">{'single'}</div>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export component MultiRootChild() {
|
|
12
|
+
<h1>{'title'}</h1>
|
|
13
|
+
<p>{'description'}</p>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export component EmptyLayout() {
|
|
17
|
+
<Layout />
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export component LayoutWithSingleChild() {
|
|
21
|
+
<Layout>
|
|
22
|
+
<SingleChild />
|
|
23
|
+
</Layout>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export component LayoutWithMultipleChildren() {
|
|
27
|
+
<Layout>
|
|
28
|
+
<SingleChild />
|
|
29
|
+
<div class="extra">{'extra'}</div>
|
|
30
|
+
</Layout>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export component LayoutWithMultiRootChild() {
|
|
34
|
+
<Layout>
|
|
35
|
+
<MultiRootChild />
|
|
36
|
+
</Layout>
|
|
37
|
+
}
|
|
@@ -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
|
+
}
|