@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,4 +1,4 @@
1
- import { createMixin, renderMixinElement } from './mixin.ts'
1
+ import { createMixin, renderMixinElement, type MixinFactory } from './mixin.ts'
2
2
  import { navigate } from '../navigation.ts'
3
3
  import { on } from './on-mixin.ts'
4
4
  import type { ElementProps } from '../jsx.ts'
@@ -21,108 +21,110 @@ const nativeLinkHostTypes = new Set(['a', 'area'])
21
21
  /**
22
22
  * Adds client-side navigation behavior to anchor-like elements.
23
23
  */
24
- export const link = createMixin<
24
+ export const link: MixinFactory<
25
25
  HTMLElement,
26
26
  [href: string, options?: NavigationOptions],
27
27
  LinkCurrentProps
28
- >((handle, hostType) => {
29
- let suppressKeyboardClick = false
28
+ > = createMixin<HTMLElement, [href: string, options?: NavigationOptions], LinkCurrentProps>(
29
+ (handle, hostType) => {
30
+ let suppressKeyboardClick = false
30
31
 
31
- return (href, options, props: LinkCurrentProps) => {
32
- if (nativeLinkHostTypes.has(hostType)) {
33
- return renderMixinElement(handle.element, {
34
- ...(props ?? {}),
35
- href,
36
- ...(options?.target == null ? {} : { 'rmx-target': options.target }),
37
- ...(options?.src == null ? {} : { 'rmx-src': options.src }),
38
- ...(options?.resetScroll === false ? { 'rmx-reset-scroll': 'false' } : {}),
39
- })
40
- }
41
-
42
- let nextProps = { ...props }
43
- if (nextProps.role == null) {
44
- nextProps.role = 'link'
45
- }
46
- if (nextProps.disabled === true && nextProps['aria-disabled'] == null) {
47
- nextProps['aria-disabled'] = 'true'
48
- }
49
- if (hostType === 'button' && nextProps.type == null) {
50
- nextProps.type = 'button'
51
- }
52
- if (
53
- hostType !== 'button' &&
54
- nextProps.tabIndex == null &&
55
- nextProps.tabindex == null &&
56
- nextProps.contentEditable == null &&
57
- nextProps.contenteditable == null
58
- ) {
59
- nextProps.tabIndex = 0
60
- }
32
+ return (href, options, props: LinkCurrentProps) => {
33
+ if (nativeLinkHostTypes.has(hostType)) {
34
+ return renderMixinElement(handle.element, {
35
+ ...(props ?? {}),
36
+ href,
37
+ ...(options?.target == null ? {} : { 'rmx-target': options.target }),
38
+ ...(options?.src == null ? {} : { 'rmx-src': options.src }),
39
+ ...(options?.resetScroll === false ? { 'rmx-reset-scroll': 'false' } : {}),
40
+ })
41
+ }
61
42
 
62
- return renderMixinElement(handle.element, {
63
- ...nextProps,
64
- mix: [
65
- on('click', (event) => {
66
- if (event.detail === 0 && suppressKeyboardClick) {
67
- suppressKeyboardClick = false
68
- event.preventDefault()
69
- return
70
- }
43
+ let nextProps = { ...props }
44
+ if (nextProps.role == null) {
45
+ nextProps.role = 'link'
46
+ }
47
+ if (nextProps.disabled === true && nextProps['aria-disabled'] == null) {
48
+ nextProps['aria-disabled'] = 'true'
49
+ }
50
+ if (hostType === 'button' && nextProps.type == null) {
51
+ nextProps.type = 'button'
52
+ }
53
+ if (
54
+ hostType !== 'button' &&
55
+ nextProps.tabIndex == null &&
56
+ nextProps.tabindex == null &&
57
+ nextProps.contentEditable == null &&
58
+ nextProps.contenteditable == null
59
+ ) {
60
+ nextProps.tabIndex = 0
61
+ }
71
62
 
72
- suppressKeyboardClick = false
73
- if (isDisabledElement(event.currentTarget)) {
74
- event.preventDefault()
75
- return
76
- }
77
- if (event.button !== 0) return
63
+ return renderMixinElement(handle.element, {
64
+ ...nextProps,
65
+ mix: [
66
+ on('click', (event) => {
67
+ if (event.detail === 0 && suppressKeyboardClick) {
68
+ suppressKeyboardClick = false
69
+ event.preventDefault()
70
+ return
71
+ }
78
72
 
79
- event.preventDefault()
80
- if (event.metaKey || event.ctrlKey) {
81
- globalThis.open(href, '_blank')
82
- return
83
- }
73
+ suppressKeyboardClick = false
74
+ if (isDisabledElement(event.currentTarget)) {
75
+ event.preventDefault()
76
+ return
77
+ }
78
+ if (event.button !== 0) return
84
79
 
85
- void navigate(href, options)
86
- }),
87
- on('auxclick', (event) => {
88
- suppressKeyboardClick = false
89
- if (isDisabledElement(event.currentTarget)) {
90
80
  event.preventDefault()
91
- return
92
- }
93
- if (event.button !== 1) return
81
+ if (event.metaKey || event.ctrlKey) {
82
+ globalThis.open(href, '_blank')
83
+ return
84
+ }
94
85
 
95
- event.preventDefault()
96
- globalThis.open(href, '_blank')
97
- }),
98
- on('keydown', (event) => {
99
- if (event.key === 'Enter') {
100
- if (event.repeat) return
86
+ void navigate(href, options)
87
+ }),
88
+ on('auxclick', (event) => {
89
+ suppressKeyboardClick = false
101
90
  if (isDisabledElement(event.currentTarget)) {
102
91
  event.preventDefault()
103
92
  return
104
93
  }
94
+ if (event.button !== 1) return
105
95
 
106
- suppressKeyboardClick = hostType === 'button'
107
96
  event.preventDefault()
108
- void navigate(href, options)
109
- return
110
- }
97
+ globalThis.open(href, '_blank')
98
+ }),
99
+ on('keydown', (event) => {
100
+ if (event.key === 'Enter') {
101
+ if (event.repeat) return
102
+ if (isDisabledElement(event.currentTarget)) {
103
+ event.preventDefault()
104
+ return
105
+ }
111
106
 
112
- if (hostType === 'button' && event.key === ' ') {
113
- suppressKeyboardClick = true
114
- event.preventDefault()
115
- }
116
- }),
117
- on('keyup', (event) => {
118
- if (hostType === 'button' && event.key === ' ') {
119
- event.preventDefault()
120
- }
121
- }),
122
- ],
123
- })
124
- }
125
- })
107
+ suppressKeyboardClick = hostType === 'button'
108
+ event.preventDefault()
109
+ void navigate(href, options)
110
+ return
111
+ }
112
+
113
+ if (hostType === 'button' && event.key === ' ') {
114
+ suppressKeyboardClick = true
115
+ event.preventDefault()
116
+ }
117
+ }),
118
+ on('keyup', (event) => {
119
+ if (hostType === 'button' && event.key === ' ') {
120
+ event.preventDefault()
121
+ }
122
+ }),
123
+ ],
124
+ })
125
+ }
126
+ },
127
+ )
126
128
 
