@remix-run/ui 0.3.0 → 0.5.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 (434) hide show
  1. package/README.md +116 -157
  2. package/dist/accordion/index.d.ts +46 -0
  3. package/dist/accordion/index.js +142 -0
  4. package/dist/accordion/index.js.map +1 -0
  5. package/dist/accordion/primitives.d.ts +104 -0
  6. package/dist/accordion/primitives.js +312 -0
  7. package/dist/accordion/primitives.js.map +1 -0
  8. package/dist/{components/anchor/anchor.js → anchor/index.js} +1 -1
  9. package/dist/anchor/index.js.map +1 -0
  10. package/dist/animation/animate-layout-mixin.js +1 -1
  11. package/dist/animation/animate-layout-mixin.js.map +1 -1
  12. package/dist/animation/animate-mixins.js +2 -2
  13. package/dist/animation/animate-mixins.js.map +1 -1
  14. package/dist/animation/demos/color-interpolation.js +1 -1
  15. package/dist/animation/demos/color-interpolation.js.map +1 -1
  16. package/dist/animation/demos/drag-release.d.ts +1 -1
  17. package/dist/animation/demos/hold-to-confirm.d.ts +3 -3
  18. package/dist/animation/demos/material-ripple.js +1 -3
  19. package/dist/animation/demos/material-ripple.js.map +1 -1
  20. package/dist/animation/demos/reordering.js +9 -6
  21. package/dist/animation/demos/reordering.js.map +1 -1
  22. package/dist/animation/index.js +4 -4
  23. package/dist/animation/spring.js.map +1 -1
  24. package/dist/{components/breadcrumbs/breadcrumbs.js → breadcrumbs/index.js} +19 -19
  25. package/dist/breadcrumbs/index.js.map +1 -0
  26. package/dist/button/index.d.ts +15 -0
  27. package/dist/button/index.js +128 -0
  28. package/dist/button/index.js.map +1 -0
  29. package/dist/checkbox/index.d.ts +14 -0
  30. package/dist/checkbox/index.js +127 -0
  31. package/dist/checkbox/index.js.map +1 -0
  32. package/dist/combobox/index.d.ts +21 -0
  33. package/dist/combobox/index.js +58 -0
  34. package/dist/combobox/index.js.map +1 -0
  35. package/dist/{components/combobox/combobox.d.ts → combobox/primitives.d.ts} +4 -8
  36. package/dist/{components/combobox/combobox.js → combobox/primitives.js} +8 -61
  37. package/dist/combobox/primitives.js.map +1 -0
  38. package/dist/dev/refresh.d.ts +1 -0
  39. package/dist/dev/refresh.js +2 -0
  40. package/dist/dev/refresh.js.map +1 -0
  41. package/dist/index.d.ts +1 -1
  42. package/dist/index.js +14 -14
  43. package/dist/input/index.d.ts +13 -0
  44. package/dist/input/index.js +121 -0
  45. package/dist/input/index.js.map +1 -0
  46. package/dist/jsx-runtime.js +2 -2
  47. package/dist/listbox/flash-attribute.js.map +1 -0
  48. package/dist/{components/listbox/listbox.d.ts → listbox/index.d.ts} +2 -6
  49. package/dist/{components/listbox/listbox.js → listbox/index.js} +4 -82
  50. package/dist/listbox/index.js.map +1 -0
  51. package/dist/menu/hover-aim.js.map +1 -0
  52. package/dist/menu/index.d.ts +42 -0
  53. package/dist/menu/index.js +250 -0
  54. package/dist/menu/index.js.map +1 -0
  55. package/dist/{components/menu/menu.d.ts → menu/primitives.d.ts} +4 -33
  56. package/dist/{components/menu/menu.js → menu/primitives.js} +98 -315
  57. package/dist/menu/primitives.js.map +1 -0
  58. package/dist/{components/popover/popover.d.ts → popover/index.d.ts} +2 -4
  59. package/dist/{components/popover/popover.js → popover/index.js} +5 -48
  60. package/dist/popover/index.js.map +1 -0
  61. package/dist/{interactions/outside-click/outside-click-mixin.js → popover/outside-click.js} +1 -1
  62. package/dist/popover/outside-click.js.map +1 -0
  63. package/dist/popover/scroll-lock.d.ts +1 -0
  64. package/dist/{utils → popover}/scroll-lock.js +0 -18
  65. package/dist/popover/scroll-lock.js.map +1 -0
  66. package/dist/radio/index.d.ts +12 -0
  67. package/dist/radio/index.js +77 -0
  68. package/dist/radio/index.js.map +1 -0
  69. package/dist/runtime/client-entry-boundary.d.ts +14 -0
  70. package/dist/runtime/client-entry-boundary.js +29 -0
  71. package/dist/runtime/client-entry-boundary.js.map +1 -0
  72. package/dist/runtime/component.d.ts +19 -4
  73. package/dist/runtime/component.js +39 -9
  74. package/dist/runtime/component.js.map +1 -1
  75. package/dist/runtime/core/attributes.js +36 -3
  76. package/dist/runtime/core/attributes.js.map +1 -1
  77. package/dist/runtime/core/children.d.ts +2 -0
  78. package/dist/runtime/core/children.js +26 -2
  79. package/dist/runtime/core/children.js.map +1 -1
  80. package/dist/runtime/core/props.js +2 -2
  81. package/dist/runtime/core/vnode.d.ts +3 -3
  82. package/dist/runtime/core/vnode.js +22 -1
  83. package/dist/runtime/core/vnode.js.map +1 -1
  84. package/dist/runtime/create-element.d.ts +2 -2
  85. package/dist/runtime/create-element.js +3 -2
  86. package/dist/runtime/create-element.js.map +1 -1
  87. package/dist/runtime/diff-dom.js +294 -219
  88. package/dist/runtime/diff-dom.js.map +1 -1
  89. package/dist/runtime/diff-props.js +3 -3
  90. package/dist/runtime/dom.d.ts +12 -0
  91. package/dist/runtime/element-function.d.ts +3 -0
  92. package/dist/runtime/element-function.js +2 -0
  93. package/dist/runtime/element-function.js.map +1 -0
  94. package/dist/runtime/form-navigation.d.ts +25 -0
  95. package/dist/runtime/form-navigation.js +132 -0
  96. package/dist/runtime/form-navigation.js.map +1 -0
  97. package/dist/runtime/frame-resolution.d.ts +5 -0
  98. package/dist/runtime/frame-resolution.js +10 -0
  99. package/dist/runtime/frame-resolution.js.map +1 -0
  100. package/dist/runtime/frame.d.ts +58 -15
  101. package/dist/runtime/frame.js +367 -114
  102. package/dist/runtime/frame.js.map +1 -1
  103. package/dist/runtime/jsx.d.ts +1 -1
  104. package/dist/runtime/jsx.js +1 -1
  105. package/dist/runtime/jsx.js.map +1 -1
  106. package/dist/runtime/key.d.ts +1 -0
  107. package/dist/runtime/key.js +2 -0
  108. package/dist/runtime/key.js.map +1 -0
  109. package/dist/runtime/mixins/attrs-mixin.js +2 -2
  110. package/dist/runtime/mixins/link-mixin.js +4 -3
  111. package/dist/runtime/mixins/link-mixin.js.map +1 -1
  112. package/dist/runtime/mixins/mixin.d.ts +15 -10
  113. package/dist/runtime/mixins/mixin.js +4 -4
  114. package/dist/runtime/mixins/mixin.js.map +1 -1
  115. package/dist/runtime/mixins/on-mixin.js +1 -1
  116. package/dist/runtime/mixins/ref-mixin.js +1 -1
  117. package/dist/runtime/navigation.d.ts +4 -1
  118. package/dist/runtime/navigation.js +157 -32
  119. package/dist/runtime/navigation.js.map +1 -1
  120. package/dist/runtime/reconcile.d.ts +11 -10
  121. package/dist/runtime/reconcile.js +557 -423
  122. package/dist/runtime/reconcile.js.map +1 -1
  123. package/dist/runtime/refresh.d.ts +28 -0
  124. package/dist/runtime/refresh.js +31 -0
  125. package/dist/runtime/refresh.js.map +1 -0
  126. package/dist/runtime/render.js +1 -1
  127. package/dist/runtime/render.js.map +1 -1
  128. package/dist/runtime/run.d.ts +3 -1
  129. package/dist/runtime/run.js +15 -7
  130. package/dist/runtime/run.js.map +1 -1
  131. package/dist/runtime/scheduler.js +8 -16
  132. package/dist/runtime/scheduler.js.map +1 -1
  133. package/dist/runtime/to-vnode.d.ts +2 -2
  134. package/dist/runtime/to-vnode.js +68 -20
  135. package/dist/runtime/to-vnode.js.map +1 -1
  136. package/dist/runtime/vdom.d.ts +4 -2
  137. package/dist/runtime/vdom.js +89 -41
  138. package/dist/runtime/vdom.js.map +1 -1
  139. package/dist/runtime/vnode.d.ts +138 -67
  140. package/dist/runtime/vnode.js +17 -12
  141. package/dist/runtime/vnode.js.map +1 -1
  142. package/dist/select/index.d.ts +19 -0
  143. package/dist/select/index.js +95 -0
  144. package/dist/select/index.js.map +1 -0
  145. package/dist/select/keydown.js.map +1 -0
  146. package/dist/{components/select/select.d.ts → select/primitives.d.ts} +5 -7
  147. package/dist/{components/select/select.js → select/primitives.js} +11 -63
  148. package/dist/select/primitives.js.map +1 -0
  149. package/dist/server/stream.js +22 -29
  150. package/dist/server/stream.js.map +1 -1
  151. package/dist/shared/focus-styles.d.ts +1 -0
  152. package/dist/shared/focus-styles.js +2 -0
  153. package/dist/shared/focus-styles.js.map +1 -0
  154. package/dist/shared/icons.d.ts +9 -0
  155. package/dist/shared/icons.js +42 -0
  156. package/dist/shared/icons.js.map +1 -0
  157. package/dist/shared/listbox-popover-styles.d.ts +6 -0
  158. package/dist/shared/listbox-popover-styles.js +137 -0
  159. package/dist/shared/listbox-popover-styles.js.map +1 -0
  160. package/dist/shared/style-values.d.ts +78 -0
  161. package/dist/shared/style-values.js +88 -0
  162. package/dist/shared/style-values.js.map +1 -0
  163. package/dist/{interactions/typeahead/typeahead-mixin.js → shared/typeahead.js} +1 -1
  164. package/dist/shared/typeahead.js.map +1 -0
  165. package/dist/shared/wait-for-css-transition.js.map +1 -0
  166. package/dist/shared/wait.js.map +1 -0
  167. package/dist/style/css-mixin.js +3 -3
  168. package/dist/style/index.js +2 -2
  169. package/dist/style/style.js +25 -6
  170. package/dist/style/style.js.map +1 -1
  171. package/dist/style/stylesheet.d.ts +0 -1
  172. package/dist/style/stylesheet.js +13 -54
  173. package/dist/style/stylesheet.js.map +1 -1
  174. package/dist/tabs/index.d.ts +32 -0
  175. package/dist/tabs/index.js +157 -0
  176. package/dist/tabs/index.js.map +1 -0
  177. package/dist/tabs/primitives.d.ts +57 -0
  178. package/dist/tabs/primitives.js +238 -0
  179. package/dist/tabs/primitives.js.map +1 -0
  180. package/dist/test.js +1 -1
  181. package/dist/toggle/index.d.ts +9 -0
  182. package/dist/toggle/index.js +108 -0
  183. package/dist/toggle/index.js.map +1 -0
  184. package/dist/toggle/primitives.d.ts +29 -0
  185. package/dist/toggle/primitives.js +127 -0
  186. package/dist/toggle/primitives.js.map +1 -0
  187. package/package.json +81 -49
  188. package/src/{components/accordion → accordion}/README.md +50 -14
  189. package/src/accordion/index.tsx +285 -0
  190. package/src/accordion/primitives.tsx +512 -0
  191. package/src/{components/anchor → anchor}/README.md +37 -2
  192. package/src/animation/animate-mixins.ts +3 -3
  193. package/src/animation/demos/color-interpolation.tsx +1 -1
  194. package/src/animation/demos/reordering.tsx +9 -6
  195. package/src/{components/breadcrumbs → breadcrumbs}/README.md +4 -4
  196. package/src/{components/breadcrumbs/breadcrumbs.tsx → breadcrumbs/index.tsx} +18 -18
  197. package/src/button/README.md +44 -0
  198. package/src/button/index.ts +168 -0
  199. package/src/checkbox/README.md +59 -0
  200. package/src/checkbox/index.ts +164 -0
  201. package/src/{components/combobox → combobox}/README.md +58 -9
  202. package/src/combobox/index.tsx +112 -0
  203. package/src/{components/combobox/combobox.tsx → combobox/primitives.tsx} +5 -91
  204. package/src/dev/refresh.ts +1 -0
  205. package/src/index.ts +1 -1
  206. package/src/input/README.md +52 -0
  207. package/src/input/index.ts +147 -0
  208. package/src/{components/listbox → listbox}/README.md +9 -41
  209. package/src/{components/listbox/listbox.ts → listbox/index.ts} +2 -87
  210. package/src/{components/menu → menu}/README.md +55 -14
  211. package/src/menu/index.tsx +354 -0
  212. package/src/{components/menu/menu.tsx → menu/primitives.tsx} +99 -413
  213. package/src/{components/popover → popover}/README.md +20 -39
  214. package/src/{components/popover/popover.ts → popover/index.ts} +3 -57
  215. package/src/{utils → popover}/scroll-lock.ts +0 -28
  216. package/src/radio/README.md +53 -0
  217. package/src/radio/index.ts +101 -0
  218. package/src/runtime/client-entry-boundary.ts +54 -0
  219. package/src/runtime/component.ts +65 -16
  220. package/src/runtime/core/attributes.ts +35 -1
  221. package/src/runtime/core/children.ts +24 -2
  222. package/src/runtime/core/vnode.ts +25 -4
  223. package/src/runtime/create-element.ts +6 -5
  224. package/src/runtime/demos/readme.demo.tsx +20 -18
  225. package/src/runtime/diff-dom.ts +358 -243
  226. package/src/runtime/dom.ts +331 -214
  227. package/src/runtime/element-function.ts +1 -0
  228. package/src/runtime/event-listeners.ts +1 -1
  229. package/src/runtime/form-navigation.ts +184 -0
  230. package/src/runtime/frame-resolution.ts +13 -0
  231. package/src/runtime/frame.ts +482 -124
  232. package/src/runtime/jsx.ts +3 -5
  233. package/src/runtime/key.ts +1 -0
  234. package/src/runtime/mixins/link-mixin.ts +1 -0
  235. package/src/runtime/mixins/mixin.ts +21 -18
  236. package/src/runtime/navigation.ts +204 -30
  237. package/src/runtime/reconcile.ts +769 -760
  238. package/src/runtime/refresh.ts +46 -0
  239. package/src/runtime/run.ts +12 -2
  240. package/src/runtime/scheduler.ts +9 -29
  241. package/src/runtime/to-vnode.ts +85 -23
  242. package/src/runtime/vdom.ts +91 -69
  243. package/src/runtime/vnode.ts +192 -88
  244. package/src/{components/select → select}/README.md +29 -19
  245. package/src/select/index.tsx +159 -0
  246. package/src/{components/select/select.tsx → select/primitives.tsx} +10 -98
  247. package/src/server/stream.ts +25 -31
  248. package/src/shared/focus-styles.ts +2 -0
  249. package/src/shared/icons.tsx +62 -0
  250. package/src/shared/listbox-popover-styles.ts +146 -0
  251. package/src/shared/style-values.ts +165 -0
  252. package/src/style/style.ts +25 -6
  253. package/src/style/stylesheet.ts +32 -57
  254. package/src/tabs/README.md +141 -0
  255. package/src/tabs/index.tsx +245 -0
  256. package/src/tabs/primitives.ts +365 -0
  257. package/src/test/README.md +151 -60
  258. package/src/toggle/README.md +56 -0
  259. package/src/toggle/index.tsx +136 -0
  260. package/src/toggle/primitives.ts +192 -0
  261. package/dist/animation/demos/animation.demo.d.ts +0 -5
  262. package/dist/animation/demos/animation.demo.js +0 -92
  263. package/dist/animation/demos/animation.demo.js.map +0 -1
  264. package/dist/animation/demos/spring.demo.d.ts +0 -6
  265. package/dist/animation/demos/spring.demo.js +0 -304
  266. package/dist/animation/demos/spring.demo.js.map +0 -1
  267. package/dist/components/accordion/accordion.d.ts +0 -92
  268. package/dist/components/accordion/accordion.js +0 -335
  269. package/dist/components/accordion/accordion.js.map +0 -1
  270. package/dist/components/accordion/demos/card.demo.d.ts +0 -7
  271. package/dist/components/accordion/demos/card.demo.js +0 -59
  272. package/dist/components/accordion/demos/card.demo.js.map +0 -1
  273. package/dist/components/accordion/demos/multiple.demo.d.ts +0 -7
  274. package/dist/components/accordion/demos/multiple.demo.js +0 -20
  275. package/dist/components/accordion/demos/multiple.demo.js.map +0 -1
  276. package/dist/components/accordion/demos/overview.demo.d.ts +0 -7
  277. package/dist/components/accordion/demos/overview.demo.js +0 -20
  278. package/dist/components/accordion/demos/overview.demo.js.map +0 -1
  279. package/dist/components/anchor/anchor.js.map +0 -1
  280. package/dist/components/breadcrumbs/basic.demo.d.ts +0 -6
  281. package/dist/components/breadcrumbs/basic.demo.js +0 -15
  282. package/dist/components/breadcrumbs/basic.demo.js.map +0 -1
  283. package/dist/components/breadcrumbs/breadcrumbs.js.map +0 -1
  284. package/dist/components/breadcrumbs/separator.demo.d.ts +0 -6
  285. package/dist/components/breadcrumbs/separator.demo.js +0 -15
  286. package/dist/components/breadcrumbs/separator.demo.js.map +0 -1
  287. package/dist/components/button/button.d.ts +0 -105
  288. package/dist/components/button/button.js +0 -207
  289. package/dist/components/button/button.js.map +0 -1
  290. package/dist/components/button/demos/aliases.demo.d.ts +0 -7
  291. package/dist/components/button/demos/aliases.demo.js +0 -19
  292. package/dist/components/button/demos/aliases.demo.js.map +0 -1
  293. package/dist/components/button/demos/basic.demo.d.ts +0 -7
  294. package/dist/components/button/demos/basic.demo.js +0 -20
  295. package/dist/components/button/demos/basic.demo.js.map +0 -1
  296. package/dist/components/button/demos/states.demo.d.ts +0 -7
  297. package/dist/components/button/demos/states.demo.js +0 -30
  298. package/dist/components/button/demos/states.demo.js.map +0 -1
  299. package/dist/components/combobox/combobox.js.map +0 -1
  300. package/dist/components/combobox/overview.demo.d.ts +0 -7
  301. package/dist/components/combobox/overview.demo.js +0 -154
  302. package/dist/components/combobox/overview.demo.js.map +0 -1
  303. package/dist/components/glyph/glyph.d.ts +0 -14
  304. package/dist/components/glyph/glyph.js +0 -65
  305. package/dist/components/glyph/glyph.js.map +0 -1
  306. package/dist/components/listbox/listbox.js.map +0 -1
  307. package/dist/components/listbox/overview.demo.d.ts +0 -7
  308. package/dist/components/listbox/overview.demo.js +0 -65
  309. package/dist/components/listbox/overview.demo.js.map +0 -1
  310. package/dist/components/menu/demos/bubbling.demo.d.ts +0 -6
  311. package/dist/components/menu/demos/bubbling.demo.js +0 -16
  312. package/dist/components/menu/demos/bubbling.demo.js.map +0 -1
  313. package/dist/components/menu/demos/context-trigger.demo.d.ts +0 -7
  314. package/dist/components/menu/demos/context-trigger.demo.js +0 -87
  315. package/dist/components/menu/demos/context-trigger.demo.js.map +0 -1
  316. package/dist/components/menu/demos/overview.demo.d.ts +0 -7
  317. package/dist/components/menu/demos/overview.demo.js +0 -33
  318. package/dist/components/menu/demos/overview.demo.js.map +0 -1
  319. package/dist/components/menu/hover-aim.js.map +0 -1
  320. package/dist/components/menu/menu.js.map +0 -1
  321. package/dist/components/popover/overview.demo.d.ts +0 -7
  322. package/dist/components/popover/overview.demo.js +0 -65
  323. package/dist/components/popover/overview.demo.js.map +0 -1
  324. package/dist/components/popover/popover.js.map +0 -1
  325. package/dist/components/select/deconstructed.demo.d.ts +0 -7
  326. package/dist/components/select/deconstructed.demo.js +0 -54
  327. package/dist/components/select/deconstructed.demo.js.map +0 -1
  328. package/dist/components/select/overview.demo.d.ts +0 -6
  329. package/dist/components/select/overview.demo.js +0 -28
  330. package/dist/components/select/overview.demo.js.map +0 -1
  331. package/dist/components/select/select.js.map +0 -1
  332. package/dist/components/separator/separator.d.ts +0 -2
  333. package/dist/components/separator/separator.js +0 -15
  334. package/dist/components/separator/separator.js.map +0 -1
  335. package/dist/components/tabs/tabs.d.ts +0 -78
  336. package/dist/components/tabs/tabs.js +0 -339
  337. package/dist/components/tabs/tabs.js.map +0 -1
  338. package/dist/interactions/keydown/keydown.js.map +0 -1
  339. package/dist/interactions/outside-click/outside-click-mixin.js.map +0 -1
  340. package/dist/interactions/typeahead/typeahead-mixin.js.map +0 -1
  341. package/dist/runtime/demos/basic.demo.d.ts +0 -6
  342. package/dist/runtime/demos/basic.demo.js +0 -16
  343. package/dist/runtime/demos/basic.demo.js.map +0 -1
  344. package/dist/runtime/demos/controlled-uncontrolled-values.demo.d.ts +0 -6
  345. package/dist/runtime/demos/controlled-uncontrolled-values.demo.js +0 -77
  346. package/dist/runtime/demos/controlled-uncontrolled-values.demo.js.map +0 -1
  347. package/dist/runtime/demos/keyed-list.demo.d.ts +0 -6
  348. package/dist/runtime/demos/keyed-list.demo.js +0 -62
  349. package/dist/runtime/demos/keyed-list.demo.js.map +0 -1
  350. package/dist/runtime/demos/readme.demo.d.ts +0 -5
  351. package/dist/runtime/demos/readme.demo.js +0 -396
  352. package/dist/runtime/demos/readme.demo.js.map +0 -1
  353. package/dist/theme/contract.d.ts +0 -61
  354. package/dist/theme/contract.js +0 -131
  355. package/dist/theme/contract.js.map +0 -1
  356. package/dist/theme/glyph-contract.d.ts +0 -11
  357. package/dist/theme/glyph-contract.js +0 -34
  358. package/dist/theme/glyph-contract.js.map +0 -1
  359. package/dist/theme/presets/rmx-01/glyphs.d.ts +0 -2
  360. package/dist/theme/presets/rmx-01/glyphs.js +0 -252
  361. package/dist/theme/presets/rmx-01/glyphs.js.map +0 -1
  362. package/dist/theme/presets/rmx-01/index.d.ts +0 -4
  363. package/dist/theme/presets/rmx-01/index.js +0 -119
  364. package/dist/theme/presets/rmx-01/index.js.map +0 -1
  365. package/dist/theme/runtime.d.ts +0 -2
  366. package/dist/theme/runtime.js +0 -84
  367. package/dist/theme/runtime.js.map +0 -1
  368. package/dist/theme/theme.d.ts +0 -6
  369. package/dist/theme/theme.js +0 -5
  370. package/dist/theme/theme.js.map +0 -1
  371. package/dist/utils/flash-attribute.js.map +0 -1
  372. package/dist/utils/scroll-lock.d.ts +0 -3
  373. package/dist/utils/scroll-lock.js.map +0 -1
  374. package/dist/utils/wait-for-css-transition.js.map +0 -1
  375. package/dist/utils/wait.js.map +0 -1
  376. package/src/animation/demos/animation.demo.tsx +0 -194
  377. package/src/animation/demos/spring.demo.tsx +0 -385
  378. package/src/components/accordion/accordion.tsx +0 -565
  379. package/src/components/accordion/demos/card.demo.tsx +0 -114
  380. package/src/components/accordion/demos/multiple.demo.tsx +0 -57
  381. package/src/components/accordion/demos/overview.demo.tsx +0 -57
  382. package/src/components/breadcrumbs/basic.demo.tsx +0 -18
  383. package/src/components/breadcrumbs/separator.demo.tsx +0 -19
  384. package/src/components/button/README.md +0 -44
  385. package/src/components/button/button.tsx +0 -286
  386. package/src/components/button/demos/aliases.demo.tsx +0 -28
  387. package/src/components/button/demos/basic.demo.tsx +0 -31
  388. package/src/components/button/demos/states.demo.tsx +0 -53
  389. package/src/components/combobox/overview.demo.tsx +0 -200
  390. package/src/components/glyph/README.md +0 -72
  391. package/src/components/glyph/glyph.tsx +0 -110
  392. package/src/components/listbox/overview.demo.tsx +0 -102
  393. package/src/components/menu/demos/bubbling.demo.tsx +0 -35
  394. package/src/components/menu/demos/context-trigger.demo.tsx +0 -133
  395. package/src/components/menu/demos/overview.demo.tsx +0 -77
  396. package/src/components/popover/overview.demo.tsx +0 -144
  397. package/src/components/select/deconstructed.demo.tsx +0 -96
  398. package/src/components/select/overview.demo.tsx +0 -52
  399. package/src/components/separator/separator.ts +0 -19
  400. package/src/components/tabs/README.md +0 -105
  401. package/src/components/tabs/tabs.tsx +0 -504
  402. package/src/runtime/demos/basic.demo.tsx +0 -21
  403. package/src/runtime/demos/controlled-uncontrolled-values.demo.tsx +0 -177
  404. package/src/runtime/demos/keyed-list.demo.tsx +0 -94
  405. package/src/theme/README.md +0 -103
  406. package/src/theme/contract.ts +0 -215
  407. package/src/theme/glyph-contract.ts +0 -59
  408. package/src/theme/presets/rmx-01/glyphs.tsx +0 -308
  409. package/src/theme/presets/rmx-01/index.ts +0 -123
  410. package/src/theme/runtime.ts +0 -119
  411. package/src/theme/theme.ts +0 -15
  412. package/src/utils/scroll-lock/README.md +0 -33
  413. /package/dist/{components/anchor/anchor.d.ts → anchor/index.d.ts} +0 -0
  414. /package/dist/{components/breadcrumbs/breadcrumbs.d.ts → breadcrumbs/index.d.ts} +0 -0
  415. /package/dist/{utils → listbox}/flash-attribute.d.ts +0 -0
  416. /package/dist/{utils → listbox}/flash-attribute.js +0 -0
  417. /package/dist/{components/menu → menu}/hover-aim.d.ts +0 -0
  418. /package/dist/{components/menu → menu}/hover-aim.js +0 -0
  419. /package/dist/{interactions/outside-click/outside-click-mixin.d.ts → popover/outside-click.d.ts} +0 -0
  420. /package/dist/{interactions/keydown → select}/keydown.d.ts +0 -0
  421. /package/dist/{interactions/keydown → select}/keydown.js +0 -0
  422. /package/dist/{interactions/typeahead/typeahead-mixin.d.ts → shared/typeahead.d.ts} +0 -0
  423. /package/dist/{utils → shared}/wait-for-css-transition.d.ts +0 -0
  424. /package/dist/{utils → shared}/wait-for-css-transition.js +0 -0
  425. /package/dist/{utils → shared}/wait.d.ts +0 -0
  426. /package/dist/{utils → shared}/wait.js +0 -0
  427. /package/src/{components/anchor/anchor.ts → anchor/index.ts} +0 -0
  428. /package/src/{utils → listbox}/flash-attribute.ts +0 -0
  429. /package/src/{components/menu → menu}/hover-aim.ts +0 -0
  430. /package/src/{interactions/outside-click/outside-click-mixin.ts → popover/outside-click.ts} +0 -0
  431. /package/src/{interactions/keydown → select}/keydown.ts +0 -0
  432. /package/src/{interactions/typeahead/typeahead-mixin.ts → shared/typeahead.ts} +0 -0
  433. /package/src/{utils → shared}/wait-for-css-transition.ts +0 -0
  434. /package/src/{utils → shared}/wait.ts +0 -0
