@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
@@ -20,6 +20,7 @@ import {
20
20
  isCommittedTextNode,
21
21
  isFragmentNode,
22
22
  isHostNode,
23
+ isNonRenderNode,
23
24
  isTextNode,
24
25
  findContextFromAncestry,
25
26
  } from './vnode.ts'
@@ -421,6 +422,10 @@ export function diffVNodes(
421
422
  return rootCursor
422
423
  }
423
424
 
425
+ if (isNonRenderNode(curr) && isNonRenderNode(next)) {
426
+ return rootCursor
427
+ }
428
+
424
429
  if (isCommittedTextNode(curr) && isTextNode(next)) {
425
430
  diffText(curr, next, vParent)
426
431
  return rootCursor
@@ -444,7 +449,7 @@ export function diffVNodes(
444
449
  frame,
445
450
  scheduler,
446
451
  styles,
447
- vParent,
452
+ next,
448
453
  rootTarget,
449
454
  undefined,
450
455
  anchor,
@@ -484,7 +489,7 @@ function replace(
484
489
  return
485
490
  }
486
491
 
487
- let replacementAnchor = findNextSiblingDomAnchor(curr, vParent) ?? anchor
492
+ let replacementAnchor = findNextSiblingDomAnchor(curr) ?? anchor
488
493
  remove(curr, domParent, scheduler, styles)
489
494
  insert(next, domParent, frame, scheduler, styles, vParent, rootTarget, replacementAnchor)
490
495
  }
@@ -709,6 +714,10 @@ function insert(
709
714
  ? (dom: Node) => domParent.insertBefore(dom, anchor)
710
715
  : (dom: Node) => domParent.appendChild(dom)
711
716
 
717
+ if (isNonRenderNode(node)) {
718
+ return cursor
719
+ }
720
+
712
721
  if (isTextNode(node)) {
713
722
  if (cursor instanceof Text) {
714
723
  node._parent = vParent
@@ -878,17 +887,7 @@ function insert(
878
887
  if (isFragmentNode(node)) {
879
888
  // Insert fragment children in order before the same anchor
880
889
  for (let child of node._children) {
881
- cursor = insert(
882
- child,
883
- domParent,
884
- frame,
885
- scheduler,
886
- styles,
887
- vParent,
888
- rootTarget,
889
- anchor,
890
- cursor,
891
- )
890
+ cursor = insert(child, domParent, frame, scheduler, styles, node, rootTarget, anchor, cursor)
892
891
  }
893
892
  return cursor
894
893
  }
@@ -1275,6 +1274,8 @@ export function renderComponent(
1275
1274
  anchor?: Node,
1276
1275
  cursor?: Node | null,
1277
1276
  ): Node | null | undefined {
1277
+ if (handle.isRemoved()) return cursor
1278
+
1278
1279
  let [element, tasks] = handle.render(next.props)
1279
1280
  let content = toVNode(element)
1280
1281
  let newCursor = diffVNodes(
@@ -2020,15 +2021,23 @@ function shouldDispatchInlineMixinLifecycle(node: Node): boolean {
2020
2021
  return true
2021
2022
  }
2022
2023
 
2023
- export function findNextSiblingDomAnchor(curr: VNode, vParent?: VNode): Node | null {
2024
+ export function findNextSiblingDomAnchor(curr: VNode): Node | null {
2025
+ let vParent = curr._parent
2024
2026
  if (!vParent || !Array.isArray(vParent._children)) return null
2025
2027
  let children = vParent._children
2028
+ if (children.length === 0) return findNextSiblingDomAnchor(vParent)
2029
+
2026
2030
  let idx = children.indexOf(curr)
2027
2031
  if (idx === -1) return null
2028
2032
  for (let i = idx + 1; i < children.length; i++) {
2029
2033
  let dom = findFirstDomAnchor(children[i])
2030
2034
  if (dom) return dom
2031
2035
  }
2036
+
2037
+ if (isFragmentNode(vParent)) {
2038
+ return findNextSiblingDomAnchor(vParent)
2039
+ }
2040
+
2032
2041
  return null
2033
2042
  }
2034
2043
 
@@ -17,7 +17,23 @@ type SchedulerPhaseListener = EventListenerOrEventListenerObject | null
17
17
  /**
18
18
  * Scheduler API used by the reconciler and frame runtime.
19
19
  */
20
- export type Scheduler = ReturnType<typeof createScheduler>
20
+ export interface Scheduler {
21
+ enqueue(vnode: CommittedComponentNode, domParent: ParentNode): void
22
+ enqueueWork(newTasks: EmptyFn[]): void
23
+ enqueueCommitPhase(newTasks: EmptyFn[]): void
24
+ enqueueTasks(newTasks: EmptyFn[]): void
25
+ addEventListener(
26
+ type: SchedulerPhaseType,
27
+ listener: SchedulerPhaseListener,
28
+ options?: AddEventListenerOptions | boolean,
29
+ ): void
30
+ removeEventListener(
31
+ type: SchedulerPhaseType,
32
+ listener: SchedulerPhaseListener,
33
+ options?: EventListenerOptions | boolean,
34
+ ): void
35
+ dequeue(): void
36
+ }
21
37
 
22
38
  // Protect against infinite cascading updates (e.g. handle.update() during render)
23
39
  const MAX_CASCADING_UPDATES = 50
@@ -38,7 +54,7 @@ export function createScheduler(
38
54
  doc: Document,
39
55
  rootTarget: EventTarget,
40
56
  styles: StyleManager = defaultStyleManager,
41
- ) {
57
+ ): Scheduler {
42
58
  let documentState = createDocumentState(doc)
43
59
  let scheduled = new Map<CommittedComponentNode, ParentNode>()
44
60
  let workTasks: EmptyFn[] = []
@@ -53,23 +69,7 @@ export function createScheduler(
53
69
  beforeUpdate: 0,
54
70
  commit: 0,
55
71
  }
56
- let scheduler: {
57
- enqueue(vnode: CommittedComponentNode, domParent: ParentNode): void
58
- enqueueWork(newTasks: EmptyFn[]): void
59
- enqueueCommitPhase(newTasks: EmptyFn[]): void
60
- enqueueTasks(newTasks: EmptyFn[]): void
61
- addEventListener(
62
- type: SchedulerPhaseType,
63
- listener: SchedulerPhaseListener,
64
- options?: AddEventListenerOptions | boolean,
65
- ): void
66
- removeEventListener(
67
- type: SchedulerPhaseType,
68
- listener: SchedulerPhaseListener,
69
- options?: EventListenerOptions | boolean,
70
- ): void
71
- dequeue(): void
72
- }
72
+ let scheduler: Scheduler
73
73
 
74
74
  function dispatchError(error: unknown) {
75
75
  console.error(error)
@@ -137,7 +137,7 @@ export function createScheduler(
137
137
  // Needed for fragment self-updates that add children - without this, new children
138
138
  // would be appended after siblings. The keyed diff has placement logic, but unkeyed
139
139
  // diff relies on anchor for correct positioning.
140
- let anchor = findNextSiblingDomAnchor(vnode, vParent) || undefined
140
+ let anchor = findNextSiblingDomAnchor(vnode) || undefined
141
141
  try {
142
142
  let updateStyles = getFrameStyleManager(vnode)
143
143
  renderComponent(
@@ -5,6 +5,7 @@ const CANONICAL_CAMEL_SVG_ATTRS = new Set([
5
5
  'accentHeight',
6
6
  'attributeName',
7
7
  'attributeType',
8
+ 'autoReverse',
8
9
  'baseFrequency',
9
10
  'baseProfile',
10
11
  'calcMode',
@@ -2,7 +2,7 @@ import { Fragment } from './component.ts'
2
2
  import { invariant } from './invariant.ts'
3
3
  import { isEmptyChild, isPrimitiveChild, normalizeChildren } from './core/children.ts'
4
4
  import type { RemixElement, RemixNode } from './jsx.ts'
5
- import { isRemixElement, TEXT_NODE, type VNode } from './vnode.ts'
5
+ import { isRemixElement, NON_RENDER_NODE, TEXT_NODE, type VNode } from './vnode.ts'
6
6
 
7
7
  function flatMapChildrenToVNodes(node: RemixElement): VNode[] {
8
8
  if (!('children' in node.props)) return []
@@ -21,7 +21,7 @@ function flattenChildrenToVNodes(nodes: RemixNode[], out: VNode[]): void {
21
21
 
22
22
  export function toVNode(node: RemixNode): VNode {
23
23
  if (isEmptyChild(node)) {
24
- return { type: TEXT_NODE, _text: '' }
24
+ return { type: NON_RENDER_NODE }
25
25
  }
26
26
 
27
27
  if (isPrimitiveChild(node)) {
@@ -6,6 +6,7 @@ import type { ElementProps, RemixNode } from './jsx.ts'
6
6
  export { isRemixElement }
7
7
 
8
8
  export const TEXT_NODE = Symbol('TEXT_NODE')
9
+ export const NON_RENDER_NODE = Symbol('NON_RENDER_NODE')
9
10
  export const ROOT_VNODE = Symbol('ROOT_VNODE')
10
11
 
11
12
  export type VNodeType =
@@ -13,6 +14,7 @@ export type VNodeType =
13
14
  | string // host element
14
15
  | Function // component
15
16
  | typeof TEXT_NODE
17
+ | typeof NON_RENDER_NODE
16
18
  | typeof Fragment
17
19
  | typeof Frame
18
20
 
@@ -66,6 +68,10 @@ export type TextNode = VNode & {
66
68
  _text: string
67
69
  }
68
70
 
71
+ export type NonRenderNode = VNode & {
72
+ type: typeof NON_RENDER_NODE
73
+ }
74
+
69
75
  export type CommittedTextNode = TextNode & {
70
76
  _dom: Text
71
77
  }
@@ -102,6 +108,10 @@ export function isTextNode(node: VNode): node is TextNode {
102
108
  return node.type === TEXT_NODE
103
109
  }
104
110
 
111
+ export function isNonRenderNode(node: VNode): node is NonRenderNode {
112
+ return node.type === NON_RENDER_NODE
113
+ }
114
+
105
115
  export function isCommittedTextNode(node: VNode): node is CommittedTextNode {
106
116
  return isTextNode(node) && node._dom instanceof Text
107
117
  }
@@ -1,6 +1,6 @@
1
1
  # Server
2
2
 
3
- Remix Component can render to HTML on the server using two APIs:
3
+ Remix UI can render to HTML on the server using two APIs:
4
4
 
5
5
  - `renderToString` - Returns a complete HTML string. Simple, but buffers the entire response.
6
6
  - `renderToStream` - Returns a `ReadableStream<Uint8Array>`. Sends the initial HTML immediately and streams frame content as it resolves.
@@ -26,6 +26,7 @@ import { renderToStream } from 'remix/ui/server'
26
26
 
27
27
  let stream = renderToStream(<App />, {
28
28
  frameSrc: request.url,
29
+ signal: request.signal,
29
30
  resolveFrame(src, _target, context) {
30
31
  let frameUrl = new URL(src, context?.currentFrameSrc ?? request.url)
31
32
  return fetchHtml(frameUrl)
@@ -44,6 +45,7 @@ return new Response(stream, {
44
45
 
45
46
  - **`frameSrc`** - Seeds SSR frame state for the current render. When provided, server-rendered components can read `handle.frame.src` and `handle.frames.top.src` during SSR.
46
47
  - **`topFrameSrc`** - Overrides the root frame URL used for `handle.frames.top.src`. This is mainly useful when calling `renderToStream()` from inside `resolveFrame()` for a nested frame render.
48
+ - **`signal`** - Cancels pending server rendering work. Pass `request.signal` so client disconnects can stop unresolved frame work without invoking `onError` for the disconnect itself.
47
49
  - **`resolveFrame(src, target, context)`** - Called when a `<Frame>` needs its content. Return a string of HTML, a `ReadableStream<Uint8Array>`, or a promise of either. `context.currentFrameSrc` is the URL for the frame that contains the `<Frame>`, and `context.topFrameSrc` is the outer document URL. Required if your component tree contains `<Frame>` elements.
48
50
  - **`onError(error)`** - Called when a rendering error occurs. If not provided, the stream rejects with the error.
49
51
 
@@ -80,7 +82,7 @@ function ProductPage() {
80
82
 
81
83
  ## CSS
82
84
 
83
- Components using the `css` prop have their styles collected during rendering and emitted as a single `<style>` tag in the `<head>`. No client-side style injection is needed for server-rendered content.
85
+ Components using the `css(...)` mixin through `mix` have their styles collected during rendering and emitted as a single `<style>` tag in the `<head>`. No client-side style injection is needed for server-rendered content.
84
86
 
85
87
  ## See Also
86
88
 
@@ -7,6 +7,7 @@ import {
7
7
  SELF_CLOSING_TAGS,
8
8
  normalizeAttributeName,
9
9
  serializeStyleObject,
10
+ shouldStringifyBooleanAttribute,
10
11
  } from '../runtime/core/attributes.ts'
11
12
  import { appendFlushMarker, type FlushKind, stripFlushMarkers } from '../runtime/stream-protocol.ts'
12
13
  import { REMIX_UI_STYLE_LAYER } from '../style/layers.ts'
@@ -31,6 +32,8 @@ export interface RenderToStreamOptions {
31
32
  frameSrc?: string | URL
32
33
  /** Source URL for the top-level frame in nested frame renders. */
33
34
  topFrameSrc?: string | URL
35
+ /** Signal that cancels pending server rendering work. */
36
+ signal?: AbortSignal
34
37
  /** Error hook invoked when rendering work throws. */
35
38
  onError?: (error: unknown) => void
36
39
  /** Callback used to resolve nested frame content during streaming SSR. */
@@ -97,6 +100,7 @@ interface RenderContext {
97
100
  unresolvedHydrationData: Map<string, UnresolvedHydrationData>
98
101
  frameData: Map<string, FrameData>
99
102
  blockingFrameTails: ReadableStream<Uint8Array>[]
103
+ signal: AbortSignal
100
104
  flushKind: FlushKind
101
105
  serverIdScope: string
102
106
  serverIdCounter: number
@@ -192,6 +196,7 @@ export function renderToStream(
192
196
  let currentFrameSrc = normalizeFrameSrc(options?.frameSrc ?? options?.topFrameSrc)
193
197
  let topFrameSrc = normalizeFrameSrc(options?.topFrameSrc ?? currentFrameSrc)
194
198
  let rootFrameState = createSsrFrameState(currentFrameSrc, topFrameSrc)
199
+ let renderAbortController = new AbortController()
195
200
 
196
201
  let context: RenderContext = {
197
202
  insideSvg: false,
@@ -204,21 +209,38 @@ export function renderToStream(
204
209
  unresolvedHydrationData: new Map(),
205
210
  frameData: new Map(),
206
211
  blockingFrameTails: [],
212
+ signal: renderAbortController.signal,
207
213
  flushKind: 'fragment',
208
214
  serverIdScope: crypto.randomUUID().slice(0, 8),
209
215
  serverIdCounter: 0,
210
216
  }
211
217
 
218
+ function cancel(reason: unknown): void {
219
+ if (!renderAbortController.signal.aborted) {
220
+ renderAbortController.abort(reason)
221
+ }
222
+ }
223
+
224
+ let signal = options?.signal
225
+ if (signal?.aborted) {
226
+ cancel(signal.reason)
227
+ } else {
228
+ signal?.addEventListener('abort', () => cancel(signal.reason), { once: true })
229
+ }
230
+
212
231
  return new ReadableStream({
213
232
  async start(controller) {
214
233
  try {
215
234
  let root = buildSegment(node, context, rootFrameState)
216
235
  await resolveBlocking(root)
236
+ if (closeIfCancelled(controller, context)) return
217
237
  await resolveClientEntries(context, options?.resolveClientEntry)
238
+ if (closeIfCancelled(controller, context)) return
218
239
  validateClientEntriesForHydration(context)
219
240
  let html = serializeSegment(root)
220
241
  let finalHtml = finalizeHtml(html, context)
221
242
  let bytes = encoder.encode(appendFlushMarker(finalHtml, context.flushKind))
243
+ if (closeIfCancelled(controller, context)) return
222
244
  controller.enqueue(bytes)
223
245
 
224
246
  // If we have any tails from blocking frame streams, stream them now.
@@ -226,7 +248,7 @@ export function renderToStream(
226
248
  // that must come after the initial document chunk.
227
249
  let tailPromise =
228
250
  context.blockingFrameTails.length > 0
229
- ? streamByteStreams(context.blockingFrameTails, controller, context.onError)
251
+ ? streamByteStreams(context.blockingFrameTails, controller, context)
230
252
  : Promise.resolve()
231
253
 
232
254
  // If we have pending non-blocking frames, stream them as they resolve
@@ -237,15 +259,44 @@ export function renderToStream(
237
259
 
238
260
  await Promise.all([tailPromise, pendingPromise])
239
261
 
262
+ if (closeIfCancelled(controller, context)) return
240
263
  controller.close()
241
264
  } catch (error) {
265
+ if (isSignalAbortError(context.signal, error)) {
266
+ closeStream(controller)
267
+ return
268
+ }
242
269
  onError(error)
243
270
  controller.error(error)
244
271
  }
245
272
  },
273
+ cancel(reason) {
274
+ cancel(reason)
275
+ },
246
276
  })
247
277
  }
248
278
 
279
+ function isSignalAbortError(signal: AbortSignal, error: unknown): boolean {
280
+ return signal.aborted && error === signal.reason
281
+ }
282
+
283
+ function closeIfCancelled(
284
+ controller: ReadableStreamDefaultController,
285
+ context: RenderContext,
286
+ ): boolean {
287
+ if (!context.signal.aborted) return false
288
+ closeStream(controller)
289
+ return true
290
+ }
291
+
292
+ function closeStream(controller: ReadableStreamDefaultController): void {
293
+ try {
294
+ controller.close()
295
+ } catch {
296
+ // The consumer may already have cancelled the stream.
297
+ }
298
+ }
299
+
249
300
  function defaultResolveFrame(): never {
250
301
  throw new Error('No resolveFrame provided')
251
302
  }
@@ -387,7 +438,7 @@ function buildSegment(node: RemixNode, context: RenderContext, frameState: SsrFr
387
438
 
388
439
  if (typeof type === 'function') {
389
440
  if (type === Frame) {
390
- return buildFrameSegment(props, context, frameState)
441
+ return buildFrameSegment(node, context, frameState)
391
442
  }
392
443
  if (isEntry(type)) {
393
444
  return buildEntrySegment(type, props, context, frameState)
@@ -405,7 +456,12 @@ function buildSegment(node: RemixNode, context: RenderContext, frameState: SsrFr
405
456
  return staticSeg('')
406
457
  }
407
458
 
408
- function buildFrameSegment(props: any, context: RenderContext, frameState: SsrFrameState): Segment {
459
+ function buildFrameSegment(
460
+ node: RemixElement,
461
+ context: RenderContext,
462
+ frameState: SsrFrameState,
463
+ ): Segment {
464
+ let props = node.props
409
465
  let frameId = randomId('f')
410
466
 
411
467
  // Store frame data in context for aggregation
@@ -428,6 +484,9 @@ function buildFrameSegment(props: any, context: RenderContext, frameState: SsrFr
428
484
  let framePromise = Promise.resolve(
429
485
  context.resolveFrame(props.src, props.name, resolveFrameContext),
430
486
  ).then(async (resolved) => resolveFrameHtml(resolved))
487
+ // The response stream can be cancelled before pending frames are drained.
488
+ // Keep the promise observed so request aborts don't become unhandled.
489
+ framePromise.catch(() => {})
431
490
  context.pendingFrames.push({ frameId, promise: framePromise })
432
491
  } else {
433
492
  seg.pending = Promise.resolve(
@@ -529,11 +588,15 @@ function renderAttributes(props: any, isSvg: boolean, excludedProps?: Set<string
529
588
  if (excludedProps?.has(key)) continue
530
589
 
531
590
  let value = props[key]
532
- if (value === undefined || value === null || value === false) continue
533
-
534
591
  let attrName = transformAttributeName(key, isSvg)
592
+ let shouldStringifyBoolean = shouldStringifyBooleanAttribute(attrName)
593
+ if (value === undefined || value === null || (value === false && !shouldStringifyBoolean)) {
594
+ continue
595
+ }
535
596
 
536
- if (value === true) {
597
+ if (typeof value === 'boolean' && shouldStringifyBoolean) {
598
+ attrs += ` ${attrName}="${escapeHtml(String(value))}"`
599
+ } else if (value === true) {
537
600
  attrs += ` ${attrName}`
538
601
  } else {
539
602
  attrs += ` ${attrName}="${escapeHtml(String(value))}"`
@@ -1248,26 +1311,33 @@ async function streamPendingFrames(
1248
1311
  let processedFrames = new Set<string>()
1249
1312
 
1250
1313
  while (true) {
1314
+ if (context.signal.aborted) break
1315
+
1251
1316
  let batch = context.pendingFrames.filter(({ frameId }) => !processedFrames.has(frameId))
1252
1317
  if (batch.length === 0) break
1253
1318
 
1254
1319
  await Promise.all(
1255
1320
  batch.map(async ({ frameId, promise }) => {
1321
+ if (context.signal.aborted) return
1256
1322
  processedFrames.add(frameId)
1257
1323
  try {
1258
1324
  let { html, tail } = await promise
1325
+ if (context.signal.aborted) return
1259
1326
  html = dedupeServerStyleTagsInHtml(html, context.emittedStyles)
1260
1327
 
1261
1328
  // Stream as a template element (first chunk only)
1262
1329
  let templateHtml = `<template id="${frameId}">${escapeTemplateContent(html)}</template>`
1330
+ if (context.signal.aborted) return
1263
1331
  controller.enqueue(encoder.encode(templateHtml))
1264
1332
 
1265
1333
  // Forward any additional chunks from a stream-valued resolveFrame result.
1266
1334
  if (tail) {
1267
- await streamByteStreams([tail], controller, context.onError)
1335
+ await streamByteStreams([tail], controller, context)
1268
1336
  }
1269
1337
  } catch (error) {
1270
- context.onError(error)
1338
+ if (!isSignalAbortError(context.signal, error)) {
1339
+ context.onError(error)
1340
+ }
1271
1341
  }
1272
1342
  }),
1273
1343
  )
@@ -1277,19 +1347,23 @@ async function streamPendingFrames(
1277
1347
  async function streamByteStreams(
1278
1348
  streams: ReadableStream<Uint8Array>[],
1279
1349
  controller: ReadableStreamDefaultController,
1280
- onError: (error: unknown) => void,
1350
+ context: RenderContext,
1281
1351
  ): Promise<void> {
1282
1352
  await Promise.all(
1283
1353
  streams.map(async (stream) => {
1284
1354
  let reader = stream.getReader()
1285
1355
  try {
1286
1356
  while (true) {
1357
+ if (context.signal.aborted) break
1287
1358
  let { done, value } = await reader.read()
1288
1359
  if (done) break
1360
+ if (context.signal.aborted) break
1289
1361
  controller.enqueue(value)
1290
1362
  }
1291
1363
  } catch (error) {
1292
- onError(error)
1364
+ if (!isSignalAbortError(context.signal, error)) {
1365
+ context.onError(error)
1366
+ }
1293
1367
  } finally {
1294
1368
  reader.releaseLock()
1295
1369
  }
@@ -1,4 +1,5 @@
1
1
  import { createMixin, renderMixinElement } from '../runtime/mixins/mixin.ts'
2
+ import type { MixinFactory } from '../runtime/mixins/mixin.ts'
2
3
  import type { MixinDescriptor } from '../runtime/mixins/mixin.ts'
3
4
  import type { ElementProps } from '../runtime/jsx.ts'
4
5
  import { invariant } from '../runtime/invariant.ts'
@@ -20,7 +21,11 @@ const clientStyleCache: StyleCache = new Map()
20
21
  /**
21
22
  * Applies generated class names for CSS object styles.
22
23
  */
23
- export const css = createMixin<Element, [styles: CSSProps], ElementProps>((handle) => {
24
+ export const css: MixinFactory<Element, [styles: CSSProps], ElementProps> = createMixin<
25
+ Element,
26
+ [styles: CSSProps],
27
+ ElementProps
28
+ >((handle) => {
24
29
  let activeSelector = ''
25
30
  let activeGeneration = -1
26
31
  let currentStyles: CSSProps = {}
@@ -6,5 +6,4 @@ export type {
6
6
  } from './style.ts'
7
7
  export { processStyleClass, normalizeCssValue } from './style.ts'
8
8
  export { createStyleManager }
9
-
10
- export type StyleManager = ReturnType<typeof createStyleManager>
9
+ export type { ServerStyleSource, StyleManager } from './stylesheet.ts'
@@ -141,22 +141,15 @@ function styleToCss(styles: StyleObject, selector: string = ''): string {
141
141
  continue
142
142
  }
143
143
 
144
- // For nested selectors, keep them wholesale inside the base block
145
144
  // Allow nested selectors to be conditionally disabled.
146
145
  // e.g. { '&:hover': condition ? undefined : { ... } }
147
146
  let record = toRecord(value)
148
147
  if (!record) continue
149
148
 
150
- let nestedContent = ''
151
- for (let [prop, propValue] of Object.entries(record)) {
152
- if (propValue != null) {
153
- let normalizedValue = normalizeCssValue(prop, propValue)
154
- nestedContent += ` ${camelToKebab(prop)}: ${normalizedValue};\n`
155
- }
156
- }
157
- if (nestedContent) {
149
+ let nestedContent = nestedStyleBodyToCss(record, 4)
150
+ if (nestedContent.trim().length > 0) {
158
151
  // Preserve key verbatim (e.g., '&[aria-selected], &[rmx-focus]')
159
- nestedBlocks.push(` ${key} {\n${nestedContent} }`)
152
+ nestedBlocks.push(` ${key} {\n${nestedContent}\n }`)
160
153
  }
161
154
  } else {
162
155
  // Base declaration
@@ -189,6 +182,32 @@ function styleToCss(styles: StyleObject, selector: string = ''): string {
189
182
  return css
190
183
  }
191
184
 
185
+ function nestedStyleBodyToCss(styles: StyleObject, spaces: number): string {
186
+ // This renders content that is already inside a style rule. Nested selectors
187
+ // and nested at-rules can stay in that rule body; root at-rule placement is
188
+ // handled by styleToCss before entering a nested style body.
189
+ let pad = ' '.repeat(spaces)
190
+ let declarations: string[] = []
191
+ let nestedBlocks: string[] = []
192
+
193
+ for (let [key, value] of Object.entries(styles)) {
194
+ if (isComplexSelector(key)) {
195
+ let record = toRecord(value)
196
+ if (!record) continue
197
+
198
+ let nestedContent = nestedStyleBodyToCss(record, spaces + 2)
199
+ if (nestedContent.trim().length > 0) {
200
+ nestedBlocks.push(`${pad}${key} {\n${nestedContent}\n${pad}}`)
201
+ }
202
+ } else if (value != null) {
203
+ let normalizedValue = normalizeCssValue(key, value)
204
+ declarations.push(`${pad}${camelToKebab(key)}: ${normalizedValue};`)
205
+ }
206
+ }
207
+
208
+ return [...declarations, ...nestedBlocks].join('\n')
209
+ }
210
+
192
211
  function indent(text: string, spaces: number): string {
193
212
  let pad = ' '.repeat(spaces)
194
213
  return text
@@ -1,7 +1,19 @@
1
1
  import { REMIX_UI_STYLE_LAYER } from './layers.ts'
2
2
 
3
3
  type RuleEntry = { count: number; index: number }
4
- type ServerStyleSource = ParentNode | Iterable<Node>
4
+ export type ServerStyleSource = ParentNode | Iterable<Node>
5
+
6
+ export interface StyleManager {
7
+ insert(className: string, rule: string): void
8
+ remove(className: string): void
9
+ has(className: string): boolean
10
+ getGeneration(): number
11
+ reset(): void
12
+ adoptServerStyles(source: ServerStyleSource): Set<string>
13
+ replaceServerStyles(source: ServerStyleSource): void
14
+ selectors(): IterableIterator<string>
15
+ dispose(): void
16
+ }
5
17
 
6
18
  const SERVER_STYLE_SELECTOR = 'style[data-rmx]'
7
19
 
@@ -68,7 +80,7 @@ function getStyleSelector(styleEl: HTMLStyleElement): string | null {
68
80
  return selector ? selector : null
69
81
  }
70
82
 
71
- export function createStyleManager(layer: string = REMIX_UI_STYLE_LAYER) {
83
+ export function createStyleManager(layer: string = REMIX_UI_STYLE_LAYER): StyleManager {
72
84
  let stylesheet: CSSStyleSheet | null = null
73
85
  let generation = 0
74
86