@remix-run/ui 0.1.2 → 0.3.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 (318) hide show
  1. package/README.md +9 -7
  2. package/dist/animation/demos/animation.demo.d.ts +5 -0
  3. package/dist/animation/demos/animation.demo.js +92 -0
  4. package/dist/animation/demos/animation.demo.js.map +1 -0
  5. package/dist/animation/demos/aspect-ratio.d.ts +2 -0
  6. package/dist/animation/demos/aspect-ratio.js +69 -0
  7. package/dist/animation/demos/aspect-ratio.js.map +1 -0
  8. package/dist/animation/demos/bouncy-switch.d.ts +2 -0
  9. package/dist/animation/demos/bouncy-switch.js +36 -0
  10. package/dist/animation/demos/bouncy-switch.js.map +1 -0
  11. package/dist/animation/demos/color-interpolation.d.ts +1 -0
  12. package/dist/animation/demos/color-interpolation.js +53 -0
  13. package/dist/animation/demos/color-interpolation.js.map +1 -0
  14. package/dist/animation/demos/cube.d.ts +2 -0
  15. package/dist/animation/demos/cube.js +86 -0
  16. package/dist/animation/demos/cube.js.map +1 -0
  17. package/dist/animation/demos/default-animate.d.ts +2 -0
  18. package/dist/animation/demos/default-animate.js +89 -0
  19. package/dist/animation/demos/default-animate.js.map +1 -0
  20. package/dist/animation/demos/drag-release.d.ts +24 -0
  21. package/dist/animation/demos/drag-release.js +96 -0
  22. package/dist/animation/demos/drag-release.js.map +1 -0
  23. package/dist/animation/demos/enter.d.ts +1 -0
  24. package/dist/animation/demos/enter.js +19 -0
  25. package/dist/animation/demos/enter.js.map +1 -0
  26. package/dist/animation/demos/exit.d.ts +2 -0
  27. package/dist/animation/demos/exit.js +58 -0
  28. package/dist/animation/demos/exit.js.map +1 -0
  29. package/dist/animation/demos/flip-toggle.d.ts +2 -0
  30. package/dist/animation/demos/flip-toggle.js +37 -0
  31. package/dist/animation/demos/flip-toggle.js.map +1 -0
  32. package/dist/animation/demos/hold-to-confirm.d.ts +13 -0
  33. package/dist/animation/demos/hold-to-confirm.js +211 -0
  34. package/dist/animation/demos/hold-to-confirm.js.map +1 -0
  35. package/dist/animation/demos/html-content.d.ts +2 -0
  36. package/dist/animation/demos/html-content.js +30 -0
  37. package/dist/animation/demos/html-content.js.map +1 -0
  38. package/dist/animation/demos/interruptible-keyframes.d.ts +1 -0
  39. package/dist/animation/demos/interruptible-keyframes.js +48 -0
  40. package/dist/animation/demos/interruptible-keyframes.js.map +1 -0
  41. package/dist/animation/demos/keyframes.d.ts +1 -0
  42. package/dist/animation/demos/keyframes.js +24 -0
  43. package/dist/animation/demos/keyframes.js.map +1 -0
  44. package/dist/animation/demos/material-ripple.d.ts +2 -0
  45. package/dist/animation/demos/material-ripple.js +127 -0
  46. package/dist/animation/demos/material-ripple.js.map +1 -0
  47. package/dist/animation/demos/mixin-presence-list.d.ts +2 -0
  48. package/dist/animation/demos/mixin-presence-list.js +88 -0
  49. package/dist/animation/demos/mixin-presence-list.js.map +1 -0
  50. package/dist/animation/demos/mixin-reclaim.d.ts +2 -0
  51. package/dist/animation/demos/mixin-reclaim.js +90 -0
  52. package/dist/animation/demos/mixin-reclaim.js.map +1 -0
  53. package/dist/animation/demos/multi-state-badge.d.ts +2 -0
  54. package/dist/animation/demos/multi-state-badge.js +231 -0
  55. package/dist/animation/demos/multi-state-badge.js.map +1 -0
  56. package/dist/animation/demos/press.d.ts +2 -0
  57. package/dist/animation/demos/press.js +63 -0
  58. package/dist/animation/demos/press.js.map +1 -0
  59. package/dist/animation/demos/reordering.d.ts +2 -0
  60. package/dist/animation/demos/reordering.js +50 -0
  61. package/dist/animation/demos/reordering.js.map +1 -0
  62. package/dist/animation/demos/rolling-square.d.ts +2 -0
  63. package/dist/animation/demos/rolling-square.js +42 -0
  64. package/dist/animation/demos/rolling-square.js.map +1 -0
  65. package/dist/animation/demos/rotate.d.ts +1 -0
  66. package/dist/animation/demos/rotate.js +18 -0
  67. package/dist/animation/demos/rotate.js.map +1 -0
  68. package/dist/animation/demos/shared-layout.d.ts +2 -0
  69. package/dist/animation/demos/shared-layout.js +114 -0
  70. package/dist/animation/demos/shared-layout.js.map +1 -0
  71. package/dist/animation/demos/spring.demo.d.ts +6 -0
  72. package/dist/animation/demos/spring.demo.js +304 -0
  73. package/dist/animation/demos/spring.demo.js.map +1 -0
  74. package/dist/animation/demos/transition-options.d.ts +1 -0
  75. package/dist/animation/demos/transition-options.js +21 -0
  76. package/dist/animation/demos/transition-options.js.map +1 -0
  77. package/dist/animation/spring.d.ts +24 -31
  78. package/dist/animation/spring.js +31 -8
  79. package/dist/animation/spring.js.map +1 -1
  80. package/dist/components/accordion/accordion.d.ts +6 -6
  81. package/dist/components/accordion/accordion.js.map +1 -1
  82. package/dist/components/accordion/demos/card.demo.d.ts +1 -0
  83. package/dist/components/accordion/demos/card.demo.js +1 -0
  84. package/dist/components/accordion/demos/card.demo.js.map +1 -1
  85. package/dist/components/accordion/demos/multiple.demo.d.ts +1 -0
  86. package/dist/components/accordion/demos/multiple.demo.js +1 -0
  87. package/dist/components/accordion/demos/multiple.demo.js.map +1 -1
  88. package/dist/components/accordion/demos/overview.demo.d.ts +1 -0
  89. package/dist/components/accordion/demos/overview.demo.js +1 -0
  90. package/dist/components/accordion/demos/overview.demo.js.map +1 -1
  91. package/dist/components/anchor/anchor.d.ts +8 -1
  92. package/dist/components/anchor/anchor.js +30 -11
  93. package/dist/components/anchor/anchor.js.map +1 -1
  94. package/dist/components/breadcrumbs/{demos/basic.demo.d.ts → basic.demo.d.ts} +1 -0
  95. package/dist/components/breadcrumbs/{demos/basic.demo.js → basic.demo.js} +1 -0
  96. package/dist/components/breadcrumbs/basic.demo.js.map +1 -0
  97. package/dist/components/breadcrumbs/breadcrumbs.d.ts +1 -1
  98. package/dist/components/breadcrumbs/{demos/separator.demo.d.ts → separator.demo.d.ts} +1 -0
  99. package/dist/components/breadcrumbs/{demos/separator.demo.js → separator.demo.js} +1 -0
  100. package/dist/components/breadcrumbs/separator.demo.js.map +1 -0
  101. package/dist/components/button/button.d.ts +4 -4
  102. package/dist/components/button/button.js +3 -1
  103. package/dist/components/button/button.js.map +1 -1
  104. package/dist/components/button/demos/aliases.demo.d.ts +1 -0
  105. package/dist/components/button/demos/aliases.demo.js +1 -0
  106. package/dist/components/button/demos/aliases.demo.js.map +1 -1
  107. package/dist/components/button/demos/basic.demo.d.ts +1 -0
  108. package/dist/components/button/demos/basic.demo.js +1 -0
  109. package/dist/components/button/demos/basic.demo.js.map +1 -1
  110. package/dist/components/button/demos/states.demo.d.ts +1 -0
  111. package/dist/components/button/demos/states.demo.js +1 -0
  112. package/dist/components/button/demos/states.demo.js.map +1 -1
  113. package/dist/components/combobox/combobox.d.ts +10 -10
  114. package/dist/components/combobox/combobox.js.map +1 -1
  115. package/dist/components/combobox/{demos/overview.demo.d.ts → overview.demo.d.ts} +1 -0
  116. package/dist/components/combobox/{demos/overview.demo.js → overview.demo.js} +1 -0
  117. package/dist/components/combobox/overview.demo.js.map +1 -0
  118. package/dist/components/listbox/listbox.d.ts +3 -3
  119. package/dist/components/listbox/listbox.js.map +1 -1
  120. package/dist/components/listbox/{demos/overview.demo.d.ts → overview.demo.d.ts} +1 -0
  121. package/dist/components/listbox/{demos/overview.demo.js → overview.demo.js} +1 -0
  122. package/dist/components/listbox/overview.demo.js.map +1 -0
  123. package/dist/components/menu/demos/bubbling.demo.d.ts +1 -0
  124. package/dist/components/menu/demos/bubbling.demo.js +1 -0
  125. package/dist/components/menu/demos/bubbling.demo.js.map +1 -1
  126. package/dist/components/menu/demos/context-trigger.demo.d.ts +7 -0
  127. package/dist/components/menu/demos/context-trigger.demo.js +87 -0
  128. package/dist/components/menu/demos/context-trigger.demo.js.map +1 -0
  129. package/dist/components/menu/demos/overview.demo.d.ts +1 -0
  130. package/dist/components/menu/demos/overview.demo.js +1 -0
  131. package/dist/components/menu/demos/overview.demo.js.map +1 -1
  132. package/dist/components/menu/menu.d.ts +15 -12
  133. package/dist/components/menu/menu.js +55 -0
  134. package/dist/components/menu/menu.js.map +1 -1
  135. package/dist/components/popover/{demos/overview.demo.d.ts → overview.demo.d.ts} +1 -0
  136. package/dist/components/popover/{demos/overview.demo.js → overview.demo.js} +1 -0
  137. package/dist/components/popover/overview.demo.js.map +1 -0
  138. package/dist/components/popover/popover.d.ts +7 -7
  139. package/dist/components/popover/popover.js +11 -4
  140. package/dist/components/popover/popover.js.map +1 -1
  141. package/dist/components/select/{demos/deconstructed.demo.d.ts → deconstructed.demo.d.ts} +1 -0
  142. package/dist/components/select/{demos/deconstructed.demo.js → deconstructed.demo.js} +1 -0
  143. package/dist/components/select/deconstructed.demo.js.map +1 -0
  144. package/dist/components/select/{demos/overview.demo.d.ts → overview.demo.d.ts} +1 -0
  145. package/dist/components/select/{demos/overview.demo.js → overview.demo.js} +1 -0
  146. package/dist/components/select/overview.demo.js.map +1 -0
  147. package/dist/components/select/select.d.ts +10 -10
  148. package/dist/components/select/select.js +1 -1
  149. package/dist/components/select/select.js.map +1 -1
  150. package/dist/components/tabs/tabs.d.ts +3 -3
  151. package/dist/index.d.ts +1 -1
  152. package/dist/index.js.map +1 -1
  153. package/dist/interactions/keydown/keydown.d.ts +1 -1
  154. package/dist/interactions/outside-click/outside-click-mixin.d.ts +2 -1
  155. package/dist/interactions/typeahead/typeahead-mixin.d.ts +3 -1
  156. package/dist/runtime/client-entries.d.ts +1 -2
  157. package/dist/runtime/client-entries.js.map +1 -1
  158. package/dist/runtime/component.d.ts +20 -17
  159. package/dist/runtime/component.js +15 -6
  160. package/dist/runtime/component.js.map +1 -1
  161. package/dist/runtime/core/attributes.d.ts +3 -1
  162. package/dist/runtime/core/attributes.js +19 -1
  163. package/dist/runtime/core/attributes.js.map +1 -1
  164. package/dist/runtime/core/props.js +6 -5
  165. package/dist/runtime/core/props.js.map +1 -1
  166. package/dist/runtime/demos/basic.demo.d.ts +6 -0
  167. package/dist/runtime/demos/basic.demo.js +16 -0
  168. package/dist/runtime/demos/basic.demo.js.map +1 -0
  169. package/dist/runtime/demos/controlled-uncontrolled-values.demo.d.ts +6 -0
  170. package/dist/runtime/demos/controlled-uncontrolled-values.demo.js +77 -0
  171. package/dist/runtime/demos/controlled-uncontrolled-values.demo.js.map +1 -0
  172. package/dist/runtime/demos/keyed-list.demo.d.ts +6 -0
  173. package/dist/runtime/demos/keyed-list.demo.js +62 -0
  174. package/dist/runtime/demos/keyed-list.demo.js.map +1 -0
  175. package/dist/runtime/demos/readme.demo.d.ts +5 -0
  176. package/dist/runtime/demos/readme.demo.js +396 -0
  177. package/dist/runtime/demos/readme.demo.js.map +1 -0
  178. package/dist/runtime/diff-dom.js +194 -21
  179. package/dist/runtime/diff-dom.js.map +1 -1
  180. package/dist/runtime/dom.d.ts +16 -9
  181. package/dist/runtime/frame.d.ts +7 -0
  182. package/dist/runtime/frame.js +176 -41
  183. package/dist/runtime/frame.js.map +1 -1
  184. package/dist/runtime/jsx.d.ts +3 -4
  185. package/dist/runtime/jsx.js.map +1 -1
  186. package/dist/runtime/mixins/link-mixin.d.ts +5 -1
  187. package/dist/runtime/mixins/link-mixin.js.map +1 -1
  188. package/dist/runtime/mixins/mixin.d.ts +2 -1
  189. package/dist/runtime/mixins/mixin.js.map +1 -1
  190. package/dist/runtime/mixins/ref-mixin.d.ts +2 -1
  191. package/dist/runtime/mixins/ref-mixin.js.map +1 -1
  192. package/dist/runtime/reconcile.d.ts +1 -1
  193. package/dist/runtime/reconcile.js +19 -5
  194. package/dist/runtime/reconcile.js.map +1 -1
  195. package/dist/runtime/scheduler.d.ts +10 -10
  196. package/dist/runtime/scheduler.js +1 -1
  197. package/dist/runtime/scheduler.js.map +1 -1
  198. package/dist/runtime/svg-attributes.js +1 -0
  199. package/dist/runtime/svg-attributes.js.map +1 -1
  200. package/dist/runtime/to-vnode.js +2 -2
  201. package/dist/runtime/to-vnode.js.map +1 -1
  202. package/dist/runtime/vnode.d.ts +6 -1
  203. package/dist/runtime/vnode.js +4 -0
  204. package/dist/runtime/vnode.js.map +1 -1
  205. package/dist/server/stream.d.ts +2 -0
  206. package/dist/server/stream.js +82 -11
  207. package/dist/server/stream.js.map +1 -1
  208. package/dist/style/css-mixin.d.ts +2 -1
  209. package/dist/style/css-mixin.js.map +1 -1
  210. package/dist/style/index.d.ts +1 -1
  211. package/dist/style/style.js +27 -10
  212. package/dist/style/style.js.map +1 -1
  213. package/dist/style/stylesheet.d.ts +13 -13
  214. package/dist/style/stylesheet.js.map +1 -1
  215. package/dist/theme/contract.d.ts +24 -222
  216. package/dist/theme/contract.js +2 -2
  217. package/dist/theme/contract.js.map +1 -1
  218. package/dist/theme/glyph-contract.d.ts +1 -3
  219. package/dist/theme/glyph-contract.js.map +1 -1
  220. package/dist/theme/presets/rmx-01/index.d.ts +4 -2
  221. package/dist/theme/presets/rmx-01/index.js.map +1 -1
  222. package/dist/utils/scroll-lock.d.ts +3 -3
  223. package/dist/utils/scroll-lock.js.map +1 -1
  224. package/package.json +4 -4
  225. package/src/animation/README.md +7 -5
  226. package/src/animation/demos/animation.demo.tsx +194 -0
  227. package/src/animation/demos/aspect-ratio.tsx +120 -0
  228. package/src/animation/demos/bouncy-switch.tsx +47 -0
  229. package/src/animation/demos/color-interpolation.tsx +73 -0
  230. package/src/animation/demos/cube.tsx +116 -0
  231. package/src/animation/demos/default-animate.tsx +129 -0
  232. package/src/animation/demos/drag-release.ts +118 -0
  233. package/src/animation/demos/enter.tsx +22 -0
  234. package/src/animation/demos/exit.tsx +79 -0
  235. package/src/animation/demos/flip-toggle.tsx +46 -0
  236. package/src/animation/demos/hold-to-confirm.tsx +317 -0
  237. package/src/animation/demos/html-content.tsx +41 -0
  238. package/src/animation/demos/interruptible-keyframes.tsx +62 -0
  239. package/src/animation/demos/keyframes.tsx +29 -0
  240. package/src/animation/demos/material-ripple.tsx +158 -0
  241. package/src/animation/demos/mixin-presence-list.tsx +121 -0
  242. package/src/animation/demos/mixin-reclaim.tsx +125 -0
  243. package/src/animation/demos/multi-state-badge.tsx +426 -0
  244. package/src/animation/demos/press.tsx +67 -0
  245. package/src/animation/demos/reordering.tsx +65 -0
  246. package/src/animation/demos/rolling-square.tsx +57 -0
  247. package/src/animation/demos/rotate.tsx +21 -0
  248. package/src/animation/demos/shared-layout.tsx +211 -0
  249. package/src/animation/demos/spring.demo.tsx +385 -0
  250. package/src/animation/demos/transition-options.tsx +24 -0
  251. package/src/animation/spring.ts +43 -9
  252. package/src/components/accordion/accordion.tsx +7 -5
  253. package/src/components/accordion/demos/card.demo.tsx +1 -0
  254. package/src/components/accordion/demos/multiple.demo.tsx +1 -0
  255. package/src/components/accordion/demos/overview.demo.tsx +1 -0
  256. package/src/components/anchor/README.md +11 -3
  257. package/src/components/anchor/anchor.ts +54 -13
  258. package/src/components/breadcrumbs/{demos/basic.demo.tsx → basic.demo.tsx} +1 -0
  259. package/src/components/breadcrumbs/breadcrumbs.tsx +1 -1
  260. package/src/components/breadcrumbs/{demos/separator.demo.tsx → separator.demo.tsx} +1 -0
  261. package/src/components/button/button.tsx +30 -18
  262. package/src/components/button/demos/aliases.demo.tsx +1 -0
  263. package/src/components/button/demos/basic.demo.tsx +1 -0
  264. package/src/components/button/demos/states.demo.tsx +1 -0
  265. package/src/components/combobox/README.md +1 -1
  266. package/src/components/combobox/combobox.tsx +30 -12
  267. package/src/components/combobox/{demos/overview.demo.tsx → overview.demo.tsx} +1 -0
  268. package/src/components/glyph/glyph.tsx +2 -2
  269. package/src/components/listbox/listbox.ts +88 -83
  270. package/src/components/listbox/{demos/overview.demo.tsx → overview.demo.tsx} +1 -0
  271. package/src/components/menu/README.md +26 -2
  272. package/src/components/menu/demos/bubbling.demo.tsx +1 -0
  273. package/src/components/menu/demos/context-trigger.demo.tsx +133 -0
  274. package/src/components/menu/demos/overview.demo.tsx +1 -0
  275. package/src/components/menu/menu.tsx +100 -16
  276. package/src/components/popover/{demos/overview.demo.tsx → overview.demo.tsx} +1 -0
  277. package/src/components/popover/popover.ts +101 -81
  278. package/src/components/select/{demos/deconstructed.demo.tsx → deconstructed.demo.tsx} +1 -0
  279. package/src/components/select/{demos/overview.demo.tsx → overview.demo.tsx} +1 -0
  280. package/src/components/select/select.tsx +27 -10
  281. package/src/index.ts +1 -0
  282. package/src/runtime/client-entries.ts +1 -5
  283. package/src/runtime/component.ts +37 -15
  284. package/src/runtime/core/attributes.ts +21 -0
  285. package/src/runtime/core/props.ts +6 -4
  286. package/src/runtime/demos/basic.demo.tsx +21 -0
  287. package/src/runtime/demos/controlled-uncontrolled-values.demo.tsx +177 -0
  288. package/src/runtime/demos/keyed-list.demo.tsx +94 -0
  289. package/src/runtime/demos/readme.demo.tsx +702 -0
  290. package/src/runtime/diff-dom.ts +270 -20
  291. package/src/runtime/dom.ts +16 -9
  292. package/src/runtime/frame.ts +218 -41
  293. package/src/runtime/jsx.ts +7 -11
  294. package/src/runtime/mixins/link-mixin.ts +88 -86
  295. package/src/runtime/mixins/mixin.ts +9 -1
  296. package/src/runtime/mixins/ref-mixin.ts +4 -5
  297. package/src/runtime/reconcile.ts +23 -14
  298. package/src/runtime/scheduler.ts +20 -20
  299. package/src/runtime/svg-attributes.ts +1 -0
  300. package/src/runtime/to-vnode.ts +2 -2
  301. package/src/runtime/vnode.ts +10 -0
  302. package/src/server/README.md +4 -2
  303. package/src/server/stream.ts +84 -10
  304. package/src/style/css-mixin.ts +6 -1
  305. package/src/style/index.ts +1 -2
  306. package/src/style/style.ts +29 -10
  307. package/src/style/stylesheet.ts +14 -2
  308. package/src/theme/contract.ts +37 -5
  309. package/src/theme/glyph-contract.ts +3 -1
  310. package/src/theme/presets/rmx-01/index.ts +4 -2
  311. package/src/utils/scroll-lock.ts +7 -3
  312. package/dist/components/breadcrumbs/demos/basic.demo.js.map +0 -1
  313. package/dist/components/breadcrumbs/demos/separator.demo.js.map +0 -1
  314. package/dist/components/combobox/demos/overview.demo.js.map +0 -1
  315. package/dist/components/listbox/demos/overview.demo.js.map +0 -1
  316. package/dist/components/popover/demos/overview.demo.js.map +0 -1
  317. package/dist/components/select/demos/deconstructed.demo.js.map +0 -1
  318. package/dist/components/select/demos/overview.demo.js.map +0 -1
