ripple 0.3.14 → 0.3.15

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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # ripple
2
2
 
3
+ ## 0.3.15
4
+
5
+ ### Patch Changes
6
+
7
+ - [`a14097a`](https://github.com/Ripple-TS/ripple/commit/a14097a688ad85c236a6619cef527c78787ab367)
8
+ Thanks [@leonidaz](https://github.com/leonidaz)! - Fix children prop precedence
9
+ when invoking components so that template children always win over an explicit
10
+ `children=` attribute, while still respecting JSX-like ordering between explicit
11
+ props and spreads when no template children are present.
12
+
13
+ - Updated dependencies
14
+ [[`a14097a`](https://github.com/Ripple-TS/ripple/commit/a14097a688ad85c236a6619cef527c78787ab367)]:
15
+ - ripple@0.3.15
16
+
3
17
  ## 0.3.14
4
18
 
5
19
  ### Patch Changes
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Ripple is an elegant TypeScript UI framework",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.3.14",
6
+ "version": "0.3.15",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index-client.js",
9
9
  "main": "src/runtime/index-client.js",
@@ -83,9 +83,9 @@
83
83
  "@volar/language-core": "~2.4.28",
84
84
  "vscode-languageserver-types": "^3.17.5",
85
85
  "@tsrx/core": "0.0.2",
86
- "@tsrx/ripple": "0.0.2"
86
+ "@tsrx/ripple": "0.0.3"
87
87
  },
88
88
  "peerDependencies": {
89
- "ripple": "0.3.14"
89
+ "ripple": "0.3.15"
90
90
  }
91
91
  }
@@ -1,6 +1,5 @@
1
1
  import type {
2
2
  Tracked,
3
- Props,
4
3
  PropsWithChildren,
5
4
  PropsWithExtras,
6
5
  Component,
@@ -414,4 +413,107 @@ describe('basic client > components & composition', () => {
414
413
  button.click();
415
414
  }).not.toThrow();
416
415
  });
416
+
417
+ it('renders explicit children prop without spread', () => {
418
+ component Card(props: PropsWithChildren<{}>) {
419
+ <div class="card">{props.children}</div>
420
+ }
421
+
422
+ component App() {
423
+ <Card children="fallback text" />
424
+ }
425
+
426
+ render(App);
427
+ expect(container.querySelector('.card').textContent).toBe('fallback text');
428
+ });
429
+
430
+ it('renders explicit children before spread', () => {
431
+ component Card(props: PropsWithChildren<{ id: string }>) {
432
+ <div class="card">{props.children}</div>
433
+ }
434
+
435
+ component App() {
436
+ const extra = { id: '1' };
437
+ <Card children="fallback text" {...extra} />
438
+ }
439
+
440
+ render(App);
441
+ expect(container.querySelector('.card').textContent).toBe('fallback text');
442
+ });
443
+
444
+ it('renders spread before explicit children', () => {
445
+ component Card(props: PropsWithChildren<{ id: string }>) {
446
+ <div class="card">{props.children}</div>
447
+ }
448
+
449
+ component App() {
450
+ const extra = { id: '1' };
451
+ <Card {...extra} children="fallback text" />
452
+ }
453
+
454
+ render(App);
455
+ expect(container.querySelector('.card').textContent).toBe('fallback text');
456
+ });
457
+
458
+ it('template children override explicit children before spread', () => {
459
+ component Card(props: PropsWithChildren<{ id: string }>) {
460
+ <div class="card">{props.children}</div>
461
+ }
462
+
463
+ component App() {
464
+ const extra = { id: '1' };
465
+ <Card children="fallback text" {...extra}>
466
+ <span>{'template content'}</span>
467
+ </Card>
468
+ }
469
+
470
+ render(App);
471
+ expect(container.querySelector('.card span').textContent).toBe('template content');
472
+ expect(container.querySelector('.card').textContent).toBe('template content');
473
+ });
474
+
475
+ it('template children override explicit children after spread', () => {
476
+ component Card(props: PropsWithChildren<{ id: string }>) {
477
+ <div class="card">{props.children}</div>
478
+ }
479
+
480
+ component App() {
481
+ const extra = { id: '1' };
482
+ <Card {...extra} children="fallback text">
483
+ <span>{'template content'}</span>
484
+ </Card>
485
+ }
486
+
487
+ render(App);
488
+ expect(container.querySelector('.card span').textContent).toBe('template content');
489
+ expect(container.querySelector('.card').textContent).toBe('template content');
490
+ });
491
+
492
+ it('spread can override explicit children when no template children', () => {
493
+ component Card(props: PropsWithChildren<{ id: string }>) {
494
+ <div class="card">{props.children}</div>
495
+ }
496
+
497
+ component App() {
498
+ const extra = { id: '1', children: 'from spread' };
499
+ <Card children="explicit" {...extra} />
500
+ }
501
+
502
+ render(App);
503
+ expect(container.querySelector('.card').textContent).toBe('from spread');
504
+ });
505
+
506
+ it('explicit children overrides spread children when it comes after', () => {
507
+ component Card(props: PropsWithChildren<{ id: string }>) {
508
+ <div class="card">{props.children}</div>
509
+ }
510
+
511
+ component App() {
512
+ const extra = { id: '1', children: 'from spread' };
513
+ <Card {...extra} children="explicit" />
514
+ }
515
+
516
+ render(App);
517
+ expect(container.querySelector('.card').textContent).toBe('explicit');
518
+ });
417
519
  });
