@remix-run/ui 0.4.0 → 0.6.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 (195) hide show
  1. package/README.md +74 -1
  2. package/dist/accordion/index.js +2 -2
  3. package/dist/accordion/primitives.d.ts +1 -1
  4. package/dist/accordion/primitives.js.map +1 -1
  5. package/dist/anchor/index.js.map +1 -1
  6. package/dist/animation/animate-layout-mixin.js +1 -1
  7. package/dist/animation/animate-layout-mixin.js.map +1 -1
  8. package/dist/animation/animate-mixins.js +2 -2
  9. package/dist/animation/animate-mixins.js.map +1 -1
  10. package/dist/animation/demos/color-interpolation.js +1 -1
  11. package/dist/animation/demos/color-interpolation.js.map +1 -1
  12. package/dist/animation/demos/drag-release.d.ts +1 -1
  13. package/dist/animation/demos/hold-to-confirm.d.ts +3 -3
  14. package/dist/animation/demos/material-ripple.js +1 -3
  15. package/dist/animation/demos/material-ripple.js.map +1 -1
  16. package/dist/animation/index.js +4 -4
  17. package/dist/animation/spring.js.map +1 -1
  18. package/dist/breadcrumbs/index.js +2 -2
  19. package/dist/button/index.js +25 -25
  20. package/dist/button/index.js.map +1 -1
  21. package/dist/checkbox/index.js +11 -9
  22. package/dist/checkbox/index.js.map +1 -1
  23. package/dist/combobox/index.js +4 -4
  24. package/dist/combobox/index.js.map +1 -1
  25. package/dist/combobox/primitives.d.ts +1 -1
  26. package/dist/combobox/primitives.js +5 -5
  27. package/dist/combobox/primitives.js.map +1 -1
  28. package/dist/dev/refresh.d.ts +1 -0
  29. package/dist/dev/refresh.js +2 -0
  30. package/dist/dev/refresh.js.map +1 -0
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.js +14 -14
  33. package/dist/input/index.js +9 -9
  34. package/dist/input/index.js.map +1 -1
  35. package/dist/jsx-runtime.js +2 -2
  36. package/dist/listbox/index.js +2 -2
  37. package/dist/menu/hover-aim.js.map +1 -1
  38. package/dist/menu/index.js +3 -3
  39. package/dist/menu/primitives.d.ts +1 -1
  40. package/dist/menu/primitives.js +6 -6
  41. package/dist/menu/primitives.js.map +1 -1
  42. package/dist/popover/index.js +3 -3
  43. package/dist/popover/scroll-lock.js.map +1 -1
  44. package/dist/radio/index.js +7 -7
  45. package/dist/radio/index.js.map +1 -1
  46. package/dist/runtime/client-entry-boundary.d.ts +14 -0
  47. package/dist/runtime/client-entry-boundary.js +29 -0
  48. package/dist/runtime/client-entry-boundary.js.map +1 -0
  49. package/dist/runtime/component.d.ts +19 -4
  50. package/dist/runtime/component.js +39 -9
  51. package/dist/runtime/component.js.map +1 -1
  52. package/dist/runtime/core/attributes.js +36 -3
  53. package/dist/runtime/core/attributes.js.map +1 -1
  54. package/dist/runtime/core/children.d.ts +2 -0
  55. package/dist/runtime/core/children.js +26 -2
  56. package/dist/runtime/core/children.js.map +1 -1
  57. package/dist/runtime/core/props.js +2 -2
  58. package/dist/runtime/core/vnode.d.ts +3 -3
  59. package/dist/runtime/core/vnode.js +22 -1
  60. package/dist/runtime/core/vnode.js.map +1 -1
  61. package/dist/runtime/create-element.d.ts +2 -2
  62. package/dist/runtime/create-element.js +3 -2
  63. package/dist/runtime/create-element.js.map +1 -1
  64. package/dist/runtime/diff-dom.js +307 -220
  65. package/dist/runtime/diff-dom.js.map +1 -1
  66. package/dist/runtime/diff-props.js +3 -3
  67. package/dist/runtime/dom.d.ts +12 -0
  68. package/dist/runtime/element-function.d.ts +3 -0
  69. package/dist/runtime/element-function.js +2 -0
  70. package/dist/runtime/element-function.js.map +1 -0
  71. package/dist/runtime/form-navigation.d.ts +25 -0
  72. package/dist/runtime/form-navigation.js +132 -0
  73. package/dist/runtime/form-navigation.js.map +1 -0
  74. package/dist/runtime/frame-resolution.d.ts +5 -0
  75. package/dist/runtime/frame-resolution.js +10 -0
  76. package/dist/runtime/frame-resolution.js.map +1 -0
  77. package/dist/runtime/frame.d.ts +59 -15
  78. package/dist/runtime/frame.js +383 -114
  79. package/dist/runtime/frame.js.map +1 -1
  80. package/dist/runtime/jsx.d.ts +1 -1
  81. package/dist/runtime/jsx.js +1 -1
  82. package/dist/runtime/jsx.js.map +1 -1
  83. package/dist/runtime/key.d.ts +1 -0
  84. package/dist/runtime/key.js +2 -0
  85. package/dist/runtime/key.js.map +1 -0
  86. package/dist/runtime/mixins/attrs-mixin.js +2 -2
  87. package/dist/runtime/mixins/link-mixin.js +4 -3
  88. package/dist/runtime/mixins/link-mixin.js.map +1 -1
  89. package/dist/runtime/mixins/mixin.d.ts +15 -10
  90. package/dist/runtime/mixins/mixin.js +4 -4
  91. package/dist/runtime/mixins/mixin.js.map +1 -1
  92. package/dist/runtime/mixins/on-mixin.js +1 -1
  93. package/dist/runtime/mixins/ref-mixin.js +1 -1
  94. package/dist/runtime/module-preloader.d.ts +8 -0
  95. package/dist/runtime/module-preloader.js +95 -0
  96. package/dist/runtime/module-preloader.js.map +1 -0
  97. package/dist/runtime/navigation.d.ts +4 -1
  98. package/dist/runtime/navigation.js +157 -32
  99. package/dist/runtime/navigation.js.map +1 -1
  100. package/dist/runtime/reconcile.d.ts +11 -10
  101. package/dist/runtime/reconcile.js +549 -421
  102. package/dist/runtime/reconcile.js.map +1 -1
  103. package/dist/runtime/refresh.d.ts +28 -0
  104. package/dist/runtime/refresh.js +31 -0
  105. package/dist/runtime/refresh.js.map +1 -0
  106. package/dist/runtime/render.js +1 -1
  107. package/dist/runtime/render.js.map +1 -1
  108. package/dist/runtime/run.d.ts +3 -1
  109. package/dist/runtime/run.js +15 -7
  110. package/dist/runtime/run.js.map +1 -1
  111. package/dist/runtime/scheduler.js +8 -16
  112. package/dist/runtime/scheduler.js.map +1 -1
  113. package/dist/runtime/to-vnode.d.ts +2 -2
  114. package/dist/runtime/to-vnode.js +68 -20
  115. package/dist/runtime/to-vnode.js.map +1 -1
  116. package/dist/runtime/vdom.d.ts +4 -2
  117. package/dist/runtime/vdom.js +87 -42
  118. package/dist/runtime/vdom.js.map +1 -1
  119. package/dist/runtime/vnode.d.ts +138 -67
  120. package/dist/runtime/vnode.js +17 -12
  121. package/dist/runtime/vnode.js.map +1 -1
  122. package/dist/select/index.js +11 -11
  123. package/dist/select/index.js.map +1 -1
  124. package/dist/select/primitives.d.ts +1 -1
  125. package/dist/select/primitives.js +6 -6
  126. package/dist/server/stream.d.ts +2 -0
  127. package/dist/server/stream.js +74 -29
  128. package/dist/server/stream.js.map +1 -1
  129. package/dist/shared/focus-styles.d.ts +1 -1
  130. package/dist/shared/focus-styles.js +1 -1
  131. package/dist/shared/focus-styles.js.map +1 -1
  132. package/dist/shared/listbox-popover-styles.js +1 -1
  133. package/dist/shared/style-values.js +26 -26
  134. package/dist/shared/style-values.js.map +1 -1
  135. package/dist/style/css-mixin.js +3 -3
  136. package/dist/style/index.js +2 -2
  137. package/dist/style/style.js +25 -6
  138. package/dist/style/style.js.map +1 -1
  139. package/dist/style/stylesheet.d.ts +0 -1
  140. package/dist/style/stylesheet.js +13 -54
  141. package/dist/style/stylesheet.js.map +1 -1
  142. package/dist/tabs/index.js +11 -11
  143. package/dist/tabs/index.js.map +1 -1
  144. package/dist/tabs/primitives.d.ts +1 -1
  145. package/dist/test.js +1 -1
  146. package/dist/toggle/index.js +9 -9
  147. package/dist/toggle/index.js.map +1 -1
  148. package/dist/toggle/primitives.d.ts +1 -1
  149. package/package.json +10 -7
  150. package/src/animation/animate-mixins.ts +3 -3
  151. package/src/animation/demos/color-interpolation.tsx +1 -1
  152. package/src/button/index.ts +24 -24
  153. package/src/checkbox/index.ts +10 -8
  154. package/src/combobox/index.tsx +1 -1
  155. package/src/dev/refresh.ts +1 -0
  156. package/src/index.ts +1 -1
  157. package/src/input/index.ts +8 -8
  158. package/src/radio/index.ts +6 -5
  159. package/src/runtime/client-entry-boundary.ts +54 -0
  160. package/src/runtime/component.ts +65 -16
  161. package/src/runtime/core/attributes.ts +35 -1
  162. package/src/runtime/core/children.ts +24 -2
  163. package/src/runtime/core/vnode.ts +25 -4
  164. package/src/runtime/create-element.ts +6 -5
  165. package/src/runtime/demos/readme.demo.tsx +4 -4
  166. package/src/runtime/diff-dom.ts +370 -244
  167. package/src/runtime/dom.ts +331 -214
  168. package/src/runtime/element-function.ts +1 -0
  169. package/src/runtime/event-listeners.ts +1 -1
  170. package/src/runtime/form-navigation.ts +184 -0
  171. package/src/runtime/frame-resolution.ts +13 -0
  172. package/src/runtime/frame.ts +500 -124
  173. package/src/runtime/jsx.ts +3 -5
  174. package/src/runtime/key.ts +1 -0
  175. package/src/runtime/mixins/link-mixin.ts +1 -0
  176. package/src/runtime/mixins/mixin.ts +21 -18
  177. package/src/runtime/module-preloader.ts +115 -0
  178. package/src/runtime/navigation.ts +204 -30
  179. package/src/runtime/reconcile.ts +762 -759
  180. package/src/runtime/refresh.ts +46 -0
  181. package/src/runtime/run.ts +12 -2
  182. package/src/runtime/scheduler.ts +9 -29
  183. package/src/runtime/to-vnode.ts +85 -23
  184. package/src/runtime/vdom.ts +89 -70
  185. package/src/runtime/vnode.ts +192 -88
  186. package/src/select/index.tsx +9 -9
  187. package/src/server/README.md +13 -0
  188. package/src/server/stream.ts +93 -30
  189. package/src/shared/focus-styles.ts +1 -1
  190. package/src/shared/style-values.ts +26 -26
  191. package/src/style/style.ts +25 -6
  192. package/src/style/stylesheet.ts +32 -57
  193. package/src/tabs/index.tsx +12 -11
  194. package/src/test/README.md +151 -60
  195. package/src/toggle/index.tsx +9 -7
