@remix-run/ui 0.1.1 → 0.2.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 (243) hide show
  1. package/README.md +33 -10
  2. package/dist/animation/animate-layout-mixin.js +66 -66
  3. package/dist/animation/animate-layout-mixin.js.map +1 -1
  4. package/dist/animation/spring.d.ts +6 -35
  5. package/dist/animation/spring.js +9 -8
  6. package/dist/animation/spring.js.map +1 -1
  7. package/dist/components/accordion/accordion.d.ts +7 -7
  8. package/dist/components/accordion/accordion.js +0 -2
  9. package/dist/components/accordion/accordion.js.map +1 -1
  10. package/dist/components/accordion/demos/card.demo.d.ts +6 -0
  11. package/dist/components/accordion/demos/card.demo.js +58 -0
  12. package/dist/components/accordion/demos/card.demo.js.map +1 -0
  13. package/dist/components/accordion/demos/multiple.demo.d.ts +6 -0
  14. package/dist/components/accordion/demos/multiple.demo.js +19 -0
  15. package/dist/components/accordion/demos/multiple.demo.js.map +1 -0
  16. package/dist/components/accordion/demos/overview.demo.d.ts +6 -0
  17. package/dist/components/accordion/demos/overview.demo.js +19 -0
  18. package/dist/components/accordion/demos/overview.demo.js.map +1 -0
  19. package/dist/components/breadcrumbs/breadcrumbs.d.ts +1 -1
  20. package/dist/components/breadcrumbs/breadcrumbs.js +0 -2
  21. package/dist/components/breadcrumbs/breadcrumbs.js.map +1 -1
  22. package/dist/components/breadcrumbs/demos/basic.demo.d.ts +5 -0
  23. package/dist/components/breadcrumbs/demos/basic.demo.js +14 -0
  24. package/dist/components/breadcrumbs/demos/basic.demo.js.map +1 -0
  25. package/dist/components/breadcrumbs/demos/separator.demo.d.ts +5 -0
  26. package/dist/components/breadcrumbs/demos/separator.demo.js +14 -0
  27. package/dist/components/breadcrumbs/demos/separator.demo.js.map +1 -0
  28. package/dist/components/button/button.d.ts +86 -4
  29. package/dist/components/button/button.js +63 -3
  30. package/dist/components/button/button.js.map +1 -1
  31. package/dist/components/button/demos/aliases.demo.d.ts +6 -0
  32. package/dist/components/button/demos/aliases.demo.js +18 -0
  33. package/dist/components/button/demos/aliases.demo.js.map +1 -0
  34. package/dist/components/button/demos/basic.demo.d.ts +6 -0
  35. package/dist/components/button/demos/basic.demo.js +19 -0
  36. package/dist/components/button/demos/basic.demo.js.map +1 -0
  37. package/dist/components/button/demos/states.demo.d.ts +6 -0
  38. package/dist/components/button/demos/states.demo.js +29 -0
  39. package/dist/components/button/demos/states.demo.js.map +1 -0
  40. package/dist/components/combobox/combobox.d.ts +11 -11
  41. package/dist/components/combobox/combobox.js +0 -2
  42. package/dist/components/combobox/combobox.js.map +1 -1
  43. package/dist/components/combobox/demos/overview.demo.d.ts +6 -0
  44. package/dist/components/combobox/demos/overview.demo.js +153 -0
  45. package/dist/components/combobox/demos/overview.demo.js.map +1 -0
  46. package/dist/components/listbox/demos/overview.demo.d.ts +6 -0
  47. package/dist/components/listbox/demos/overview.demo.js +64 -0
  48. package/dist/components/listbox/demos/overview.demo.js.map +1 -0
  49. package/dist/components/listbox/listbox.d.ts +4 -9
  50. package/dist/components/listbox/listbox.js +21 -33
  51. package/dist/components/listbox/listbox.js.map +1 -1
  52. package/dist/components/menu/demos/bubbling.demo.d.ts +5 -0
  53. package/dist/components/menu/demos/bubbling.demo.js +15 -0
  54. package/dist/components/menu/demos/bubbling.demo.js.map +1 -0
  55. package/dist/components/menu/demos/overview.demo.d.ts +6 -0
  56. package/dist/components/menu/demos/overview.demo.js +32 -0
  57. package/dist/components/menu/demos/overview.demo.js.map +1 -0
  58. package/dist/components/menu/menu.d.ts +15 -24
  59. package/dist/components/menu/menu.js +23 -38
  60. package/dist/components/menu/menu.js.map +1 -1
  61. package/dist/components/popover/demos/overview.demo.d.ts +6 -0
  62. package/dist/components/popover/demos/overview.demo.js +64 -0
  63. package/dist/components/popover/demos/overview.demo.js.map +1 -0
  64. package/dist/components/popover/popover.d.ts +5 -5
  65. package/dist/components/popover/popover.js.map +1 -1
  66. package/dist/components/select/demos/deconstructed.demo.d.ts +6 -0
  67. package/dist/components/select/demos/deconstructed.demo.js +53 -0
  68. package/dist/components/select/demos/deconstructed.demo.js.map +1 -0
  69. package/dist/components/select/demos/overview.demo.d.ts +5 -0
  70. package/dist/components/select/demos/overview.demo.js +27 -0
  71. package/dist/components/select/demos/overview.demo.js.map +1 -0
  72. package/dist/components/select/select.d.ts +11 -11
  73. package/dist/components/select/select.js +17 -26
  74. package/dist/components/select/select.js.map +1 -1
  75. package/dist/components/tabs/tabs.d.ts +6 -6
  76. package/dist/components/tabs/tabs.js +0 -2
  77. package/dist/components/tabs/tabs.js.map +1 -1
  78. package/dist/index.d.ts +2 -2
  79. package/dist/index.js.map +1 -1
  80. package/dist/interactions/keydown/keydown.d.ts +1 -1
  81. package/dist/interactions/outside-click/outside-click-mixin.d.ts +2 -1
  82. package/dist/interactions/typeahead/typeahead-mixin.d.ts +3 -1
  83. package/dist/runtime/component.d.ts +18 -12
  84. package/dist/runtime/component.js +93 -80
  85. package/dist/runtime/component.js.map +1 -1
  86. package/dist/runtime/core/attributes.d.ts +14 -0
  87. package/dist/runtime/core/attributes.js +103 -0
  88. package/dist/runtime/core/attributes.js.map +1 -0
  89. package/dist/runtime/core/children.d.ts +7 -0
  90. package/dist/runtime/core/children.js +30 -0
  91. package/dist/runtime/core/children.js.map +1 -0
  92. package/dist/runtime/core/props.d.ts +9 -0
  93. package/dist/runtime/core/props.js +170 -0
  94. package/dist/runtime/core/props.js.map +1 -0
  95. package/dist/runtime/core/vnode.d.ts +3 -0
  96. package/dist/runtime/core/vnode.js +39 -0
  97. package/dist/runtime/core/vnode.js.map +1 -0
  98. package/dist/runtime/create-element.js +2 -1
  99. package/dist/runtime/create-element.js.map +1 -1
  100. package/dist/runtime/diff-dom.js +47 -14
  101. package/dist/runtime/diff-dom.js.map +1 -1
  102. package/dist/runtime/diff-props.d.ts +1 -2
  103. package/dist/runtime/diff-props.js +2 -181
  104. package/dist/runtime/diff-props.js.map +1 -1
  105. package/dist/runtime/dom.d.ts +20 -9
  106. package/dist/runtime/frame.d.ts +23 -2
  107. package/dist/runtime/frame.js +41 -47
  108. package/dist/runtime/frame.js.map +1 -1
  109. package/dist/runtime/jsx.d.ts +1 -1
  110. package/dist/runtime/jsx.js +2 -28
  111. package/dist/runtime/jsx.js.map +1 -1
  112. package/dist/runtime/mixins/link-mixin.d.ts +5 -1
  113. package/dist/runtime/mixins/link-mixin.js.map +1 -1
  114. package/dist/runtime/mixins/mixin.d.ts +2 -1
  115. package/dist/runtime/mixins/mixin.js +9 -4
  116. package/dist/runtime/mixins/mixin.js.map +1 -1
  117. package/dist/runtime/mixins/on-mixin.d.ts +15 -4
  118. package/dist/runtime/mixins/on-mixin.js +9 -2
  119. package/dist/runtime/mixins/on-mixin.js.map +1 -1
  120. package/dist/runtime/mixins/ref-mixin.d.ts +2 -1
  121. package/dist/runtime/mixins/ref-mixin.js.map +1 -1
  122. package/dist/runtime/navigation.js +9 -1
  123. package/dist/runtime/navigation.js.map +1 -1
  124. package/dist/runtime/reconcile.d.ts +1 -1
  125. package/dist/runtime/reconcile.js +452 -197
  126. package/dist/runtime/reconcile.js.map +1 -1
  127. package/dist/runtime/run.d.ts +17 -2
  128. package/dist/runtime/run.js.map +1 -1
  129. package/dist/runtime/scheduler.d.ts +10 -10
  130. package/dist/runtime/scheduler.js +1 -1
  131. package/dist/runtime/scheduler.js.map +1 -1
  132. package/dist/runtime/stream-protocol.d.ts +8 -0
  133. package/dist/runtime/stream-protocol.js +20 -0
  134. package/dist/runtime/stream-protocol.js.map +1 -0
  135. package/dist/runtime/svg-attributes.js +1 -0
  136. package/dist/runtime/svg-attributes.js.map +1 -1
  137. package/dist/runtime/to-vnode.js +7 -11
  138. package/dist/runtime/to-vnode.js.map +1 -1
  139. package/dist/runtime/vnode.d.ts +11 -5
  140. package/dist/runtime/vnode.js +6 -3
  141. package/dist/runtime/vnode.js.map +1 -1
  142. package/dist/server/stream.d.ts +2 -0
  143. package/dist/server/stream.js +154 -114
  144. package/dist/server/stream.js.map +1 -1
  145. package/dist/style/css-mixin.d.ts +2 -1
  146. package/dist/style/css-mixin.js.map +1 -1
  147. package/dist/style/index.d.ts +1 -1
  148. package/dist/style/layers.d.ts +2 -0
  149. package/dist/style/layers.js +3 -0
  150. package/dist/style/layers.js.map +1 -0
  151. package/dist/style/stylesheet.d.ts +13 -11
  152. package/dist/style/stylesheet.js +69 -8
  153. package/dist/style/stylesheet.js.map +1 -1
  154. package/dist/theme/contract.d.ts +24 -222
  155. package/dist/theme/contract.js +2 -2
  156. package/dist/theme/contract.js.map +1 -1
  157. package/dist/theme/glyph-contract.d.ts +1 -3
  158. package/dist/theme/glyph-contract.js.map +1 -1
  159. package/dist/theme/presets/rmx-01/index.d.ts +4 -2
  160. package/dist/theme/presets/rmx-01/index.js.map +1 -1
  161. package/dist/theme/runtime.js +4 -1
  162. package/dist/theme/runtime.js.map +1 -1
  163. package/dist/utils/scroll-lock.d.ts +3 -3
  164. package/dist/utils/scroll-lock.js.map +1 -1
  165. package/package.json +3 -3
  166. package/src/animation/README.md +316 -0
  167. package/src/animation/animate-layout-mixin.ts +79 -71
  168. package/src/animation/spring.ts +21 -9
  169. package/src/components/accordion/README.md +166 -0
  170. package/src/components/accordion/accordion.tsx +14 -14
  171. package/src/components/accordion/demos/card.demo.tsx +113 -0
  172. package/src/components/accordion/demos/multiple.demo.tsx +56 -0
  173. package/src/components/accordion/demos/overview.demo.tsx +56 -0
  174. package/src/components/anchor/README.md +153 -0
  175. package/src/components/anchor/anchor.ts +1 -1
  176. package/src/components/breadcrumbs/README.md +55 -0
  177. package/src/components/breadcrumbs/breadcrumbs.tsx +1 -3
  178. package/src/components/breadcrumbs/demos/basic.demo.tsx +17 -0
  179. package/src/components/breadcrumbs/demos/separator.demo.tsx +18 -0
  180. package/src/components/button/README.md +4 -4
  181. package/src/components/button/button.tsx +118 -20
  182. package/src/components/button/demos/aliases.demo.tsx +27 -0
  183. package/src/components/button/demos/basic.demo.tsx +30 -0
  184. package/src/components/button/demos/states.demo.tsx +52 -0
  185. package/src/components/combobox/README.md +1 -1
  186. package/src/components/combobox/combobox.tsx +37 -21
  187. package/src/components/combobox/demos/overview.demo.tsx +199 -0
  188. package/src/components/glyph/README.md +72 -0
  189. package/src/components/glyph/glyph.tsx +2 -2
  190. package/src/components/listbox/README.md +115 -0
  191. package/src/components/listbox/demos/overview.demo.tsx +101 -0
  192. package/src/components/listbox/listbox.ts +106 -110
  193. package/src/components/menu/README.md +96 -0
  194. package/src/components/menu/demos/bubbling.demo.tsx +34 -0
  195. package/src/components/menu/demos/overview.demo.tsx +76 -0
  196. package/src/components/menu/menu.tsx +61 -55
  197. package/src/components/popover/README.md +3 -3
  198. package/src/components/popover/demos/overview.demo.tsx +143 -0
  199. package/src/components/popover/popover.ts +90 -79
  200. package/src/components/select/README.md +107 -0
  201. package/src/components/select/demos/deconstructed.demo.tsx +95 -0
  202. package/src/components/select/demos/overview.demo.tsx +51 -0
  203. package/src/components/select/select.tsx +53 -36
  204. package/src/components/tabs/tabs.tsx +10 -5
  205. package/src/index.ts +2 -1
  206. package/src/runtime/component.ts +122 -88
  207. package/src/runtime/core/attributes.ts +118 -0
  208. package/src/runtime/core/children.ts +42 -0
  209. package/src/runtime/core/props.ts +187 -0
  210. package/src/runtime/core/vnode.ts +48 -0
  211. package/src/runtime/create-element.ts +2 -1
  212. package/src/runtime/diff-dom.ts +59 -12
  213. package/src/runtime/diff-props.ts +2 -187
  214. package/src/runtime/dom.ts +20 -9
  215. package/src/runtime/frame.ts +71 -55
  216. package/src/runtime/jsx.ts +3 -30
  217. package/src/runtime/mixins/link-mixin.ts +88 -86
  218. package/src/runtime/mixins/mixin.ts +18 -5
  219. package/src/runtime/mixins/on-mixin.ts +20 -12
  220. package/src/runtime/mixins/ref-mixin.ts +4 -5
  221. package/src/runtime/navigation.ts +10 -1
  222. package/src/runtime/reconcile.ts +563 -223
  223. package/src/runtime/run.ts +17 -1
  224. package/src/runtime/scheduler.ts +20 -20
  225. package/src/runtime/stream-protocol.ts +27 -0
  226. package/src/runtime/svg-attributes.ts +1 -0
  227. package/src/runtime/to-vnode.ts +7 -10
  228. package/src/runtime/vnode.ts +16 -9
  229. package/src/server/README.md +90 -0
  230. package/src/server/stream.ts +176 -124
  231. package/src/style/css-mixin.ts +6 -1
  232. package/src/style/index.ts +1 -2
  233. package/src/style/layers.ts +2 -0
  234. package/src/style/stylesheet.ts +88 -11
  235. package/src/test/README.md +107 -0
  236. package/src/test/utils.ts +3 -1
  237. package/src/theme/README.md +103 -0
  238. package/src/theme/contract.ts +37 -5
  239. package/src/theme/glyph-contract.ts +3 -1
  240. package/src/theme/presets/rmx-01/index.ts +4 -2
  241. package/src/theme/runtime.ts +4 -1
  242. package/src/utils/scroll-lock/README.md +33 -0
  243. package/src/utils/scroll-lock.ts +7 -3
