@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,14 +1,22 @@
1
1
  import { jsx } from './jsx.ts'
2
- import { Frame, createFrameHandle, type FrameContent } from './component.ts'
2
+ import { Frame, createFrameHandle, type FrameContent, type FrameResolution } from './component.ts'
3
3
  import { createComponentErrorEvent, getComponentError } from './error-event.ts'
4
4
  import { invariant } from './invariant.ts'
5
5
  import type { RemixElement, RemixNode } from './jsx.ts'
6
+ import type { ElementFunction } from './element-function.ts'
6
7
  import type { FrameHandle } from './component.ts'
7
8
  import type { Scheduler, VirtualRoot } from './vdom.ts'
8
9
  import { createRangeRoot, createRoot } from './vdom.ts'
9
10
  import { diffNodes } from './diff-dom.ts'
10
11
  import { createStyleManager, type StyleManager } from '../style/index.ts'
11
12
  import { findFlushMarker, type FlushKind } from './stream-protocol.ts'
13
+ import { unwrapFrameResolution } from './frame-resolution.ts'
14
+ import {
15
+ disposeClientEntryBoundary,
16
+ getClientEntryBoundaryOwner,
17
+ setClientEntryBoundaryOwner,
18
+ type ClientEntryIdentity,
19
+ } from './client-entry-boundary.ts'
12
20
 
13
21
  type FrameRoot = [Comment, Comment] | Element | Document | DocumentFragment
14
22
 
@@ -62,24 +70,71 @@ export type LoadModule = (moduleUrl: string, exportName: string) => Promise<Func
62
70
  * Resolves content for a browser-loaded frame.
63
71
  *
64
72
  * @param src Source string from the `<Frame src>` prop.
65
- * @param signal Abort signal for the active frame load or reload.
66
- * @param target Optional name of the frame being reloaded.
67
- * @returns HTML, a stream of HTML bytes, or Remix node content to render into the frame.
73
+ * @param options Information about the active frame load or form submission.
74
+ * @returns Frame content or a response whose body should be rendered into the frame.
68
75
  */
69
76
  export type ResolveFrame = (
70
77
  src: string,
71
- signal?: AbortSignal,
72
- target?: string,
73
- ) => Promise<FrameContent> | FrameContent
78
+ options?: ResolveFrameOptions,
79
+ ) => Promise<FrameResolution> | FrameResolution
80
+
81
+ /**
82
+ * Information available while resolving browser-loaded frame content.
83
+ */
84
+ export interface ResolveFrameOptions {
85
+ /** Optional name of the frame being loaded or reloaded. */
86
+ target?: string
87
+ /** Form values submitted to the frame source for a non-GET submission. */
88
+ formData?: FormData
89
+ /** HTTP method selected by the form and its submitter. */
90
+ method?: string
91
+ /** Form encoding selected by the form and its submitter. */
92
+ encType?: string
93
+ /** Aborts the reload when the navigation that started it is cancelled. */
94
+ signal?: AbortSignal
95
+ }
74
96
 
75
97
  type InternalFrameContent = FrameContent | DocumentFragment
76
98
 
99
+ type FrameReloadOptions = Omit<ResolveFrameOptions, 'target'>
100
+
101
+ type FrameReloadResult = {
102
+ signal: AbortSignal
103
+ redirectedTo?: string
104
+ }
105
+
77
106
  type FrameTemplateListener = (fragment: DocumentFragment) => void
78
107
 
79
108
  const bufferedFrameTemplates = new Map<string, DocumentFragment[]>()
80
109
  const frameTemplateListeners = new Map<string, Set<FrameTemplateListener>>()
81
110
  const DOCTYPE_PATTERN = /<!doctype(?:\s[^>]*)?>/gi
82
111
 
112
+ function createLinkedAbortController(
113
+ first: AbortSignal,
114
+ second?: AbortSignal,
115
+ ): { controller: AbortController; disconnect: () => void } {
116
+ let controller = new AbortController()
117
+ let signals = second ? [first, second] : [first]
118
+ let abort = () => controller.abort()
119
+
120
+ for (let signal of signals) {
121
+ if (signal.aborted) {
122
+ controller.abort()
123
+ break
124
+ }
125
+ signal.addEventListener('abort', abort, { once: true })
126
+ }
127
+
128
+ return {
129
+ controller,
130
+ disconnect() {
131
+ for (let signal of signals) {
132
+ signal.removeEventListener('abort', abort)
133
+ }
134
+ },
135
+ }
136
+ }
137
+
83
138
  function stripDoctypeMarkup(html: string): string {
84
139
  return html.replace(DOCTYPE_PATTERN, '')
85
140
  }
@@ -98,7 +153,11 @@ function syncElementAttributes(target: Element, source: Element) {
98
153
  }
99
154
  }
100
155
 
