@remix-run/ui 0.0.0 → 0.1.0

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 (273) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +195 -2
  3. package/dist/animation/animate-layout-mixin.d.ts +12 -0
  4. package/dist/animation/animate-layout-mixin.js +214 -0
  5. package/dist/animation/animate-layout-mixin.js.map +1 -0
  6. package/dist/animation/animate-mixins.d.ts +29 -0
  7. package/dist/animation/animate-mixins.js +235 -0
  8. package/dist/animation/animate-mixins.js.map +1 -0
  9. package/dist/animation/index.d.ts +6 -0
  10. package/dist/animation/index.js +5 -0
  11. package/dist/animation/index.js.map +1 -0
  12. package/dist/animation/spring.d.ts +75 -0
  13. package/dist/animation/spring.js +183 -0
  14. package/dist/animation/spring.js.map +1 -0
  15. package/dist/animation/tween.d.ts +70 -0
  16. package/dist/animation/tween.js +93 -0
  17. package/dist/animation/tween.js.map +1 -0
  18. package/dist/components/accordion/accordion.d.ts +92 -0
  19. package/dist/components/accordion/accordion.js +337 -0
  20. package/dist/components/accordion/accordion.js.map +1 -0
  21. package/dist/components/anchor/anchor.d.ts +13 -0
  22. package/dist/components/anchor/anchor.js +558 -0
  23. package/dist/components/anchor/anchor.js.map +1 -0
  24. package/dist/components/breadcrumbs/breadcrumbs.d.ts +11 -0
  25. package/dist/components/breadcrumbs/breadcrumbs.js +78 -0
  26. package/dist/components/breadcrumbs/breadcrumbs.js.map +1 -0
  27. package/dist/components/button/button.d.ts +23 -0
  28. package/dist/components/button/button.js +147 -0
  29. package/dist/components/button/button.js.map +1 -0
  30. package/dist/components/combobox/combobox.d.ts +101 -0
  31. package/dist/components/combobox/combobox.js +708 -0
  32. package/dist/components/combobox/combobox.js.map +1 -0
  33. package/dist/components/glyph/glyph.d.ts +14 -0
  34. package/dist/components/glyph/glyph.js +65 -0
  35. package/dist/components/glyph/glyph.js.map +1 -0
  36. package/dist/components/listbox/listbox.d.ts +67 -0
  37. package/dist/components/listbox/listbox.js +340 -0
  38. package/dist/components/listbox/listbox.js.map +1 -0
  39. package/dist/components/menu/hover-aim.d.ts +5 -0
  40. package/dist/components/menu/hover-aim.js +308 -0
  41. package/dist/components/menu/hover-aim.js.map +1 -0
  42. package/dist/components/menu/menu.d.ts +164 -0
  43. package/dist/components/menu/menu.js +1106 -0
  44. package/dist/components/menu/menu.js.map +1 -0
  45. package/dist/components/popover/popover.d.ts +35 -0
  46. package/dist/components/popover/popover.js +138 -0
  47. package/dist/components/popover/popover.js.map +1 -0
  48. package/dist/components/select/select.d.ts +67 -0
  49. package/dist/components/select/select.js +397 -0
  50. package/dist/components/select/select.js.map +1 -0
  51. package/dist/components/separator/separator.d.ts +2 -0
  52. package/dist/components/separator/separator.js +15 -0
  53. package/dist/components/separator/separator.js.map +1 -0
  54. package/dist/components/tabs/tabs.d.ts +78 -0
  55. package/dist/components/tabs/tabs.js +341 -0
  56. package/dist/components/tabs/tabs.js.map +1 -0
  57. package/dist/index.d.ts +28 -0
  58. package/dist/index.js +21 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/interactions/keydown/keydown.d.ts +1 -0
  61. package/dist/interactions/keydown/keydown.js +8 -0
  62. package/dist/interactions/keydown/keydown.js.map +1 -0
  63. package/dist/interactions/outside-click/outside-click-mixin.d.ts +2 -0
  64. package/dist/interactions/outside-click/outside-click-mixin.js +29 -0
  65. package/dist/interactions/outside-click/outside-click-mixin.js.map +1 -0
  66. package/dist/interactions/typeahead/typeahead-mixin.d.ts +8 -0
  67. package/dist/interactions/typeahead/typeahead-mixin.js +59 -0
  68. package/dist/interactions/typeahead/typeahead-mixin.js.map +1 -0
  69. package/dist/jsx-runtime.d.ts +2 -0
  70. package/dist/jsx-runtime.js +3 -0
  71. package/dist/jsx-runtime.js.map +1 -0
  72. package/dist/runtime/client-entries.d.ts +91 -0
  73. package/dist/runtime/client-entries.js +40 -0
  74. package/dist/runtime/client-entries.js.map +1 -0
  75. package/dist/runtime/component.d.ts +223 -0
  76. package/dist/runtime/component.js +152 -0
  77. package/dist/runtime/component.js.map +1 -0
  78. package/dist/runtime/create-element.d.ts +10 -0
  79. package/dist/runtime/create-element.js +29 -0
  80. package/dist/runtime/create-element.js.map +1 -0
  81. package/dist/runtime/diff-dom.d.ts +2 -0
  82. package/dist/runtime/diff-dom.js +364 -0
  83. package/dist/runtime/diff-dom.js.map +1 -0
  84. package/dist/runtime/diff-props.d.ts +9 -0
  85. package/dist/runtime/diff-props.js +195 -0
  86. package/dist/runtime/diff-props.js.map +1 -0
  87. package/dist/runtime/document-state.d.ts +11 -0
  88. package/dist/runtime/document-state.js +106 -0
  89. package/dist/runtime/document-state.js.map +1 -0
  90. package/dist/runtime/dom.d.ts +3189 -0
  91. package/dist/runtime/dom.js +2 -0
  92. package/dist/runtime/dom.js.map +1 -0
  93. package/dist/runtime/error-event.d.ts +20 -0
  94. package/dist/runtime/error-event.js +19 -0
  95. package/dist/runtime/error-event.js.map +1 -0
  96. package/dist/runtime/event-listeners.d.ts +50 -0
  97. package/dist/runtime/event-listeners.js +31 -0
  98. package/dist/runtime/event-listeners.js.map +1 -0
  99. package/dist/runtime/frame.d.ts +119 -0
  100. package/dist/runtime/frame.js +915 -0
  101. package/dist/runtime/frame.js.map +1 -0
  102. package/dist/runtime/invariant.d.ts +23 -0
  103. package/dist/runtime/invariant.js +33 -0
  104. package/dist/runtime/invariant.js.map +1 -0
  105. package/dist/runtime/jsx.d.ts +320 -0
  106. package/dist/runtime/jsx.js +31 -0
  107. package/dist/runtime/jsx.js.map +1 -0
  108. package/dist/runtime/mixins/attrs-mixin.d.ts +9 -0
  109. package/dist/runtime/mixins/attrs-mixin.js +23 -0
  110. package/dist/runtime/mixins/attrs-mixin.js.map +1 -0
  111. package/dist/runtime/mixins/link-mixin.d.ts +17 -0
  112. package/dist/runtime/mixins/link-mixin.js +101 -0
  113. package/dist/runtime/mixins/link-mixin.js.map +1 -0
  114. package/dist/runtime/mixins/mixin.d.ts +145 -0
  115. package/dist/runtime/mixins/mixin.js +570 -0
  116. package/dist/runtime/mixins/mixin.js.map +1 -0
  117. package/dist/runtime/mixins/on-mixin.d.ts +16 -0
  118. package/dist/runtime/mixins/on-mixin.js +49 -0
  119. package/dist/runtime/mixins/on-mixin.js.map +1 -0
  120. package/dist/runtime/mixins/ref-mixin.d.ts +9 -0
  121. package/dist/runtime/mixins/ref-mixin.js +21 -0
  122. package/dist/runtime/mixins/ref-mixin.js.map +1 -0
  123. package/dist/runtime/navigation.d.ts +22 -0
  124. package/dist/runtime/navigation.js +102 -0
  125. package/dist/runtime/navigation.js.map +1 -0
  126. package/dist/runtime/reconcile.d.ts +11 -0
  127. package/dist/runtime/reconcile.js +1240 -0
  128. package/dist/runtime/reconcile.js.map +1 -0
  129. package/dist/runtime/render.d.ts +54 -0
  130. package/dist/runtime/render.js +50 -0
  131. package/dist/runtime/render.js.map +1 -0
  132. package/dist/runtime/run.d.ts +45 -0
  133. package/dist/runtime/run.js +68 -0
  134. package/dist/runtime/run.js.map +1 -0
  135. package/dist/runtime/scheduler.d.ts +30 -0
  136. package/dist/runtime/scheduler.js +185 -0
  137. package/dist/runtime/scheduler.js.map +1 -0
  138. package/dist/runtime/svg-attributes.d.ts +5 -0
  139. package/dist/runtime/svg-attributes.js +124 -0
  140. package/dist/runtime/svg-attributes.js.map +1 -0
  141. package/dist/runtime/to-vnode.d.ts +3 -0
  142. package/dist/runtime/to-vnode.js +43 -0
  143. package/dist/runtime/to-vnode.js.map +1 -0
  144. package/dist/runtime/typed-event-target.d.ts +50 -0
  145. package/dist/runtime/typed-event-target.js +6 -0
  146. package/dist/runtime/typed-event-target.js.map +1 -0
  147. package/dist/runtime/vdom.d.ts +55 -0
  148. package/dist/runtime/vdom.js +198 -0
  149. package/dist/runtime/vdom.js.map +1 -0
  150. package/dist/runtime/vnode.d.ts +79 -0
  151. package/dist/runtime/vnode.js +38 -0
  152. package/dist/runtime/vnode.js.map +1 -0
  153. package/dist/server/stream.d.ts +57 -0
  154. package/dist/server/stream.js +1007 -0
  155. package/dist/server/stream.js.map +1 -0
  156. package/dist/style/css-mixin.d.ts +8 -0
  157. package/dist/style/css-mixin.js +54 -0
  158. package/dist/style/css-mixin.js.map +1 -0
  159. package/dist/style/index.d.ts +5 -0
  160. package/dist/style/index.js +4 -0
  161. package/dist/style/index.js.map +1 -0
  162. package/dist/style/style.d.ts +24 -0
  163. package/dist/style/style.js +269 -0
  164. package/dist/style/style.js.map +1 -0
  165. package/dist/style/stylesheet.d.ts +11 -0
  166. package/dist/style/stylesheet.js +174 -0
  167. package/dist/style/stylesheet.js.map +1 -0
  168. package/dist/test.d.ts +2 -0
  169. package/dist/test.js +2 -0
  170. package/dist/test.js.map +1 -0
  171. package/dist/theme/contract.d.ts +259 -0
  172. package/dist/theme/contract.js +131 -0
  173. package/dist/theme/contract.js.map +1 -0
  174. package/dist/theme/glyph-contract.d.ts +13 -0
  175. package/dist/theme/glyph-contract.js +34 -0
  176. package/dist/theme/glyph-contract.js.map +1 -0
  177. package/dist/theme/presets/rmx-01/glyphs.d.ts +2 -0
  178. package/dist/theme/presets/rmx-01/glyphs.js +252 -0
  179. package/dist/theme/presets/rmx-01/glyphs.js.map +1 -0
  180. package/dist/theme/presets/rmx-01/index.d.ts +2 -0
  181. package/dist/theme/presets/rmx-01/index.js +119 -0
  182. package/dist/theme/presets/rmx-01/index.js.map +1 -0
  183. package/dist/theme/runtime.d.ts +2 -0
  184. package/dist/theme/runtime.js +81 -0
  185. package/dist/theme/runtime.js.map +1 -0
  186. package/dist/theme/theme.d.ts +6 -0
  187. package/dist/theme/theme.js +5 -0
  188. package/dist/theme/theme.js.map +1 -0
  189. package/dist/utils/flash-attribute.d.ts +1 -0
  190. package/dist/utils/flash-attribute.js +11 -0
  191. package/dist/utils/flash-attribute.js.map +1 -0
  192. package/dist/utils/scroll-lock.d.ts +3 -0
  193. package/dist/utils/scroll-lock.js +69 -0
  194. package/dist/utils/scroll-lock.js.map +1 -0
  195. package/dist/utils/wait-for-css-transition.d.ts +1 -0
  196. package/dist/utils/wait-for-css-transition.js +58 -0
  197. package/dist/utils/wait-for-css-transition.js.map +1 -0
  198. package/dist/utils/wait.d.ts +1 -0
  199. package/dist/utils/wait.js +6 -0
  200. package/dist/utils/wait.js.map +1 -0
  201. package/package.json +125 -5
  202. package/src/animation/animate-layout-mixin.ts +266 -0
  203. package/src/animation/animate-mixins.ts +292 -0
  204. package/src/animation/index.ts +6 -0
  205. package/src/animation/spring.ts +299 -0
  206. package/src/animation/tween.ts +134 -0
  207. package/src/components/accordion/accordion.tsx +565 -0
  208. package/src/components/anchor/anchor.ts +904 -0
  209. package/src/components/breadcrumbs/breadcrumbs.tsx +124 -0
  210. package/src/components/button/README.md +44 -0
  211. package/src/components/button/button.tsx +188 -0
  212. package/src/components/combobox/README.md +145 -0
  213. package/src/components/combobox/combobox.tsx +1014 -0
  214. package/src/components/glyph/glyph.tsx +110 -0
  215. package/src/components/listbox/listbox.ts +447 -0
  216. package/src/components/menu/hover-aim.ts +428 -0
  217. package/src/components/menu/menu.tsx +1547 -0
  218. package/src/components/popover/README.md +122 -0
  219. package/src/components/popover/popover.ts +201 -0
  220. package/src/components/select/select.tsx +585 -0
  221. package/src/components/separator/separator.ts +19 -0
  222. package/src/components/tabs/README.md +105 -0
  223. package/src/components/tabs/tabs.tsx +499 -0
  224. package/src/index.ts +72 -0
  225. package/src/interactions/keydown/keydown.ts +14 -0
  226. package/src/interactions/outside-click/outside-click-mixin.ts +55 -0
  227. package/src/interactions/typeahead/typeahead-mixin.ts +89 -0
  228. package/src/jsx-runtime.ts +2 -0
  229. package/src/runtime/client-entries.ts +137 -0
  230. package/src/runtime/component.ts +402 -0
  231. package/src/runtime/create-element.ts +38 -0
  232. package/src/runtime/diff-dom.ts +404 -0
  233. package/src/runtime/diff-props.ts +209 -0
  234. package/src/runtime/document-state.ts +130 -0
  235. package/src/runtime/dom.ts +3971 -0
  236. package/src/runtime/error-event.ts +26 -0
  237. package/src/runtime/event-listeners.ts +171 -0
  238. package/src/runtime/frame.ts +1211 -0
  239. package/src/runtime/invariant.ts +31 -0
  240. package/src/runtime/jsx.ts +398 -0
  241. package/src/runtime/mixins/attrs-mixin.ts +35 -0
  242. package/src/runtime/mixins/link-mixin.ts +131 -0
  243. package/src/runtime/mixins/mixin.ts +908 -0
  244. package/src/runtime/mixins/on-mixin.ts +89 -0
  245. package/src/runtime/mixins/ref-mixin.ts +32 -0
  246. package/src/runtime/navigation.ts +136 -0
  247. package/src/runtime/reconcile.ts +1722 -0
  248. package/src/runtime/render.ts +101 -0
  249. package/src/runtime/run.ts +100 -0
  250. package/src/runtime/scheduler.ts +262 -0
  251. package/src/runtime/svg-attributes.ts +133 -0
  252. package/src/runtime/to-vnode.ts +50 -0
  253. package/src/runtime/typed-event-target.ts +67 -0
  254. package/src/runtime/vdom.ts +295 -0
  255. package/src/runtime/vnode.ts +137 -0
  256. package/src/server/stream.ts +1342 -0
  257. package/src/style/css-mixin.ts +82 -0
  258. package/src/style/index.ts +10 -0
  259. package/src/style/style.ts +312 -0
  260. package/src/style/stylesheet.ts +209 -0
  261. package/src/test/setup.ts +7 -0
  262. package/src/test/utils.ts +45 -0
  263. package/src/test.ts +2 -0
  264. package/src/theme/contract.ts +183 -0
  265. package/src/theme/glyph-contract.ts +57 -0
  266. package/src/theme/presets/rmx-01/glyphs.tsx +308 -0
  267. package/src/theme/presets/rmx-01/index.ts +121 -0
  268. package/src/theme/runtime.ts +116 -0
  269. package/src/theme/theme.ts +15 -0
  270. package/src/utils/flash-attribute.ts +11 -0
  271. package/src/utils/scroll-lock.ts +97 -0
  272. package/src/utils/wait-for-css-transition.ts +66 -0
  273. package/src/utils/wait.ts +5 -0
