ripple 0.3.76 → 0.3.78

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,46 @@
1
1
  # ripple
2
2
 
3
+ ## 0.3.78
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1240](https://github.com/Ripple-TS/ripple/pull/1240)
8
+ [`92982ee`](https://github.com/Ripple-TS/ripple/commit/92982ee5cd2e6d971b5b650ec1df70483c9716aa)
9
+ Thanks [@leonidaz](https://github.com/leonidaz)! - Render `<{expr}>` dynamic
10
+ tags directly through `_$_.composite` in the client production output instead of
11
+ lowering to the `Dynamic` helper component, and fix hydration of dynamic string
12
+ tags claiming the SSR-rendered element.
13
+
14
+ - [#1241](https://github.com/Ripple-TS/ripple/pull/1241)
15
+ [`b826234`](https://github.com/Ripple-TS/ripple/commit/b8262342111a977ba5a0d44086154e386b06f4b9)
16
+ Thanks [@leonidaz](https://github.com/leonidaz)! - Remove the runtime `Dynamic`
17
+ component exports; dynamic rendering is the `<{expr}>` tag syntax. The `Dynamic`
18
+ type declarations remain so type-only output keeps type-checking, but the JS is
19
+ gone: React and Preact production output now lowers dynamic tags to a scoped
20
+ component alias (`const TsrxDynamic_N = expr;`), Ripple SSR uses the internal
21
+ `_$_.dynamic_element` helper, and the imported-`Dynamic` detection for scoped
22
+ CSS is removed (the element marking is now `metadata.dynamicElement`, set by the
23
+ dynamic-tag lowering).
24
+ - Updated dependencies
25
+ [[`92982ee`](https://github.com/Ripple-TS/ripple/commit/92982ee5cd2e6d971b5b650ec1df70483c9716aa),
26
+ [`92982ee`](https://github.com/Ripple-TS/ripple/commit/92982ee5cd2e6d971b5b650ec1df70483c9716aa),
27
+ [`b826234`](https://github.com/Ripple-TS/ripple/commit/b8262342111a977ba5a0d44086154e386b06f4b9),
28
+ [`b826234`](https://github.com/Ripple-TS/ripple/commit/b8262342111a977ba5a0d44086154e386b06f4b9),
29
+ [`b826234`](https://github.com/Ripple-TS/ripple/commit/b8262342111a977ba5a0d44086154e386b06f4b9)]:
30
+ - @tsrx/ripple@0.1.26
31
+ - @tsrx/core@0.1.26
32
+
33
+ ## 0.3.77
34
+
35
+ ### Patch Changes
36
+
37
+ - Updated dependencies
38
+ [[`d14ec84`](https://github.com/Ripple-TS/ripple/commit/d14ec84f26233e514be9e59ffc94e61db5089587),
39
+ [`921fb9c`](https://github.com/Ripple-TS/ripple/commit/921fb9ce6485db41527b631f5236b7abbac74986),
40
+ [`1693c9e`](https://github.com/Ripple-TS/ripple/commit/1693c9e6daf1421e71171fe3c50e37adfc858b69)]:
41
+ - @tsrx/core@0.1.25
42
+ - @tsrx/ripple@0.1.25
43
+
3
44
  ## 0.3.76
4
45
 
5
46
  ### 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.76",
6
+ "version": "0.3.78",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index-client.js",
9
9
  "main": "src/runtime/index-client.js",
@@ -74,8 +74,8 @@
74
74
  "clsx": "^2.1.1",
75
75
  "devalue": "^5.8.1",
76
76
  "esm-env": "^1.2.2",
77
- "@tsrx/core": "0.1.24",
78
- "@tsrx/ripple": "0.1.24"
77
+ "@tsrx/core": "0.1.26",
78
+ "@tsrx/ripple": "0.1.26"
79
79
  },
80
80
  "devDependencies": {
81
81
  "@types/estree": "^1.0.8",
@@ -183,8 +183,6 @@ export { user_effect as effect } from './internal/client/blocks.js';
183
183
 
184
184
  export { Portal } from './internal/client/portal.js';
185
185
 
186
- export { Dynamic } from './dynamic-client.js';
187
-
188
186
  export { ref_prop as createRefKey } from './internal/client/runtime.js';
189
187
 
190
188
  export { isRefProp } from '@tsrx/core/runtime/ref';
@@ -68,8 +68,6 @@ export const bindNode = noop;
68
68
  export const bindOffsetWidth = noop;
69
69
  export const bindOffsetHeight = noop;
70
70
 
71
- export { Dynamic } from './dynamic-server.js';
72
-
73
71
  /**
74
72
  * Portal component noop for server-side rendering.
75
73
  * Portals are client-only and do not render on the server.
@@ -3,7 +3,8 @@
3
3
  import { exclude_prop_from_object } from '@tsrx/core/runtime/language-helpers';
4
4
  import { branch, destroy_block, render, render_spread } from './blocks.js';
5
5
  import { COMPOSITE_BLOCK, DEFAULT_NAMESPACE, NAMESPACE_URI } from './constants.js';
6
- import { hydrate_next, hydrating } from './hydration.js';
6
+ import { hydrate_node, hydrate_next, hydrating, set_hydrate_node } from './hydration.js';
7
+ import { first_child } from './operations.js';
7
8
  import { active_block, active_namespace, get, with_ns } from './runtime.js';
8
9
  import { top_element_to_ns } from './utils.js';
9
10
  import { is_tsrx_element } from '../../element.js';
@@ -57,15 +58,22 @@ export function composite(get_component, node, props, exclude_prop) {
57
58
  var run = () => {
58
59
  var block = /** @type {Block} */ (active_block);
59
60
 
60
- var element =
61
- ns !== DEFAULT_NAMESPACE
62
- ? document.createElementNS(
63
- NAMESPACE_URI[ns],
64
- /** @type {keyof HTMLElementTagNameMap} */ (component),
65
- )
66
- : document.createElement(/** @type {keyof HTMLElementTagNameMap} */ (component));
61
+ /** @type {Element} */
62
+ var element;
63
+ if (hydrating) {
64
+ // Claim the SSR-rendered element instead of creating a new one.
65
+ element = /** @type {Element} */ (hydrate_node);
66
+ } else {
67
+ element =
68
+ ns !== DEFAULT_NAMESPACE
69
+ ? document.createElementNS(
70
+ NAMESPACE_URI[ns],
71
+ /** @type {keyof HTMLElementTagNameMap} */ (component),
72
+ )
73
+ : document.createElement(/** @type {keyof HTMLElementTagNameMap} */ (component));
67
74
 
68
- /** @type {ChildNode} */ (anchor).before(element);
75
+ /** @type {ChildNode} */ (anchor).before(element);
76
+ }
69
77
 
70
78
  if (block.s === null) {
71
79
  block.s = {
@@ -77,14 +85,28 @@ export function composite(get_component, node, props, exclude_prop) {
77
85
  render_spread(element, () => props || {}, 0, exclude_prop);
78
86
 
79
87
  if (is_tsrx_element(props?.children)) {
80
- var child_anchor = document.createComment('');
81
- element.appendChild(child_anchor);
88
+ /** @type {Node} */
89
+ var child_anchor;
90
+ if (hydrating) {
91
+ // The server renders children directly inside the element with no
92
+ // extra markers; descend the cursor so they claim those nodes.
93
+ child_anchor = /** @type {Node} */ (first_child(element));
94
+ } else {
95
+ child_anchor = document.createComment('');
96
+ element.appendChild(child_anchor);
97
+ }
82
98
 
83
99
  if (ns !== DEFAULT_NAMESPACE) {
84
100
  with_ns(ns, () => props.children.render(child_anchor, block));
85
101
  } else {
86
102
  props.children.render(child_anchor, block);
87
103
  }
104
+
105
+ if (hydrating) {
106
+ // Reset the cursor to the claimed element so sibling traversal
107
+ // continues after it.
108
+ set_hydrate_node(element);
109
+ }
88
110
  }
89
111
  };
90
112
 
@@ -9,8 +9,8 @@ import {
9
9
  render_tsrx_element,
10
10
  spread_attrs,
11
11
  spread_inner_html,
12
- } from './internal/server/index.js';
13
- import { tsrx_element } from './element.js';
12
+ } from './index.js';
13
+ import { tsrx_element } from '../../element.js';
14
14
 
15
15
  /**
16
16
  * @param {any} value
@@ -58,9 +58,9 @@ function render_element(tag, props) {
58
58
 
59
59
  /**
60
60
  * @param {{ is?: Function | string | null | undefined | false, [key: string]: any }} props
61
- * @returns {import('./element.js').TSRXElement}
61
+ * @returns {import('../../element.js').TSRXElement}
62
62
  */
63
- export function Dynamic(props) {
63
+ export function dynamic_element(props) {
64
64
  return tsrx_element(() => {
65
65
  const component = get(props?.is);
66
66
  if (component == null || component === false) {
@@ -2037,3 +2037,5 @@ export function ripple_map(iterable) {
2037
2037
  export function fallback(value, fallback) {
2038
2038
  return value === undefined ? fallback : value;
2039
2039
  }
2040
+
2041
+ export { dynamic_element } from './dynamic.js';
@@ -5,7 +5,7 @@ import type {
5
5
  Component,
6
6
  PropsWithChildrenOptional,
7
7
  } from 'ripple';
8
- import { Dynamic, flushSync, track } from 'ripple';
8
+ import { flushSync, track } from 'ripple';
9
9
  import { did_error } from '../capture-error.js';
10
10
 
11
11
  describe('basic client > components & composition', () => {
@@ -503,7 +503,7 @@ describe('basic client > components & composition', () => {
503
503
  function App() @{
504
504
  let &[Content] = track(() => Noop);
505
505
  <>
506
- <Dynamic is={Content} />
506
+ <{Content} />
507
507
  <button onClick={() => (Content = Op)}>{'Show Op'}</button>
508
508
  </>
509
509
  }
@@ -1,14 +1,14 @@
1
- import { Dynamic, flushSync, track } from 'ripple';
1
+ import { flushSync, track } from 'ripple';
2
2
 
3
3
  describe('composite > dynamic components', () => {
4
- it('supports rendering composite components using <Dynamic is={component}> syntax', () => {
4
+ it('supports rendering composite components using dynamic tag syntax', () => {
5
5
  function basic() @{
6
6
  <div>{'Basic Component'}</div>
7
7
  }
8
8
 
9
9
  function App() @{
10
10
  const tracked_basic = track(() => basic);
11
- <Dynamic is={tracked_basic} />
11
+ <{tracked_basic} />
12
12
  }
13
13
 
14
14
  render(App);
@@ -28,7 +28,7 @@ describe('composite > dynamic components', () => {
28
28
  tracked_basic,
29
29
  };
30
30
  const comp = obj.tracked_basic;
31
- <Dynamic is={comp} />
31
+ <{comp} />
32
32
  }
33
33
 
34
34
  render(App);
@@ -49,7 +49,7 @@ describe('composite > dynamic components', () => {
49
49
  };
50
50
  let &[inner] = track(obj);
51
51
  const comp = inner.tracked_basic;
52
- <Dynamic is={comp} />
52
+ <{comp} />
53
53
  }
54
54
 
55
55
  render(App);
@@ -67,7 +67,7 @@ describe('composite > dynamic components', () => {
67
67
 
68
68
  function App() @{
69
69
  const component = track(() => Child);
70
- <Dynamic is={component} label="child" />
70
+ <{component} label="child" />
71
71
  }
72
72
 
73
73
  render(App);
@@ -89,7 +89,7 @@ describe('composite > dynamic components', () => {
89
89
  let &[thing] = track(() => Child1);
90
90
  <>
91
91
  <div id="container">
92
- <Dynamic is={thing} />
92
+ <{thing} />
93
93
  </div>
94
94
  <button
95
95
  onClick={() => (thing = thing === Child1 ? Child2 : Child1)}
@@ -463,7 +463,7 @@ function Child1() @{
463
463
  'handles sibling combinators with dynamic component and :global before scoped elements',
464
464
  () => {
465
465
  const source = `
466
- import { Dynamic, track } from 'ripple';
466
+ import { track } from 'ripple';
467
467
 
468
468
  export function Test({ children }) @{
469
469
  const DynamicComponent = track(() => Child1);
@@ -471,7 +471,7 @@ export function Test({ children }) @{
471
471
  <div>
472
472
  <p class="before">{'before'}</p>
473
473
 
474
- <Dynamic is={DynamicComponent} />
474
+ <{DynamicComponent} />
475
475
 
476
476
  <p class="foo">
477
477
  <span>{'foo'}</span>
@@ -512,7 +512,7 @@ function Child1() @{
512
512
  'handles sibling combinators with dynamic element or regular element and :global before scoped elements',
513
513
  () => {
514
514
  const source = `
515
- import { Dynamic, track } from 'ripple';
515
+ import { track } from 'ripple';
516
516
 
517
517
  export function Test({ children, classes }) @{
518
518
  const dynamicElement = track('div');
@@ -520,7 +520,7 @@ export function Test({ children, classes }) @{
520
520
  <div>
521
521
  <p class="before">{'before'}</p>
522
522
  // Use Dynamic Element but it's the same with a regular one
523
- <Dynamic is={dynamicElement} class={classes} />
523
+ <{dynamicElement} class={classes} />
524
524
 
525
525
  <p class="foo">
526
526
  <span>{'foo'}</span>
@@ -1,4 +1,4 @@
1
- import { Dynamic, track } from 'ripple';
1
+ import { track } from 'ripple';
2
2
  import { compile } from '@tsrx/ripple';
3
3
 
4
4
  const external_styles = <style>
@@ -104,7 +104,7 @@ function App() @{
104
104
 
105
105
  let dynamic = track(() => Child);
106
106
  <div class="wrapper">
107
- <Dynamic is={dynamic} cls={styles.text} />
107
+ <{dynamic} cls={styles.text} />
108
108
  </div>
109
109
  }
110
110
 
@@ -1,11 +1,11 @@
1
1
  import type { PropsWithExtras } from 'ripple';
2
- import { createRefKey, Dynamic, effect, flushSync, track } from 'ripple';
2
+ import { createRefKey, effect, flushSync, track } from 'ripple';
3
3
 
4
4
  describe('dynamic DOM elements', () => {
5
5
  it('renders static dynamic element', () => {
6
6
  function App() @{
7
7
  let tag = track('div');
8
- <Dynamic is={tag}>{'Hello World'}</Dynamic>
8
+ <{tag}>{'Hello World'}</{tag}>
9
9
  }
10
10
  render(App);
11
11
 
@@ -17,7 +17,7 @@ describe('dynamic DOM elements', () => {
17
17
  it('renders static dynamic element from a plain object with a tracked property', () => {
18
18
  function App() @{
19
19
  let obj = { tag: track('div') };
20
- <Dynamic is={obj.tag}>{'Hello World'}</Dynamic>
20
+ <{obj.tag}>{'Hello World'}</{obj.tag}>
21
21
  }
22
22
  render(App);
23
23
 
@@ -30,7 +30,7 @@ describe('dynamic DOM elements', () => {
30
30
  function App() @{
31
31
  let obj = track({ tag: track('div') });
32
32
  let tag = obj.value.tag;
33
- <Dynamic is={tag}>{'Hello World'}</Dynamic>
33
+ <{tag}>{'Hello World'}</{tag}>
34
34
  }
35
35
  render(App);
36
36
 
@@ -45,7 +45,7 @@ describe('dynamic DOM elements', () => {
45
45
  function App() @{
46
46
  let obj = track({ tag: track('div') });
47
47
  let tag = obj.value['tag'];
48
- <Dynamic is={tag}>{'Hello World'}</Dynamic>
48
+ <{tag}>{'Hello World'}</{tag}>
49
49
  }
50
50
  render(App);
51
51
 
@@ -64,7 +64,7 @@ describe('dynamic DOM elements', () => {
64
64
  tag = 'span';
65
65
  }}
66
66
  >{'Change Tag'}</button>
67
- <Dynamic is={tag} id="dynamic">{'Hello World'}</Dynamic>
67
+ <{tag} id="dynamic">{'Hello World'}</{tag}>
68
68
  </>
69
69
  }
70
70
  render(App);
@@ -88,7 +88,7 @@ describe('dynamic DOM elements', () => {
88
88
  it('renders self-closing dynamic element', () => {
89
89
  function App() @{
90
90
  let tag = track('input');
91
- <Dynamic is={tag} type="text" value="test" />
91
+ <{tag} type="text" value="test" />
92
92
  }
93
93
  render(App);
94
94
 
@@ -102,12 +102,7 @@ describe('dynamic DOM elements', () => {
102
102
  function App() @{
103
103
  let tag = track('div');
104
104
  let &[className] = track('test-class');
105
- <Dynamic
106
- is={tag}
107
- class={className}
108
- id="test"
109
- data-testid="dynamic-element"
110
- >{'Content'}</Dynamic>
105
+ <{tag} class={className} id="test" data-testid="dynamic-element">{'Content'}</{tag}>
111
106
  }
112
107
  render(App);
113
108
 
@@ -122,9 +117,9 @@ describe('dynamic DOM elements', () => {
122
117
  function App() @{
123
118
  let outerTag = track('div');
124
119
  let innerTag = track('span');
125
- <Dynamic is={outerTag} class="outer">
126
- <Dynamic is={innerTag} class="inner">{'Nested content'}</Dynamic>
127
- </Dynamic>
120
+ <{outerTag} class="outer">
121
+ <{innerTag} class="inner">{'Nested content'}</{innerTag}>
122
+ </{outerTag}>
128
123
  }
129
124
  render(App);
130
125
 
@@ -141,10 +136,9 @@ describe('dynamic DOM elements', () => {
141
136
  function App() @{
142
137
  let tag = track('div');
143
138
  let &[active] = track(true);
144
- <Dynamic
145
- is={tag}
139
+ <{tag}
146
140
  class={{ active: active, 'dynamic-element': true }}
147
- >{'Element with class object'}</Dynamic>
141
+ >{'Element with class object'}</{tag}>
148
142
  }
149
143
  render(App);
150
144
 
@@ -157,14 +151,13 @@ describe('dynamic DOM elements', () => {
157
151
  it('handles dynamic element with style object', () => {
158
152
  function App() @{
159
153
  let tag = track('span');
160
- <Dynamic
161
- is={tag}
154
+ <{tag}
162
155
  style={{
163
156
  color: 'red',
164
157
  fontSize: '16px',
165
158
  fontWeight: 'bold',
166
159
  }}
167
- >{'Styled dynamic element'}</Dynamic>
160
+ >{'Styled dynamic element'}</{tag}>
168
161
  }
169
162
  render(App);
170
163
 
@@ -183,11 +176,7 @@ describe('dynamic DOM elements', () => {
183
176
  'data-testid': 'spread-test',
184
177
  class: 'spread-class',
185
178
  };
186
- <Dynamic
187
- is={tag}
188
- {...attrs}
189
- data-extra="additional"
190
- >{'Element with spread attributes'}</Dynamic>
179
+ <{tag} {...attrs} data-extra="additional">{'Element with spread attributes'}</{tag}>
191
180
  }
192
181
  render(App);
193
182
 
@@ -204,13 +193,12 @@ describe('dynamic DOM elements', () => {
204
193
 
205
194
  function App() @{
206
195
  let tag = track('article');
207
- <Dynamic
208
- is={tag}
196
+ <{tag}
209
197
  ref={(node: HTMLElement) => {
210
198
  capturedElement = node;
211
199
  }}
212
200
  id="ref-test"
213
- >{'Element with ref'}</Dynamic>
201
+ >{'Element with ref'}</{tag}>
214
202
  }
215
203
  render(App);
216
204
  flushSync();
@@ -234,8 +222,7 @@ describe('dynamic DOM elements', () => {
234
222
  refAttrElement = input ?? null;
235
223
  anonymousRefElement = state.anonymous ?? null;
236
224
  });
237
- <Dynamic
238
- is={tag}
225
+ <{tag}
239
226
  id="dynamic-ref-combo"
240
227
  type="text"
241
228
  ref={[
@@ -277,8 +264,7 @@ describe('dynamic DOM elements', () => {
277
264
  refAttrElement = input ?? null;
278
265
  anonymousRefElement = state.anonymous ?? null;
279
266
  });
280
- <Dynamic
281
- is={dynamic}
267
+ <{dynamic}
282
268
  ref={[
283
269
  input,
284
270
  state.anonymous,
@@ -322,8 +308,7 @@ describe('dynamic DOM elements', () => {
322
308
  dynamic = dynamic === TextInput ? SearchInput : TextInput;
323
309
  }}
324
310
  >{'Change Component'}</button>
325
- <Dynamic
326
- is={dynamic}
311
+ <{dynamic}
327
312
  ref={[
328
313
  (node: HTMLInputElement | null) => {
329
314
  refAttrElement = node;
@@ -373,7 +358,7 @@ describe('dynamic DOM elements', () => {
373
358
  class: 'ref-element',
374
359
  [createRefKey()]: elementRef,
375
360
  };
376
- <Dynamic is={tag} {...dynamicProps}>{'Element with spread ref'}</Dynamic>
361
+ <{tag} {...dynamicProps}>{'Element with spread ref'}</{tag}>
377
362
  }
378
363
  render(App);
379
364
  flushSync();
@@ -397,15 +382,14 @@ describe('dynamic DOM elements', () => {
397
382
  count++;
398
383
  }}
399
384
  >{'Increment'}</button>
400
- <Dynamic
401
- is={tag}
385
+ <{tag}
402
386
  id={count % 2 ? 'even' : 'odd'}
403
387
  class={count % 2 ? 'even-class' : 'odd-class'}
404
388
  data-count={count}
405
389
  >
406
390
  {'Count: '}
407
391
  {count}
408
- </Dynamic>
392
+ </{tag}>
409
393
  </>
410
394
  }
411
395
 
@@ -445,7 +429,7 @@ describe('dynamic DOM elements', () => {
445
429
  function App() @{
446
430
  let tag = track('div');
447
431
  <>
448
- <Dynamic is={tag} class="test-class">{'Dynamic element'}</Dynamic>
432
+ <{tag} class="test-class">{'Dynamic element'}</{tag}>
449
433
  <style>
450
434
  .test-class {
451
435
  color: red;
@@ -471,8 +455,7 @@ describe('dynamic DOM elements', () => {
471
455
  let tag = track('button');
472
456
  let &[count] = track(0);
473
457
  <>
474
- <Dynamic
475
- is={tag}
458
+ <{tag}
476
459
  class={count % 2 ? 'even' : 'odd'}
477
460
  id={count % 2 ? 'even' : 'odd'}
478
461
  onClick={() => {
@@ -481,7 +464,7 @@ describe('dynamic DOM elements', () => {
481
464
  >
482
465
  {'Count: '}
483
466
  {count}
484
- </Dynamic>
467
+ </{tag}>
485
468
  <style>
486
469
  .even {
487
470
  background-color: green;
@@ -545,7 +528,7 @@ describe('dynamic DOM elements', () => {
545
528
  }>) @{
546
529
  const tag = track('button');
547
530
  <>
548
- <Dynamic is={tag} {...rest}>{rest.class}</Dynamic>
531
+ <{tag} {...rest}>{rest.class}</{tag}>
549
532
  <style>
550
533
  .even {
551
534
  background-color: green;
@@ -603,9 +586,9 @@ describe('dynamic DOM elements', () => {
603
586
  function App() @{
604
587
  let tag = track('div');
605
588
  <>
606
- <Dynamic is={tag} class="scoped">
589
+ <{tag} class="scoped">
607
590
  <p>{'Scoped dynamic element'}</p>
608
- </Dynamic>
591
+ </{tag}>
609
592
  <style>
610
593
  .scoped {
611
594
  color: blue;
@@ -626,9 +609,9 @@ describe('dynamic DOM elements', () => {
626
609
  function App() @{
627
610
  let tag = track('div');
628
611
  <>
629
- <Dynamic is={tag} class="scoped">
612
+ <{tag} class="scoped">
630
613
  <p>{'Scoped dynamic element'}</p>
631
- </Dynamic>
614
+ </{tag}>
632
615
  <style>
633
616
  div {
634
617
  color: blue;
@@ -662,10 +645,10 @@ describe('dynamic DOM elements', () => {
662
645
  function App() @{
663
646
  let tag = track('div');
664
647
  <>
665
- <Dynamic is={tag} class="scoped">
648
+ <{tag} class="scoped">
666
649
  <p>{'Scoped dynamic element'}</p>
667
650
  <Child />
668
- </Dynamic>
651
+ </{tag}>
669
652
  <style>
670
653
  div {
671
654
  color: blue;
@@ -708,7 +691,7 @@ describe('dynamic DOM elements', () => {
708
691
  function App() @{
709
692
  let tag = track(() => Child);
710
693
  <>
711
- <Dynamic is={tag} />
694
+ <{tag} />
712
695
  <style>
713
696
  .child {
714
697
  color: red;
@@ -734,7 +717,7 @@ describe('dynamic DOM elements', () => {
734
717
 
735
718
  function Button(props: any) @{
736
719
  const el = track('button');
737
- <Dynamic is={el} {...props} />
720
+ <{el} {...props} />
738
721
  }
739
722
 
740
723
  function App() @{
@@ -774,7 +757,7 @@ describe('dynamic DOM elements', () => {
774
757
 
775
758
  function Button(props: any) @{
776
759
  const el = track('button');
777
- <Dynamic is={el} {...props} />
760
+ <{el} {...props} />
778
761
  }
779
762
 
780
763
  function App() @{
@@ -819,7 +802,7 @@ describe('dynamic DOM elements', () => {
819
802
 
820
803
  function Button(props: any) @{
821
804
  const el = track('button');
822
- <Dynamic is={el} {...props} />
805
+ <{el} {...props} />
823
806
  }
824
807
 
825
808
  function App() @{
@@ -1,4 +1,4 @@
1
- import { Dynamic, track } from 'ripple';
1
+ import { track } from 'ripple';
2
2
  import type { Component, PropsWithChildren, PropsWithExtras } from 'ripple';
3
3
 
4
4
  describe('SVG namespace handling', () => {
@@ -296,7 +296,7 @@ describe('SVG namespace handling', () => {
296
296
  function App() @{
297
297
  let &[dynTag] = track('polygon');
298
298
  <SVG>
299
- <Dynamic is={dynTag} points="0,0 30,0 15,10" />
299
+ <{dynTag} points="0,0 30,0 15,10" />
300
300
  </SVG>
301
301
  }
302
302
 
@@ -327,7 +327,7 @@ describe('SVG namespace handling', () => {
327
327
  function App() @{
328
328
  let &[Component] = track(() => Polygon);
329
329
  <SVG>
330
- <Dynamic is={Component} points="0,0 30,0 15,10" />
330
+ <{Component} points="0,0 30,0 15,10" />
331
331
  </SVG>
332
332
  }
333
333
 
@@ -343,25 +343,24 @@ describe('SVG namespace handling', () => {
343
343
  it('should render SVG as a dynamic top element with any dynamic children elements', () => {
344
344
  function SVG({ children }: PropsWithChildren<{}>) @{
345
345
  let &[tag] = track('svg');
346
- <Dynamic
347
- is={tag}
346
+ <{tag}
348
347
  width={100}
349
348
  height={50}
350
349
  fill="red"
351
350
  viewBox="0 0 30 10"
352
351
  preserveAspectRatio="none"
353
- >{children}</Dynamic>
352
+ >{children}</{tag}>
354
353
  }
355
354
 
356
355
  function Polygon({ points }: PropsWithExtras<{ points: string }>) @{
357
356
  let &[dynTag] = track('polygon');
358
- <Dynamic is={dynTag} {points} />
357
+ <{dynTag} {points} />
359
358
  }
360
359
 
361
360
  function App() @{
362
361
  let &[Component] = track(() => Polygon);
363
362
  <SVG>
364
- <Dynamic is={Component} points="0,0 30,0 15,10" />
363
+ <{Component} points="0,0 30,0 15,10" />
365
364
  </SVG>
366
365
  }
367
366
 
@@ -24,7 +24,8 @@ describe('try block with catch and pending', () => {
24
24
  @try {
25
25
  let &[data] = trackAsync(() => promise);
26
26
  <p class="resolved">{data}</p>
27
- } @pending {}
27
+ } @pending {
28
+ }
28
29
  <span class="after">{'after'}</span>
29
30
  </>
30
31
  }
@@ -15,6 +15,9 @@ var root_11 = _$_.template(`<!>`, 1, 1);
15
15
  var root_10 = _$_.template(`<!>`, 1, 1);
16
16
  var root_13 = _$_.template(`<!>`, 1, 1);
17
17
  var root_12 = _$_.template(`<!>`, 1, 1);
18
+ var root_14 = _$_.template(`<!>`, 1, 1);
19
+ var root_16 = _$_.template(`<!>`, 1, 1);
20
+ var root_15 = _$_.template(`<!>`, 1, 1);
18
21
 
19
22
  export function Layout(__props) {
20
23
  return _$_.tsrx_element((__anchor, __block) => {
@@ -155,4 +158,43 @@ export function LayoutWithTextAroundChildren() {
155
158
 
156
159
  _$_.append(__anchor, fragment_9);
157
160
  });
161
+ }
162
+
163
+ export function DynamicTagElement() {
164
+ return _$_.tsrx_element((__anchor, __block) => {
165
+ const Tag = 'section';
166
+ var fragment_11 = root_14();
167
+ var node_10 = _$_.first_child_frag(fragment_11);
168
+
169
+ _$_.composite(() => Tag, node_10, {
170
+ class: "host",
171
+ children: _$_.tsrx_element((__anchor, __block) => {
172
+ var expression_3 = _$_.text('hello');
173
+
174
+ _$_.append(__anchor, expression_3);
175
+ })
176
+ });
177
+
178
+ _$_.append(__anchor, fragment_11);
179
+ });
180
+ }
181
+
182
+ export function DynamicTagComponent() {
183
+ return _$_.tsrx_element((__anchor, __block) => {
184
+ const Comp = SingleChild;
185
+ var fragment_12 = root_15();
186
+ var node_11 = _$_.first_child_frag(fragment_12);
187
+
188
+ _$_.render_component(Layout, node_11, {
189
+ children: _$_.tsrx_element((__anchor, __block) => {
190
+ var fragment_13 = root_16();
191
+ var node_12 = _$_.first_child_frag(fragment_13);
192
+
193
+ _$_.composite(() => Comp, node_12, {});
194
+ _$_.append(__anchor, fragment_13);
195
+ })
196
+ });
197
+
198
+ _$_.append(__anchor, fragment_12);
199
+ });
158
200
  }
@@ -201,6 +201,61 @@ export function LayoutWithTextAroundChildren() {
201
201
  }
202
202
  ];
203
203
 
204
+ _$_.render_component(comp, ...args);
205
+ }
206
+ });
207
+ });
208
+ }
209
+
210
+ export function DynamicTagElement() {
211
+ return _$_.tsrx_element(() => {
212
+ const Tag = 'section';
213
+
214
+ _$_.regular_block(() => {
215
+ {
216
+ const comp = _$_.dynamic_element;
217
+
218
+ const args = [
219
+ {
220
+ is: Tag,
221
+ class: "host",
222
+ children: _$_.tsrx_element(() => {
223
+ return _$_.tsrx_element(() => {
224
+ _$_.output_push('hello');
225
+ });
226
+ })
227
+ }
228
+ ];
229
+
230
+ _$_.render_component(comp, ...args);
231
+ }
232
+ });
233
+ });
234
+ }
235
+
236
+ export function DynamicTagComponent() {
237
+ return _$_.tsrx_element(() => {
238
+ const Comp = SingleChild;
239
+
240
+ _$_.regular_block(() => {
241
+ {
242
+ const comp = Layout;
243
+
244
+ const args = [
245
+ {
246
+ children: _$_.tsrx_element(() => {
247
+ return _$_.tsrx_element(() => {
248
+ {
249
+ const comp = _$_.dynamic_element;
250
+ const args = [{ is: Comp }];
251
+
252
+ _$_.render_component(comp, ...args);
253
+ }
254
+ });
255
+ })
256
+ }
257
+ ];
258
+
204
259
  _$_.render_component(comp, ...args);
205
260
  }
206
261
  });
@@ -51,3 +51,15 @@ export function LayoutWithTextAroundChildren() @{
51
51
  <SingleChild />
52
52
  </TextWrappedLayout>
53
53
  }
54
+
55
+ export function DynamicTagElement() @{
56
+ const Tag = 'section';
57
+ <{Tag} class="host">{'hello'}</{Tag}>
58
+ }
59
+
60
+ export function DynamicTagComponent() @{
61
+ const Comp = SingleChild;
62
+ <Layout>
63
+ <{Comp} />
64
+ </Layout>
65
+ }
@@ -50,4 +50,19 @@ describe('hydration > composite', () => {
50
50
  );
51
51
  expect(container.querySelector('.layout')?.textContent).toBe('beforesingleafter');
52
52
  });
53
+
54
+ it('hydrates a dynamic tag element', async () => {
55
+ await hydrateComponent(ServerComponents.DynamicTagElement, ClientComponents.DynamicTagElement);
56
+ expect(container.innerHTML).toBeHtml('<section class="host">hello</section>');
57
+ });
58
+
59
+ it('hydrates a dynamic tag component', async () => {
60
+ await hydrateComponent(
61
+ ServerComponents.DynamicTagComponent,
62
+ ClientComponents.DynamicTagComponent,
63
+ );
64
+ expect(container.innerHTML).toBeHtml(
65
+ '<div class="layout"><div class="single">single</div></div>',
66
+ );
67
+ });
53
68
  });
@@ -1,4 +1,4 @@
1
- import { Dynamic, track } from 'ripple';
1
+ import { track } from 'ripple';
2
2
  import type {
3
3
  Tracked,
4
4
  PropsWithChildren,
@@ -331,7 +331,7 @@ describe('basic server > components & composition', () => {
331
331
 
332
332
  function App() @{
333
333
  let Content = track(() => Noop);
334
- <Dynamic is={Content} />
334
+ <{Content} />
335
335
  }
336
336
 
337
337
  const { body } = await render(App);
@@ -1,4 +1,4 @@
1
- import { Dynamic, Fragment, track } from 'ripple';
1
+ import { Fragment, track } from 'ripple';
2
2
  import type { Tracked, PropsNoChildren } from 'ripple';
3
3
 
4
4
  describe('basic client', () => {
@@ -403,7 +403,7 @@ second
403
403
  function Parent() @{
404
404
  const count = track(10);
405
405
  let DynamicChild = track(() => Child);
406
- <Dynamic is={DynamicChild} {count} class={{ test: true }} />
406
+ <{DynamicChild} {count} class={{ test: true }} />
407
407
  }
408
408
 
409
409
  const { body } = await render(Parent);
@@ -1,11 +1,11 @@
1
1
  import type { DynamicProps, PropsWithExtras, TSRXElement } from 'ripple';
2
- import { createRefKey, Dynamic, track } from 'ripple';
2
+ import { createRefKey, track } from 'ripple';
3
3
 
4
4
  describe('server dynamic DOM elements', () => {
5
5
  it('renders static dynamic element', async () => {
6
6
  function App() @{
7
7
  let tag = track('div');
8
- <Dynamic is={tag}>{'Hello World'}</Dynamic>
8
+ <{tag}>{'Hello World'}</{tag}>
9
9
  }
10
10
 
11
11
  const { body } = await render(App);
@@ -17,7 +17,7 @@ describe('server dynamic DOM elements', () => {
17
17
  function App() @{
18
18
  let obj = { tag: track('div') };
19
19
  let tag = obj.tag;
20
- <Dynamic is={tag}>{'Hello World'}</Dynamic>
20
+ <{tag}>{'Hello World'}</{tag}>
21
21
  }
22
22
 
23
23
  const { body } = await render(App);
@@ -36,7 +36,7 @@ describe('server dynamic DOM elements', () => {
36
36
  }
37
37
 
38
38
  function App() @{
39
- <Dynamic is={Child} label="child" class="yo" />
39
+ <{Child} label="child" class="yo" />
40
40
  }
41
41
 
42
42
  const { body } = await render(App);
@@ -48,7 +48,7 @@ describe('server dynamic DOM elements', () => {
48
48
  function App() @{
49
49
  let obj = track({ tag: track('div') });
50
50
  let tag = obj.value.tag;
51
- <Dynamic is={tag}>{'Hello World'}</Dynamic>
51
+ <{tag}>{'Hello World'}</{tag}>
52
52
  }
53
53
 
54
54
  const { body } = await render(App);
@@ -62,7 +62,7 @@ describe('server dynamic DOM elements', () => {
62
62
  function App() @{
63
63
  let obj = track({ tag: track('div') });
64
64
  let tag = obj.value['tag'];
65
- <Dynamic is={tag}>{'Hello World'}</Dynamic>
65
+ <{tag}>{'Hello World'}</{tag}>
66
66
  }
67
67
 
68
68
  const { body } = await render(App);
@@ -74,7 +74,7 @@ describe('server dynamic DOM elements', () => {
74
74
  it('renders self-closing dynamic element', async () => {
75
75
  function App() @{
76
76
  let tag = track('input');
77
- <Dynamic is={tag} type="text" value="test" />
77
+ <{tag} type="text" value="test" />
78
78
  }
79
79
 
80
80
  const { body } = await render(App);
@@ -86,12 +86,7 @@ describe('server dynamic DOM elements', () => {
86
86
  function App() @{
87
87
  let tag = track('div');
88
88
  let &[className] = track('test-class');
89
- <Dynamic
90
- is={tag}
91
- class={className}
92
- id="test"
93
- data-testid="dynamic-element"
94
- >{'Content'}</Dynamic>
89
+ <{tag} class={className} id="test" data-testid="dynamic-element">{'Content'}</{tag}>
95
90
  }
96
91
  const { body } = await render(App);
97
92
  const { document } = parseHtml(body);
@@ -108,9 +103,9 @@ describe('server dynamic DOM elements', () => {
108
103
  function App() @{
109
104
  let outerTag = track('div');
110
105
  let innerTag = track('span');
111
- <Dynamic is={outerTag} class="outer">
112
- <Dynamic is={innerTag} class="inner">{'Nested content'}</Dynamic>
113
- </Dynamic>
106
+ <{outerTag} class="outer">
107
+ <{innerTag} class="inner">{'Nested content'}</{innerTag}>
108
+ </{outerTag}>
114
109
  }
115
110
  const { body } = await render(App);
116
111
  const { document } = parseHtml(body);
@@ -128,10 +123,9 @@ describe('server dynamic DOM elements', () => {
128
123
  function App() @{
129
124
  let tag = track('div');
130
125
  let &[active] = track(true);
131
- <Dynamic
132
- is={tag}
126
+ <{tag}
133
127
  class={{ active: active, 'dynamic-element': true }}
134
- >{'Element with class object'}</Dynamic>
128
+ >{'Element with class object'}</{tag}>
135
129
  }
136
130
 
137
131
  const { body } = await render(App);
@@ -146,14 +140,13 @@ describe('server dynamic DOM elements', () => {
146
140
  it('handles dynamic element with style object', async () => {
147
141
  function App() @{
148
142
  let tag = track('span');
149
- <Dynamic
150
- is={tag}
143
+ <{tag}
151
144
  style={{
152
145
  color: 'red',
153
146
  fontSize: '16px',
154
147
  fontWeight: 'bold',
155
148
  }}
156
- >{'Styled dynamic element'}</Dynamic>
149
+ >{'Styled dynamic element'}</{tag}>
157
150
  }
158
151
 
159
152
  const { body } = await render(App);
@@ -174,11 +167,7 @@ describe('server dynamic DOM elements', () => {
174
167
  'data-testid': 'spread-test',
175
168
  class: 'spread-class',
176
169
  };
177
- <Dynamic
178
- is={tag}
179
- {...attrs}
180
- data-extra="additional"
181
- >{'Element with spread attributes'}</Dynamic>
170
+ <{tag} {...attrs} data-extra="additional">{'Element with spread attributes'}</{tag}>
182
171
  }
183
172
  const { body } = await render(App);
184
173
  const { document } = parseHtml(body);
@@ -196,13 +185,12 @@ describe('server dynamic DOM elements', () => {
196
185
 
197
186
  function App() @{
198
187
  let tag = track('article');
199
- <Dynamic
200
- is={tag}
188
+ <{tag}
201
189
  ref={(node: HTMLElement) => {
202
190
  capturedElement = node;
203
191
  }}
204
192
  id="ref-test"
205
- >{'Element with ref'}</Dynamic>
193
+ >{'Element with ref'}</{tag}>
206
194
  }
207
195
 
208
196
  const { body } = await render(App);
@@ -230,7 +218,7 @@ describe('server dynamic DOM elements', () => {
230
218
  class: 'ref-element',
231
219
  [createRefKey()]: elementRef,
232
220
  };
233
- <Dynamic is={tag} {...dynamicProps}>{'Element with spread ref'}</Dynamic>
221
+ <{tag} {...dynamicProps}>{'Element with spread ref'}</{tag}>
234
222
  }
235
223
 
236
224
  const { body } = await render(App);
@@ -249,7 +237,7 @@ describe('server dynamic DOM elements', () => {
249
237
  function App() @{
250
238
  let tag = track('div');
251
239
  <>
252
- <Dynamic is={tag} class="test-class">{'Dynamic element'}</Dynamic>
240
+ <{tag} class="test-class">{'Dynamic element'}</{tag}>
253
241
  <style>
254
242
  .test-class {
255
243
  color: red;
@@ -277,7 +265,7 @@ describe('server dynamic DOM elements', () => {
277
265
  }>) @{
278
266
  const tag = track('button');
279
267
  <>
280
- <Dynamic is={tag} {...rest}>{rest.class}</Dynamic>
268
+ <{tag} {...rest}>{rest.class}</{tag}>
281
269
  <style>
282
270
  .even {
283
271
  background-color: green;
@@ -314,9 +302,9 @@ describe('server dynamic DOM elements', () => {
314
302
  function App() @{
315
303
  let tag = track('div');
316
304
  <>
317
- <Dynamic is={tag} class="scoped">
305
+ <{tag} class="scoped">
318
306
  <p>{'Scoped dynamic element'}</p>
319
- </Dynamic>
307
+ </{tag}>
320
308
  <style>
321
309
  .scoped {
322
310
  color: blue;
@@ -343,9 +331,9 @@ describe('server dynamic DOM elements', () => {
343
331
  function App() @{
344
332
  let tag = track('div');
345
333
  <>
346
- <Dynamic is={tag} class="scoped">
334
+ <{tag} class="scoped">
347
335
  <p>{'Scoped dynamic element'}</p>
348
- </Dynamic>
336
+ </{tag}>
349
337
  <style>
350
338
  div {
351
339
  color: blue;
@@ -382,10 +370,10 @@ describe('server dynamic DOM elements', () => {
382
370
  function App() @{
383
371
  let tag = track('div');
384
372
  <>
385
- <Dynamic is={tag} class="scoped">
373
+ <{tag} class="scoped">
386
374
  <p>{'Scoped dynamic element'}</p>
387
375
  <Child />
388
- </Dynamic>
376
+ </{tag}>
389
377
  <style>
390
378
  div {
391
379
  color: blue;
@@ -430,7 +418,7 @@ describe('server dynamic DOM elements', () => {
430
418
  function App() @{
431
419
  let tag = track(() => Child);
432
420
  <>
433
- <Dynamic is={tag} />
421
+ <{tag} />
434
422
  <style>
435
423
  .child {
436
424
  color: red;
@@ -1,4 +1,4 @@
1
- import { Dynamic, track } from 'ripple';
1
+ import { track } from 'ripple';
2
2
  import { compile } from '@tsrx/ripple';
3
3
 
4
4
  const external_styles = <style>
@@ -178,7 +178,7 @@ describe('style class maps (server)', () => {
178
178
 
179
179
  let dynamic = track(() => Child);
180
180
  <div class="wrapper">
181
- <Dynamic is={dynamic} cls={styles.text} />
181
+ <{dynamic} cls={styles.text} />
182
182
  </div>
183
183
  }
184
184
 
@@ -269,9 +269,9 @@ describe('style class maps (server)', () => {
269
269
  function App() @{
270
270
  const DynamicWrapper = track(() => Wrapper);
271
271
  <>
272
- <Dynamic is={DynamicWrapper}>
272
+ <{DynamicWrapper}>
273
273
  <div class="green">{'Slotted child'}</div>
274
- </Dynamic>
274
+ </{DynamicWrapper}>
275
275
  <style>
276
276
  .green {
277
277
  color: green;
@@ -12,7 +12,8 @@ describe('try block with catch and pending (server)', () => {
12
12
  <span>{'before'}</span>
13
13
  @try {
14
14
  <DataChild />
15
- } @pending {}
15
+ } @pending {
16
+ }
16
17
  <span>{'after'}</span>
17
18
  </>
18
19
  }
@@ -1,33 +0,0 @@
1
- /** @import { Block } from '#client' */
2
-
3
- import { composite } from './internal/client/composite.js';
4
- import { with_block } from './internal/client/runtime.js';
5
- import { tsrx_element } from './element.js';
6
-
7
- /**
8
- * @typedef {Function | string | null | undefined | false} DynamicTarget
9
- * @typedef {{ is?: DynamicTarget, [key: string]: any }} DynamicProps
10
- */
11
-
12
- /**
13
- * @param {DynamicProps} props
14
- * @returns {import('./element.js').TSRXElement}
15
- */
16
- export function Dynamic(props) {
17
- return tsrx_element(
18
- /**
19
- * @param {Node} anchor
20
- * @param {Block | null} block
21
- */
22
- (anchor, block) => {
23
- const render_dynamic = () =>
24
- composite(() => /** @type {DynamicTarget} */ (props?.is), anchor, props || {}, 'is');
25
-
26
- if (block !== null) {
27
- with_block(block, render_dynamic);
28
- } else {
29
- render_dynamic();
30
- }
31
- },
32
- );
33
- }