156
+ const FRAME_RUNTIME = Symbol('FrameRuntime')
157
+
101
158
  export type FrameRuntime = {
159
+ [FRAME_RUNTIME]: true
160
+ canResolveFrames?: boolean
102
161
  topFrame?: FrameHandle
103
162
  errorTarget: EventTarget
104
163
  loadModule: LoadModule
@@ -106,11 +165,34 @@ export type FrameRuntime = {
106
165
  pendingClientEntries: PendingClientEntries
107
166
  scheduler: Scheduler
108
167
  styleManager: StyleManager
109
- data: RmxData
110
- moduleCache: Map<string, Function>
111
- moduleLoads: Map<string, Promise<Function | undefined>>
168
+ moduleCache: Map<string, ElementFunction>
169
+ moduleLoads: Map<string, Promise<ElementFunction | undefined>>
112
170
  frameInstances: WeakMap<Comment, Frame>
113
171
  namedFrames: Map<string, FrameHandle>
172
+ serverFrameReload: { signal: AbortSignal } | undefined
173
+ reloadForNavigation?: (options?: FrameReloadOptions) => Promise<FrameReloadResult>
174
+ }
175
+
176
+ export function isFrameRuntime(value: unknown): value is FrameRuntime {
177
+ return isRecord(value) && Reflect.get(value, FRAME_RUNTIME) === true
178
+ }
179
+
180
+ /**
181
+ * Reloads a frame and returns response metadata used by the navigation runtime.
182
+ *
183
+ * @param frame Frame handle to reload.
184
+ * @param options Form submission metadata and cancellation signal.
185
+ * @returns The reload signal and final response URL, when redirected.
186
+ */
187
+ export function reloadFrameForNavigation(
188
+ frame: FrameHandle,
189
+ options?: FrameReloadOptions,
190
+ ): Promise<FrameReloadResult> {
191
+ let runtime = frame.$runtime
192
+ invariant(isFrameRuntime(runtime), 'Expected a frame runtime')
193
+ let reload = runtime.reloadForNavigation
194
+ invariant(reload, 'Expected frame runtime to support navigation reloads')
195
+ return reload(options)
114
196
  }
115
197
 
116
198
  export type FrameContext = {
@@ -123,10 +205,11 @@ export type FrameContext = {
123
205
  frame: FrameHandle
124
206
  styleManager: StyleManager
125
207
  data: RmxData
126
- moduleCache: Map<string, Function>
127
- moduleLoads: Map<string, Promise<Function | undefined>>
208
+ moduleCache: Map<string, ElementFunction>
209
+ moduleLoads: Map<string, Promise<ElementFunction | undefined>>
128
210
  frameInstances: WeakMap<Comment, Frame>
129
211
  namedFrames: Map<string, FrameHandle>
212
+ lifecycleSignal: AbortSignal
130
213
  regionTailRef?: ChildNode | null
131
214
  regionParent?: ParentNode | null
132
215
  signal?: AbortSignal
@@ -144,8 +227,8 @@ type FrameInit = {
144
227
  styleManager?: StyleManager
145
228
  marker?: FrameMarkerData
146
229
  data: RmxData
147
- moduleCache: Map<string, Function>
148
- moduleLoads: Map<string, Promise<Function | undefined>>
230
+ moduleCache: Map<string, ElementFunction>
231
+ moduleLoads: Map<string, Promise<ElementFunction | undefined>>
149
232
  frameInstances: WeakMap<Comment, Frame>
150
233
  namedFrames: Map<string, FrameHandle>
151
234
  }
@@ -156,6 +239,11 @@ export type Frame = {
156
239
  flush: () => void
157
240
  clearPendingTemplateWatch: () => void
158
241
  isDisplayingResolvedContent: () => boolean
242
+ beginClientFrameReloadForAncestorReload: (signal: AbortSignal) => {
243
+ controller: AbortController
244
+ complete: () => void
245
+ }
246
+ cancelReload: () => void
159
247
  startInheritedReload: (signal?: AbortSignal) => void
160
248
  updateMarker: (marker: FrameMarkerData, options?: RenderOptions) => Promise<void>
161
249
  renderMarkerContent: (
@@ -163,6 +251,7 @@ export type Frame = {
163
251
  content: InternalFrameContent,
164
252
  options?: RenderOptions,
165
253
  ) => Promise<void>
254
+ matchesIdentity: (src: string, name: string | undefined) => boolean
166
255
  dispose: () => void
167
256
  handle: FrameHandle
168
257
  }
@@ -172,12 +261,22 @@ type RenderOptions = {
172
261
  initialHydrationTracker?: InitialHydrationTracker
173
262
  signal?: AbortSignal
174
263
  contentStatus?: 'pending' | 'resolved'
264
+ data?: RmxData
265
+ }
266
+
267
+ type ActiveRenderOptions = RenderOptions & {
268
+ data: RmxData
175
269
  }
176
270
 
177
271
  export function createFrame(root: FrameRoot, init: FrameInit): Frame {
178
272
  let container = createContainer(root)
179
273
  let contentRoot: VirtualRoot | undefined
180
274
  let reloadController: AbortController | undefined
275
+ // The style registry is document-level and shared by every frame; only the
276
+ // frame that created it (the runtime root) disposes it.
277
+ let ownsStyleManager = !init.styleManager
278
+ let reloadAbortUnsubscribe: (() => void) | undefined
279
+ let reloadKind: 'direct' | 'ancestor' | undefined
181
280
  let styleManager = init.styleManager ?? createStyleManager()
182
281
  let currentMarker = init.marker
183
282
  let displayedContentStatus: 'pending' | 'resolved' = init.marker?.status ?? 'resolved'
@@ -186,36 +285,18 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
186
285
  let pendingTemplateUnsubscribe: (() => void) | undefined
187
286
  let inheritedReloadPending = false
188
287
  let inheritedReloadAbortUnsubscribe: (() => void) | undefined
288
+ let disposed = false
289
+ let lifecycleController = new AbortController()
189
290
 
190
291
  // Merge any rmx-data found in the current document once at startup.
191
292
  mergeRmxDataFromDocument(init.data, container.doc)
192
293
 
193
- let runtime = createFrameRuntime({ ...init, styleManager })
294
+ let runtime = createFrameRuntime({ ...init, styleManager, reloadForNavigation: reload })
194
295
 
195
296
  let frame = createFrameHandle({
196
297
  src: init.src,
197
298
  $runtime: runtime,
198
- reload: async () => {
199
- reloadController?.abort()
200
- let controller = new AbortController()
201
- reloadController = controller
202
- frame.dispatchEvent(new Event('reloadStart'))
203
- startSubFrameInheritedReloads(getContentNodes(), controller.signal)
204
- try {
205
- let content = await init.resolveFrame(frame.src, controller.signal, frameName)
206
- if (reloadController !== controller || controller.signal.aborted) return controller.signal
207
- await render(content, { signal: controller.signal })
208
- return controller.signal
209
- } catch (error) {
210
- if (reloadController !== controller || controller.signal.aborted) return controller.signal
211
- init.errorTarget.dispatchEvent(createComponentErrorEvent(error))
212
- throw error
213
- } finally {
214
- if (reloadController === controller) {
215
- frame.dispatchEvent(new Event('reloadComplete'))
216
- }
217
- }
218
- },
299
+ reload: async () => (await reload()).signal,
219
300
  replace: async (content: FrameContent) => {
220
301
  await render(content)
221
302
  },
@@ -241,18 +322,47 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
241
322
  moduleLoads: init.moduleLoads,
242
323
  frameInstances: init.frameInstances,
243
324
  namedFrames: init.namedFrames,
325
+ lifecycleSignal: lifecycleController.signal,
244
326
  regionTailRef: container.regionTailRef,
245
327
  regionParent: container.regionParent,
246
328
  }
247
329
 
248
330
  async function render(content: InternalFrameContent, options?: RenderOptions): Promise<void> {
249
- if (options?.signal?.aborted) return
331
+ if (disposed || lifecycleController.signal.aborted || options?.signal?.aborted) return
332
+ let ownsData = options?.data === undefined
333
+ let renderOptions: ActiveRenderOptions = {
334
+ ...options,
335
+ data: options?.data ?? {},
336
+ }
337
+
338
+ try {
339
+ await renderContent(content, renderOptions)
340
+ } finally {
341
+ if (ownsData) clearRmxData(renderOptions.data)
342
+ }
343
+ }
344
+
345
+ async function renderContent(
346
+ content: InternalFrameContent,
347
+ options: ActiveRenderOptions,
348
+ ): Promise<void> {
349
+ if (isRenderAborted(options.signal)) return
250
350
 
251
351
  if (content instanceof ReadableStream) {
252
- await renderFrameStream(content, container.doc, async (html, flushKind) => {
253
- if (options?.signal?.aborted) return
254
- await render(html, { ...options, flushKind })
255
- })
352
+ let linkedAbort = createLinkedAbortController(lifecycleController.signal, options.signal)
353
+ try {
354
+ await renderFrameStream(
355
+ content,
356
+ container.doc,
357
+ async (html, flushKind) => {
358
+ if (isRenderAborted(options.signal)) return
359
+ await render(html, { ...options, flushKind })
360
+ },
361
+ linkedAbort.controller.signal,
362
+ )
363
+ } finally {
364
+ linkedAbort.disconnect()
365
+ }
256
366
  return
257
367
  }
258
368
 
@@ -265,9 +375,9 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
265
375
  contentRoot = createFrameContentRoot()
266
376
  }
267
377
 
268
- if (options?.signal?.aborted) return
378
+ if (isRenderAborted(options.signal)) return
269
379
  contentRoot.render(content)
270
- displayedContentStatus = options?.contentStatus ?? 'resolved'
380
+ displayedContentStatus = options.contentStatus ?? 'resolved'
271
381
  return
272
382
  }
273
383
 
@@ -293,61 +403,81 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
293
403
  let isFullDocumentReload =
294
404
  container.root instanceof Document &&
295
405
  htmlContent !== undefined &&
296
- options?.flushKind === 'document'
406
+ options.flushKind === 'document'
297
407
 
298
408
  if (isFullDocumentReload && htmlContent !== undefined) {
299
409
  let parsed = new DOMParser().parseFromString(htmlContent, 'text/html')
300
- mergeRmxDataFromDocument(context.data, parsed)
301
- context.styleManager.replaceServerStyles(
410
+ let responseData = options.data
411
+ mergeRmxDataFromDocument(responseData, parsed)
412
+ let responseContext = { ...context, data: responseData }
413
+ context.styleManager.adoptServerStyles(
302
414
  collectFrameServerStyleTags(createElementContainer(parsed)),
303
415
  )
304
416
 
305
417
  syncElementAttributes(container.doc.documentElement, parsed.documentElement)
306
418
 
307
419
  diffNodes([container.doc.head], [parsed.head], {
308
- ...context,
420
+ ...responseContext,
309
421
  regionParent: container.doc.documentElement,
310
422
  regionTailRef: null,
311
- signal: options?.signal,
423
+ signal: options.signal,
312
424
  })
313
425
  diffNodes([container.doc.body], [parsed.body], {
314
- ...context,
426
+ ...responseContext,
315
427
  regionParent: container.doc.documentElement,
316
428
  regionTailRef: null,
317
- signal: options?.signal,
429
+ signal: options.signal,
318
430
  })
319
431
 
320
432
  let bodyContainer = createElementContainer(container.doc.body)
321
- if (options?.signal?.aborted) return
322
- scheduleHydrationInContainer(bodyContainer, context, options?.initialHydrationTracker)
323
- await createSubFrames(bodyContainer.childNodes, context, options)
324
- displayedContentStatus = options?.contentStatus ?? 'resolved'
433
+ if (isRenderAborted(options.signal)) return
434
+ scheduleHydrationInContainer(
435
+ bodyContainer,
436
+ responseContext,
437
+ options.initialHydrationTracker,
438
+ options.signal,
439
+ )
440
+ await createSubFrames(bodyContainer.childNodes, responseContext, options)
441
+ if (isRenderAborted(options.signal)) return
442
+ displayedContentStatus = options.contentStatus ?? 'resolved'
325
443
  return
326
444
  }
327
445
 
328
446
  let fragment =
329
447
  htmlContent !== undefined ? createFragmentFromString(container.doc, htmlContent) : content
330
- context.styleManager.replaceServerStyles(
448
+ context.styleManager.adoptServerStyles(
331
449
  collectFrameServerStyleTags(createElementContainer(fragment)),
332
450
  )
333
451
  removeEmptyHeads(fragment)
334
- mergeRmxDataFromFragment(context.data, fragment)
452
+ let responseData = options.data
453
+ mergeRmxDataFromFragment(responseData, fragment)
454
+ let responseContext = { ...context, data: responseData }
335
455
 
336
456
  let nextContainer = createContainer(fragment)
337
457
 
338
- if (options?.signal?.aborted) return
458
+ if (isRenderAborted(options.signal)) return
339
459
 
340
460
  diffNodes(container.childNodes, Array.from(nextContainer.childNodes), {
341
- ...context,
461
+ ...responseContext,
342
462
  regionTailRef: container.regionTailRef,
343
463
  regionParent: container.regionParent,
344
- signal: options?.signal,
464
+ signal: options.signal,
345
465
  })
346
466
 
347
- if (options?.signal?.aborted) return
348
- scheduleHydrationInContainer(container, context, options?.initialHydrationTracker)
349
- await createSubFrames(container.childNodes, context, options)
350
- displayedContentStatus = options?.contentStatus ?? 'resolved'
467
+ if (isRenderAborted(options.signal)) return
468
+ scheduleHydrationInContainer(
469
+ container,
470
+ responseContext,
471
+ options.initialHydrationTracker,
472
+ options.signal,
473
+ )
474
+ await createSubFrames(container.childNodes, responseContext, options)
475
+ if (isRenderAborted(options.signal)) return
476
+ displayedContentStatus = options.contentStatus ?? 'resolved'
477
+ }
478
+
479
+ function isRenderAborted(signal?: AbortSignal): boolean {
480
+ return disposed || lifecycleController.signal.aborted || signal?.aborted === true
351
481
  }
352
482
 
353
483
  function createFrameContentRoot(): VirtualRoot {
@@ -390,21 +520,36 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
390
520
  async function hydrateInitial(): Promise<void> {
391
521
  let initialHydrationTracker = createInitialHydrationTracker()
392
522
 
393
- context.styleManager.replaceServerStyles(collectFrameServerStyleTags(container))
394
- await createSubFrames(container.childNodes, context)
523
+ context.styleManager.adoptServerStyles(collectFrameServerStyleTags(container))
524
+ let subFramesReady = createSubFrames(container.childNodes, context)
395
525
  scheduleHydrationInContainer(container, context, initialHydrationTracker)
396
526
 
397
- if (currentMarker?.status === 'pending') {
398
- await watchPendingFrameTemplate(currentMarker, initialHydrationTracker)
399
- }
527
+ try {
528
+ await subFramesReady
529
+
530
+ if (disposed || context.lifecycleSignal.aborted) return
531
+
532
+ if (currentMarker?.status === 'pending') {
533
+ await watchPendingFrameTemplate(currentMarker, initialHydrationTracker)
534
+ }
400
535
 
401
- initialHydrationTracker.finalize()
402
- await initialHydrationTracker.ready()
536
+ initialHydrationTracker.finalize()
537
+ await initialHydrationTracker.ready()
538
+ } finally {
539
+ clearRmxData(context.data)
540
+ }
403
541
  }
404
542
 
405
543
  function dispose(): void {
544
+ if (disposed) return
545
+ disposed = true
546
+ lifecycleController.abort()
547
+ clearRmxData(context.data)
406
548
  reloadController?.abort()
407
549
  reloadController = undefined
550
+ reloadAbortUnsubscribe?.()
551
+ reloadAbortUnsubscribe = undefined
552
+ reloadKind = undefined
408
553
  contentRoot?.dispose()
409
554
  contentRoot = undefined
410
555
  clearPendingFrameTemplateWatch()
@@ -414,7 +559,9 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
414
559
 
415
560
  // Dispose sub-frames recursively.
416
561
  disposeSubFrames(container.childNodes, context)
417
- context.styleManager.dispose()
562
+ if (ownsStyleManager) {
563
+ context.styleManager.dispose()
564
+ }
418
565
 
419
566
  if (frameName) {
420
567
  if (init.namedFrames.get(frameName) === frame) {
@@ -431,15 +578,18 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
431
578
  flush: () => context.scheduler.dequeue(),
432
579
  clearPendingTemplateWatch: clearPendingFrameTemplateWatch,
433
580
  isDisplayingResolvedContent: () => displayedContentStatus === 'resolved',
581
+ beginClientFrameReloadForAncestorReload,
582
+ cancelReload,
434
583
  startInheritedReload,
435
584
  updateMarker,
436
585
  renderMarkerContent,
586
+ matchesIdentity: (src, name) => !disposed && frame.src === src && frameName === name,
437
587
  dispose,
438
588
  handle: frame,
439
589
  }
440
590
 
441
591
  async function updateMarker(marker: FrameMarkerData, options?: RenderOptions): Promise<void> {
442
- if (options?.signal?.aborted) return
592
+ if (disposed || context.lifecycleSignal.aborted || options?.signal?.aborted) return
443
593
  let previousMarker = currentMarker
444
594
  let isInheritedReload = previousMarker !== undefined && previousMarker.id !== marker.id
445
595
  currentMarker = marker
@@ -472,7 +622,7 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
472
622
  content: InternalFrameContent,
473
623
  options?: RenderOptions,
474
624
  ): Promise<void> {
475
- if (options?.signal?.aborted) return
625
+ if (disposed || context.lifecycleSignal.aborted || options?.signal?.aborted) return
476
626
  let previousMarker = currentMarker
477
627
  let isInheritedReload = previousMarker !== undefined && previousMarker.id !== marker.id
478
628
  currentMarker = marker
@@ -484,11 +634,119 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
484
634
  clearPendingFrameTemplateWatch()
485
635
  await render(content, { ...options, contentStatus: 'resolved' })
486
636
 
487
- if (isInheritedReload && !options?.signal?.aborted) {
637
+ if (
638
+ isInheritedReload &&
639
+ !disposed &&
640
+ !context.lifecycleSignal.aborted &&
641
+ !options?.signal?.aborted
642
+ ) {
488
643
  completeInheritedReload()
489
644
  }
490
645
  }
491
646
 
647
+ async function reload(options?: FrameReloadOptions): Promise<FrameReloadResult> {
648
+ let controller = startReload(options?.signal)
649
+ return await resolveAndRenderReload(controller, options)
650
+ }
651
+
652
+ function startReload(signal?: AbortSignal): AbortController {
653
+ let controller = replaceReloadController(signal)
654
+ reloadKind = 'direct'
655
+ frame.dispatchEvent(new Event('reloadStart'))
656
+ startSubFrameInheritedReloads(getContentNodes(), controller.signal)
657
+ return controller
658
+ }
659
+
660
+ function beginClientFrameReloadForAncestorReload(signal: AbortSignal): {
661
+ controller: AbortController
662
+ complete: () => void
663
+ } {
664
+ let inheritedReloadStarted = reuseInheritedReloadStart()
665
+ let continuingAncestorReload = reloadKind === 'ancestor'
666
+ let controller = replaceReloadController(signal)
667
+ reloadKind = 'ancestor'
668
+
669
+ if (!inheritedReloadStarted && !continuingAncestorReload) {
670
+ frame.dispatchEvent(new Event('reloadStart'))
671
+ startSubFrameInheritedReloads(getContentNodes(), controller.signal)
672
+ }
673
+
674
+ return {
675
+ controller,
676
+ complete: () => completeReload(controller),
677
+ }
678
+ }
679
+
680
+ function cancelReload(): void {
681
+ let controller = reloadController
682
+ if (!controller) return
683
+ controller.abort()
684
+ completeReload(controller)
685
+ }
686
+
687
+ function replaceReloadController(signal?: AbortSignal): AbortController {
688
+ reloadController?.abort()
689
+ reloadAbortUnsubscribe?.()
690
+ reloadAbortUnsubscribe = undefined
691
+
692
+ let controller = new AbortController()
693
+ reloadController = controller
694
+
695
+ if (signal) {
696
+ if (signal.aborted) {
697
+ controller.abort()
698
+ } else {
699
+ let abort = () => controller.abort()
700
+ signal.addEventListener('abort', abort, { once: true })
701
+ reloadAbortUnsubscribe = () => signal.removeEventListener('abort', abort)
702
+ }
703
+ }
704
+
705
+ return controller
706
+ }
707
+
708
+ async function resolveAndRenderReload(
709
+ controller: AbortController,
710
+ options?: FrameReloadOptions,
711
+ ): Promise<FrameReloadResult> {
712
+ try {
713
+ let resolution = await init.resolveFrame(frame.src, {
714
+ ...options,
715
+ signal: controller.signal,
716
+ target: frameName,
717
+ })
718
+ if (reloadController !== controller || controller.signal.aborted) {
719
+ return { signal: controller.signal }
720
+ }
721
+ let { content, redirectedTo } = await unwrapFrameResolution(resolution)
722
+ if (reloadController !== controller || controller.signal.aborted) {
723
+ return { signal: controller.signal }
724
+ }
725
+ await render(content, { signal: controller.signal })
726
+ return {
727
+ signal: controller.signal,
728
+ redirectedTo:
729
+ reloadController === controller && !controller.signal.aborted ? redirectedTo : undefined,
730
+ }
731
+ } catch (error) {
732
+ if (reloadController !== controller || controller.signal.aborted) {
733
+ return { signal: controller.signal }
734
+ }
735
+ init.errorTarget.dispatchEvent(createComponentErrorEvent(error))
736
+ throw error
737
+ } finally {
738
+ completeReload(controller)
739
+ }
740
+ }
741
+
742
+ function completeReload(controller: AbortController): void {
743
+ if (reloadController !== controller || reloadKind === undefined) return
744
+ reloadAbortUnsubscribe?.()
745
+ reloadAbortUnsubscribe = undefined
746
+ reloadKind = undefined
747
+ frame.dispatchEvent(new Event('reloadComplete'))
748
+ }
749
+
492
750
  function startInheritedReload(signal?: AbortSignal): void {
493
751
  if (signal?.aborted) return
494
752
  if (!inheritedReloadPending) {
@@ -508,6 +766,14 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
508
766
  }
509
767
  }
510
768
 
769
+ function reuseInheritedReloadStart(): boolean {
770
+ if (!inheritedReloadPending) return false
771
+ inheritedReloadPending = false
772
+ inheritedReloadAbortUnsubscribe?.()
773
+ inheritedReloadAbortUnsubscribe = undefined
774
+ return true
775
+ }
776
+
511
777
  function completeInheritedReload(): void {
512
778
  if (!inheritedReloadPending) return
513
779
  inheritedReloadPending = false
@@ -525,7 +791,7 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
525
791
  if (isFrameStart(node)) {
526
792
  let end = findEndMarker(node, isFrameStart, isFrameEnd)
527
793
  context.frameInstances.get(node)?.startInheritedReload(signal)
528
- i = nodes.indexOf(end)
794
+ i = findMarkerRangeEndIndex(nodes, end, i)
529
795
  continue
530
796
  }
531
797
 
@@ -549,7 +815,7 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
549
815
  signal?: AbortSignal,
550
816
  onResolved?: () => void,
551
817
  ): Promise<void> {
552
- if (signal?.aborted) return
818
+ if (disposed || context.lifecycleSignal.aborted || signal?.aborted) return
553
819
  if (pendingTemplateMarkerId === marker.id) return
554
820
 
555
821
  clearPendingFrameTemplateWatch()
@@ -559,11 +825,11 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
559
825
  if (early) {
560
826
  clearPendingFrameTemplateWatch()
561
827
  await render(early, { initialHydrationTracker, signal, contentStatus: 'resolved' })
562
- if (!signal?.aborted) onResolved?.()
828
+ if (!disposed && !context.lifecycleSignal.aborted && !signal?.aborted) onResolved?.()
563
829
  return
564
830
  }
565
831
 
566
- if (signal?.aborted) {
832
+ if (disposed || context.lifecycleSignal.aborted || signal?.aborted) {
567
833
  clearPendingFrameTemplateWatch()
568
834
  return
569
835
  }
@@ -571,11 +837,11 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
571
837
  let observer = setupTemplateObserver()
572
838
  pendingTemplateObserver = observer
573
839
  let unsubscribe = subscribeFrameTemplate(marker.id, async (fragment) => {
574
- if (signal?.aborted) return
840
+ if (disposed || context.lifecycleSignal.aborted || signal?.aborted) return
575
841
  if (pendingTemplateMarkerId !== marker.id) return
576
842
  clearPendingFrameTemplateWatch()
577
843
  await render(fragment, { signal, contentStatus: 'resolved' })
578
- if (!signal?.aborted) onResolved?.()
844
+ if (!disposed && !context.lifecycleSignal.aborted && !signal?.aborted) onResolved?.()
579
845
  })
580
846
  pendingTemplateUnsubscribe = unsubscribe
581
847
 
@@ -593,7 +859,7 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
593
859
  if (buffered) {
594
860
  clearPendingFrameTemplateWatch()
595
861
  await render(buffered, { initialHydrationTracker, signal, contentStatus: 'resolved' })
596
- if (!signal?.aborted) onResolved?.()
862
+ if (!disposed && !context.lifecycleSignal.aborted && !signal?.aborted) onResolved?.()
597
863
  }
598
864
  }
599
865
  }
@@ -606,13 +872,14 @@ export function createFrameRuntime(init: {
606
872
  pendingClientEntries: PendingClientEntries
607
873
  scheduler: Scheduler
608
874
  styleManager: StyleManager
609
- data: RmxData
610
- moduleCache: Map<string, Function>
611
- moduleLoads: Map<string, Promise<Function | undefined>>
875
+ moduleCache: Map<string, ElementFunction>
876
+ moduleLoads: Map<string, Promise<ElementFunction | undefined>>
612
877
  frameInstances: WeakMap<Comment, Frame>
613
878
  namedFrames: Map<string, FrameHandle>
879
+ reloadForNavigation?: (options?: FrameReloadOptions) => Promise<FrameReloadResult>
614
880
  }): FrameRuntime {
615
881
  return {
882
+ [FRAME_RUNTIME]: true,
616
883
  topFrame: init.topFrame,
617
884
  errorTarget: init.errorTarget,
618
885
  loadModule: init.loadModule,
@@ -620,11 +887,12 @@ export function createFrameRuntime(init: {
620
887
  pendingClientEntries: init.pendingClientEntries,
621
888
  scheduler: init.scheduler,
622
889
  styleManager: init.styleManager,
623
- data: init.data,
624
890
  moduleCache: init.moduleCache,
625
891
  moduleLoads: init.moduleLoads,
626
892
  frameInstances: init.frameInstances,
627
893
  namedFrames: init.namedFrames,
894
+ serverFrameReload: undefined,
895
+ reloadForNavigation: init.reloadForNavigation,
628
896
  }
629
897
  }
630
898
 
@@ -689,6 +957,11 @@ function mergeRmxDataFromFragment(into: RmxData, fragment: DocumentFragment): vo
689
957
  }
690
958
  }
691
959
 
960
+ function clearRmxData(data: RmxData): void {
961
+ delete data.h
962
+ delete data.f
963
+ }
964
+
692
965
  function removeEmptyHeads(fragment: DocumentFragment): void {
693
966
  let heads = Array.from(fragment.querySelectorAll('head'))
694
967
  for (let head of heads) {
@@ -715,7 +988,7 @@ function collectOwnedServerStyleTags(nodes: Node[], styles: HTMLStyleElement[]):
715
988
 
716
989
  if (isFrameStart(node)) {
717
990
  let end = findEndMarker(node, isFrameStart, isFrameEnd)
718
- i = nodes.indexOf(end)
991
+ i = findMarkerRangeEndIndex(nodes, end, i)
719
992
  continue
720
993
  }
721
994
 
@@ -724,7 +997,7 @@ function collectOwnedServerStyleTags(nodes: Node[], styles: HTMLStyleElement[]):
724
997
  continue
725
998
  }
726
999
 
727
- if (node instanceof Element || node instanceof Document || node instanceof DocumentFragment) {
1000
+ if (node.childNodes.length > 0) {
728
1001
  collectOwnedServerStyleTags(Array.from(node.childNodes), styles)
729
1002
  }
730
1003
  }
@@ -763,6 +1036,7 @@ function scheduleHydrationInContainer(
763
1036
  container: FrameContainer,
764
1037
  context: FrameContext,
765
1038
  initialHydrationTracker?: InitialHydrationTracker,
1039
+ signal?: AbortSignal,
766
1040
  ): void {
767
1041
  let hydrationMarkers = findHydrationMarkers(container)
768
1042
  if (hydrationMarkers.length === 0) return
@@ -773,7 +1047,7 @@ function scheduleHydrationInContainer(
773
1047
  for (let marker of hydrationMarkers) {
774
1048
  let entry = hydrationData[marker.id]
775
1049
  if (!entry) continue
776
- scheduleHydrationMarker(marker, entry, context, initialHydrationTracker)
1050
+ scheduleHydrationMarker(marker, entry, context, initialHydrationTracker, signal)
777
1051
  }
778
1052
  }
779
1053
 
@@ -782,49 +1056,74 @@ function scheduleHydrationMarker(
782
1056
  entry: HydrationData,
783
1057
  context: FrameContext,
784
1058
  initialHydrationTracker?: InitialHydrationTracker,
1059
+ signal?: AbortSignal,
785
1060
  ): void {
1061
+ if (signal?.aborted || context.lifecycleSignal.aborted) return
1062
+
786
1063
  let done = initialHydrationTracker?.track()
787
1064
  let key = `${entry.moduleUrl}#${entry.exportName}`
1065
+ let identity: ClientEntryIdentity = {
1066
+ moduleUrl: entry.moduleUrl,
1067
+ exportName: entry.exportName,
1068
+ }
1069
+ let props: Record<string, unknown> | undefined = entry.props
1070
+ let completed = false
1071
+
1072
+ let complete = () => {
1073
+ if (completed) return
1074
+ completed = true
1075
+ props = undefined
1076
+ signal?.removeEventListener('abort', complete)
1077
+ context.lifecycleSignal.removeEventListener('abort', complete)
1078
+ done?.()
1079
+ }
1080
+
1081
+ signal?.addEventListener('abort', complete, { once: true })
1082
+ context.lifecycleSignal.addEventListener('abort', complete, { once: true })
788
1083
 
789
- let hydrateWithComponent = (component: Function) => {
1084
+ let hydrateWithComponent = (component: ElementFunction) => {
1085
+ if (signal?.aborted || context.lifecycleSignal.aborted) return
790
1086
  if (!isHydrationMarkerLive(marker, context)) return
791
- let vElement = createElement(component, entry.props)
1087
+ if (!props) return
1088
+ let vElement = createElement(component, props)
792
1089
  context.pendingClientEntries.set(marker.start, [marker.end, vElement])
793
- hydrateRegion(vElement, marker.start, marker.end, context)
1090
+ hydrateRegion(vElement, marker.start, marker.end, identity, context, signal)
794
1091
  }
795
1092
 
796
1093
  let cached = context.moduleCache.get(key)
797
1094
  if (cached) {
798
1095
  hydrateWithComponent(cached)
799
- done?.()
1096
+ complete()
800
1097
  return
801
1098
  }
802
1099
 
803
- getOrStartModuleLoad(key, entry, marker.id, context)
1100
+ getOrStartModuleLoad(key, identity, marker.id, context)
804
1101
  .then((component) => {
805
1102
  if (component) {
806
1103
  hydrateWithComponent(component)
807
1104
  }
808
1105
  })
809
1106
  .finally(() => {
810
- done?.()
1107
+ complete()
811
1108
  })
812
1109
  }
813
1110
 
814
1111
  function getOrStartModuleLoad(
815
1112
  key: string,
816
- entry: HydrationData,
1113
+ identity: ClientEntryIdentity,
817
1114
  markerId: string,
818
1115
  context: FrameContext,
819
- ): Promise<Function | undefined> {
1116
+ ): Promise<ElementFunction | undefined> {
820
1117
  let inFlight = context.moduleLoads.get(key)
821
1118
  if (inFlight) return inFlight
822
1119
 
823
1120
  let loadPromise = (async () => {
824
1121
  try {
825
- let mod = await context.loadModule(entry.moduleUrl, entry.exportName)
826
- if (typeof mod !== 'function') {
827
- throw new Error(`Export "${entry.exportName}" from "${entry.moduleUrl}" is not a function`)
1122
+ let mod = await context.loadModule(identity.moduleUrl, identity.exportName)
1123
+ if (!isElementFunction(mod)) {
1124
+ throw new Error(
1125
+ `Export "${identity.exportName}" from "${identity.moduleUrl}" is not a function`,
1126
+ )
828
1127
  }
829
1128
  context.moduleCache.set(key, mod)
830
1129
  return mod
@@ -840,9 +1139,14 @@ function getOrStartModuleLoad(
840
1139
  return loadPromise
841
1140
  }
842
1141
 
843
- function createElement(component: Function, props: Record<string, unknown>): RemixElement {
1142
+ function createElement(component: ElementFunction, props: Record<string, unknown>): RemixElement {
844
1143
  let revivedProps = reviveSerializedValue(props)
845
- return jsx(component, revivedProps as any)
1144
+ invariant(isRecord(revivedProps), 'Expected revived component props to be an object')
1145
+ return jsx(component, revivedProps)
1146
+ }
1147
+
1148
+ function isElementFunction(value: unknown): value is ElementFunction {
1149
+ return typeof value === 'function'
846
1150
  }
847
1151
 
848
1152
  function reviveSerializedValue(value: unknown): unknown {
@@ -853,18 +1157,19 @@ function reviveSerializedValue(value: unknown): unknown {
853
1157
  return value.map((item) => reviveSerializedValue(item))
854
1158
  }
855
1159
 
856
- let record = value as Record<string, unknown>
1160
+ if (!isRecord(value)) return value
1161
+ let record = value
857
1162
 
858
1163
  if (record.$rmxFrame === true) {
859
1164
  let props = reviveSerializedObject(record.props)
860
1165
  let key = reviveSerializedValue(record.key)
861
- return jsx(Frame as any, props as any, key as any)
1166
+ return jsx(Frame, props, key)
862
1167
  }
863
1168
 
864
1169
  if (record.$rmx === true && typeof record.type === 'string') {
865
1170
  let props = reviveSerializedObject(record.props)
866
1171
  let key = reviveSerializedValue(record.key)
867
- return jsx(record.type as any, props as any, key as any)
1172
+ return jsx(record.type, props, key)
868
1173
  }
869
1174
 
870
1175
  let revived: Record<string, unknown> = {}
@@ -878,22 +1183,49 @@ function reviveSerializedObject(value: unknown): Record<string, unknown> {
878
1183
  if (!value || typeof value !== 'object' || Array.isArray(value)) return {}
879
1184
  let revived = reviveSerializedValue(value)
880
1185
  if (!revived || typeof revived !== 'object' || Array.isArray(revived)) return {}
881
- return revived as Record<string, unknown>
1186
+ return isRecord(revived) ? revived : {}
1187
+ }
1188
+
1189
+ function isRecord(value: unknown): value is Record<string, unknown> {
1190
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
882
1191
  }
883
1192
 
884
1193
  function hydrateRegion(
885
1194
  vElement: RemixElement,
886
1195
  start: Comment,
887
1196
  end: Comment,
1197
+ identity: ClientEntryIdentity,
888
1198
  context: FrameContext,
1199
+ signal?: AbortSignal,
889
1200
  ): void {
1201
+ if (signal?.aborted) return
1202
+
890
1203
  context.pendingClientEntries.delete(start)
891
1204
 
892
1205
  // The same marker can be discovered by overlapping hydration passes
893
1206
  // (for example, document root + nested frame root). Reuse the existing
894
1207
  // virtual root instead of redefining the marker property.
895
- if (isHydratedVirtualRootMarker(start)) {
896
- start.$rmx.render(vElement)
1208
+ let owner = getClientEntryBoundaryOwner(start)
1209
+ if (owner) {
1210
+ if (!signal) {
1211
+ owner.root.render(vElement)
1212
+ return
1213
+ }
1214
+
1215
+ let frameRuntime = context.frame.$runtime
1216
+ invariant(
1217
+ isFrameRuntime(frameRuntime),
1218
+ 'Expected frame runtime while rendering a preserved client entry',
1219
+ )
1220
+
1221
+ let previousServerFrameReload = frameRuntime.serverFrameReload
1222
+
1223
+ frameRuntime.serverFrameReload = { signal }
1224
+ try {
1225
+ owner.root.render(vElement)
1226
+ } finally {
1227
+ frameRuntime.serverFrameReload = previousServerFrameReload
1228
+ }
897
1229
  return
898
1230
  }
899
1231
 
@@ -907,7 +1239,7 @@ function hydrateRegion(
907
1239
  context.errorTarget.dispatchEvent(createComponentErrorEvent(getComponentError(event)))
908
1240
  })
909
1241
 
910
- Object.defineProperty(start, '$rmx', { value: root, enumerable: false })
1242
+ setClientEntryBoundaryOwner(start, identity, root)
911
1243
  root.render(vElement)
912
1244
  }
913
1245
 
@@ -948,6 +1280,7 @@ async function createSubFrames(
948
1280
  resolveFrame: context.resolveFrame,
949
1281
  pendingClientEntries: context.pendingClientEntries,
950
1282
  scheduler: context.scheduler,
1283
+ styleManager: context.styleManager,
951
1284
  data: context.data,
952
1285
  moduleCache: context.moduleCache,
953
1286
  moduleLoads: context.moduleLoads,
@@ -958,7 +1291,7 @@ async function createSubFrames(
958
1291
  }
959
1292
  }
960
1293
 
961
- i = nodes.indexOf(end)
1294
+ i = findMarkerRangeEndIndex(nodes, end, i)
962
1295
  continue
963
1296
  }
964
1297
 
@@ -996,10 +1329,9 @@ function removeVirtualRoots(nodes: Node[]): void {
996
1329
  for (let i = 0; i < nodes.length; i++) {
997
1330
  let node = nodes[i]
998
1331
 
999
- if (isHydratedVirtualRootMarker(node)) {
1000
- node.$rmx.dispose()
1332
+ if (isCommentNode(node) && isHydrationStart(node) && disposeClientEntryBoundary(node)) {
1001
1333
  let end = findEndMarker(node, isHydrationStart, isHydrationEnd)
1002
- i = nodes.indexOf(end)
1334
+ i = findMarkerRangeEndIndex(nodes, end, i)
1003
1335
  continue
1004
1336
  }
1005
1337
 
@@ -1020,7 +1352,7 @@ function disposeSubFrames(nodes: Node[], context: FrameContext): void {
1020
1352
  subFrame.dispose()
1021
1353
  context.frameInstances.delete(node)
1022
1354
  }
1023
- i = nodes.indexOf(end)
1355
+ i = findMarkerRangeEndIndex(nodes, end, i)
1024
1356
  continue
1025
1357
  }
1026
1358
 
@@ -1087,7 +1419,9 @@ export function publishFrameTemplate(id: string, fragment: DocumentFragment): vo
1087
1419
  }
1088
1420
 
1089
1421
  for (let listener of listeners) {
1090
- listener(fragment.cloneNode(true) as DocumentFragment)
1422
+ let clone = fragment.cloneNode(true)
1423
+ invariant(isDocumentFragmentNode(clone), 'Expected cloned frame template fragment')
1424
+ listener(clone)
1091
1425
  }
1092
1426
  }
1093
1427
 
@@ -1186,12 +1520,22 @@ async function renderFrameStream(
1186
1520
  stream: ReadableStream<Uint8Array>,
1187
1521
  doc: Document,
1188
1522
  applyHtml: (html: string, flushKind: FlushKind) => Promise<void>,
1523
+ signal?: AbortSignal,
1189
1524
  ): Promise<void> {
1190
1525
  let reader = stream.getReader()
1191
1526
  let decoder = new TextDecoder()
1192
1527
  let buffer = ''
1193
1528
  let html = ''
1194
1529
  let appliedOnce = false
1530
+ let abort = () => {
1531
+ void reader.cancel().catch(() => {})
1532
+ }
1533
+ if (signal?.aborted) {
1534
+ await reader.cancel()
1535
+ reader.releaseLock()
1536
+ return
1537
+ }
1538
+ signal?.addEventListener('abort', abort, { once: true })
1195
1539
 
1196
1540
  try {
1197
1541
  while (true) {
@@ -1230,6 +1574,7 @@ async function renderFrameStream(
1230
1574
  await applyHtml('', 'fragment')
1231
1575
  }
1232
1576
  } finally {
1577
+ signal?.removeEventListener('abort', abort)
1233
1578
  reader.releaseLock()
1234
1579
  }
1235
1580
  }
@@ -1312,7 +1657,7 @@ function createFragmentFromString(doc: Document, content: string): DocumentFragm
1312
1657
  function isRemixNodeFrameContent(content: InternalFrameContent): content is RemixNode {
1313
1658
  return !(
1314
1659
  content instanceof ReadableStream ||
1315
- content instanceof DocumentFragment ||
1660
+ isDocumentFragmentNode(content) ||
1316
1661
  typeof content === 'string'
1317
1662
  )
1318
1663
  }
@@ -1350,11 +1695,11 @@ function walkCommentsInNodes(nodes: Node[], cb: (comment: Comment) => void): voi
1350
1695
  // are discovered and hydrated by the nested frame instance only.
1351
1696
  if (isFrameStart(node)) {
1352
1697
  let end = findEndMarker(node, isFrameStart, isFrameEnd)
1353
- i = nodes.indexOf(end)
1698
+ i = findMarkerRangeEndIndex(nodes, end, i)
1354
1699
  continue
1355
1700
  }
1356
1701
 
1357
- if (node.nodeType === Node.COMMENT_NODE) cb(node as Comment)
1702
+ if (isCommentNode(node)) cb(node)
1358
1703
  if (node.childNodes && node.childNodes.length > 0) {
1359
1704
  walkCommentsInNodes(Array.from(node.childNodes), cb)
1360
1705
  }
@@ -1369,12 +1714,8 @@ function isHydrationEnd(node: Comment): boolean {
1369
1714
  return node.data.trim() === '/rmx:h'
1370
1715
  }
1371
1716
 
1372
- function isHydratedVirtualRootMarker(node: Node): node is VirtualRootMarker {
1373
- return node instanceof Comment && '$rmx' in node
1374
- }
1375
-
1376
1717
  function isFrameStart(node: Node): node is Comment {
1377
- return node instanceof Comment && node.data.trim().startsWith('rmx:f:')
1718
+ return isCommentNode(node) && node.data.trim().startsWith('rmx:f:')
1378
1719
  }
1379
1720
 
1380
1721
  function isFrameEnd(node: Comment): boolean {
@@ -1387,6 +1728,11 @@ function getFrameId(start: Comment): string {
1387
1728
  return trimmed.slice('rmx:f:'.length)
1388
1729
  }
1389
1730
 
1731
+ function findMarkerRangeEndIndex(nodes: Node[], end: Comment, startIndex: number): number {
1732
+ // The snapshot may not contain an end marker moved by a DOM update.
1733
+ return Math.max(startIndex, nodes.indexOf(end))
1734
+ }
1735
+
1390
1736
  function findEndMarker(
1391
1737
  start: Comment,
1392
1738
  isStart: (node: Comment) => boolean,
@@ -1396,8 +1742,8 @@ function findEndMarker(
1396
1742
  let depth = 1
1397
1743
 
1398
1744
  while (node) {
1399
- if (node.nodeType === Node.COMMENT_NODE) {
1400
- let comment = node as Comment
1745
+ if (isCommentNode(node)) {
1746
+ let comment = node
1401
1747
  if (isStart(comment)) depth++
1402
1748
  else if (isEnd(comment)) {
1403
1749
  depth--
@@ -1409,3 +1755,15 @@ function findEndMarker(
1409
1755
 
1410
1756
  throw new Error('End marker not found')
1411
1757
  }
1758
+
1759
+ function isCommentNode(node: Node | null | undefined): node is Comment {
1760
+ return node?.nodeType === Node.COMMENT_NODE
1761
+ }
1762
+
1763
+ function isDocumentFragmentNode(value: unknown): value is DocumentFragment {
1764
+ return (
1765
+ typeof value === 'object' &&
1766
+ value !== null &&
1767
+ Reflect.get(value, 'nodeType') === Node.DOCUMENT_FRAGMENT_NODE
1768
+ )
1769
+ }