127
129
  function isDisabledElement(node: Element) {
128
130
  return (
@@ -130,6 +130,14 @@ export type MixinDescriptor<
130
130
  readonly __node?: (node: node) => void
131
131
  }
132
132
 
133
+ export type MixinFactory<
134
+ node extends EventTarget = Element,
135
+ args extends unknown[] = [],
136
+ props extends ElementProps = ElementProps,
137
+ > = <boundNode extends node = node>(
138
+ ...args: RebindTuple<args, node, boundNode>
139
+ ) => MixinDescriptor<boundNode, RebindTuple<args, node, boundNode>, props>
140
+
133
141
  type PreviousMixDepth = [0, 0, 1, 2, 3, 4]
134
142
  type FalsyMixValue = false | 0 | 0n | '' | null | undefined
135
143
  type NullableMixValue<descriptor> = descriptor | FalsyMixValue
@@ -244,7 +252,7 @@ export function createMixin<
244
252
  node extends EventTarget = Element,
245
253
  args extends unknown[] = [],
246
254
  props extends ElementProps = ElementProps,
247
- >(type: MixinType<node, args, props>) {
255
+ >(type: MixinType<node, args, props>): MixinFactory<node, args, props> {
248
256
  return <boundNode extends node = node>(
249
257
  ...args: RebindTuple<args, node, boundNode>
250
258
  ): MixinDescriptor<boundNode, RebindTuple<args, node, boundNode>, props> => ({
@@ -598,7 +606,6 @@ class MixinHandleImpl
598
606
  this.#activeScope = scope
599
607
  if (!scope) return
600
608
  if (this.#scopeTargets.has(scope)) return
601
- this.#scopeSignals.set(scope, new AbortController())
602
609
  this.#scopeTargets.set(scope, new TypedEventTarget<MixinHandleEventMap<Element>>())
603
610
  this.#scopePhaseCounts.set(scope, { beforeUpdate: 0, commit: 0 })
604
611
  }
@@ -616,8 +623,11 @@ class MixinHandleImpl
616
623
  this.#decrementGlobalPhaseCount('beforeUpdate', scopePhaseCounts.beforeUpdate)
617
624
  this.#decrementGlobalPhaseCount('commit', scopePhaseCounts.commit)
618
625
  }
619
- this.#scopeSignals.get(scope)?.abort()
620
- this.#scopeSignals.delete(scope)
626
+ let controller = this.#scopeSignals.get(scope)
627
+ if (controller) {
628
+ controller.abort()
629
+ this.#scopeSignals.delete(scope)
630
+ }
621
631
  this.#scopePhaseCounts.delete(scope)
622
632
  this.#scopeTargets.delete(scope)
623
633
  if (this.#activeScope === scope) {
@@ -646,7 +656,10 @@ class MixinHandleImpl
646
656
 
647
657
  #getScopeSignal(scope: symbol) {
648
658
  let controller = this.#scopeSignals.get(scope)
649
- invariant(controller)
659
+ if (!controller) {
660
+ controller = new AbortController()
661
+ this.#scopeSignals.set(scope, controller)
662
+ }
650
663
  return controller.signal
651
664
  }
652
665
 
@@ -1,28 +1,28 @@
1
- import { createMixin } from './mixin.ts'
1
+ import { createMixin, type MixinType } from './mixin.ts'
2
2
  import type { ElementProps } from '../jsx.ts'
3
3
  import type { MixinDescriptor } from './mixin.ts'
4
- import type {
5
- EventType as AddEventType,
6
- ListenerFor as AddEventListenerFor,
7
- } from '../event-listeners.ts'
8
-
9
- export type { Dispatched } from '../event-listeners.ts'
4
+ import type { EnsureEvent, EventMap } from '../event-listeners.ts'
10
5
 
11
6
  type SignaledListener<event extends Event> = (
12
7
  event: event,
13
8
  signal: AbortSignal,
14
9
  ) => void | Promise<void>
15
10
 
16
- type EventType<target extends Element> = Extract<AddEventType<target>, string>
11
+ type EventType<target extends Element> = string & keyof EventMap<target>
12
+
17
13
  type ListenerFor<target extends Element, type extends EventType<target>> = SignaledListener<
18
- Parameters<AddEventListenerFor<target, type>>[0]
14
+ EnsureEvent<EventMap<target>[type], target>
19
15
  >
16
+ export type OnMixinDescriptor = {
17
+ type: typeof onMixinType
18
+ args: [type: string, handler: SignaledListener<Event>, captureBoolean?: boolean]
19
+ }
20
20
 
21
- const onMixin = createMixin<
21
+ const onMixinType: MixinType<
22
22
  Element,
23
23
  [type: string, handler: SignaledListener<Event>, captureBoolean?: boolean],
24
24
  ElementProps
25
- >((handle) => {
25
+ > = (handle) => {
26
26
  let currentHandler: SignaledListener<Event> = () => {}
27
27
  let currentType = ''
28
28
  let currentCapture = false
@@ -61,7 +61,15 @@ const onMixin = createMixin<
61
61
 
62
62
  return handle.element
63
63
  }
64
- })
64
+ }
65
+
66
+ const onMixin = createMixin(onMixinType)
67
+
68
+ export function isOnMixinDescriptor(descriptor: unknown): descriptor is OnMixinDescriptor {
69
+ if (!descriptor || typeof descriptor !== 'object') return false
70
+ let candidate = descriptor as { type?: unknown; args?: unknown }
71
+ return candidate.type === onMixinType && Array.isArray(candidate.args)
72
+ }
65
73
 
66
74
  /**
67
75
  * Attaches a typed DOM event handler through the mixin system.
@@ -1,4 +1,4 @@
1
- import { createMixin } from './mixin.ts'
1
+ import { createMixin, type MixinFactory } from './mixin.ts'
2
2
  import type { ElementProps } from '../jsx.ts'
3
3
 
4
4
  /**
@@ -9,8 +9,8 @@ export type RefCallback<node extends EventTarget> = (node: node, signal: AbortSi
9
9
  /**
10
10
  * Calls a callback when an element is inserted and aborts it when removed.
11
11
  */
12
- export const ref = createMixin<Element, [callback: RefCallback<Element>], ElementProps>(
13
- (handle) => {
12
+ export const ref: MixinFactory<Element, [callback: RefCallback<Element>], ElementProps> =
13
+ createMixin<Element, [callback: RefCallback<Element>], ElementProps>((handle) => {
14
14
  let controller: AbortController | undefined
15
15
 
16
16
  handle.addEventListener('insert', (event) => {
@@ -28,5 +28,4 @@ export const ref = createMixin<Element, [callback: RefCallback<Element>], Elemen
28
28
  callback = nextCallback
29
29
  return handle.element
30
30
  }
31
- },
32
- )
31
+ })
@@ -65,7 +65,11 @@ export function startNavigationListenerImpl(
65
65
  navigation.addEventListener(
66
66
  'navigate',
67
67
  (event) => {
68
- if (!event.canIntercept) return
68
+ // Safari seems to incorrectly set canIntercept to true for sub-domain navigations, so
69
+ // we do a host check ourselves/. The spec is clear that a different host should prevent
70
+ // interception so this is likely a bug in Safari:
71
+ // https://html.spec.whatwg.org/multipage/nav-history-apis.html#can-have-its-url-rewritten
72
+ if (!event.canIntercept || isCrossOriginDestination(event)) return
69
73
 
70
74
  let state = getRuntimeNavigationState(event)
71
75
  if (!state) return
@@ -98,6 +102,11 @@ function isRuntimeNavigation(info: unknown): info is NavigationState {
98
102
  return typeof info === 'object' && info != null && '$rmx' in info
99
103
  }
100
104
 
105
+ function isCrossOriginDestination(event: NavigateEvent): boolean {
106
+ let destination = new URL(event.destination.url)
107
+ return destination.origin !== window.location.origin
108
+ }
109
+
101
110
  function getRuntimeNavigationState(event: NavigateEvent): NavigationState | undefined {
102
111
  if (event.navigationType === 'traverse') {
103
112
  return getTraverseNavigationState(event)