@@ -1,13 +1,16 @@
1
- import { createComponent, Frame } from "./component.js";
2
- import { createFrame } from "./frame.js";
3
- import { createRangeRoot } from "./vdom.js";
4
- import { isCommittedComponentNode, isComponentNode, isCommittedHostNode, isCommittedTextNode, isFragmentNode, isHostNode, isNonRenderNode, isTextNode, findContextFromAncestry, } from "./vnode.js";
5
- import { invariant } from "./invariant.js";
6
- import { patchHostProps } from "./core/props.js";
7
- import { skipComments, logHydrationMismatch } from "./client-entries.js";
8
- import { toVNode } from "./to-vnode.js";
9
- import { bindMixinRuntime, cancelPendingMixinRemoval, dispatchMixinBeforeUpdate, dispatchMixinCommit, getMixinRuntimeSignal, prepareMixinRemoval, resolveMixedProps, teardownMixins, } from "./mixins/mixin.js";
10
- import { isOnMixinDescriptor } from "./mixins/on-mixin.js";
1
+ import { createComponent } from './component.js';
2
+ import { createFrame, isFrameRuntime } from './frame.js';
3
+ import { unwrapFrameResolution } from './frame-resolution.js';
4
+ import { createRangeRoot } from './vdom.js';
5
+ import { isCommittedComponentNode, isCommittedHostNode, isCommittedTextNode, isFragmentNode, isHostNode, findContextFromAncestry, } from './vnode.js';
6
+ import { invariant } from './invariant.js';
7
+ import { patchHostProps } from './core/props.js';
8
+ import { skipComments, logHydrationMismatch } from './client-entries.js';
9
+ import { toVNode } from './to-vnode.js';
10
+ import { bindMixinRuntime, cancelPendingMixinRemoval, dispatchMixinBeforeUpdate, dispatchMixinCommit, getMixinRuntimeSignal, prepareMixinRemoval, resolveMixedProps, teardownMixins, } from './mixins/mixin.js';
11
+ import { isOnMixinDescriptor } from './mixins/on-mixin.js';
12
+ import { createComponentErrorEvent } from './error-event.js';
13
+ import { componentStalenessCheck } from './refresh.js';
11
14
  const SVG_NS = 'http://www.w3.org/2000/svg';
12
15
  let idCounter = 0;
13
16
  let persistedRemovalToken = 0;
@@ -15,40 +18,36 @@ const persistedMixinNodes = new Set();
15
18
  let activeSchedulerUpdateParents;
16
19
  // Compute SVG context for a node based on its parent and type.
17
20
  // Returns true if the node is within an SVG subtree, false otherwise.