@@ -1,33 +1,43 @@
1
1
  import type { Component, ComponentHandle, FrameContent, FrameHandle } from './component.ts'
2
- import { createComponent, Frame } from './component.ts'
3
- import type { Frame as FrameInstance, FrameRuntime } from './frame.ts'
4
- import { createFrame } from './frame.ts'
2
+ import { createComponent } from './component.ts'
3
+ import type { FrameRuntime } from './frame.ts'
4
+ import { createFrame, isFrameRuntime } from './frame.ts'
5
+ import { unwrapFrameResolution } from './frame-resolution.ts'
5
6
  import { createRangeRoot } from './vdom.ts'
6
7
  import type {
8
+ CommittedFragmentNode,
9
+ CommittedFrameNode,
10
+ CommittedNonRenderNode,
7
11
  ComponentNode,
8
12
  CommittedComponentNode,
9
13
  CommittedHostNode,
10
14
  CommittedTextNode,
15
+ ControlledReflectionState,
16
+ DirectEventBinding,
17
+ DirectEventState,
18
+ FrameNode,
11
19
  HostNode,
20
+ MountingComponentNode,
21
+ ReconcileContext,
22
+ RuntimeHostProps,
12
23
  TextNode,
13
- VNode,
14
- VNodeType,
24
+ VNodeInput,
25
+ VNodeParent,
26
+ CommittedVNode,
15
27
  } from './vnode.ts'
16
28
  import {
17
29
  isCommittedComponentNode,
18
- isComponentNode,
19
30
  isCommittedHostNode,
20
31
  isCommittedTextNode,
21
32
  isFragmentNode,
22
33
  isHostNode,
23
- isNonRenderNode,
24
- isTextNode,
25
34
  findContextFromAncestry,
26
35
  } from './vnode.ts'
27
36
  import { invariant } from './invariant.ts'
28
37
  import { patchHostProps } from './core/props.ts'
29
38
  import type { StyleManager } from '../style/index.ts'
30
- import type { ElementProps } from './jsx.ts'
39
+ import type { ElementFunction } from './element-function.ts'
40
+ import type { Key } from './key.ts'
31
41
  import { skipComments, logHydrationMismatch } from './client-entries.ts'
32
42
  import type { Scheduler } from './scheduler.ts'
33
43
  import { toVNode } from './to-vnode.ts'
@@ -44,6 +54,8 @@ import {
44
54
  type MixinRuntimeState,
45
55
  } from './mixins/mixin.ts'
46
56
  import { isOnMixinDescriptor, type OnMixinDescriptor } from './mixins/on-mixin.ts'
57
+ import { createComponentErrorEvent } from './error-event.ts'
58
+ import { componentStalenessCheck } from './refresh.ts'
47
59
 
48
60
  const SVG_NS = 'http://www.w3.org/2000/svg'
49
61
 
@@ -54,45 +66,41 @@ let activeSchedulerUpdateParents: ParentNode[] | undefined
54
66
 
55
67
  // Compute SVG context for a node based on its parent and type.
56
68
  // Returns true if the node is within an SVG subtree, false otherwise.