@@ -535,8 +535,9 @@ export component App() {
535
535
 
536
536
  const result = compile(source, 'test.ripple', { mode: 'client' }).js.code;
537
537
 
538
+ // Template children should take precedence - explicit children prop should be removed
538
539
  expect((result.match(/children:/g) || []).length).toBe(1);
539
- expect(result).toContain('children: _$_.normalize_children(fallback) ?? _$_.ripple_element(');
540
+ expect(result).toContain('children: _$_.ripple_element(');
540
541
  });
541
542
 
542
543
  it('should not error on `this` MemberExpression with a UpdateExpression', () => {
@@ -654,4 +655,40 @@ export component App() {}`;
654
655
 
655
656
  expect(result).toContain('class StringMap extends Map<string, string> {}');
656
657
  });
658
+
659
+ it('wraps children in normalize_children for explicit children prop passed to component', () => {
660
+ const source = `
661
+ component Card(props) {
662
+ <div>{props.children}</div>
663
+ }
664
+
665
+ export component App() {
666
+ const content = 'hello';
667
+
668
+ <Card children={content} />
669
+ }
670
+ `;
671
+
672
+ const result = compile(source, 'test.ripple', { mode: 'client' }).js.code;
673
+
674
+ expect(result).toContain('_$_.normalize_children(');
675
+ });
676
+
677
+ it('uses spread_props for spreads that may contain children', () => {
678
+ const source = `
679
+ component Card(props) {
680
+ <div>{props.children}</div>
681
+ }
682
+
683
+ export component App() {
684
+ const props = { children: 'hello' };
685
+
686
+ <Card {...props} />
687
+ }
688
+ `;
689
+
690
+ const result = compile(source, 'test.ripple', { mode: 'client' }).js.code;
691
+
692
+ expect(result).toContain('_$_.spread_props(');
693
+ });
657
694
  });
@@ -235,4 +235,118 @@ describe('basic server > components & composition', () => {
235
235
 
236
236
  expect(document.querySelector('div')).toBeNull();
237
237
  });
238
+
239
+ it('renders explicit children prop without spread', async () => {
240
+ component Card(props: PropsWithChildren<{}>) {
241
+ <div class="card">{props.children}</div>
242
+ }
243
+
244
+ component App() {
245
+ <Card children="fallback text" />
246
+ }
247
+
248
+ const { body } = await render(App);
249
+ const { document } = parseHtml(body);
250
+ expect(document.querySelector('.card').textContent).toBe('fallback text');
251
+ });
252
+
253
+ it('renders explicit children before spread', async () => {
254
+ component Card(props: PropsWithChildren<{ id: string }>) {
255
+ <div class="card">{props.children}</div>
256
+ }
257
+
258
+ component App() {
259
+ const extra = { id: '1' };
260
+ <Card children="fallback text" {...extra} />
261
+ }
262
+
263
+ const { body } = await render(App);
264
+ const { document } = parseHtml(body);
265
+ expect(document.querySelector('.card').textContent).toBe('fallback text');
266
+ });
267
+
268
+ it('renders spread before explicit children', async () => {
269
+ component Card(props: PropsWithChildren<{ id: string }>) {
270
+ <div class="card">{props.children}</div>
271
+ }
272
+
273
+ component App() {
274
+ const extra = { id: '1' };
275
+ <Card {...extra} children="fallback text" />
276
+ }
277
+
278
+ const { body } = await render(App);
279
+ const { document } = parseHtml(body);
280
+ expect(document.querySelector('.card').textContent).toBe('fallback text');
281
+ });
282
+
283
+ it('template children override explicit children before spread', async () => {
284
+ component Card(props: PropsWithChildren<{ id: string }>) {
285
+ <div class="card">{props.children}</div>
286
+ }
287
+
288
+ component App() {
289
+ const extra = { id: '1' };
290
+ <Card children="fallback text" {...extra}>
291
+ <span>{'template content'}</span>
292
+ </Card>
293
+ }
294
+
295
+ const { body } = await render(App);
296
+ const { document } = parseHtml(body);
297
+ expect(document.querySelector('.card span').textContent).toBe('template content');
298
+ expect(document.querySelector('.card').textContent).toBe('template content');
299
+ });
300
+
301
+ it('template children override explicit children after spread', async () => {
302
+ component Card(props: PropsWithChildren<{ id: string }>) {
303
+ <div class="card">{props.children}</div>
304
+ }
305
+
306
+ component App() {
307
+ const extra = { id: '1' };
308
+ <Card {...extra} children="fallback text">
309
+ <span>{'template content'}</span>
310
+ </Card>
311
+ }
312
+
313
+ const { body } = await render(App);
314
+ const { document } = parseHtml(body);
315
+ expect(document.querySelector('.card span').textContent).toBe('template content');
316
+ expect(document.querySelector('.card').textContent).toBe('template content');
317
+ });
318
+
319
+ it('spread can override explicit children when no template children', async () => {
320
+ component Card(props: PropsWithChildren<{ id: string }>) {
321
+ <div class="card">{props.children}</div>
322
+ }
323
+
324
+ component App() {
325
+ const extra = { id: '1', children: 'from spread' };
326
+ <Card
327
+ // @ts-ignore
328
+ children="explicit"
329
+ {...extra}
330
+ />
331
+ }
332
+
333
+ const { body } = await render(App);
334
+ const { document } = parseHtml(body);
335
+ expect(document.querySelector('.card').textContent).toBe('from spread');
336
+ });
337
+
338
+ it('explicit children overrides spread children when it comes after', async () => {
339
+ component Card(props: PropsWithChildren<{ id: string }>) {
340
+ <div class="card">{props.children}</div>
341
+ }
342
+
343
+ component App() {
344
+ const extra = { id: '1', children: 'from spread' };
345
+ <Card {...extra} children="explicit" />
346
+ }
347
+
348
+ const { body } = await render(App);
349
+ const { document } = parseHtml(body);
350
+ expect(document.querySelector('.card').textContent).toBe('explicit');
351
+ });
238
352
  });
@@ -112,8 +112,9 @@ export component App() {
112
112
 
113
113
  const result = compile(source, 'test.ripple', { mode: 'server' }).js.code;
114
114
 
115
+ // Template children should take precedence - explicit children prop should be removed
115
116
  expect((result.match(/children:/g) || []).length).toBe(1);
116
- expect(result).toContain('children: _$_.normalize_children(fallback) ?? _$_.ripple_element(');
117
+ expect(result).toContain('children: _$_.ripple_element(');
117
118
  });
118
119
  });
119
120
 
@@ -199,4 +200,40 @@ describe('compiler server block tests', () => {
199
200
 
200
201
  expect(() => compile(source, 'test.ripple', { mode: 'server' })).toThrowError();
201
202
  });
203
+
204
+ it('wraps children in normalize_children for explicit children prop passed to component', () => {
205
+ const source = `
206
+ component Card(props) {
207
+ <div>{props.children}</div>
208
+ }
209
+
210
+ export component App() {
211
+ const content = 'hello';
212
+
213
+ <Card children={content} />
214
+ }
215
+ `;
216
+
217
+ const result = compile(source, 'test.ripple', { mode: 'server' }).js.code;
218
+
219
+ expect(result).toContain('_$_.normalize_children(');
220
+ });
221
+
222
+ it('passes spread through to component when spread may contain children', () => {
223
+ const source = `
224
+ component Card(props) {
225
+ <div>{props.children}</div>
226
+ }
227
+
228
+ export component App() {
229
+ const props = { children: 'hello' };
230
+
231
+ <Card {...props} />
232
+ }
233
+ `;
234
+
235
+ const result = compile(source, 'test.ripple', { mode: 'server' }).js.code;
236
+
237
+ expect(result).toContain('...props');
238
+ });
202
239
  });
package/types/index.d.ts CHANGED
@@ -8,7 +8,7 @@ export type RippleElement = {
8
8
  };
9
9
 
10
10
  /** Type for implicit children fragments rendered with `{children}`. */
11
- export type Children = RippleElement;
11
+ export type Children = RippleElement | Component | string | number | boolean | null | undefined;
12
12
 
13
13
  export function mount(
14
14
  component: Component,