ripple 0.3.8 → 0.3.10

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 (79) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/package.json +2 -2
  3. package/src/compiler/phases/1-parse/index.js +38 -172
  4. package/src/compiler/phases/2-analyze/index.js +308 -115
  5. package/src/compiler/phases/2-analyze/prune.js +13 -5
  6. package/src/compiler/phases/3-transform/client/index.js +197 -213
  7. package/src/compiler/phases/3-transform/segments.js +0 -7
  8. package/src/compiler/phases/3-transform/server/index.js +77 -170
  9. package/src/compiler/types/acorn.d.ts +1 -1
  10. package/src/compiler/types/estree.d.ts +1 -1
  11. package/src/compiler/types/import.d.ts +0 -2
  12. package/src/compiler/types/index.d.ts +14 -18
  13. package/src/compiler/types/parse.d.ts +3 -9
  14. package/src/compiler/utils.js +154 -21
  15. package/src/runtime/element.js +39 -0
  16. package/src/runtime/index-client.js +2 -13
  17. package/src/runtime/index-server.js +2 -2
  18. package/src/runtime/internal/client/bindings.js +3 -1
  19. package/src/runtime/internal/client/composite.js +11 -6
  20. package/src/runtime/internal/client/events.js +1 -1
  21. package/src/runtime/internal/client/expression.js +218 -0
  22. package/src/runtime/internal/client/head.js +3 -4
  23. package/src/runtime/internal/client/index.js +4 -1
  24. package/src/runtime/internal/client/portal.js +12 -6
  25. package/src/runtime/internal/client/runtime.js +0 -52
  26. package/src/runtime/internal/server/index.js +57 -56
  27. package/tests/client/basic/basic.components.test.ripple +85 -87
  28. package/tests/client/basic/basic.errors.test.ripple +28 -4
  29. package/tests/client/basic/basic.reactivity.test.ripple +10 -155
  30. package/tests/client/basic/basic.rendering.test.ripple +23 -8
  31. package/tests/client/capture-error.js +12 -0
  32. package/tests/client/compiler/compiler.basic.test.ripple +107 -18
  33. package/tests/client/composite/composite.props.test.ripple +5 -9
  34. package/tests/client/composite/composite.reactivity.test.ripple +35 -36
  35. package/tests/client/composite/composite.render.test.ripple +45 -13
  36. package/tests/client/css/global-additional-cases.test.ripple +3 -3
  37. package/tests/client/dynamic-elements.test.ripple +3 -4
  38. package/tests/client/lazy-destructuring.test.ripple +69 -12
  39. package/tests/client/svg.test.ripple +4 -4
  40. package/tests/hydration/basic.test.js +23 -0
  41. package/tests/hydration/compiled/client/basic.js +118 -66
  42. package/tests/hydration/compiled/client/composite.js +90 -37
  43. package/tests/hydration/compiled/client/events.js +18 -18
  44. package/tests/hydration/compiled/client/for.js +62 -62
  45. package/tests/hydration/compiled/client/head.js +10 -10
  46. package/tests/hydration/compiled/client/hmr.js +13 -10
  47. package/tests/hydration/compiled/client/html.js +274 -236
  48. package/tests/hydration/compiled/client/if-children.js +41 -35
  49. package/tests/hydration/compiled/client/if.js +2 -2
  50. package/tests/hydration/compiled/client/mixed-control-flow.js +12 -12
  51. package/tests/hydration/compiled/client/nested-control-flow.js +46 -46
  52. package/tests/hydration/compiled/client/portal.js +8 -8
  53. package/tests/hydration/compiled/client/reactivity.js +14 -14
  54. package/tests/hydration/compiled/client/return.js +2 -2
  55. package/tests/hydration/compiled/client/try.js +4 -4
  56. package/tests/hydration/compiled/server/basic.js +64 -31
  57. package/tests/hydration/compiled/server/composite.js +62 -29
  58. package/tests/hydration/compiled/server/hmr.js +24 -37
  59. package/tests/hydration/compiled/server/html.js +472 -611
  60. package/tests/hydration/compiled/server/if-children.js +77 -103
  61. package/tests/hydration/compiled/server/portal.js +8 -8
  62. package/tests/hydration/components/basic.ripple +15 -5
  63. package/tests/hydration/components/composite.ripple +13 -1
  64. package/tests/hydration/components/hmr.ripple +1 -3
  65. package/tests/hydration/components/html.ripple +13 -35
  66. package/tests/hydration/components/if-children.ripple +4 -8
  67. package/tests/hydration/composite.test.js +11 -0
  68. package/tests/server/basic.attributes.test.ripple +50 -0
  69. package/tests/server/basic.components.test.ripple +22 -28
  70. package/tests/server/basic.test.ripple +12 -0
  71. package/tests/server/compiler.test.ripple +43 -4
  72. package/tests/server/composite.props.test.ripple +5 -9
  73. package/tests/server/dynamic-elements.test.ripple +3 -4
  74. package/tests/server/lazy-destructuring.test.ripple +68 -12
  75. package/tests/server/style-identifier.test.ripple +2 -4
  76. package/tsconfig.typecheck.json +4 -0
  77. package/types/index.d.ts +9 -21
  78. package/tests/client/__snapshots__/tracked-expression.test.ripple.snap +0 -34
  79. package/tests/client/tracked-expression.test.ripple +0 -26