57
- function getSvgContext(vParent: VNode, nodeType: VNodeType): boolean {
69
+ function getSvgContext(vParent: VNodeParent, node: VNodeInput): boolean {
58
70
  // Only host elements (strings) can affect SVG context
59
- if (typeof nodeType === 'string') {
71
+ if (node.kind === 'host') {
60
72
  // svg element creates SVG context
61
- if (nodeType === 'svg') return true
73
+ if (node.type === 'svg') return true
62
74
  // foreignObject switches back to HTML context
63
- if (nodeType === 'foreignObject') return false
75
+ if (node.type === 'foreignObject') return false
64
76
  }
65
77
  // Otherwise inherit from parent
66
- return vParent._svg ?? false
78
+ return vParent._svg
67
79
  }
68
80
 
69
- function getHostProps(node: HostNode | CommittedHostNode): ElementProps {
70
- return node._mixedProps ?? node.props
81
+ function getHostProps(node: HostNode | CommittedHostNode): RuntimeHostProps {
82
+ return '_mixedProps' in node ? node._mixedProps : node.props
71
83
  }
72
84
 
73
85
  function markNodePersistedByMixins(node: CommittedHostNode, domParent: ParentNode, token: number) {
74
- node._persistedByMixins = true
75
- node._persistedParentByMixins = domParent
76
- node._persistedRemovalToken = token
86
+ node._persistence = { parent: domParent, token }
77
87
  persistedMixinNodes.add(node)
78
- bindMixinRuntime(node._mixState as MixinRuntimeState | undefined, undefined)
88
+ bindMixinRuntime(node._mixState, undefined)
79
89
  }
80
90
 
81
91
  function unmarkNodePersistedByMixins(node: CommittedHostNode) {
82
- node._persistedByMixins = false
83
- node._persistedParentByMixins = undefined
84
- node._persistedRemovalToken = undefined
92
+ node._persistence = undefined
85
93
  persistedMixinNodes.delete(node)
86
94
  }
87
95
 
88
96
  function findMatchingPersistedMixinNode(
89
97
  type: string,
90
- key: string | undefined,
98
+ key: Key | undefined,
91
99
  domParent: ParentNode,
92
100
  ): CommittedHostNode | null {
93
101
  if (key == null) return null
94
102
  for (let node of persistedMixinNodes) {
95
- if (node._persistedParentByMixins !== domParent) continue
103
+ if (node._persistence?.parent !== domParent) continue
96
104
  if (node.type !== type) continue
97
105
  if (node.key !== key) continue
98
106
  return node
@@ -100,33 +108,10 @@ function findMatchingPersistedMixinNode(
100
108
  return null
101
109
  }
102
110
 
103
- type ControlledReflectionState = {
104
- disposed: boolean
105
- listenersAttached: boolean
106
- pendingRestoreVersion: number
107
- managesValue: boolean
108
- managesChecked: boolean
109
- hasControlledValue: boolean
110
- controlledValue: unknown
111
- hasControlledChecked: boolean
112
- controlledChecked: unknown
113
- onInput: () => void
114
- onChange: () => void
115
- }
116
-
117
- type DirectEventBinding = {
118
- type: string
119
- handler: (event: Event, signal: AbortSignal) => void | Promise<void>
120
- capture: boolean
121
- stableHandler: ((event: Event) => void) | null
122
- reentry: AbortController | null
123
- }
124
-
125
- type DirectEventState = {
126
- bindings: DirectEventBinding[]
127
- }
128
-
129
111
  const EMPTY_DIRECT_EVENT_DESCRIPTORS: OnMixinDescriptor[] = []
112
+ const EMPTY_COMMITTED_CHILDREN: CommittedVNode[] = []
113
+
114
+ type HydrationCursor = { current: Node | null | undefined }
130
115
 
131
116
  function shouldRestoreControlledReflectionOnInput(
132
117
  node: CommittedHostNode,
@@ -144,7 +129,7 @@ function ensureControlledReflection(
144
129
  node: CommittedHostNode,
145
130
  scheduler: Scheduler,
146
131
  ): ControlledReflectionState {
147
- let existing = node._controlledState as ControlledReflectionState | undefined
132
+ let existing = node._controlledState
148
133
  if (existing) return existing
149
134
 
150
135
  let state: ControlledReflectionState = {
@@ -178,8 +163,8 @@ function ensureControlledReflection(
178
163
  return state
179
164
  }
180
165
 
181
- function syncControlledReflection(node: CommittedHostNode, props: ElementProps): void {
182
- let state = node._controlledState as ControlledReflectionState | undefined
166
+ function syncControlledReflection(node: CommittedHostNode, props: RuntimeHostProps): void {
167
+ let state = node._controlledState
183
168
  if (!state || state.disposed) return
184
169
 
185
170
  state.managesValue = canManageValue(node.type, node._dom)
@@ -191,7 +176,7 @@ function syncControlledReflection(node: CommittedHostNode, props: ElementProps):
191
176
  state.pendingRestoreVersion++
192
177
  }
193
178
 
194
- function shouldTrackControlledReflection(props: ElementProps): boolean {
179
+ function shouldTrackControlledReflection(props: RuntimeHostProps): boolean {
195
180
  return hasControlledValueProp(props) || hasControlledCheckedProp(props)
196
181
  }
197
182
 
@@ -222,7 +207,7 @@ function restoreControlledReflections(
222
207
  }
223
208
 
224
209
  function teardownControlledReflection(node: CommittedHostNode): void {
225
- let state = node._controlledState as ControlledReflectionState | undefined
210
+ let state = node._controlledState
226
211
  if (!state) return
227
212
  state.disposed = true
228
213
  state.pendingRestoreVersion++
@@ -233,16 +218,25 @@ function teardownControlledReflection(node: CommittedHostNode): void {
233
218
  }
234
219
  }
235
220
 
221
+ // See abandonDirectEventListeners: skips removeEventListener for discarded subtrees.
222
+ function abandonControlledReflection(node: CommittedHostNode): void {
223
+ let state = node._controlledState
224
+ if (!state) return
225
+ state.disposed = true
226
+ state.pendingRestoreVersion++
227
+ state.listenersAttached = false
228
+ }
229
+
236
230
  function canManageValue(type: string, element: Element): boolean {
237
231
  if (type === 'progress') return false
238
232
  return canReflectProperty(element, 'value')
239
233
  }
240
234
 
241
- function hasControlledValueProp(props: ElementProps): boolean {
235
+ function hasControlledValueProp(props: RuntimeHostProps): boolean {
242
236
  return 'value' in props && props.value !== undefined
243
237
  }
244
238
 
245
- function hasControlledCheckedProp(props: ElementProps): boolean {
239
+ function hasControlledCheckedProp(props: RuntimeHostProps): boolean {
246
240
  return 'checked' in props && props.checked !== undefined
247
241
  }
248
242
 
@@ -263,29 +257,28 @@ function setPropertyReflection(element: Element, key: string, value: unknown): v
263
257
  element[key] = value == null ? '' : value
264
258
  }
265
259
 
260
+ type ResolvedHostProps = {
261
+ props: RuntimeHostProps
262
+ mixState?: MixinRuntimeState
263
+ directEventDescriptors?: OnMixinDescriptor[]
264
+ }
265
+
266
266
  function resolveNodeMixProps(
267
- node: HostNode,
267
+ node: HostNode | CommittedHostNode,
268
+ parent: VNodeParent,
268
269
  frame: FrameHandle,
269
270
  scheduler: Scheduler,
270
271
  state?: MixinRuntimeState,
271
- ): ElementProps {
272
+ ): ResolvedHostProps {
272
273
  let mix = node.props.mix
273
274
  let directEventDescriptors = resolveDirectEventDescriptors(mix)
274
275
  if (directEventDescriptors) {
275
- if (state) {
276
- teardownMixins(state)
277
- }
278
- node._mixState = undefined
279
- node._mixedProps = node.props
280
- node._directEventDescriptors = directEventDescriptors
281
- return node.props
276
+ if (state) teardownMixins(state)
277
+ return { props: node.props, directEventDescriptors }
282
278
  }
283
279
 
284
- node._directEventDescriptors = undefined
285
280
  if (state == null && (mix == null || (Array.isArray(mix) && mix.length === 0))) {
286
- node._mixState = undefined
287
- node._mixedProps = node.props
288
- return node.props
281
+ return { props: node.props }
289
282
  }
290
283
 
291
284
  let resolved = resolveMixedProps({
@@ -297,17 +290,21 @@ function resolveNodeMixProps(
297
290
  return undefined
298
291
  }
299
292
 
300
- return findContextFromAncestry(node, type as Component)
293
+ return findContextFromAncestry(parent, type)
301
294
  },
302
295
  props: node.props,
303
296
  state,
304
297
  })
305
- node._mixState = resolved.state
298
+ return { props: resolved.props, mixState: resolved.state }
299
+ }
300
+
301
+ function applyResolvedHostProps(node: CommittedHostNode, resolved: ResolvedHostProps): void {
306
302
  node._mixedProps = resolved.props
307
- return resolved.props
303
+ node._mixState = resolved.mixState
304
+ node._directEventDescriptors = resolved.directEventDescriptors
308
305
  }
309
306
 
310
- function resolveDirectEventDescriptors(mix: ElementProps['mix']): OnMixinDescriptor[] | null {
307
+ function resolveDirectEventDescriptors(mix: RuntimeHostProps['mix']): OnMixinDescriptor[] | null {
311
308
  if (!mix) return EMPTY_DIRECT_EVENT_DESCRIPTORS
312
309
  if (!Array.isArray(mix)) {
313
310
  return isOnMixinDescriptor(mix) ? [mix] : null
@@ -317,18 +314,18 @@ function resolveDirectEventDescriptors(mix: ElementProps['mix']): OnMixinDescrip
317
314
  }
318
315
 
319
316
  function areOnMixinDescriptors(descriptors: unknown[]): descriptors is OnMixinDescriptor[] {
320
- for (let item of descriptors) {
321
- if (!isOnMixinDescriptor(item)) return false
317
+ for (let i = 0; i < descriptors.length; i++) {
318
+ if (!isOnMixinDescriptor(descriptors[i])) return false
322
319
  }
323
320
  return true
324
321
  }
325
322
 
326
323
  function enqueueMixinBindingUpdate(
327
- this: MixinRuntimeBinding,
324
+ this: MixinRuntimeBinding<CommittedHostNode>,
328
325
  done: (signal: AbortSignal) => void,
329
326
  ): void {
330
- let node = this.target as CommittedHostNode
331
- let state = node._mixState as MixinRuntimeState | undefined
327
+ let node = this.target
328
+ let state = node._mixState
332
329
  this.scheduler.enqueueWork([
333
330
  () => {
334
331
  if (state?.aborted) {
@@ -338,8 +335,13 @@ function enqueueMixinBindingUpdate(
338
335
 
339
336
  dispatchMixinBeforeUpdate(state)
340
337
  let prevProps = getHostProps(node)
341
- let nextProps = resolveNodeMixProps(node, this.frame, this.scheduler, state)
342
- patchHostProps(prevProps, nextProps, this.node)
338
+ let resolved = resolveNodeMixProps(node, node._parent, this.frame, this.scheduler, state)
339
+ applyResolvedHostProps(node, resolved)
340
+ patchHostProps(prevProps, resolved.props, this.node)
341
+ if (node._controlledState || shouldTrackControlledReflection(resolved.props)) {
342
+ ensureControlledReflection(node, this.scheduler)
343
+ syncControlledReflection(node, resolved.props)
344
+ }
343
345
 
344
346
  dispatchMixinCommit(state)
345
347
  done(state ? getMixinRuntimeSignal(state) : AbortSignal.abort())
@@ -355,12 +357,12 @@ function bindNodeMixRuntime(
355
357
  reclaimed: boolean = false,
356
358
  parent?: ParentNode,
357
359
  ) {
358
- let state = node._mixState as MixinRuntimeState | undefined
360
+ let state = node._mixState
359
361
  bindMixinRuntime(
360
362
  state,
361
363
  {
362
364
  node: node._dom,
363
- parent: parent ?? (node._dom.parentNode as ParentNode),
365
+ parent: parent ?? getRequiredDomParent(node._dom),
364
366
  key: node.key,
365
367
  target: node,
366
368
  frame,
@@ -371,12 +373,18 @@ function bindNodeMixRuntime(
371
373
  )
372
374
  }
373
375
 
374
- function isHeadHostNode(node: HostNode): boolean {
376
+ function isHeadHostNode(node: HostNode | CommittedHostNode): boolean {
375
377
  if (node.type === 'head') return true
376
378
  if (node.type.length !== 4) return false
377
379
  return node.type.toLowerCase() === 'head'
378
380
  }
379
381
 
382
+ function getRequiredDomParent(node: Node): ParentNode {
383
+ let parent = node.parentNode
384
+ invariant(parent, 'Expected mounted host node to have a DOM parent')
385
+ return parent
386
+ }
387
+
380
388
  function getDocumentHead(domParent: ParentNode): HTMLHeadElement | null {
381
389
  if (domParent instanceof Document) {
382
390
  return domParent.head
@@ -387,126 +395,199 @@ function getDocumentHead(domParent: ParentNode): HTMLHeadElement | null {
387
395
  return null
388
396
  }
389
397
 
398
+ function commitNonRenderNode(
399
+ node: VNodeInput & { kind: 'empty' },
400
+ parent: VNodeParent,
401
+ svg: boolean,
402
+ ): CommittedNonRenderNode {
403
+ let committed = node as unknown as CommittedNonRenderNode
404
+ committed._parent = parent
405
+ committed._svg = svg
406
+ return committed
407
+ }
408
+
409
+ function commitTextNode(
410
+ node: TextNode,
411
+ parent: VNodeParent,
412
+ svg: boolean,
413
+ dom: Text,
414
+ ): CommittedTextNode {
415
+ let committed = node as unknown as CommittedTextNode
416
+ committed._parent = parent
417
+ committed._svg = svg
418
+ committed._dom = dom
419
+ return committed
420
+ }
421
+
422
+ function beginFragmentNode(
423
+ node: VNodeInput & { kind: 'fragment' },
424
+ parent: VNodeParent,
425
+ svg: boolean,
426
+ ): CommittedFragmentNode {
427
+ let committed = node as unknown as CommittedFragmentNode
428
+ committed._parent = parent
429
+ committed._svg = svg
430
+ committed._children = EMPTY_COMMITTED_CHILDREN
431
+ return committed
432
+ }
433
+
434
+ function commitHostNode(
435
+ node: HostNode,
436
+ parent: VNodeParent,
437
+ svg: boolean,
438
+ dom: Element,
439
+ resolved: ResolvedHostProps,
440
+ children: CommittedVNode[] = EMPTY_COMMITTED_CHILDREN,
441
+ ): CommittedHostNode {
442
+ let committed = node as unknown as CommittedHostNode
443
+ committed._parent = parent
444
+ committed._svg = svg
445
+ committed._dom = dom
446
+ committed._children = children
447
+ committed._mixedProps = resolved.props
448
+ committed._mixState = resolved.mixState
449
+ committed._directEventDescriptors = resolved.directEventDescriptors
450
+ return committed
451
+ }
452
+
453
+ function beginComponentNode(
454
+ node: ComponentNode,
455
+ parent: VNodeParent,
456
+ svg: boolean,
457
+ handle: ComponentHandle,
458
+ context: ReconcileContext,
459
+ ): MountingComponentNode {
460
+ let mounting = node as unknown as MountingComponentNode
461
+ mounting._parent = parent
462
+ mounting._svg = svg
463
+ mounting._handle = handle
464
+ mounting._context = context
465
+ return mounting
466
+ }
467
+
468
+ function commitComponentNode(
469
+ node: MountingComponentNode,
470
+ content: CommittedVNode,
471
+ ): CommittedComponentNode {
472
+ let committed = node as CommittedComponentNode
473
+ committed._content = content
474
+ return committed
475
+ }
476
+
390
477
  export function diffVNodes(
391
- curr: VNode | null,
392
- next: VNode,
478
+ curr: CommittedVNode | null,
479
+ next: VNodeInput,
393
480
  domParent: ParentNode,
394
- frame: FrameHandle,
395
- scheduler: Scheduler,
396
- styles: StyleManager,
397
- vParent: VNode,
398
- rootTarget: EventTarget,
481
+ vParent: VNodeParent,
482
+ context: ReconcileContext,
399
483
  anchor?: Node,
400
- rootCursor?: Node | null,
401
- ): Node | null | undefined {
402
- next._parent = vParent // set parent for initial render context lookups
403
- next._svg = getSvgContext(vParent, next.type)
404
-
405
- // new
484
+ cursor?: HydrationCursor,
485
+ ): CommittedVNode {
406
486
  if (curr === null) {
407
- return insert(
408
- next,
409
- domParent,
410
- frame,
411
- scheduler,
412
- styles,
413
- vParent,
414
- rootTarget,
415
- anchor,
416
- rootCursor,
417
- )
487
+ return insert(next, domParent, vParent, context, anchor, cursor)
418
488
  }
419
489
 
420
- if (curr.type !== next.type) {
421
- replace(curr, next, domParent, frame, scheduler, styles, vParent, rootTarget, anchor)
422
- return rootCursor
423
- }
424
-
425
- if (isNonRenderNode(curr) && isNonRenderNode(next)) {
426
- return rootCursor
427
- }
428
-
429
- if (isCommittedTextNode(curr) && isTextNode(next)) {
430
- diffText(curr, next, vParent)
431
- return rootCursor
432
- }
433
-
434
- if (isCommittedHostNode(curr) && isHostNode(next)) {
435
- diffHost(curr, next, frame, scheduler, styles, vParent, rootTarget)
436
- return rootCursor
437
- }
438
-
439
- if (isCommittedComponentNode(curr) && isComponentNode(next)) {
440
- diffComponent(curr, next, frame, scheduler, styles, domParent, vParent, rootTarget)
441
- return rootCursor
490
+ if (
491
+ componentStalenessCheck !== null &&
492
+ curr.kind === 'component' &&
493
+ next.kind === 'component' &&
494
+ componentStalenessCheck(curr.type) === true
495
+ ) {
496
+ return replace(curr, next, domParent, vParent, context, anchor)
442
497
  }
443
498
 
444
- if (isFragmentNode(curr) && isFragmentNode(next)) {
445
- diffChildren(
446
- curr._children,
447
- next._children,
448
- domParent,
449
- frame,
450
- scheduler,
451
- styles,
452
- next,
453
- rootTarget,
454
- undefined,
455
- anchor,
456
- )
457
- return rootCursor
499
+ if (curr.kind !== next.kind || curr.type !== next.type) {
500
+ return replace(curr, next, domParent, vParent, context, anchor)
458
501
  }
459
502
 
460
- if (curr.type === Frame && next.type === Frame) {
461
- diffFrame(curr, next, domParent, frame, scheduler, styles, vParent, rootTarget, anchor)
462
- return rootCursor
503
+ switch (next.kind) {
504
+ case 'host': {
505
+ return diffHost(curr as CommittedHostNode, next, vParent, context)
506
+ }
507
+ case 'text': {
508
+ return diffText(curr as CommittedTextNode, next, vParent, getSvgContext(vParent, next))
509
+ }
510
+ case 'empty': {
511
+ return commitNonRenderNode(next, vParent, getSvgContext(vParent, next))
512
+ }
513
+ case 'fragment': {
514
+ let childInputs = next._children
515
+ let committed = beginFragmentNode(next, vParent, getSvgContext(vParent, next))
516
+ committed._children = diffChildren(
517
+ (curr as CommittedFragmentNode)._children,
518
+ childInputs,
519
+ domParent,
520
+ committed,
521
+ context,
522
+ undefined,
523
+ anchor,
524
+ )
525
+ return committed
526
+ }
527
+ case 'frame': {
528
+ return diffFrame(
529
+ curr as CommittedFrameNode,
530
+ next,
531
+ domParent,
532
+ vParent,
533
+ context,
534
+ getSvgContext(vParent, next),
535
+ anchor,
536
+ )
537
+ }
538
+ case 'component': {
539
+ return diffComponent(
540
+ curr as CommittedComponentNode,
541
+ next,
542
+ domParent,
543
+ vParent,
544
+ context,
545
+ getSvgContext(vParent, next),
546
+ anchor,
547
+ cursor,
548
+ )
549
+ }
463
550
  }
464
-
465
- invariant(false, 'Unexpected diff case')
466
551
  }
467
552
 
468
553
  function replace(
469
- curr: VNode,
470
- next: VNode,
554
+ curr: CommittedVNode,
555
+ next: VNodeInput,
471
556
  domParent: ParentNode,
472
- frame: FrameHandle,
473
- scheduler: Scheduler,
474
- styles: StyleManager,
475
- vParent: VNode,
476
- rootTarget: EventTarget,
557
+ vParent: VNodeParent,
558
+ context: ReconcileContext,
477
559
  anchor?: Node,
478
- ) {
560
+ ): CommittedVNode {
479
561
  let currAnchor = findFirstDomAnchor(curr)
480
562
  if (currAnchor && currAnchor.parentNode === domParent) {
481
563
  let replacementAnchor = document.createComment('rmx:replace')
482
564
  domParent.insertBefore(replacementAnchor, currAnchor)
483
565
  try {
484
- remove(curr, domParent, scheduler, styles)
485
- insert(next, domParent, frame, scheduler, styles, vParent, rootTarget, replacementAnchor)
566
+ remove(curr, domParent, context)
567
+ return insert(next, domParent, vParent, context, replacementAnchor)
486
568
  } finally {
487
569
  replacementAnchor.parentNode?.removeChild(replacementAnchor)
488
570
  }
489
- return
490
571
  }
491
572
 
492
573
  let replacementAnchor = findNextSiblingDomAnchor(curr) ?? anchor
493
- remove(curr, domParent, scheduler, styles)
494
- insert(next, domParent, frame, scheduler, styles, vParent, rootTarget, replacementAnchor)
574
+ remove(curr, domParent, context)
575
+ return insert(next, domParent, vParent, context, replacementAnchor)
495
576
  }
496
577
 
497
578
  function diffHost(
498
579
  curr: CommittedHostNode,
499
580
  next: HostNode,
500
- frame: FrameHandle,
501
- scheduler: Scheduler,
502
- styles: StyleManager,
503
- vParent: VNode,
504
- rootTarget: EventTarget,
505
- ) {
506
- let mixState = curr._mixState as MixinRuntimeState | undefined
581
+ vParent: VNodeParent,
582
+ context: ReconcileContext,
583
+ ): CommittedHostNode {
584
+ let { frame, scheduler, styles } = context
585
+ let mixState = curr._mixState
507
586
  let currProps = getHostProps(curr)
508
- let nextProps = resolveNodeMixProps(next, frame, scheduler, mixState)
509
- let nextMixState = next._mixState as MixinRuntimeState | undefined
587
+ let resolved = resolveNodeMixProps(next, vParent, frame, scheduler, mixState)
588
+ let childInputs = next._children
589
+ let nextProps = resolved.props
590
+ let nextMixState = resolved.mixState
510
591
  let shouldDispatchMixinLifecycle =
511
592
  (nextMixState?.runners.length ?? 0) > 0 && shouldDispatchInlineMixinLifecycle(curr._dom)
512
593
  if (shouldDispatchMixinLifecycle) {
@@ -517,62 +598,49 @@ function diffHost(
517
598
  if (nextProps.innerHTML != null) {
518
599
  // innerHTML is set, update it if changed
519
600
  if (currProps.innerHTML !== nextProps.innerHTML) {
520
- curr._dom.innerHTML = nextProps.innerHTML as string
601
+ curr._dom.innerHTML = nextProps.innerHTML
521
602
  }
522
603
  } else if (currProps.innerHTML != null) {
523
604
  // innerHTML was removed, clear it before adding children
524
605
  curr._dom.innerHTML = ''
525
606
  }
526
607
 
527
- diffChildren(
528
- curr._children,
529
- next._children,
530
- curr._dom,
531
- frame,
532
- scheduler,
533
- styles,
534
- next,
535
- rootTarget,
536
- )
608
+ let committed = commitHostNode(next, vParent, getSvgContext(vParent, next), curr._dom, resolved)
609
+ committed._directEventState = curr._directEventState
610
+ committed._controlledState = curr._controlledState
611
+ committed._children = diffChildren(curr._children, childInputs, curr._dom, committed, context)
537
612
  patchHostProps(currProps, nextProps, curr._dom)
538
613
 
539
- next._dom = curr._dom
540
- next._parent = vParent
541
- next._controller = curr._controller
542
- next._directEventState = curr._directEventState
543
- next._controlledState = curr._controlledState
544
- syncDirectEventListeners(next as CommittedHostNode)
614
+ syncDirectEventListeners(committed)
545
615
 
546
- if (next._controlledState || shouldTrackControlledReflection(nextProps)) {
547
- ensureControlledReflection(next as CommittedHostNode, scheduler)
548
- syncControlledReflection(next as CommittedHostNode, nextProps)
616
+ if (committed._controlledState || shouldTrackControlledReflection(nextProps)) {
617
+ ensureControlledReflection(committed, scheduler)
618
+ syncControlledReflection(committed, nextProps)
549
619
  }
550
620
 
551
- if (next._mixState) {
552
- bindNodeMixRuntime(next as CommittedHostNode, frame, scheduler, styles)
621
+ if (committed._mixState) {
622
+ bindNodeMixRuntime(committed, frame, scheduler, styles)
553
623
  }
554
624
  if (shouldDispatchMixinLifecycle) {
555
625
  scheduler.enqueueCommitPhase([() => dispatchMixinCommit(nextMixState)])
556
626
  }
557
627
 
558
- return
628
+ return committed
559
629
  }
560
630
 
561
- function setupHostNode(node: HostNode, dom: Element, scheduler: Scheduler): void {
562
- node._dom = dom
631
+ function setupHostNode(node: CommittedHostNode, scheduler: Scheduler): void {
563
632
  let props = getHostProps(node)
564
- let committedNode = node as CommittedHostNode
565
633
 
566
- syncDirectEventListeners(committedNode)
634
+ syncDirectEventListeners(node)
567
635
 
568
636
  if (shouldTrackControlledReflection(props)) {
569
- ensureControlledReflection(committedNode, scheduler)
570
- syncControlledReflection(committedNode, props)
637
+ ensureControlledReflection(node, scheduler)
638
+ syncControlledReflection(node, props)
571
639
  }
572
640
  }
573
641
 
574
642
  function syncDirectEventListeners(node: CommittedHostNode): void {
575
- let descriptors = node._directEventDescriptors as OnMixinDescriptor[] | undefined
643
+ let descriptors = node._directEventDescriptors
576
644
  if (!descriptors) {
577
645
  teardownDirectEventListeners(node)
578
646
  return
@@ -582,7 +650,7 @@ function syncDirectEventListeners(node: CommittedHostNode): void {
582
650
  return
583
651
  }
584
652
 
585
- let state = node._directEventState as DirectEventState | undefined
653
+ let state = node._directEventState
586
654
  if (!state) {
587
655
  state = { bindings: [] }
588
656
  node._directEventState = state
@@ -590,8 +658,12 @@ function syncDirectEventListeners(node: CommittedHostNode): void {
590
658
 
591
659
  let bindings = state.bindings
592
660
  for (let index = 0; index < descriptors.length; index++) {
593
- let descriptor = descriptors[index]
594
- let [type, handler, captureBoolean = false] = descriptor.args
661
+ // Indexed access instead of array destructuring: destructuring goes through
662
+ // the iterator protocol and allocates on every host-node update.
663
+ let args = descriptors[index].args
664
+ let type = args[0]
665
+ let handler = args[1]
666
+ let captureBoolean = args[2] ?? false
595
667
  let binding = bindings[index]
596
668
 
597
669
  if (!binding) {
@@ -655,7 +727,7 @@ function removeDirectEventBinding(dom: Element, binding: DirectEventBinding): vo
655
727
  }
656
728
 
657
729
  function teardownDirectEventListeners(node: CommittedHostNode): void {
658
- let state = node._directEventState as DirectEventState | undefined
730
+ let state = node._directEventState
659
731
  if (!state) return
660
732
 
661
733
  for (let binding of state.bindings) {
@@ -666,334 +738,337 @@ function teardownDirectEventListeners(node: CommittedHostNode): void {
666
738
  node._directEventState = undefined
667
739
  }
668
740
 
741
+ // Teardown for a node whose DOM subtree is being discarded wholesale: abort
742
+ // pending handler work but skip removeEventListener — the listeners die with
743
+ // the detached node, and removing them one-by-one dominates large teardowns.
744
+ function abandonDirectEventListeners(node: CommittedHostNode): void {
745
+ let state = node._directEventState
746
+ if (!state) return
747
+
748
+ for (let binding of state.bindings) {
749
+ binding.reentry?.abort(new DOMException('', 'AbortError'))
750
+ binding.reentry = null
751
+ }
752
+
753
+ state.bindings.length = 0
754
+ node._directEventState = undefined
755
+ }
756
+
669
757
  function invokeDirectEventBinding(binding: DirectEventBinding, event: Event): void {
670
758
  binding.reentry?.abort(new DOMException('', 'EventReentry'))
671
759
  binding.reentry = new AbortController()
672
760
  void binding.handler(event, binding.reentry.signal)
673
761
  }
674
762
 
675
- function diffText(curr: CommittedTextNode, next: TextNode, vParent: VNode) {
763
+ function diffText(
764
+ curr: CommittedTextNode,
765
+ next: TextNode,
766
+ vParent: VNodeParent,
767
+ svg: boolean,
768
+ ): CommittedTextNode {
676
769
  if (curr._text !== next._text) {
677
770
  curr._dom.textContent = next._text
678
771
  }
679
- next._dom = curr._dom
680
- next._parent = vParent
772
+ return commitTextNode(next, vParent, svg, curr._dom)
681
773
  }
682
774
 
683
775
  function insert(
684
- node: VNode,
776
+ node: VNodeInput,
685
777
  domParent: ParentNode,
686
- frame: FrameHandle,
687
- scheduler: Scheduler,
688
- styles: StyleManager,
689
- vParent: VNode,
690
- rootTarget: EventTarget,
778
+ vParent: VNodeParent,
779
+ context: ReconcileContext,
691
780
  anchor?: Node,
692
- cursor?: Node | null,
693
- ): Node | null | undefined {
694
- node._parent = vParent // set parent for initial render context lookups
695
- node._svg = getSvgContext(vParent, node.type)
781
+ cursor?: HydrationCursor,
782
+ ): CommittedVNode {
783
+ let { frame, scheduler, styles } = context
784
+ let svg = getSvgContext(vParent, node)
785
+ let hydrationNode = cursor?.current
696
786
 
697
787
  // Stop hydration if cursor has reached the anchor (end boundary)
698
788
  // Check BEFORE skipComments to prevent escaping range root markers
699
- if (cursor && anchor && cursor === anchor) {
700
- cursor = null
789
+ if (hydrationNode && anchor && hydrationNode === anchor) {
790
+ hydrationNode = null
701
791
  }
702
792
 
703
- cursor =
704
- node.type === Frame
705
- ? skipCommentsExceptFrameStart(cursor ?? null)
706
- : skipComments(cursor ?? null)
793
+ // Preserve frame-start markers for non-Frame nodes too, so a following <Frame>
794
+ // (e.g. the first child of a bare Fragment at a clientEntry boundary) can still
795
+ // claim its rmx:f marker during hydration instead of being re-inserted fresh.
796
+ // A rmx:f marker always belongs to a <Frame>, so no non-Frame node should
797
+ // consume one.
798
+ hydrationNode = skipCommentsExceptFrameStart(hydrationNode ?? null)
707
799
 
708
800
  // Also check after skipComments in case we skipped past the anchor
709
- if (cursor && anchor && cursor === anchor) {
710
- cursor = null
801
+ if (hydrationNode && anchor && hydrationNode === anchor) {
802
+ hydrationNode = null
711
803
  }
804
+ if (cursor) cursor.current = hydrationNode
712
805
 
713
806
  let doInsert = anchor
714
807
  ? (dom: Node) => domParent.insertBefore(dom, anchor)
715
808
  : (dom: Node) => domParent.appendChild(dom)
716
809
 
717
- if (isNonRenderNode(node)) {
718
- return cursor
810
+ if (node.kind === 'empty') {
811
+ return commitNonRenderNode(node, vParent, svg)
719
812
  }
720
813
 
721
- if (isTextNode(node)) {
722
- if (cursor instanceof Text) {
723
- node._parent = vParent
814
+ if (node.kind === 'text') {
815
+ if (hydrationNode instanceof Text) {
724
816
  // Handle text node consolidation: server renders adjacent text as single node
725
817
  // e.g., <span>Hello {world}</span> → server: "Hello world", client: ["Hello ", "world"]
726
- if (cursor.data !== node._text) {
727
- if (cursor.data.startsWith(node._text) && node._text.length < cursor.data.length) {
818
+ if (hydrationNode.data !== node._text) {
819
+ if (
820
+ hydrationNode.data.startsWith(node._text) &&
821
+ node._text.length < hydrationNode.data.length
822
+ ) {
728
823
  // Consolidation case: split the text node at the boundary
729
824
  // cursor becomes the first part (node._text), remainder is returned for next vnode
730
- let remainder = cursor.splitText(node._text.length)
731
- node._dom = cursor
732
- return remainder
825
+ let remainder = hydrationNode.splitText(node._text.length)
826
+ if (cursor) cursor.current = remainder
827
+ return commitTextNode(node, vParent, svg, hydrationNode)
733
828
  }
734
829
  // Genuine mismatch - correct it
735
- logHydrationMismatch('text mismatch', cursor.data, node._text)
736
- cursor.data = node._text
830
+ logHydrationMismatch('text mismatch', hydrationNode.data, node._text)
831
+ hydrationNode.data = node._text
737
832
  }
738
- node._dom = cursor
739
- return cursor.nextSibling
833
+ if (cursor) cursor.current = hydrationNode.nextSibling
834
+ return commitTextNode(node, vParent, svg, hydrationNode)
740
835
  }
741
836
  let dom = document.createTextNode(node._text)
742
- node._dom = dom
743
- node._parent = vParent
744
837
  doInsert(dom)
745
- return cursor
838
+ return commitTextNode(node, vParent, svg, dom)
746
839
  }
747
840
 
748
- if (isHostNode(node)) {
749
- let hostProps = resolveNodeMixProps(node, frame, scheduler)
841
+ if (node.kind === 'host') {
842
+ let resolved = resolveNodeMixProps(node, vParent, frame, scheduler)
843
+ let hostProps = resolved.props
844
+ let childInputs = node._children
750
845
 
751
846
  if (isHeadHostNode(node)) {
752
847
  let targetHead = getDocumentHead(domParent)
753
848
  if (targetHead) {
754
- let childCursor = cursor
755
- if (cursor instanceof Element && cursor.tagName.toLowerCase() === 'head') {
756
- childCursor = cursor.firstChild
757
- let nextCursor = cursor.nextSibling
758
- if (cursor !== targetHead) {
759
- while (cursor.firstChild) {
760
- targetHead.appendChild(cursor.firstChild)
849
+ let childCursor = cursor ? { current: hydrationNode } : undefined
850
+ if (hydrationNode instanceof Element && hydrationNode.tagName.toLowerCase() === 'head') {
851
+ if (childCursor) childCursor.current = hydrationNode.firstChild
852
+ let nextCursor = hydrationNode.nextSibling
853
+ if (hydrationNode !== targetHead) {
854
+ while (hydrationNode.firstChild) {
855
+ targetHead.appendChild(hydrationNode.firstChild)
761
856
  }
762
- cursor.remove()
857
+ hydrationNode.remove()
763
858
  }
764
- cursor = nextCursor
859
+ if (cursor) cursor.current = nextCursor
765
860
  }
766
861
 
862
+ let committed = commitHostNode(node, vParent, svg, targetHead, resolved)
767
863
  // Render explicit <head> children directly into document.head.
768
- diffChildren(
864
+ committed._children = diffChildren(
769
865
  null,
770
- node._children,
866
+ childInputs,
771
867
  targetHead,
772
- frame,
773
- scheduler,
774
- styles,
775
- node,
776
- rootTarget,
868
+ committed,
869
+ context,
777
870
  childCursor,
778
871
  )
779
872
  patchHostProps({}, hostProps, targetHead)
780
- setupHostNode(node, targetHead, scheduler)
781
- if (node._mixState) {
782
- bindNodeMixRuntime(node as CommittedHostNode, frame, scheduler, styles)
873
+ setupHostNode(committed, scheduler)
874
+ if (committed._mixState) {
875
+ bindNodeMixRuntime(committed, frame, scheduler, styles)
783
876
  }
784
- return cursor
877
+ return committed
785
878
  }
786
879
  }
787
880
 
788
881
  // Check for matching mixin-persisted node that can be reclaimed
789
882
  let persistedNode = findMatchingPersistedMixinNode(node.type, node.key, domParent)
790
883
  if (persistedNode) {
791
- reclaimPersistedMixinNode(persistedNode, node, frame, scheduler, styles, vParent, rootTarget)
792
- return cursor
884
+ return reclaimPersistedMixinNode(persistedNode, node, vParent, context)
793
885
  }
794
886
 
795
- if (cursor instanceof Element) {
887
+ if (hydrationNode instanceof Element) {
796
888
  // SVG elements have case-sensitive tag names (e.g. linearGradient, clipPath)
797
889
  // HTML elements are case-insensitive, so we lowercase for comparison
798
- let cursorTag = node._svg ? cursor.tagName : cursor.tagName.toLowerCase()
890
+ let cursorTag = svg ? hydrationNode.tagName : hydrationNode.tagName.toLowerCase()
799
891
  if (cursorTag === node.type) {
800
- let nextCursor = cursor.nextSibling
801
- patchHostProps({}, hostProps, cursor)
892
+ let nextCursor = hydrationNode.nextSibling
893
+ patchHostProps({}, hostProps, hydrationNode)
894
+ let committed = commitHostNode(node, vParent, svg, hydrationNode, resolved)
802
895
 
803
896
  // Handle innerHTML prop
804
897
  if (hostProps.innerHTML != null) {
805
- cursor.innerHTML = hostProps.innerHTML as string
898
+ hydrationNode.innerHTML = hostProps.innerHTML
806
899
  } else {
807
- let childCursor = cursor.firstChild
900
+ let childCursor = { current: hydrationNode.firstChild }
808
901
  // Ignore excess nodes - browser extensions may inject content
809
- diffChildren(
902
+ committed._children = diffChildren(
810
903
  null,
811
- node._children,
812
- cursor,
813
- frame,
814
- scheduler,
815
- styles,
816
- node,
817
- rootTarget,
904
+ childInputs,
905
+ hydrationNode,
906
+ committed,
907
+ context,
818
908
  childCursor,
819
909
  )
820
910
  }
821
911
 
822
- setupHostNode(node, cursor, scheduler)
823
- if (node._mixState) {
824
- bindNodeMixRuntime(node as CommittedHostNode, frame, scheduler, styles)
912
+ setupHostNode(committed, scheduler)
913
+ if (committed._mixState) {
914
+ bindNodeMixRuntime(committed, frame, scheduler, styles)
825
915
  }
826
- return nextCursor
916
+ if (cursor) cursor.current = nextCursor
917
+ return committed
827
918
  } else {
828
919
  // Type mismatch - try single-advance retry to handle browser extension injections
829
920
  // at the start of containers. Skip this node and try the next sibling once.
830
- let nextSibling = skipComments(cursor.nextSibling)
921
+ let nextSibling = skipComments(hydrationNode.nextSibling)
831
922
  if (nextSibling instanceof Element) {
832
- let nextTag = node._svg ? nextSibling.tagName : nextSibling.tagName.toLowerCase()
923
+ let nextTag = svg ? nextSibling.tagName : nextSibling.tagName.toLowerCase()
833
924
  if (nextTag === node.type) {
834
925
  let nextCursor = nextSibling.nextSibling
835
926
  // Found a match after skipping - adopt it and leave skipped node in place
836
927
  patchHostProps({}, hostProps, nextSibling)
928
+ let committed = commitHostNode(node, vParent, svg, nextSibling, resolved)
837
929
 
838
930
  if (hostProps.innerHTML != null) {
839
- nextSibling.innerHTML = hostProps.innerHTML as string
931
+ nextSibling.innerHTML = hostProps.innerHTML
840
932
  } else {
841
- let childCursor = nextSibling.firstChild
842
- diffChildren(
933
+ let childCursor = { current: nextSibling.firstChild }
934
+ committed._children = diffChildren(
843
935
  null,
844
- node._children,
936
+ childInputs,
845
937
  nextSibling,
846
- frame,
847
- scheduler,
848
- styles,
849
- node,
850
- rootTarget,
938
+ committed,
939
+ context,
851
940
  childCursor,
852
941
  )
853
942
  }
854
943
 
855
- setupHostNode(node, nextSibling, scheduler)
856
- if (node._mixState) {
857
- bindNodeMixRuntime(node as CommittedHostNode, frame, scheduler, styles)
944
+ setupHostNode(committed, scheduler)
945
+ if (committed._mixState) {
946
+ bindNodeMixRuntime(committed, frame, scheduler, styles)
858
947
  }
859
- return nextCursor
948
+ if (cursor) cursor.current = nextCursor
949
+ return committed
860
950
  }
861
951
  }
862
952
  // Retry failed - log mismatch and create new element (don't remove mismatched nodes)
863
953
  logHydrationMismatch('tag', cursorTag, node.type)
864
- cursor = undefined // stop hydration for this tree
954
+ if (cursor) cursor.current = undefined // stop hydration for this tree
865
955
  }
866
956
  }
867
- let dom = node._svg
868
- ? document.createElementNS(SVG_NS, node.type)
869
- : document.createElement(node.type)
957
+ let dom = svg ? document.createElementNS(SVG_NS, node.type) : document.createElement(node.type)
870
958
  patchHostProps({}, hostProps, dom)
959
+ let committed = commitHostNode(node, vParent, svg, dom, resolved)
871
960
 
872
961
  // Handle innerHTML prop
873
962
  if (hostProps.innerHTML != null) {
874
- dom.innerHTML = hostProps.innerHTML as string
963
+ dom.innerHTML = hostProps.innerHTML
875
964
  } else {
876
- diffChildren(null, node._children, dom, frame, scheduler, styles, node, rootTarget)
965
+ committed._children = diffChildren(null, childInputs, dom, committed, context)
877
966
  }
878
967
 
879
- setupHostNode(node, dom, scheduler)
880
- if (node._mixState) {
881
- bindNodeMixRuntime(node as CommittedHostNode, frame, scheduler, styles, false, domParent)
968
+ setupHostNode(committed, scheduler)
969
+ if (committed._mixState) {
970
+ bindNodeMixRuntime(committed, frame, scheduler, styles, false, domParent)
882
971
  }
883
972
  doInsert(dom)
884
- return cursor
973
+ return committed
885
974
  }
886
975
 
887
- if (isFragmentNode(node)) {
976
+ if (node.kind === 'fragment') {
977
+ let childInputs = node._children
978
+ let committed = beginFragmentNode(node, vParent, svg)
979
+ let children: Array<VNodeInput | CommittedVNode> = childInputs
888
980
  // Insert fragment children in order before the same anchor
889
- for (let child of node._children) {
890
- cursor = insert(child, domParent, frame, scheduler, styles, node, rootTarget, anchor, cursor)
981
+ for (let i = 0; i < childInputs.length; i++) {
982
+ children[i] = insert(childInputs[i], domParent, committed, context, anchor, cursor)
891
983
  }
892
- return cursor
984
+ committed._children = children as CommittedVNode[]
985
+ return committed
893
986
  }
894
987
 
895
- if (isComponentNode(node)) {
896
- return diffComponent(
897
- null,
898
- node,
899
- frame,
900
- scheduler,
901
- styles,
902
- domParent,
903
- vParent,
904
- rootTarget,
905
- anchor,
906
- cursor,
907
- )
988
+ if (node.kind === 'component') {
989
+ return diffComponent(null, node, domParent, vParent, context, svg, anchor, cursor)
908
990
  }
909
991
 
910
- if (node.type === Frame) {
911
- return insertFrame(
912
- node,
913
- domParent,
914
- frame,
915
- scheduler,
916
- styles,
917
- vParent,
918
- rootTarget,
919
- anchor,
920
- cursor,
921
- )
992
+ if (node.kind === 'frame') {
993
+ return insertFrame(node, domParent, vParent, context, svg, anchor, cursor)
922
994
  }
923
995
 
924
996
  invariant(false, 'Unexpected node type')
925
997
  }
926
998
 
927
999
  function diffFrame(
928
- curr: VNode,
929
- next: VNode,
1000
+ curr: CommittedFrameNode,
1001
+ next: FrameNode,
930
1002
  domParent: ParentNode,
931
- frame: FrameHandle,
932
- scheduler: Scheduler,
933
- styles: StyleManager,
934
- vParent: VNode,
935
- rootTarget: EventTarget,
1003
+ vParent: VNodeParent,
1004
+ context: ReconcileContext,
1005
+ svg: boolean,
936
1006
  anchor?: Node,
937
- ): void {
1007
+ ): CommittedFrameNode {
1008
+ let { frame } = context
938
1009
  let currSrc = getFrameSrc(curr)
939
1010
  let nextSrc = getFrameSrc(next)
940
1011
  let currName = getFrameName(curr)
941
1012
  let nextName = getFrameName(next)
942
1013
 
943
1014
  if (currName !== nextName) {
944
- let replaceAnchor = curr._rangeEnd?.nextSibling ?? anchor
945
- remove(curr, domParent, scheduler, styles)
946
- insert(next, domParent, frame, scheduler, styles, vParent, rootTarget, replaceAnchor)
947
- return
1015
+ let replaceAnchor = curr._rangeEnd.nextSibling ?? anchor
1016
+ remove(curr, domParent, context)
1017
+ return insertFrame(next, domParent, vParent, context, svg, replaceAnchor)
948
1018
  }
949
1019
 
950
1020
  // If the frame hasn't resolved yet, preserve existing cancel/remount behavior
951
1021
  // so pending streams from the old src cannot take over the new src.
952
- if (currSrc !== nextSrc && !curr._frameResolved) {
953
- let replaceAnchor = curr._rangeEnd?.nextSibling ?? anchor
954
- remove(curr, domParent, scheduler, styles)
955
- insert(next, domParent, frame, scheduler, styles, vParent, rootTarget, replaceAnchor)
956
- return
957
- }
1022
+ if (currSrc !== nextSrc && !curr._state.resolved) {
1023
+ let replaceAnchor = curr._rangeEnd.nextSibling ?? anchor
1024
+ remove(curr, domParent, context)
1025
+ return insertFrame(next, domParent, vParent, context, svg, replaceAnchor)
1026
+ }
1027
+
1028
+ let committed = next as unknown as CommittedFrameNode
1029
+ Object.assign(committed, {
1030
+ _rangeStart: curr._rangeStart,
1031
+ _rangeEnd: curr._rangeEnd,
1032
+ _state: curr._state,
1033
+ _parent: vParent,
1034
+ _svg: svg,
1035
+ })
958
1036
 
959
- next._rangeStart = curr._rangeStart
960
- next._rangeEnd = curr._rangeEnd
961
- next._frameInstance = curr._frameInstance
962
- next._frameFallbackRoot = curr._frameFallbackRoot
963
- next._frameResolveToken = curr._frameResolveToken
964
- next._frameResolved = curr._frameResolved
965
- next._parent = vParent
1037
+ let frameRuntime = getFrameRuntime(frame)
1038
+ let frameInstance = committed._state.instance
1039
+ let serverFrameReload = frameRuntime?.serverFrameReload
966
1040
 
967
1041
  if (currSrc !== nextSrc) {
968
- let frameInstance = next._frameInstance as FrameInstance | undefined
969
1042
  if (frameInstance) {
970
1043
  frameInstance.handle.src = nextSrc
971
1044
  }
972
1045
 
973
- let runtime = getFrameRuntime(frame)
974
- if (runtime) {
975
- resolveClientFrame(next, runtime)
1046
+ if (frameRuntime) {
1047
+ resolveClientFrame(committed, frameRuntime, serverFrameReload)
976
1048
  }
1049
+ } else if (frameRuntime && frameInstance && serverFrameReload) {
1050
+ // Reload client frames that have the same src during a render triggered by an ancestor frame reload
1051
+ resolveClientFrame(committed, frameRuntime, serverFrameReload)
977
1052
  }
978
1053
 
979
- if (!next._frameResolved && next._frameFallbackRoot) {
980
- next._frameFallbackRoot.render(next.props?.fallback ?? null)
1054
+ if (!committed._state.resolved && committed._state.fallbackRoot) {
1055
+ committed._state.fallbackRoot.render(committed.props.fallback ?? null)
981
1056
  }
1057
+ return committed
982
1058
  }
983
1059
 
984
1060
  function insertFrame(
985
- node: VNode,
1061
+ node: FrameNode,
986
1062
  domParent: ParentNode,
987
- frame: FrameHandle,
988
- scheduler: Scheduler,
989
- styles: StyleManager,
990
- vParent: VNode,
991
- rootTarget: EventTarget,
1063
+ vParent: VNodeParent,
1064
+ context: ReconcileContext,
1065
+ svg: boolean,
992
1066
  anchor?: Node,
993
- cursor?: Node | null,
994
- ): Node | null | undefined {
1067
+ cursor?: HydrationCursor,
1068
+ ): CommittedFrameNode {
1069
+ let { frame, styles } = context
995
1070
  let runtime = getFrameRuntime(frame)
996
- if (!runtime || (runtime as { canResolveFrames?: boolean }).canResolveFrames === false) {
1071
+ if (!runtime || runtime.canResolveFrames === false) {
997
1072
  throw new Error(
998
1073
  'Cannot render <Frame /> without frame runtime. Use run() or pass frameInit to createRoot/createRangeRoot.',
999
1074
  )
@@ -1001,33 +1076,23 @@ function insertFrame(
1001
1076
 
1002
1077
  // Hydration path: adopt server-rendered frame markers and reuse the existing
1003
1078
  // frame instance created during createSubFrames().
1004
- if (isFrameStartComment(cursor)) {
1005
- let start = cursor
1079
+ if (isFrameStartComment(cursor?.current)) {
1080
+ let start = cursor.current
1006
1081
  let end = findFrameEndComment(start)
1007
1082
  if (end) {
1008
- node._rangeStart = start
1009
- node._rangeEnd = end
1010
- node._parent = vParent
1011
- node._frameResolveToken = 0
1012
- node._frameResolveController = undefined
1013
- node._frameFallbackRoot = undefined
1014
- node._frameResolved = true
1015
-
1016
- let frameId = getFrameIdFromComment(start)
1017
- let marker = frameId ? runtime.data.f?.[frameId] : undefined
1018
- let src = marker?.src ?? getFrameSrc(node)
1019
1083
  let instance = runtime.frameInstances.get(start)
1084
+ let src = instance?.handle.src ?? getFrameSrc(node)
1020
1085
  if (!instance) {
1021
1086
  instance = createFrame([start, end], {
1022
1087
  name: getFrameName(node),
1023
1088
  src,
1024
- marker: frameId && marker ? { ...marker, id: frameId } : undefined,
1025
1089
  errorTarget: runtime.errorTarget,
1026
1090
  loadModule: runtime.loadModule,
1027
1091
  resolveFrame: runtime.resolveFrame,
1028
1092
  pendingClientEntries: runtime.pendingClientEntries,
1029
1093
  scheduler: runtime.scheduler,
1030
- data: runtime.data,
1094
+ styleManager: runtime.styleManager,
1095
+ data: {},
1031
1096
  moduleCache: runtime.moduleCache,
1032
1097
  moduleLoads: runtime.moduleLoads,
1033
1098
  frameInstances: runtime.frameInstances,
@@ -1036,9 +1101,20 @@ function insertFrame(
1036
1101
  runtime.frameInstances.set(start, instance)
1037
1102
  }
1038
1103
 
1039
- node._frameInstance = instance
1040
-
1041
- return end.nextSibling
1104
+ cursor.current = end.nextSibling
1105
+ return Object.assign(node as unknown as CommittedFrameNode, {
1106
+ _rangeStart: start,
1107
+ _rangeEnd: end,
1108
+ _parent: vParent,
1109
+ _svg: svg,
1110
+ _state: {
1111
+ instance,
1112
+ resolveToken: 0,
1113
+ resolveController: undefined,
1114
+ fallbackRoot: undefined,
1115
+ resolved: true,
1116
+ },
1117
+ })
1042
1118
  }
1043
1119
  }
1044
1120
 
@@ -1051,18 +1127,11 @@ function insertFrame(
1051
1127
  doInsert(start)
1052
1128
  doInsert(end)
1053
1129
 
1054
- node._rangeStart = start
1055
- node._rangeEnd = end
1056
- node._parent = vParent
1057
-
1058
1130
  let fallbackRoot = createRangeRoot([start, end], {
1059
1131
  frame,
1060
1132
  styleManager: styles,
1061
1133
  })
1062
- fallbackRoot.render(node.props?.fallback ?? null)
1063
- node._frameFallbackRoot = fallbackRoot
1064
- node._frameResolved = false
1065
- node._frameResolveToken = 0
1134
+ fallbackRoot.render(node.props.fallback ?? null)
1066
1135
 
1067
1136
  let instance = createFrame([start, end], {
1068
1137
  name: getFrameName(node),
@@ -1072,61 +1141,94 @@ function insertFrame(
1072
1141
  resolveFrame: runtime.resolveFrame,
1073
1142
  pendingClientEntries: runtime.pendingClientEntries,
1074
1143
  scheduler: runtime.scheduler,
1075
- data: runtime.data,
1144
+ styleManager: runtime.styleManager,
1145
+ data: {},
1076
1146
  moduleCache: runtime.moduleCache,
1077
1147
  moduleLoads: runtime.moduleLoads,
1078
1148
  frameInstances: runtime.frameInstances,
1079
1149
  namedFrames: runtime.namedFrames,
1080
1150
  })
1081
- node._frameInstance = instance
1082
1151
  runtime.frameInstances.set(start, instance)
1083
1152
 
1084
- resolveClientFrame(node, runtime)
1153
+ let committed = Object.assign(node as unknown as CommittedFrameNode, {
1154
+ _rangeStart: start,
1155
+ _rangeEnd: end,
1156
+ _parent: vParent,
1157
+ _svg: svg,
1158
+ _state: {
1159
+ instance,
1160
+ fallbackRoot,
1161
+ resolved: false,
1162
+ resolveToken: 0,
1163
+ resolveController: undefined,
1164
+ },
1165
+ })
1166
+ resolveClientFrame(committed, runtime)
1085
1167
 
1086
- return cursor
1168
+ return committed
1087
1169
  }
1088
1170
 
1089
- function resolveClientFrame(node: VNode, runtime: FrameRuntime): void {
1171
+ function resolveClientFrame(
1172
+ node: CommittedFrameNode,
1173
+ runtime: FrameRuntime,
1174
+ serverFrameReload?: { signal: AbortSignal },
1175
+ ): void {
1090
1176
  let frameSrc = getFrameSrc(node)
1091
- let instance = node._frameInstance as FrameInstance | undefined
1092
- if (!instance) return
1093
-
1094
- let token = (node._frameResolveToken ?? 0) + 1
1095
- node._frameResolveToken = token
1096
- node._frameResolveController?.abort()
1097
- let resolveController = new AbortController()
1098
- node._frameResolveController = resolveController
1099
-
1100
- Promise.resolve(runtime.resolveFrame(frameSrc, resolveController.signal))
1101
- .then(async (content) => {
1102
- if (node._frameResolveToken !== token || resolveController.signal.aborted) return
1103
- node._frameFallbackRoot?.dispose()
1104
- node._frameFallbackRoot = undefined
1177
+ let state = node._state
1178
+ let instance = state.instance
1179
+
1180
+ let token = state.resolveToken + 1
1181
+ state.resolveToken = token
1182
+ state.resolveController?.abort()
1183
+ let reload = serverFrameReload
1184
+ ? instance.beginClientFrameReloadForAncestorReload(serverFrameReload.signal)
1185
+ : undefined
1186
+ if (!reload) {
1187
+ instance.cancelReload()
1188
+ }
1189
+ let resolveController = reload?.controller ?? new AbortController()
1190
+ state.resolveController = resolveController
1191
+
1192
+ Promise.resolve()
1193
+ .then(() =>
1194
+ runtime.resolveFrame(frameSrc, {
1195
+ signal: resolveController.signal,
1196
+ target: getFrameName(node),
1197
+ }),
1198
+ )
1199
+ .then(async (resolution) => {
1200
+ if (state.resolveToken !== token || resolveController.signal.aborted) return
1201
+ let { content } = await unwrapFrameResolution(resolution)
1202
+ if (state.resolveToken !== token || resolveController.signal.aborted) return
1203
+ state.fallbackRoot?.dispose()
1204
+ state.fallbackRoot = undefined
1105
1205
  let nextContent = asAbortableFrameContent(content, resolveController.signal)
1106
1206
  await instance.render(nextContent, { signal: resolveController.signal })
1107
- if (node._frameResolveToken !== token || resolveController.signal.aborted) return
1108
- node._frameResolved = true
1207
+ if (state.resolveToken !== token || resolveController.signal.aborted) return
1208
+ state.resolved = true
1209
+ })
1210
+ .catch((error) => {
1211
+ if (reload && state.resolveToken === token && !resolveController.signal.aborted) {
1212
+ runtime.errorTarget.dispatchEvent(createComponentErrorEvent(error))
1213
+ }
1109
1214
  })
1110
- .catch(() => {})
1111
1215
  .finally(() => {
1112
- if (node._frameResolveController === resolveController) {
1113
- node._frameResolveController = undefined
1216
+ reload?.complete()
1217
+ if (state.resolveController === resolveController) {
1218
+ state.resolveController = undefined
1114
1219
  }
1115
1220
  })
1116
1221
  }
1117
1222
 
1118
- function disposeFrameResources(node: VNode): void {
1119
- node._frameResolveToken = (node._frameResolveToken ?? 0) + 1
1120
- node._frameResolveController?.abort()
1121
- node._frameResolveController = undefined
1122
- node._frameFallbackRoot?.dispose()
1123
- node._frameFallbackRoot = undefined
1223
+ function disposeFrameResources(node: CommittedFrameNode): void {
1224
+ let state = node._state
1225
+ state.resolveToken++
1226
+ state.resolveController?.abort()
1227
+ state.resolveController = undefined
1228
+ state.fallbackRoot?.dispose()
1229
+ state.fallbackRoot = undefined
1124
1230
 
1125
- let frameInstance = node._frameInstance as FrameInstance | undefined
1126
- if (frameInstance) {
1127
- frameInstance.dispose()
1128
- node._frameInstance = undefined
1129
- }
1231
+ state.instance.dispose()
1130
1232
  }
1131
1233
 
1132
1234
  function asAbortableFrameContent(content: FrameContent, signal: AbortSignal): FrameContent {
@@ -1157,26 +1259,26 @@ function createAbortableReadableStream(
1157
1259
  }
1158
1260
 
1159
1261
  let removeAbortReadListener: undefined | (() => void)
1160
- let abortRead = new Promise<ReadableStreamReadResult<Uint8Array>>((resolve) => {
1262
+ let abortRead = new Promise<{ done: true; value: undefined }>((resolve) => {
1161
1263
  if (signal.aborted) {
1162
- resolve({ done: true, value: undefined as unknown as Uint8Array })
1264
+ resolve({ done: true, value: undefined })
1163
1265
  return
1164
1266
  }
1165
1267
  let onAbortRead = () => {
1166
- resolve({ done: true, value: undefined as unknown as Uint8Array })
1268
+ resolve({ done: true, value: undefined })
1167
1269
  }
1168
1270
  removeAbortReadListener = () => signal.removeEventListener('abort', onAbortRead)
1169
1271
  signal.addEventListener('abort', onAbortRead, { once: true })
1170
1272
  })
1171
1273
 
1172
- let { done, value } = await Promise.race([reader.read(), abortRead])
1274
+ let result = await Promise.race([reader.read(), abortRead])
1173
1275
  removeAbortReadListener?.()
1174
1276
 
1175
- if (done) {
1277
+ if (result.done) {
1176
1278
  controller.close()
1177
1279
  return
1178
1280
  }
1179
- controller.enqueue(value)
1281
+ controller.enqueue(result.value)
1180
1282
  },
1181
1283
  cancel(reason) {
1182
1284
  signal.removeEventListener('abort', onAbort)
@@ -1185,11 +1287,9 @@ function createAbortableReadableStream(
1185
1287
  })
1186
1288
  }
1187
1289
 
1188
- function removeFrameDomRange(node: VNode, domParent: ParentNode): void {
1290
+ function removeFrameDomRange(node: CommittedFrameNode, domParent: ParentNode): void {
1189
1291
  let start = node._rangeStart
1190
1292
  let end = node._rangeEnd
1191
- if (!(start instanceof Comment) || !(end instanceof Comment)) return
1192
-
1193
1293
  let cursor: Node | null = start
1194
1294
  while (cursor) {
1195
1295
  let nextSibling: Node | null = cursor.nextSibling
@@ -1199,23 +1299,19 @@ function removeFrameDomRange(node: VNode, domParent: ParentNode): void {
1199
1299
  if (cursor === end) break
1200
1300
  cursor = nextSibling
1201
1301
  }
1202
-
1203
- node._rangeStart = undefined
1204
- node._rangeEnd = undefined
1205
1302
  }
1206
1303
 
1207
1304
  function getFrameRuntime(frame: FrameHandle): FrameRuntime | undefined {
1208
- return frame.$runtime as FrameRuntime | undefined
1305
+ let runtime = frame.$runtime
1306
+ return isFrameRuntime(runtime) ? runtime : undefined
1209
1307
  }
1210
1308
 
1211
- function getFrameSrc(node: VNode): string {
1212
- let src = node.props?.src
1213
- invariant(typeof src === 'string' && src.length > 0, '<Frame /> requires a src prop')
1214
- return src
1309
+ function getFrameSrc(node: FrameNode | CommittedFrameNode): string {
1310
+ return node.props.src
1215
1311
  }
1216
1312
 
1217
- function getFrameName(node: VNode): string | undefined {
1218
- let name = node.props?.name
1313
+ function getFrameName(node: FrameNode | CommittedFrameNode): string | undefined {
1314
+ let name = node.props.name
1219
1315
  return typeof name === 'string' && name.length > 0 ? name : undefined
1220
1316
  }
1221
1317
 
@@ -1232,17 +1328,15 @@ function skipCommentsExceptFrameStart(cursor: Node | null): Node | null {
1232
1328
  }
1233
1329
 
1234
1330
  function isFrameStartComment(node: Node | null | undefined): node is Comment {
1235
- return node instanceof Comment && node.data.trim().startsWith('rmx:f:')
1331
+ return isCommentNode(node) && node.data.trim().startsWith('rmx:f:')
1236
1332
  }
1237
1333
 
1238
1334
  function isFrameEndComment(node: Node | null | undefined): node is Comment {
1239
- return node instanceof Comment && node.data.trim() === '/rmx:f'
1335
+ return isCommentNode(node) && node.data.trim() === '/rmx:f'
1240
1336
  }
1241
1337
 
1242
- function getFrameIdFromComment(comment: Comment): string | undefined {
1243
- let text = comment.data.trim()
1244
- if (!text.startsWith('rmx:f:')) return undefined
1245
- return text.slice('rmx:f:'.length)
1338
+ function isCommentNode(node: Node | null | undefined): node is Comment {
1339
+ return node?.nodeType === Node.COMMENT_NODE
1246
1340
  }
1247
1341
 
1248
1342
  function findFrameEndComment(start: Comment): Comment | null {
@@ -1262,72 +1356,53 @@ function findFrameEndComment(start: Comment): Comment | null {
1262
1356
  }
1263
1357
 
1264
1358
  export function renderComponent(
1265
- handle: ComponentHandle,
1266
- currContent: VNode | null,
1267
- next: ComponentNode,
1359
+ currContent: CommittedVNode | null,
1360
+ next: MountingComponentNode | CommittedComponentNode,
1268
1361
  domParent: ParentNode,
1269
- frame: FrameHandle,
1270
- scheduler: Scheduler,
1271
- styles: StyleManager,
1272
- rootTarget: EventTarget,
1273
- vParent?: VNode,
1362
+ context: ReconcileContext,
1274
1363
  anchor?: Node,
1275
- cursor?: Node | null,
1276
- ): Node | null | undefined {
1277
- if (handle.isRemoved()) return cursor
1364
+ cursor?: HydrationCursor,
1365
+ ): CommittedComponentNode {
1366
+ let { scheduler } = context
1367
+ let handle = next._handle
1368
+ if (handle.isRemoved()) {
1369
+ invariant('_content' in next, 'Expected removed component to be committed')
1370
+ return next
1371
+ }
1278
1372
 
1279
1373
  let [element, tasks] = handle.render(next.props)
1280
- let content = toVNode(element)
1281
- let newCursor = diffVNodes(
1282
- currContent,
1283
- content,
1284
- domParent,
1285
- frame,
1286
- scheduler,
1287
- styles,
1288
- next,
1289
- rootTarget,
1290
- anchor,
1291
- cursor,
1292
- )
1293
- next._content = content
1294
- next._handle = handle
1295
- next._parent = vParent
1296
-
1297
- let committed = next as CommittedComponentNode
1298
- handle.setScheduleUpdate(() => {
1299
- scheduler.enqueue(committed, domParent)
1300
- })
1374
+ let content = diffVNodes(currContent, toVNode(element), domParent, next, context, anchor, cursor)
1375
+ let committed = commitComponentNode(next, content)
1376
+ handle.setScheduleUpdate(scheduler, committed, domParent)
1301
1377
 
1302
1378
  scheduler.enqueueTasks(tasks)
1303
1379
 
1304
- return newCursor
1380
+ return committed
1305
1381
  }
1306
1382
 
1307
1383
  function diffComponent(
1308
1384
  curr: CommittedComponentNode | null,
1309
1385
  next: ComponentNode,
1310
- frame: FrameHandle,
1311
- scheduler: Scheduler,
1312
- styles: StyleManager,
1313
1386
  domParent: ParentNode,
1314
- vParent: VNode,
1315
- rootTarget: EventTarget,
1387
+ vParent: VNodeParent,
1388
+ context: ReconcileContext,
1389
+ svg: boolean,
1316
1390
  anchor?: Node,
1317
- cursor?: Node | null,
1318
- ): Node | null | undefined {
1391
+ cursor?: HydrationCursor,
1392
+ ): CommittedComponentNode {
1393
+ let { frame } = context
1319
1394
  if (curr === null) {
1320
- let componentId = vParent._pendingHydrationComponentId
1395
+ let componentId = vParent.kind === 'root' ? vParent._pendingHydrationComponentId : undefined
1321
1396
  if (componentId) {
1322
- vParent._pendingHydrationComponentId = undefined
1397
+ if (vParent.kind === 'root') vParent._pendingHydrationComponentId = undefined
1323
1398
  } else {
1324
1399
  componentId = `c${++idCounter}`
1325
1400
  }
1326
- next._handle = createComponent({
1401
+ let handle = createComponent({
1327
1402
  id: componentId,
1328
1403
  frame,
1329
1404
  type: next.type,
1330
- getContext: (type: Component) => findContextFromAncestry(vParent, type),
1405
+ getContext: (type: ElementFunction) => findContextFromAncestry(vParent, type),
1331
1406
  getFrameByName(name: string) {
1332
1407
  let runtime = getFrameRuntime(frame)
1333
1408
  return runtime?.namedFrames.get(name)
@@ -1337,123 +1412,102 @@ function diffComponent(
1337
1412
  return runtime?.topFrame
1338
1413
  },
1339
1414
  })
1415
+ let mounting = beginComponentNode(next, vParent, svg, handle, context)
1340
1416
 
1341
- return renderComponent(
1342
- next._handle,
1343
- null,
1344
- next,
1345
- domParent,
1346
- frame,
1347
- scheduler,
1348
- styles,
1349
- rootTarget,
1350
- vParent,
1351
- anchor,
1352
- cursor,
1353
- )
1417
+ return renderComponent(null, mounting, domParent, context, anchor, cursor)
1354
1418
  }
1355
- next._handle = curr._handle
1356
- let { _content, _handle } = curr
1357
- return renderComponent(
1358
- _handle,
1359
- _content,
1360
- next,
1361
- domParent,
1362
- frame,
1363
- scheduler,
1364
- styles,
1365
- rootTarget,
1366
- vParent,
1367
- anchor,
1368
- cursor,
1369
- )
1419
+ let mounting = beginComponentNode(next, vParent, svg, curr._handle, context)
1420
+ return renderComponent(curr._content, mounting, domParent, context, anchor, cursor)
1370
1421
  }
1371
1422
 
1372
1423
  // Cleanup without DOM removal - used for descendants when parent DOM node is removed
1373
- function cleanupDescendants(node: VNode, scheduler: Scheduler, styles: StyleManager): void {
1424
+ function cleanupDescendants(node: CommittedVNode, context: ReconcileContext): void {
1425
+ let { scheduler } = context
1374
1426
  if (isCommittedTextNode(node)) {
1375
1427
  return
1376
1428
  }
1377
1429
 
1378
1430
  if (isCommittedHostNode(node)) {
1379
- for (let child of node._children) {
1380
- cleanupDescendants(child, scheduler, styles)
1431
+ let children = node._children
1432
+ for (let i = 0; i < children.length; i++) {
1433
+ cleanupDescendants(children[i], context)
1381
1434
  }
1382
1435
 
1383
- teardownMixins(node._mixState as MixinRuntimeState | undefined)
1384
- teardownDirectEventListeners(node)
1385
- teardownControlledReflection(node)
1386
- if (node._controller) node._controller.abort()
1436
+ teardownMixins(node._mixState)
1437
+ abandonDirectEventListeners(node)
1438
+ abandonControlledReflection(node)
1387
1439
  return
1388
1440
  }
1389
1441
 
1390
1442
  if (isFragmentNode(node)) {
1391
- for (let child of node._children) {
1392
- cleanupDescendants(child, scheduler, styles)
1443
+ let children = node._children
1444
+ for (let i = 0; i < children.length; i++) {
1445
+ cleanupDescendants(children[i], context)
1393
1446
  }
1394
1447
  return
1395
1448
  }
1396
1449
 
1397
1450
  if (isCommittedComponentNode(node)) {
1398
- cleanupDescendants(node._content, scheduler, styles)
1451
+ cleanupDescendants(node._content, context)
1399
1452
  let tasks = node._handle.remove()
1400
1453
  scheduler.enqueueTasks(tasks)
1401
1454
  return
1402
1455
  }
1403
1456
 
1404
- if (node.type === Frame) {
1457
+ if (node.kind === 'frame') {
1405
1458
  disposeFrameResources(node)
1406
1459
  return
1407
1460
  }
1408
1461
  }
1409
1462
 
1410
1463
  export function remove(
1411
- node: VNode,
1464
+ node: CommittedVNode,
1412
1465
  domParent: ParentNode,
1413
- scheduler: Scheduler,
1414
- styles: StyleManager,
1415
- ) {
1466
+ context: ReconcileContext,
1467
+ ): void {
1468
+ let { scheduler } = context
1416
1469
  if (isCommittedTextNode(node)) {
1417
1470
  node._dom.parentNode?.removeChild(node._dom)
1418
1471
  return
1419
1472
  }
1420
1473
 
1421
1474
  if (isCommittedHostNode(node)) {
1422
- if (node._persistedByMixins) return
1475
+ if (node._persistence) return
1423
1476
 
1424
- let persistedRemoval = prepareMixinRemoval(node._mixState as MixinRuntimeState | undefined)
1477
+ let persistedRemoval = prepareMixinRemoval(node._mixState)
1425
1478
  if (persistedRemoval) {
1426
1479
  let token = ++persistedRemovalToken
1427
1480
  markNodePersistedByMixins(node, domParent, token)
1428
1481
  void persistedRemoval
1429
1482
  .catch(() => {})
1430
1483
  .finally(() => {
1431
- if (!node._persistedByMixins) return
1432
- if (node._persistedRemovalToken !== token) return
1484
+ if (!node._persistence) return
1485
+ if (node._persistence.token !== token) return
1433
1486
  unmarkNodePersistedByMixins(node)
1434
- performHostNodeRemoval(node, domParent, scheduler, styles)
1487
+ performHostNodeRemoval(node, domParent, context)
1435
1488
  })
1436
1489
  return
1437
1490
  }
1438
- performHostNodeRemoval(node, domParent, scheduler, styles)
1491
+ performHostNodeRemoval(node, domParent, context)
1439
1492
  return
1440
1493
  }
1441
1494
 
1442
1495
  if (isFragmentNode(node)) {
1443
- for (let child of node._children) {
1444
- remove(child, domParent, scheduler, styles)
1496
+ let children = node._children
1497
+ for (let i = 0; i < children.length; i++) {
1498
+ remove(children[i], domParent, context)
1445
1499
  }
1446
1500
  return
1447
1501
  }
1448
1502
 
1449
1503
  if (isCommittedComponentNode(node)) {
1450
- remove(node._content, domParent, scheduler, styles)
1504
+ remove(node._content, domParent, context)
1451
1505
  let tasks = node._handle.remove()
1452
1506
  scheduler.enqueueTasks(tasks)
1453
1507
  return
1454
1508
  }
1455
1509
 
1456
- if (node.type === Frame) {
1510
+ if (node.kind === 'frame') {
1457
1511
  disposeFrameResources(node)
1458
1512
  removeFrameDomRange(node, domParent)
1459
1513
  return
@@ -1464,63 +1518,53 @@ export function remove(
1464
1518
  function performHostNodeRemoval(
1465
1519
  node: CommittedHostNode,
1466
1520
  domParent: ParentNode,
1467
- scheduler: Scheduler,
1468
- styles: StyleManager,
1469
- ) {
1521
+ context: ReconcileContext,
1522
+ ): void {
1523
+ let children = node._children
1470
1524
  if (isHeadHostNode(node)) {
1471
- for (let child of node._children) {
1472
- remove(child, node._dom, scheduler, styles)
1525
+ for (let i = 0; i < children.length; i++) {
1526
+ remove(children[i], node._dom, context)
1473
1527
  }
1474
1528
  } else {
1475
1529
  // Clean up all descendants first (before removing DOM subtree)
1476
- for (let child of node._children) {
1477
- cleanupDescendants(child, scheduler, styles)
1530
+ for (let i = 0; i < children.length; i++) {
1531
+ cleanupDescendants(children[i], context)
1478
1532
  }
1479
1533
  }
1480
1534
 
1481
- teardownMixins(node._mixState as MixinRuntimeState | undefined)
1482
- teardownDirectEventListeners(node)
1483
- teardownControlledReflection(node)
1484
- // Never remove the real document.head node when reconciling a <head> vnode.
1485
- if (!isHeadHostNode(node)) {
1535
+ teardownMixins(node._mixState)
1536
+ // Never remove the real document.head node when reconciling a <head> vnode,
1537
+ // and since it stays alive, detach its listeners for real.
1538
+ if (isHeadHostNode(node)) {
1539
+ teardownDirectEventListeners(node)
1540
+ teardownControlledReflection(node)
1541
+ } else {
1542
+ abandonDirectEventListeners(node)
1543
+ abandonControlledReflection(node)
1486
1544
  node._dom.parentNode?.removeChild(node._dom)
1487
1545
  }
1488
- if (node._controller) node._controller.abort()
1489
1546
  }
1490
1547
 
1491
1548
  function diffChildren(
1492
- curr: VNode[] | null,
1493
- next: VNode[],
1549
+ curr: CommittedVNode[] | null,
1550
+ next: VNodeInput[],
1494
1551
  domParent: ParentNode,
1495
- frame: FrameHandle,
1496
- scheduler: Scheduler,
1497
- styles: StyleManager,
1498
- vParent: VNode,
1499
- rootTarget: EventTarget,
1500
- cursor?: Node | null,
1552
+ vParent: VNodeParent,
1553
+ context: ReconcileContext,
1554
+ cursor?: HydrationCursor,
1501
1555
  anchor?: Node,
1502
- ) {
1556
+ ): CommittedVNode[] {
1503
1557
  let hasKeys = hasKeyedChildren(next)
1558
+ let committed: Array<VNodeInput | CommittedVNode> = next
1504
1559
 
1505
1560
  if (curr === null) {
1506
1561
  if (hasKeys) {
1507
1562
  warnDuplicateKeys(next)
1508
1563
  }
1509
- for (let node of next) {
1510
- cursor = insert(
1511
- node,
1512
- domParent,
1513
- frame,
1514
- scheduler,
1515
- styles,
1516
- vParent,
1517
- rootTarget,
1518
- anchor,
1519
- cursor,
1520
- )
1564
+ for (let i = 0; i < next.length; i++) {
1565
+ committed[i] = insert(next[i], domParent, vParent, context, anchor, cursor)
1521
1566
  }
1522
- vParent._children = next
1523
- return cursor
1567
+ return committed as CommittedVNode[]
1524
1568
  }
1525
1569
 
1526
1570
  if (
@@ -1529,78 +1573,49 @@ function diffChildren(
1529
1573
  !parentUsesInnerHTML(vParent) &&
1530
1574
  canBulkClearChildren(curr)
1531
1575
  ) {
1532
- for (let node of curr) {
1533
- cleanupDescendants(node, scheduler, styles)
1576
+ for (let i = 0; i < curr.length; i++) {
1577
+ cleanupDescendants(curr[i], context)
1534
1578
  }
1535
1579
  domParent.textContent = ''
1536
- vParent._children = next
1537
- return
1580
+ return EMPTY_COMMITTED_CHILDREN
1538
1581
  }
1539
1582
 
1540
1583
  if (!hasKeys) {
1541
1584
  for (let i = 0; i < next.length; i++) {
1542
1585
  let currentNode = i < curr.length ? curr[i] : null
1543
- diffVNodes(
1544
- currentNode,
1545
- next[i],
1546
- domParent,
1547
- frame,
1548
- scheduler,
1549
- styles,
1550
- vParent,
1551
- rootTarget,
1552
- anchor,
1553
- cursor,
1554
- )
1586
+ committed[i] = diffVNodes(currentNode, next[i], domParent, vParent, context, anchor, cursor)
1555
1587
  }
1556
1588
 
1557
1589
  if (curr.length > next.length) {
1558
1590
  for (let i = next.length; i < curr.length; i++) {
1559
1591
  let node = curr[i]
1560
- if (node) remove(node, domParent, scheduler, styles)
1592
+ if (node) remove(node, domParent, context)
1561
1593
  }
1562
1594
  }
1563
1595
 
1564
- vParent._children = next
1565
- return
1596
+ return committed as CommittedVNode[]
1566
1597
  }
1567
1598
 
1568
- patchKeyedChildren(
1569
- curr,
1570
- next,
1571
- domParent,
1572
- frame,
1573
- scheduler,
1574
- styles,
1575
- vParent,
1576
- rootTarget,
1577
- cursor,
1578
- anchor,
1579
- )
1580
-
1581
- return
1599
+ return patchKeyedChildren(curr, next, domParent, vParent, context, cursor, anchor)
1582
1600
  }
1583
1601
 
1584
- function parentUsesInnerHTML(parent: VNode): boolean {
1602
+ function parentUsesInnerHTML(parent: VNodeParent): boolean {
1585
1603
  return isHostNode(parent) && getHostProps(parent).innerHTML != null
1586
1604
  }
1587
1605
 
1588
- function canBulkClearChildren(children: VNode[]): boolean {
1589
- for (let child of children) {
1590
- if (!canBulkClearNode(child)) return false
1606
+ function canBulkClearChildren(children: CommittedVNode[]): boolean {
1607
+ for (let i = 0; i < children.length; i++) {
1608
+ if (!canBulkClearNode(children[i])) return false
1591
1609
  }
1592
1610
  return true
1593
1611
  }
1594
1612
 
1595
- function canBulkClearNode(node: VNode): boolean {
1613
+ function canBulkClearNode(node: CommittedVNode): boolean {
1596
1614
  if (isCommittedTextNode(node)) return true
1597
1615
 
1598
1616
  if (isCommittedHostNode(node)) {
1599
1617
  if (node._mixState) return false
1600
- for (let child of node._children) {
1601
- if (!canBulkClearNode(child)) return false
1602
- }
1603
- return true
1618
+ return canBulkClearChildren(node._children)
1604
1619
  }
1605
1620
 
1606
1621
  if (isFragmentNode(node)) {
@@ -1614,16 +1629,16 @@ function canBulkClearNode(node: VNode): boolean {
1614
1629
  return false
1615
1630
  }
1616
1631
 
1617
- function hasKeyedChildren(children: VNode[]): boolean {
1618
- for (let node of children) {
1619
- if (node.key != null) return true
1632
+ function hasKeyedChildren(children: Array<{ key?: Key }>): boolean {
1633
+ for (let i = 0; i < children.length; i++) {
1634
+ if (children[i].key != null) return true
1620
1635
  }
1621
1636
  return false
1622
1637
  }
1623
1638
 
1624
- function warnDuplicateKeys(children: VNode[]): void {
1625
- let seenKeys: Set<string> | undefined
1626
- let duplicateKeys: Set<string> | undefined
1639
+ function warnDuplicateKeys(children: Array<{ key?: Key }>): void {
1640
+ let seenKeys: Set<Key> | undefined
1641
+ let duplicateKeys: Set<Key> | undefined
1627
1642
 
1628
1643
  for (let node of children) {
1629
1644
  if (node.key == null) continue
@@ -1642,7 +1657,7 @@ function warnDuplicateKeys(children: VNode[]): void {
1642
1657
  }
1643
1658
 
1644
1659
  if (duplicateKeys?.size) {
1645
- let quotedKeys = Array.from(duplicateKeys, (key) => `"${key}"`)
1660
+ let quotedKeys = Array.from(duplicateKeys, (key) => `"${String(key)}"`)
1646
1661
  console.warn(
1647
1662
  `Duplicate keys detected in siblings: ${quotedKeys.join(', ')}. Keys should be unique.`,
1648
1663
  )
@@ -1650,17 +1665,14 @@ function warnDuplicateKeys(children: VNode[]): void {
1650
1665
  }
1651
1666
 
1652
1667
  function patchKeyedChildren(
1653
- curr: VNode[],
1654
- next: VNode[],
1668
+ curr: CommittedVNode[],
1669
+ next: VNodeInput[],
1655
1670
  domParent: ParentNode,
1656
- frame: FrameHandle,
1657
- scheduler: Scheduler,
1658
- styles: StyleManager,
1659
- vParent: VNode,
1660
- rootTarget: EventTarget,
1661
- cursor?: Node | null,
1671
+ vParent: VNodeParent,
1672
+ context: ReconcileContext,
1673
+ cursor?: HydrationCursor,
1662
1674
  anchor?: Node,
1663
- ): void {
1675
+ ): CommittedVNode[] {
1664
1676
  let matches =
1665
1677
  matchKeyedChildrenInOrder(curr, next) ??
1666
1678
  matchKeyedChildrenAfterSingleRemoval(curr, next) ??
@@ -1669,46 +1681,36 @@ function patchKeyedChildren(
1669
1681
  warnDuplicateKeys(next)
1670
1682
  matches = matchKeyedChildren(curr, next)
1671
1683
  }
1684
+ let committed: Array<VNodeInput | CommittedVNode> = next
1672
1685
 
1673
1686
  let matchAnalysis = analyzeKeyedChildMatches(curr.length, matches)
1674
1687
  if (matchAnalysis.hasRemovals) {
1675
1688
  let usedOldIndexes = new Uint8Array(curr.length)
1676
1689
 
1677
- for (let match of matches) {
1678
- if (match.oldIndex >= 0) {
1679
- usedOldIndexes[match.oldIndex] = 1
1690
+ for (let index = 0; index < matches.length; index++) {
1691
+ let oldIndex = matches[index]
1692
+ if (oldIndex >= 0) {
1693
+ usedOldIndexes[oldIndex] = 1
1680
1694
  }
1681
1695
  }
1682
1696
 
1683
1697
  for (let oldIndex = 0; oldIndex < curr.length; oldIndex++) {
1684
1698
  if (usedOldIndexes[oldIndex] === 0) {
1685
- remove(curr[oldIndex], domParent, scheduler, styles)
1699
+ remove(curr[oldIndex], domParent, context)
1686
1700
  }
1687
1701
  }
1688
1702
  }
1689
1703
 
1690
- vParent._children = next
1691
-
1692
1704
  for (let index = 0; index < next.length; index++) {
1693
- let match = matches[index]
1694
- let oldNode = match.oldIndex >= 0 ? curr[match.oldIndex] : null
1705
+ let oldIndex = matches[index]
1706
+ let oldNode = oldIndex >= 0 ? curr[oldIndex] : null
1695
1707
 
1696
- diffVNodes(
1697
- oldNode,
1698
- next[index],
1699
- domParent,
1700
- frame,
1701
- scheduler,
1702
- styles,
1703
- vParent,
1704
- rootTarget,
1705
- anchor,
1706
- cursor,
1707
- )
1708
+ committed[index] = diffVNodes(oldNode, next[index], domParent, vParent, context, anchor, cursor)
1708
1709
  }
1710
+ let committedChildren = committed as CommittedVNode[]
1709
1711
 
1710
1712
  if (matchAnalysis.canSkipPlacement) {
1711
- return
1713
+ return committedChildren
1712
1714
  }
1713
1715
 
1714
1716
  let stableIndexes = lisMatches(matches)
@@ -1716,7 +1718,7 @@ function patchKeyedChildren(
1716
1718
  let placementAnchor: Node | null = anchor ?? null
1717
1719
 
1718
1720
  for (let index = next.length - 1; index >= 0; index--) {
1719
- let nextNode = next[index]
1721
+ let nextNode = committedChildren[index]
1720
1722
  let isStable = stableIndexes[stableCursor] === index
1721
1723
 
1722
1724
  if (isStable) {
@@ -1727,10 +1729,14 @@ function patchKeyedChildren(
1727
1729
 
1728
1730
  placementAnchor = findFirstDomAnchor(nextNode) ?? placementAnchor
1729
1731
  }
1732
+ return committedChildren
1730
1733
  }
1731
1734
 
1732
- function matchKeyedChildren(curr: VNode[], next: VNode[]): Array<{ oldIndex: number }> {
1733
- let oldKeyMap = new Map<string, number>()
1735
+ // Keyed child matches are arrays of old indexes (-1 = no match / new node),
1736
+ // parallel to `next`. Plain numbers instead of wrapper objects keep large
1737
+ // keyed diffs (1000-row tables) allocation-free.
1738
+ function matchKeyedChildren(curr: CommittedVNode[], next: VNodeInput[]): number[] {
1739
+ let oldKeyMap = new Map<Key, number>()
1734
1740
  let usedOldIndexes = new Set<number>()
1735
1741
  let unkeyedSearchStart = 0
1736
1742
 
@@ -1739,7 +1745,9 @@ function matchKeyedChildren(curr: VNode[], next: VNode[]): Array<{ oldIndex: num
1739
1745
  if (key != null) oldKeyMap.set(key, index)
1740
1746
  }
1741
1747
 
1742
- return next.map((nextNode) => {
1748
+ let matches: number[] = []
1749
+ for (let nextIndex = 0; nextIndex < next.length; nextIndex++) {
1750
+ let nextNode = next[nextIndex]
1743
1751
  let oldIndex = -1
1744
1752
 
1745
1753
  if (nextNode.key != null) {
@@ -1764,16 +1772,15 @@ function matchKeyedChildren(curr: VNode[], next: VNode[]): Array<{ oldIndex: num
1764
1772
  }
1765
1773
 
1766
1774
  if (oldIndex >= 0) usedOldIndexes.add(oldIndex)
1767
- return { oldIndex }
1768
- })
1775
+ matches.push(oldIndex)
1776
+ }
1777
+
1778
+ return matches
1769
1779
  }
1770
1780
 
1771
- function matchKeyedChildrenInOrder(
1772
- curr: VNode[],
1773
- next: VNode[],
1774
- ): Array<{ oldIndex: number }> | null {
1781
+ function matchKeyedChildrenInOrder(curr: CommittedVNode[], next: VNodeInput[]): number[] | null {
1775
1782
  let length = Math.min(curr.length, next.length)
1776
- let matches: Array<{ oldIndex: number }> = []
1783
+ let matches: number[] = []
1777
1784
 
1778
1785
  for (let index = 0; index < length; index++) {
1779
1786
  let nextNode = next[index]
@@ -1784,24 +1791,24 @@ function matchKeyedChildrenInOrder(
1784
1791
  return null
1785
1792
  }
1786
1793
 
1787
- matches.push({ oldIndex: index })
1794
+ matches.push(index)
1788
1795
  }
1789
1796
 
1790
1797
  for (let index = length; index < next.length; index++) {
1791
1798
  if (next[index].key == null) return null
1792
- matches.push({ oldIndex: -1 })
1799
+ matches.push(-1)
1793
1800
  }
1794
1801
 
1795
1802
  return matches
1796
1803
  }
1797
1804
 
1798
1805
  function matchKeyedChildrenAfterSingleRemoval(
1799
- curr: VNode[],
1800
- next: VNode[],
1801
- ): Array<{ oldIndex: number }> | null {
1806
+ curr: CommittedVNode[],
1807
+ next: VNodeInput[],
1808
+ ): number[] | null {
1802
1809
  if (curr.length !== next.length + 1) return null
1803
1810
 
1804
- let matches: Array<{ oldIndex: number }> = []
1811
+ let matches: number[] = []
1805
1812
  let oldIndex = 0
1806
1813
  let skippedOldNode = false
1807
1814
 
@@ -1811,7 +1818,7 @@ function matchKeyedChildrenAfterSingleRemoval(
1811
1818
 
1812
1819
  let oldNode = curr[oldIndex]
1813
1820
  if (oldNode.key === nextNode.key && oldNode.type === nextNode.type) {
1814
- matches.push({ oldIndex })
1821
+ matches.push(oldIndex)
1815
1822
  oldIndex++
1816
1823
  continue
1817
1824
  }
@@ -1825,7 +1832,7 @@ function matchKeyedChildrenAfterSingleRemoval(
1825
1832
  return null
1826
1833
  }
1827
1834
 
1828
- matches.push({ oldIndex })
1835
+ matches.push(oldIndex)
1829
1836
  oldIndex++
1830
1837
  }
1831
1838
 
@@ -1833,12 +1840,12 @@ function matchKeyedChildrenAfterSingleRemoval(
1833
1840
  }
1834
1841
 
1835
1842
  function matchKeyedChildrenAfterPairSwap(
1836
- curr: VNode[],
1837
- next: VNode[],
1838
- ): Array<{ oldIndex: number }> | null {
1843
+ curr: CommittedVNode[],
1844
+ next: VNodeInput[],
1845
+ ): number[] | null {
1839
1846
  if (curr.length !== next.length) return null
1840
1847
 
1841
- let matches: Array<{ oldIndex: number }> = []
1848
+ let matches: number[] = []
1842
1849
  let firstMismatch = -1
1843
1850
  let secondMismatch = -1
1844
1851
 
@@ -1848,7 +1855,7 @@ function matchKeyedChildrenAfterPairSwap(
1848
1855
 
1849
1856
  let oldNode = curr[index]
1850
1857
  if (oldNode.key === nextNode.key && oldNode.type === nextNode.type) {
1851
- matches.push({ oldIndex: index })
1858
+ matches.push(index)
1852
1859
  continue
1853
1860
  }
1854
1861
 
@@ -1859,7 +1866,7 @@ function matchKeyedChildrenAfterPairSwap(
1859
1866
  } else {
1860
1867
  return null
1861
1868
  }
1862
- matches.push({ oldIndex: -1 })
1869
+ matches.push(-1)
1863
1870
  }
1864
1871
 
1865
1872
  if (firstMismatch === -1) return matches
@@ -1879,42 +1886,43 @@ function matchKeyedChildrenAfterPairSwap(
1879
1886
  return null
1880
1887
  }
1881
1888
 
1882
- matches[firstMismatch] = { oldIndex: secondMismatch }
1883
- matches[secondMismatch] = { oldIndex: firstMismatch }
1889
+ matches[firstMismatch] = secondMismatch
1890
+ matches[secondMismatch] = firstMismatch
1884
1891
  return matches
1885
1892
  }
1886
1893
 
1887
1894
  function analyzeKeyedChildMatches(
1888
1895
  currentLength: number,
1889
- matches: ReadonlyArray<{ oldIndex: number }>,
1896
+ matches: readonly number[],
1890
1897
  ): { hasRemovals: boolean; canSkipPlacement: boolean } {
1891
1898
  let hasRemovals = matches.length !== currentLength
1892
1899
  let canSkipPlacement = true
1893
1900
  let lastOldIndex = -1
1894
1901
  let sawNewNode = false
1895
1902
 
1896
- for (let match of matches) {
1897
- if (match.oldIndex < 0) {
1903
+ for (let index = 0; index < matches.length; index++) {
1904
+ let oldIndex = matches[index]
1905
+ if (oldIndex < 0) {
1898
1906
  hasRemovals = true
1899
1907
  sawNewNode = true
1900
1908
  continue
1901
1909
  }
1902
1910
 
1903
- if (sawNewNode || match.oldIndex < lastOldIndex) {
1911
+ if (sawNewNode || oldIndex < lastOldIndex) {
1904
1912
  canSkipPlacement = false
1905
1913
  }
1906
- lastOldIndex = match.oldIndex
1914
+ lastOldIndex = oldIndex
1907
1915
  }
1908
1916
 
1909
1917
  return { hasRemovals, canSkipPlacement }
1910
1918
  }
1911
1919
 
1912
- function lisMatches(matches: ReadonlyArray<{ oldIndex: number }>): number[] {
1920
+ function lisMatches(matches: readonly number[]): number[] {
1913
1921
  let predecessors = Array.from<number>({ length: matches.length })
1914
1922
  let tails: number[] = []
1915
1923
 
1916
1924
  for (let index = 0; index < matches.length; index++) {
1917
- let value = matches[index].oldIndex + 1
1925
+ let value = matches[index] + 1
1918
1926
  if (value === 0) continue
1919
1927
 
1920
1928
  let low = 0
@@ -1923,7 +1931,7 @@ function lisMatches(matches: ReadonlyArray<{ oldIndex: number }>): number[] {
1923
1931
  while (low < high) {
1924
1932
  let middle = (low + high) >> 1
1925
1933
 
1926
- if (matches[tails[middle]].oldIndex + 1 < value) {
1934
+ if (matches[tails[middle]] + 1 < value) {
1927
1935
  low = middle + 1
1928
1936
  } else {
1929
1937
  high = middle
@@ -1944,7 +1952,7 @@ function lisMatches(matches: ReadonlyArray<{ oldIndex: number }>): number[] {
1944
1952
  return tails
1945
1953
  }
1946
1954
 
1947
- function placeVNode(node: VNode, domParent: ParentNode, anchor: Node | null): void {
1955
+ function placeVNode(node: CommittedVNode, domParent: ParentNode, anchor: Node | null): void {
1948
1956
  let firstDom = findFirstDomAnchor(node)
1949
1957
  if (!firstDom || firstDom.parentNode !== domParent) return
1950
1958
 
@@ -1956,27 +1964,28 @@ function placeVNode(node: VNode, domParent: ParentNode, anchor: Node | null): vo
1956
1964
  moveDomRange(domParent, firstDom, lastDom, anchor)
1957
1965
  }
1958
1966
 
1959
- export function findFirstDomAnchor(node: VNode | null | undefined): Node | null {
1967
+ export function findFirstDomAnchor(node: CommittedVNode | null | undefined): Node | null {
1960
1968
  if (!node) return null
1961
1969
  if (isCommittedTextNode(node)) return node._dom
1962
1970
  if (isCommittedHostNode(node)) return node._dom
1963
1971
  if (isCommittedComponentNode(node)) return findFirstDomAnchor(node._content)
1964
- if (node.type === Frame) return node._rangeStart ?? null
1972
+ if (node.kind === 'frame') return node._rangeStart
1965
1973
  if (isFragmentNode(node)) {
1966
- for (let child of node._children) {
1967
- let dom = findFirstDomAnchor(child)
1974
+ let children = node._children
1975
+ for (let i = 0; i < children.length; i++) {
1976
+ let dom = findFirstDomAnchor(children[i])
1968
1977
  if (dom) return dom
1969
1978
  }
1970
1979
  }
1971
1980
  return null
1972
1981
  }
1973
1982
 
1974
- export function findLastDomAnchor(node: VNode | null | undefined): Node | null {
1983
+ export function findLastDomAnchor(node: CommittedVNode | null | undefined): Node | null {
1975
1984
  if (!node) return null
1976
1985
  if (isCommittedTextNode(node)) return node._dom
1977
1986
  if (isCommittedHostNode(node)) return node._dom
1978
1987
  if (isCommittedComponentNode(node)) return findLastDomAnchor(node._content)
1979
- if (node.type === Frame) return node._rangeEnd ?? null
1988
+ if (node.kind === 'frame') return node._rangeEnd
1980
1989
  if (isFragmentNode(node)) {
1981
1990
  for (let i = node._children.length - 1; i >= 0; i--) {
1982
1991
  let dom = findLastDomAnchor(node._children[i])
@@ -2014,20 +2023,27 @@ function shouldDispatchInlineMixinLifecycle(node: Node): boolean {
2014
2023
  let parents = activeSchedulerUpdateParents
2015
2024
  if (!parents?.length) return true
2016
2025
  for (let parent of parents) {
2017
- let parentNode = parent as Node
2026
+ if (!(parent instanceof Node)) continue
2027
+ let parentNode = parent
2018
2028
  if (parentNode === node) return false
2019
2029
  if (parentNode.contains(node)) return false
2020
2030
  }
2021
2031
  return true
2022
2032
  }
2023
2033
 
2024
- export function findNextSiblingDomAnchor(curr: VNode): Node | null {
2034
+ export function findNextSiblingDomAnchor(
2035
+ curr: CommittedVNode | MountingComponentNode,
2036
+ ): Node | null {
2025
2037
  let vParent = curr._parent
2026
- if (!vParent || !Array.isArray(vParent._children)) return null
2038
+ if (vParent.kind === 'component') return findNextSiblingDomAnchor(vParent)
2027
2039
  let children = vParent._children
2028
- if (children.length === 0) return findNextSiblingDomAnchor(vParent)
2040
+ if (children.length === 0) {
2041
+ if (vParent.kind === 'root') return vParent._rangeEnd ?? null
2042
+ if (vParent.kind === 'fragment') return findNextSiblingDomAnchor(vParent)
2043
+ return null
2044
+ }
2029
2045
 
2030
- let idx = children.indexOf(curr)
2046
+ let idx = children.indexOf(curr as CommittedVNode)
2031
2047
  if (idx === -1) return null
2032
2048
  for (let i = idx + 1; i < children.length; i++) {
2033
2049
  let dom = findFirstDomAnchor(children[i])
@@ -2038,60 +2054,53 @@ export function findNextSiblingDomAnchor(curr: VNode): Node | null {
2038
2054
  return findNextSiblingDomAnchor(vParent)
2039
2055
  }
2040
2056
 
2041
- return null
2057
+ return vParent.kind === 'root' ? (vParent._rangeEnd ?? null) : null
2042
2058
  }
2043
2059
 
2044
2060
  function reclaimPersistedMixinNode(
2045
2061
  persistedNode: CommittedHostNode,
2046
2062
  newNode: HostNode,
2047
- frame: FrameHandle,
2048
- scheduler: Scheduler,
2049
- styles: StyleManager,
2050
- vParent: VNode,
2051
- rootTarget: EventTarget,
2052
- ): void {
2053
- cancelPendingMixinRemoval(persistedNode._mixState as MixinRuntimeState | undefined)
2063
+ vParent: VNodeParent,
2064
+ context: ReconcileContext,
2065
+ ): CommittedHostNode {
2066
+ let { frame, scheduler, styles } = context
2067
+ cancelPendingMixinRemoval(persistedNode._mixState)
2054
2068
  unmarkNodePersistedByMixins(persistedNode)
2055
2069
 
2056
- newNode._dom = persistedNode._dom
2057
- newNode._parent = vParent
2058
- newNode._controller = persistedNode._controller
2059
- newNode._mixState = persistedNode._mixState
2060
- newNode._directEventState = persistedNode._directEventState
2061
- newNode._controlledState = persistedNode._controlledState
2062
-
2063
2070
  let prevProps = getHostProps(persistedNode)
2064
- let nextProps = resolveNodeMixProps(
2071
+ let childInputs = newNode._children
2072
+ let resolved = resolveNodeMixProps(newNode, vParent, frame, scheduler, persistedNode._mixState)
2073
+ let nextProps = resolved.props
2074
+ let committed = commitHostNode(
2065
2075
  newNode,
2066
- frame,
2067
- scheduler,
2068
- newNode._mixState as MixinRuntimeState | undefined,
2076
+ vParent,
2077
+ getSvgContext(vParent, newNode),
2078
+ persistedNode._dom,
2079
+ resolved,
2069
2080
  )
2081
+ committed._directEventState = persistedNode._directEventState
2082
+ committed._controlledState = persistedNode._controlledState
2070
2083
  if (shouldDispatchInlineMixinLifecycle(persistedNode._dom)) {
2071
- dispatchMixinBeforeUpdate(newNode._mixState as MixinRuntimeState | undefined)
2084
+ dispatchMixinBeforeUpdate(committed._mixState)
2072
2085
  }
2073
2086
  patchHostProps(prevProps, nextProps, persistedNode._dom)
2074
- syncDirectEventListeners(newNode as CommittedHostNode)
2075
- ensureControlledReflection(newNode as CommittedHostNode, scheduler)
2076
- syncControlledReflection(newNode as CommittedHostNode, nextProps)
2087
+ syncDirectEventListeners(committed)
2088
+ ensureControlledReflection(committed, scheduler)
2089
+ syncControlledReflection(committed, nextProps)
2077
2090
 
2078
- diffChildren(
2091
+ committed._children = diffChildren(
2079
2092
  persistedNode._children,
2080
- newNode._children,
2093
+ childInputs,
2081
2094
  persistedNode._dom,
2082
- frame,
2083
- scheduler,
2084
- styles,
2085
- newNode,
2086
- rootTarget,
2095
+ committed,
2096
+ context,
2087
2097
  )
2088
2098
 
2089
- if (newNode._mixState) {
2090
- bindNodeMixRuntime(newNode as CommittedHostNode, frame, scheduler, styles, true)
2099
+ if (committed._mixState) {
2100
+ bindNodeMixRuntime(committed, frame, scheduler, styles, true)
2091
2101
  }
2092
2102
  if (shouldDispatchInlineMixinLifecycle(persistedNode._dom)) {
2093
- scheduler.enqueueCommitPhase([
2094
- () => dispatchMixinCommit(newNode._mixState as MixinRuntimeState | undefined),
2095
- ])
2103
+ scheduler.enqueueCommitPhase([() => dispatchMixinCommit(committed._mixState)])
2096
2104
  }
2105
+ return committed
2097
2106
  }