ripple 0.3.72 → 0.3.74

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/package.json +3 -3
  3. package/src/jsx-runtime.d.ts +2 -8
  4. package/src/runtime/index-client.js +3 -13
  5. package/src/runtime/internal/client/blocks.js +3 -25
  6. package/src/runtime/internal/client/for.js +80 -5
  7. package/src/runtime/internal/client/index.js +0 -2
  8. package/src/runtime/internal/client/types.d.ts +0 -10
  9. package/tests/client/__snapshots__/computed-properties.test.tsrx.snap +8 -0
  10. package/tests/client/__snapshots__/for.test.tsrx.snap +22 -0
  11. package/tests/client/__snapshots__/html.test.tsrx.snap +4 -0
  12. package/tests/client/array/array.copy-within.test.tsrx +19 -19
  13. package/tests/client/array/array.derived.test.tsrx +97 -109
  14. package/tests/client/array/array.iteration.test.tsrx +28 -28
  15. package/tests/client/array/array.mutations.test.tsrx +68 -68
  16. package/tests/client/array/array.static.test.tsrx +82 -92
  17. package/tests/client/array/array.to-methods.test.tsrx +15 -15
  18. package/tests/client/async-suspend.test.tsrx +180 -179
  19. package/tests/client/basic/__snapshots__/basic.attributes.test.tsrx.snap +2 -0
  20. package/tests/client/basic/__snapshots__/basic.rendering.test.tsrx.snap +4 -0
  21. package/tests/client/basic/basic.attributes.test.tsrx +273 -317
  22. package/tests/client/basic/basic.collections.test.tsrx +55 -61
  23. package/tests/client/basic/basic.components.test.tsrx +196 -218
  24. package/tests/client/basic/basic.errors.test.tsrx +70 -76
  25. package/tests/client/basic/basic.events.test.tsrx +80 -85
  26. package/tests/client/basic/basic.get-set.test.tsrx +54 -64
  27. package/tests/client/basic/basic.hmr.test.tsrx +15 -19
  28. package/tests/client/basic/basic.reactivity.test.tsrx +121 -135
  29. package/tests/client/basic/basic.rendering.test.tsrx +273 -178
  30. package/tests/client/basic/basic.utilities.test.tsrx +8 -10
  31. package/tests/client/boundaries.test.tsrx +18 -18
  32. package/tests/client/compiler/compiler.assignments.test.tsrx +77 -76
  33. package/tests/client/compiler/compiler.attributes.test.tsrx +18 -14
  34. package/tests/client/compiler/compiler.basic.test.tsrx +357 -288
  35. package/tests/client/compiler/compiler.regex.test.tsrx +40 -44
  36. package/tests/client/compiler/compiler.tracked-access.test.tsrx +57 -38
  37. package/tests/client/compiler/compiler.try-in-function.test.tsrx +16 -16
  38. package/tests/client/compiler/compiler.typescript.test.tsrx +4 -3
  39. package/tests/client/composite/composite.dynamic-components.test.tsrx +41 -44
  40. package/tests/client/composite/composite.generics.test.tsrx +165 -167
  41. package/tests/client/composite/composite.props.test.tsrx +66 -74
  42. package/tests/client/composite/composite.reactivity.test.tsrx +132 -166
  43. package/tests/client/composite/composite.render.test.tsrx +92 -101
  44. package/tests/client/computed-properties.test.tsrx +14 -18
  45. package/tests/client/context.test.tsrx +14 -18
  46. package/tests/client/css/global-additional-cases.test.tsrx +491 -437
  47. package/tests/client/css/global-advanced-selectors.test.tsrx +169 -153
  48. package/tests/client/css/global-at-rules.test.tsrx +71 -66
  49. package/tests/client/css/global-basic.test.tsrx +105 -98
  50. package/tests/client/css/global-classes-ids.test.tsrx +128 -114
  51. package/tests/client/css/global-combinators.test.tsrx +83 -78
  52. package/tests/client/css/global-complex-nesting.test.tsrx +134 -120
  53. package/tests/client/css/global-edge-cases.test.tsrx +138 -120
  54. package/tests/client/css/global-keyframes.test.tsrx +108 -96
  55. package/tests/client/css/global-nested.test.tsrx +88 -78
  56. package/tests/client/css/global-pseudo.test.tsrx +104 -98
  57. package/tests/client/css/global-scoping.test.tsrx +145 -125
  58. package/tests/client/css/style-identifier.test.tsrx +62 -69
  59. package/tests/client/date.test.tsrx +83 -83
  60. package/tests/client/dynamic-elements.test.tsrx +227 -283
  61. package/tests/client/events.test.tsrx +252 -266
  62. package/tests/client/for.test.tsrx +120 -127
  63. package/tests/client/head.test.tsrx +40 -48
  64. package/tests/client/html.test.tsrx +37 -49
  65. package/tests/client/input-value.test.tsrx +1125 -1354
  66. package/tests/client/lazy-array.test.tsrx +10 -16
  67. package/tests/client/lazy-destructuring.test.tsrx +169 -221
  68. package/tests/client/map.test.tsrx +39 -41
  69. package/tests/client/media-query.test.tsrx +15 -19
  70. package/tests/client/object.test.tsrx +46 -56
  71. package/tests/client/portal.test.tsrx +31 -37
  72. package/tests/client/ref.test.tsrx +173 -193
  73. package/tests/client/return.test.tsrx +62 -37
  74. package/tests/client/set.test.tsrx +33 -33
  75. package/tests/client/svg.test.tsrx +195 -215
  76. package/tests/client/switch.test.tsrx +201 -191
  77. package/tests/client/track-async-hydration.test.tsrx +14 -18
  78. package/tests/client/tracked-index-access.test.tsrx +18 -28
  79. package/tests/client/try.test.tsrx +494 -619
  80. package/tests/client/tsx.test.tsrx +286 -292
  81. package/tests/client/typescript-generics.test.tsrx +121 -129
  82. package/tests/client/url/url.derived.test.tsrx +21 -25
  83. package/tests/client/url/url.parsing.test.tsrx +35 -35
  84. package/tests/client/url/url.partial-removal.test.tsrx +32 -32
  85. package/tests/client/url/url.reactivity.test.tsrx +68 -72
  86. package/tests/client/url/url.serialization.test.tsrx +8 -8
  87. package/tests/client/url-search-params/url-search-params.derived.test.tsrx +21 -27
  88. package/tests/client/url-search-params/url-search-params.initialization.test.tsrx +16 -16
  89. package/tests/client/url-search-params/url-search-params.iteration.test.tsrx +37 -37
  90. package/tests/client/url-search-params/url-search-params.mutation.test.tsrx +56 -60
  91. package/tests/client/url-search-params/url-search-params.retrieval.test.tsrx +32 -34
  92. package/tests/client/url-search-params/url-search-params.serialization.test.tsrx +9 -9
  93. package/tests/client/url-search-params/url-search-params.tracked-url.test.tsrx +10 -10
  94. package/tests/hydration/compiled/client/basic.js +390 -319
  95. package/tests/hydration/compiled/client/composite.js +52 -44
  96. package/tests/hydration/compiled/client/for.js +734 -604
  97. package/tests/hydration/compiled/client/head.js +183 -103
  98. package/tests/hydration/compiled/client/html.js +93 -86
  99. package/tests/hydration/compiled/client/if-children.js +95 -71
  100. package/tests/hydration/compiled/client/if.js +113 -89
  101. package/tests/hydration/compiled/client/mixed-control-flow.js +225 -209
  102. package/tests/hydration/compiled/client/nested-control-flow.js +94 -98
  103. package/tests/hydration/compiled/client/reactivity.js +26 -24
  104. package/tests/hydration/compiled/client/return.js +8 -42
  105. package/tests/hydration/compiled/client/switch.js +208 -173
  106. package/tests/hydration/compiled/client/track-async-serialization.js +176 -128
  107. package/tests/hydration/compiled/client/try.js +29 -21
  108. package/tests/hydration/compiled/server/basic.js +210 -221
  109. package/tests/hydration/compiled/server/composite.js +13 -14
  110. package/tests/hydration/compiled/server/for.js +427 -444
  111. package/tests/hydration/compiled/server/head.js +199 -189
  112. package/tests/hydration/compiled/server/html.js +33 -41
  113. package/tests/hydration/compiled/server/if-children.js +114 -117
  114. package/tests/hydration/compiled/server/if.js +77 -83
  115. package/tests/hydration/compiled/server/mixed-control-flow.js +145 -150
  116. package/tests/hydration/compiled/server/nested-control-flow.js +10 -0
  117. package/tests/hydration/compiled/server/reactivity.js +24 -22
  118. package/tests/hydration/compiled/server/return.js +6 -18
  119. package/tests/hydration/compiled/server/switch.js +179 -176
  120. package/tests/hydration/compiled/server/track-async-serialization.js +88 -70
  121. package/tests/hydration/compiled/server/try.js +31 -35
  122. package/tests/hydration/components/basic.tsrx +216 -258
  123. package/tests/hydration/components/composite.tsrx +32 -42
  124. package/tests/hydration/components/events.tsrx +81 -101
  125. package/tests/hydration/components/for.tsrx +270 -336
  126. package/tests/hydration/components/head.tsrx +43 -39
  127. package/tests/hydration/components/hmr.tsrx +16 -22
  128. package/tests/hydration/components/html-in-template.tsrx +15 -21
  129. package/tests/hydration/components/html.tsrx +442 -526
  130. package/tests/hydration/components/if-children.tsrx +107 -125
  131. package/tests/hydration/components/if.tsrx +68 -90
  132. package/tests/hydration/components/mixed-control-flow.tsrx +65 -72
  133. package/tests/hydration/components/nested-control-flow.tsrx +202 -216
  134. package/tests/hydration/components/portal.tsrx +33 -41
  135. package/tests/hydration/components/reactivity.tsrx +26 -34
  136. package/tests/hydration/components/return.tsrx +4 -6
  137. package/tests/hydration/components/switch.tsrx +73 -78
  138. package/tests/hydration/components/track-async-serialization.tsrx +83 -93
  139. package/tests/hydration/components/try.tsrx +37 -51
  140. package/tests/hydration/switch.test.js +8 -8
  141. package/tests/server/await.test.tsrx +3 -3
  142. package/tests/server/basic.attributes.test.tsrx +117 -162
  143. package/tests/server/basic.components.test.tsrx +163 -193
  144. package/tests/server/basic.test.tsrx +298 -198
  145. package/tests/server/compiler.test.tsrx +142 -72
  146. package/tests/server/composite.props.test.tsrx +54 -58
  147. package/tests/server/composite.test.tsrx +165 -167
  148. package/tests/server/context.test.tsrx +13 -17
  149. package/tests/server/dynamic-elements.test.tsrx +103 -135
  150. package/tests/server/for.test.tsrx +115 -84
  151. package/tests/server/head.test.tsrx +31 -31
  152. package/tests/server/html-nesting-validation.test.tsrx +16 -8
  153. package/tests/server/if.test.tsrx +49 -59
  154. package/tests/server/lazy-destructuring.test.tsrx +288 -366
  155. package/tests/server/return.test.tsrx +58 -36
  156. package/tests/server/streaming-ssr.test.tsrx +4 -4
  157. package/tests/server/style-identifier.test.tsrx +58 -66
  158. package/tests/server/switch.test.tsrx +89 -97
  159. package/tests/server/track-async-serialization.test.tsrx +85 -103
  160. package/tests/server/try.test.tsrx +275 -360
  161. package/tests/utils/ref-types.test.js +72 -0
  162. package/tests/utils/vite-plugin-config.test.js +41 -74
  163. package/types/index.d.ts +1 -0
  164. package/src/runtime/internal/client/compat.js +0 -40
  165. package/tests/utils/compiler-compat-config.test.js +0 -38