@@ -292,25 +292,25 @@ export function is_component_level_function(context) {
292
292
  * Returns the matched Ripple tracking call name
293
293
  * @param {AST.Expression | AST.Super} callee
294
294
  * @param {CommonContext} context
295
- * @returns {'track' | 'trackSplit' | null}
295
+ * @returns {'track' | null}
296
296
  */
297
297
  export function is_ripple_track_call(callee, context) {
298
298
  // Super expressions cannot be Ripple track calls
299
299
  if (callee.type === 'Super') return null;
300
300
 
301
- if (callee.type === 'Identifier' && (callee.name === 'track' || callee.name === 'trackSplit')) {
302
- return is_ripple_import(callee, context) ? callee.name : null;
301
+ if (callee.type === 'Identifier' && callee.name === 'track') {
302
+ return is_ripple_import(callee, context) ? 'track' : null;
303
303
  }
304
304
 
305
305
  if (
306
306
  callee.type === 'MemberExpression' &&
307
307
  callee.object.type === 'Identifier' &&
308
308
  callee.property.type === 'Identifier' &&
309
- (callee.property.name === 'track' || callee.property.name === 'trackSplit') &&
309
+ callee.property.name === 'track' &&
310
310
  !callee.computed &&
311
311
  is_ripple_import(callee, context)
312
312
  ) {
313
- return callee.property.name;
313
+ return 'track';
314
314
  }
315
315
 
316
316
  return null;
@@ -606,21 +606,8 @@ export function is_element_dynamic(node) {
606
606
  * @returns {boolean}
607
607
  */
608
608
  function is_id_dynamic(node) {
609
- if (node.type === 'Identifier' || node.type === 'Literal') {
610
- if (node.tracked) {
611
- return true;
612
- }
613
-
614
- return false;
615
- } else if (node.type === 'MemberExpression') {
616
- if (/** @type {AST.Identifier} */ (node.object).tracked === true) {
617
- return true;
618
- }
619
- if (node.property.type === 'MemberExpression') {
620
- return is_id_dynamic(node.property);
621
- }
622
-
623
- return !!(/** @type {AST.Identifier} */ (node.property).tracked);
609
+ if (node.type === 'Identifier') {
610
+ return !!node.tracked;
624
611
  }
625
612
 
626
613
  return false;
@@ -644,7 +631,22 @@ export function normalize_children(children, context) {
644
631
  const child = normalized[i];
645
632
  const prev_child = normalized[i - 1];
646
633
 
647
- if (child.type === 'Text' && prev_child?.type === 'Text') {
634
+ if (
635
+ (child.type === 'RippleExpression' || child.type === 'Text') &&
636
+ (prev_child?.type === 'RippleExpression' || prev_child?.type === 'Text')
637
+ ) {
638
+ if (
639
+ (child.type === 'RippleExpression' &&
640
+ is_children_template_expression(child.expression, context.state.scope)) ||
641
+ (prev_child.type === 'RippleExpression' &&
642
+ is_children_template_expression(prev_child.expression, context.state.scope))
643
+ ) {
644
+ continue;
645
+ }
646
+
647
+ if (prev_child.type === 'Text' || child.type === 'Text') {
648
+ prev_child.type = 'Text';
649
+ }
648
650
  if (child.expression.type === 'Literal' && prev_child.expression.type === 'Literal') {
649
651
  prev_child.expression = b.literal(
650
652
  prev_child.expression.value + String(child.expression.value),
@@ -663,6 +665,91 @@ export function normalize_children(children, context) {
663
665
  return normalized;
664
666
  }
665
667
 
668
+ /**
669
+ * @param {AST.Expression} expression
670
+ * @returns {AST.Expression}
671
+ */
672
+ export function unwrap_template_expression(expression) {
673
+ /** @type {AST.Expression} */
674
+ let node = expression;
675
+
676
+ while (true) {
677
+ if (
678
+ node.type === 'ParenthesizedExpression' ||
679
+ node.type === 'TSAsExpression' ||
680
+ node.type === 'TSSatisfiesExpression' ||
681
+ node.type === 'TSNonNullExpression' ||
682
+ node.type === 'TSInstantiationExpression'
683
+ ) {
684
+ node = /** @type {AST.Expression} */ (node.expression);
685
+ continue;
686
+ }
687
+
688
+ if (node.type === 'ChainExpression') {
689
+ node = /** @type {AST.Expression} */ (node.expression);
690
+ continue;
691
+ }
692
+
693
+ break;
694
+ }
695
+
696
+ return node;
697
+ }
698
+
699
+ /**
700
+ * @param {AST.Expression} expression
701
+ * @param {ScopeInterface | null | undefined} scope
702
+ * @param {ScopeInterface | null} [component_scope]
703
+ * @returns {boolean}
704
+ */
705
+ export function is_children_template_expression(expression, scope, component_scope = null) {
706
+ if (scope == null) {
707
+ return false;
708
+ }
709
+
710
+ const unwrapped = unwrap_template_expression(expression);
711
+
712
+ if (unwrapped.type === 'MemberExpression') {
713
+ let property_name = null;
714
+
715
+ if (!unwrapped.computed && unwrapped.property.type === 'Identifier') {
716
+ property_name = unwrapped.property.name;
717
+ } else if (
718
+ unwrapped.computed &&
719
+ unwrapped.property.type === 'Literal' &&
720
+ typeof unwrapped.property.value === 'string'
721
+ ) {
722
+ property_name = unwrapped.property.value;
723
+ }
724
+
725
+ if (property_name === 'children') {
726
+ const target = unwrap_template_expression(/** @type {AST.Expression} */ (unwrapped.object));
727
+
728
+ if (target.type === 'Identifier') {
729
+ const binding = scope.get(target.name);
730
+ return (
731
+ binding?.declaration_kind === 'param' &&
732
+ (component_scope === null || binding.scope === component_scope)
733
+ );
734
+ }
735
+ }
736
+ }
737
+
738
+ if (unwrapped.type !== 'Identifier' || unwrapped.name !== 'children') {
739
+ return false;
740
+ }
741
+
742
+ const binding = scope.get(unwrapped.name);
743
+ return (
744
+ (binding?.declaration_kind === 'param' ||
745
+ binding?.kind === 'prop' ||
746
+ binding?.kind === 'prop_fallback' ||
747
+ binding?.kind === 'lazy' ||
748
+ binding?.kind === 'lazy_fallback') &&
749
+ (component_scope === null || binding.scope === component_scope)
750
+ );
751
+ }
752
+
666
753
  /**
667
754
  * @param {AST.Node} node
668
755
  * @param {AST.Node[]} normalized
@@ -686,6 +773,52 @@ function normalize_child(node, normalized, context) {
686
773
  }
687
774
  }
688
775
 
776
+ /**
777
+ * Replaces any lazy subpatterns in a parameter pattern with their generated identifiers.
778
+ * This is used by client and server transforms so nested lazy destructuring can coexist
779
+ * with otherwise normal object/array params.
780
+ * @param {AST.Pattern} pattern
781
+ * @returns {AST.Pattern}
782
+ */
783
+ export function replace_lazy_param_pattern(pattern) {
784
+ switch (pattern.type) {
785
+ case 'AssignmentPattern':
786
+ return { ...pattern, left: replace_lazy_param_pattern(pattern.left) };
787
+
788
+ case 'ObjectPattern':
789
+ if (pattern.lazy && pattern.metadata?.lazy_id) {
790
+ return /** @type {AST.Pattern} */ (b.id(pattern.metadata.lazy_id));
791
+ }
792
+
793
+ return {
794
+ ...pattern,
795
+ properties: pattern.properties.map((property) =>
796
+ property.type === 'RestElement'
797
+ ? { ...property, argument: replace_lazy_param_pattern(property.argument) }
798
+ : { ...property, value: replace_lazy_param_pattern(property.value) },
799
+ ),
800
+ };
801
+
802
+ case 'ArrayPattern':
803
+ if (pattern.lazy && pattern.metadata?.lazy_id) {
804
+ return /** @type {AST.Pattern} */ (b.id(pattern.metadata.lazy_id));
805
+ }
806
+
807
+ return {
808
+ ...pattern,
809
+ elements: pattern.elements.map((element) =>
810
+ element === null ? null : replace_lazy_param_pattern(element),
811
+ ),
812
+ };
813
+
814
+ case 'RestElement':
815
+ return { ...pattern, argument: replace_lazy_param_pattern(pattern.argument) };
816
+
817
+ default:
818
+ return pattern;
819
+ }
820
+ }
821
+
689
822
  /**
690
823
  * @param {CommonContext} context
691
824
  */
@@ -0,0 +1,39 @@
1
+ const RIPPLE_ELEMENT = Symbol.for('ripple.element');
2
+
3
+ /**
4
+ * @typedef {{
5
+ * render: Function;
6
+ * [RIPPLE_ELEMENT]: true;
7
+ * }} RippleElement
8
+ */
9
+
10
+ /**
11
+ * @param {Function} render
12
+ * @returns {RippleElement}
13
+ */
14
+ export function ripple_element(render) {
15
+ return {
16
+ render,
17
+ [RIPPLE_ELEMENT]: true,
18
+ };
19
+ }
20
+
21
+ /**
22
+ * @param {any} value
23
+ * @returns {value is RippleElement}
24
+ */
25
+ export function is_ripple_element(value) {
26
+ return value != null && value[RIPPLE_ELEMENT] === true;
27
+ }
28
+
29
+ /**
30
+ * @param {any} value
31
+ * @returns {any}
32
+ */
33
+ export function normalize_children(value) {
34
+ if (value == null || is_ripple_element(value) || typeof value !== 'function') {
35
+ return value;
36
+ }
37
+
38
+ return ripple_element(value);
39
+ }
@@ -102,13 +102,7 @@ export function hydrate(component, options) {
102
102
 
103
103
  export { Context } from './internal/client/context.js';
104
104
 
105
- export {
106
- flush_sync as flushSync,
107
- track,
108
- track_split as trackSplit,
109
- untrack,
110
- tick,
111
- } from './internal/client/runtime.js';
105
+ export { flush_sync as flushSync, track, untrack, tick } from './internal/client/runtime.js';
112
106
 
113
107
  export { RippleArray } from './array.js';
114
108
 
@@ -165,10 +159,5 @@ import { RippleURL } from './url.js';
165
159
  import { RippleURLSearchParams } from './url-search-params.js';
166
160
  import { RippleDate } from './date.js';
167
161
  import { MediaQuery } from './media-query.js';
168
- import {
169
- track,
170
- track_split as trackSplit,
171
- untrack,
172
- ref_prop as createRefKey,
173
- } from './internal/client/runtime.js';
162
+ import { track, untrack, ref_prop as createRefKey } from './internal/client/runtime.js';
174
163
  import { user_effect as effect } from './internal/client/blocks.js';
@@ -1,8 +1,8 @@
1
- import { get, set, untrack, track, track_split } from './internal/server/index.js';
1
+ import { get, set, untrack, track } from './internal/server/index.js';
2
2
 
3
3
  export { Context } from './internal/server/context.js';
4
4
 
5
- export { get, set, untrack, track, track_split as trackSplit };
5
+ export { get, set, untrack, track };
6
6
 
7
7
  function noop() {}
8
8
 
@@ -186,7 +186,9 @@ function select_option(select, value, mounting = false) {
186
186
 
187
187
  // Otherwise, update the selection
188
188
  for (var option of select.options) {
189
- option.selected = /** @type {string[]} */ (value).includes(get_option_value(option));
189
+ option.selected = /** @type {string[]} */ (value).includes(
190
+ /** @type {string} */ (get_option_value(option)),
191
+ );
190
192
  }
191
193
 
192
194
  return;
@@ -5,6 +5,7 @@ import { COMPOSITE_BLOCK, DEFAULT_NAMESPACE, NAMESPACE_URI } from './constants.j
5
5
  import { hydrate_next, hydrating } from './hydration.js';
6
6
  import { active_block, active_namespace, get, with_ns } from './runtime.js';
7
7
  import { top_element_to_ns } from './utils.js';
8
+ import { is_ripple_element } from '../../element.js';
8
9
 
9
10
  /**
10
11
  * @typedef {((anchor: Node, props: Record<string, any>, block: Block | null) => void)} ComponentFunction
@@ -29,6 +30,7 @@ export function composite(get_component, node, props) {
29
30
 
30
31
  render(
31
32
  () => {
33
+ // @ts-ignore — get() handles non-tracked values via is_ripple_object() check
32
34
  var component = get(get_component());
33
35
 
34
36
  if (b !== null) {
@@ -44,13 +46,14 @@ export function composite(get_component, node, props) {
44
46
  });
45
47
  } else if (component != null) {
46
48
  // Custom element - only create if component is not null/undefined
49
+ const ns = top_element_to_ns(component, active_namespace);
47
50
  var run = () => {
48
51
  var block = /** @type {Block} */ (active_block);
49
52
 
50
53
  var element =
51
- active_namespace !== DEFAULT_NAMESPACE
54
+ ns !== DEFAULT_NAMESPACE
52
55
  ? document.createElementNS(
53
- NAMESPACE_URI[active_namespace],
56
+ NAMESPACE_URI[ns],
54
57
  /** @type {keyof HTMLElementTagNameMap} */ (component),
55
58
  )
56
59
  : document.createElement(/** @type {keyof HTMLElementTagNameMap} */ (component));
@@ -66,16 +69,18 @@ export function composite(get_component, node, props) {
66
69
 
67
70
  render_spread(element, () => props || {});
68
71
 
69
- if (typeof props?.children === 'function') {
72
+ if (is_ripple_element(props?.children)) {
70
73
  var child_anchor = document.createComment('');
71
74
  element.appendChild(child_anchor);
72
75
 
73
- props?.children?.(child_anchor, {}, block);
76
+ if (ns !== DEFAULT_NAMESPACE) {
77
+ with_ns(ns, () => props.children.render(child_anchor, {}, block));
78
+ } else {
79
+ props.children.render(child_anchor, {}, block);
80
+ }
74
81
  }
75
82
  };
76
83
 
77
- const ns = top_element_to_ns(component, active_namespace);
78
-
79
84
  if (ns !== active_namespace) {
80
85
  // support top-level dynamic element svg/math <@tag />
81
86
  b = branch(() => with_ns(ns, run));
@@ -168,7 +168,7 @@ export function handle_event_propagation(event) {
168
168
  null;
169
169
 
170
170
  try {
171
- var delegated = current_target['__' + event_name];
171
+ var delegated = /** @type {Record<string, any>} */ (current_target)['__' + event_name];
172
172
 
173
173
  if (delegated !== undefined && !(/** @type {any} */ (current_target).disabled)) {
174
174
  if (is_array(delegated)) {
@@ -0,0 +1,218 @@
1
+ /** @import { Block } from '#client' */
2
+
3
+ import { branch, destroy_block, render } from './blocks.js';
4
+ import { UNINITIALIZED } from './constants.js';
5
+ import { create_text, get_next_sibling } from './operations.js';
6
+ import { active_block } from './runtime.js';
7
+ import { hydrating, set_hydrate_node } from './hydration.js';
8
+ import { COMMENT_NODE, HYDRATION_END, HYDRATION_START, TEXT_NODE } from '../../../constants.js';
9
+ import { is_ripple_element } from '../../element.js';
10
+
11
+ /**
12
+ * @param {Node} node
13
+ * @param {() => any} get_value
14
+ * @returns {void}
15
+ */
16
+ export function expression(node, get_value) {
17
+ var anchor = /** @type {ChildNode} */ (node);
18
+ /** @type {Block | null} */
19
+ var child_block = null;
20
+ /** @type {Comment | null} */
21
+ var end = null;
22
+ /** @type {Text | null} */
23
+ var text = null;
24
+ /** @type {string | import('../../element.js').RippleElement | typeof UNINITIALIZED} */
25
+ var value = UNINITIALIZED;
26
+ var is_element = false;
27
+ var initialized = false;
28
+
29
+ render(() => {
30
+ var next_value = get_value();
31
+ var next_is_element = is_ripple_element(next_value);
32
+ var is_hydration_marker = hydrating && anchor.nodeType === COMMENT_NODE;
33
+
34
+ if (is_hydration_marker) {
35
+ end ??= ensure_expression_end(anchor);
36
+ }
37
+
38
+ if (next_is_element) {
39
+ if (initialized && is_element && value === next_value) {
40
+ if (end !== null) {
41
+ advance_hydration(end);
42
+ }
43
+ return;
44
+ }
45
+
46
+ if (anchor.nodeType === TEXT_NODE) {
47
+ /** @type {Text} */ (anchor).nodeValue = '';
48
+ } else if (text !== null) {
49
+ text.remove();
50
+ text = null;
51
+ }
52
+
53
+ if (child_block !== null) {
54
+ destroy_block(child_block);
55
+ child_block = null;
56
+ }
57
+
58
+ if (end !== null && (initialized || !hydrating)) {
59
+ clear_expression_range(anchor, end);
60
+ }
61
+
62
+ if (is_hydration_marker) {
63
+ set_hydrate_node(get_next_sibling(anchor) ?? end);
64
+ }
65
+
66
+ child_block = branch(() => {
67
+ var block = active_block;
68
+ next_value.render(end ?? anchor, {}, block);
69
+ });
70
+
71
+ value = next_value;
72
+ is_element = true;
73
+ initialized = true;
74
+ if (end !== null) {
75
+ advance_hydration(end);
76
+ }
77
+ return;
78
+ }
79
+
80
+ var next_text = (next_value ?? '') + '';
81
+
82
+ if (initialized && !is_element && value === next_text) {
83
+ if (end !== null) {
84
+ advance_hydration(end);
85
+ }
86
+ return;
87
+ }
88
+
89
+ if (child_block !== null) {
90
+ destroy_block(child_block);
91
+ child_block = null;
92
+ }
93
+
94
+ if (is_hydration_marker) {
95
+ text = get_hydrated_text(anchor, /** @type {Comment} */ (end));
96
+
97
+ if (next_text === '') {
98
+ if (text !== null) {
99
+ text.remove();
100
+ text = null;
101
+ }
102
+ } else if (text === null) {
103
+ text = create_text(next_text);
104
+ /** @type {Comment} */ (end).before(text);
105
+ } else if (text.nodeValue !== next_text) {
106
+ text.nodeValue = next_text;
107
+ }
108
+ } else if (anchor.nodeType === COMMENT_NODE) {
109
+ if (next_text === '') {
110
+ if (text !== null) {
111
+ text.remove();
112
+ text = null;
113
+ }
114
+ } else if (text === null) {
115
+ text = create_text(next_text);
116
+ (end ?? anchor).before(text);
117
+ } else if (text.nodeValue !== next_text) {
118
+ text.nodeValue = next_text;
119
+ }
120
+ } else if (anchor.nodeType === TEXT_NODE) {
121
+ /** @type {Text} */ (anchor).nodeValue = next_text;
122
+ }
123
+
124
+ value = next_text;
125
+ is_element = false;
126
+ initialized = true;
127
+ if (end !== null) {
128
+ advance_hydration(end);
129
+ }
130
+ });
131
+ }
132
+
133
+ /**
134
+ * @param {Node} anchor
135
+ * @returns {Comment}
136
+ */
137
+ function ensure_expression_end(anchor) {
138
+ if (hydrating) {
139
+ /** @type {Node | null} */
140
+ var current = get_next_sibling(anchor);
141
+ var depth = 0;
142
+
143
+ while (current !== null) {
144
+ if (current.nodeType === COMMENT_NODE) {
145
+ var data = /** @type {Comment} */ (current).data;
146
+
147
+ if (data === HYDRATION_START) {
148
+ depth += 1;
149
+ } else if (data === HYDRATION_END) {
150
+ if (depth === 0) {
151
+ return /** @type {Comment} */ (current);
152
+ }
153
+
154
+ depth -= 1;
155
+ }
156
+ }
157
+
158
+ current = get_next_sibling(current);
159
+ }
160
+
161
+ throw new Error('Hydration mismatch: expected end marker for expression block');
162
+ }
163
+
164
+ var end = document.createComment(HYDRATION_END);
165
+ /** @type {ChildNode} */ (anchor).after(end);
166
+ return end;
167
+ }
168
+
169
+ /**
170
+ * @param {Node} anchor
171
+ * @param {Node} end
172
+ * @returns {Text | null}
173
+ */
174
+ function get_hydrated_text(anchor, end) {
175
+ var first = get_next_sibling(anchor);
176
+
177
+ if (first === end) {
178
+ return null;
179
+ }
180
+
181
+ if (first?.nodeType === TEXT_NODE && get_next_sibling(first) === end) {
182
+ return /** @type {Text} */ (first);
183
+ }
184
+
185
+ clear_expression_range(anchor, end);
186
+ return null;
187
+ }
188
+
189
+ /**
190
+ * @param {Node} anchor
191
+ * @param {Node} end
192
+ * @returns {void}
193
+ */
194
+ function clear_expression_range(anchor, end) {
195
+ var current = get_next_sibling(anchor);
196
+
197
+ while (current !== null && current !== end) {
198
+ var next = get_next_sibling(current);
199
+ /** @type {ChildNode} */ (current).remove();
200
+ current = next;
201
+ }
202
+ }
203
+
204
+ /**
205
+ * @param {Comment} end
206
+ * @returns {void}
207
+ */
208
+ function advance_hydration(end) {
209
+ if (!hydrating) {
210
+ return;
211
+ }
212
+
213
+ var next = get_next_sibling(end);
214
+
215
+ if (next !== null) {
216
+ set_hydrate_node(next);
217
+ }
218
+ }
@@ -1,4 +1,3 @@
1
- /** @import { TemplateNode } from '#client' */
2
1
  import { render } from './blocks.js';
3
2
  import { HEAD_BLOCK } from './constants.js';
4
3
  import { COMMENT_NODE } from '../../../constants.js';
@@ -38,8 +37,8 @@ export function head(hash, render_fn) {
38
37
  if (head_anchor === null) {
39
38
  set_hydrating(false);
40
39
  } else {
41
- var start = /** @type {TemplateNode} */ (get_next_sibling(head_anchor));
42
- head_anchor.remove(); // in case this component is repeated
40
+ var start = get_next_sibling(head_anchor);
41
+ /** @type {ChildNode} */ (head_anchor).remove(); // in case this component is repeated
43
42
 
44
43
  set_hydrate_node(start);
45
44
  }
@@ -54,7 +53,7 @@ export function head(hash, render_fn) {
54
53
  } finally {
55
54
  if (was_hydrating) {
56
55
  set_hydrating(true);
57
- set_hydrate_node(/** @type {TemplateNode} */ (previous_hydrate_node));
56
+ set_hydrate_node(previous_hydrate_node);
58
57
  }
59
58
  }
60
59
  }
@@ -52,7 +52,6 @@ export {
52
52
  update_property,
53
53
  update_pre_property,
54
54
  track,
55
- track_split,
56
55
  push_component,
57
56
  pop_component,
58
57
  untrack,
@@ -106,6 +105,8 @@ export { script } from './script.js';
106
105
 
107
106
  export { html } from './html.js';
108
107
 
108
+ export { expression } from './expression.js';
109
+
109
110
  export { rpc } from './rpc.js';
110
111
 
111
112
  export { tsx_compat } from './compat.js';
@@ -115,3 +116,5 @@ export { TRY_BLOCK, HMR } from './constants.js';
115
116
  export { hmr } from './hmr.js';
116
117
 
117
118
  export { pop, next } from './hydration.js';
119
+
120
+ export { ripple_element, normalize_children } from '../../element.js';
@@ -12,10 +12,11 @@ import {
12
12
  set_hydrating,
13
13
  set_hydrate_node,
14
14
  } from './hydration.js';
15
+ import { is_ripple_element } from '../../element.js';
15
16
 
16
17
  /**
17
18
  * @param {any} _
18
- * @param {{ target: Element, children: (anchor: Node, props: {}, block: Block) => void }} props
19
+ * @param {{ target: Element, children: import('../../element.js').RippleElement }} props
19
20
  * @returns {void}
20
21
  */
21
22
  export function Portal(_, props) {
@@ -26,7 +27,7 @@ export function Portal(_, props) {
26
27
 
27
28
  /** @type {Element | symbol} */
28
29
  let target = UNINITIALIZED;
29
- /** @type {((anchor: Node, props: {}, block: Block) => void) | symbol} */
30
+ /** @type {import('../../element.js').RippleElement | symbol} */
30
31
  let children = UNINITIALIZED;
31
32
  /** @type {Block | null} */
32
33
  var b = null;
@@ -44,8 +45,13 @@ export function Portal(_, props) {
44
45
 
45
46
  try {
46
47
  render(() => {
47
- if (target === (target = props.target)) return;
48
- if (children === (children = props.children)) return;
48
+ const next_target = props.target;
49
+ const next_children = props.children;
50
+
51
+ if (target === next_target && children === next_children) return;
52
+
53
+ target = next_target;
54
+ children = next_children;
49
55
 
50
56
  if (b !== null) {
51
57
  destroy_block(b);
@@ -65,8 +71,8 @@ export function Portal(_, props) {
65
71
  var block = /** @type {Block} */ (active_block);
66
72
 
67
73
  b = branch(() => {
68
- if (typeof children === 'function') {
69
- children(/** @type {Text} */ (anchor), {}, block);
74
+ if (is_ripple_element(children)) {
75
+ children.render(/** @type {Text} */ (anchor), {}, block);
70
76
  }
71
77
  });
72
78