ripple 0.3.67 → 0.3.69

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 (182) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/package.json +3 -3
  3. package/src/jsx-runtime.d.ts +2 -2
  4. package/src/runtime/element.js +1 -1
  5. package/src/runtime/index-client.js +11 -11
  6. package/src/runtime/index-server.js +7 -4
  7. package/src/runtime/internal/client/bindings.js +1 -1
  8. package/src/runtime/internal/client/blocks.js +13 -4
  9. package/src/runtime/internal/client/component.js +55 -0
  10. package/src/runtime/internal/client/composite.js +4 -2
  11. package/src/runtime/internal/client/expression.js +65 -7
  12. package/src/runtime/internal/client/hmr.js +54 -43
  13. package/src/runtime/internal/client/index.js +5 -1
  14. package/src/runtime/internal/client/portal.js +70 -69
  15. package/src/runtime/internal/client/render.js +3 -0
  16. package/src/runtime/internal/server/index.js +92 -8
  17. package/tests/client/__snapshots__/html.test.tsrx.snap +3 -3
  18. package/tests/client/array/array.copy-within.test.tsrx +33 -31
  19. package/tests/client/array/array.derived.test.tsrx +186 -169
  20. package/tests/client/array/array.iteration.test.tsrx +40 -37
  21. package/tests/client/array/array.mutations.test.tsrx +113 -101
  22. package/tests/client/array/array.static.test.tsrx +119 -101
  23. package/tests/client/array/array.to-methods.test.tsrx +24 -21
  24. package/tests/client/async-suspend.test.tsrx +247 -246
  25. package/tests/client/basic/__snapshots__/basic.rendering.test.tsrx.snap +0 -1
  26. package/tests/client/basic/basic.attributes.test.tsrx +428 -423
  27. package/tests/client/basic/basic.collections.test.tsrx +109 -102
  28. package/tests/client/basic/basic.components.test.tsrx +323 -205
  29. package/tests/client/basic/basic.errors.test.tsrx +91 -91
  30. package/tests/client/basic/basic.events.test.tsrx +114 -115
  31. package/tests/client/basic/basic.get-set.test.tsrx +97 -87
  32. package/tests/client/basic/basic.hmr.test.tsrx +19 -16
  33. package/tests/client/basic/basic.reactivity.test.tsrx +199 -191
  34. package/tests/client/basic/basic.rendering.test.tsrx +272 -182
  35. package/tests/client/basic/basic.styling.test.tsrx +23 -22
  36. package/tests/client/basic/basic.utilities.test.tsrx +10 -8
  37. package/tests/client/boundaries.test.tsrx +26 -26
  38. package/tests/client/compiler/__snapshots__/compiler.assignments.test.rsrx.snap +5 -5
  39. package/tests/client/compiler/__snapshots__/compiler.assignments.test.tsrx.snap +5 -5
  40. package/tests/client/compiler/compiler.assignments.test.tsrx +77 -81
  41. package/tests/client/compiler/compiler.attributes.test.tsrx +15 -15
  42. package/tests/client/compiler/compiler.basic.test.tsrx +322 -314
  43. package/tests/client/compiler/compiler.regex.test.tsrx +44 -47
  44. package/tests/client/compiler/compiler.tracked-access.test.tsrx +38 -38
  45. package/tests/client/compiler/compiler.try-in-function.test.tsrx +16 -16
  46. package/tests/client/compiler/compiler.typescript.test.tsrx +2 -2
  47. package/tests/client/composite/composite.dynamic-components.test.tsrx +47 -48
  48. package/tests/client/composite/composite.generics.test.tsrx +168 -192
  49. package/tests/client/composite/composite.props.test.tsrx +97 -81
  50. package/tests/client/composite/composite.reactivity.test.tsrx +177 -147
  51. package/tests/client/composite/composite.render.test.tsrx +122 -105
  52. package/tests/client/computed-properties.test.tsrx +28 -28
  53. package/tests/client/context.test.tsrx +21 -21
  54. package/tests/client/css/global-additional-cases.test.tsrx +58 -58
  55. package/tests/client/css/global-advanced-selectors.test.tsrx +16 -16
  56. package/tests/client/css/global-at-rules.test.tsrx +10 -10
  57. package/tests/client/css/global-basic.test.tsrx +14 -14
  58. package/tests/client/css/global-classes-ids.test.tsrx +14 -14
  59. package/tests/client/css/global-combinators.test.tsrx +10 -10
  60. package/tests/client/css/global-complex-nesting.test.tsrx +14 -14
  61. package/tests/client/css/global-edge-cases.test.tsrx +18 -18
  62. package/tests/client/css/global-keyframes.test.tsrx +12 -12
  63. package/tests/client/css/global-nested.test.tsrx +10 -10
  64. package/tests/client/css/global-pseudo.test.tsrx +12 -12
  65. package/tests/client/css/global-scoping.test.tsrx +20 -20
  66. package/tests/client/css/style-identifier.test.tsrx +143 -291
  67. package/tests/client/date.test.tsrx +146 -133
  68. package/tests/client/dynamic-elements.test.tsrx +398 -365
  69. package/tests/client/events.test.tsrx +292 -290
  70. package/tests/client/for.test.tsrx +156 -153
  71. package/tests/client/head.test.tsrx +105 -96
  72. package/tests/client/html.test.tsrx +122 -26
  73. package/tests/client/input-value.test.tsrx +1361 -1314
  74. package/tests/client/lazy-array.test.tsrx +16 -13
  75. package/tests/client/lazy-destructuring.test.tsrx +257 -213
  76. package/tests/client/map.test.tsrx +65 -60
  77. package/tests/client/media-query.test.tsrx +22 -20
  78. package/tests/client/object.test.tsrx +87 -81
  79. package/tests/client/portal.test.tsrx +57 -51
  80. package/tests/client/ref.test.tsrx +233 -202
  81. package/tests/client/return.test.tsrx +71 -2560
  82. package/tests/client/set.test.tsrx +54 -45
  83. package/tests/client/svg.test.tsrx +216 -186
  84. package/tests/client/switch.test.tsrx +194 -193
  85. package/tests/client/track-async-hydration.test.tsrx +18 -14
  86. package/tests/client/tracked-index-access.test.tsrx +28 -18
  87. package/tests/client/try.test.tsrx +675 -548
  88. package/tests/client/tsx.test.tsrx +373 -311
  89. package/tests/client/typescript-generics.test.tsrx +145 -145
  90. package/tests/client/url/url.derived.test.tsrx +33 -28
  91. package/tests/client/url/url.parsing.test.tsrx +61 -51
  92. package/tests/client/url/url.partial-removal.test.tsrx +56 -48
  93. package/tests/client/url/url.reactivity.test.tsrx +142 -125
  94. package/tests/client/url/url.serialization.test.tsrx +13 -11
  95. package/tests/client/url-search-params/url-search-params.derived.test.tsrx +34 -29
  96. package/tests/client/url-search-params/url-search-params.initialization.test.tsrx +25 -21
  97. package/tests/client/url-search-params/url-search-params.iteration.test.tsrx +50 -45
  98. package/tests/client/url-search-params/url-search-params.mutation.test.tsrx +111 -99
  99. package/tests/client/url-search-params/url-search-params.retrieval.test.tsrx +49 -43
  100. package/tests/client/url-search-params/url-search-params.serialization.test.tsrx +14 -12
  101. package/tests/client/url-search-params/url-search-params.tracked-url.test.tsrx +16 -14
  102. package/tests/hydration/basic.test.js +3 -3
  103. package/tests/hydration/compiled/client/basic.js +586 -651
  104. package/tests/hydration/compiled/client/composite.js +79 -104
  105. package/tests/hydration/compiled/client/events.js +140 -148
  106. package/tests/hydration/compiled/client/for.js +1005 -1018
  107. package/tests/hydration/compiled/client/head.js +124 -134
  108. package/tests/hydration/compiled/client/hmr.js +41 -48
  109. package/tests/hydration/compiled/client/html-in-template.js +38 -41
  110. package/tests/hydration/compiled/client/html.js +970 -1314
  111. package/tests/hydration/compiled/client/if-children.js +234 -249
  112. package/tests/hydration/compiled/client/if.js +182 -189
  113. package/tests/hydration/compiled/client/mixed-control-flow.js +347 -303
  114. package/tests/hydration/compiled/client/nested-control-flow.js +1084 -832
  115. package/tests/hydration/compiled/client/portal.js +65 -85
  116. package/tests/hydration/compiled/client/reactivity.js +84 -90
  117. package/tests/hydration/compiled/client/return.js +38 -1939
  118. package/tests/hydration/compiled/client/switch.js +218 -224
  119. package/tests/hydration/compiled/client/track-async-serialization.js +250 -259
  120. package/tests/hydration/compiled/client/try.js +123 -132
  121. package/tests/hydration/compiled/server/basic.js +773 -831
  122. package/tests/hydration/compiled/server/composite.js +166 -191
  123. package/tests/hydration/compiled/server/events.js +170 -184
  124. package/tests/hydration/compiled/server/for.js +851 -909
  125. package/tests/hydration/compiled/server/head.js +206 -216
  126. package/tests/hydration/compiled/server/hmr.js +64 -72
  127. package/tests/hydration/compiled/server/html-in-template.js +42 -76
  128. package/tests/hydration/compiled/server/html.js +1362 -1667
  129. package/tests/hydration/compiled/server/if-children.js +419 -445
  130. package/tests/hydration/compiled/server/if.js +194 -208
  131. package/tests/hydration/compiled/server/mixed-control-flow.js +249 -257
  132. package/tests/hydration/compiled/server/nested-control-flow.js +491 -515
  133. package/tests/hydration/compiled/server/portal.js +152 -160
  134. package/tests/hydration/compiled/server/reactivity.js +94 -106
  135. package/tests/hydration/compiled/server/return.js +28 -2172
  136. package/tests/hydration/compiled/server/switch.js +274 -286
  137. package/tests/hydration/compiled/server/track-async-serialization.js +340 -358
  138. package/tests/hydration/compiled/server/try.js +167 -185
  139. package/tests/hydration/components/basic.tsrx +320 -272
  140. package/tests/hydration/components/composite.tsrx +44 -32
  141. package/tests/hydration/components/events.tsrx +101 -91
  142. package/tests/hydration/components/for.tsrx +510 -452
  143. package/tests/hydration/components/head.tsrx +87 -80
  144. package/tests/hydration/components/hmr.tsrx +22 -17
  145. package/tests/hydration/components/html-in-template.tsrx +22 -17
  146. package/tests/hydration/components/html.tsrx +525 -443
  147. package/tests/hydration/components/if-children.tsrx +158 -148
  148. package/tests/hydration/components/if.tsrx +109 -95
  149. package/tests/hydration/components/mixed-control-flow.tsrx +100 -96
  150. package/tests/hydration/components/nested-control-flow.tsrx +215 -203
  151. package/tests/hydration/components/portal.tsrx +41 -34
  152. package/tests/hydration/components/reactivity.tsrx +37 -27
  153. package/tests/hydration/components/return.tsrx +12 -556
  154. package/tests/hydration/components/switch.tsrx +120 -114
  155. package/tests/hydration/components/track-async-serialization.tsrx +107 -91
  156. package/tests/hydration/components/try.tsrx +55 -40
  157. package/tests/hydration/html.test.js +4 -4
  158. package/tests/hydration/return.test.js +13 -532
  159. package/tests/server/await.test.tsrx +3 -3
  160. package/tests/server/basic.attributes.test.tsrx +264 -195
  161. package/tests/server/basic.components.test.tsrx +296 -169
  162. package/tests/server/basic.test.tsrx +300 -198
  163. package/tests/server/compiler.test.tsrx +62 -60
  164. package/tests/server/composite.props.test.tsrx +77 -63
  165. package/tests/server/composite.test.tsrx +168 -192
  166. package/tests/server/context.test.tsrx +18 -12
  167. package/tests/server/dynamic-elements.test.tsrx +197 -180
  168. package/tests/server/for.test.tsrx +85 -78
  169. package/tests/server/head.test.tsrx +50 -43
  170. package/tests/server/html-nesting-validation.test.tsrx +8 -8
  171. package/tests/server/if.test.tsrx +57 -51
  172. package/tests/server/lazy-destructuring.test.tsrx +366 -294
  173. package/tests/server/return.test.tsrx +76 -1355
  174. package/tests/server/streaming-ssr.test.tsrx +4 -75
  175. package/tests/server/style-identifier.test.tsrx +169 -148
  176. package/tests/server/switch.test.tsrx +91 -85
  177. package/tests/server/track-async-serialization.test.tsrx +105 -85
  178. package/tests/server/try.test.tsrx +374 -280
  179. package/tests/utils/compiler-compat-config.test.js +2 -2
  180. package/tests/utils/runtime-imports.test.js +10 -0
  181. package/types/index.d.ts +8 -0
  182. package/tests/client/__snapshots__/html.test.rsrx.snap +0 -40