@@ -0,0 +1,1007 @@
1
+ import { Fragment, createComponent, createFrameHandle, Frame } from "../runtime/component.js";
2
+ import { isEntry } from "../runtime/client-entries.js";
3
+ import { normalizeSvgAttribute } from "../runtime/svg-attributes.js";
4
+ export function createVNode(type, props, key) {
5
+ return { type, props, key };
6
+ }
7
+ const SELF_CLOSING_TAGS = new Set([
8
+ 'area',
9
+ 'base',
10
+ 'br',
11
+ 'col',
12
+ 'embed',
13
+ 'hr',
14
+ 'img',
15
+ 'input',
16
+ 'link',
17
+ 'meta',
18
+ 'param',
19
+ 'source',
20
+ 'track',
21
+ 'wbr',
22
+ ]);
23
+ const DEFAULT_STYLE_LAYER = 'rmx';
24
+ function getStyleLayerName(selector, layer = DEFAULT_STYLE_LAYER) {
25
+ return `${layer}.${selector}`;
26
+ }
27
+ const NUMERIC_CSS_PROPS = new Set([
28
+ 'z-index',
29
+ 'opacity',
30
+ 'flex-grow',
31
+ 'flex-shrink',
32
+ 'flex-order',
33
+ 'grid-area',
34
+ 'grid-row',
35
+ 'grid-column',
36
+ 'font-weight',
37
+ 'line-height',
38
+ 'order',
39
+ 'orphans',
40
+ 'widows',
41
+ 'zoom',
42
+ 'columns',
43
+ 'column-count',
44
+ ]);
45
+ const FRAMEWORK_PROPS = new Set(['children', 'innerHTML', 'on', 'key', 'mix']);
46
+ const ssrSignal = Object.freeze({
47
+ get aborted() {
48
+ return false;
49
+ },
50
+ get reason() {
51
+ return undefined;
52
+ },
53
+ get onabort() {
54
+ return null;
55
+ },
56
+ set onabort(_) { },
57
+ addEventListener(_type, _listener, _options) { },
58
+ removeEventListener(_type, _listener, _options) { },
59
+ dispatchEvent(_event) {
60
+ return true;
61
+ },
62
+ throwIfAborted() { },
63
+ });
64
+ /**
65
+ * Renders a node tree to a streaming HTML response body.
66
+ *
67
+ * @param node Node tree to render.
68
+ * @param options Stream rendering options.
69
+ * @returns A readable byte stream of HTML.
70
+ */
71
+ export function renderToStream(node, options) {
72
+ let encoder = new TextEncoder();
73
+ let onError = options?.onError ?? ((error) => console.error(error));
74
+ let currentFrameSrc = normalizeFrameSrc(options?.frameSrc ?? options?.topFrameSrc);
75
+ let topFrameSrc = normalizeFrameSrc(options?.topFrameSrc ?? currentFrameSrc);
76
+ let rootFrameState = createSsrFrameState(currentFrameSrc, topFrameSrc);
77
+ let context = {
78
+ insideSvg: false,
79
+ onError,
80
+ resolveFrame: options?.resolveFrame ?? defaultResolveFrame,
81
+ styleCache: new Map(),
82
+ emittedStyles: new Set(),
83
+ pendingFrames: [],
84
+ hydrationData: new Map(),
85
+ unresolvedHydrationData: new Map(),
86
+ frameData: new Map(),
87
+ blockingFrameTails: [],
88
+ serverIdScope: crypto.randomUUID().slice(0, 8),
89
+ serverIdCounter: 0,
90
+ };
91
+ return new ReadableStream({
92
+ async start(controller) {
93
+ try {
94
+ let root = buildSegment(node, context, rootFrameState);
95
+ await resolveBlocking(root);
96
+ await resolveClientEntries(context, options?.resolveClientEntry);
97
+ validateClientEntriesForHydration(context);
98
+ let html = serializeSegment(root);
99
+ let finalHtml = finalizeHtml(html, context);
100
+ let bytes = encoder.encode(finalHtml);
101
+ controller.enqueue(bytes);
102
+ // If we have any tails from blocking frame streams, stream them now.
103
+ // These contain nested non-blocking frame templates (or other follow-up chunks)
104
+ // that must come after the initial document chunk.
105
+ let tailPromise = context.blockingFrameTails.length > 0
106
+ ? streamByteStreams(context.blockingFrameTails, controller, context.onError)
107
+ : Promise.resolve();
108
+ // If we have pending non-blocking frames, stream them as they resolve
109
+ let pendingPromise = context.pendingFrames.length > 0
110
+ ? streamPendingFrames(context, controller, encoder)
111
+ : Promise.resolve();
112
+ await Promise.all([tailPromise, pendingPromise]);
113
+ controller.close();
114
+ }
115
+ catch (error) {
116
+ onError(error);
117
+ controller.error(error);
118
+ }
119
+ },
120
+ });
121
+ }
122
+ function defaultResolveFrame() {
123
+ throw new Error('No resolveFrame provided');
124
+ }
125
+ function normalizeFrameSrc(value) {
126
+ return value == null ? '' : String(value);
127
+ }
128
+ function createSsrFrameState(frameSrc, topFrameSrc = frameSrc) {
129
+ let topFrame = createFrameHandle({ src: topFrameSrc });
130
+ let frame = frameSrc === topFrameSrc ? topFrame : createFrameHandle({ src: frameSrc });
131
+ return { frame, topFrame };
132
+ }
133
+ function getResolveFrameContext(frameState) {
134
+ return {
135
+ currentFrameSrc: frameState.frame.src,
136
+ topFrameSrc: frameState.topFrame.src,
137
+ };
138
+ }
139
+ function randomId(prefix) {
140
+ return prefix + crypto.randomUUID().slice(0, 8);
141
+ }
142
+ function createServerComponentId(context) {
143
+ context.serverIdCounter++;
144
+ return `s${context.serverIdScope}-${context.serverIdCounter}`;
145
+ }
146
+ async function splitFirstChunk(stream) {
147
+ let reader = stream.getReader();
148
+ let { value, done } = await reader.read();
149
+ if (done || !value) {
150
+ reader.releaseLock();
151
+ return {
152
+ first: new Uint8Array(),
153
+ tail: new ReadableStream({
154
+ start(controller) {
155
+ controller.close();
156
+ },
157
+ }),
158
+ };
159
+ }
160
+ let released = false;
161
+ function release() {
162
+ if (released)
163
+ return;
164
+ released = true;
165
+ try {
166
+ reader.releaseLock();
167
+ }
168
+ catch {
169
+ // ignore
170
+ }
171
+ }
172
+ let tail = new ReadableStream({
173
+ async pull(controller) {
174
+ let next = await reader.read();
175
+ if (next.done) {
176
+ controller.close();
177
+ release();
178
+ return;
179
+ }
180
+ controller.enqueue(next.value);
181
+ },
182
+ cancel(reason) {
183
+ release();
184
+ return reader.cancel(reason);
185
+ },
186
+ });
187
+ return { first: value, tail };
188
+ }
189
+ async function resolveFrameHtml(input) {
190
+ if (typeof input === 'string')
191
+ return { html: input };
192
+ let decoder = new TextDecoder();
193
+ let { first, tail } = await splitFirstChunk(input);
194
+ return { html: decoder.decode(first), tail };
195
+ }
196
+ function isRemixElement(node) {
197
+ return typeof node === 'object' && node !== null && '$rmx' in node;
198
+ }
199
+ function staticSeg(html) {
200
+ return { kind: 'static', html };
201
+ }
202
+ function compositeSeg(parts) {
203
+ return { kind: 'composite', parts };
204
+ }
205
+ function buildSegment(node, context, frameState) {
206
+ if (typeof node === 'string' || typeof node === 'number' || typeof node === 'bigint') {
207
+ return staticSeg(escapeTextContent(String(node)));
208
+ }
209
+ if (node === null || node === undefined || typeof node === 'boolean') {
210
+ return staticSeg('');
211
+ }
212
+ if (Array.isArray(node)) {
213
+ return compositeSeg(node.map((child) => buildSegment(child, context, frameState)));
214
+ }
215
+ if (isRemixElement(node)) {
216
+ let type = node.type;
217
+ let props = node.props;
218
+ if (type === Fragment) {
219
+ let children = props.children;
220
+ return children != null ? buildSegment(children, context, frameState) : staticSeg('');
221
+ }
222
+ if (typeof type === 'string') {
223
+ let tag = type;
224
+ if (tag === 'html') {
225
+ return buildElementSegment(tag, props, context, frameState);
226
+ }
227
+ if (tag === 'head') {
228
+ return buildHeadElementSegment(tag, props, context, frameState);
229
+ }
230
+ return buildElementSegment(tag, props, context, frameState);
231
+ }
232
+ if (typeof type === 'function') {
233
+ if (type === Frame) {
234
+ return buildFrameSegment(props, context, frameState);
235
+ }
236
+ if (isEntry(type)) {
237
+ return buildEntrySegment(type, props, context, frameState);
238
+ }
239
+ return buildComponentSegment(type, props, context, createServerComponentId(context), frameState);
240
+ }
241
+ }
242
+ return staticSeg('');
243
+ }
244
+ function buildFrameSegment(props, context, frameState) {
245
+ let frameId = randomId('f');
246
+ // Store frame data in context for aggregation
247
+ context.frameData.set(frameId, {
248
+ status: props.fallback ? 'pending' : 'resolved',
249
+ name: props.name,
250
+ src: props.src,
251
+ });
252
+ let seg = {
253
+ kind: 'frame',
254
+ frameId,
255
+ content: null,
256
+ };
257
+ let resolveFrameContext = getResolveFrameContext(frameState);
258
+ let nonBlocking = !!props.fallback;
259
+ if (nonBlocking) {
260
+ seg.content = buildSegment(props.fallback, context, frameState);
261
+ let framePromise = Promise.resolve(context.resolveFrame(props.src, props.name, resolveFrameContext)).then(async (resolved) => resolveFrameHtml(resolved));
262
+ context.pendingFrames.push({ frameId, promise: framePromise });
263
+ }
264
+ else {
265
+ seg.pending = Promise.resolve(context.resolveFrame(props.src, props.name, resolveFrameContext)).then(async (resolved) => {
266
+ let { html, tail } = await resolveFrameHtml(resolved);
267
+ seg.content = staticSeg(html);
268
+ if (tail) {
269
+ context.blockingFrameTails.push(tail);
270
+ }
271
+ });
272
+ }
273
+ return seg;
274
+ }
275
+ function buildElementSegment(tag, props, context, frameState) {
276
+ let mixedProps = resolveSsrMixedProps(tag, props, context, frameState);
277
+ let processedProps = processStyleProps(mixedProps);
278
+ // Determine namespace context for the current element and its children
279
+ let currentIsSvg = context.insideSvg || tag === 'svg';
280
+ let attrs = renderAttributes(processedProps, currentIsSvg);
281
+ if (SELF_CLOSING_TAGS.has(tag)) {
282
+ return staticSeg(`<${tag}${attrs} />`);
283
+ }
284
+ if (props.innerHTML) {
285
+ return staticSeg(`<${tag}${attrs}>${props.innerHTML}</${tag}>`);
286
+ }
287
+ let open = staticSeg(`<${tag}${attrs}>`);
288
+ // Adjust svg context for children: foreignObject switches back to HTML
289
+ let previousInsideSvg = context.insideSvg;
290
+ context.insideSvg = tag === 'foreignObject' ? false : currentIsSvg;
291
+ let children = props.children != null ? buildSegment(props.children, context, frameState) : staticSeg('');
292
+ context.insideSvg = previousInsideSvg;
293
+ let close = staticSeg(`</${tag}>`);
294
+ return compositeSeg([open, children, close]);
295
+ }
296
+ function buildHeadElementSegment(tag, props, context, frameState) {
297
+ let processedProps = processStyleProps(props);
298
+ let attrs = renderAttributes(processedProps, false);
299
+ let open = staticSeg(`<${tag}${attrs}>`);
300
+ let children = props.children != null ? buildSegment(props.children, context, frameState) : staticSeg('');
301
+ let close = staticSeg(`</${tag}>`);
302
+ return compositeSeg([open, children, close]);
303
+ }
304
+ function renderAttributes(props, isSvg) {
305
+ let attrs = '';
306
+ for (let key in props) {
307
+ if (FRAMEWORK_PROPS.has(key))
308
+ continue;
309
+ let value = props[key];
310
+ if (value === undefined || value === null || value === false)
311
+ continue;
312
+ let attrName = transformAttributeName(key, isSvg);
313
+ if (value === true) {
314
+ attrs += ` ${attrName}`;
315
+ }
316
+ else {
317
+ attrs += ` ${attrName}="${escapeHtml(String(value))}"`;
318
+ }
319
+ }
320
+ return attrs;
321
+ }
322
+ function resolveSsrMixedProps(hostType, initialProps, context, frameState) {
323
+ let descriptors = resolveSsrMixDescriptors(initialProps);
324
+ if (descriptors.length === 0)
325
+ return initialProps;
326
+ let composedProps = withoutSsrMix(initialProps);
327
+ let mixinProps = withoutSsrMixinTreeProps(composedProps);
328
+ let maxDescriptors = 1024;
329
+ for (let index = 0; index < descriptors.length && index < maxDescriptors; index++) {
330
+ let descriptor = descriptors[index];
331
+ let runner = resolveSsrMixinRunner(hostType, descriptor, context, frameState);
332
+ if (!runner)
333
+ continue;
334
+ let result;
335
+ try {
336
+ result = runner(...descriptor.args, mixinProps);
337
+ }
338
+ catch (error) {
339
+ console.error(error);
340
+ continue;
341
+ }
342
+ if (!result)
343
+ continue;
344
+ if (isSsrMixinElement(result))
345
+ continue;
346
+ let returnedDescriptors = resolveReturnedSsrMixDescriptors(result);
347
+ if (returnedDescriptors) {
348
+ for (let returned of returnedDescriptors)
349
+ descriptors.push(returned);
350
+ continue;
351
+ }
352
+ if (!isRemixElement(result)) {
353
+ console.error(new Error('mixins must return a remix element'));
354
+ continue;
355
+ }
356
+ let remixResult = result;
357
+ let resultType = typeof remixResult.type === 'string'
358
+ ? remixResult.type
359
+ : isSsrMixinElement(remixResult.type)
360
+ ? remixResult.type.__rmxMixinElementType
361
+ : null;
362
+ if (resultType !== hostType) {
363
+ console.error(new Error('mixins must return an element with the same host type'));
364
+ continue;
365
+ }
366
+ if (remixResult.type !== resultType) {
367
+ remixResult = { ...remixResult, type: resultType };
368
+ }
369
+ let nextProps = sanitizeReturnedSsrMixinProps(remixResult.props);
370
+ let nestedDescriptors = resolveSsrMixDescriptors(nextProps);
371
+ for (let nested of nestedDescriptors)
372
+ descriptors.push(nested);
373
+ composedProps = { ...composedProps, ...withoutSsrMix(nextProps) };
374
+ mixinProps = withoutSsrMixinTreeProps(composedProps);
375
+ }
376
+ let nextMix = initialProps.mix;
377
+ return {
378
+ ...composedProps,
379
+ ...(nextMix === undefined ? {} : { mix: nextMix }),
380
+ };
381
+ }
382
+ function resolveSsrMixinRunner(hostType, descriptor, context, frameState) {
383
+ if (typeof descriptor.type !== 'function')
384
+ return null;
385
+ try {
386
+ let handle = createSsrMixinHandle(hostType, descriptor, context, frameState);
387
+ let runner = descriptor.type(handle, hostType);
388
+ if (typeof runner !== 'function')
389
+ return null;
390
+ return runner;
391
+ }
392
+ catch (error) {
393
+ console.error(error);
394
+ return null;
395
+ }
396
+ }
397
+ function createSsrMixinHandle(hostType, _descriptor, context, frameState) {
398
+ let element = ((handle) => () => ({
399
+ $rmx: true,
400
+ type: hostType,
401
+ key: null,
402
+ props: handle.props,
403
+ }));
404
+ element.__rmxMixinElementType = hostType;
405
+ return {
406
+ id: 'ssr-mixin',
407
+ context: {
408
+ get(providerType) {
409
+ if (typeof providerType !== 'function') {
410
+ return undefined;
411
+ }
412
+ let current = context.parentVNode;
413
+ while (current) {
414
+ if (current.type === providerType) {
415
+ let providerHandle = current._handle;
416
+ if (providerHandle) {
417
+ return providerHandle.getContextValue();
418
+ }
419
+ }
420
+ current = current._parent;
421
+ }
422
+ return undefined;
423
+ },
424
+ },
425
+ frame: createFrameHandle({
426
+ src: frameState.frame.src,
427
+ $runtime: {
428
+ styleCache: context.styleCache,
429
+ },
430
+ }),
431
+ element,
432
+ signal: ssrSignal,
433
+ update: () => {
434
+ throw new Error('handle.update() is not available during SSR.');
435
+ },
436
+ queueTask: () => { },
437
+ on: () => { },
438
+ addEventListener: () => { },
439
+ removeEventListener: () => { },
440
+ dispatchEvent: () => true,
441
+ };
442
+ }
443
+ function resolveSsrMixDescriptors(props) {
444
+ let mix = props.mix;
445
+ if (!mix)
446
+ return [];
447
+ if (Array.isArray(mix)) {
448
+ if (mix.length === 0)
449
+ return [];
450
+ return mix.filter(Boolean);
451
+ }
452
+ return [mix];
453
+ }
454
+ function withoutSsrMix(props) {
455
+ if (!('mix' in props))
456
+ return props;
457
+ let output = { ...props };
458
+ delete output.mix;
459
+ return output;
460
+ }
461
+ function withoutSsrMixinTreeProps(props) {
462
+ if (!('children' in props) && !('innerHTML' in props))
463
+ return props;
464
+ let output = { ...props };
465
+ delete output.children;
466
+ delete output.innerHTML;
467
+ return output;
468
+ }
469
+ function sanitizeReturnedSsrMixinProps(props) {
470
+ if (!('children' in props) && !('innerHTML' in props))
471
+ return props;
472
+ console.error(new Error('mixins must not return children or innerHTML'));
473
+ return withoutSsrMixinTreeProps(props);
474
+ }
475
+ function resolveReturnedSsrMixDescriptors(value) {
476
+ let descriptors = [];
477
+ if (!collectReturnedSsrMixDescriptors(value, descriptors)) {
478
+ return null;
479
+ }
480
+ return descriptors;
481
+ }
482
+ function collectReturnedSsrMixDescriptors(value, output) {
483
+ if (!value) {
484
+ return true;
485
+ }
486
+ if (Array.isArray(value)) {
487
+ for (let item of value) {
488
+ if (!collectReturnedSsrMixDescriptors(item, output)) {
489
+ return false;
490
+ }
491
+ }
492
+ return true;
493
+ }
494
+ if (!isSsrMixinDescriptor(value)) {
495
+ return false;
496
+ }
497
+ output.push(value);
498
+ return true;
499
+ }
500
+ function isSsrMixinElement(value) {
501
+ if (typeof value !== 'function')
502
+ return false;
503
+ return '__rmxMixinElementType' in value;
504
+ }
505
+ function isSsrMixinDescriptor(value) {
506
+ if (!value || typeof value !== 'object' || isRemixElement(value)) {
507
+ return false;
508
+ }
509
+ let descriptor = value;
510
+ return typeof descriptor.type === 'function' && Array.isArray(descriptor.args);
511
+ }
512
+ function buildComponentSegment(type, props, context, componentId, frameState) {
513
+ let vnode = createVNode(type, props);
514
+ if (context.parentVNode) {
515
+ vnode._parent = context.parentVNode;
516
+ }
517
+ let handle = createComponent({
518
+ id: componentId,
519
+ type: type,
520
+ frame: frameState.frame,
521
+ signal: ssrSignal,
522
+ getContext(providerType) {
523
+ let current = vnode._parent;
524
+ while (current) {
525
+ if (current.type === providerType) {
526
+ let providerHandle = current._handle;
527
+ // TODO: need better vnode types to avoid defensive checks
528
+ if (providerHandle) {
529
+ return providerHandle.getContextValue();
530
+ }
531
+ }
532
+ current = current._parent;
533
+ }
534
+ return undefined;
535
+ },
536
+ getFrameByName() {
537
+ return undefined;
538
+ },
539
+ getTopFrame() {
540
+ return frameState.topFrame;
541
+ },
542
+ });
543
+ vnode._handle = handle;
544
+ let [renderedNode] = handle.render(props);
545
+ let childContext = { ...context, parentVNode: vnode };
546
+ return buildSegment(renderedNode, childContext, frameState);
547
+ }
548
+ function createHydrationPropsReplacer(context, frameState) {
549
+ function unwrapNode(node) {
550
+ if (node === null || node === undefined || typeof node === 'boolean')
551
+ return node;
552
+ if (typeof node === 'string' || typeof node === 'number' || typeof node === 'bigint') {
553
+ return node;
554
+ }
555
+ if (Array.isArray(node)) {
556
+ return node.map((child) => unwrapNode(child));
557
+ }
558
+ if (isRemixElement(node)) {
559
+ return unwrapElement(node);
560
+ }
561
+ return node;
562
+ }
563
+ function unwrapElement(element) {
564
+ let type = element.type;
565
+ let props = element.props;
566
+ // Preserve Frame semantics through serialized props by emitting
567
+ // a dedicated descriptor that can be revived on the client.
568
+ if (type === Frame) {
569
+ return {
570
+ $rmxFrame: true,
571
+ props: transformProps(props),
572
+ key: element.key,
573
+ };
574
+ }
575
+ // If it's a DOM tag, return a serializable shape with transformed props
576
+ if (typeof type === 'string') {
577
+ return { $rmx: true, type, props: transformProps(props) };
578
+ }
579
+ // Component function: render synchronously, then unwrap its result
580
+ if (typeof type === 'function') {
581
+ let vnode = createVNode(type, props);
582
+ if (context.parentVNode) {
583
+ vnode._parent = context.parentVNode;
584
+ }
585
+ let handle = createComponent({
586
+ id: 'SERIALIZED',
587
+ type: type,
588
+ frame: frameState.frame,
589
+ signal: ssrSignal,
590
+ getContext(providerType) {
591
+ let current = vnode._parent;
592
+ while (current) {
593
+ if (current.type === providerType) {
594
+ let providerHandle = current._handle;
595
+ if (providerHandle) {
596
+ return providerHandle.getContextValue();
597
+ }
598
+ }
599
+ current = current._parent;
600
+ }
601
+ return undefined;
602
+ },
603
+ getFrameByName() {
604
+ return undefined;
605
+ },
606
+ getTopFrame() {
607
+ return frameState.topFrame;
608
+ },
609
+ });
610
+ vnode._handle = handle;
611
+ let [renderedNode] = handle.render(props);
612
+ return unwrapNode(renderedNode);
613
+ }
614
+ return null;
615
+ }
616
+ function transformProps(input) {
617
+ let out = {};
618
+ for (let key in input) {
619
+ let value = input[key];
620
+ if (key === 'children') {
621
+ out[key] = unwrapNode(value);
622
+ }
623
+ else {
624
+ if (isRemixElement(value)) {
625
+ out[key] = unwrapNode(value);
626
+ }
627
+ else if (Array.isArray(value)) {
628
+ out[key] = value.map((v) => unwrapNode(v));
629
+ }
630
+ else {
631
+ out[key] = value;
632
+ }
633
+ }
634
+ }
635
+ return out;
636
+ }
637
+ return function replacer(_key, value) {
638
+ if (isRemixElement(value)) {
639
+ return unwrapElement(value);
640
+ }
641
+ if (Array.isArray(value)) {
642
+ return value.map((v) => unwrapNode(v));
643
+ }
644
+ return value;
645
+ };
646
+ }
647
+ function buildEntrySegment(type, props, context, frameState) {
648
+ let instanceId = randomId('h');
649
+ let rendered = buildComponentSegment(type, props, context, instanceId, frameState);
650
+ // Store hydration data in context for aggregation
651
+ let replacer = createHydrationPropsReplacer(context, frameState);
652
+ context.unresolvedHydrationData.set(instanceId, {
653
+ entryId: type.$entryId,
654
+ component: type,
655
+ props: JSON.parse(JSON.stringify(props, replacer)),
656
+ });
657
+ let start = staticSeg(`<!-- rmx:h:${instanceId} -->`);
658
+ let end = staticSeg('<!-- /rmx:h -->');
659
+ return compositeSeg([start, rendered, end]);
660
+ }
661
+ function resolveDefaultClientEntry(entryId, component) {
662
+ let fallbackExportName = component.name || '';
663
+ let hashIndex = entryId.lastIndexOf('#');
664
+ if (hashIndex === -1 && fallbackExportName) {
665
+ return {
666
+ exportName: fallbackExportName,
667
+ href: entryId,
668
+ };
669
+ }
670
+ if (hashIndex !== -1) {
671
+ let exportName = entryId.slice(hashIndex + 1) || fallbackExportName;
672
+ if (exportName) {
673
+ return {
674
+ exportName,
675
+ href: entryId.slice(0, hashIndex),
676
+ };
677
+ }
678
+ }
679
+ throw new Error(`clientEntry() requires either an export name in the entry ID (e.g., "/js/module.js#ComponentName"), a named component function, or a resolveClientEntry hook that resolves one. Received "${entryId}".`);
680
+ }
681
+ async function resolveClientEntries(context, resolveClientEntry) {
682
+ if (context.unresolvedHydrationData.size === 0)
683
+ return;
684
+ let resolvedEntries = new Map();
685
+ for (let [hydrationId, unresolvedHydrationData] of context.unresolvedHydrationData) {
686
+ let { entryId, component, props } = unresolvedHydrationData;
687
+ let resolvedEntry = resolvedEntries.get(entryId);
688
+ if (!resolvedEntry) {
689
+ resolvedEntry = resolveClientEntry
690
+ ? await Promise.resolve(resolveClientEntry(entryId, component))
691
+ : resolveDefaultClientEntry(entryId, component);
692
+ validateResolvedClientEntry(entryId, resolvedEntry);
693
+ resolvedEntries.set(entryId, resolvedEntry);
694
+ }
695
+ context.hydrationData.set(hydrationId, {
696
+ exportName: resolvedEntry.exportName,
697
+ moduleUrl: resolvedEntry.href,
698
+ props,
699
+ });
700
+ }
701
+ context.unresolvedHydrationData.clear();
702
+ }
703
+ function validateResolvedClientEntry(entryId, resolvedEntry) {
704
+ if (!resolvedEntry || typeof resolvedEntry !== 'object') {
705
+ throw new Error(`resolveClientEntry must return an object with href and exportName. Received "${entryId}".`);
706
+ }
707
+ if (!resolvedEntry.href) {
708
+ throw new Error(`resolveClientEntry must return a non-empty href. Received "${entryId}".`);
709
+ }
710
+ if (!resolvedEntry.exportName) {
711
+ throw new Error(`resolveClientEntry must return a non-empty exportName. Received "${entryId}".`);
712
+ }
713
+ }
714
+ function validateClientEntriesForHydration(context) {
715
+ if (context.unresolvedHydrationData.size > 0) {
716
+ let [hydrationId, unresolvedHydrationData] = context.unresolvedHydrationData.entries().next()
717
+ .value;
718
+ throw new Error(`Client entry was not resolved for hydration. Received "${unresolvedHydrationData.entryId}" (${hydrationId}).`);
719
+ }
720
+ }
721
+ // Resolve all blocking frame content once
722
+ async function resolveBlocking(segment) {
723
+ if (segment.kind === 'frame') {
724
+ if (segment.pending) {
725
+ await segment.pending;
726
+ segment.pending = undefined;
727
+ }
728
+ if (segment.content)
729
+ await resolveBlocking(segment.content);
730
+ return;
731
+ }
732
+ if (segment.kind === 'composite') {
733
+ for (let part of segment.parts) {
734
+ await resolveBlocking(part);
735
+ }
736
+ }
737
+ }
738
+ // Serialize the segment tree to HTML
739
+ function serializeSegment(seg) {
740
+ if (seg.kind === 'static')
741
+ return seg.html;
742
+ if (seg.kind === 'composite')
743
+ return seg.parts.map(serializeSegment).join('');
744
+ // frame
745
+ let inner = seg.content ? serializeSegment(seg.content) : '';
746
+ let start = `<!-- rmx:f:${seg.frameId} -->`;
747
+ let end = `<!-- /rmx:f -->`;
748
+ return start + inner + end;
749
+ }
750
+ function escapeHtml(str) {
751
+ return str
752
+ .replace(/&/g, '&amp;')
753
+ .replace(/</g, '&lt;')
754
+ .replace(/>/g, '&gt;')
755
+ .replace(/"/g, '&quot;')
756
+ .replace(/'/g, '&#39;');
757
+ }
758
+ function escapeTextContent(str) {
759
+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
760
+ }
761
+ function escapeTemplateContent(html) {
762
+ return html.replace(/<\/template/gi, '<\\/template');
763
+ }
764
+ function transformAttributeName(name, isSvg) {
765
+ // aria-/data- pass through
766
+ if (name.startsWith('aria-') || name.startsWith('data-'))
767
+ return name;
768
+ // HTML mappings
769
+ if (name === 'className')
770
+ return 'class';
771
+ if (!isSvg) {
772
+ if (name === 'htmlFor')
773
+ return 'for';
774
+ if (name === 'tabIndex')
775
+ return 'tabindex';
776
+ if (name === 'acceptCharset')
777
+ return 'accept-charset';
778
+ if (name === 'httpEquiv')
779
+ return 'http-equiv';
780
+ return name.toLowerCase();
781
+ }
782
+ return normalizeSvgAttribute(name).attr;
783
+ }
784
+ function finalizeHtml(html, context) {
785
+ let hasHtmlRoot = html.trimStart().toLowerCase().startsWith('<html');
786
+ let styles = collectStyleTags(context);
787
+ if (styles) {
788
+ let headContent = styles;
789
+ if (hasHtmlRoot) {
790
+ // For HTML root, inject into existing head or create one
791
+ let headCloseIndex = html.indexOf('</head>');
792
+ if (headCloseIndex !== -1) {
793
+ // Inject before existing </head>
794
+ html = html.slice(0, headCloseIndex) + headContent + html.slice(headCloseIndex);
795
+ }
796
+ else {
797
+ // No existing head, inject after <html>
798
+ let htmlOpenMatch = html.match(/<html[^>]*>/);
799
+ if (htmlOpenMatch) {
800
+ let insertIndex = htmlOpenMatch.index + htmlOpenMatch[0].length;
801
+ html =
802
+ html.slice(0, insertIndex) + `<head>${headContent}</head>` + html.slice(insertIndex);
803
+ }
804
+ }
805
+ }
806
+ else {
807
+ // No HTML root, prepend head
808
+ html = `<head>${headContent}</head>${html}`;
809
+ }
810
+ }
811
+ html = dedupeServerStyleTagsInHtml(html, context.emittedStyles);
812
+ // Append aggregated hydration/frame data script at the end
813
+ let rmxData = buildRmxDataScript(context);
814
+ if (rmxData) {
815
+ if (hasHtmlRoot) {
816
+ // Insert before </body> if present, otherwise before </html>
817
+ let bodyCloseIndex = html.indexOf('</body>');
818
+ if (bodyCloseIndex !== -1) {
819
+ html = html.slice(0, bodyCloseIndex) + rmxData + html.slice(bodyCloseIndex);
820
+ }
821
+ else {
822
+ let htmlCloseIndex = html.indexOf('</html>');
823
+ if (htmlCloseIndex !== -1) {
824
+ html = html.slice(0, htmlCloseIndex) + rmxData + html.slice(htmlCloseIndex);
825
+ }
826
+ else {
827
+ html += rmxData;
828
+ }
829
+ }
830
+ }
831
+ else {
832
+ html += rmxData;
833
+ }
834
+ }
835
+ return html;
836
+ }
837
+ function processStyleProps(props) {
838
+ let processedProps = { ...props };
839
+ let classAttr = typeof props.class === 'string' ? props.class : '';
840
+ let className = typeof props.className === 'string' ? props.className : '';
841
+ let mergedClassName = [classAttr, className].filter(Boolean).join(' ');
842
+ if (mergedClassName) {
843
+ processedProps.className = mergedClassName;
844
+ delete processedProps.class;
845
+ }
846
+ if (typeof props.style === 'object') {
847
+ processedProps.style = serializeStyleObject(props.style);
848
+ }
849
+ return processedProps;
850
+ }
851
+ function collectStyleTags(context) {
852
+ if (context.styleCache.size === 0)
853
+ return '';
854
+ let tags = [];
855
+ for (let { selector, css } of context.styleCache.values()) {
856
+ let tag = renderStyleTag(selector, css);
857
+ if (tag)
858
+ tags.push(tag);
859
+ }
860
+ return tags.join('');
861
+ }
862
+ function wrapStyleForLayer(selector, css, layer = DEFAULT_STYLE_LAYER) {
863
+ let trimmed = css.trim();
864
+ if (!trimmed)
865
+ return '';
866
+ return `@layer ${getStyleLayerName(selector, layer)} { ${trimmed} }`;
867
+ }
868
+ function renderStyleTag(selector, css, layer = DEFAULT_STYLE_LAYER) {
869
+ let wrappedCss = wrapStyleForLayer(selector, css, layer);
870
+ if (!wrappedCss)
871
+ return '';
872
+ return `<style data-rmx="${escapeHtml(selector)}">${wrappedCss}</style>`;
873
+ }
874
+ function readStyleTagAttribute(attrs, name) {
875
+ let match = attrs.match(new RegExp(`\\b${name}=(?:"([^"]*)"|'([^']*)')`));
876
+ if (!match)
877
+ return null;
878
+ return match[1] ?? match[2] ?? null;
879
+ }
880
+ function dedupeServerStyleTagsInHtml(html, seenStyles) {
881
+ return html.replace(/<style\b([^>]*)>[\s\S]*?<\/style>/gi, (match, attrs) => {
882
+ let selector = readStyleTagAttribute(attrs, 'data-rmx');
883
+ if (!selector)
884
+ return match;
885
+ if (seenStyles.has(selector))
886
+ return '';
887
+ seenStyles.add(selector);
888
+ return match;
889
+ });
890
+ }
891
+ function buildRmxDataScript(context) {
892
+ if (context.hydrationData.size === 0 && context.frameData.size === 0) {
893
+ return '';
894
+ }
895
+ let data = {};
896
+ if (context.hydrationData.size > 0) {
897
+ data.h = Object.fromEntries(context.hydrationData);
898
+ }
899
+ if (context.frameData.size > 0) {
900
+ data.f = Object.fromEntries(context.frameData);
901
+ }
902
+ let serializedData = escapeScriptJson(JSON.stringify(data));
903
+ return `<script type="application/json" id="rmx-data">${serializedData}</script>`;
904
+ }
905
+ function escapeScriptJson(json) {
906
+ // Avoid prematurely closing the script tag when serialized data contains "</script>".
907
+ return json.replace(/</g, '\\u003c');
908
+ }
909
+ function serializeStyleObject(style) {
910
+ let parts = [];
911
+ for (let [key, value] of Object.entries(style)) {
912
+ if (value == null)
913
+ continue;
914
+ if (typeof value === 'boolean')
915
+ continue;
916
+ if (typeof value === 'number' && !Number.isFinite(value))
917
+ continue;
918
+ // Convert camelCase to kebab-case
919
+ let cssKey = key.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
920
+ // Add px to numeric values where appropriate
921
+ let shouldAppendPx = typeof value === 'number' &&
922
+ value !== 0 &&
923
+ !NUMERIC_CSS_PROPS.has(cssKey) &&
924
+ !cssKey.startsWith('--');
925
+ let cssValue = shouldAppendPx
926
+ ? `${value}px`
927
+ : Array.isArray(value)
928
+ ? value.join(', ')
929
+ : String(value);
930
+ parts.push(`${cssKey}: ${cssValue};`);
931
+ }
932
+ return parts.join(' ');
933
+ }
934
+ // Frame styles work end-to-end when frame handlers use their own `renderToStream`:
935
+ // the handler's `finalizeHtml` emits selector-addressed `<style>` tags in its HTML, and on the client,
936
+ // the `adoptServerStyleTag` MutationObserver (stylesheet.ts) picks it up anywhere in the
937
+ // document and adopts the CSS into an adopted stylesheet.
938
+ async function streamPendingFrames(context, controller, encoder) {
939
+ let processedFrames = new Set();
940
+ while (true) {
941
+ let batch = context.pendingFrames.filter(({ frameId }) => !processedFrames.has(frameId));
942
+ if (batch.length === 0)
943
+ break;
944
+ await Promise.all(batch.map(async ({ frameId, promise }) => {
945
+ processedFrames.add(frameId);
946
+ try {
947
+ let { html, tail } = await promise;
948
+ html = dedupeServerStyleTagsInHtml(html, context.emittedStyles);
949
+ // Stream as a template element (first chunk only)
950
+ let templateHtml = `<template id="${frameId}">${escapeTemplateContent(html)}</template>`;
951
+ controller.enqueue(encoder.encode(templateHtml));
952
+ // Forward any additional chunks from a stream-valued resolveFrame result.
953
+ if (tail) {
954
+ await streamByteStreams([tail], controller, context.onError);
955
+ }
956
+ }
957
+ catch (error) {
958
+ context.onError(error);
959
+ }
960
+ }));
961
+ }
962
+ }
963
+ async function streamByteStreams(streams, controller, onError) {
964
+ await Promise.all(streams.map(async (stream) => {
965
+ let reader = stream.getReader();
966
+ try {
967
+ while (true) {
968
+ let { done, value } = await reader.read();
969
+ if (done)
970
+ break;
971
+ controller.enqueue(value);
972
+ }
973
+ }
974
+ catch (error) {
975
+ onError(error);
976
+ }
977
+ finally {
978
+ reader.releaseLock();
979
+ }
980
+ }));
981
+ }
982
+ async function drain(stream) {
983
+ let reader = stream.getReader();
984
+ let decoder = new TextDecoder();
985
+ let html = '';
986
+ while (true) {
987
+ let { done, value } = await reader.read();
988
+ if (done)
989
+ break;
990
+ html += decoder.decode(value);
991
+ }
992
+ return html;
993
+ }
994
+ /**
995
+ * Renders a node tree to a complete HTML string.
996
+ *
997
+ * @param node Node tree to render.
998
+ * @returns Rendered HTML.
999
+ */
1000
+ export async function renderToString(node) {
1001
+ return drain(renderToStream(node, {
1002
+ onError(error) {
1003
+ throw error;
1004
+ },
1005
+ }));
1006
+ }
1007
+ //# sourceMappingURL=stream.js.map