@@ -7,6 +7,13 @@ type HydratedVirtualRootStartMarker = Comment & {
7
7
  }
8
8
  }
9
9
 
10
+ type CommentMarkerRangeReplacement = {
11
+ currentStart: Comment
12
+ nextStart: Comment
13
+ currentEndIndex: number
14
+ nextEndIndex: number
15
+ }
16
+
10
17
  export function diffNodes(curr: Node[], next: Node[], context: FrameContext) {
11
18
  let parent = curr[0]?.parentNode ?? context.regionParent ?? null
12
19
  invariant(parent, 'Parent node not found')
@@ -17,10 +24,11 @@ export function diffNodes(curr: Node[], next: Node[], context: FrameContext) {
17
24
  context.regionTailRef ??
18
25
  (curr.length > 0 ? (curr[curr.length - 1].nextSibling as ChildNode | null) : null)
19
26
 
20
- let max = Math.max(curr.length, next.length)
21
- for (let i = 0; i < max; i++) {
22
- let c = curr[i]
23
- let n = next[i]
27
+ let currentIndex = 0
28
+ let nextIndex = 0
29
+ while (currentIndex < curr.length || nextIndex < next.length) {
30
+ let c = curr[currentIndex]
31
+ let n = next[nextIndex]
24
32
 
25
33
  if (!c && n) {
26
34
  if (regionTailRef) {
@@ -28,10 +36,28 @@ export function diffNodes(curr: Node[], next: Node[], context: FrameContext) {
28
36
  } else {
29
37
  parent.appendChild(n)
30
38
  }
39
+ nextIndex++
31
40
  } else if (c && !n) {
32
41
  removeNode(c, parent, context)
42
+ currentIndex++
33
43
  } else if (c && n) {
34
- // Skip hydrated client-entry boundary ranges; hydration pass re-renders
44
+ let replacement = getCommentMarkerRangeReplacement(
45
+ c,
46
+ n,
47
+ curr,
48
+ next,
49
+ currentIndex,
50
+ nextIndex,
51
+ context,
52
+ )
53
+ if (replacement) {
54
+ replaceCommentMarkerRange(replacement, parent, context)
55
+ currentIndex = replacement.currentEndIndex + 1
56
+ nextIndex = replacement.nextEndIndex + 1
57
+ continue
58
+ }
59
+
60
+ // Skip hydrated client-entry marker ranges; hydration pass re-renders
35
61
  // roots with new props from incoming payload
36
62
  if (isVirtualRootStartMarker(c) && isVirtualRootStartMarker(n)) {
37
63
  let currentEnd = findHydrationEndMarker(c)
@@ -41,14 +67,25 @@ export function diffNodes(curr: Node[], next: Node[], context: FrameContext) {
41
67
 
42
68
  let currentEndIndex = curr.indexOf(currentEnd)
43
69
  let nextEndIndex = next.indexOf(nextEnd)
44
- i = Math.max(currentEndIndex, nextEndIndex)
70
+ currentIndex = currentEndIndex + 1
71
+ nextIndex = nextEndIndex + 1
45
72
  continue
46
73
  }
47
74
 
48
75
  let cursor = diffNode(c, n, context)
49
76
  if (cursor) {
50
- i = next.indexOf(cursor)
77
+ let nextEndIndex = next.indexOf(cursor)
78
+ let currentEndIndex =
79
+ isFrameStartMarker(c) && isFrameEndMarker(cursor)
80
+ ? findFrameEndIndex(curr, currentIndex)
81
+ : currentIndex
82
+ currentIndex = currentEndIndex + 1
83
+ nextIndex = nextEndIndex + 1
84
+ continue
51
85
  }
86
+
87
+ currentIndex++
88
+ nextIndex++
52
89
  }
53
90
  }
54
91
  }
@@ -61,7 +98,7 @@ function diffNode(current: Node, next: Node, context: FrameContext): ChildNode |
61
98
  return
62
99
  }
63
100
 
64
- // Hydration boundary -> Hydration boundary
101
+ // Hydration marker range -> Hydration marker range
65
102
  if (isVirtualRootStartMarker(current) && isVirtualRootStartMarker(next)) {
66
103
  let nextData = next.data
67
104
  if (current.data !== nextData) {
@@ -77,8 +114,41 @@ function diffNode(current: Node, next: Node, context: FrameContext): ChildNode |
77
114
  if (isCommentNode(current) && isCommentNode(next)) {
78
115
  let newData = next.data
79
116
  if (current.data !== newData) {
80
- if (isFrameStartMarker(current)) disposeFrameStartMarker(current, context)
81
- current.data = newData
117
+ let updated = false
118
+ if (isFrameStartMarker(current)) {
119
+ if (shouldPreserveFrameStartMarker(current, next, context)) {
120
+ current.data = newData
121
+ updated = true
122
+
123
+ let frame = context.frameInstances.get(current)
124
+ let nextMarkerData = getFrameMarkerData(next, context)
125
+ if (frame && nextMarkerData) {
126
+ if (nextMarkerData.status === 'resolved') {
127
+ let nextEnd = findFrameEndMarker(next)
128
+ let nextContent = collectFrameContentFragment(current.ownerDocument, next, nextEnd)
129
+ void frame.renderMarkerContent(
130
+ { ...nextMarkerData, id: getFrameId(next) },
131
+ nextContent,
132
+ {
133
+ signal: context.signal,
134
+ },
135
+ )
136
+ return nextEnd
137
+ }
138
+
139
+ if (frame.isDisplayingResolvedContent()) {
140
+ return findFrameEndMarker(next)
141
+ }
142
+ }
143
+ } else {
144
+ disposeFrameStartMarker(current, context)
145
+ current.data = newData
146
+ updated = true
147
+ }
148
+ }
149
+ if (!updated) {
150
+ current.data = newData
151
+ }
82
152
  }
83
153
  return
84
154
  }
@@ -224,7 +294,14 @@ function diffElementChildren(current: Element, next: Element, context: FrameCont
224
294
  let nextChild = nextChildren[i]
225
295
  let matchIndex = -1
226
296
 
227
- if (isElement(nextChild)) {
297
+ if (isFrameEndMarker(nextChild)) {
298
+ for (let j = 0; j < currentChildren.length; j++) {
299
+ if (!used[j] && isFrameEndMarker(currentChildren[j])) {
300
+ matchIndex = j
301
+ break
302
+ }
303
+ }
304
+ } else if (isElement(nextChild)) {
228
305
  let key = nextChild.getAttribute('data-key')
229
306
  if (key != null && keyToIndex.has(key)) {
230
307
  let idx = keyToIndex.get(key)!
@@ -253,14 +330,42 @@ function diffElementChildren(current: Element, next: Element, context: FrameCont
253
330
  let mi = matchIndexForNext[i]
254
331
  if (mi !== -1) {
255
332
  let curChild = currentChildren[mi]
256
- let cursor = diffNode(curChild, nextChildren[i], context)
333
+ let nextChild = nextChildren[i]
334
+
335
+ let replacement = getCommentMarkerRangeReplacement(
336
+ curChild,
337
+ nextChild,
338
+ currentChildren,
339
+ nextChildren,
340
+ mi,
341
+ i,
342
+ context,
343
+ )
344
+ if (replacement) {
345
+ replaceCommentMarkerRange(replacement, current, context)
346
+
347
+ for (let k = mi; k <= replacement.currentEndIndex; k++) used[k] = true
348
+
349
+ committed[i] = replacement.nextStart
350
+ committed[replacement.nextEndIndex] = nextChildren[replacement.nextEndIndex]
351
+ for (let j = i + 1; j < replacement.nextEndIndex; j++) committed[j] = undefined
352
+
353
+ i = replacement.nextEndIndex
354
+ continue
355
+ }
356
+
357
+ let cursor = diffNode(curChild, nextChild, context)
257
358
  if (cursor) {
258
- // Fast-forward across a hydrated virtual root region.
359
+ // `diffNode` can preserve hydration and frame marker ranges by returning
360
+ // the next end marker, so skip the owned interior nodes here.
259
361
  let nextEndIdx = nextChildren.indexOf(cursor)
260
- let currEndIdx = findHydrationEndIndex(currentChildren, mi)
362
+ let currEndIdx =
363
+ isFrameStartMarker(curChild) && isFrameEndMarker(cursor)
364
+ ? findFrameEndIndex(currentChildren, mi)
365
+ : findHydrationEndIndex(currentChildren, mi)
261
366
 
262
- // Adjacent hydration regions can pre-match into the next region and leave
263
- // an orphaned `<!-- /rmx:h -->` behind. Clear those stale matches first.
367
+ // Adjacent marker ranges can pre-match into the next range and leave an
368
+ // orphaned end marker behind. Clear those stale matches first.
264
369
  for (let j = i + 1; j <= nextEndIdx; j++) {
265
370
  let matchedIndex = matchIndexForNext[j]
266
371
  if (matchedIndex > currEndIdx) {
@@ -272,7 +377,7 @@ function diffElementChildren(current: Element, next: Element, context: FrameCont
272
377
  // Mark the entire current region as used to avoid removals.
273
378
  for (let k = mi; k <= currEndIdx; k++) used[k] = true
274
379
 
275
- // Preserve both boundary markers in committed; skip interior in reorder pass.
380
+ // Preserve both comment markers in committed; skip interior in reorder pass.
276
381
  committed[i] = curChild // start marker
277
382
  committed[nextEndIdx] = currentChildren[currEndIdx] // end marker
278
383
  for (let j = i + 1; j < nextEndIdx; j++) committed[j] = undefined
@@ -296,9 +401,15 @@ function diffElementChildren(current: Element, next: Element, context: FrameCont
296
401
  // Use only an anchor that is actually a child of the current parent
297
402
  let ref = anchor && anchor.parentNode === current ? anchor : null
298
403
 
299
- // Do not move hydration boundary markers; keep region stable.
300
- // If a boundary marker is new, ensure it is inserted before using it as an anchor.
301
- if (isVirtualRootStartMarker(node) || isVirtualRootEndMarker(node)) {
404
+ // Existing comment markers delimit live ranges, so do not reorder them
405
+ // independently from their contents. New markers still need to be inserted
406
+ // before they can be used as anchors.
407
+ if (
408
+ isVirtualRootStartMarker(node) ||
409
+ isVirtualRootEndMarker(node) ||
410
+ isFrameStartMarker(node) ||
411
+ isFrameEndMarker(node)
412
+ ) {
302
413
  if (node.parentNode !== current) {
303
414
  current.insertBefore(node, ref)
304
415
  }
@@ -372,6 +483,37 @@ function findHydrationEndIndex(nodes: Node[], startIdx: number): number {
372
483
  return startIdx
373
484
  }
374
485
 
486
+ function findFrameEndMarker(start: Comment): Comment {
487
+ let node: Node | null = start.nextSibling
488
+ let depth = 1
489
+
490
+ while (node) {
491
+ if (isFrameStartMarker(node)) depth++
492
+ if (isFrameEndMarker(node)) {
493
+ depth--
494
+ if (depth === 0) return node
495
+ }
496
+ node = node.nextSibling
497
+ }
498
+
499
+ throw new Error('Frame end marker not found')
500
+ }
501
+
502
+ function findFrameEndIndex(nodes: Node[], startIdx: number): number {
503
+ let depth = 1
504
+
505
+ for (let j = startIdx + 1; j < nodes.length; j++) {
506
+ let node = nodes[j]
507
+ if (isFrameStartMarker(node)) depth++
508
+ if (isFrameEndMarker(node)) {
509
+ depth--
510
+ if (depth === 0) return j
511
+ }
512
+ }
513
+
514
+ return startIdx
515
+ }
516
+
375
517
  function isTextNode(node: Node): node is Text {
376
518
  return node.nodeType === Node.TEXT_NODE
377
519
  }
@@ -388,6 +530,114 @@ function isFrameStartMarker(node: Node): node is Comment {
388
530
  return node instanceof Comment && node.data.trim().startsWith('rmx:f:')
389
531
  }
390
532
 
533
+ function isFrameEndMarker(node: Node): node is Comment {
534
+ return node instanceof Comment && node.data.trim() === '/rmx:f'
535
+ }
536
+
537
+ function shouldPreserveFrameStartMarker(
538
+ current: Comment,
539
+ next: Comment,
540
+ context: FrameContext,
541
+ ): boolean {
542
+ if (!isFrameStartMarker(next)) return false
543
+
544
+ let currentData = getFrameMarkerData(current, context)
545
+ let nextData = getFrameMarkerData(next, context)
546
+
547
+ return (
548
+ currentData !== undefined &&
549
+ nextData !== undefined &&
550
+ currentData.src === nextData.src &&
551
+ currentData.name === nextData.name
552
+ )
553
+ }
554
+
555
+ function getCommentMarkerRangeReplacement(
556
+ current: Node,
557
+ next: Node,
558
+ currentNodes: Node[],
559
+ nextNodes: Node[],
560
+ currentIndex: number,
561
+ nextIndex: number,
562
+ context: FrameContext,
563
+ ): CommentMarkerRangeReplacement | undefined {
564
+ // Comment markers can represent owned DOM ranges, not standalone comments. If
565
+ // the incoming range no longer has the same identity, replace the whole range
566
+ // before regular node diffing mutates either marker.
567
+ if (
568
+ isFrameStartMarker(current) &&
569
+ isFrameStartMarker(next) &&
570
+ !shouldPreserveFrameStartMarker(current, next, context)
571
+ ) {
572
+ return {
573
+ currentStart: current,
574
+ nextStart: next,
575
+ currentEndIndex: findFrameEndIndex(currentNodes, currentIndex),
576
+ nextEndIndex: findFrameEndIndex(nextNodes, nextIndex),
577
+ }
578
+ }
579
+ }
580
+
581
+ function getFrameMarkerData(marker: Comment, context: FrameContext) {
582
+ let id = getFrameId(marker)
583
+ return context.data.f?.[id]
584
+ }
585
+
586
+ function getFrameId(marker: Comment): string {
587
+ let trimmed = marker.data.trim()
588
+ invariant(trimmed.startsWith('rmx:f:'), 'Invalid frame start marker')
589
+ return trimmed.slice('rmx:f:'.length)
590
+ }
591
+
592
+ function replaceCommentMarkerRange(
593
+ replacement: CommentMarkerRangeReplacement,
594
+ parent: ParentNode,
595
+ context: FrameContext,
596
+ ): void {
597
+ let currentEnd = findFrameEndMarker(replacement.currentStart)
598
+ let nextEnd = findFrameEndMarker(replacement.nextStart)
599
+ let nextNodes = collectNodeRange(replacement.nextStart, nextEnd)
600
+ let currentNodes = collectNodeRange(replacement.currentStart, currentEnd)
601
+
602
+ for (let node of nextNodes) {
603
+ parent.insertBefore(node, replacement.currentStart)
604
+ }
605
+
606
+ for (let node of currentNodes) {
607
+ removeNode(node, parent, context)
608
+ }
609
+ }
610
+
611
+ function collectNodeRange(start: Node, end: Node): Node[] {
612
+ let nodes: Node[] = []
613
+ let node: Node | null = start
614
+
615
+ while (node) {
616
+ nodes.push(node)
617
+ if (node === end) break
618
+ node = node.nextSibling
619
+ }
620
+
621
+ return nodes
622
+ }
623
+
624
+ function collectFrameContentFragment(
625
+ doc: Document,
626
+ start: Comment,
627
+ end: Comment,
628
+ ): DocumentFragment {
629
+ let fragment = doc.createDocumentFragment()
630
+ let node = start.nextSibling
631
+
632
+ while (node && node !== end) {
633
+ let next = node.nextSibling
634
+ fragment.appendChild(node)
635
+ node = next
636
+ }
637
+
638
+ return fragment
639
+ }
640
+
391
641
  function removeNode(node: Node, parent: ParentNode, context: FrameContext): void {
392
642
  disposeRemovedVirtualRoots(node)
393
643
  disposeRemovedSubFrames(node, context)
@@ -9,6 +9,7 @@ import type { MixInput } from './mixins/mixin.ts'
9
9
  * - Copyright (c) 2015-present Jason Miller
10
10
  */
11
11
  type Booleanish = boolean | 'true' | 'false'
12
+ type TranslateValue = 'yes' | 'no'
12
13
 
13
14
  /**
14
15
  * Layout animation configuration for FLIP-based position animations.
@@ -109,6 +110,8 @@ export interface SVGProps<eventTarget extends EventTarget = SVGElement>
109
110
  attributeName?: Trackable<string | undefined>
110
111
  /** The `attributeType` SVG attribute. */
111
112
  attributeType?: Trackable<string | undefined>
113
+ /** The `autoReverse` SVG attribute. */
114
+ autoReverse?: Trackable<Booleanish | undefined>
112
115
  /** The `azimuth` SVG attribute. */
113
116
  azimuth?: Trackable<number | string | undefined>
114
117
  /** The `baseFrequency` SVG attribute. */
@@ -212,7 +215,7 @@ export interface SVGProps<eventTarget extends EventTarget = SVGElement>
212
215
  /** The `exponent` SVG attribute. */
213
216
  exponent?: Trackable<number | string | undefined>
214
217
  /** The `externalResourcesRequired` SVG attribute. */
215
- externalResourcesRequired?: Trackable<number | string | undefined>
218
+ externalResourcesRequired?: Trackable<Booleanish | undefined>
216
219
  /** The `fill` SVG attribute. */
217
220
  fill?: Trackable<string | undefined>
218
221
  /** The `fillOpacity` SVG attribute. */
@@ -238,7 +241,7 @@ export interface SVGProps<eventTarget extends EventTarget = SVGElement>
238
241
  /** The `flood-opacity` SVG attribute. */
239
242
  'flood-opacity'?: Trackable<number | string | undefined>
240
243
  /** The `focusable` SVG attribute. */
241
- focusable?: Trackable<number | string | undefined>
244
+ focusable?: Trackable<Booleanish | undefined>
242
245
  /** The `fontFamily` SVG attribute. */
243
246
  fontFamily?: Trackable<string | undefined>
244
247
  /** The `font-family` SVG attribute. */
@@ -446,7 +449,7 @@ export interface SVGProps<eventTarget extends EventTarget = SVGElement>
446
449
  /** The `pointsAtZ` SVG attribute. */
447
450
  pointsAtZ?: Trackable<number | string | undefined>
448
451
  /** The `preserveAlpha` SVG attribute. */
449
- preserveAlpha?: Trackable<number | string | undefined>
452
+ preserveAlpha?: Trackable<Booleanish | undefined>
450
453
  /** The `preserveAspectRatio` SVG attribute. */
451
454
  preserveAspectRatio?: Trackable<string | undefined>
452
455
  /** The `primitiveUnits` SVG attribute. */
@@ -1263,7 +1266,7 @@ export interface AllHTMLProps<eventTarget extends EventTarget = EventTarget>
1263
1266
  /** The `decoding` HTML attribute. */
1264
1267
  decoding?: Trackable<'sync' | 'async' | 'auto' | undefined>
1265
1268
  /** The `draggable` HTML attribute. */
1266
- draggable?: Trackable<boolean | undefined>
1269
+ draggable?: Trackable<Booleanish | undefined>
1267
1270
  /** The `encType` HTML attribute. */
1268
1271
  encType?: Trackable<string | undefined>
1269
1272
  /** The `enctype` HTML attribute. */
@@ -1487,7 +1490,9 @@ export interface AllHTMLProps<eventTarget extends EventTarget = EventTarget>
1487
1490
  /** The `span` HTML attribute. */
1488
1491
  span?: Trackable<number | undefined>
1489
1492
  /** The `spellcheck` HTML attribute. */
1490
- spellcheck?: Trackable<boolean | undefined>
1493
+ spellcheck?: Trackable<Booleanish | undefined>
1494
+ /** The `spellCheck` HTML attribute. */
1495
+ spellCheck?: Trackable<Booleanish | undefined>
1491
1496
  /** The `src` HTML attribute. */
1492
1497
  src?: Trackable<string | undefined>
1493
1498
  /** The `srcDoc` HTML attribute. */
@@ -1551,7 +1556,7 @@ export interface AllHTMLProps<eventTarget extends EventTarget = EventTarget>
1551
1556
  /** The `results` HTML attribute. */
1552
1557
  results?: Trackable<number | undefined>
1553
1558
  /** The `translate` HTML attribute. */
1554
- translate?: Trackable<boolean | undefined>
1559
+ translate?: Trackable<TranslateValue | undefined>
1555
1560
 
1556
1561
  // RDFa Attributes
1557
1562
  /** The `about` HTML attribute. */
@@ -1632,7 +1637,7 @@ export interface HTMLProps<eventTarget extends EventTarget = EventTarget>
1632
1637
  /** The `dir` HTML attribute. */
1633
1638
  dir?: Trackable<'auto' | 'rtl' | 'ltr' | undefined>
1634
1639
  /** The `draggable` HTML attribute. */
1635
- draggable?: Trackable<boolean | undefined>
1640
+ draggable?: Trackable<Booleanish | undefined>
1636
1641
  /** The `enterkeyhint` HTML attribute. */
1637
1642
  enterkeyhint?: Trackable<
1638
1643
  'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send' | undefined
@@ -1662,7 +1667,9 @@ export interface HTMLProps<eventTarget extends EventTarget = EventTarget>
1662
1667
  /** The `slot` HTML attribute. */
1663
1668
  slot?: Trackable<string | undefined>
1664
1669
  /** The `spellcheck` HTML attribute. */
1665
- spellcheck?: Trackable<boolean | undefined>
1670
+ spellcheck?: Trackable<Booleanish | undefined>
1671
+ /** The `spellCheck` HTML attribute. */
1672
+ spellCheck?: Trackable<Booleanish | undefined>
1666
1673
  /** The `style` HTML attribute. */
1667
1674
  style?: Trackable<string | StyleProps | undefined>
1668
1675
  /** The `tabindex` HTML attribute. */
@@ -1672,7 +1679,7 @@ export interface HTMLProps<eventTarget extends EventTarget = EventTarget>
1672
1679
  /** The `title` HTML attribute. */
1673
1680
  title?: Trackable<string | undefined>
1674
1681
  /** The `translate` HTML attribute. */
1675
- translate?: Trackable<boolean | undefined>
1682
+ translate?: Trackable<TranslateValue | undefined>
1676
1683
 
1677
1684
  // WAI-ARIA Attributes
1678
1685
  // Most elements only allow a subset of roles and so this