package/CHANGELOG.md CHANGED
@@ -1,5 +1,62 @@
1
1
  # ripple
2
2
 
3
+ ## 0.3.69
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1177](https://github.com/Ripple-TS/ripple/pull/1177)
8
+ [`054bd1e`](https://github.com/Ripple-TS/ripple/commit/054bd1e75347e395f6c096f8e293d1baf8e03549)
9
+ Thanks [@trueadm](https://github.com/trueadm)! - Parse tags and bare fragments
10
+ as native TSRX by default, remove `component` keyword parsing, and
11
+ compile/format/lint function components that return native TSRX across the
12
+ React, Preact, Solid, Vue, and Ripple targets. Ripple component compilation now
13
+ only renders TSRX reachable from returned values and supports string and `null`
14
+ component returns.
15
+
16
+ Ripple now also preserves directly called PascalCase helpers as ordinary
17
+ functions while still compiling renderable component functions used as
18
+ components or render entries.
19
+
20
+ The old explicit TSRX wrapper tag is no longer special; TSRX elements and
21
+ fragments are the default expression syntax, and the tag name is treated like
22
+ any ordinary element name.
23
+
24
+ Ripple now exports a typed `Fragment` helper from its public runtimes and
25
+ supports `innerHTML` on both host elements and `Fragment`. Ripple also treats
26
+ `innerHTML` from element spreads as rendered content instead of serializing it
27
+ as an `innerhtml` attribute.
28
+
29
+ The `{html ...}` template directive has been removed. Use each target's native
30
+ raw HTML prop instead, such as `innerHTML` for Ripple/Solid/Vue or
31
+ `dangerouslySetInnerHTML` for React/Preact.
32
+
33
+ The `{text ...}` template directive has also been removed. Text values now use
34
+ ordinary `{expr}` containers, with explicit coercion written as JavaScript
35
+ (`String(value)`, `value + ''`, or a typed string value). Ripple optimizes
36
+ clearly string-shaped expressions and typed string props into text-node updates
37
+ without requiring a TSRX-specific directive.
38
+
39
+ - [#1177](https://github.com/Ripple-TS/ripple/pull/1177)
40
+ [`054bd1e`](https://github.com/Ripple-TS/ripple/commit/054bd1e75347e395f6c096f8e293d1baf8e03549)
41
+ Thanks [@trueadm](https://github.com/trueadm)! - Compile native TSRX functions
42
+ as value-producing functions and route component syntax through runtime
43
+ component helpers.
44
+
45
+ - Updated dependencies
46
+ [[`054bd1e`](https://github.com/Ripple-TS/ripple/commit/054bd1e75347e395f6c096f8e293d1baf8e03549),
47
+ [`054bd1e`](https://github.com/Ripple-TS/ripple/commit/054bd1e75347e395f6c096f8e293d1baf8e03549)]:
48
+ - @tsrx/core@0.1.17
49
+ - @tsrx/ripple@0.1.17
50
+
51
+ ## 0.3.68
52
+
53
+ ### Patch Changes
54
+
55
+ - Updated dependencies
56
+ [[`d045396`](https://github.com/Ripple-TS/ripple/commit/d0453962cfe1df7a98a0981b0bf3e5729195a9ae)]:
57
+ - @tsrx/ripple@0.1.16
58
+ - @tsrx/core@0.1.16
59
+
3
60
  ## 0.3.67
4
61
 
5
62
  ### 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.67",
6
+ "version": "0.3.69",
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.15",
78
- "@tsrx/ripple": "0.1.15"
77
+ "@tsrx/core": "0.1.17",
78
+ "@tsrx/ripple": "0.1.17"
79
79
  },
80
80
  "devDependencies": {
81
81
  "@types/estree": "^1.0.8",
@@ -1,4 +1,4 @@
1
- import type { AddEventObject, RefKey, TSRXElement } from '#public';
1
+ import type { AddEventObject, FragmentProps, RefKey, TSRXElement } from '#public';
2
2
  import type { Nullable } from '#helpers';
3
3
 
4
4
  /**
@@ -40,7 +40,7 @@ export function jsxs(
40
40
  * JSX Fragment component
41
41
  * Ripple fragments are renderable expression values.
42
42
  */
43
- export function Fragment(props: { children?: any }): TSRXElement;
43
+ export function Fragment(props: FragmentProps): TSRXElement;
44
44
 
45
45
  export type ClassValue = string | import('clsx').ClassArray | import('clsx').ClassDictionary;
46
46
 
@@ -35,5 +35,5 @@ export function normalize_children(value) {
35
35
  return value;
36
36
  }
37
37
 
38
- return tsrx_element(value);
38
+ return tsrx_element(() => value({}));
39
39
  }
@@ -1,4 +1,4 @@
1
- /** @import { Block, CompatOptions, RootBoundaryOptions } from '#client' */
1
+ /** @import { CompatOptions, RootBoundaryOptions } from '#client' */
2
2
 
3
3
  import { destroy_block, root } from './internal/client/blocks.js';
4
4
  import { handle_root_events } from './internal/client/events.js';
@@ -7,7 +7,7 @@ import {
7
7
  get_next_sibling,
8
8
  init_operations,
9
9
  } from './internal/client/operations.js';
10
- import { active_block } from './internal/client/runtime.js';
10
+ import { render_component } from './internal/client/component.js';
11
11
  import { create_anchor } from './internal/client/utils.js';
12
12
  import { try_block } from './internal/client/try.js';
13
13
  import { remove_ssr_css } from './internal/client/css.js';
@@ -39,34 +39,34 @@ function get_default_compat() {
39
39
 
40
40
  /**
41
41
  * @param {Node} anchor
42
- * @param {(anchor: Node) => void} render_component
42
+ * @param {(anchor: Node) => void} render_content
43
43
  * @param {RootBoundaryOptions | undefined} boundary
44
44
  * @returns {void}
45
45
  */
46
- function render_root_boundary(anchor, render_component, boundary) {
46
+ function render_root_boundary(anchor, render_content, boundary) {
47
47
  const Pending = boundary?.pending;
48
48
  const Catch = boundary?.catch;
49
49
 
50
50
  try_block(
51
51
  anchor,
52
52
  (component_anchor) => {
53
- render_component(component_anchor);
53
+ render_content(component_anchor);
54
54
  },
55
55
  Catch
56
56
  ? (catch_anchor, error, reset) => {
57
- Catch(catch_anchor, { error, reset: reset ?? (() => {}) }, active_block);
57
+ render_component(Catch, catch_anchor, { error, reset: reset ?? (() => {}) });
58
58
  }
59
59
  : null,
60
60
  (pending_anchor) => {
61
61
  if (Pending) {
62
- Pending(pending_anchor, {}, active_block);
62
+ render_component(Pending, pending_anchor, {});
63
63
  }
64
64
  },
65
65
  );
66
66
  }
67
67
 
68
68
  /**
69
- * @param {(anchor: Node, props: Record<string, any>, active_block: Block | null) => void} component
69
+ * @param {Function} component
70
70
  * @param {{ props?: Record<string, any>, target: HTMLElement, rootBoundary?: RootBoundaryOptions }} options
71
71
  * @returns {() => void}
72
72
  */
@@ -92,7 +92,7 @@ export function mount(component, options) {
92
92
  render_root_boundary(
93
93
  anchor,
94
94
  (component_anchor) => {
95
- component(component_anchor, props, active_block);
95
+ render_component(component, component_anchor, props);
96
96
  },
97
97
  options.rootBoundary,
98
98
  );
@@ -105,7 +105,7 @@ export function mount(component, options) {
105
105
  }
106
106
 
107
107
  /**
108
- * @param {(anchor: Node, props: Record<string, any>, active_block: Block | null) => void} component
108
+ * @param {Function} component
109
109
  * @param {{ props?: Record<string, any>, target: HTMLElement, rootBoundary?: RootBoundaryOptions }} options
110
110
  * @returns {() => void}
111
111
  */
@@ -138,7 +138,7 @@ export function hydrate(component, options) {
138
138
  render_root_boundary(
139
139
  /** @type {Comment} */ (anchor),
140
140
  (component_anchor) => {
141
- component(component_anchor, props, active_block);
141
+ render_component(component, component_anchor, props);
142
142
  },
143
143
  options.rootBoundary,
144
144
  );
@@ -1,5 +1,6 @@
1
- import { output_push, noop } from './internal/server/index.js';
1
+ import { output_push, noop, tsrx_element } from './internal/server/index.js';
2
2
 
3
+ export { Fragment } from '../jsx-runtime.js';
3
4
  export { Context } from './internal/server/context.js';
4
5
  export {
5
6
  untrack,
@@ -73,7 +74,9 @@ export const bindOffsetHeight = noop;
73
74
  * However, we need to output a marker comment so hydration can work correctly.
74
75
  */
75
76
  export function Portal() {
76
- // Portals are client-only, but we need to output a marker for hydration
77
- // Output an empty HTML comment as a placeholder
78
- output_push('<!--portal-->');
77
+ return tsrx_element(function render_portal() {
78
+ // Portals are client-only, but we need to output a marker for hydration
79
+ // Output an empty HTML comment as a placeholder
80
+ output_push('<!--portal-->');
81
+ });
79
82
  }
@@ -717,7 +717,7 @@ export function bindFiles(maybe_tracked, set_func = undefined) {
717
717
  }
718
718
 
719
719
  /**
720
- * Syntactic sugar for binding a HTMLElement with {ref fn}
720
+ * Syntactic sugar for binding a HTMLElement with ref={fn}
721
721
  * @param {unknown} maybe_tracked
722
722
  * @param {SetFunction | undefined} set_func
723
723
  * @returns {(node: HTMLElement) => void}
@@ -18,6 +18,7 @@ import {
18
18
  } from './constants.js';
19
19
  import { next_sibling } from './operations.js';
20
20
  import { apply_element_spread } from './render.js';
21
+ import { is_array } from '@tsrx/core/runtime/language-helpers';
21
22
  import {
22
23
  active_block,
23
24
  active_component,
@@ -99,18 +100,19 @@ export function branch(fn, flags = 0, state = null) {
99
100
  }
100
101
 
101
102
  /**
102
- * Wire up a `{ref expr}` attribute. `expr` may be:
103
+ * Wire up a `ref={expr}` attribute. `expr` may be:
103
104
  * - a callback function — invoked with the element on mount; if it returns
104
105
  * a function, that function runs as the cleanup on unmount.
105
106
  * - a `Tracked` (e.g. from `track()`) — `tracked.value` is set to the
106
107
  * element on mount and reset to `null` on unmount.
107
108
  * - a plain mutable var (`let foo;`) — the element is assigned to the
108
109
  * variable on mount and reset to `null` on unmount.
110
+ * - an array of any of the above.
109
111
  *
110
112
  * `get_fn` is invoked through `untrack` so the surrounding render block
111
113
  * doesn't subscribe to whatever the thunk happens to read. The supported
112
- * shape is to pass the ref slot itself (`{ref tracker}`); a foot-gun like
113
- * `{ref tracker.value}` would otherwise read the cell reactively and cause
114
+ * shape is to pass the ref slot itself (`ref={tracker}`); a foot-gun like
115
+ * `ref={tracker.value}` would otherwise read the cell reactively and cause
114
116
  * spurious re-runs.
115
117
  *
116
118
  * @param {Element} element
@@ -134,7 +136,14 @@ export function ref(element, get_fn, set_fn) {
134
136
  e = null;
135
137
  }
136
138
 
137
- if (typeof ref_value === 'function') {
139
+ if (is_array(ref_value)) {
140
+ e = branch(() => {
141
+ for (var i = 0; i < ref_value.length; i++) {
142
+ let current = ref_value[i];
143
+ ref(element, () => current);
144
+ }
145
+ });
146
+ } else if (typeof ref_value === 'function') {
138
147
  e = branch(() => {
139
148
  effect(() => ref_value(element));
140
149
  });
@@ -0,0 +1,55 @@
1
+ /** @import { Block } from '#client' */
2
+
3
+ import { is_tsrx_element } from '../../element.js';
4
+ import { render_value } from './expression.js';
5
+ import { active_block, pop_component, push_component } from './runtime.js';
6
+
7
+ /**
8
+ * @param {Function} fn
9
+ * @param {Node} anchor
10
+ * @param {Record<string, any>} props
11
+ * @param {Block | null} [block=active_block]
12
+ * @returns {void}
13
+ */
14
+ export function render_component(fn, anchor, props, block = active_block) {
15
+ if (typeof fn !== 'function' || is_tsrx_element(fn)) {
16
+ throw_invalid_component_type(fn);
17
+ }
18
+
19
+ run_component(fn, anchor, props, block);
20
+ }
21
+
22
+ /**
23
+ * @param {Function} fn
24
+ * @param {Node} anchor
25
+ * @param {Record<string, any>} props
26
+ * @param {Block | null} [block=active_block]
27
+ * @returns {void}
28
+ */
29
+ function run_component(fn, anchor, props, block = active_block) {
30
+ push_component();
31
+ render_value(fn(props), /** @type {ChildNode} */ (anchor), block);
32
+ pop_component();
33
+ }
34
+
35
+ /**
36
+ * @param {import('../../element.js').TSRXElement} value
37
+ * @param {Node} anchor
38
+ * @param {Block | null} [block=active_block]
39
+ * @returns {void}
40
+ */
41
+ export function render_tsrx_element(value, anchor, block = active_block) {
42
+ render_value(value, /** @type {ChildNode} */ (anchor), block);
43
+ }
44
+
45
+ /**
46
+ * @param {any} value
47
+ * @returns {never}
48
+ */
49
+ function throw_invalid_component_type(value) {
50
+ if (is_tsrx_element(value)) {
51
+ throw new TypeError('Invalid component type: received a TSRXElement value.');
52
+ }
53
+
54
+ throw new TypeError('Invalid component type: expected a component function.');
55
+ }
@@ -6,6 +6,7 @@ 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
8
  import { is_tsrx_element } from '../../element.js';
9
+ import { render_component } from './component.js';
9
10
 
10
11
  /**
11
12
  * @typedef {((anchor: Node, props: Record<string, any>, block: Block | null) => void)} ComponentFunction
@@ -41,9 +42,10 @@ export function composite(get_component, node, props) {
41
42
  if (typeof component === 'function') {
42
43
  // Handle as regular component
43
44
  b = branch(() => {
44
- var block = active_block;
45
- /** @type {ComponentFunction} */ (component)(anchor, props, block);
45
+ render_component(component, anchor, props);
46
46
  });
47
+ } else if (is_tsrx_element(component)) {
48
+ throw new TypeError('Invalid component type: received a TSRXElement value.');
47
49
  } else if (component != null) {
48
50
  // Custom element - only create if component is not null/undefined
49
51
  const ns = top_element_to_ns(component, active_namespace);
@@ -25,10 +25,29 @@ function find_enclosing_branch(block) {
25
25
  return null;
26
26
  }
27
27
 
28
+ /**
29
+ * @param {any} value
30
+ * @param {ChildNode} anchor
31
+ * @param {Block | null} block
32
+ * @returns {void}
33
+ */
34
+ export function render_value(value, anchor, block) {
35
+ if (is_tsrx_element(value)) {
36
+ render_tsrx_element(value, anchor, block);
37
+ } else if (is_array(value)) {
38
+ render_tsrx_collection(value, anchor, block);
39
+ } else if (value != null) {
40
+ var text = value + '';
41
+ if (text !== '') {
42
+ render_tsrx_collection_text(text, anchor, true);
43
+ }
44
+ }
45
+ }
46
+
28
47
  /**
29
48
  * @param {any[]} value
30
49
  * @param {ChildNode} anchor
31
- * @param {Block} block
50
+ * @param {Block | null} block
32
51
  * @returns {void}
33
52
  */
34
53
  function render_tsrx_collection(value, anchor, block) {
@@ -49,7 +68,7 @@ function render_tsrx_collection(value, anchor, block) {
49
68
  /**
50
69
  * @param {any[]} value
51
70
  * @param {ChildNode} anchor
52
- * @param {Block} block
71
+ * @param {Block | null} block
53
72
  * @returns {void}
54
73
  */
55
74
  function render_tsrx_collection_items(value, anchor, block) {
@@ -57,7 +76,7 @@ function render_tsrx_collection_items(value, anchor, block) {
57
76
  var item = value[i];
58
77
 
59
78
  if (is_tsrx_element(item)) {
60
- item.render(anchor, block);
79
+ render_tsrx_element(item, anchor, block);
61
80
  } else if (is_array(item)) {
62
81
  render_tsrx_collection_items(item, anchor, block);
63
82
  } else if (item != null) {
@@ -66,19 +85,46 @@ function render_tsrx_collection_items(value, anchor, block) {
66
85
  }
67
86
  }
68
87
 
88
+ /**
89
+ * @param {import('../../element.js').TSRXElement} value
90
+ * @param {ChildNode} anchor
91
+ * @param {Block | null} block
92
+ * @returns {void}
93
+ */
94
+ function render_tsrx_element(value, anchor, block) {
95
+ var result = value.render(anchor, block);
96
+
97
+ if (is_tsrx_element(result)) {
98
+ render_tsrx_element(result, anchor, block);
99
+ } else if (is_array(result)) {
100
+ render_tsrx_collection(result, anchor, block);
101
+ } else if (result != null) {
102
+ render_tsrx_collection_text(result + '', anchor, true);
103
+ }
104
+ }
105
+
69
106
  /**
70
107
  * @param {string} value
71
108
  * @param {ChildNode} anchor
109
+ * @param {boolean} [assign=false]
72
110
  * @returns {void}
73
111
  */
74
- function render_tsrx_collection_text(value, anchor) {
112
+ function render_tsrx_collection_text(value, anchor, assign = false) {
75
113
  if (!hydrating) {
76
- anchor.before(create_text(value));
114
+ var text = create_text(value);
115
+ anchor.before(text);
116
+ if (assign) {
117
+ assign_nodes(text, text);
118
+ }
77
119
  return;
78
120
  }
79
121
 
80
122
  var node = hydrate_node;
81
123
 
124
+ if (node?.nodeType === COMMENT_NODE && /** @type {Comment} */ (node).data === HYDRATION_START) {
125
+ node = get_next_sibling(node);
126
+ }
127
+
82
128
  if (node?.nodeType === TEXT_NODE) {
83
129
  var current_value = /** @type {Text} */ (node).nodeValue ?? '';
84
130
 
@@ -91,12 +137,18 @@ function render_tsrx_collection_text(value, anchor) {
91
137
  if (remaining !== '') {
92
138
  var remaining_text = create_text(remaining);
93
139
  /** @type {ChildNode} */ (node).after(remaining_text);
140
+ if (assign) {
141
+ assign_nodes(node, node);
142
+ }
94
143
  set_hydrate_node(remaining_text);
95
144
  return;
96
145
  }
97
146
  }
98
147
  }
99
148
 
149
+ if (assign) {
150
+ assign_nodes(node, node);
151
+ }
100
152
  set_hydrate_node(get_next_sibling(node) ?? anchor);
101
153
  return;
102
154
  }
@@ -109,6 +161,9 @@ function render_tsrx_collection_text(value, anchor) {
109
161
  anchor.before(new_text);
110
162
  }
111
163
 
164
+ if (assign) {
165
+ assign_nodes(new_text, new_text);
166
+ }
112
167
  set_hydrate_node(node ?? anchor);
113
168
  }
114
169
 
@@ -138,7 +193,10 @@ export function expression(node, get_value) {
138
193
  var next_value = get_value();
139
194
  var next_is_collection = is_array(next_value);
140
195
  var next_is_element = next_is_collection || is_tsrx_element(next_value);
141
- var is_hydration_marker = hydrating && anchor.nodeType === COMMENT_NODE;
196
+ var is_hydration_marker =
197
+ hydrating &&
198
+ anchor.nodeType === COMMENT_NODE &&
199
+ /** @type {Comment} */ (anchor).data === HYDRATION_START;
142
200
 
143
201
  if (is_hydration_marker) {
144
202
  end ??= ensure_expression_end(anchor);
@@ -187,7 +245,7 @@ export function expression(node, get_value) {
187
245
  if (next_is_collection) {
188
246
  render_tsrx_collection(next_value, end ?? anchor, block);
189
247
  } else {
190
- next_value.render(end ?? anchor, block);
248
+ render_tsrx_element(next_value, end ?? anchor, block);
191
249
  }
192
250
  });
193
251
 
@@ -5,8 +5,10 @@ import { HMR } from './constants.js';
5
5
  import { hydrate_node, hydrating } from './hydration.js';
6
6
  import { branch, destroy_block, render } from './blocks.js';
7
7
  import { active_block, get, set, tracked } from './runtime.js';
8
+ import { render_component } from './component.js';
9
+ import { tsrx_element } from '../../element.js';
8
10
 
9
- /** @typedef {(anchor: Node, props: any, block: Block | null) => void} Component */
11
+ /** @typedef {(props?: any) => any} Component */
10
12
 
11
13
  /** @typedef {Component & { [HMR]: { fn: Component; current: Tracked | undefined; update: (incoming: ComponentWrapper) => void; } }} ComponentWrapper */
12
14
 
@@ -23,53 +25,62 @@ export function hmr(fn) {
23
25
  var current;
24
26
 
25
27
  /**
26
- * @param {Node} anchor
27
28
  * @param {any} props
28
- * @param {Block | null} [block]
29
29
  */
30
- function wrapper(anchor, props, block = active_block) {
31
- if (current === undefined) {
32
- current = wrapper[HMR].current;
33
- }
34
- /** @type {Node} */
35
- var target = anchor;
36
-
37
- if (current === undefined) {
38
- current = tracked(fn, /** @type {Block} */ (block));
39
- wrapper[HMR].current = current;
40
- }
41
- var component = {};
42
-
43
- /** @type {Block | null} */
44
- var effect = null;
45
-
46
- render(
47
- () => {
48
- var next_component = get(/** @type {Tracked} */ (current));
49
-
50
- if (component === next_component) {
51
- return;
52
- }
53
-
54
- component = next_component;
55
-
56
- if (effect) {
57
- destroy_block(effect);
58
- }
59
-
60
- effect = branch(() => {
61
- /** @type {Function} */ (component)(target, props, active_block);
62
- });
63
- },
64
- null,
65
- RENDER_BLOCK,
66
- );
30
+ function wrapper(props) {
31
+ /**
32
+ * @param {Node} anchor
33
+ * @param {Block | null} [block]
34
+ * @returns {void}
35
+ */
36
+ function render_children(anchor, block = active_block) {
37
+ if (current === undefined) {
38
+ current = wrapper[HMR].current;
39
+ }
40
+ /** @type {Node} */
41
+ var target = anchor;
67
42
 
68
- if (hydrating) {
69
- target = /** @type {Node} */ (hydrate_node);
43
+ if (current === undefined) {
44
+ current = tracked(fn, /** @type {Block} */ (block));
45
+ wrapper[HMR].current = current;
46
+ }
47
+ var component = {};
48
+
49
+ /** @type {Block | null} */
50
+ var effect = null;
51
+
52
+ render(
53
+ () => {
54
+ var next_component = get(/** @type {Tracked} */ (current));
55
+
56
+ if (component === next_component) {
57
+ return;
58
+ }
59
+
60
+ component = next_component;
61
+
62
+ if (effect) {
63
+ destroy_block(effect);
64
+ }
65
+
66
+ effect = branch(() => {
67
+ render_component(
68
+ /** @type {Function} */ (component),
69
+ /** @type {ChildNode} */ (target),
70
+ props,
71
+ );
72
+ });
73
+ },
74
+ null,
75
+ RENDER_BLOCK,
76
+ );
77
+
78
+ if (hydrating) {
79
+ target = /** @type {Node} */ (hydrate_node);
80
+ }
70
81
  }
71
82
 
72
- return wrapper;
83
+ return tsrx_element(render_children);
73
84
  }
74
85
 
75
86
  wrapper[HMR] = {
@@ -85,6 +85,8 @@ export {
85
85
 
86
86
  export { composite } from './composite.js';
87
87
 
88
+ export { render_component } from './component.js';
89
+
88
90
  export { for_block as for, for_block_keyed as for_keyed } from './for.js';
89
91
 
90
92
  export { if_block as if } from './if.js';
@@ -127,10 +129,12 @@ export { rpc } from './rpc.js';
127
129
 
128
130
  export { tsx_compat } from './compat.js';
129
131
 
132
+ export { render_tsrx_element } from './component.js';
133
+
130
134
  export { TRY_BLOCK, HMR } from './constants.js';
131
135
 
132
136
  export { hmr } from './hmr.js';
133
137
 
134
138
  export { pop, next } from './hydration.js';
135
139
 
136
- export { tsrx_element, normalize_children } from '../../element.js';
140
+ export { is_tsrx_element, tsrx_element, normalize_children } from '../../element.js';