18
- function getSvgContext(vParent, nodeType) {
21
+ function getSvgContext(vParent, node) {
19
22
  // Only host elements (strings) can affect SVG context
20
- if (typeof nodeType === 'string') {
23
+ if (node.kind === 'host') {
21
24
  // svg element creates SVG context
22
- if (nodeType === 'svg')
25
+ if (node.type === 'svg')
23
26
  return true;
24
27
  // foreignObject switches back to HTML context
25
- if (nodeType === 'foreignObject')
28
+ if (node.type === 'foreignObject')
26
29
  return false;
27
30
  }
28
31
  // Otherwise inherit from parent
29
- return vParent._svg ?? false;
32
+ return vParent._svg;
30
33
  }
31
34
  function getHostProps(node) {
32
- return node._mixedProps ?? node.props;
35
+ return '_mixedProps' in node ? node._mixedProps : node.props;
33
36
  }
34
37
  function markNodePersistedByMixins(node, domParent, token) {
35
- node._persistedByMixins = true;
36
- node._persistedParentByMixins = domParent;
37
- node._persistedRemovalToken = token;
38
+ node._persistence = { parent: domParent, token };
38
39
  persistedMixinNodes.add(node);
39
40
  bindMixinRuntime(node._mixState, undefined);
40
41
  }
41
42
  function unmarkNodePersistedByMixins(node) {
42
- node._persistedByMixins = false;
43
- node._persistedParentByMixins = undefined;
44
- node._persistedRemovalToken = undefined;
43
+ node._persistence = undefined;
45
44
  persistedMixinNodes.delete(node);
46
45
  }
47
46
  function findMatchingPersistedMixinNode(type, key, domParent) {
48
47
  if (key == null)
49
48
  return null;
50
49
  for (let node of persistedMixinNodes) {
51
- if (node._persistedParentByMixins !== domParent)
50
+ if (node._persistence?.parent !== domParent)
52
51
  continue;
53
52
  if (node.type !== type)
54
53
  continue;
@@ -59,6 +58,7 @@ function findMatchingPersistedMixinNode(type, key, domParent) {
59
58
  return null;
60
59
  }
61
60
  const EMPTY_DIRECT_EVENT_DESCRIPTORS = [];
61
+ const EMPTY_COMMITTED_CHILDREN = [];
62
62
  function shouldRestoreControlledReflectionOnInput(node, state) {
63
63
  // Some controls dispatch `input` before `change` for the same interaction.
64
64
  // When checked/value state is typically handled on `change`, restoring on the
@@ -152,6 +152,15 @@ function teardownControlledReflection(node) {
152
152
  state.listenersAttached = false;
153
153
  }
154
154
  }
155
+ // See abandonDirectEventListeners: skips removeEventListener for discarded subtrees.
156
+ function abandonControlledReflection(node) {
157
+ let state = node._controlledState;
158
+ if (!state)
159
+ return;
160
+ state.disposed = true;
161
+ state.pendingRestoreVersion++;
162
+ state.listenersAttached = false;
163
+ }
155
164
  function canManageValue(type, element) {
156
165
  if (type === 'progress')
157
166
  return false;
@@ -176,23 +185,16 @@ function setPropertyReflection(element, key, value) {
176
185
  return;
177
186
  element[key] = value == null ? '' : value;
178
187
  }
179
- function resolveNodeMixProps(node, frame, scheduler, state) {
188
+ function resolveNodeMixProps(node, parent, frame, scheduler, state) {
180
189
  let mix = node.props.mix;
181
190
  let directEventDescriptors = resolveDirectEventDescriptors(mix);
182
191
  if (directEventDescriptors) {
183
- if (state) {
192
+ if (state)
184
193
  teardownMixins(state);
185
- }
186
- node._mixState = undefined;
187
- node._mixedProps = node.props;
188
- node._directEventDescriptors = directEventDescriptors;
189
- return node.props;
194
+ return { props: node.props, directEventDescriptors };
190
195
  }
191
- node._directEventDescriptors = undefined;
192
196
  if (state == null && (mix == null || (Array.isArray(mix) && mix.length === 0))) {
193
- node._mixState = undefined;
194
- node._mixedProps = node.props;
195
- return node.props;
197
+ return { props: node.props };
196
198
  }
197
199
  let resolved = resolveMixedProps({
198
200
  hostType: node.type,
@@ -202,14 +204,17 @@ function resolveNodeMixProps(node, frame, scheduler, state) {
202
204
  if (typeof type !== 'function') {
203
205
  return undefined;
204
206
  }
205
- return findContextFromAncestry(node, type);
207
+ return findContextFromAncestry(parent, type);
206
208
  },
207
209
  props: node.props,
208
210
  state,
209
211
  });
210
- node._mixState = resolved.state;
212
+ return { props: resolved.props, mixState: resolved.state };
213
+ }
214
+ function applyResolvedHostProps(node, resolved) {
211
215
  node._mixedProps = resolved.props;
212
- return resolved.props;
216
+ node._mixState = resolved.mixState;
217
+ node._directEventDescriptors = resolved.directEventDescriptors;
213
218
  }
214
219
  function resolveDirectEventDescriptors(mix) {
215
220
  if (!mix)
@@ -220,8 +225,8 @@ function resolveDirectEventDescriptors(mix) {
220
225
  return areOnMixinDescriptors(mix) ? mix : null;
221
226
  }
222
227
  function areOnMixinDescriptors(descriptors) {
223
- for (let item of descriptors) {
224
- if (!isOnMixinDescriptor(item))
228
+ for (let i = 0; i < descriptors.length; i++) {
229
+ if (!isOnMixinDescriptor(descriptors[i]))
225
230
  return false;
226
231
  }
227
232
  return true;
@@ -237,11 +242,12 @@ function enqueueMixinBindingUpdate(done) {
237
242
  }
238
243
  dispatchMixinBeforeUpdate(state);
239
244
  let prevProps = getHostProps(node);
240
- let nextProps = resolveNodeMixProps(node, this.frame, this.scheduler, state);
241
- patchHostProps(prevProps, nextProps, this.node);
242
- if (node._controlledState || shouldTrackControlledReflection(nextProps)) {
245
+ let resolved = resolveNodeMixProps(node, node._parent, this.frame, this.scheduler, state);
246
+ applyResolvedHostProps(node, resolved);
247
+ patchHostProps(prevProps, resolved.props, this.node);
248
+ if (node._controlledState || shouldTrackControlledReflection(resolved.props)) {
243
249
  ensureControlledReflection(node, this.scheduler);
244
- syncControlledReflection(node, nextProps);
250
+ syncControlledReflection(node, resolved.props);
245
251
  }
246
252
  dispatchMixinCommit(state);
247
253
  done(state ? getMixinRuntimeSignal(state) : AbortSignal.abort());
@@ -252,7 +258,7 @@ function bindNodeMixRuntime(node, frame, scheduler, styles, reclaimed = false, p
252
258
  let state = node._mixState;
253
259
  bindMixinRuntime(state, {
254
260
  node: node._dom,
255
- parent: parent ?? node._dom.parentNode,
261
+ parent: parent ?? getRequiredDomParent(node._dom),
256
262
  key: node.key,
257
263
  target: node,
258
264
  frame,
@@ -267,6 +273,11 @@ function isHeadHostNode(node) {
267
273
  return false;
268
274
  return node.type.toLowerCase() === 'head';
269
275
  }
276
+ function getRequiredDomParent(node) {
277
+ let parent = node.parentNode;
278
+ invariant(parent, 'Expected mounted host node to have a DOM parent');
279
+ return parent;
280
+ }
270
281
  function getDocumentHead(domParent) {
271
282
  if (domParent instanceof Document) {
272
283
  return domParent.head;
@@ -276,65 +287,112 @@ function getDocumentHead(domParent) {
276
287
  }
277
288
  return null;
278
289
  }
279
- export function diffVNodes(curr, next, domParent, frame, scheduler, styles, vParent, rootTarget, anchor, rootCursor) {
280
- next._parent = vParent; // set parent for initial render context lookups
281
- next._svg = getSvgContext(vParent, next.type);
282
- // new
290
+ function commitNonRenderNode(node, parent, svg) {
291
+ let committed = node;
292
+ committed._parent = parent;
293
+ committed._svg = svg;
294
+ return committed;
295
+ }
296
+ function commitTextNode(node, parent, svg, dom) {
297
+ let committed = node;
298
+ committed._parent = parent;
299
+ committed._svg = svg;
300
+ committed._dom = dom;
301
+ return committed;
302
+ }
303
+ function beginFragmentNode(node, parent, svg) {
304
+ let committed = node;
305
+ committed._parent = parent;
306
+ committed._svg = svg;
307
+ committed._children = EMPTY_COMMITTED_CHILDREN;
308
+ return committed;
309
+ }
310
+ function commitHostNode(node, parent, svg, dom, resolved, children = EMPTY_COMMITTED_CHILDREN) {
311
+ let committed = node;
312
+ committed._parent = parent;
313
+ committed._svg = svg;
314
+ committed._dom = dom;
315
+ committed._children = children;
316
+ committed._mixedProps = resolved.props;
317
+ committed._mixState = resolved.mixState;
318
+ committed._directEventDescriptors = resolved.directEventDescriptors;
319
+ return committed;
320
+ }
321
+ function beginComponentNode(node, parent, svg, handle, context) {
322
+ let mounting = node;
323
+ mounting._parent = parent;
324
+ mounting._svg = svg;
325
+ mounting._handle = handle;
326
+ mounting._context = context;
327
+ return mounting;
328
+ }
329
+ function commitComponentNode(node, content) {
330
+ let committed = node;
331
+ committed._content = content;
332
+ return committed;
333
+ }
334
+ export function diffVNodes(curr, next, domParent, vParent, context, anchor, cursor) {
283
335
  if (curr === null) {
284
- return insert(next, domParent, frame, scheduler, styles, vParent, rootTarget, anchor, rootCursor);
285
- }
286
- if (curr.type !== next.type) {
287
- replace(curr, next, domParent, frame, scheduler, styles, vParent, rootTarget, anchor);
288
- return rootCursor;
289
- }
290
- if (isNonRenderNode(curr) && isNonRenderNode(next)) {
291
- return rootCursor;
336
+ return insert(next, domParent, vParent, context, anchor, cursor);
292
337
  }
293
- if (isCommittedTextNode(curr) && isTextNode(next)) {
294
- diffText(curr, next, vParent);
295
- return rootCursor;
338
+ if (componentStalenessCheck !== null &&
339
+ curr.kind === 'component' &&
340
+ next.kind === 'component' &&
341
+ componentStalenessCheck(curr.type) === true) {
342
+ return replace(curr, next, domParent, vParent, context, anchor);
296
343
  }
297
- if (isCommittedHostNode(curr) && isHostNode(next)) {
298
- diffHost(curr, next, frame, scheduler, styles, vParent, rootTarget);
299
- return rootCursor;
344
+ if (curr.kind !== next.kind || curr.type !== next.type) {
345
+ return replace(curr, next, domParent, vParent, context, anchor);
300
346
  }
301
- if (isCommittedComponentNode(curr) && isComponentNode(next)) {
302
- diffComponent(curr, next, frame, scheduler, styles, domParent, vParent, rootTarget);
303
- return rootCursor;
304
- }
305
- if (isFragmentNode(curr) && isFragmentNode(next)) {
306
- diffChildren(curr._children, next._children, domParent, frame, scheduler, styles, next, rootTarget, undefined, anchor);
307
- return rootCursor;
308
- }
309
- if (curr.type === Frame && next.type === Frame) {
310
- diffFrame(curr, next, domParent, frame, scheduler, styles, vParent, rootTarget, anchor);
311
- return rootCursor;
347
+ switch (next.kind) {
348
+ case 'host': {
349
+ return diffHost(curr, next, vParent, context);
350
+ }
351
+ case 'text': {
352
+ return diffText(curr, next, vParent, getSvgContext(vParent, next));
353
+ }
354
+ case 'empty': {
355
+ return commitNonRenderNode(next, vParent, getSvgContext(vParent, next));
356
+ }
357
+ case 'fragment': {
358
+ let childInputs = next._children;
359
+ let committed = beginFragmentNode(next, vParent, getSvgContext(vParent, next));
360
+ committed._children = diffChildren(curr._children, childInputs, domParent, committed, context, undefined, anchor);
361
+ return committed;
362
+ }
363
+ case 'frame': {
364
+ return diffFrame(curr, next, domParent, vParent, context, getSvgContext(vParent, next), anchor);
365
+ }
366
+ case 'component': {
367
+ return diffComponent(curr, next, domParent, vParent, context, getSvgContext(vParent, next), anchor, cursor);
368
+ }
312
369
  }
313
- invariant(false, 'Unexpected diff case');
314
370
  }
315
- function replace(curr, next, domParent, frame, scheduler, styles, vParent, rootTarget, anchor) {
371
+ function replace(curr, next, domParent, vParent, context, anchor) {
316
372
  let currAnchor = findFirstDomAnchor(curr);
317
373
  if (currAnchor && currAnchor.parentNode === domParent) {
318
374
  let replacementAnchor = document.createComment('rmx:replace');
319
375
  domParent.insertBefore(replacementAnchor, currAnchor);
320
376
  try {
321
- remove(curr, domParent, scheduler, styles);
322
- insert(next, domParent, frame, scheduler, styles, vParent, rootTarget, replacementAnchor);
377
+ remove(curr, domParent, context);
378
+ return insert(next, domParent, vParent, context, replacementAnchor);
323
379
  }
324
380
  finally {
325
381
  replacementAnchor.parentNode?.removeChild(replacementAnchor);
326
382
  }
327
- return;
328
383
  }
329
384
  let replacementAnchor = findNextSiblingDomAnchor(curr) ?? anchor;
330
- remove(curr, domParent, scheduler, styles);
331
- insert(next, domParent, frame, scheduler, styles, vParent, rootTarget, replacementAnchor);
385
+ remove(curr, domParent, context);
386
+ return insert(next, domParent, vParent, context, replacementAnchor);
332
387
  }
333
- function diffHost(curr, next, frame, scheduler, styles, vParent, rootTarget) {
388
+ function diffHost(curr, next, vParent, context) {
389
+ let { frame, scheduler, styles } = context;
334
390
  let mixState = curr._mixState;
335
391
  let currProps = getHostProps(curr);
336
- let nextProps = resolveNodeMixProps(next, frame, scheduler, mixState);
337
- let nextMixState = next._mixState;
392
+ let resolved = resolveNodeMixProps(next, vParent, frame, scheduler, mixState);
393
+ let childInputs = next._children;
394
+ let nextProps = resolved.props;
395
+ let nextMixState = resolved.mixState;
338
396
  let shouldDispatchMixinLifecycle = (nextMixState?.runners.length ?? 0) > 0 && shouldDispatchInlineMixinLifecycle(curr._dom);
339
397
  if (shouldDispatchMixinLifecycle) {
340
398
  dispatchMixinBeforeUpdate(nextMixState);
@@ -350,34 +408,30 @@ function diffHost(curr, next, frame, scheduler, styles, vParent, rootTarget) {
350
408
  // innerHTML was removed, clear it before adding children
351
409
  curr._dom.innerHTML = '';
352
410
  }
353
- diffChildren(curr._children, next._children, curr._dom, frame, scheduler, styles, next, rootTarget);
411
+ let committed = commitHostNode(next, vParent, getSvgContext(vParent, next), curr._dom, resolved);
412
+ committed._directEventState = curr._directEventState;
413
+ committed._controlledState = curr._controlledState;
414
+ committed._children = diffChildren(curr._children, childInputs, curr._dom, committed, context);
354
415
  patchHostProps(currProps, nextProps, curr._dom);
355
- next._dom = curr._dom;
356
- next._parent = vParent;
357
- next._controller = curr._controller;
358
- next._directEventState = curr._directEventState;
359
- next._controlledState = curr._controlledState;
360
- syncDirectEventListeners(next);
361
- if (next._controlledState || shouldTrackControlledReflection(nextProps)) {
362
- ensureControlledReflection(next, scheduler);
363
- syncControlledReflection(next, nextProps);
364
- }
365
- if (next._mixState) {
366
- bindNodeMixRuntime(next, frame, scheduler, styles);
416
+ syncDirectEventListeners(committed);
417
+ if (committed._controlledState || shouldTrackControlledReflection(nextProps)) {
418
+ ensureControlledReflection(committed, scheduler);
419
+ syncControlledReflection(committed, nextProps);
420
+ }
421
+ if (committed._mixState) {
422
+ bindNodeMixRuntime(committed, frame, scheduler, styles);
367
423
  }
368
424
  if (shouldDispatchMixinLifecycle) {
369
425
  scheduler.enqueueCommitPhase([() => dispatchMixinCommit(nextMixState)]);
370
426
  }
371
- return;
427
+ return committed;
372
428
  }
373
- function setupHostNode(node, dom, scheduler) {
374
- node._dom = dom;
429
+ function setupHostNode(node, scheduler) {
375
430
  let props = getHostProps(node);
376
- let committedNode = node;
377
- syncDirectEventListeners(committedNode);
431
+ syncDirectEventListeners(node);
378
432
  if (shouldTrackControlledReflection(props)) {
379
- ensureControlledReflection(committedNode, scheduler);
380
- syncControlledReflection(committedNode, props);
433
+ ensureControlledReflection(node, scheduler);
434
+ syncControlledReflection(node, props);
381
435
  }
382
436
  }
383
437
  function syncDirectEventListeners(node) {
@@ -397,8 +451,12 @@ function syncDirectEventListeners(node) {
397
451
  }
398
452
  let bindings = state.bindings;
399
453
  for (let index = 0; index < descriptors.length; index++) {
400
- let descriptor = descriptors[index];
401
- let [type, handler, captureBoolean = false] = descriptor.args;
454
+ // Indexed access instead of array destructuring: destructuring goes through
455
+ // the iterator protocol and allocates on every host-node update.
456
+ let args = descriptors[index].args;
457
+ let type = args[0];
458
+ let handler = args[1];
459
+ let captureBoolean = args[2] ?? false;
402
460
  let binding = bindings[index];
403
461
  if (!binding) {
404
462
  binding = createDirectEventBinding(type, handler, captureBoolean);
@@ -457,258 +515,286 @@ function teardownDirectEventListeners(node) {
457
515
  state.bindings.length = 0;
458
516
  node._directEventState = undefined;
459
517
  }
518
+ // Teardown for a node whose DOM subtree is being discarded wholesale: abort
519
+ // pending handler work but skip removeEventListener — the listeners die with
520
+ // the detached node, and removing them one-by-one dominates large teardowns.
521
+ function abandonDirectEventListeners(node) {
522
+ let state = node._directEventState;
523
+ if (!state)
524
+ return;
525
+ for (let binding of state.bindings) {
526
+ binding.reentry?.abort(new DOMException('', 'AbortError'));
527
+ binding.reentry = null;
528
+ }
529
+ state.bindings.length = 0;
530
+ node._directEventState = undefined;
531
+ }
460
532
  function invokeDirectEventBinding(binding, event) {
461
533
  binding.reentry?.abort(new DOMException('', 'EventReentry'));
462
534
  binding.reentry = new AbortController();
463
535
  void binding.handler(event, binding.reentry.signal);
464
536
  }
465
- function diffText(curr, next, vParent) {
537
+ function diffText(curr, next, vParent, svg) {
466
538
  if (curr._text !== next._text) {
467
539
  curr._dom.textContent = next._text;
468
540
  }
469
- next._dom = curr._dom;
470
- next._parent = vParent;
541
+ return commitTextNode(next, vParent, svg, curr._dom);
471
542
  }
472
- function insert(node, domParent, frame, scheduler, styles, vParent, rootTarget, anchor, cursor) {
473
- node._parent = vParent; // set parent for initial render context lookups
474
- node._svg = getSvgContext(vParent, node.type);
543
+ function insert(node, domParent, vParent, context, anchor, cursor) {
544
+ let { frame, scheduler, styles } = context;
545
+ let svg = getSvgContext(vParent, node);
546
+ let hydrationNode = cursor?.current;
475
547
  // Stop hydration if cursor has reached the anchor (end boundary)
476
548
  // Check BEFORE skipComments to prevent escaping range root markers
477
- if (cursor && anchor && cursor === anchor) {
478
- cursor = null;
549
+ if (hydrationNode && anchor && hydrationNode === anchor) {
550
+ hydrationNode = null;
479
551
  }
480
552
  // Preserve frame-start markers for non-Frame nodes too, so a following <Frame>
481
553
  // (e.g. the first child of a bare Fragment at a clientEntry boundary) can still
482
554
  // claim its rmx:f marker during hydration instead of being re-inserted fresh.
483
555
  // A rmx:f marker always belongs to a <Frame>, so no non-Frame node should
484
556
  // consume one.
485
- cursor = skipCommentsExceptFrameStart(cursor ?? null);
557
+ hydrationNode = skipCommentsExceptFrameStart(hydrationNode ?? null);
486
558
  // Also check after skipComments in case we skipped past the anchor
487
- if (cursor && anchor && cursor === anchor) {
488
- cursor = null;
559
+ if (hydrationNode && anchor && hydrationNode === anchor) {
560
+ hydrationNode = null;
489
561
  }
562
+ if (cursor)
563
+ cursor.current = hydrationNode;
490
564
  let doInsert = anchor
491
565
  ? (dom) => domParent.insertBefore(dom, anchor)
492
566
  : (dom) => domParent.appendChild(dom);
493
- if (isNonRenderNode(node)) {
494
- return cursor;
567
+ if (node.kind === 'empty') {
568
+ return commitNonRenderNode(node, vParent, svg);
495
569
  }
496
- if (isTextNode(node)) {
497
- if (cursor instanceof Text) {
498
- node._parent = vParent;
570
+ if (node.kind === 'text') {
571
+ if (hydrationNode instanceof Text) {
499
572
  // Handle text node consolidation: server renders adjacent text as single node
500
573
  // e.g., <span>Hello {world}</span> → server: "Hello world", client: ["Hello ", "world"]
501
- if (cursor.data !== node._text) {
502
- if (cursor.data.startsWith(node._text) && node._text.length < cursor.data.length) {
574
+ if (hydrationNode.data !== node._text) {
575
+ if (hydrationNode.data.startsWith(node._text) &&
576
+ node._text.length < hydrationNode.data.length) {
503
577
  // Consolidation case: split the text node at the boundary
504
578
  // cursor becomes the first part (node._text), remainder is returned for next vnode
505
- let remainder = cursor.splitText(node._text.length);
506
- node._dom = cursor;
507
- return remainder;
579
+ let remainder = hydrationNode.splitText(node._text.length);
580
+ if (cursor)
581
+ cursor.current = remainder;
582
+ return commitTextNode(node, vParent, svg, hydrationNode);
508
583
  }
509
584
  // Genuine mismatch - correct it
510
- logHydrationMismatch('text mismatch', cursor.data, node._text);
511
- cursor.data = node._text;
585
+ logHydrationMismatch('text mismatch', hydrationNode.data, node._text);
586
+ hydrationNode.data = node._text;
512
587
  }
513
- node._dom = cursor;
514
- return cursor.nextSibling;
588
+ if (cursor)
589
+ cursor.current = hydrationNode.nextSibling;
590
+ return commitTextNode(node, vParent, svg, hydrationNode);
515
591
  }
516
592
  let dom = document.createTextNode(node._text);
517
- node._dom = dom;
518
- node._parent = vParent;
519
593
  doInsert(dom);
520
- return cursor;
594
+ return commitTextNode(node, vParent, svg, dom);
521
595
  }
522
- if (isHostNode(node)) {
523
- let hostProps = resolveNodeMixProps(node, frame, scheduler);
596
+ if (node.kind === 'host') {
597
+ let resolved = resolveNodeMixProps(node, vParent, frame, scheduler);
598
+ let hostProps = resolved.props;
599
+ let childInputs = node._children;
524
600
  if (isHeadHostNode(node)) {
525
601
  let targetHead = getDocumentHead(domParent);
526
602
  if (targetHead) {
527
- let childCursor = cursor;
528
- if (cursor instanceof Element && cursor.tagName.toLowerCase() === 'head') {
529
- childCursor = cursor.firstChild;
530
- let nextCursor = cursor.nextSibling;
531
- if (cursor !== targetHead) {
532
- while (cursor.firstChild) {
533
- targetHead.appendChild(cursor.firstChild);
603
+ let childCursor = cursor ? { current: hydrationNode } : undefined;
604
+ if (hydrationNode instanceof Element && hydrationNode.tagName.toLowerCase() === 'head') {
605
+ if (childCursor)
606
+ childCursor.current = hydrationNode.firstChild;
607
+ let nextCursor = hydrationNode.nextSibling;
608
+ if (hydrationNode !== targetHead) {
609
+ while (hydrationNode.firstChild) {
610
+ targetHead.appendChild(hydrationNode.firstChild);
534
611
  }
535
- cursor.remove();
612
+ hydrationNode.remove();
536
613
  }
537
- cursor = nextCursor;
614
+ if (cursor)
615
+ cursor.current = nextCursor;
538
616
  }
617
+ let committed = commitHostNode(node, vParent, svg, targetHead, resolved);
539
618
  // Render explicit <head> children directly into document.head.
540
- diffChildren(null, node._children, targetHead, frame, scheduler, styles, node, rootTarget, childCursor);
619
+ committed._children = diffChildren(null, childInputs, targetHead, committed, context, childCursor);
541
620
  patchHostProps({}, hostProps, targetHead);
542
- setupHostNode(node, targetHead, scheduler);
543
- if (node._mixState) {
544
- bindNodeMixRuntime(node, frame, scheduler, styles);
621
+ setupHostNode(committed, scheduler);
622
+ if (committed._mixState) {
623
+ bindNodeMixRuntime(committed, frame, scheduler, styles);
545
624
  }
546
- return cursor;
625
+ return committed;
547
626
  }
548
627
  }
549
628
  // Check for matching mixin-persisted node that can be reclaimed
550
629
  let persistedNode = findMatchingPersistedMixinNode(node.type, node.key, domParent);
551
630
  if (persistedNode) {
552
- reclaimPersistedMixinNode(persistedNode, node, frame, scheduler, styles, vParent, rootTarget);
553
- return cursor;
631
+ return reclaimPersistedMixinNode(persistedNode, node, vParent, context);
554
632
  }
555
- if (cursor instanceof Element) {
633
+ if (hydrationNode instanceof Element) {
556
634
  // SVG elements have case-sensitive tag names (e.g. linearGradient, clipPath)
557
635
  // HTML elements are case-insensitive, so we lowercase for comparison
558
- let cursorTag = node._svg ? cursor.tagName : cursor.tagName.toLowerCase();
636
+ let cursorTag = svg ? hydrationNode.tagName : hydrationNode.tagName.toLowerCase();
559
637
  if (cursorTag === node.type) {
560
- let nextCursor = cursor.nextSibling;
561
- patchHostProps({}, hostProps, cursor);
638
+ let nextCursor = hydrationNode.nextSibling;
639
+ patchHostProps({}, hostProps, hydrationNode);
640
+ let committed = commitHostNode(node, vParent, svg, hydrationNode, resolved);
562
641
  // Handle innerHTML prop
563
642
  if (hostProps.innerHTML != null) {
564
- cursor.innerHTML = hostProps.innerHTML;
643
+ hydrationNode.innerHTML = hostProps.innerHTML;
565
644
  }
566
645
  else {
567
- let childCursor = cursor.firstChild;
646
+ let childCursor = { current: hydrationNode.firstChild };
568
647
  // Ignore excess nodes - browser extensions may inject content
569
- diffChildren(null, node._children, cursor, frame, scheduler, styles, node, rootTarget, childCursor);
648
+ committed._children = diffChildren(null, childInputs, hydrationNode, committed, context, childCursor);
570
649
  }
571
- setupHostNode(node, cursor, scheduler);
572
- if (node._mixState) {
573
- bindNodeMixRuntime(node, frame, scheduler, styles);
650
+ setupHostNode(committed, scheduler);
651
+ if (committed._mixState) {
652
+ bindNodeMixRuntime(committed, frame, scheduler, styles);
574
653
  }
575
- return nextCursor;
654
+ if (cursor)
655
+ cursor.current = nextCursor;
656
+ return committed;
576
657
  }
577
658
  else {
578
659
  // Type mismatch - try single-advance retry to handle browser extension injections
579
660
  // at the start of containers. Skip this node and try the next sibling once.
580
- let nextSibling = skipComments(cursor.nextSibling);
661
+ let nextSibling = skipComments(hydrationNode.nextSibling);
581
662
  if (nextSibling instanceof Element) {
582
- let nextTag = node._svg ? nextSibling.tagName : nextSibling.tagName.toLowerCase();
663
+ let nextTag = svg ? nextSibling.tagName : nextSibling.tagName.toLowerCase();
583
664
  if (nextTag === node.type) {
584
665
  let nextCursor = nextSibling.nextSibling;
585
666
  // Found a match after skipping - adopt it and leave skipped node in place
586
667
  patchHostProps({}, hostProps, nextSibling);
668
+ let committed = commitHostNode(node, vParent, svg, nextSibling, resolved);
587
669
  if (hostProps.innerHTML != null) {
588
670
  nextSibling.innerHTML = hostProps.innerHTML;
589
671
  }
590
672
  else {
591
- let childCursor = nextSibling.firstChild;
592
- diffChildren(null, node._children, nextSibling, frame, scheduler, styles, node, rootTarget, childCursor);
673
+ let childCursor = { current: nextSibling.firstChild };
674
+ committed._children = diffChildren(null, childInputs, nextSibling, committed, context, childCursor);
593
675
  }
594
- setupHostNode(node, nextSibling, scheduler);
595
- if (node._mixState) {
596
- bindNodeMixRuntime(node, frame, scheduler, styles);
676
+ setupHostNode(committed, scheduler);
677
+ if (committed._mixState) {
678
+ bindNodeMixRuntime(committed, frame, scheduler, styles);
597
679
  }
598
- return nextCursor;
680
+ if (cursor)
681
+ cursor.current = nextCursor;
682
+ return committed;
599
683
  }
600
684
  }
601
685
  // Retry failed - log mismatch and create new element (don't remove mismatched nodes)
602
686
  logHydrationMismatch('tag', cursorTag, node.type);
603
- cursor = undefined; // stop hydration for this tree
687
+ if (cursor)
688
+ cursor.current = undefined; // stop hydration for this tree
604
689
  }
605
690
  }
606
- let dom = node._svg
607
- ? document.createElementNS(SVG_NS, node.type)
608
- : document.createElement(node.type);
691
+ let dom = svg ? document.createElementNS(SVG_NS, node.type) : document.createElement(node.type);
609
692
  patchHostProps({}, hostProps, dom);
693
+ let committed = commitHostNode(node, vParent, svg, dom, resolved);
610
694
  // Handle innerHTML prop
611
695
  if (hostProps.innerHTML != null) {
612
696
  dom.innerHTML = hostProps.innerHTML;
613
697
  }
614
698
  else {
615
- diffChildren(null, node._children, dom, frame, scheduler, styles, node, rootTarget);
699
+ committed._children = diffChildren(null, childInputs, dom, committed, context);
616
700
  }
617
- setupHostNode(node, dom, scheduler);
618
- if (node._mixState) {
619
- bindNodeMixRuntime(node, frame, scheduler, styles, false, domParent);
701
+ setupHostNode(committed, scheduler);
702
+ if (committed._mixState) {
703
+ bindNodeMixRuntime(committed, frame, scheduler, styles, false, domParent);
620
704
  }
621
705
  doInsert(dom);
622
- return cursor;
706
+ return committed;
623
707
  }
624
- if (isFragmentNode(node)) {
708
+ if (node.kind === 'fragment') {
709
+ let childInputs = node._children;
710
+ let committed = beginFragmentNode(node, vParent, svg);
711
+ let children = childInputs;
625
712
  // Insert fragment children in order before the same anchor
626
- for (let child of node._children) {
627
- cursor = insert(child, domParent, frame, scheduler, styles, node, rootTarget, anchor, cursor);
713
+ for (let i = 0; i < childInputs.length; i++) {
714
+ children[i] = insert(childInputs[i], domParent, committed, context, anchor, cursor);
628
715
  }
629
- return cursor;
716
+ committed._children = children;
717
+ return committed;
630
718
  }
631
- if (isComponentNode(node)) {
632
- return diffComponent(null, node, frame, scheduler, styles, domParent, vParent, rootTarget, anchor, cursor);
719
+ if (node.kind === 'component') {
720
+ return diffComponent(null, node, domParent, vParent, context, svg, anchor, cursor);
633
721
  }
634
- if (node.type === Frame) {
635
- return insertFrame(node, domParent, frame, scheduler, styles, vParent, rootTarget, anchor, cursor);
722
+ if (node.kind === 'frame') {
723
+ return insertFrame(node, domParent, vParent, context, svg, anchor, cursor);
636
724
  }
637
725
  invariant(false, 'Unexpected node type');
638
726
  }
639
- function diffFrame(curr, next, domParent, frame, scheduler, styles, vParent, rootTarget, anchor) {
727
+ function diffFrame(curr, next, domParent, vParent, context, svg, anchor) {
728
+ let { frame } = context;
640
729
  let currSrc = getFrameSrc(curr);
641
730
  let nextSrc = getFrameSrc(next);
642
731
  let currName = getFrameName(curr);
643
732
  let nextName = getFrameName(next);
644
733
  if (currName !== nextName) {
645
- let replaceAnchor = curr._rangeEnd?.nextSibling ?? anchor;
646
- remove(curr, domParent, scheduler, styles);
647
- insert(next, domParent, frame, scheduler, styles, vParent, rootTarget, replaceAnchor);
648
- return;
734
+ let replaceAnchor = curr._rangeEnd.nextSibling ?? anchor;
735
+ remove(curr, domParent, context);
736
+ return insertFrame(next, domParent, vParent, context, svg, replaceAnchor);
649
737
  }
650
738
  // If the frame hasn't resolved yet, preserve existing cancel/remount behavior
651
739
  // so pending streams from the old src cannot take over the new src.
652
- if (currSrc !== nextSrc && !curr._frameResolved) {
653
- let replaceAnchor = curr._rangeEnd?.nextSibling ?? anchor;
654
- remove(curr, domParent, scheduler, styles);
655
- insert(next, domParent, frame, scheduler, styles, vParent, rootTarget, replaceAnchor);
656
- return;
740
+ if (currSrc !== nextSrc && !curr._state.resolved) {
741
+ let replaceAnchor = curr._rangeEnd.nextSibling ?? anchor;
742
+ remove(curr, domParent, context);
743
+ return insertFrame(next, domParent, vParent, context, svg, replaceAnchor);
657
744
  }
658
- next._rangeStart = curr._rangeStart;
659
- next._rangeEnd = curr._rangeEnd;
660
- next._frameInstance = curr._frameInstance;
661
- next._frameFallbackRoot = curr._frameFallbackRoot;
662
- next._frameResolveToken = curr._frameResolveToken;
663
- next._frameResolved = curr._frameResolved;
664
- next._parent = vParent;
745
+ let committed = next;
746
+ Object.assign(committed, {
747
+ _rangeStart: curr._rangeStart,
748
+ _rangeEnd: curr._rangeEnd,
749
+ _state: curr._state,
750
+ _parent: vParent,
751
+ _svg: svg,
752
+ });
753
+ let frameRuntime = getFrameRuntime(frame);
754
+ let frameInstance = committed._state.instance;
755
+ let serverFrameReload = frameRuntime?.serverFrameReload;
665
756
  if (currSrc !== nextSrc) {
666
- let frameInstance = next._frameInstance;
667
757
  if (frameInstance) {
668
758
  frameInstance.handle.src = nextSrc;
669
759
  }
670
- let runtime = getFrameRuntime(frame);
671
- if (runtime) {
672
- resolveClientFrame(next, runtime);
760
+ if (frameRuntime) {
761
+ resolveClientFrame(committed, frameRuntime, serverFrameReload);
673
762
  }
674
763
  }
675
- if (!next._frameResolved && next._frameFallbackRoot) {
676
- next._frameFallbackRoot.render(next.props?.fallback ?? null);
764
+ else if (frameRuntime && frameInstance && serverFrameReload) {
765
+ // Reload client frames that have the same src during a render triggered by an ancestor frame reload
766
+ resolveClientFrame(committed, frameRuntime, serverFrameReload);
767
+ }
768
+ if (!committed._state.resolved && committed._state.fallbackRoot) {
769
+ committed._state.fallbackRoot.render(committed.props.fallback ?? null);
677
770
  }
771
+ return committed;
678
772
  }
679
- function insertFrame(node, domParent, frame, scheduler, styles, vParent, rootTarget, anchor, cursor) {
773
+ function insertFrame(node, domParent, vParent, context, svg, anchor, cursor) {
774
+ let { frame, styles } = context;
680
775
  let runtime = getFrameRuntime(frame);
681
776
  if (!runtime || runtime.canResolveFrames === false) {
682
777
  throw new Error('Cannot render <Frame /> without frame runtime. Use run() or pass frameInit to createRoot/createRangeRoot.');
683
778
  }
684
779
  // Hydration path: adopt server-rendered frame markers and reuse the existing
685
780
  // frame instance created during createSubFrames().
686
- if (isFrameStartComment(cursor)) {
687
- let start = cursor;
781
+ if (isFrameStartComment(cursor?.current)) {
782
+ let start = cursor.current;
688
783
  let end = findFrameEndComment(start);
689
784
  if (end) {
690
- node._rangeStart = start;
691
- node._rangeEnd = end;
692
- node._parent = vParent;
693
- node._frameResolveToken = 0;
694
- node._frameResolveController = undefined;
695
- node._frameFallbackRoot = undefined;
696
- node._frameResolved = true;
697
- let frameId = getFrameIdFromComment(start);
698
- let marker = frameId ? runtime.data.f?.[frameId] : undefined;
699
- let src = marker?.src ?? getFrameSrc(node);
700
785
  let instance = runtime.frameInstances.get(start);
786
+ let src = instance?.handle.src ?? getFrameSrc(node);
701
787
  if (!instance) {
702
788
  instance = createFrame([start, end], {
703
789
  name: getFrameName(node),
704
790
  src,
705
- marker: frameId && marker ? { ...marker, id: frameId } : undefined,
706
791
  errorTarget: runtime.errorTarget,
707
792
  loadModule: runtime.loadModule,
708
793
  resolveFrame: runtime.resolveFrame,
709
794
  pendingClientEntries: runtime.pendingClientEntries,
710
795
  scheduler: runtime.scheduler,
711
- data: runtime.data,
796
+ styleManager: runtime.styleManager,
797
+ data: {},
712
798
  moduleCache: runtime.moduleCache,
713
799
  moduleLoads: runtime.moduleLoads,
714
800
  frameInstances: runtime.frameInstances,
@@ -716,8 +802,20 @@ function insertFrame(node, domParent, frame, scheduler, styles, vParent, rootTar
716
802
  });
717
803
  runtime.frameInstances.set(start, instance);
718
804
  }
719
- node._frameInstance = instance;
720
- return end.nextSibling;
805
+ cursor.current = end.nextSibling;
806
+ return Object.assign(node, {
807
+ _rangeStart: start,
808
+ _rangeEnd: end,
809
+ _parent: vParent,
810
+ _svg: svg,
811
+ _state: {
812
+ instance,
813
+ resolveToken: 0,
814
+ resolveController: undefined,
815
+ fallbackRoot: undefined,
816
+ resolved: true,
817
+ },
818
+ });
721
819
  }
722
820
  }
723
821
  let start = document.createComment(` rmx:f:${randomFrameId()} `);
@@ -727,17 +825,11 @@ function insertFrame(node, domParent, frame, scheduler, styles, vParent, rootTar
727
825
  : (dom) => domParent.appendChild(dom);
728
826
  doInsert(start);
729
827
  doInsert(end);
730
- node._rangeStart = start;
731
- node._rangeEnd = end;
732
- node._parent = vParent;
733
828
  let fallbackRoot = createRangeRoot([start, end], {
734
829
  frame,
735
830
  styleManager: styles,
736
831
  });
737
- fallbackRoot.render(node.props?.fallback ?? null);
738
- node._frameFallbackRoot = fallbackRoot;
739
- node._frameResolved = false;
740
- node._frameResolveToken = 0;
832
+ fallbackRoot.render(node.props.fallback ?? null);
741
833
  let instance = createFrame([start, end], {
742
834
  name: getFrameName(node),
743
835
  src: getFrameSrc(node),
@@ -746,57 +838,84 @@ function insertFrame(node, domParent, frame, scheduler, styles, vParent, rootTar
746
838
  resolveFrame: runtime.resolveFrame,
747
839
  pendingClientEntries: runtime.pendingClientEntries,
748
840
  scheduler: runtime.scheduler,
749
- data: runtime.data,
841
+ styleManager: runtime.styleManager,
842
+ data: {},
750
843
  moduleCache: runtime.moduleCache,
751
844
  moduleLoads: runtime.moduleLoads,
752
845
  frameInstances: runtime.frameInstances,
753
846
  namedFrames: runtime.namedFrames,
754
847
  });
755
- node._frameInstance = instance;
756
848
  runtime.frameInstances.set(start, instance);
757
- resolveClientFrame(node, runtime);
758
- return cursor;
849
+ let committed = Object.assign(node, {
850
+ _rangeStart: start,
851
+ _rangeEnd: end,
852
+ _parent: vParent,
853
+ _svg: svg,
854
+ _state: {
855
+ instance,
856
+ fallbackRoot,
857
+ resolved: false,
858
+ resolveToken: 0,
859
+ resolveController: undefined,
860
+ },
861
+ });
862
+ resolveClientFrame(committed, runtime);
863
+ return committed;
759
864
  }
760
- function resolveClientFrame(node, runtime) {
865
+ function resolveClientFrame(node, runtime, serverFrameReload) {
761
866
  let frameSrc = getFrameSrc(node);
762
- let instance = node._frameInstance;
763
- if (!instance)
764
- return;
765
- let token = (node._frameResolveToken ?? 0) + 1;
766
- node._frameResolveToken = token;
767
- node._frameResolveController?.abort();
768
- let resolveController = new AbortController();
769
- node._frameResolveController = resolveController;
770
- Promise.resolve(runtime.resolveFrame(frameSrc, resolveController.signal, getFrameName(node)))
771
- .then(async (content) => {
772
- if (node._frameResolveToken !== token || resolveController.signal.aborted)
867
+ let state = node._state;
868
+ let instance = state.instance;
869
+ let token = state.resolveToken + 1;
870
+ state.resolveToken = token;
871
+ state.resolveController?.abort();
872
+ let reload = serverFrameReload
873
+ ? instance.beginClientFrameReloadForAncestorReload(serverFrameReload.signal)
874
+ : undefined;
875
+ if (!reload) {
876
+ instance.cancelReload();
877
+ }
878
+ let resolveController = reload?.controller ?? new AbortController();
879
+ state.resolveController = resolveController;
880
+ Promise.resolve()
881
+ .then(() => runtime.resolveFrame(frameSrc, {
882
+ signal: resolveController.signal,
883
+ target: getFrameName(node),
884
+ }))
885
+ .then(async (resolution) => {
886
+ if (state.resolveToken !== token || resolveController.signal.aborted)
773
887
  return;
774
- node._frameFallbackRoot?.dispose();
775
- node._frameFallbackRoot = undefined;
888
+ let { content } = await unwrapFrameResolution(resolution);
889
+ if (state.resolveToken !== token || resolveController.signal.aborted)
890
+ return;
891
+ state.fallbackRoot?.dispose();
892
+ state.fallbackRoot = undefined;
776
893
  let nextContent = asAbortableFrameContent(content, resolveController.signal);
777
894
  await instance.render(nextContent, { signal: resolveController.signal });
778
- if (node._frameResolveToken !== token || resolveController.signal.aborted)
895
+ if (state.resolveToken !== token || resolveController.signal.aborted)
779
896
  return;
780
- node._frameResolved = true;
897
+ state.resolved = true;
898
+ })
899
+ .catch((error) => {
900
+ if (reload && state.resolveToken === token && !resolveController.signal.aborted) {
901
+ runtime.errorTarget.dispatchEvent(createComponentErrorEvent(error));
902
+ }
781
903
  })
782
- .catch(() => { })
783
904
  .finally(() => {
784
- if (node._frameResolveController === resolveController) {
785
- node._frameResolveController = undefined;
905
+ reload?.complete();
906
+ if (state.resolveController === resolveController) {
907
+ state.resolveController = undefined;
786
908
  }
787
909
  });
788
910
  }
789
911
  function disposeFrameResources(node) {
790
- node._frameResolveToken = (node._frameResolveToken ?? 0) + 1;
791
- node._frameResolveController?.abort();
792
- node._frameResolveController = undefined;
793
- node._frameFallbackRoot?.dispose();
794
- node._frameFallbackRoot = undefined;
795
- let frameInstance = node._frameInstance;
796
- if (frameInstance) {
797
- frameInstance.dispose();
798
- node._frameInstance = undefined;
799
- }
912
+ let state = node._state;
913
+ state.resolveToken++;
914
+ state.resolveController?.abort();
915
+ state.resolveController = undefined;
916
+ state.fallbackRoot?.dispose();
917
+ state.fallbackRoot = undefined;
918
+ state.instance.dispose();
800
919
  }
801
920
  function asAbortableFrameContent(content, signal) {
802
921
  if (!(content instanceof ReadableStream))
@@ -832,13 +951,13 @@ function createAbortableReadableStream(source, signal) {
832
951
  removeAbortReadListener = () => signal.removeEventListener('abort', onAbortRead);
833
952
  signal.addEventListener('abort', onAbortRead, { once: true });
834
953
  });
835
- let { done, value } = await Promise.race([reader.read(), abortRead]);
954
+ let result = await Promise.race([reader.read(), abortRead]);
836
955
  removeAbortReadListener?.();
837
- if (done) {
956
+ if (result.done) {
838
957
  controller.close();
839
958
  return;
840
959
  }
841
- controller.enqueue(value);
960
+ controller.enqueue(result.value);
842
961
  },
843
962
  cancel(reason) {
844
963
  signal.removeEventListener('abort', onAbort);
@@ -849,8 +968,6 @@ function createAbortableReadableStream(source, signal) {
849
968
  function removeFrameDomRange(node, domParent) {
850
969
  let start = node._rangeStart;
851
970
  let end = node._rangeEnd;
852
- if (!(start instanceof Comment) || !(end instanceof Comment))
853
- return;
854
971
  let cursor = start;
855
972
  while (cursor) {
856
973
  let nextSibling = cursor.nextSibling;
@@ -861,19 +978,16 @@ function removeFrameDomRange(node, domParent) {
861
978
  break;
862
979
  cursor = nextSibling;
863
980
  }
864
- node._rangeStart = undefined;
865
- node._rangeEnd = undefined;
866
981
  }
867
982
  function getFrameRuntime(frame) {
868
- return frame.$runtime;
983
+ let runtime = frame.$runtime;
984
+ return isFrameRuntime(runtime) ? runtime : undefined;
869
985
  }
870
986
  function getFrameSrc(node) {
871
- let src = node.props?.src;
872
- invariant(typeof src === 'string' && src.length > 0, '<Frame /> requires a src prop');
873
- return src;
987
+ return node.props.src;
874
988
  }
875
989
  function getFrameName(node) {
876
- let name = node.props?.name;
990
+ let name = node.props.name;
877
991
  return typeof name === 'string' && name.length > 0 ? name : undefined;
878
992
  }
879
993
  function randomFrameId() {
@@ -888,16 +1002,13 @@ function skipCommentsExceptFrameStart(cursor) {
888
1002
  return cursor;
889
1003
  }
890
1004
  function isFrameStartComment(node) {
891
- return node instanceof Comment && node.data.trim().startsWith('rmx:f:');
1005
+ return isCommentNode(node) && node.data.trim().startsWith('rmx:f:');
892
1006
  }
893
1007
  function isFrameEndComment(node) {
894
- return node instanceof Comment && node.data.trim() === '/rmx:f';
1008
+ return isCommentNode(node) && node.data.trim() === '/rmx:f';
895
1009
  }
896
- function getFrameIdFromComment(comment) {
897
- let text = comment.data.trim();
898
- if (!text.startsWith('rmx:f:'))
899
- return undefined;
900
- return text.slice('rmx:f:'.length);
1010
+ function isCommentNode(node) {
1011
+ return node?.nodeType === Node.COMMENT_NODE;
901
1012
  }
902
1013
  function findFrameEndComment(start) {
903
1014
  let depth = 1;
@@ -914,32 +1025,32 @@ function findFrameEndComment(start) {
914
1025
  }
915
1026
  return null;
916
1027
  }
917
- export function renderComponent(handle, currContent, next, domParent, frame, scheduler, styles, rootTarget, vParent, anchor, cursor) {
918
- if (handle.isRemoved())
919
- return cursor;
1028
+ export function renderComponent(currContent, next, domParent, context, anchor, cursor) {
1029
+ let { scheduler } = context;
1030
+ let handle = next._handle;
1031
+ if (handle.isRemoved()) {
1032
+ invariant('_content' in next, 'Expected removed component to be committed');
1033
+ return next;
1034
+ }
920
1035
  let [element, tasks] = handle.render(next.props);
921
- let content = toVNode(element);
922
- let newCursor = diffVNodes(currContent, content, domParent, frame, scheduler, styles, next, rootTarget, anchor, cursor);
923
- next._content = content;
924
- next._handle = handle;
925
- next._parent = vParent;
926
- let committed = next;
927
- handle.setScheduleUpdate(() => {
928
- scheduler.enqueue(committed, domParent);
929
- });
1036
+ let content = diffVNodes(currContent, toVNode(element), domParent, next, context, anchor, cursor);
1037
+ let committed = commitComponentNode(next, content);
1038
+ handle.setScheduleUpdate(scheduler, committed, domParent);
930
1039
  scheduler.enqueueTasks(tasks);
931
- return newCursor;
1040
+ return committed;
932
1041
  }
933
- function diffComponent(curr, next, frame, scheduler, styles, domParent, vParent, rootTarget, anchor, cursor) {
1042
+ function diffComponent(curr, next, domParent, vParent, context, svg, anchor, cursor) {
1043
+ let { frame } = context;
934
1044
  if (curr === null) {
935
- let componentId = vParent._pendingHydrationComponentId;
1045
+ let componentId = vParent.kind === 'root' ? vParent._pendingHydrationComponentId : undefined;
936
1046
  if (componentId) {
937
- vParent._pendingHydrationComponentId = undefined;
1047
+ if (vParent.kind === 'root')
1048
+ vParent._pendingHydrationComponentId = undefined;
938
1049
  }
939
1050
  else {
940
1051
  componentId = `c${++idCounter}`;
941
1052
  }
942
- next._handle = createComponent({
1053
+ let handle = createComponent({
943
1054
  id: componentId,
944
1055
  frame,
945
1056
  type: next.type,
@@ -953,52 +1064,54 @@ function diffComponent(curr, next, frame, scheduler, styles, domParent, vParent,
953
1064
  return runtime?.topFrame;
954
1065
  },
955
1066
  });
956
- return renderComponent(next._handle, null, next, domParent, frame, scheduler, styles, rootTarget, vParent, anchor, cursor);
1067
+ let mounting = beginComponentNode(next, vParent, svg, handle, context);
1068
+ return renderComponent(null, mounting, domParent, context, anchor, cursor);
957
1069
  }
958
- next._handle = curr._handle;
959
- let { _content, _handle } = curr;
960
- return renderComponent(_handle, _content, next, domParent, frame, scheduler, styles, rootTarget, vParent, anchor, cursor);
1070
+ let mounting = beginComponentNode(next, vParent, svg, curr._handle, context);
1071
+ return renderComponent(curr._content, mounting, domParent, context, anchor, cursor);
961
1072
  }
962
1073
  // Cleanup without DOM removal - used for descendants when parent DOM node is removed
963
- function cleanupDescendants(node, scheduler, styles) {
1074
+ function cleanupDescendants(node, context) {
1075
+ let { scheduler } = context;
964
1076
  if (isCommittedTextNode(node)) {
965
1077
  return;
966
1078
  }
967
1079
  if (isCommittedHostNode(node)) {
968
- for (let child of node._children) {
969
- cleanupDescendants(child, scheduler, styles);
1080
+ let children = node._children;
1081
+ for (let i = 0; i < children.length; i++) {
1082
+ cleanupDescendants(children[i], context);
970
1083
  }
971
1084
  teardownMixins(node._mixState);
972
- teardownDirectEventListeners(node);
973
- teardownControlledReflection(node);
974
- if (node._controller)
975
- node._controller.abort();
1085
+ abandonDirectEventListeners(node);
1086
+ abandonControlledReflection(node);
976
1087
  return;
977
1088
  }
978
1089
  if (isFragmentNode(node)) {
979
- for (let child of node._children) {
980
- cleanupDescendants(child, scheduler, styles);
1090
+ let children = node._children;
1091
+ for (let i = 0; i < children.length; i++) {
1092
+ cleanupDescendants(children[i], context);
981
1093
  }
982
1094
  return;
983
1095
  }
984
1096
  if (isCommittedComponentNode(node)) {
985
- cleanupDescendants(node._content, scheduler, styles);
1097
+ cleanupDescendants(node._content, context);
986
1098
  let tasks = node._handle.remove();
987
1099
  scheduler.enqueueTasks(tasks);
988
1100
  return;
989
1101
  }
990
- if (node.type === Frame) {
1102
+ if (node.kind === 'frame') {
991
1103
  disposeFrameResources(node);
992
1104
  return;
993
1105
  }
994
1106
  }
995
- export function remove(node, domParent, scheduler, styles) {
1107
+ export function remove(node, domParent, context) {
1108
+ let { scheduler } = context;
996
1109
  if (isCommittedTextNode(node)) {
997
1110
  node._dom.parentNode?.removeChild(node._dom);
998
1111
  return;
999
1112
  }
1000
1113
  if (isCommittedHostNode(node)) {
1001
- if (node._persistedByMixins)
1114
+ if (node._persistence)
1002
1115
  return;
1003
1116
  let persistedRemoval = prepareMixinRemoval(node._mixState);
1004
1117
  if (persistedRemoval) {
@@ -1007,106 +1120,108 @@ export function remove(node, domParent, scheduler, styles) {
1007
1120
  void persistedRemoval
1008
1121
  .catch(() => { })
1009
1122
  .finally(() => {
1010
- if (!node._persistedByMixins)
1123
+ if (!node._persistence)
1011
1124
  return;
1012
- if (node._persistedRemovalToken !== token)
1125
+ if (node._persistence.token !== token)
1013
1126
  return;
1014
1127
  unmarkNodePersistedByMixins(node);
1015
- performHostNodeRemoval(node, domParent, scheduler, styles);
1128
+ performHostNodeRemoval(node, domParent, context);
1016
1129
  });
1017
1130
  return;
1018
1131
  }
1019
- performHostNodeRemoval(node, domParent, scheduler, styles);
1132
+ performHostNodeRemoval(node, domParent, context);
1020
1133
  return;
1021
1134
  }
1022
1135
  if (isFragmentNode(node)) {
1023
- for (let child of node._children) {
1024
- remove(child, domParent, scheduler, styles);
1136
+ let children = node._children;
1137
+ for (let i = 0; i < children.length; i++) {
1138
+ remove(children[i], domParent, context);
1025
1139
  }
1026
1140
  return;
1027
1141
  }
1028
1142
  if (isCommittedComponentNode(node)) {
1029
- remove(node._content, domParent, scheduler, styles);
1143
+ remove(node._content, domParent, context);
1030
1144
  let tasks = node._handle.remove();
1031
1145
  scheduler.enqueueTasks(tasks);
1032
1146
  return;
1033
1147
  }
1034
- if (node.type === Frame) {
1148
+ if (node.kind === 'frame') {
1035
1149
  disposeFrameResources(node);
1036
1150
  removeFrameDomRange(node, domParent);
1037
1151
  return;
1038
1152
  }
1039
1153
  }
1040
1154
  // Actually remove a host node from DOM and clean up
1041
- function performHostNodeRemoval(node, domParent, scheduler, styles) {
1155
+ function performHostNodeRemoval(node, domParent, context) {
1156
+ let children = node._children;
1042
1157
  if (isHeadHostNode(node)) {
1043
- for (let child of node._children) {
1044
- remove(child, node._dom, scheduler, styles);
1158
+ for (let i = 0; i < children.length; i++) {
1159
+ remove(children[i], node._dom, context);
1045
1160
  }
1046
1161
  }
1047
1162
  else {
1048
1163
  // Clean up all descendants first (before removing DOM subtree)
1049
- for (let child of node._children) {
1050
- cleanupDescendants(child, scheduler, styles);
1164
+ for (let i = 0; i < children.length; i++) {
1165
+ cleanupDescendants(children[i], context);
1051
1166
  }
1052
1167
  }
1053
1168
  teardownMixins(node._mixState);
1054
- teardownDirectEventListeners(node);
1055
- teardownControlledReflection(node);
1056
- // Never remove the real document.head node when reconciling a <head> vnode.
1057
- if (!isHeadHostNode(node)) {
1169
+ // Never remove the real document.head node when reconciling a <head> vnode,
1170
+ // and since it stays alive, detach its listeners for real.
1171
+ if (isHeadHostNode(node)) {
1172
+ teardownDirectEventListeners(node);
1173
+ teardownControlledReflection(node);
1174
+ }
1175
+ else {
1176
+ abandonDirectEventListeners(node);
1177
+ abandonControlledReflection(node);
1058
1178
  node._dom.parentNode?.removeChild(node._dom);
1059
1179
  }
1060
- if (node._controller)
1061
- node._controller.abort();
1062
1180
  }
1063
- function diffChildren(curr, next, domParent, frame, scheduler, styles, vParent, rootTarget, cursor, anchor) {
1181
+ function diffChildren(curr, next, domParent, vParent, context, cursor, anchor) {
1064
1182
  let hasKeys = hasKeyedChildren(next);
1183
+ let committed = next;
1065
1184
  if (curr === null) {
1066
1185
  if (hasKeys) {
1067
1186
  warnDuplicateKeys(next);
1068
1187
  }
1069
- for (let node of next) {
1070
- cursor = insert(node, domParent, frame, scheduler, styles, vParent, rootTarget, anchor, cursor);
1188
+ for (let i = 0; i < next.length; i++) {
1189
+ committed[i] = insert(next[i], domParent, vParent, context, anchor, cursor);
1071
1190
  }
1072
- vParent._children = next;
1073
- return cursor;
1191
+ return committed;
1074
1192
  }
1075
1193
  if (next.length === 0 &&
1076
1194
  anchor === undefined &&
1077
1195
  !parentUsesInnerHTML(vParent) &&
1078
1196
  canBulkClearChildren(curr)) {
1079
- for (let node of curr) {
1080
- cleanupDescendants(node, scheduler, styles);
1197
+ for (let i = 0; i < curr.length; i++) {
1198
+ cleanupDescendants(curr[i], context);
1081
1199
  }
1082
1200
  domParent.textContent = '';
1083
- vParent._children = next;
1084
- return;
1201
+ return EMPTY_COMMITTED_CHILDREN;
1085
1202
  }
1086
1203
  if (!hasKeys) {
1087
1204
  for (let i = 0; i < next.length; i++) {
1088
1205
  let currentNode = i < curr.length ? curr[i] : null;
1089
- diffVNodes(currentNode, next[i], domParent, frame, scheduler, styles, vParent, rootTarget, anchor, cursor);
1206
+ committed[i] = diffVNodes(currentNode, next[i], domParent, vParent, context, anchor, cursor);
1090
1207
  }
1091
1208
  if (curr.length > next.length) {
1092
1209
  for (let i = next.length; i < curr.length; i++) {
1093
1210
  let node = curr[i];
1094
1211
  if (node)
1095
- remove(node, domParent, scheduler, styles);
1212
+ remove(node, domParent, context);
1096
1213
  }
1097
1214
  }
1098
- vParent._children = next;
1099
- return;
1215
+ return committed;
1100
1216
  }
1101
- patchKeyedChildren(curr, next, domParent, frame, scheduler, styles, vParent, rootTarget, cursor, anchor);
1102
- return;
1217
+ return patchKeyedChildren(curr, next, domParent, vParent, context, cursor, anchor);
1103
1218
  }
1104
1219
  function parentUsesInnerHTML(parent) {
1105
1220
  return isHostNode(parent) && getHostProps(parent).innerHTML != null;
1106
1221
  }
1107
1222
  function canBulkClearChildren(children) {
1108
- for (let child of children) {
1109
- if (!canBulkClearNode(child))
1223
+ for (let i = 0; i < children.length; i++) {
1224
+ if (!canBulkClearNode(children[i]))
1110
1225
  return false;
1111
1226
  }
1112
1227
  return true;
@@ -1117,11 +1232,7 @@ function canBulkClearNode(node) {
1117
1232
  if (isCommittedHostNode(node)) {
1118
1233
  if (node._mixState)
1119
1234
  return false;
1120
- for (let child of node._children) {
1121
- if (!canBulkClearNode(child))
1122
- return false;
1123
- }
1124
- return true;
1235
+ return canBulkClearChildren(node._children);
1125
1236
  }
1126
1237
  if (isFragmentNode(node)) {
1127
1238
  return canBulkClearChildren(node._children);
@@ -1132,8 +1243,8 @@ function canBulkClearNode(node) {
1132
1243
  return false;
1133
1244
  }
1134
1245
  function hasKeyedChildren(children) {
1135
- for (let node of children) {
1136
- if (node.key != null)
1246
+ for (let i = 0; i < children.length; i++) {
1247
+ if (children[i].key != null)
1137
1248
  return true;
1138
1249
  }
1139
1250
  return false;
@@ -1157,11 +1268,11 @@ function warnDuplicateKeys(children) {
1157
1268
  }
1158
1269
  }
1159
1270
  if (duplicateKeys?.size) {
1160
- let quotedKeys = Array.from(duplicateKeys, (key) => `"${key}"`);
1271
+ let quotedKeys = Array.from(duplicateKeys, (key) => `"${String(key)}"`);
1161
1272
  console.warn(`Duplicate keys detected in siblings: ${quotedKeys.join(', ')}. Keys should be unique.`);
1162
1273
  }
1163
1274
  }
1164
- function patchKeyedChildren(curr, next, domParent, frame, scheduler, styles, vParent, rootTarget, cursor, anchor) {
1275
+ function patchKeyedChildren(curr, next, domParent, vParent, context, cursor, anchor) {
1165
1276
  let matches = matchKeyedChildrenInOrder(curr, next) ??
1166
1277
  matchKeyedChildrenAfterSingleRemoval(curr, next) ??
1167
1278
  matchKeyedChildrenAfterPairSwap(curr, next);
@@ -1169,34 +1280,36 @@ function patchKeyedChildren(curr, next, domParent, frame, scheduler, styles, vPa
1169
1280
  warnDuplicateKeys(next);
1170
1281
  matches = matchKeyedChildren(curr, next);
1171
1282
  }
1283
+ let committed = next;
1172
1284
  let matchAnalysis = analyzeKeyedChildMatches(curr.length, matches);
1173
1285
  if (matchAnalysis.hasRemovals) {
1174
1286
  let usedOldIndexes = new Uint8Array(curr.length);
1175
- for (let match of matches) {
1176
- if (match.oldIndex >= 0) {
1177
- usedOldIndexes[match.oldIndex] = 1;
1287
+ for (let index = 0; index < matches.length; index++) {
1288
+ let oldIndex = matches[index];
1289
+ if (oldIndex >= 0) {
1290
+ usedOldIndexes[oldIndex] = 1;
1178
1291
  }
1179
1292
  }
1180
1293
  for (let oldIndex = 0; oldIndex < curr.length; oldIndex++) {
1181
1294
  if (usedOldIndexes[oldIndex] === 0) {
1182
- remove(curr[oldIndex], domParent, scheduler, styles);
1295
+ remove(curr[oldIndex], domParent, context);
1183
1296
  }
1184
1297
  }
1185
1298
  }
1186
- vParent._children = next;
1187
1299
  for (let index = 0; index < next.length; index++) {
1188
- let match = matches[index];
1189
- let oldNode = match.oldIndex >= 0 ? curr[match.oldIndex] : null;
1190
- diffVNodes(oldNode, next[index], domParent, frame, scheduler, styles, vParent, rootTarget, anchor, cursor);
1300
+ let oldIndex = matches[index];
1301
+ let oldNode = oldIndex >= 0 ? curr[oldIndex] : null;
1302
+ committed[index] = diffVNodes(oldNode, next[index], domParent, vParent, context, anchor, cursor);
1191
1303
  }
1304
+ let committedChildren = committed;
1192
1305
  if (matchAnalysis.canSkipPlacement) {
1193
- return;
1306
+ return committedChildren;
1194
1307
  }
1195
1308
  let stableIndexes = lisMatches(matches);
1196
1309
  let stableCursor = stableIndexes.length - 1;
1197
1310
  let placementAnchor = anchor ?? null;
1198
1311
  for (let index = next.length - 1; index >= 0; index--) {
1199
- let nextNode = next[index];
1312
+ let nextNode = committedChildren[index];
1200
1313
  let isStable = stableIndexes[stableCursor] === index;
1201
1314
  if (isStable) {
1202
1315
  stableCursor--;
@@ -1206,7 +1319,11 @@ function patchKeyedChildren(curr, next, domParent, frame, scheduler, styles, vPa
1206
1319
  }
1207
1320
  placementAnchor = findFirstDomAnchor(nextNode) ?? placementAnchor;
1208
1321
  }
1322
+ return committedChildren;
1209
1323
  }
1324
+ // Keyed child matches are arrays of old indexes (-1 = no match / new node),
1325
+ // parallel to `next`. Plain numbers instead of wrapper objects keep large
1326
+ // keyed diffs (1000-row tables) allocation-free.
1210
1327
  function matchKeyedChildren(curr, next) {
1211
1328
  let oldKeyMap = new Map();
1212
1329
  let usedOldIndexes = new Set();
@@ -1216,7 +1333,9 @@ function matchKeyedChildren(curr, next) {
1216
1333
  if (key != null)
1217
1334
  oldKeyMap.set(key, index);
1218
1335
  }
1219
- return next.map((nextNode) => {
1336
+ let matches = [];
1337
+ for (let nextIndex = 0; nextIndex < next.length; nextIndex++) {
1338
+ let nextNode = next[nextIndex];
1220
1339
  let oldIndex = -1;
1221
1340
  if (nextNode.key != null) {
1222
1341
  let keyedOldIndex = oldKeyMap.get(nextNode.key);
@@ -1240,8 +1359,9 @@ function matchKeyedChildren(curr, next) {
1240
1359
  }
1241
1360
  if (oldIndex >= 0)
1242
1361
  usedOldIndexes.add(oldIndex);
1243
- return { oldIndex };
1244
- });
1362
+ matches.push(oldIndex);
1363
+ }
1364
+ return matches;
1245
1365
  }
1246
1366
  function matchKeyedChildrenInOrder(curr, next) {
1247
1367
  let length = Math.min(curr.length, next.length);
@@ -1254,12 +1374,12 @@ function matchKeyedChildrenInOrder(curr, next) {
1254
1374
  if (oldNode.key !== nextNode.key || oldNode.type !== nextNode.type) {
1255
1375
  return null;
1256
1376
  }
1257
- matches.push({ oldIndex: index });
1377
+ matches.push(index);
1258
1378
  }
1259
1379
  for (let index = length; index < next.length; index++) {
1260
1380
  if (next[index].key == null)
1261
1381
  return null;
1262
- matches.push({ oldIndex: -1 });
1382
+ matches.push(-1);
1263
1383
  }
1264
1384
  return matches;
1265
1385
  }
@@ -1275,7 +1395,7 @@ function matchKeyedChildrenAfterSingleRemoval(curr, next) {
1275
1395
  return null;
1276
1396
  let oldNode = curr[oldIndex];
1277
1397
  if (oldNode.key === nextNode.key && oldNode.type === nextNode.type) {
1278
- matches.push({ oldIndex });
1398
+ matches.push(oldIndex);
1279
1399
  oldIndex++;
1280
1400
  continue;
1281
1401
  }
@@ -1287,7 +1407,7 @@ function matchKeyedChildrenAfterSingleRemoval(curr, next) {
1287
1407
  if (oldNode.key !== nextNode.key || oldNode.type !== nextNode.type) {
1288
1408
  return null;
1289
1409
  }
1290
- matches.push({ oldIndex });
1410
+ matches.push(oldIndex);
1291
1411
  oldIndex++;
1292
1412
  }
1293
1413
  return matches;
@@ -1304,7 +1424,7 @@ function matchKeyedChildrenAfterPairSwap(curr, next) {
1304
1424
  return null;
1305
1425
  let oldNode = curr[index];
1306
1426
  if (oldNode.key === nextNode.key && oldNode.type === nextNode.type) {
1307
- matches.push({ oldIndex: index });
1427
+ matches.push(index);
1308
1428
  continue;
1309
1429
  }
1310
1430
  if (firstMismatch === -1) {
@@ -1316,7 +1436,7 @@ function matchKeyedChildrenAfterPairSwap(curr, next) {
1316
1436
  else {
1317
1437
  return null;
1318
1438
  }
1319
- matches.push({ oldIndex: -1 });
1439
+ matches.push(-1);
1320
1440
  }
1321
1441
  if (firstMismatch === -1)
1322
1442
  return matches;
@@ -1332,8 +1452,8 @@ function matchKeyedChildrenAfterPairSwap(curr, next) {
1332
1452
  secondOldNode.type !== firstNextNode.type) {
1333
1453
  return null;
1334
1454
  }
1335
- matches[firstMismatch] = { oldIndex: secondMismatch };
1336
- matches[secondMismatch] = { oldIndex: firstMismatch };
1455
+ matches[firstMismatch] = secondMismatch;
1456
+ matches[secondMismatch] = firstMismatch;
1337
1457
  return matches;
1338
1458
  }
1339
1459
  function analyzeKeyedChildMatches(currentLength, matches) {
@@ -1341,16 +1461,17 @@ function analyzeKeyedChildMatches(currentLength, matches) {
1341
1461
  let canSkipPlacement = true;
1342
1462
  let lastOldIndex = -1;
1343
1463
  let sawNewNode = false;
1344
- for (let match of matches) {
1345
- if (match.oldIndex < 0) {
1464
+ for (let index = 0; index < matches.length; index++) {
1465
+ let oldIndex = matches[index];
1466
+ if (oldIndex < 0) {
1346
1467
  hasRemovals = true;
1347
1468
  sawNewNode = true;
1348
1469
  continue;
1349
1470
  }
1350
- if (sawNewNode || match.oldIndex < lastOldIndex) {
1471
+ if (sawNewNode || oldIndex < lastOldIndex) {
1351
1472
  canSkipPlacement = false;
1352
1473
  }
1353
- lastOldIndex = match.oldIndex;
1474
+ lastOldIndex = oldIndex;
1354
1475
  }
1355
1476
  return { hasRemovals, canSkipPlacement };
1356
1477
  }
@@ -1358,14 +1479,14 @@ function lisMatches(matches) {
1358
1479
  let predecessors = Array.from({ length: matches.length });
1359
1480
  let tails = [];
1360
1481
  for (let index = 0; index < matches.length; index++) {
1361
- let value = matches[index].oldIndex + 1;
1482
+ let value = matches[index] + 1;
1362
1483
  if (value === 0)
1363
1484
  continue;
1364
1485
  let low = 0;
1365
1486
  let high = tails.length;
1366
1487
  while (low < high) {
1367
1488
  let middle = (low + high) >> 1;
1368
- if (matches[tails[middle]].oldIndex + 1 < value) {
1489
+ if (matches[tails[middle]] + 1 < value) {
1369
1490
  low = middle + 1;
1370
1491
  }
1371
1492
  else {
@@ -1404,11 +1525,12 @@ export function findFirstDomAnchor(node) {
1404
1525
  return node._dom;
1405
1526
  if (isCommittedComponentNode(node))
1406
1527
  return findFirstDomAnchor(node._content);
1407
- if (node.type === Frame)
1408
- return node._rangeStart ?? null;
1528
+ if (node.kind === 'frame')
1529
+ return node._rangeStart;
1409
1530
  if (isFragmentNode(node)) {
1410
- for (let child of node._children) {
1411
- let dom = findFirstDomAnchor(child);
1531
+ let children = node._children;
1532
+ for (let i = 0; i < children.length; i++) {
1533
+ let dom = findFirstDomAnchor(children[i]);
1412
1534
  if (dom)
1413
1535
  return dom;
1414
1536
  }
@@ -1424,8 +1546,8 @@ export function findLastDomAnchor(node) {
1424
1546
  return node._dom;
1425
1547
  if (isCommittedComponentNode(node))
1426
1548
  return findLastDomAnchor(node._content);
1427
- if (node.type === Frame)
1428
- return node._rangeEnd ?? null;
1549
+ if (node.kind === 'frame')
1550
+ return node._rangeEnd;
1429
1551
  if (isFragmentNode(node)) {
1430
1552
  for (let i = node._children.length - 1; i >= 0; i--) {
1431
1553
  let dom = findLastDomAnchor(node._children[i]);
@@ -1464,6 +1586,8 @@ function shouldDispatchInlineMixinLifecycle(node) {
1464
1586
  if (!parents?.length)
1465
1587
  return true;
1466
1588
  for (let parent of parents) {
1589
+ if (!(parent instanceof Node))
1590
+ continue;
1467
1591
  let parentNode = parent;
1468
1592
  if (parentNode === node)
1469
1593
  return false;
@@ -1474,11 +1598,16 @@ function shouldDispatchInlineMixinLifecycle(node) {
1474
1598
  }
1475
1599
  export function findNextSiblingDomAnchor(curr) {
1476
1600
  let vParent = curr._parent;
1477
- if (!vParent || !Array.isArray(vParent._children))
1478
- return null;
1479
- let children = vParent._children;
1480
- if (children.length === 0)
1601
+ if (vParent.kind === 'component')
1481
1602
  return findNextSiblingDomAnchor(vParent);
1603
+ let children = vParent._children;
1604
+ if (children.length === 0) {
1605
+ if (vParent.kind === 'root')
1606
+ return vParent._rangeEnd ?? null;
1607
+ if (vParent.kind === 'fragment')
1608
+ return findNextSiblingDomAnchor(vParent);
1609
+ return null;
1610
+ }
1482
1611
  let idx = children.indexOf(curr);
1483
1612
  if (idx === -1)
1484
1613
  return null;
@@ -1490,34 +1619,33 @@ export function findNextSiblingDomAnchor(curr) {
1490
1619
  if (isFragmentNode(vParent)) {
1491
1620
  return findNextSiblingDomAnchor(vParent);
1492
1621
  }
1493
- return null;
1622
+ return vParent.kind === 'root' ? (vParent._rangeEnd ?? null) : null;
1494
1623
  }
1495
- function reclaimPersistedMixinNode(persistedNode, newNode, frame, scheduler, styles, vParent, rootTarget) {
1624
+ function reclaimPersistedMixinNode(persistedNode, newNode, vParent, context) {
1625
+ let { frame, scheduler, styles } = context;
1496
1626
  cancelPendingMixinRemoval(persistedNode._mixState);
1497
1627
  unmarkNodePersistedByMixins(persistedNode);
1498
- newNode._dom = persistedNode._dom;
1499
- newNode._parent = vParent;
1500
- newNode._controller = persistedNode._controller;
1501
- newNode._mixState = persistedNode._mixState;
1502
- newNode._directEventState = persistedNode._directEventState;
1503
- newNode._controlledState = persistedNode._controlledState;
1504
1628
  let prevProps = getHostProps(persistedNode);
1505
- let nextProps = resolveNodeMixProps(newNode, frame, scheduler, newNode._mixState);
1629
+ let childInputs = newNode._children;
1630
+ let resolved = resolveNodeMixProps(newNode, vParent, frame, scheduler, persistedNode._mixState);
1631
+ let nextProps = resolved.props;
1632
+ let committed = commitHostNode(newNode, vParent, getSvgContext(vParent, newNode), persistedNode._dom, resolved);
1633
+ committed._directEventState = persistedNode._directEventState;
1634
+ committed._controlledState = persistedNode._controlledState;
1506
1635
  if (shouldDispatchInlineMixinLifecycle(persistedNode._dom)) {
1507
- dispatchMixinBeforeUpdate(newNode._mixState);
1636
+ dispatchMixinBeforeUpdate(committed._mixState);
1508
1637
  }
1509
1638
  patchHostProps(prevProps, nextProps, persistedNode._dom);
1510
- syncDirectEventListeners(newNode);
1511
- ensureControlledReflection(newNode, scheduler);
1512
- syncControlledReflection(newNode, nextProps);
1513
- diffChildren(persistedNode._children, newNode._children, persistedNode._dom, frame, scheduler, styles, newNode, rootTarget);
1514
- if (newNode._mixState) {
1515
- bindNodeMixRuntime(newNode, frame, scheduler, styles, true);
1639
+ syncDirectEventListeners(committed);
1640
+ ensureControlledReflection(committed, scheduler);
1641
+ syncControlledReflection(committed, nextProps);
1642
+ committed._children = diffChildren(persistedNode._children, childInputs, persistedNode._dom, committed, context);
1643
+ if (committed._mixState) {
1644
+ bindNodeMixRuntime(committed, frame, scheduler, styles, true);
1516
1645
  }
1517
1646
  if (shouldDispatchInlineMixinLifecycle(persistedNode._dom)) {
1518
- scheduler.enqueueCommitPhase([
1519
- () => dispatchMixinCommit(newNode._mixState),
1520
- ]);
1647
+ scheduler.enqueueCommitPhase([() => dispatchMixinCommit(committed._mixState)]);
1521
1648
  }
1649
+ return committed;
1522
1650
  }
1523
1651
  //# sourceMappingURL=reconcile.js.map