@@ -1,200 +1,15 @@
1
1
  import { invariant } from './invariant.ts'
2
- import { createStyleManager, normalizeCssValue } from '../style/index.ts'
2
+ import { createStyleManager } from '../style/index.ts'
3
3
  import type { StyleManager } from '../style/index.ts'
4
- import type { ElementProps } from './jsx.ts'
5
- import { normalizeSvgAttribute } from './svg-attributes.ts'
6
-
7
- const SVG_NS = 'http://www.w3.org/2000/svg'
8
4
 
9
5
  let globalStyleManager =
10
6
  typeof window !== 'undefined' ? createStyleManager() : (null as unknown as StyleManager)
11
7
 
12
8
  export { type StyleManager }
9
+ export { patchHostProps as diffHostProps } from './core/props.ts'
13
10
 
14
11
  export let defaultStyleManager: StyleManager = globalStyleManager
15
12
 
16
- // Preact excludes certain attributes from the property path due to browser quirks
17
- const ATTRIBUTE_FALLBACK_NAMES = new Set([
18
- 'width',
19
- 'height',
20
- 'href',
21
- 'list',
22
- 'form',
23
- 'tabIndex',
24
- 'download',
25
- 'rowSpan',
26
- 'colSpan',
27
- 'role',
28
- 'popover',
29
- ])
30
-
31
- // Determine if we should use the property path for a given name.
32
- // Also acts as a type guard to allow bracket assignment without casts.
33
- function canUseProperty(
34
- dom: Element,
35
- name: string,
36
- isSvg: boolean,
37
- ): dom is Element & Record<string, unknown> {
38
- if (isSvg) return false
39
- if (ATTRIBUTE_FALLBACK_NAMES.has(name)) return false
40
- return name in dom
41
- }
42
-
43
- function isFrameworkProp(name: string): boolean {
44
- return (
45
- name === 'children' ||
46
- name === 'mix' ||
47
- name === 'key' ||
48
- name === 'animate' ||
49
- name === 'innerHTML'
50
- )
51
- }
52
-
53
- // TODO: would rather actually diff el.style object directly instead of writing
54
- // to the style attribute
55
- function serializeStyleObject(style: Record<string, unknown>): string {
56
- let parts: string[] = []
57
- for (let [key, value] of Object.entries(style)) {
58
- if (value == null) continue
59
- if (typeof value === 'boolean') continue
60
- if (typeof value === 'number' && !Number.isFinite(value)) continue
61
-
62
- let cssKey = key.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`)
63
-
64
- let cssValue = Array.isArray(value)
65
- ? (value as unknown[]).join(', ')
66
- : normalizeCssValue(key, value)
67
-
68
- parts.push(`${cssKey}: ${cssValue};`)
69
- }
70
- return parts.join(' ')
71
- }
72
-
73
- function normalizePropName(name: string, isSvg: boolean): { ns?: string; attr: string } {
74
- // aria-/data- pass through
75
- if (name.startsWith('aria-') || name.startsWith('data-')) return { attr: name }
76
-
77
- // DOM property -> HTML mappings
78
- if (name === 'className') return { attr: 'class' }
79
- if (!isSvg) {
80
- if (name === 'htmlFor') return { attr: 'for' }
81
- if (name === 'tabIndex') return { attr: 'tabindex' }
82
- if (name === 'acceptCharset') return { attr: 'accept-charset' }
83
- if (name === 'httpEquiv') return { attr: 'http-equiv' }
84
- return { attr: name.toLowerCase() }
85
- }
86
-
87
- return normalizeSvgAttribute(name)
88
- }
89
-
90
- function toLocalName(attrName: string): string {
91
- let separatorIndex = attrName.indexOf(':')
92
- if (separatorIndex === -1) return attrName
93
- return attrName.slice(separatorIndex + 1)
94
- }
95
-
96
- function clearRuntimePropertyOnRemoval(dom: Element & Record<string, unknown>, name: string): void {
97
- try {
98
- if (name === 'value' || name === 'defaultValue') {
99
- dom[name] = ''
100
- return
101
- }
102
- if (name === 'checked' || name === 'defaultChecked' || name === 'selected') {
103
- dom[name] = false
104
- return
105
- }
106
- if (name === 'selectedIndex') {
107
- dom[name] = -1
108
- }
109
- } catch {}
110
- }
111
-
112
- function getMergedClassName(props: ElementProps): string | undefined {
113
- let classAttr = typeof props.class === 'string' ? props.class : ''
114
- let className = typeof props.className === 'string' ? props.className : ''
115
- let mergedClassName =
116
- classAttr && className ? `${classAttr} ${className}` : classAttr || className
117
- return mergedClassName || undefined
118
- }
119
-
120
- export function diffHostProps(curr: ElementProps, next: ElementProps, dom: Element) {
121
- let isSvg = dom.namespaceURI === SVG_NS
122
- let currClassName = getMergedClassName(curr)
123
- let nextClassName = getMergedClassName(next)
124
-
125
- if (currClassName !== nextClassName) {
126
- if (nextClassName) {
127
- dom.setAttribute('class', nextClassName)
128
- } else {
129
- dom.removeAttribute('class')
130
- }
131
- }
132
-
133
- // Removals
134
- for (let name in curr) {
135
- if (isFrameworkProp(name)) continue
136
- if (name === 'class' || name === 'className') continue
137
- if (!(name in next) || next[name] == null) {
138
- // Clear runtime state for form-like props where removing the attribute is not enough.
139
- if (canUseProperty(dom, name, isSvg)) {
140
- clearRuntimePropertyOnRemoval(dom, name)
141
- }
142
-
143
- let { ns, attr } = normalizePropName(name, isSvg)
144
- if (ns) dom.removeAttributeNS(ns, toLocalName(attr))
145
- else dom.removeAttribute(attr)
146
- }
147
- }
148
-
149
- // Additions/updates
150
- for (let name in next) {
151
- if (isFrameworkProp(name)) continue
152
- if (name === 'class' || name === 'className') continue
153
- let nextValue = next[name]
154
- if (nextValue == null) continue
155
- let prevValue = curr[name]
156
- if (prevValue !== nextValue) {
157
- let { ns, attr } = normalizePropName(name, isSvg)
158
-
159
- // Object style: serialize to attribute for now
160
- if (
161
- attr === 'style' &&
162
- typeof nextValue === 'object' &&
163
- nextValue &&
164
- !Array.isArray(nextValue)
165
- ) {
166
- dom.setAttribute('style', serializeStyleObject(nextValue))
167
- continue
168
- }
169
-
170
- // Prefer property assignment when possible (HTML only, not SVG)
171
- if (canUseProperty(dom, name, isSvg)) {
172
- try {
173
- dom[name] = nextValue == null ? '' : nextValue
174
- continue
175
- } catch {}
176
- }
177
-
178
- // Attribute path
179
- if (typeof nextValue === 'function') {
180
- // Never serialize functions as attribute values
181
- continue
182
- }
183
-
184
- let isAriaOrData = name.startsWith('aria-') || name.startsWith('data-')
185
- if (nextValue != null && (nextValue !== false || isAriaOrData)) {
186
- // Special-case popover: true => presence only
187
- let attrValue = name === 'popover' && nextValue === true ? '' : String(nextValue)
188
- if (ns) dom.setAttributeNS(ns, attr, attrValue)
189
- else dom.setAttribute(attr, attrValue)
190
- } else {
191
- if (ns) dom.removeAttributeNS(ns, toLocalName(attr))
192
- else dom.removeAttribute(attr)
193
- }
194
- }
195
- }
196
- }
197
-
198
13
  /**
199
14
  * Reset the global style state. For testing only - not exported from index.ts.
200
15
  */
@@ -9,6 +9,7 @@ import type { MixInput } from './mixins/mixin.ts'
9
9
  * - Copyright (c) 2015-present Jason Miller
10
10
  */
11
11
  type Booleanish = boolean | 'true' | 'false'
12
+ type TranslateValue = 'yes' | 'no'
12
13
 
13
14
  /**
14
15
  * Layout animation configuration for FLIP-based position animations.
@@ -109,6 +110,8 @@ export interface SVGProps<eventTarget extends EventTarget = SVGElement>
109
110
  attributeName?: Trackable<string | undefined>
110
111
  /** The `attributeType` SVG attribute. */
111
112
  attributeType?: Trackable<string | undefined>
113
+ /** The `autoReverse` SVG attribute. */
114
+ autoReverse?: Trackable<Booleanish | undefined>
112
115
  /** The `azimuth` SVG attribute. */
113
116
  azimuth?: Trackable<number | string | undefined>
114
117
  /** The `baseFrequency` SVG attribute. */
@@ -212,7 +215,7 @@ export interface SVGProps<eventTarget extends EventTarget = SVGElement>
212
215
  /** The `exponent` SVG attribute. */
213
216
  exponent?: Trackable<number | string | undefined>
214
217
  /** The `externalResourcesRequired` SVG attribute. */
215
- externalResourcesRequired?: Trackable<number | string | undefined>
218
+ externalResourcesRequired?: Trackable<Booleanish | undefined>
216
219
  /** The `fill` SVG attribute. */
217
220
  fill?: Trackable<string | undefined>
218
221
  /** The `fillOpacity` SVG attribute. */
@@ -238,7 +241,7 @@ export interface SVGProps<eventTarget extends EventTarget = SVGElement>
238
241
  /** The `flood-opacity` SVG attribute. */
239
242
  'flood-opacity'?: Trackable<number | string | undefined>
240
243
  /** The `focusable` SVG attribute. */
241
- focusable?: Trackable<number | string | undefined>
244
+ focusable?: Trackable<Booleanish | undefined>
242
245
  /** The `fontFamily` SVG attribute. */
243
246
  fontFamily?: Trackable<string | undefined>
244
247
  /** The `font-family` SVG attribute. */
@@ -446,7 +449,7 @@ export interface SVGProps<eventTarget extends EventTarget = SVGElement>
446
449
  /** The `pointsAtZ` SVG attribute. */
447
450
  pointsAtZ?: Trackable<number | string | undefined>
448
451
  /** The `preserveAlpha` SVG attribute. */
449
- preserveAlpha?: Trackable<number | string | undefined>
452
+ preserveAlpha?: Trackable<Booleanish | undefined>
450
453
  /** The `preserveAspectRatio` SVG attribute. */
451
454
  preserveAspectRatio?: Trackable<string | undefined>
452
455
  /** The `primitiveUnits` SVG attribute. */
@@ -1263,7 +1266,7 @@ export interface AllHTMLProps<eventTarget extends EventTarget = EventTarget>
1263
1266
  /** The `decoding` HTML attribute. */
1264
1267
  decoding?: Trackable<'sync' | 'async' | 'auto' | undefined>
1265
1268
  /** The `draggable` HTML attribute. */
1266
- draggable?: Trackable<boolean | undefined>
1269
+ draggable?: Trackable<Booleanish | undefined>
1267
1270
  /** The `encType` HTML attribute. */
1268
1271
  encType?: Trackable<string | undefined>
1269
1272
  /** The `enctype` HTML attribute. */
@@ -1487,7 +1490,9 @@ export interface AllHTMLProps<eventTarget extends EventTarget = EventTarget>
1487
1490
  /** The `span` HTML attribute. */
1488
1491
  span?: Trackable<number | undefined>
1489
1492
  /** The `spellcheck` HTML attribute. */
1490
- spellcheck?: Trackable<boolean | undefined>
1493
+ spellcheck?: Trackable<Booleanish | undefined>
1494
+ /** The `spellCheck` HTML attribute. */
1495
+ spellCheck?: Trackable<Booleanish | undefined>
1491
1496
  /** The `src` HTML attribute. */
1492
1497
  src?: Trackable<string | undefined>
1493
1498
  /** The `srcDoc` HTML attribute. */
@@ -1551,7 +1556,7 @@ export interface AllHTMLProps<eventTarget extends EventTarget = EventTarget>
1551
1556
  /** The `results` HTML attribute. */
1552
1557
  results?: Trackable<number | undefined>
1553
1558
  /** The `translate` HTML attribute. */
1554
- translate?: Trackable<boolean | undefined>
1559
+ translate?: Trackable<TranslateValue | undefined>
1555
1560
 
1556
1561
  // RDFa Attributes
1557
1562
  /** The `about` HTML attribute. */
@@ -1632,7 +1637,7 @@ export interface HTMLProps<eventTarget extends EventTarget = EventTarget>
1632
1637
  /** The `dir` HTML attribute. */
1633
1638
  dir?: Trackable<'auto' | 'rtl' | 'ltr' | undefined>
1634
1639
  /** The `draggable` HTML attribute. */
1635
- draggable?: Trackable<boolean | undefined>
1640
+ draggable?: Trackable<Booleanish | undefined>
1636
1641
  /** The `enterkeyhint` HTML attribute. */
1637
1642
  enterkeyhint?: Trackable<
1638
1643
  'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send' | undefined
@@ -1662,7 +1667,9 @@ export interface HTMLProps<eventTarget extends EventTarget = EventTarget>
1662
1667
  /** The `slot` HTML attribute. */
1663
1668
  slot?: Trackable<string | undefined>
1664
1669
  /** The `spellcheck` HTML attribute. */
1665
- spellcheck?: Trackable<boolean | undefined>
1670
+ spellcheck?: Trackable<Booleanish | undefined>
1671
+ /** The `spellCheck` HTML attribute. */
1672
+ spellCheck?: Trackable<Booleanish | undefined>
1666
1673
  /** The `style` HTML attribute. */
1667
1674
  style?: Trackable<string | StyleProps | undefined>
1668
1675
  /** The `tabindex` HTML attribute. */
@@ -1672,7 +1679,7 @@ export interface HTMLProps<eventTarget extends EventTarget = EventTarget>
1672
1679
  /** The `title` HTML attribute. */
1673
1680
  title?: Trackable<string | undefined>
1674
1681
  /** The `translate` HTML attribute. */
1675
- translate?: Trackable<boolean | undefined>
1682
+ translate?: Trackable<TranslateValue | undefined>
1676
1683
 
1677
1684
  // WAI-ARIA Attributes
1678
1685
  // Most elements only allow a subset of roles and so this
@@ -3418,6 +3425,10 @@ export interface TemplateHTMLProps<eventTarget extends EventTarget = HTMLTemplat
3418
3425
  */
3419
3426
  export interface TextareaHTMLProps<eventTarget extends EventTarget = HTMLTextAreaElement>
3420
3427
  extends HTMLProps<eventTarget> {
3428
+ /** Textarea content comes from `value` or `defaultValue`. */
3429
+ children?: never
3430
+ /** Textarea content comes from `value` or `defaultValue`. */
3431
+ innerHTML?: never
3421
3432
  /** The `autocomplete` HTML attribute. */
3422
3433
  autocomplete?: Trackable<string | undefined>
3423
3434
  /** The `autoComplete` HTML attribute. */
@@ -8,6 +8,7 @@ import type { Scheduler, VirtualRoot } from './vdom.ts'
8
8
  import { createRangeRoot, createRoot } from './vdom.ts'
9
9
  import { diffNodes } from './diff-dom.ts'
10
10
  import { createStyleManager, type StyleManager } from '../style/index.ts'
11
+ import { findFlushMarker, type FlushKind } from './stream-protocol.ts'
11
12
 
12
13
  type FrameRoot = [Comment, Comment] | Element | Document | DocumentFragment
13
14
 
@@ -39,12 +40,31 @@ type FrameMarkerData = FrameData & {
39
40
  type PendingClientEntries = Map<Comment, [Comment, RemixElement]>
40
41
 
41
42
  /**
42
- * Loads a client entry module for hydration.
43
+ * Loads a named client-entry export for hydration.
44
+ *
45
+ * @param moduleUrl Browser-resolvable URL for the module that contains the client entry.
46
+ * @param exportName Named export to read from the loaded module.
47
+ * @returns The exported component function, or a promise for it.
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * run({
52
+ * async loadModule(moduleUrl, exportName) {
53
+ * let mod = await import(moduleUrl)
54
+ * return mod[exportName]
55
+ * },
56
+ * })
57
+ * ```
43
58
  */
44
59
  export type LoadModule = (moduleUrl: string, exportName: string) => Promise<Function> | Function
45
60
 
46
61
  /**
47
- * Resolves frame content for the given frame source.
62
+ * Resolves content for a browser-loaded frame.
63
+ *
64
+ * @param src Source string from the `<Frame src>` prop.
65
+ * @param signal Abort signal for the active frame load or reload.
66
+ * @param target Optional name of the frame being reloaded.
67
+ * @returns HTML, a stream of HTML bytes, or Remix node content to render into the frame.
48
68
  */
49
69
  export type ResolveFrame = (
50
70
  src: string,
@@ -138,6 +158,7 @@ export type Frame = {
138
158
  }
139
159
 
140
160
  type RenderOptions = {
161
+ flushKind?: FlushKind
141
162
  initialHydrationTracker?: InitialHydrationTracker
142
163
  signal?: AbortSignal
143
164
  }
@@ -211,9 +232,9 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
211
232
  if (options?.signal?.aborted) return
212
233
 
213
234
  if (content instanceof ReadableStream) {
214
- await renderFrameStream(content, container.doc, async (html) => {
235
+ await renderFrameStream(content, container.doc, async (html, flushKind) => {
215
236
  if (options?.signal?.aborted) return
216
- await render(html, options)
237
+ await render(html, { ...options, flushKind })
217
238
  })
218
239
  return
219
240
  }
@@ -237,39 +258,42 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
237
258
  contentRoot = undefined
238
259
  }
239
260
 
261
+ if (typeof content === 'string') {
262
+ let flushed = await consumeFlushBatches(content, async (html, flushKind) => {
263
+ await render(html, { ...options, flushKind })
264
+ })
265
+ if (flushed.applied) {
266
+ if (flushed.remainder !== '') {
267
+ await render(flushed.remainder, { ...options, flushKind: 'fragment' })
268
+ }
269
+ return
270
+ }
271
+ }
272
+
240
273
  let htmlContent = typeof content === 'string' ? stripDoctypeMarkup(content) : undefined
241
274
 
242
275
  let isFullDocumentReload =
243
276
  container.root instanceof Document &&
244
277
  htmlContent !== undefined &&
245
- isFullDocumentHtml(htmlContent)
278
+ options?.flushKind === 'document'
246
279
 
247
280
  if (isFullDocumentReload && htmlContent !== undefined) {
248
- // Full-document reload should tear down existing hydrated roots and subframes
249
- // before diffing fresh HTML, otherwise stale component instances can survive
250
- // on detached DOM nodes.
251
- let previousBodyNodes = Array.from(container.doc.body.childNodes)
252
- removeVirtualRoots(previousBodyNodes)
253
- disposeSubFrames(previousBodyNodes, context)
254
281
  let parsed = new DOMParser().parseFromString(htmlContent, 'text/html')
255
282
  mergeRmxDataFromDocument(context.data, parsed)
256
- context.styleManager.reset()
257
- context.styleManager.adoptServerStyles(
283
+ context.styleManager.replaceServerStyles(
258
284
  collectFrameServerStyleTags(createElementContainer(parsed)),
259
285
  )
260
286
 
261
287
  syncElementAttributes(container.doc.documentElement, parsed.documentElement)
262
- syncElementAttributes(container.doc.head, parsed.head)
263
- syncElementAttributes(container.doc.body, parsed.body)
264
288
 
265
- diffNodes(Array.from(container.doc.head.childNodes), Array.from(parsed.head.childNodes), {
289
+ diffNodes([container.doc.head], [parsed.head], {
266
290
  ...context,
267
- regionParent: container.doc.head,
291
+ regionParent: container.doc.documentElement,
268
292
  regionTailRef: null,
269
293
  })
270
- diffNodes(Array.from(container.doc.body.childNodes), Array.from(parsed.body.childNodes), {
294
+ diffNodes([container.doc.body], [parsed.body], {
271
295
  ...context,
272
- regionParent: container.doc.body,
296
+ regionParent: container.doc.documentElement,
273
297
  regionTailRef: null,
274
298
  })
275
299
 
@@ -282,8 +306,7 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
282
306
 
283
307
  let fragment =
284
308
  htmlContent !== undefined ? createFragmentFromString(container.doc, htmlContent) : content
285
- context.styleManager.reset()
286
- context.styleManager.adoptServerStyles(
309
+ context.styleManager.replaceServerStyles(
287
310
  collectFrameServerStyleTags(createElementContainer(fragment)),
288
311
  )
289
312
  removeEmptyHeads(fragment)
@@ -344,8 +367,7 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
344
367
  async function hydrateInitial(): Promise<void> {
345
368
  let initialHydrationTracker = createInitialHydrationTracker()
346
369
 
347
- context.styleManager.reset()
348
- context.styleManager.adoptServerStyles(collectFrameServerStyleTags(container))
370
+ context.styleManager.replaceServerStyles(collectFrameServerStyleTags(container))
349
371
  createSubFrames(container.childNodes, context)
350
372
  scheduleHydrationInContainer(container, context, initialHydrationTracker)
351
373
 
@@ -986,13 +1008,12 @@ function extractTemplatesFromBuffer(
986
1008
  async function renderFrameStream(
987
1009
  stream: ReadableStream<Uint8Array>,
988
1010
  doc: Document,
989
- applyHtml: (html: string) => Promise<void>,
1011
+ applyHtml: (html: string, flushKind: FlushKind) => Promise<void>,
990
1012
  ): Promise<void> {
991
1013
  let reader = stream.getReader()
992
1014
  let decoder = new TextDecoder()
993
1015
  let buffer = ''
994
1016
  let html = ''
995
- let appliedLength = 0
996
1017
  let appliedOnce = false
997
1018
 
998
1019
  try {
@@ -1006,13 +1027,9 @@ async function renderFrameStream(
1006
1027
 
1007
1028
  if (parsed.html !== '') {
1008
1029
  html += parsed.html
1009
- let htmlMarkers = collectHtmlMarkerSummary(html)
1010
- if (!hasBalancedMarkerSummary(htmlMarkers)) {
1011
- continue
1012
- }
1013
- await applyHtml(html)
1014
- appliedLength = html.length
1015
- appliedOnce = true
1030
+ let flushed = await consumeFlushBatches(html, applyHtml)
1031
+ appliedOnce = flushed.applied || appliedOnce
1032
+ html = flushed.remainder
1016
1033
  }
1017
1034
  }
1018
1035
 
@@ -1025,21 +1042,40 @@ async function renderFrameStream(
1025
1042
  buffer = ''
1026
1043
  }
1027
1044
 
1028
- if (html !== '' && html.length > appliedLength) {
1029
- await applyHtml(html)
1045
+ if (html !== '') {
1046
+ await applyHtml(html, 'fragment')
1030
1047
  appliedOnce = true
1031
1048
  }
1032
1049
 
1033
1050
  // A frame stream can legitimately resolve to empty content. Ensure the
1034
1051
  // existing frame region is cleared instead of treated as a no-op.
1035
1052
  if (html === '' && !appliedOnce) {
1036
- await applyHtml('')
1053
+ await applyHtml('', 'fragment')
1037
1054
  }
1038
1055
  } finally {
1039
1056
  reader.releaseLock()
1040
1057
  }
1041
1058
  }
1042
1059
 
1060
+ async function consumeFlushBatches(
1061
+ html: string,
1062
+ applyHtml: (html: string, flushKind: FlushKind) => Promise<void>,
1063
+ ): Promise<{ applied: boolean; remainder: string }> {
1064
+ let applied = false
1065
+ let cursor = 0
1066
+ let marker = findFlushMarker(html, cursor)
1067
+
1068
+ while (marker) {
1069
+ let batch = html.slice(cursor, marker.index)
1070
+ await applyHtml(batch, marker.kind)
1071
+ applied = true
1072
+ cursor = marker.endIndex
1073
+ marker = findFlushMarker(html, cursor)
1074
+ }
1075
+
1076
+ return { applied, remainder: html.slice(cursor) }
1077
+ }
1078
+
1043
1079
  type FrameContainer = {
1044
1080
  doc: Document
1045
1081
  root: ParentNode
@@ -1104,11 +1140,6 @@ function isRemixNodeFrameContent(content: InternalFrameContent): content is Remi
1104
1140
  )
1105
1141
  }
1106
1142
 
1107
- function isFullDocumentHtml(content: string): boolean {
1108
- let trimmed = content.trimStart()
1109
- return /^<!doctype html\b/i.test(trimmed) || /^<html[\s>]/i.test(trimmed)
1110
- }
1111
-
1112
1143
  type HydrationMarker = {
1113
1144
  id: string
1114
1145
  start: Comment
@@ -1201,18 +1232,3 @@ function findEndMarker(
1201
1232
 
1202
1233
  throw new Error('End marker not found')
1203
1234
  }
1204
-
1205
- function collectHtmlMarkerSummary(html: string): Record<string, number> {
1206
- return {
1207
- frameStarts: html.match(/<!--\s*rmx:f:/g)?.length ?? 0,
1208
- frameEnds: html.match(/<!--\s*\/rmx:f\s*-->/g)?.length ?? 0,
1209
- hydrationStarts: html.match(/<!--\s*rmx:h:/g)?.length ?? 0,
1210
- hydrationEnds: html.match(/<!--\s*\/rmx:h\s*-->/g)?.length ?? 0,
1211
- }
1212
- }
1213
-
1214
- function hasBalancedMarkerSummary(summary: Record<string, number>): boolean {
1215
- return (
1216
- summary.frameStarts === summary.frameEnds && summary.hydrationStarts === summary.hydrationEnds
1217
- )
1218
- }
@@ -1,5 +1,6 @@
1
1
  import type * as dom from './dom.d.ts'
2
2
  import type { Handle, RenderFn } from './component.ts'
3
+ import { createRemixElement } from './core/vnode.ts'
3
4
 
4
5
  /**
5
6
  * Any valid element type accepted by JSX or {@link import('./create-element.ts').createElement}.
@@ -43,7 +44,7 @@ export type Renderable = RemixElement | string | number | bigint | boolean | nul
43
44
  * Particularly useful for `props.children`.
44
45
  *
45
46
  * ```tsx
46
- * function MyComponent({ children }: { children: RemixNode }) {}
47
+ * function MyComponent(handle: Handle<{ children: RemixNode }>) {}
47
48
  * ```
48
49
  */
49
50
  export type RemixNode = Renderable | RemixNode[]
@@ -102,7 +103,7 @@ export type Props<T extends keyof JSX.IntrinsicElements> = NormalizeMixProp<
102
103
  */
103
104
  export function jsx(type: ElementType, props: ElementProps, key?: string): RemixElement
104
105
  export function jsx(type: ElementType, props: ElementProps, key?: string): RemixElement {
105
- return { type, props: normalizeElementProps(props), key, $rmx: true }
106
+ return createRemixElement(type, props, key)
106
107
  }
107
108
 
108
109
  export { jsx as jsxDEV, jsx as jsxs }
@@ -367,31 +368,3 @@ declare global {
367
368
  IntrinsicHTMLElements {}
368
369
  }
369
370
  }
370
-
371
- function normalizeElementProps(props: ElementProps | null | undefined): ElementProps {
372
- if (!props) return {}
373
- if (!('mix' in props)) return props
374
-
375
- let { mix, ...rest } = props
376
- let normalizedMix = normalizeMixValue(mix)
377
- return normalizedMix === undefined ? rest : { ...rest, mix: normalizedMix }
378
- }
379
-
380
- function normalizeMixValue(mix: unknown): unknown[] | undefined {
381
- if (!mix) return undefined
382
-
383
- let normalizedMix: unknown[] = []
384
- flattenMixValue(mix, normalizedMix)
385
- return normalizedMix.length === 0 ? undefined : normalizedMix
386
- }
387
-
388
- function flattenMixValue(mix: unknown, out: unknown[]): void {
389
- if (!mix) return
390
- if (!Array.isArray(mix)) {
391
- out.push(mix)
392
- return
393
- }
394
- for (let item of mix) {
395
- flattenMixValue(item, out)
396
- }
397
- }