@@ -2,53 +2,51 @@ import { flushSync, track } from 'ripple';
2
2
 
3
3
  describe('tsx expression', () => {
4
4
  it('renders an empty <></> element', () => {
5
- function App() {
6
- return <>
7
- <></>
8
- </>;
5
+ function App() @{
6
+ <></>
9
7
  }
10
8
  render(App);
11
9
  expect(container.textContent).toBe('');
12
10
  });
13
11
 
14
12
  it('renders an empty <></> fragment shorthand', () => {
15
- function App() {
16
- return <>
17
- <></>
18
- </>;
13
+ function App() @{
14
+ <></>
19
15
  }
20
16
  render(App);
21
17
  expect(container.textContent).toBe('');
22
18
  });
23
19
 
24
20
  it('renders an empty <></> assigned to a variable', () => {
25
- function App() {
26
- return <>
27
- const el = <></>;
21
+ function App() @{
22
+ const el = <></>;
23
+ <>
28
24
  {el}
29
- </>;
25
+ </>
30
26
  }
31
27
  render(App);
32
28
  expect(container.textContent).toBe('');
33
29
  });
34
30
 
35
31
  it('renders an empty <></> fragment assigned to a variable', () => {
36
- function App() {
37
- return <>
38
- const el = <></>;
32
+ function App() @{
33
+ const el = <></>;
34
+ <>
39
35
  {el}
40
- </>;
36
+ </>
41
37
  }
42
38
  render(App);
43
39
  expect(container.textContent).toBe('');
44
40
  });
45
41
 
46
42
  it('renders a basic fragment shorthand element', () => {
47
- function App() {
48
- return <>
49
- const el = <><div>"hello world"</div></>;
50
- {el}
43
+ function App() @{
44
+ const el = <>
45
+ <div>{'hello world'}</div>
51
46
  </>;
47
+ <>
48
+ {el}
49
+ </>
52
50
  }
53
51
  render(App);
54
52
  expect(container.textContent).toBe('hello world');
@@ -56,8 +54,8 @@ describe('tsx expression', () => {
56
54
  });
57
55
 
58
56
  it('applies scoped classes inside tsx blocks and fragment shorthand', () => {
59
- function App() {
60
- return <>
57
+ function App() @{
58
+ <>
61
59
  <>
62
60
  <div class="card">
63
61
  <h2>{'tsx block'}</h2>
@@ -77,7 +75,7 @@ describe('tsx expression', () => {
77
75
  color: red;
78
76
  }
79
77
  </style>
80
- </>;
78
+ </>
81
79
  }
82
80
 
83
81
  render(App);
@@ -102,11 +100,11 @@ describe('tsx expression', () => {
102
100
  });
103
101
 
104
102
  it('renders a tsx element assigned to a variable', () => {
105
- function App() {
106
- return <>
107
- const el = <><span class="test">"content"</span></>;
103
+ function App() @{
104
+ const el = <span class="test">{'content'}</span>;
105
+ <>
108
106
  {el}
109
- </>;
107
+ </>
110
108
  }
111
109
  render(App);
112
110
  const span = container.querySelector('span.test');
@@ -115,14 +113,14 @@ describe('tsx expression', () => {
115
113
  });
116
114
 
117
115
  it('renders a tsx element with multiple children', () => {
118
- function App() {
119
- return <>
120
- const el = <>
121
- <div>"first"</div>
122
- <div>"second"</div>
123
- </>;
124
- {el}
116
+ function App() @{
117
+ const el = <>
118
+ <div>first</div>
119
+ <div>second</div>
125
120
  </>;
121
+ <>
122
+ {el}
123
+ </>
126
124
  }
127
125
  render(App);
128
126
  const divs = container.querySelectorAll('div');
@@ -132,15 +130,15 @@ describe('tsx expression', () => {
132
130
  });
133
131
 
134
132
  it('renders a tsx element with nested elements', () => {
135
- function App() {
136
- return <>
137
- const el = <>
138
- <div class="outer">
139
- <span class="inner">"nested"</span>
140
- </div>
141
- </>;
142
- {el}
133
+ function App() @{
134
+ const el = <>
135
+ <div class="outer">
136
+ <span class="inner">nested</span>
137
+ </div>
143
138
  </>;
139
+ <>
140
+ {el}
141
+ </>
144
142
  }
145
143
  render(App);
146
144
  const outer = container.querySelector('div.outer');
@@ -151,11 +149,9 @@ describe('tsx expression', () => {
151
149
  });
152
150
 
153
151
  it('renders a tsx element inline in a parent element', () => {
154
- function App() {
155
- return <>
156
- const el = <><span>"inline"</span></>;
157
- <div class="parent">{el}</div>
158
- </>;
152
+ function App() @{
153
+ const el = <span>{'inline'}</span>;
154
+ <div class="parent">{el}</div>
159
155
  }
160
156
  render(App);
161
157
  const parent = container.querySelector('div.parent');
@@ -165,17 +161,17 @@ describe('tsx expression', () => {
165
161
  });
166
162
 
167
163
  it('renders a tsx element with reactive expressions', () => {
168
- function App() {
169
- return <>
170
- let &[count] = track(0);
171
- const el = <>
172
- <div>
173
- {'count: ' + count}
174
- </div>
175
- </>;
164
+ function App() @{
165
+ let &[count] = track(0);
166
+ const el = <>
167
+ <div>
168
+ {'count: ' + count}
169
+ </div>
170
+ </>;
171
+ <>
176
172
  {el}
177
173
  <button onClick={() => count++}>{'increment'}</button>
178
- </>;
174
+ </>
179
175
  }
180
176
  render(App);
181
177
  expect(container.querySelector('div').textContent).toBe('count: 0');
@@ -186,15 +182,17 @@ describe('tsx expression', () => {
186
182
  });
187
183
 
188
184
  it('conditionally renders tsx elements', () => {
189
- function App() {
190
- return <>
191
- let &[show] = track(true);
192
- const el = <><div class="tsx-content">"visible"</div></>;
193
- if (show) {
194
- {el}
185
+ function App() @{
186
+ let &[show] = track(true);
187
+ const el = <div class="tsx-content">{'content'}</div>;
188
+ <>
189
+ @if (show) {
190
+ <>
191
+ {el}
192
+ </>
195
193
  }
196
194
  <button onClick={() => (show = !show)}>{'toggle'}</button>
197
- </>;
195
+ </>
198
196
  }
199
197
  render(App);
200
198
  expect(container.querySelector('.tsx-content')).toBeTruthy();
@@ -209,15 +207,13 @@ describe('tsx expression', () => {
209
207
  });
210
208
 
211
209
  it('renders tsx element passed as children prop', () => {
212
- function Child(&{ children }) {
213
- return <><div class="wrapper">{children}</div></>;
210
+ function Child(&{ children }) @{
211
+ <div class="wrapper">{children}</div>
214
212
  }
215
213
 
216
- function App() {
217
- return <>
218
- const el = <><span>"from tsx"</span></>;
219
- <Child children={el} />
220
- </>;
214
+ function App() @{
215
+ const el = <span>{'from tsx'}</span>;
216
+ <Child children={el} />
221
217
  }
222
218
  render(App);
223
219
  const wrapper = container.querySelector('.wrapper');
@@ -227,11 +223,11 @@ describe('tsx expression', () => {
227
223
  });
228
224
 
229
225
  it('renders tsx element with text content only', () => {
230
- function App() {
231
- return <>
232
- const el = <>"just text"</>;
226
+ function App() @{
227
+ const el = <>just text</>;
228
+ <>
233
229
  {el}
234
- </>;
230
+ </>
235
231
  }
236
232
 
237
233
  render(App);
@@ -239,13 +235,13 @@ describe('tsx expression', () => {
239
235
  });
240
236
 
241
237
  it('renders tsx element with static attributes', () => {
242
- function App() {
243
- return <>
244
- const el = <>
245
- <div id="my-id" class="my-class" data-testid="test" aria-label="label">"content"</div>
246
- </>;
238
+ function App() @{
239
+ const el = <div id="my-id" class="my-class" data-testid="test" aria-label="label">
240
+ content
241
+ </div>;
242
+ <>
247
243
  {el}
248
- </>;
244
+ </>
249
245
  }
250
246
 
251
247
  render(App);
@@ -257,13 +253,13 @@ describe('tsx expression', () => {
257
253
  });
258
254
 
259
255
  it('renders tsx element with dynamic attribute values', () => {
260
- function App() {
261
- return <>
262
- let &[name] = track('initial');
263
- const el = <><div id={name} class={'cls-' + name}>"content"</div></>;
256
+ function App() @{
257
+ let &[name] = track('initial');
258
+ const el = <div id={name} class={'cls-' + name}>{'content'}</div>;
259
+ <>
264
260
  {el}
265
261
  <button onClick={() => (name = 'updated')}>{'update'}</button>
266
- </>;
262
+ </>
267
263
  }
268
264
 
269
265
  render(App);
@@ -278,13 +274,15 @@ describe('tsx expression', () => {
278
274
  });
279
275
 
280
276
  it('renders tsx element with event handlers', () => {
281
- function App() {
282
- return <>
283
- let &[clicked] = track(false);
284
- const el = <><button onClick={() => (clicked = true)}>{'click me'}</button></>;
277
+ function App() @{
278
+ let &[clicked] = track(false);
279
+ const el = <button onClick={() => (clicked = true)}>{'click'}</button>;
280
+ <>
285
281
  {el}
286
- <div class="status">{clicked ? 'clicked' : 'not clicked'}</div>
287
- </>;
282
+ <div class="status">
283
+ {clicked ? 'clicked' : 'not clicked'}
284
+ </div>
285
+ </>
288
286
  }
289
287
 
290
288
  render(App);
@@ -296,13 +294,13 @@ describe('tsx expression', () => {
296
294
  });
297
295
 
298
296
  it('renders tsx element with boolean attributes', () => {
299
- function App() {
300
- return <>
301
- let &[isDisabled] = track(true);
302
- const el = <><button disabled={isDisabled}>{'btn'}</button></>;
297
+ function App() @{
298
+ let &[isDisabled] = track(true);
299
+ const el = <button disabled={isDisabled}>{'submit'}</button>;
300
+ <>
303
301
  {el}
304
302
  <button class="toggle" onClick={() => (isDisabled = !isDisabled)}>{'toggle'}</button>
305
- </>;
303
+ </>
306
304
  }
307
305
 
308
306
  render(App);
@@ -314,13 +312,13 @@ describe('tsx expression', () => {
314
312
  });
315
313
 
316
314
  it('renders tsx element with style attribute', () => {
317
- function App() {
318
- return <>
319
- let &[color] = track('red');
320
- const el = <><div style={'color: ' + color}>"styled"</div></>;
315
+ function App() @{
316
+ let &[color] = track('red');
317
+ const el = <div style={{ color }}>{'content'}</div>;
318
+ <>
321
319
  {el}
322
320
  <button onClick={() => (color = 'blue')}>{'change color'}</button>
323
- </>;
321
+ </>
324
322
  }
325
323
 
326
324
  render(App);
@@ -332,22 +330,20 @@ describe('tsx expression', () => {
332
330
  });
333
331
 
334
332
  it('renders tsx element with multiple dynamic attributes', () => {
335
- function App() {
336
- return <>
337
- let &[index] = track(0);
338
- const el = <>
339
- <div
340
- id={'item-' + index}
341
- class={'item pos-' + index}
342
- data-index={index}
343
- title={'Item ' + index}
344
- >
345
- {'Item ' + index}
346
- </div>
347
- </>;
333
+ function App() @{
334
+ let &[index] = track(0);
335
+ const el = <div
336
+ id={'item-' + index}
337
+ class={'item pos-' + index}
338
+ data-index={index}
339
+ title={'Item ' + index}
340
+ >
341
+ {'Item ' + index}
342
+ </div>;
343
+ <>
348
344
  {el}
349
345
  <button onClick={() => index++}>{'next'}</button>
350
- </>;
346
+ </>
351
347
  }
352
348
 
353
349
  render(App);
@@ -366,12 +362,12 @@ describe('tsx expression', () => {
366
362
  });
367
363
 
368
364
  it('renders fragment shorthand passed directly as component prop', () => {
369
- function Wrapper(&{ content }) {
370
- return <><div class="wrapper">{content}</div></>;
365
+ function Wrapper(&{ content }) @{
366
+ <div class="wrapper">{content}</div>
371
367
  }
372
368
 
373
- function App() {
374
- return <><Wrapper content={<><span class="inner">"direct prop"</span></>} /></>;
369
+ function App() @{
370
+ <Wrapper content={<><span class="inner">direct prop</span></>} />
375
371
  }
376
372
 
377
373
  render(App);
@@ -382,22 +378,15 @@ describe('tsx expression', () => {
382
378
  });
383
379
 
384
380
  it('renders fragment shorthand passed directly as children prop', () => {
385
- function Card(&{ title, children }) {
386
- return <>
387
- <div class="card">
388
- <h2 class="card-title">{title}</h2>
389
- <div class="card-body">{children}</div>
390
- </div>
391
- </>;
381
+ function Card(&{ title, children }) @{
382
+ <div class="card">
383
+ <h2 class="card-title">{title}</h2>
384
+ <div class="card-body">{children}</div>
385
+ </div>
392
386
  }
393
387
 
394
- function App() {
395
- return <>
396
- <Card
397
- title={<><span class="bold">"Title"</span></>}
398
- children={<><p>"Card content here"</p></>}
399
- />
400
- </>;
388
+ function App() @{
389
+ <Card title={<><span class="bold">Title</span></>} children={<><p>Card content here</p></>} />
401
390
  }
402
391
 
403
392
  render(App);
@@ -407,15 +396,15 @@ describe('tsx expression', () => {
407
396
  });
408
397
 
409
398
  it('renders tsx from function defined outside component', () => {
410
- function createBadge(label) {
411
- return <><span class="badge">{label}</span></>;
399
+ function createBadge(label) @{
400
+ <span class="badge">{label}</span>
412
401
  }
413
402
 
414
- function App() {
415
- return <>
416
- const badge = createBadge('New');
403
+ function App() @{
404
+ const badge = createBadge('New');
405
+ <>
417
406
  {badge}
418
- </>;
407
+ </>
419
408
  }
420
409
 
421
410
  render(App);
@@ -424,19 +413,19 @@ describe('tsx expression', () => {
424
413
  });
425
414
 
426
415
  it('renders tsx from function with multiple elements', () => {
427
- function createListItem(item) {
428
- return <><li class="list-item">{item}</li></>;
416
+ function createListItem(item) @{
417
+ <li class="list-item">{item}</li>
429
418
  }
430
419
 
431
- function App() {
432
- return <>
433
- const items = ['Apple', 'Banana', 'Cherry'];
434
- <ul>
435
- for (const item of items) {
420
+ function App() @{
421
+ const items = ['Apple', 'Banana', 'Cherry'];
422
+ <ul>
423
+ @for (const item of items) {
424
+ <>
436
425
  {createListItem(item)}
437
- }
438
- </ul>
439
- </>;
426
+ </>
427
+ }
428
+ </ul>
440
429
  }
441
430
 
442
431
  render(App);
@@ -448,26 +437,24 @@ describe('tsx expression', () => {
448
437
  });
449
438
 
450
439
  it('renders tsx from factory function passed to component', () => {
451
- function List(&{ renderItem, items }) {
452
- return <>
453
- <ul class="list">
454
- for (const item of items) {
440
+ function List(&{ renderItem, items }) @{
441
+ <ul class="list">
442
+ @for (const item of items) {
443
+ <>
455
444
  {renderItem(item)}
456
- }
457
- </ul>
458
- </>;
445
+ </>
446
+ }
447
+ </ul>
459
448
  }
460
449
 
461
- function itemRenderer(item) {
462
- return <>
463
- <li>
464
- <span class="item-content">{item}</span>
465
- </li>
466
- </>;
450
+ function itemRenderer(item) @{
451
+ <li>
452
+ <span class="item-content">{item}</span>
453
+ </li>
467
454
  }
468
455
 
469
- function App() {
470
- return <><List items={['One', 'Two', 'Three']} renderItem={itemRenderer} /></>;
456
+ function App() @{
457
+ <List items={['One', 'Two', 'Three']} renderItem={itemRenderer} />
471
458
  }
472
459
 
473
460
  render(App);
@@ -479,24 +466,20 @@ describe('tsx expression', () => {
479
466
  });
480
467
 
481
468
  it('renders tsx from function with reactive state', () => {
482
- function createCounter(label, getCount) {
483
- return <>
484
- <div class="counter-display">
485
- <span class="label">{label}</span>
486
- <span class="count">
487
- {getCount()}
488
- </span>
489
- </div>
490
- </>;
469
+ function createCounter(label, getCount) @{
470
+ <div class="counter-display">
471
+ <span class="label">{label}</span>
472
+ <span class="count">{getCount()}</span>
473
+ </div>
491
474
  }
492
475
 
493
- function App() {
494
- return <>
495
- let &[count] = track(0);
496
- const counterElement = createCounter('Count:', () => count);
476
+ function App() @{
477
+ let &[count] = track(0);
478
+ const counterElement = createCounter('Count:', () => count);
479
+ <>
497
480
  {counterElement}
498
481
  <button onClick={() => count++}>{'increment'}</button>
499
- </>;
482
+ </>
500
483
  }
501
484
 
502
485
  render(App);
@@ -509,24 +492,22 @@ describe('tsx expression', () => {
509
492
  });
510
493
 
511
494
  it('renders nested tsx from multiple functions', () => {
512
- function createIcon(name) {
513
- return <><i class={'icon icon-' + name} /></>;
495
+ function createIcon(name) @{
496
+ <i class={'icon icon-' + name} />
514
497
  }
515
498
 
516
- function createButton(icon, label) {
517
- return <>
518
- <button class="icon-button">
519
- {createIcon(icon)}
520
- <span class="btn-label">{label}</span>
521
- </button>
522
- </>;
499
+ function createButton(icon, label) @{
500
+ <button class="icon-button">
501
+ {createIcon(icon)}
502
+ <span class="btn-label">{label}</span>
503
+ </button>
523
504
  }
524
505
 
525
- function App() {
526
- return <>
527
- const btn = createButton('save', 'Save');
506
+ function App() @{
507
+ const btn = createButton('save', 'Save');
508
+ <>
528
509
  {btn}
529
- </>;
510
+ </>
530
511
  }
531
512
 
532
513
  render(App);
@@ -537,23 +518,31 @@ describe('tsx expression', () => {
537
518
  });
538
519
 
539
520
  it('renders deeply nested tsx and tsrx expression values', () => {
540
- function HelperItem({ item }) {
541
- return <><div class="helper-item">{item}</div></>;
521
+ function HelperItem({ item }) @{
522
+ <div class="helper-item">{item}</div>
542
523
  }
543
524
 
544
- function MakeFragment({ label }) {
545
- return <>
525
+ function MakeFragment({ label }) @{
526
+ const test = <>
527
+ {[1, 2, 3, 4].map(
528
+ (item) => @{
529
+ <HelperItem {item} />
530
+ },
531
+ )}
532
+ </>;
533
+ <>
546
534
  <span class="label">{label}</span>
547
- const test = <>{[1, 2, 3, 4].map((item) => <><HelperItem {item} /></>)}</>;
548
535
  {test}
549
- </>;
536
+ </>
550
537
  }
551
538
 
552
- function App() {
553
- return <>
554
- {<>{[1, 2, 3].map((item) => <div class="app-item">{item}</div>)}</>}
539
+ function App() @{
540
+ <>
541
+ <>
542
+ {[1, 2, 3].map((item) => <div class="app-item">{item}</div>)}
543
+ </>
555
544
  <MakeFragment label="from helper" />
556
- </>;
545
+ </>
557
546
  }
558
547
 
559
548
  render(App);
@@ -572,15 +561,15 @@ describe('tsx expression', () => {
572
561
  });
573
562
 
574
563
  it('renders tsrx nested directly inside a top-level tsx expression value', () => {
575
- function App() {
576
- return <>
577
- const content = <>
578
- <section class="outer">
579
- {<><div class="inner">{'from tsrx'}</div></>}
580
- </section>
581
- </>;
582
- {content}
564
+ function App() @{
565
+ const content = <>
566
+ <section class="outer">
567
+ {<><div class="inner">{'from tsrx'}</div></>}
568
+ </section>
583
569
  </>;
570
+ <>
571
+ {content}
572
+ </>
584
573
  }
585
574
 
586
575
  render(App);
@@ -591,19 +580,19 @@ describe('tsx expression', () => {
591
580
  });
592
581
 
593
582
  it('renders nested elements from tsrx inside a top-level tsx value', () => {
594
- function App() {
595
- return <>
596
- const content = <>
597
- <div class="wrapper">
598
- {<>
599
- <section class="native">
600
- <span class="nested-tsrx">{'inside nested tsrx'}</span>
601
- </section>
602
- </>}
603
- </div>
604
- </>;
605
- {content}
583
+ function App() @{
584
+ const content = <>
585
+ <div class="wrapper">
586
+ {<>
587
+ <section class="native">
588
+ <span class="nested-tsrx">{'inside nested tsrx'}</span>
589
+ </section>
590
+ </>}
591
+ </div>
606
592
  </>;
593
+ <>
594
+ {content}
595
+ </>
607
596
  }
608
597
 
609
598
  render(App);
@@ -614,12 +603,12 @@ describe('tsx expression', () => {
614
603
  });
615
604
 
616
605
  it('renders tsx declared before a top-level tsx value', () => {
617
- function App() {
618
- return <>
619
- const nested = <><span class="nested-tsx">{'inside nested tsx'}</span></>;
620
- const content = <><div class="native">{nested}</div></>;
606
+ function App() @{
607
+ const nested = <span class="nested-tsx">{'inside nested tsx'}</span>;
608
+ const content = <><div class="native">{nested}</div></>;
609
+ <>
621
610
  {content}
622
- </>;
611
+ </>
623
612
  }
624
613
 
625
614
  render(App);
@@ -630,20 +619,18 @@ describe('tsx expression', () => {
630
619
  });
631
620
 
632
621
  it('renders tsx as prop with fallback in component', () => {
633
- function Alert(&{ icon, message }) {
634
- return <>
635
- <div class="alert">
636
- {icon}
637
- <span class="message">{message}</span>
638
- </div>
639
- </>;
622
+ function Alert(&{ icon, message }) @{
623
+ <div class="alert">
624
+ {icon}
625
+ <span class="message">{message}</span>
626
+ </div>
640
627
  }
641
628
 
642
- function App() {
643
- return <>
629
+ function App() @{
630
+ <>
644
631
  <Alert message="No icon" />
645
- <Alert icon={<><span class="custom-icon">""</span></>} message="Custom icon" />
646
- </>;
632
+ <Alert icon={<><span class="custom-icon">{''}</span></>} message="Custom icon" />
633
+ </>
647
634
  }
648
635
 
649
636
  render(App);
@@ -654,23 +641,23 @@ describe('tsx expression', () => {
654
641
  });
655
642
 
656
643
  it('renders tsx stored in array via function', () => {
657
- function createItem(className, content) {
658
- return <><div class={className}>{content}</div></>;
659
- }
660
-
661
- function App() {
662
- return <>
663
- const items = [
664
- { className: 'item-a', content: 'A' },
665
- { className: 'item-b', content: 'B' },
666
- { className: 'item-c', content: 'C' },
667
- ];
668
- <div class="container">
669
- for (const item of items) {
644
+ function createItem(className, content) @{
645
+ <div class={className}>{content}</div>
646
+ }
647
+
648
+ function App() @{
649
+ const items = [
650
+ { className: 'item-a', content: 'A' },
651
+ { className: 'item-b', content: 'B' },
652
+ { className: 'item-c', content: 'C' },
653
+ ];
654
+ <div class="container">
655
+ @for (const item of items) {
656
+ <>
670
657
  {createItem(item.className, item.content)}
671
- }
672
- </div>
673
- </>;
658
+ </>
659
+ }
660
+ </div>
674
661
  }
675
662
 
676
663
  render(App);
@@ -681,22 +668,23 @@ describe('tsx expression', () => {
681
668
  });
682
669
 
683
670
  it('renders tsx conditionally from function', () => {
684
- function createContent(type) {
685
- if (type === 'success') {
686
- return <><div class="success">"Success!"</div></>;
687
- } else if (type === 'error') {
688
- return <><div class="error">"Error!"</div></>;
671
+ function createContent(type) @{
672
+ @if (type === 'success') {
673
+ <div class="success">Success!</div>
674
+ } @else if (type === 'error') {
675
+ <div class="error">Error!</div>
676
+ } @else {
677
+ <div class="default">Default</div>
689
678
  }
690
- return <><div class="default">"Default"</div></>;
691
679
  }
692
680
 
693
- function App() {
694
- return <>
695
- let &[status] = track('default');
681
+ function App() @{
682
+ let &[status] = track('default');
683
+ <>
696
684
  {createContent(status)}
697
685
  <button class="set-success" onClick={() => (status = 'success')}>{'Success'}</button>
698
686
  <button class="set-error" onClick={() => (status = 'error')}>{'Error'}</button>
699
- </>;
687
+ </>
700
688
  }
701
689
 
702
690
  render(App);
@@ -715,12 +703,14 @@ describe('tsx expression', () => {
715
703
  });
716
704
 
717
705
  describe('tsrx expression', () => {
718
- it('renders native double-quoted text in an assigned fragment', () => {
719
- function App() {
720
- return <>
721
- const el = <><div>"Hello world"</div></>;
722
- {el}
706
+ it('renders native JSX text in an assigned fragment', () => {
707
+ function App() @{
708
+ const el = <>
709
+ <div>Hello world</div>
723
710
  </>;
711
+ <>
712
+ {el}
713
+ </>
724
714
  }
725
715
 
726
716
  render(App);
@@ -731,14 +721,14 @@ describe('tsrx expression', () => {
731
721
  });
732
722
 
733
723
  it('runs setup statements before native template output', () => {
734
- function App() {
735
- return <>
736
- const el = <>
737
- const label = 'from setup';
738
- <div>{label}</div>
739
- </>;
740
- {el}
724
+ function App() @{
725
+ const label = 'from setup';
726
+ const el = <>
727
+ <div>{label}</div>
741
728
  </>;
729
+ <>
730
+ {el}
731
+ </>
742
732
  }
743
733
 
744
734
  render(App);
@@ -747,13 +737,17 @@ describe('tsrx expression', () => {
747
737
  });
748
738
 
749
739
  it('updates reactive expressions inside native fragments', () => {
750
- function App() {
751
- return <>
752
- let &[count] = track(0);
753
- const el = <><div>{'count: ' + count}</div></>;
740
+ function App() @{
741
+ let &[count] = track(0);
742
+ const el = <>
743
+ <div>
744
+ {'count: ' + count}
745
+ </div>
746
+ </>;
747
+ <>
754
748
  {el}
755
749
  <button onClick={() => count++}>{'increment'}</button>
756
- </>;
750
+ </>
757
751
  }
758
752
 
759
753
  render(App);
@@ -765,15 +759,15 @@ describe('tsrx expression', () => {
765
759
  });
766
760
 
767
761
  it('renders native control flow inside an assigned fragment', () => {
768
- function App() {
769
- return <>
770
- const el = <>
771
- if (true) {
772
- <span>"visible"</span>
773
- }
774
- </>;
775
- {el}
762
+ function App() @{
763
+ const el = <>
764
+ @if (true) {
765
+ <span>visible</span>
766
+ }
776
767
  </>;
768
+ <>
769
+ {el}
770
+ </>
777
771
  }
778
772
 
779
773
  render(App);
@@ -782,12 +776,12 @@ describe('tsrx expression', () => {
782
776
  });
783
777
 
784
778
  it('renders fragments returned from component functions outside components', () => {
785
- function MakeFragment({ label }) {
786
- return <><span>{label}</span></>;
779
+ function MakeFragment({ label }) @{
780
+ <span>{label}</span>
787
781
  }
788
782
 
789
- function App() {
790
- return <><MakeFragment label="from helper" /></>;
783
+ function App() @{
784
+ <MakeFragment label="from helper" />
791
785
  }
792
786
 
793
787
  render(App);