@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,22 +1,15 @@
1
1
  import {
2
2
  attrs,
3
3
  createMixin,
4
- css,
5
4
  on,
6
- type CSSMixinDescriptor,
7
5
  type ElementProps,
8
6
  type Handle,
9
7
  type MixinFactory,
10
8
  type RemixNode,
11
9
  } from '@remix-run/ui'
12
- import {
13
- anchor as positionAnchor,
14
- type AnchorOptions,
15
- type AnchorTarget,
16
- } from '../anchor/anchor.ts'
17
- import { onOutsideClick } from '../../interactions/outside-click/outside-click-mixin.ts'
18
- import { theme } from '../../theme/theme.ts'
19
- import { lockScroll } from '../../utils/scroll-lock.ts'
10
+ import { anchor as positionAnchor, type AnchorOptions, type AnchorTarget } from '../anchor/index.ts'
11
+ import { onOutsideClick } from './outside-click.ts'
12
+ import { lockScroll } from './scroll-lock.ts'
20
13
 
21
14
  export interface PopoverContext {
22
15
  hideFocusTarget: HTMLElement | null
@@ -34,53 +27,6 @@ interface AnchorRef {
34
27
  options: AnchorOptions
35
28
  }
36
29
 
37
- const popupViewportClampMaxHeight = '50dvh'
38
-
39
- const popoverSurfaceTransitionCss: CSSMixinDescriptor = css({
40
- opacity: 0,
41
- '&:popover-open': {
42
- opacity: 1,
43
- },
44
- '&:not(:popover-open)': {
45
- pointerEvents: 'none',
46
- transition: 'opacity 180ms ease-in, overlay 180ms ease-in, display 180ms ease-in',
47
- transitionBehavior: 'allow-discrete',
48
- },
49
- })
50
-
51
- const popoverContentCss: CSSMixinDescriptor = css({
52
- flex: '1 1 auto',
53
- minHeight: 0,
54
- padding: theme.space.xs,
55
- overflow: 'auto',
56
- overscrollBehavior: 'contain',
57
- })
58
-
59
- const popoverSurfaceCss: CSSMixinDescriptor = css({
60
- position: 'fixed',
61
- inset: 'auto',
62
- margin: 0,
63
- padding: theme.space.none,
64
- display: 'flex',
65
- flexDirection: 'column',
66
- minHeight: 0,
67
- minWidth: '12rem',
68
- maxWidth: `min(24rem, calc(100vw - (${theme.space.lg} * 2)))`,
69
- maxHeight: popupViewportClampMaxHeight,
70
- border: `1px solid ${theme.colors.border.subtle}`,
71
- borderRadius: theme.radius.lg,
72
- backgroundColor: theme.surface.lvl0,
73
- color: theme.colors.text.primary,
74
- overflow: 'hidden',
75
- boxShadow: `${theme.shadow.xs}, ${theme.shadow.md}`,
76
- '&::backdrop': {
77
- background: 'transparent',
78
- },
79
- })
80
-
81
- export const contentStyle = popoverContentCss
82
- export const surfaceStyle = [popoverSurfaceCss, popoverSurfaceTransitionCss] as const
83
-
84
30
  function PopoverProvider(handle: Handle<PopoverProps, PopoverContext>): () => RemixNode {
85
31
  handle.context.set({
86
32
  hideFocusTarget: null,
@@ -1,5 +1,3 @@
1
- import { createMixin, on, type ElementProps, type MixinFactory } from '@remix-run/ui'
2
-
3
1
  type ScrollLockState = {
4
2
  count: number
5
3
  documentOverflow: string
@@ -73,29 +71,3 @@ export function lockScroll(targetDocument: Document | undefined = globalThis.doc
73
71
  view.scrollTo(currentState.scrollX, currentState.scrollY)
74
72
  }
75
73
  }
76
-
77
- export const lockScrollOnToggle: MixinFactory<HTMLElement, [], ElementProps> = createMixin<
78
- HTMLElement,
79
- [],
80
- ElementProps
81
- >((handle) => {
82
- let unlockScroll = () => {}
83
-
84
- handle.signal.addEventListener('abort', () => {
85
- unlockScroll()
86
- unlockScroll = () => {}
87
- })
88
-
89
- return () => [
90
- on('beforetoggle', (event) => {
91
- unlockScroll()
92
- unlockScroll = () => {}
93
-
94
- if (event.newState !== 'open') {
95
- return
96
- }
97
-
98
- unlockScroll = lockScroll((event.currentTarget as HTMLElement).ownerDocument)
99
- }),
100
- ]
101
- })
@@ -0,0 +1,53 @@
1
+ # radio
2
+
3
+ `radio` is a style mixin for native radio inputs. It only owns radio visuals and the default `type="radio"` behavior for native `<input>` hosts.
4
+
5
+ ## Primitive Usage
6
+
7
+ ```tsx
8
+ import radio from 'remix/ui/radio'
9
+
10
+ function ShippingSpeed() {
11
+ return () => (
12
+ <fieldset>
13
+ <label>
14
+ <input defaultChecked mix={radio()} name="shipping-speed" value="standard" />
15
+ Standard
16
+ </label>
17
+ <label>
18
+ <input mix={radio({ size: 'lg' })} name="shipping-speed" value="express" />
19
+ Express
20
+ </label>
21
+ </fieldset>
22
+ )
23
+ }
24
+ ```
25
+
26
+ Compose app-owned styles around the primitive when a radio option needs local layout:
27
+
28
+ ```tsx
29
+ import radio from 'remix/ui/radio'
30
+ import { optionStyle } from './shipping.styles'
31
+
32
+ function ShippingOption() {
33
+ return () => (
34
+ <label mix={optionStyle}>
35
+ <input mix={radio()} name="shipping-speed" value="overnight" />
36
+ Overnight
37
+ </label>
38
+ )
39
+ }
40
+ ```
41
+
42
+ ## `remix/ui/radio`
43
+
44
+ - `radio(options)`: style mixin for native radio inputs or radio-like hosts.
45
+ - `RadioOptions`: accepts `size`.
46
+ - `RadioSize`: `'md'` or `'lg'`. Defaults to `'md'`.
47
+
48
+ ## Behavior Notes
49
+
50
+ - `radio()` returns a mixin descriptor, so it composes with other mixins in the host element's `mix` prop.
51
+ - Native `<input>` hosts receive `type="radio"` unless an explicit `type` is provided.
52
+ - Radio state is native browser behavior. The mixin also styles `aria-checked="true"` and `data-state="checked"` for custom hosts.
53
+ - Disabled hosts use the shared disabled treatment through `disabled` or `aria-disabled="true"`.
@@ -0,0 +1,101 @@
1
+ import { createMixin, css } from '@remix-run/ui'
2
+ import type { CSSMixinDescriptor, ElementProps, MixinDescriptor } from '@remix-run/ui'
3
+ import { renderMixinElement } from '../runtime/mixins/mixin.ts'
4
+ import { controlFocusShadow } from '../shared/focus-styles.ts'
5
+
6
+ export type RadioSize = 'md' | 'lg'
7
+
8
+ export interface RadioOptions {
9
+ size?: RadioSize
10
+ }
11
+
12
+ type RadioMixin = readonly [
13
+ MixinDescriptor<Element, [], ElementProps>,
14
+ CSSMixinDescriptor,
15
+ CSSMixinDescriptor,
16
+ ]
17
+
18
+ const checkedSelector = '&:checked, &[aria-checked="true"], &[data-state="checked"]'
19
+ const checkedMarkSelector =
20
+ '&:checked::before, &[aria-checked="true"]::before, &[data-state="checked"]::before'
21
+
22
+ const radioDefaultAttrs = createMixin<Element, [], ElementProps>((handle, hostType) => (props) => {
23
+ if (hostType !== 'input' || props.type !== undefined) {
24
+ return handle.element
25
+ }
26
+
27
+ return renderMixinElement(handle.element, {
28
+ ...props,
29
+ type: 'radio',
30
+ })
31
+ })()
32
+
33
+ const baseStyle: CSSMixinDescriptor = css({
34
+ appearance: 'none',
35
+ WebkitAppearance: 'none',
36
+ margin: 0,
37
+ boxSizing: 'border-box',
38
+ position: 'relative',
39
+ display: 'inline-grid',
40
+ placeItems: 'center',
41
+ width: 'var(--rmx-radio-size)',
42
+ height: 'var(--rmx-radio-size)',
43
+ minWidth: 'var(--rmx-radio-size)',
44
+ minHeight: 'var(--rmx-radio-size)',
45
+ padding: 0,
46
+ border: '1px solid light-dark(rgba(0, 0, 0, 0.12), rgba(255, 255, 255, 0.2))',
47
+ borderRadius: '9999px',
48
+ background: 'light-dark(#FFFFFF, #1a1a1a)',
49
+ boxShadow: 'inset 0 1px 1px rgba(0, 0, 0, 0.06), inset 0 2px 2px rgba(0, 0, 0, 0.06)',
50
+ verticalAlign: 'middle',
51
+ flex: 'none',
52
+ '&::before': {
53
+ content: '""',
54
+ width: 'var(--rmx-radio-mark-size)',
55
+ height: 'var(--rmx-radio-mark-size)',
56
+ borderRadius: '99px',
57
+ background:
58
+ 'linear-gradient(180deg, rgba(53, 115, 246, 0) 25%, rgba(53, 115, 246, 0.15) 100%), light-dark(#FFFFFF, #f5f9ff)',
59
+ boxShadow:
60
+ '0 1px 1px -0.5px #0944BE, 0 2px 2px -1px #0944BE, 0 4px 4px -2px #0944BE, 0 0 1px 1px light-dark(rgba(0, 0, 0, 0.25), rgba(255, 255, 255, 0.28)), inset 0 0 2px 1px light-dark(#FFFFFF, rgba(255, 255, 255, 0.5))',
61
+ opacity: 0,
62
+ pointerEvents: 'none',
63
+ },
64
+ '&:disabled, &[aria-disabled="true"]': {
65
+ opacity: 0.55,
66
+ },
67
+ [checkedSelector]: {
68
+ border: 0,
69
+ borderRadius: '99px',
70
+ background:
71
+ 'linear-gradient(180deg, light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.16)) 0%, rgba(0, 0, 0, 0) 75.48%), light-dark(#3573F6, #6eaaff)',
72
+ backgroundBlendMode: 'overlay, normal',
73
+ boxShadow:
74
+ '0 1px 0 rgba(255, 255, 255, 0.6), 0 0 16px rgba(53, 115, 246, 0.25), inset 0 0 3px 1px rgba(0, 0, 0, 0.1)',
75
+ },
76
+ [checkedMarkSelector]: {
77
+ opacity: 1,
78
+ },
79
+ '&:focus-visible': {
80
+ outline: 0,
81
+ boxShadow: controlFocusShadow,
82
+ },
83
+ })
84
+
85
+ const sizeStyles = {
86
+ md: css({
87
+ '--rmx-radio-size': '16px',
88
+ '--rmx-radio-mark-size': '8px',
89
+ }),
90
+ lg: css({
91
+ '--rmx-radio-size': '20px',
92
+ '--rmx-radio-mark-size': '10px',
93
+ }),
94
+ } as const satisfies Record<RadioSize, CSSMixinDescriptor>
95
+
96
+ export function radio(options: RadioOptions = {}): RadioMixin {
97
+ let { size = 'md' } = options
98
+ return [radioDefaultAttrs, baseStyle, sizeStyles[size]]
99
+ }
100
+
101
+ export default radio
@@ -0,0 +1,54 @@
1
+ import type { VirtualRoot } from './vdom.ts'
2
+
3
+ export type ClientEntryIdentity = {
4
+ moduleUrl: string
5
+ exportName: string
6
+ }
7
+
8
+ type ClientEntryRoot = Pick<VirtualRoot, 'dispose' | 'render'>
9
+
10
+ export type ClientEntryBoundaryOwner = {
11
+ identity: ClientEntryIdentity
12
+ root: ClientEntryRoot
13
+ }
14
+
15
+ const CLIENT_ENTRY_BOUNDARY_OWNER = Symbol('ClientEntryBoundaryOwner')
16
+
17
+ type ClientEntryBoundaryMarker = Comment & {
18
+ [CLIENT_ENTRY_BOUNDARY_OWNER]?: ClientEntryBoundaryOwner
19
+ $rmx?: ClientEntryRoot
20
+ }
21
+
22
+ export function getClientEntryBoundaryOwner(marker: Comment): ClientEntryBoundaryOwner | undefined {
23
+ return (marker as ClientEntryBoundaryMarker)[CLIENT_ENTRY_BOUNDARY_OWNER]
24
+ }
25
+
26
+ export function setClientEntryBoundaryOwner(
27
+ marker: Comment,
28
+ identity: ClientEntryIdentity,
29
+ root: ClientEntryRoot,
30
+ ): ClientEntryBoundaryOwner {
31
+ let owner = { identity, root }
32
+ Object.defineProperties(marker, {
33
+ [CLIENT_ENTRY_BOUNDARY_OWNER]: {
34
+ configurable: true,
35
+ value: owner,
36
+ },
37
+ $rmx: {
38
+ configurable: true,
39
+ value: root,
40
+ },
41
+ })
42
+ return owner
43
+ }
44
+
45
+ export function disposeClientEntryBoundary(marker: Comment): boolean {
46
+ let boundaryMarker = marker as ClientEntryBoundaryMarker
47
+ let owner = boundaryMarker[CLIENT_ENTRY_BOUNDARY_OWNER]
48
+ if (!owner) return false
49
+
50
+ delete boundaryMarker[CLIENT_ENTRY_BOUNDARY_OWNER]
51
+ delete boundaryMarker.$rmx
52
+ owner.root.dispose()
53
+ return true
54
+ }
@@ -1,4 +1,6 @@
1
1
  import type { ElementProps, ElementType, RemixNode, Renderable } from './jsx.ts'
2
+ import type { ElementFunction } from './element-function.ts'
3
+ import type { Key } from './key.ts'
2
4
  import { TypedEventTarget } from './typed-event-target.ts'
3
5
 
4
6
  /**
@@ -138,6 +140,14 @@ export interface Context<C> {
138
140
  */
139
141
  export type FrameContent = ReadableStream<Uint8Array> | string | RemixNode
140
142
 
143
+ /**
144
+ * Value returned by a browser frame resolver.
145
+ *
146
+ * A response body is rendered as frame content. When `fetch()` followed a redirect, the response's
147
+ * final URL updates the frame source and browser URL for a top-frame navigation.
148
+ */
149
+ export type FrameResolution = FrameContent | Response
150
+
141
151
  /**
142
152
  * Events emitted by frame handles during reloads.
143
153
  */
@@ -208,18 +218,26 @@ export interface BuiltinElements {
208
218
  /**
209
219
  * Key type used to stabilize host elements and components during reconciliation.
210
220
  */
211
- export type Key = string | number | bigint
221
+ export type { Key } from './key.ts'
212
222
 
213
223
  type ComponentConfig = {
214
224
  id: string
215
- type: Function
225
+ type: ElementFunction
216
226
  frame: FrameHandle
217
- getContext: (type: Component) => unknown
227
+ getContext: (type: ElementFunction) => unknown
218
228
  getFrameByName: (name: string) => FrameHandle | undefined
219
229
  getTopFrame?: () => FrameHandle | undefined
220
230
  signal?: AbortSignal
221
231
  }
222
232
 
233
+ /**
234
+ * Minimal structural view of the scheduler used for handle.update() so this
235
+ * module doesn't depend on the reconciler.
236
+ */
237
+ export interface UpdateQueue {
238
+ enqueue(vnode: object, domParent: ParentNode): void
239
+ }
240
+
223
241
  /**
224
242
  * Runtime handle returned by {@link createComponent}.
225
243
  */
@@ -227,7 +245,7 @@ export interface ComponentHandle<C = NoContext> {
227
245
  frame: FrameHandle
228
246
  render(nextProps: ElementProps): [RemixNode, Array<() => void>]
229
247
  remove(): Array<() => void>
230
- setScheduleUpdate(nextScheduleUpdate: () => void): void
248
+ setScheduleUpdate(queue: UpdateQueue, vnode: object, domParent: ParentNode): void
231
249
  getContextValue(): C | undefined
232
250
  isRemoved(): boolean
233
251
  }
@@ -249,12 +267,22 @@ class ComponentRuntime<C = NoContext> implements ComponentHandle<C> {
249
267
  #connectedController: AbortController | undefined
250
268
  #contextValue: C | undefined
251
269
  #handle: Handle<ElementProps, C>
252
- #props = {} as ElementProps
270
+ #props: ElementProps = {}
253
271
  #renderController: AbortController | undefined
254
272
  #renderFn: RenderFn | undefined
255
273
  #removed = false
256
- #scheduleUpdate: () => void = () => {
257
- throw new Error('scheduleUpdate not implemented')
274
+ // The schedule target is stored as fields (updated each render) rather than
275
+ // a closure so re-renders don't allocate a new function per component.
276
+ #updateQueue: UpdateQueue | undefined
277
+ #updateVNode: object | undefined
278
+ #updateDomParent: ParentNode | undefined
279
+ #scheduleUpdate = (): void => {
280
+ let queue = this.#updateQueue
281
+ if (!queue) throw new Error('scheduleUpdate not implemented')
282
+ let vnode = this.#updateVNode
283
+ let domParent = this.#updateDomParent
284
+ if (!vnode || !domParent) throw new Error('scheduleUpdate target not initialized')
285
+ queue.enqueue(vnode, domParent)
258
286
  }
259
287
  #tasks: Task[] = []
260
288
 
@@ -276,14 +304,15 @@ class ComponentRuntime<C = NoContext> implements ComponentHandle<C> {
276
304
  let renderFn = this.#renderFn
277
305
 
278
306
  if (renderFn === undefined) {
279
- let result = this.#config.type(this.#handle)
307
+ let initialize = this.#config.type as unknown as (handle: Handle<ElementProps, C>) => unknown
308
+ let result = initialize(this.#handle)
280
309
 
281
- if (typeof result !== 'function') {
310
+ if (!isRenderFn(result)) {
282
311
  let name = this.#config.type.name || 'Anonymous'
283
312
  throw new Error(`${name} must return a render function, received ${typeof result}`)
284
313
  }
285
314
 
286
- renderFn = result as RenderFn
315
+ renderFn = result
287
316
  this.#renderFn = renderFn
288
317
  }
289
318
 
@@ -291,15 +320,18 @@ class ComponentRuntime<C = NoContext> implements ComponentHandle<C> {
291
320
  }
292
321
 
293
322
  remove = (): Array<() => void> => {
294
- if (this.#removed) return []
323
+ if (this.#removed) return EMPTY_TASKS
295
324
  this.#removed = true
296
325
  this.#connectedController?.abort()
297
326
  this.#abortRenderSignal()
298
- return this.#dequeueTasks(AbortSignal.abort())
327
+ if (this.#tasks.length === 0) return EMPTY_TASKS
328
+ return this.#dequeueTasks((sharedAbortedSignal ??= AbortSignal.abort()))
299
329
  }
300
330
 
301
- setScheduleUpdate = (nextScheduleUpdate: () => void): void => {
302
- this.#scheduleUpdate = nextScheduleUpdate
331
+ setScheduleUpdate = (queue: UpdateQueue, vnode: object, domParent: ParentNode): void => {
332
+ this.#updateQueue = queue
333
+ this.#updateVNode = vnode
334
+ this.#updateDomParent = domParent
303
335
  }
304
336
 
305
337
  getContextValue = (): C | undefined => this.#contextValue
@@ -312,7 +344,8 @@ class ComponentRuntime<C = NoContext> implements ComponentHandle<C> {
312
344
  set: (value: C) => {
313
345
  this.#contextValue = value
314
346
  },
315
- get: (type: ElementType | symbol) => this.#config.getContext(type as Component),
347
+ get: (type: ElementType | symbol) =>
348
+ isElementFunction(type) ? this.#config.getContext(type) : undefined,
316
349
  }
317
350
 
318
351
  return {
@@ -358,6 +391,8 @@ class ComponentRuntime<C = NoContext> implements ComponentHandle<C> {
358
391
  }
359
392
 
360
393
  #dequeueTasks(signal?: AbortSignal): Array<() => void> {
394
+ if (this.#tasks.length === 0) return EMPTY_TASKS
395
+
361
396
  let needsSignal = signal === undefined && this.#tasks.some((task) => task.length >= 1)
362
397
 
363
398
  if (needsSignal) {
@@ -365,11 +400,25 @@ class ComponentRuntime<C = NoContext> implements ComponentHandle<C> {
365
400
  }
366
401
 
367
402
  signal ??= this.#renderController?.signal
403
+ signal ??= sharedAbortedSignal ??= AbortSignal.abort()
368
404
  let tasks = this.#tasks.splice(0, this.#tasks.length)
369
- return tasks.map((task) => () => task(signal!))
405
+ return tasks.map((task) => () => task(signal))
370
406
  }
371
407
  }
372
408
 
409
+ function isRenderFn(value: unknown): value is RenderFn {
410
+ return typeof value === 'function'
411
+ }
412
+
413
+ function isElementFunction(value: unknown): value is ElementFunction {
414
+ return typeof value === 'function'
415
+ }
416
+
417
+ // Shared empty result and aborted signal so the common no-task removal path
418
+ // (large subtree teardowns dispose many components at once) allocates nothing.
419
+ const EMPTY_TASKS: Array<() => void> = []
420
+ let sharedAbortedSignal: AbortSignal | undefined
421
+
373
422
  function syncProps(target: ElementProps, next: ElementProps): void {
374
423
  for (let key in target) {
375
424
  if (!(key in next)) {
@@ -62,10 +62,31 @@ export function canUseProperty(
62
62
  return name in element
63
63
  }
64
64
 
65
+ // Prop names repeat constantly across elements and renders; cache the
66
+ // normalized results so the string work and object allocation happen once
67
+ // per distinct name. Entries are shared — callers must not mutate them.
68
+ const NORMALIZATION_CACHE_LIMIT = 256
69
+ const htmlAttributeNameCache = new Map<string, { ns?: string; attr: string }>()
70
+ const svgAttributeNameCache = new Map<string, { ns?: string; attr: string }>()
71
+
65
72
  export function normalizeAttributeName(
66
73
  name: string,
67
74
  isSvg: boolean,
68
75
  ): { ns?: string; attr: string } {
76
+ let cache = isSvg ? svgAttributeNameCache : htmlAttributeNameCache
77
+ let cached = cache.get(name)
78
+ if (cached === undefined) {
79
+ cached = computeAttributeName(name, isSvg)
80
+ if (cache.size >= NORMALIZATION_CACHE_LIMIT) {
81
+ let oldest = cache.keys().next()
82
+ if (!oldest.done) cache.delete(oldest.value)
83
+ }
84
+ cache.set(name, cached)
85
+ }
86
+ return cached
87
+ }
88
+
89
+ function computeAttributeName(name: string, isSvg: boolean): { ns?: string; attr: string } {
69
90
  if (name.startsWith('aria-') || name.startsWith('data-')) return { attr: name }
70
91
 
71
92
  if (name === 'className') return { attr: 'class' }
@@ -113,6 +134,19 @@ export function getMergedClassName(props: ElementProps): string | undefined {
113
134
  return merged || undefined
114
135
  }
115
136
 
137
+ // Style/attribute names come from a small recurring set, so cache conversions
138
+ // instead of running a regex per property per element per render.
139
+ const kebabCaseCache = new Map<string, string>()
140
+
116
141
  export function toKebabCase(value: string): string {
117
- return value.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`)
142
+ let cached = kebabCaseCache.get(value)
143
+ if (cached === undefined) {
144
+ cached = value.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`)
145
+ if (kebabCaseCache.size >= NORMALIZATION_CACHE_LIMIT) {
146
+ let oldest = kebabCaseCache.keys().next()
147
+ if (!oldest.done) kebabCaseCache.delete(oldest.value)
148
+ }
149
+ kebabCaseCache.set(value, cached)
150
+ }
151
+ return cached
118
152
  }
@@ -1,4 +1,6 @@
1
1
  import type { RemixNode } from '../jsx.ts'
2
+ import { invariant } from '../invariant.ts'
3
+ import { isRemixElement } from './vnode.ts'
2
4
 
3
5
  export type EmptyChild = boolean | null | undefined
4
6
  export type PrimitiveChild = string | number | bigint
@@ -13,8 +15,8 @@ export function isPrimitiveChild(value: RemixNode): value is PrimitiveChild {
13
15
  }
14
16
 
15
17
  export function normalizeChildren(children: readonly RemixNode[]): RemixNode[] {
16
- for (let child of children) {
17
- if (Array.isArray(child)) {
18
+ for (let i = 0; i < children.length; i++) {
19
+ if (Array.isArray(children[i])) {
18
20
  return (children as unknown[]).flat(Infinity) as RemixNode[]
19
21
  }
20
22
  }
@@ -22,6 +24,26 @@ export function normalizeChildren(children: readonly RemixNode[]): RemixNode[] {
22
24
  return children as RemixNode[]
23
25
  }
24
26
 
27
+ export function assertRemixNodes(values: readonly unknown[]): asserts values is RemixNode[] {
28
+ for (let value of values) {
29
+ invariant(isRemixNode(value), 'Invalid child node')
30
+ }
31
+ }
32
+
33
+ export function isRemixNode(value: unknown): value is RemixNode {
34
+ if (value == null) return true
35
+ let type = typeof value
36
+ if (type === 'string' || type === 'number' || type === 'bigint' || type === 'boolean') {
37
+ return true
38
+ }
39
+ if (isRemixElement(value)) return true
40
+ if (!Array.isArray(value)) return false
41
+ for (let child of value) {
42
+ if (!isRemixNode(child)) return false
43
+ }
44
+ return true
45
+ }
46
+
25
47
  export function packChildren(children: readonly RemixNode[]): RemixNode | undefined {
26
48
  if (children.length === 0) {
27
49
  return undefined
@@ -1,9 +1,9 @@
1
- import type { ElementProps, ElementType, RemixElement, RemixNode } from '../jsx.ts'
1
+ import type { ElementProps, ElementType, RemixElement } from '../jsx.ts'
2
2
 
3
3
  export function createRemixElement(
4
4
  type: ElementType,
5
5
  props: ElementProps | null | undefined,
6
- key?: string,
6
+ key?: any,
7
7
  ): RemixElement {
8
8
  return {
9
9
  $rmx: true,
@@ -13,19 +13,40 @@ export function createRemixElement(
13
13
  }
14
14
  }
15
15
 
16
- export function isRemixElement(node: RemixNode): node is RemixElement {
17
- return typeof node === 'object' && node !== null && '$rmx' in node
16
+ export function isRemixElement(node: unknown): node is RemixElement {
17
+ if (typeof node !== 'object' || node === null) return false
18
+ let type = Reflect.get(node, 'type')
19
+ let props = Reflect.get(node, 'props')
20
+ return (
21
+ Reflect.get(node, '$rmx') === true &&
22
+ (typeof type === 'string' || typeof type === 'function') &&
23
+ typeof props === 'object' &&
24
+ props !== null
25
+ )
18
26
  }
19
27
 
20
28
  function normalizeElementProps(props: ElementProps | null | undefined): ElementProps {
21
29
  if (!props) return {}
22
30
  if (!('mix' in props)) return props
23
31
 
32
+ // Fast path: `mix={[a, b]}` with flat, truthy entries is already normalized.
33
+ // Skipping the copy avoids two prop-object spreads per element per render.
34
+ if (isNormalizedMix(props.mix)) return props
35
+
24
36
  let { mix, ...rest } = props
25
37
  let normalizedMix = normalizeMixValue(mix)
26
38
  return normalizedMix === undefined ? rest : { ...rest, mix: normalizedMix }
27
39
  }
28
40
 
41
+ function isNormalizedMix(mix: unknown): boolean {
42
+ if (!Array.isArray(mix) || mix.length === 0) return false
43
+ for (let i = 0; i < mix.length; i++) {
44
+ let item = mix[i]
45
+ if (!item || Array.isArray(item)) return false
46
+ }
47
+ return true
48
+ }
49
+
29
50
  function normalizeMixValue(mix: unknown): unknown[] | undefined {
30
51
  if (!mix) return undefined
31
52
 
@@ -1,6 +1,6 @@
1
1
  import { jsx } from './jsx.ts'
2
- import type { ElementType, RemixElement } from './jsx.ts'
3
- import { normalizeChildren } from './core/children.ts'
2
+ import type { ElementProps, ElementType, RemixElement } from './jsx.ts'
3
+ import { assertRemixNodes, normalizeChildren } from './core/children.ts'
4
4
 
5
5
  /**
6
6
  * Creates a Remix virtual element from a JSX-like call signature.
@@ -12,8 +12,8 @@ import { normalizeChildren } from './core/children.ts'
12
12
  */
13
13
  export function createElement(
14
14
  type: ElementType,
15
- props?: Record<string, any>,
16
- ...children: any[]
15
+ props?: ElementProps,
16
+ ...children: unknown[]
17
17
  ): RemixElement {
18
18
  let nextProps = { ...(props ?? {}) }
19
19
  if (isMixinElementType(type)) {
@@ -21,6 +21,7 @@ export function createElement(
21
21
  console.error(new Error('mixin elements must not receive children'))
22
22
  }
23
23
  } else {
24
+ assertRemixNodes(children)
24
25
  nextProps.children = normalizeChildren(children)
25
26
  }
26
27
 
@@ -34,6 +35,6 @@ export function createElement(
34
35
 
35
36
  function isMixinElementType(
36
37
  type: ElementType,
37
- ): type is ((...args: unknown[]) => unknown) & { __rmxMixinElementType: string } {
38
+ ): type is ElementType & { __rmxMixinElementType: string } {
38
39
  return typeof type === 'function' && '__rmxMixinElementType' in type
39
40
  }