@remix-run/ui 0.0.0 → 0.1.1
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.
- package/LICENSE +21 -0
- package/README.md +195 -2
- package/dist/animation/animate-layout-mixin.d.ts +12 -0
- package/dist/animation/animate-layout-mixin.js +214 -0
- package/dist/animation/animate-layout-mixin.js.map +1 -0
- package/dist/animation/animate-mixins.d.ts +29 -0
- package/dist/animation/animate-mixins.js +235 -0
- package/dist/animation/animate-mixins.js.map +1 -0
- package/dist/animation/index.d.ts +6 -0
- package/dist/animation/index.js +5 -0
- package/dist/animation/index.js.map +1 -0
- package/dist/animation/spring.d.ts +75 -0
- package/dist/animation/spring.js +183 -0
- package/dist/animation/spring.js.map +1 -0
- package/dist/animation/tween.d.ts +70 -0
- package/dist/animation/tween.js +93 -0
- package/dist/animation/tween.js.map +1 -0
- package/dist/components/accordion/accordion.d.ts +92 -0
- package/dist/components/accordion/accordion.js +337 -0
- package/dist/components/accordion/accordion.js.map +1 -0
- package/dist/components/anchor/anchor.d.ts +13 -0
- package/dist/components/anchor/anchor.js +558 -0
- package/dist/components/anchor/anchor.js.map +1 -0
- package/dist/components/breadcrumbs/breadcrumbs.d.ts +11 -0
- package/dist/components/breadcrumbs/breadcrumbs.js +78 -0
- package/dist/components/breadcrumbs/breadcrumbs.js.map +1 -0
- package/dist/components/button/button.d.ts +23 -0
- package/dist/components/button/button.js +147 -0
- package/dist/components/button/button.js.map +1 -0
- package/dist/components/combobox/combobox.d.ts +101 -0
- package/dist/components/combobox/combobox.js +708 -0
- package/dist/components/combobox/combobox.js.map +1 -0
- package/dist/components/glyph/glyph.d.ts +14 -0
- package/dist/components/glyph/glyph.js +65 -0
- package/dist/components/glyph/glyph.js.map +1 -0
- package/dist/components/listbox/listbox.d.ts +67 -0
- package/dist/components/listbox/listbox.js +340 -0
- package/dist/components/listbox/listbox.js.map +1 -0
- package/dist/components/menu/hover-aim.d.ts +5 -0
- package/dist/components/menu/hover-aim.js +308 -0
- package/dist/components/menu/hover-aim.js.map +1 -0
- package/dist/components/menu/menu.d.ts +164 -0
- package/dist/components/menu/menu.js +1106 -0
- package/dist/components/menu/menu.js.map +1 -0
- package/dist/components/popover/popover.d.ts +35 -0
- package/dist/components/popover/popover.js +138 -0
- package/dist/components/popover/popover.js.map +1 -0
- package/dist/components/select/select.d.ts +67 -0
- package/dist/components/select/select.js +397 -0
- package/dist/components/select/select.js.map +1 -0
- package/dist/components/separator/separator.d.ts +2 -0
- package/dist/components/separator/separator.js +15 -0
- package/dist/components/separator/separator.js.map +1 -0
- package/dist/components/tabs/tabs.d.ts +78 -0
- package/dist/components/tabs/tabs.js +341 -0
- package/dist/components/tabs/tabs.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/interactions/keydown/keydown.d.ts +1 -0
- package/dist/interactions/keydown/keydown.js +8 -0
- package/dist/interactions/keydown/keydown.js.map +1 -0
- package/dist/interactions/outside-click/outside-click-mixin.d.ts +2 -0
- package/dist/interactions/outside-click/outside-click-mixin.js +29 -0
- package/dist/interactions/outside-click/outside-click-mixin.js.map +1 -0
- package/dist/interactions/typeahead/typeahead-mixin.d.ts +8 -0
- package/dist/interactions/typeahead/typeahead-mixin.js +59 -0
- package/dist/interactions/typeahead/typeahead-mixin.js.map +1 -0
- package/dist/jsx-runtime.d.ts +2 -0
- package/dist/jsx-runtime.js +3 -0
- package/dist/jsx-runtime.js.map +1 -0
- package/dist/runtime/client-entries.d.ts +91 -0
- package/dist/runtime/client-entries.js +40 -0
- package/dist/runtime/client-entries.js.map +1 -0
- package/dist/runtime/component.d.ts +223 -0
- package/dist/runtime/component.js +152 -0
- package/dist/runtime/component.js.map +1 -0
- package/dist/runtime/create-element.d.ts +10 -0
- package/dist/runtime/create-element.js +29 -0
- package/dist/runtime/create-element.js.map +1 -0
- package/dist/runtime/diff-dom.d.ts +2 -0
- package/dist/runtime/diff-dom.js +364 -0
- package/dist/runtime/diff-dom.js.map +1 -0
- package/dist/runtime/diff-props.d.ts +9 -0
- package/dist/runtime/diff-props.js +195 -0
- package/dist/runtime/diff-props.js.map +1 -0
- package/dist/runtime/document-state.d.ts +11 -0
- package/dist/runtime/document-state.js +106 -0
- package/dist/runtime/document-state.js.map +1 -0
- package/dist/runtime/dom.d.ts +3189 -0
- package/dist/runtime/dom.js +2 -0
- package/dist/runtime/dom.js.map +1 -0
- package/dist/runtime/error-event.d.ts +20 -0
- package/dist/runtime/error-event.js +19 -0
- package/dist/runtime/error-event.js.map +1 -0
- package/dist/runtime/event-listeners.d.ts +50 -0
- package/dist/runtime/event-listeners.js +31 -0
- package/dist/runtime/event-listeners.js.map +1 -0
- package/dist/runtime/frame.d.ts +119 -0
- package/dist/runtime/frame.js +920 -0
- package/dist/runtime/frame.js.map +1 -0
- package/dist/runtime/invariant.d.ts +23 -0
- package/dist/runtime/invariant.js +33 -0
- package/dist/runtime/invariant.js.map +1 -0
- package/dist/runtime/jsx.d.ts +320 -0
- package/dist/runtime/jsx.js +32 -0
- package/dist/runtime/jsx.js.map +1 -0
- package/dist/runtime/mixins/attrs-mixin.d.ts +9 -0
- package/dist/runtime/mixins/attrs-mixin.js +23 -0
- package/dist/runtime/mixins/attrs-mixin.js.map +1 -0
- package/dist/runtime/mixins/link-mixin.d.ts +17 -0
- package/dist/runtime/mixins/link-mixin.js +101 -0
- package/dist/runtime/mixins/link-mixin.js.map +1 -0
- package/dist/runtime/mixins/mixin.d.ts +145 -0
- package/dist/runtime/mixins/mixin.js +570 -0
- package/dist/runtime/mixins/mixin.js.map +1 -0
- package/dist/runtime/mixins/on-mixin.d.ts +16 -0
- package/dist/runtime/mixins/on-mixin.js +49 -0
- package/dist/runtime/mixins/on-mixin.js.map +1 -0
- package/dist/runtime/mixins/ref-mixin.d.ts +9 -0
- package/dist/runtime/mixins/ref-mixin.js +21 -0
- package/dist/runtime/mixins/ref-mixin.js.map +1 -0
- package/dist/runtime/navigation.d.ts +28 -0
- package/dist/runtime/navigation.js +107 -0
- package/dist/runtime/navigation.js.map +1 -0
- package/dist/runtime/reconcile.d.ts +11 -0
- package/dist/runtime/reconcile.js +1262 -0
- package/dist/runtime/reconcile.js.map +1 -0
- package/dist/runtime/render.d.ts +54 -0
- package/dist/runtime/render.js +50 -0
- package/dist/runtime/render.js.map +1 -0
- package/dist/runtime/run.d.ts +45 -0
- package/dist/runtime/run.js +68 -0
- package/dist/runtime/run.js.map +1 -0
- package/dist/runtime/scheduler.d.ts +30 -0
- package/dist/runtime/scheduler.js +195 -0
- package/dist/runtime/scheduler.js.map +1 -0
- package/dist/runtime/svg-attributes.d.ts +5 -0
- package/dist/runtime/svg-attributes.js +124 -0
- package/dist/runtime/svg-attributes.js.map +1 -0
- package/dist/runtime/to-vnode.d.ts +3 -0
- package/dist/runtime/to-vnode.js +46 -0
- package/dist/runtime/to-vnode.js.map +1 -0
- package/dist/runtime/typed-event-target.d.ts +50 -0
- package/dist/runtime/typed-event-target.js +6 -0
- package/dist/runtime/typed-event-target.js.map +1 -0
- package/dist/runtime/vdom.d.ts +55 -0
- package/dist/runtime/vdom.js +198 -0
- package/dist/runtime/vdom.js.map +1 -0
- package/dist/runtime/vnode.d.ts +79 -0
- package/dist/runtime/vnode.js +38 -0
- package/dist/runtime/vnode.js.map +1 -0
- package/dist/server/stream.d.ts +57 -0
- package/dist/server/stream.js +1011 -0
- package/dist/server/stream.js.map +1 -0
- package/dist/style/css-mixin.d.ts +8 -0
- package/dist/style/css-mixin.js +54 -0
- package/dist/style/css-mixin.js.map +1 -0
- package/dist/style/index.d.ts +5 -0
- package/dist/style/index.js +4 -0
- package/dist/style/index.js.map +1 -0
- package/dist/style/style.d.ts +24 -0
- package/dist/style/style.js +269 -0
- package/dist/style/style.js.map +1 -0
- package/dist/style/stylesheet.d.ts +11 -0
- package/dist/style/stylesheet.js +174 -0
- package/dist/style/stylesheet.js.map +1 -0
- package/dist/test.d.ts +2 -0
- package/dist/test.js +2 -0
- package/dist/test.js.map +1 -0
- package/dist/theme/contract.d.ts +259 -0
- package/dist/theme/contract.js +131 -0
- package/dist/theme/contract.js.map +1 -0
- package/dist/theme/glyph-contract.d.ts +13 -0
- package/dist/theme/glyph-contract.js +34 -0
- package/dist/theme/glyph-contract.js.map +1 -0
- package/dist/theme/presets/rmx-01/glyphs.d.ts +2 -0
- package/dist/theme/presets/rmx-01/glyphs.js +252 -0
- package/dist/theme/presets/rmx-01/glyphs.js.map +1 -0
- package/dist/theme/presets/rmx-01/index.d.ts +2 -0
- package/dist/theme/presets/rmx-01/index.js +119 -0
- package/dist/theme/presets/rmx-01/index.js.map +1 -0
- package/dist/theme/runtime.d.ts +2 -0
- package/dist/theme/runtime.js +81 -0
- package/dist/theme/runtime.js.map +1 -0
- package/dist/theme/theme.d.ts +6 -0
- package/dist/theme/theme.js +5 -0
- package/dist/theme/theme.js.map +1 -0
- package/dist/utils/flash-attribute.d.ts +1 -0
- package/dist/utils/flash-attribute.js +11 -0
- package/dist/utils/flash-attribute.js.map +1 -0
- package/dist/utils/scroll-lock.d.ts +3 -0
- package/dist/utils/scroll-lock.js +69 -0
- package/dist/utils/scroll-lock.js.map +1 -0
- package/dist/utils/wait-for-css-transition.d.ts +1 -0
- package/dist/utils/wait-for-css-transition.js +58 -0
- package/dist/utils/wait-for-css-transition.js.map +1 -0
- package/dist/utils/wait.d.ts +1 -0
- package/dist/utils/wait.js +6 -0
- package/dist/utils/wait.js.map +1 -0
- package/package.json +124 -5
- package/src/animation/animate-layout-mixin.ts +266 -0
- package/src/animation/animate-mixins.ts +292 -0
- package/src/animation/index.ts +6 -0
- package/src/animation/spring.ts +299 -0
- package/src/animation/tween.ts +134 -0
- package/src/components/accordion/accordion.tsx +565 -0
- package/src/components/anchor/anchor.ts +904 -0
- package/src/components/breadcrumbs/breadcrumbs.tsx +124 -0
- package/src/components/button/README.md +44 -0
- package/src/components/button/button.tsx +188 -0
- package/src/components/combobox/README.md +145 -0
- package/src/components/combobox/combobox.tsx +1014 -0
- package/src/components/glyph/glyph.tsx +110 -0
- package/src/components/listbox/listbox.ts +447 -0
- package/src/components/menu/hover-aim.ts +428 -0
- package/src/components/menu/menu.tsx +1547 -0
- package/src/components/popover/README.md +122 -0
- package/src/components/popover/popover.ts +201 -0
- package/src/components/select/select.tsx +585 -0
- package/src/components/separator/separator.ts +19 -0
- package/src/components/tabs/README.md +105 -0
- package/src/components/tabs/tabs.tsx +499 -0
- package/src/index.ts +72 -0
- package/src/interactions/keydown/keydown.ts +14 -0
- package/src/interactions/outside-click/outside-click-mixin.ts +55 -0
- package/src/interactions/typeahead/typeahead-mixin.ts +89 -0
- package/src/jsx-runtime.ts +2 -0
- package/src/runtime/client-entries.ts +137 -0
- package/src/runtime/component.ts +402 -0
- package/src/runtime/create-element.ts +38 -0
- package/src/runtime/diff-dom.ts +404 -0
- package/src/runtime/diff-props.ts +209 -0
- package/src/runtime/document-state.ts +130 -0
- package/src/runtime/dom.ts +3971 -0
- package/src/runtime/error-event.ts +26 -0
- package/src/runtime/event-listeners.ts +171 -0
- package/src/runtime/frame.ts +1218 -0
- package/src/runtime/invariant.ts +31 -0
- package/src/runtime/jsx.ts +397 -0
- package/src/runtime/mixins/attrs-mixin.ts +35 -0
- package/src/runtime/mixins/link-mixin.ts +131 -0
- package/src/runtime/mixins/mixin.ts +908 -0
- package/src/runtime/mixins/on-mixin.ts +89 -0
- package/src/runtime/mixins/ref-mixin.ts +32 -0
- package/src/runtime/navigation.ts +148 -0
- package/src/runtime/reconcile.ts +1757 -0
- package/src/runtime/render.ts +101 -0
- package/src/runtime/run.ts +100 -0
- package/src/runtime/scheduler.ts +269 -0
- package/src/runtime/svg-attributes.ts +133 -0
- package/src/runtime/to-vnode.ts +51 -0
- package/src/runtime/typed-event-target.ts +67 -0
- package/src/runtime/vdom.ts +295 -0
- package/src/runtime/vnode.ts +137 -0
- package/src/server/stream.ts +1347 -0
- package/src/style/css-mixin.ts +82 -0
- package/src/style/index.ts +10 -0
- package/src/style/style.ts +312 -0
- package/src/style/stylesheet.ts +209 -0
- package/src/test/utils.ts +45 -0
- package/src/test.ts +2 -0
- package/src/theme/contract.ts +183 -0
- package/src/theme/glyph-contract.ts +57 -0
- package/src/theme/presets/rmx-01/glyphs.tsx +308 -0
- package/src/theme/presets/rmx-01/index.ts +121 -0
- package/src/theme/runtime.ts +116 -0
- package/src/theme/theme.ts +15 -0
- package/src/utils/flash-attribute.ts +11 -0
- package/src/utils/scroll-lock.ts +97 -0
- package/src/utils/wait-for-css-transition.ts +66 -0
- package/src/utils/wait.ts +5 -0
|
@@ -0,0 +1,908 @@
|
|
|
1
|
+
import type { Context, FrameHandle } from '../component.ts'
|
|
2
|
+
import type { ElementProps, RemixElement } from '../jsx.ts'
|
|
3
|
+
import type { Scheduler } from '../scheduler.ts'
|
|
4
|
+
import type { SchedulerPhaseEvent } from '../scheduler.ts'
|
|
5
|
+
import { jsx } from '../jsx.ts'
|
|
6
|
+
import { TypedEventTarget } from '../typed-event-target.ts'
|
|
7
|
+
import { invariant } from '../invariant.ts'
|
|
8
|
+
|
|
9
|
+
type RebindNode<value, baseNode, boundNode> = value extends (
|
|
10
|
+
...args: infer fnArgs
|
|
11
|
+
) => infer fnResult
|
|
12
|
+
? (...args: RebindTuple<fnArgs, baseNode, boundNode>) => RebindNode<fnResult, baseNode, boundNode>
|
|
13
|
+
: [value] extends [baseNode]
|
|
14
|
+
? [baseNode] extends [value]
|
|
15
|
+
? boundNode
|
|
16
|
+
: value
|
|
17
|
+
: value
|
|
18
|
+
|
|
19
|
+
type RebindTuple<args extends unknown[], baseNode, boundNode> = {
|
|
20
|
+
[index in keyof args]: RebindNode<args[index], baseNode, boundNode>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type MixinProps<
|
|
24
|
+
node extends EventTarget = Element,
|
|
25
|
+
props extends ElementProps = ElementProps,
|
|
26
|
+
> = Omit<props, 'children' | 'innerHTML' | 'mix'> & {
|
|
27
|
+
mix?: MixValue<node, props>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type MixinElement<
|
|
31
|
+
node extends EventTarget = Element,
|
|
32
|
+
props extends ElementProps = ElementProps,
|
|
33
|
+
> = ((
|
|
34
|
+
handle: { update(): Promise<AbortSignal> },
|
|
35
|
+
setup: unknown,
|
|
36
|
+
) => (props: MixinProps<node, props>) => RemixElement) & {
|
|
37
|
+
__rmxMixinElementType: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type MixinInsertEvent<node extends EventTarget = Element> = Event & {
|
|
41
|
+
node: node
|
|
42
|
+
parent: ParentNode
|
|
43
|
+
key?: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type MixinReclaimedEvent<node extends EventTarget = Element> = Event & {
|
|
47
|
+
node: node
|
|
48
|
+
parent: ParentNode
|
|
49
|
+
key?: string
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type MixinUpdateEvent<node extends EventTarget = Element> = Event & {
|
|
53
|
+
node: node
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type MixinBeforeRemoveEvent = Event & {
|
|
57
|
+
persistNode(teardown: (signal: AbortSignal) => void | Promise<void>): void
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
type MixinContext = Pick<Context<Record<string, never>>, 'get'>
|
|
61
|
+
|
|
62
|
+
type MixinHandleEventMap<node extends EventTarget = Element> = {
|
|
63
|
+
beforeRemove: MixinBeforeRemoveEvent
|
|
64
|
+
reclaimed: MixinReclaimedEvent<node>
|
|
65
|
+
remove: Event
|
|
66
|
+
insert: MixinInsertEvent<node>
|
|
67
|
+
beforeUpdate: MixinUpdateEvent<node>
|
|
68
|
+
commit: MixinUpdateEvent<node>
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Runtime handle passed to mixin setup functions.
|
|
73
|
+
*
|
|
74
|
+
* Mixin render callbacks receive host props with `children` and `innerHTML` removed.
|
|
75
|
+
* Returned mixin elements may patch host attributes and nested `mix`, but cannot replace
|
|
76
|
+
* the host subtree.
|
|
77
|
+
*/
|
|
78
|
+
export type MixinHandle<
|
|
79
|
+
node extends EventTarget = Element,
|
|
80
|
+
props extends ElementProps = ElementProps,
|
|
81
|
+
> = TypedEventTarget<MixinHandleEventMap<node>> & {
|
|
82
|
+
id: string
|
|
83
|
+
context: MixinContext
|
|
84
|
+
frame: FrameHandle
|
|
85
|
+
element: MixinElement<node, props>
|
|
86
|
+
signal: AbortSignal
|
|
87
|
+
update(): Promise<AbortSignal>
|
|
88
|
+
queueTask(task: (node: node, signal: AbortSignal) => void): void
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function renderMixinElement<
|
|
92
|
+
node extends EventTarget = Element,
|
|
93
|
+
props extends ElementProps = ElementProps,
|
|
94
|
+
>(element: MixinElement<node, props>, props?: MixinProps<node, props>): RemixElement {
|
|
95
|
+
let { key, ...rest } = (props ?? {}) as MixinProps<node, props> & { key?: any }
|
|
96
|
+
return jsx(element, rest, key)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
type MixinRuntimeType<
|
|
100
|
+
args extends unknown[] = [],
|
|
101
|
+
node extends EventTarget = Element,
|
|
102
|
+
props extends ElementProps = ElementProps,
|
|
103
|
+
> = (
|
|
104
|
+
handle: MixinHandle<node, props>,
|
|
105
|
+
type: string,
|
|
106
|
+
) => ((...args: [...args, currentProps: props]) => MixinReturn<node, props>) | void
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Public mixin setup function signature.
|
|
110
|
+
*/
|
|
111
|
+
export type MixinType<
|
|
112
|
+
node extends EventTarget = Element,
|
|
113
|
+
args extends unknown[] = [],
|
|
114
|
+
props extends ElementProps = ElementProps,
|
|
115
|
+
> = (
|
|
116
|
+
handle: MixinHandle<node, props>,
|
|
117
|
+
type: string,
|
|
118
|
+
) => ((...args: [...args, currentProps: props]) => MixinReturn<node, props>) | void
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Serializable descriptor stored in the `mix` prop.
|
|
122
|
+
*/
|
|
123
|
+
export type MixinDescriptor<
|
|
124
|
+
node extends EventTarget = Element,
|
|
125
|
+
args extends unknown[] = [],
|
|
126
|
+
props extends ElementProps = ElementProps,
|
|
127
|
+
> = {
|
|
128
|
+
type: MixinRuntimeType<args, node, props>
|
|
129
|
+
args: args
|
|
130
|
+
readonly __node?: (node: node) => void
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
type PreviousMixDepth = [0, 0, 1, 2, 3, 4]
|
|
134
|
+
type FalsyMixValue = false | 0 | 0n | '' | null | undefined
|
|
135
|
+
type NullableMixValue<descriptor> = descriptor | FalsyMixValue
|
|
136
|
+
type NestedMixValue<descriptor, depth extends number = 4> = depth extends 0
|
|
137
|
+
? NullableMixValue<descriptor> | ReadonlyArray<NullableMixValue<descriptor>>
|
|
138
|
+
:
|
|
139
|
+
| NullableMixValue<descriptor>
|
|
140
|
+
| ReadonlyArray<NestedMixValue<descriptor, PreviousMixDepth[depth]>>
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Accepted authoring shape for the `mix` prop on host elements.
|
|
144
|
+
*/
|
|
145
|
+
export type MixInput<
|
|
146
|
+
node extends EventTarget = Element,
|
|
147
|
+
props extends ElementProps = ElementProps,
|
|
148
|
+
> = NestedMixValue<MixinDescriptor<node, any, props>>
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Accepted value shape for the `mix` prop.
|
|
152
|
+
*/
|
|
153
|
+
export type MixValue<
|
|
154
|
+
node extends EventTarget = Element,
|
|
155
|
+
props extends ElementProps = ElementProps,
|
|
156
|
+
> = MixinDescriptor<node, any, props> | ReadonlyArray<MixinDescriptor<node, any, props>>
|
|
157
|
+
|
|
158
|
+
type MixinReturn<node extends EventTarget = Element, props extends ElementProps = ElementProps> =
|
|
159
|
+
| void
|
|
160
|
+
| null
|
|
161
|
+
| RemixElement
|
|
162
|
+
| MixinElement<node, props>
|
|
163
|
+
| MixInput<node, props>
|
|
164
|
+
|
|
165
|
+
type AnyMixinType = MixinRuntimeType<unknown[], Element, ElementProps>
|
|
166
|
+
type AnyMixinDescriptor = MixinDescriptor<Element, unknown[], ElementProps>
|
|
167
|
+
type AnyMixinRunner = (
|
|
168
|
+
...args: [...unknown[], currentProps: ElementProps]
|
|
169
|
+
) => MixinReturn<Element, ElementProps>
|
|
170
|
+
type AnyMixinRunnerResult = ReturnType<AnyMixinRunner>
|
|
171
|
+
type AnyMixinSetupResult = ReturnType<AnyMixinType> | AnyMixinRunnerResult
|
|
172
|
+
type AnyMixinHandle = MixinHandle<Element, ElementProps>
|
|
173
|
+
type ScopedAnyMixinHandle = AnyMixinHandle & {
|
|
174
|
+
queueCommitTask(task: () => void): void
|
|
175
|
+
setActiveScope(scope?: symbol): void
|
|
176
|
+
dispatchScopedEvent(scope: symbol, event: Event): void
|
|
177
|
+
releaseScope(scope: symbol): void
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
type RunnerEntry = {
|
|
181
|
+
type: AnyMixinType
|
|
182
|
+
runner: AnyMixinRunner
|
|
183
|
+
scope: symbol
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
type MixinHandleFactoryOptions = {
|
|
187
|
+
id: string
|
|
188
|
+
hostType: string
|
|
189
|
+
frame: FrameHandle
|
|
190
|
+
scheduler: Scheduler
|
|
191
|
+
getContext: MixinContext['get']
|
|
192
|
+
getRuntimeSignal: () => AbortSignal
|
|
193
|
+
getBinding: () => MixinRuntimeBinding | undefined
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export type MixinRuntimeBinding = {
|
|
197
|
+
node: Element
|
|
198
|
+
parent: ParentNode
|
|
199
|
+
key?: string
|
|
200
|
+
target: unknown
|
|
201
|
+
frame: FrameHandle
|
|
202
|
+
scheduler: Scheduler
|
|
203
|
+
enqueueUpdate(done: (signal: AbortSignal) => void): void
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
type ResolveMixedPropsInput = {
|
|
207
|
+
hostType: string
|
|
208
|
+
frame: FrameHandle
|
|
209
|
+
scheduler: Scheduler
|
|
210
|
+
getContext?: MixinContext['get']
|
|
211
|
+
props: ElementProps
|
|
212
|
+
state?: MixinRuntimeState
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
type ResolveMixedPropsOutput = {
|
|
216
|
+
props: ElementProps
|
|
217
|
+
state: MixinRuntimeState
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export type MixinRuntimeState = {
|
|
221
|
+
id: string
|
|
222
|
+
controller?: AbortController
|
|
223
|
+
aborted: boolean
|
|
224
|
+
handle?: AnyMixinHandle
|
|
225
|
+
runners: RunnerEntry[]
|
|
226
|
+
binding?: MixinRuntimeBinding
|
|
227
|
+
removePrepared?: boolean
|
|
228
|
+
pendingRemoval?: {
|
|
229
|
+
signal: AbortSignal
|
|
230
|
+
cancel: (reason?: unknown) => void
|
|
231
|
+
done: Promise<void>
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let mixinHandleId = 0
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Creates a typed mixin factory that can be passed through the `mix` prop.
|
|
239
|
+
*
|
|
240
|
+
* @param type Mixin setup function.
|
|
241
|
+
* @returns A function that captures mixin arguments and returns a descriptor.
|
|
242
|
+
*/
|
|
243
|
+
export function createMixin<
|
|
244
|
+
node extends EventTarget = Element,
|
|
245
|
+
args extends unknown[] = [],
|
|
246
|
+
props extends ElementProps = ElementProps,
|
|
247
|
+
>(type: MixinType<node, args, props>) {
|
|
248
|
+
return <boundNode extends node = node>(
|
|
249
|
+
...args: RebindTuple<args, node, boundNode>
|
|
250
|
+
): MixinDescriptor<boundNode, RebindTuple<args, node, boundNode>, props> => ({
|
|
251
|
+
type: type as unknown as MixinRuntimeType<RebindTuple<args, node, boundNode>, boundNode, props>,
|
|
252
|
+
args: args as RebindTuple<args, node, boundNode>,
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function resolveMixedProps(input: ResolveMixedPropsInput): ResolveMixedPropsOutput {
|
|
257
|
+
let state = input.state ?? createMixinRuntimeState()
|
|
258
|
+
let handle = state.handle as ScopedAnyMixinHandle | undefined
|
|
259
|
+
if (!handle) {
|
|
260
|
+
handle = createMixinHandle({
|
|
261
|
+
id: state.id,
|
|
262
|
+
hostType: input.hostType,
|
|
263
|
+
frame: input.frame,
|
|
264
|
+
scheduler: input.scheduler,
|
|
265
|
+
getContext: input.getContext ?? (() => undefined),
|
|
266
|
+
getRuntimeSignal: () => getMixinRuntimeSignal(state),
|
|
267
|
+
getBinding: () => state.binding,
|
|
268
|
+
}) as ScopedAnyMixinHandle
|
|
269
|
+
state.handle = handle
|
|
270
|
+
}
|
|
271
|
+
let hostType = input.hostType
|
|
272
|
+
let descriptors = resolveMixDescriptors(input.props)
|
|
273
|
+
let composedProps = withoutMix(input.props)
|
|
274
|
+
let mixinProps = withoutMixinTreeProps(composedProps)
|
|
275
|
+
let maxDescriptors = 1024
|
|
276
|
+
|
|
277
|
+
for (let index = 0; index < descriptors.length && index < maxDescriptors; index++) {
|
|
278
|
+
let descriptor = descriptors[index]
|
|
279
|
+
let entry = state.runners[index]
|
|
280
|
+
if (!entry || entry.type !== descriptor.type) {
|
|
281
|
+
if (entry) {
|
|
282
|
+
queueMixinRemove(handle, entry.scope)
|
|
283
|
+
}
|
|
284
|
+
let scope = Symbol('mixin-scope')
|
|
285
|
+
handle.setActiveScope(scope)
|
|
286
|
+
entry = {
|
|
287
|
+
scope,
|
|
288
|
+
type: descriptor.type as AnyMixinType,
|
|
289
|
+
runner: normalizeMixinRunner(
|
|
290
|
+
descriptor.type(handle, hostType) as AnyMixinSetupResult,
|
|
291
|
+
handle,
|
|
292
|
+
),
|
|
293
|
+
}
|
|
294
|
+
handle.setActiveScope(undefined)
|
|
295
|
+
state.runners[index] = entry
|
|
296
|
+
let binding = state.binding
|
|
297
|
+
if (binding?.node) {
|
|
298
|
+
queueMixinInsert(handle, entry.scope, binding.node, binding.parent, binding.key)
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
handle.setActiveScope(entry.scope)
|
|
303
|
+
let result = entry.runner(...descriptor.args, mixinProps)
|
|
304
|
+
handle.setActiveScope(undefined)
|
|
305
|
+
if (!result) continue
|
|
306
|
+
if (isMixinElement(result)) continue
|
|
307
|
+
|
|
308
|
+
let returnedDescriptors = resolveReturnedMixDescriptors(result)
|
|
309
|
+
if (returnedDescriptors) {
|
|
310
|
+
for (let returned of returnedDescriptors) descriptors.push(returned)
|
|
311
|
+
continue
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (!isRemixElement(result)) {
|
|
315
|
+
console.error(new Error('mixins must return a remix element'))
|
|
316
|
+
continue
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
let resultType =
|
|
320
|
+
typeof result.type === 'string'
|
|
321
|
+
? result.type
|
|
322
|
+
: isMixinElement(result.type)
|
|
323
|
+
? result.type.__rmxMixinElementType
|
|
324
|
+
: null
|
|
325
|
+
if (resultType !== hostType) {
|
|
326
|
+
console.error(new Error('mixins must return an element with the same host type'))
|
|
327
|
+
continue
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (result.type !== resultType) {
|
|
331
|
+
result = { ...result, type: resultType }
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
let nextProps = sanitizeReturnedMixinProps(result.props)
|
|
335
|
+
let nestedDescriptors = resolveMixDescriptors(nextProps)
|
|
336
|
+
for (let nested of nestedDescriptors) descriptors.push(nested)
|
|
337
|
+
composedProps = composeMixinProps(composedProps, withoutMix(nextProps))
|
|
338
|
+
mixinProps = withoutMixinTreeProps(composedProps)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
for (let index = descriptors.length; index < state.runners.length; index++) {
|
|
342
|
+
let entry = state.runners[index]
|
|
343
|
+
if (entry) {
|
|
344
|
+
handle.dispatchScopedEvent(entry.scope, new Event('remove'))
|
|
345
|
+
handle.releaseScope(entry.scope)
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (state.runners.length > descriptors.length) {
|
|
350
|
+
state.runners.length = descriptors.length
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
let nextMix = input.props.mix
|
|
354
|
+
return {
|
|
355
|
+
state,
|
|
356
|
+
props: {
|
|
357
|
+
...composedProps,
|
|
358
|
+
...(nextMix === undefined ? {} : { mix: nextMix }),
|
|
359
|
+
},
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export function teardownMixins(state?: MixinRuntimeState) {
|
|
364
|
+
if (!state) return
|
|
365
|
+
state.binding = undefined
|
|
366
|
+
prepareMixinRemoval(state)
|
|
367
|
+
cancelPendingMixinRemoval(state)
|
|
368
|
+
let handle = state.handle as ScopedAnyMixinHandle | undefined
|
|
369
|
+
if (handle) {
|
|
370
|
+
handle.queueCommitTask(() => finalizeMixinTeardown(state))
|
|
371
|
+
return
|
|
372
|
+
}
|
|
373
|
+
finalizeMixinTeardown(state)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function bindMixinRuntime(
|
|
377
|
+
state: MixinRuntimeState | undefined,
|
|
378
|
+
binding?: MixinRuntimeBinding,
|
|
379
|
+
options?: { dispatchReclaimed?: boolean },
|
|
380
|
+
) {
|
|
381
|
+
if (!state) return
|
|
382
|
+
let previousNode = state.binding?.node
|
|
383
|
+
let nextBinding = binding
|
|
384
|
+
state.binding = nextBinding
|
|
385
|
+
if (!nextBinding?.node || previousNode === nextBinding.node) return
|
|
386
|
+
let nextNode = nextBinding.node
|
|
387
|
+
let handle = state.handle as ScopedAnyMixinHandle | undefined
|
|
388
|
+
if (!handle) return
|
|
389
|
+
for (let entry of state.runners) {
|
|
390
|
+
if (options?.dispatchReclaimed) {
|
|
391
|
+
queueMixinReclaimed(handle, entry.scope, nextNode, nextBinding.parent, nextBinding.key)
|
|
392
|
+
} else {
|
|
393
|
+
queueMixinInsert(handle, entry.scope, nextNode, nextBinding.parent, nextBinding.key)
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export function prepareMixinRemoval(state?: MixinRuntimeState) {
|
|
399
|
+
if (!state || state.removePrepared) return state?.pendingRemoval?.done
|
|
400
|
+
state.removePrepared = true
|
|
401
|
+
|
|
402
|
+
let pendingRemoval: MixinRuntimeState['pendingRemoval']
|
|
403
|
+
let persistTeardowns: Array<(signal: AbortSignal) => void | Promise<void>> = []
|
|
404
|
+
let registerPersistNode = (teardown: (signal: AbortSignal) => void | Promise<void>) => {
|
|
405
|
+
persistTeardowns.push(teardown)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
let handle = state.handle as ScopedAnyMixinHandle | undefined
|
|
409
|
+
if (!handle) return
|
|
410
|
+
for (let entry of state.runners) {
|
|
411
|
+
dispatchMixinBeforeRemove(handle, entry.scope, registerPersistNode)
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (persistTeardowns.length > 0) {
|
|
415
|
+
let controller = new AbortController()
|
|
416
|
+
let done = Promise.allSettled(
|
|
417
|
+
persistTeardowns.map((teardown) => Promise.resolve().then(() => teardown(controller.signal))),
|
|
418
|
+
).then(() => {})
|
|
419
|
+
pendingRemoval = {
|
|
420
|
+
signal: controller.signal,
|
|
421
|
+
cancel(reason) {
|
|
422
|
+
controller.abort(reason)
|
|
423
|
+
},
|
|
424
|
+
done,
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
state.pendingRemoval = pendingRemoval
|
|
429
|
+
return pendingRemoval?.done
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export function cancelPendingMixinRemoval(
|
|
433
|
+
state?: MixinRuntimeState,
|
|
434
|
+
reason: unknown = new DOMException('', 'AbortError'),
|
|
435
|
+
) {
|
|
436
|
+
if (!state?.pendingRemoval) return
|
|
437
|
+
state.pendingRemoval.cancel(reason)
|
|
438
|
+
state.pendingRemoval = undefined
|
|
439
|
+
state.removePrepared = false
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function createMixinRuntimeState(): MixinRuntimeState {
|
|
443
|
+
return {
|
|
444
|
+
id: `m${++mixinHandleId}`,
|
|
445
|
+
aborted: false,
|
|
446
|
+
runners: [],
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function createMixinHandle(options: {
|
|
451
|
+
id: string
|
|
452
|
+
hostType: string
|
|
453
|
+
frame: FrameHandle
|
|
454
|
+
scheduler: Scheduler
|
|
455
|
+
getContext: MixinContext['get']
|
|
456
|
+
getRuntimeSignal: () => AbortSignal
|
|
457
|
+
getBinding: () => MixinRuntimeBinding | undefined
|
|
458
|
+
}): AnyMixinHandle {
|
|
459
|
+
return new MixinHandleImpl(options)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
class MixinHandleImpl
|
|
463
|
+
extends TypedEventTarget<MixinHandleEventMap<Element>>
|
|
464
|
+
implements ScopedAnyMixinHandle
|
|
465
|
+
{
|
|
466
|
+
id: string
|
|
467
|
+
context: MixinContext
|
|
468
|
+
frame: FrameHandle
|
|
469
|
+
element: MixinElement<Element, ElementProps>
|
|
470
|
+
#options: MixinHandleFactoryOptions
|
|
471
|
+
#phaseListenerCounts: Record<'beforeUpdate' | 'commit', number> = {
|
|
472
|
+
beforeUpdate: 0,
|
|
473
|
+
commit: 0,
|
|
474
|
+
}
|
|
475
|
+
#activeScope?: symbol
|
|
476
|
+
#scopeSignals = new Map<symbol, AbortController>()
|
|
477
|
+
#scopeTargets = new Map<symbol, TypedEventTarget<MixinHandleEventMap<Element>>>()
|
|
478
|
+
#scopePhaseCounts = new Map<symbol, Record<'beforeUpdate' | 'commit', number>>()
|
|
479
|
+
#onSchedulerBeforeUpdate = (event: Event) => {
|
|
480
|
+
this.#dispatchSchedulerPhaseToHandle('beforeUpdate', event as SchedulerPhaseEvent)
|
|
481
|
+
}
|
|
482
|
+
#onSchedulerCommit = (event: Event) => {
|
|
483
|
+
this.#dispatchSchedulerPhaseToHandle('commit', event as SchedulerPhaseEvent)
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
constructor(options: MixinHandleFactoryOptions) {
|
|
487
|
+
super()
|
|
488
|
+
this.#options = options
|
|
489
|
+
this.id = options.id
|
|
490
|
+
this.context = {
|
|
491
|
+
get: options.getContext,
|
|
492
|
+
}
|
|
493
|
+
this.frame = options.frame
|
|
494
|
+
|
|
495
|
+
let element = ((_: { update(): Promise<AbortSignal> }, __: unknown) =>
|
|
496
|
+
(props: ElementProps) => ({
|
|
497
|
+
$rmx: true as const,
|
|
498
|
+
type: options.hostType,
|
|
499
|
+
key: null,
|
|
500
|
+
props,
|
|
501
|
+
})) as unknown as MixinElement<Element, ElementProps>
|
|
502
|
+
element.__rmxMixinElementType = options.hostType
|
|
503
|
+
this.element = element
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
get signal() {
|
|
507
|
+
let scope = this.#activeScope
|
|
508
|
+
invariant(
|
|
509
|
+
scope,
|
|
510
|
+
'handle.signal is only available during mixin setup, render, or lifecycle callbacks',
|
|
511
|
+
)
|
|
512
|
+
return this.#getScopeSignal(scope)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
addEventListener(
|
|
516
|
+
type: string,
|
|
517
|
+
listener: EventListenerOrEventListenerObject | null,
|
|
518
|
+
options?: AddEventListenerOptions | boolean,
|
|
519
|
+
): void {
|
|
520
|
+
let target = this.#getActiveScopeTarget()
|
|
521
|
+
target.addEventListener(
|
|
522
|
+
type as keyof MixinHandleEventMap<Element>,
|
|
523
|
+
listener as EventListener,
|
|
524
|
+
options,
|
|
525
|
+
)
|
|
526
|
+
if (!listener || !isSchedulerPhaseType(type)) return
|
|
527
|
+
let scope = this.#activeScope
|
|
528
|
+
invariant(scope)
|
|
529
|
+
let scopePhaseCounts = this.#scopePhaseCounts.get(scope)
|
|
530
|
+
invariant(scopePhaseCounts)
|
|
531
|
+
scopePhaseCounts[type] += 1
|
|
532
|
+
this.#phaseListenerCounts[type] += 1
|
|
533
|
+
if (this.#phaseListenerCounts[type] !== 1) return
|
|
534
|
+
if (type === 'beforeUpdate') {
|
|
535
|
+
this.#options.scheduler.addEventListener('beforeUpdate', this.#onSchedulerBeforeUpdate)
|
|
536
|
+
} else {
|
|
537
|
+
this.#options.scheduler.addEventListener('commit', this.#onSchedulerCommit)
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
removeEventListener(
|
|
542
|
+
type: string,
|
|
543
|
+
listener: EventListenerOrEventListenerObject | null,
|
|
544
|
+
options?: EventListenerOptions | boolean,
|
|
545
|
+
): void {
|
|
546
|
+
let target = this.#getActiveScopeTarget()
|
|
547
|
+
target.removeEventListener(
|
|
548
|
+
type as keyof MixinHandleEventMap<Element>,
|
|
549
|
+
listener as EventListener,
|
|
550
|
+
typeof options === 'boolean' ? { capture: options } : options,
|
|
551
|
+
)
|
|
552
|
+
if (!listener || !isSchedulerPhaseType(type)) return
|
|
553
|
+
let scope = this.#activeScope
|
|
554
|
+
invariant(scope)
|
|
555
|
+
let scopePhaseCounts = this.#scopePhaseCounts.get(scope)
|
|
556
|
+
invariant(scopePhaseCounts)
|
|
557
|
+
scopePhaseCounts[type] = Math.max(0, scopePhaseCounts[type] - 1)
|
|
558
|
+
this.#phaseListenerCounts[type] = Math.max(0, this.#phaseListenerCounts[type] - 1)
|
|
559
|
+
if (this.#phaseListenerCounts[type] !== 0) return
|
|
560
|
+
if (type === 'beforeUpdate') {
|
|
561
|
+
this.#options.scheduler.removeEventListener('beforeUpdate', this.#onSchedulerBeforeUpdate)
|
|
562
|
+
} else {
|
|
563
|
+
this.#options.scheduler.removeEventListener('commit', this.#onSchedulerCommit)
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
update(): Promise<AbortSignal> {
|
|
568
|
+
return new Promise((resolve) => {
|
|
569
|
+
let signal = this.#options.getRuntimeSignal()
|
|
570
|
+
if (signal.aborted) {
|
|
571
|
+
resolve(signal)
|
|
572
|
+
return
|
|
573
|
+
}
|
|
574
|
+
let binding = this.#options.getBinding()
|
|
575
|
+
if (!binding) {
|
|
576
|
+
resolve(signal)
|
|
577
|
+
return
|
|
578
|
+
}
|
|
579
|
+
binding.enqueueUpdate(resolve)
|
|
580
|
+
})
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
queueTask(task: (node: Element, signal: AbortSignal) => void): void {
|
|
584
|
+
this.#options.scheduler.enqueueTasks([
|
|
585
|
+
() => {
|
|
586
|
+
let binding = this.#options.getBinding()
|
|
587
|
+
invariant(binding)
|
|
588
|
+
task(binding.node, this.#options.getRuntimeSignal())
|
|
589
|
+
},
|
|
590
|
+
])
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
queueCommitTask(task: () => void): void {
|
|
594
|
+
this.#options.scheduler.enqueueCommitPhase([task])
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
setActiveScope(scope?: symbol): void {
|
|
598
|
+
this.#activeScope = scope
|
|
599
|
+
if (!scope) return
|
|
600
|
+
if (this.#scopeTargets.has(scope)) return
|
|
601
|
+
this.#scopeSignals.set(scope, new AbortController())
|
|
602
|
+
this.#scopeTargets.set(scope, new TypedEventTarget<MixinHandleEventMap<Element>>())
|
|
603
|
+
this.#scopePhaseCounts.set(scope, { beforeUpdate: 0, commit: 0 })
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
dispatchScopedEvent(scope: symbol, event: Event): void {
|
|
607
|
+
let previousScope = this.#activeScope
|
|
608
|
+
this.#activeScope = scope
|
|
609
|
+
this.#scopeTargets.get(scope)?.dispatchEvent(event)
|
|
610
|
+
this.#activeScope = previousScope
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
releaseScope(scope: symbol): void {
|
|
614
|
+
let scopePhaseCounts = this.#scopePhaseCounts.get(scope)
|
|
615
|
+
if (scopePhaseCounts) {
|
|
616
|
+
this.#decrementGlobalPhaseCount('beforeUpdate', scopePhaseCounts.beforeUpdate)
|
|
617
|
+
this.#decrementGlobalPhaseCount('commit', scopePhaseCounts.commit)
|
|
618
|
+
}
|
|
619
|
+
this.#scopeSignals.get(scope)?.abort()
|
|
620
|
+
this.#scopeSignals.delete(scope)
|
|
621
|
+
this.#scopePhaseCounts.delete(scope)
|
|
622
|
+
this.#scopeTargets.delete(scope)
|
|
623
|
+
if (this.#activeScope === scope) {
|
|
624
|
+
this.#activeScope = undefined
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
#dispatchSchedulerPhaseToHandle(type: 'beforeUpdate' | 'commit', event: SchedulerPhaseEvent) {
|
|
629
|
+
let binding = this.#options.getBinding()
|
|
630
|
+
if (!binding) return
|
|
631
|
+
if (!isBindingInUpdateScope(binding, event.parents)) return
|
|
632
|
+
for (let [, target] of this.#scopeTargets) {
|
|
633
|
+
let updateEvent = new Event(type) as MixinUpdateEvent<Element>
|
|
634
|
+
updateEvent.node = binding.node
|
|
635
|
+
target.dispatchEvent(updateEvent)
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
#getActiveScopeTarget(): TypedEventTarget<MixinHandleEventMap<Element>> {
|
|
640
|
+
let scope = this.#activeScope
|
|
641
|
+
invariant(scope)
|
|
642
|
+
let target = this.#scopeTargets.get(scope)
|
|
643
|
+
invariant(target)
|
|
644
|
+
return target
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
#getScopeSignal(scope: symbol) {
|
|
648
|
+
let controller = this.#scopeSignals.get(scope)
|
|
649
|
+
invariant(controller)
|
|
650
|
+
return controller.signal
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
#decrementGlobalPhaseCount(type: 'beforeUpdate' | 'commit', amount: number) {
|
|
654
|
+
if (amount <= 0) return
|
|
655
|
+
this.#phaseListenerCounts[type] = Math.max(0, this.#phaseListenerCounts[type] - amount)
|
|
656
|
+
if (this.#phaseListenerCounts[type] !== 0) return
|
|
657
|
+
if (type === 'beforeUpdate') {
|
|
658
|
+
this.#options.scheduler.removeEventListener('beforeUpdate', this.#onSchedulerBeforeUpdate)
|
|
659
|
+
} else {
|
|
660
|
+
this.#options.scheduler.removeEventListener('commit', this.#onSchedulerCommit)
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
export function getMixinRuntimeSignal(state: MixinRuntimeState): AbortSignal {
|
|
666
|
+
let controller = state.controller
|
|
667
|
+
if (!controller) {
|
|
668
|
+
controller = new AbortController()
|
|
669
|
+
if (state.aborted) {
|
|
670
|
+
controller.abort()
|
|
671
|
+
}
|
|
672
|
+
state.controller = controller
|
|
673
|
+
}
|
|
674
|
+
return controller.signal
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
export function dispatchMixinBeforeUpdate(state?: MixinRuntimeState) {
|
|
678
|
+
dispatchMixinUpdateEvent(state, 'beforeUpdate')
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
export function dispatchMixinCommit(state?: MixinRuntimeState) {
|
|
682
|
+
dispatchMixinUpdateEvent(state, 'commit')
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function dispatchMixinInsert(
|
|
686
|
+
handle: ScopedAnyMixinHandle,
|
|
687
|
+
scope: symbol,
|
|
688
|
+
node: Element,
|
|
689
|
+
parent: ParentNode,
|
|
690
|
+
key?: string,
|
|
691
|
+
) {
|
|
692
|
+
let event = new Event('insert') as MixinInsertEvent<Element>
|
|
693
|
+
event.node = node
|
|
694
|
+
event.parent = parent
|
|
695
|
+
event.key = key
|
|
696
|
+
handle.dispatchScopedEvent(scope, event)
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function dispatchMixinReclaimed(
|
|
700
|
+
handle: ScopedAnyMixinHandle,
|
|
701
|
+
scope: symbol,
|
|
702
|
+
node: Element,
|
|
703
|
+
parent: ParentNode,
|
|
704
|
+
key?: string,
|
|
705
|
+
) {
|
|
706
|
+
let event = new Event('reclaimed') as MixinReclaimedEvent<Element>
|
|
707
|
+
event.node = node
|
|
708
|
+
event.parent = parent
|
|
709
|
+
event.key = key
|
|
710
|
+
handle.dispatchScopedEvent(scope, event)
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function dispatchMixinBeforeRemove(
|
|
714
|
+
handle: ScopedAnyMixinHandle,
|
|
715
|
+
scope: symbol,
|
|
716
|
+
persistNode: (teardown: (signal: AbortSignal) => void | Promise<void>) => void,
|
|
717
|
+
) {
|
|
718
|
+
let event = new Event('beforeRemove') as MixinBeforeRemoveEvent
|
|
719
|
+
event.persistNode = persistNode
|
|
720
|
+
handle.dispatchScopedEvent(scope, event)
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function queueMixinInsert(
|
|
724
|
+
handle: ScopedAnyMixinHandle,
|
|
725
|
+
scope: symbol,
|
|
726
|
+
node: Element,
|
|
727
|
+
parent: ParentNode,
|
|
728
|
+
key?: string,
|
|
729
|
+
) {
|
|
730
|
+
handle.queueCommitTask(() => {
|
|
731
|
+
dispatchMixinInsert(handle, scope, node, parent, key)
|
|
732
|
+
})
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function queueMixinReclaimed(
|
|
736
|
+
handle: ScopedAnyMixinHandle,
|
|
737
|
+
scope: symbol,
|
|
738
|
+
node: Element,
|
|
739
|
+
parent: ParentNode,
|
|
740
|
+
key?: string,
|
|
741
|
+
) {
|
|
742
|
+
handle.queueCommitTask(() => {
|
|
743
|
+
dispatchMixinReclaimed(handle, scope, node, parent, key)
|
|
744
|
+
})
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function queueMixinRemove(handle: ScopedAnyMixinHandle, scope: symbol) {
|
|
748
|
+
handle.queueCommitTask(() => {
|
|
749
|
+
handle.dispatchScopedEvent(scope, new Event('remove'))
|
|
750
|
+
handle.releaseScope(scope)
|
|
751
|
+
})
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function dispatchMixinRemoveEvent(state?: MixinRuntimeState) {
|
|
755
|
+
let runners = state?.runners
|
|
756
|
+
if (!runners?.length) return
|
|
757
|
+
let handle = state?.handle as ScopedAnyMixinHandle | undefined
|
|
758
|
+
if (!handle) return
|
|
759
|
+
for (let entry of runners) {
|
|
760
|
+
handle.dispatchScopedEvent(entry.scope, new Event('remove'))
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function finalizeMixinTeardown(state: MixinRuntimeState) {
|
|
765
|
+
dispatchMixinRemoveEvent(state)
|
|
766
|
+
let handle = state.handle as ScopedAnyMixinHandle | undefined
|
|
767
|
+
if (handle) {
|
|
768
|
+
for (let entry of state.runners) {
|
|
769
|
+
handle.releaseScope(entry.scope)
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
state.runners.length = 0
|
|
773
|
+
state.aborted = true
|
|
774
|
+
state.controller?.abort()
|
|
775
|
+
state.pendingRemoval = undefined
|
|
776
|
+
state.removePrepared = true
|
|
777
|
+
state.handle = undefined
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function dispatchMixinUpdateEvent(
|
|
781
|
+
state: MixinRuntimeState | undefined,
|
|
782
|
+
type: 'beforeUpdate' | 'commit',
|
|
783
|
+
) {
|
|
784
|
+
let node = state?.binding?.node
|
|
785
|
+
if (!node) return
|
|
786
|
+
let runners = state?.runners
|
|
787
|
+
if (!runners?.length) return
|
|
788
|
+
let handle = state?.handle as ScopedAnyMixinHandle | undefined
|
|
789
|
+
if (!handle) return
|
|
790
|
+
for (let entry of runners) {
|
|
791
|
+
let event = new Event(type) as MixinUpdateEvent<Element>
|
|
792
|
+
event.node = node
|
|
793
|
+
handle.dispatchScopedEvent(entry.scope, event)
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function isSchedulerPhaseType(type: string): type is 'beforeUpdate' | 'commit' {
|
|
798
|
+
return type === 'beforeUpdate' || type === 'commit'
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function isBindingInUpdateScope(binding: MixinRuntimeBinding, parents: ParentNode[]): boolean {
|
|
802
|
+
if (parents.length === 0) return false
|
|
803
|
+
let node = binding.node as Node
|
|
804
|
+
for (let parent of parents) {
|
|
805
|
+
let parentNode = parent as Node
|
|
806
|
+
if (parentNode === node) return true
|
|
807
|
+
if (parentNode.contains(node)) return true
|
|
808
|
+
}
|
|
809
|
+
return false
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function resolveMixDescriptors(props: ElementProps): AnyMixinDescriptor[] {
|
|
813
|
+
let mix = props.mix
|
|
814
|
+
if (!mix) return []
|
|
815
|
+
if (Array.isArray(mix)) {
|
|
816
|
+
if (mix.length === 0) return []
|
|
817
|
+
return mix.filter(Boolean) as AnyMixinDescriptor[]
|
|
818
|
+
}
|
|
819
|
+
return [mix] as AnyMixinDescriptor[]
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function withoutMix(props: ElementProps): ElementProps {
|
|
823
|
+
if (!('mix' in props)) return props
|
|
824
|
+
let output = { ...props }
|
|
825
|
+
delete output.mix
|
|
826
|
+
return output
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
function withoutMixinTreeProps(props: ElementProps): ElementProps {
|
|
830
|
+
if (!('children' in props) && !('innerHTML' in props)) return props
|
|
831
|
+
let output = { ...props }
|
|
832
|
+
delete output.children
|
|
833
|
+
delete output.innerHTML
|
|
834
|
+
return output
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function sanitizeReturnedMixinProps(props: ElementProps): ElementProps {
|
|
838
|
+
if (!('children' in props) && !('innerHTML' in props)) return props
|
|
839
|
+
console.error(new Error('mixins must not return children or innerHTML'))
|
|
840
|
+
return withoutMixinTreeProps(props)
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
function composeMixinProps(previous: ElementProps, next: ElementProps): ElementProps {
|
|
844
|
+
return { ...previous, ...next }
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function resolveReturnedMixDescriptors(value: unknown): AnyMixinDescriptor[] | null {
|
|
848
|
+
let descriptors: AnyMixinDescriptor[] = []
|
|
849
|
+
if (!collectReturnedMixDescriptors(value, descriptors)) {
|
|
850
|
+
return null
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
return descriptors
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function collectReturnedMixDescriptors(
|
|
857
|
+
value: unknown,
|
|
858
|
+
output: AnyMixinDescriptor[],
|
|
859
|
+
): value is MixInput<Element, ElementProps> {
|
|
860
|
+
if (!value) {
|
|
861
|
+
return true
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
if (Array.isArray(value)) {
|
|
865
|
+
for (let item of value) {
|
|
866
|
+
if (!collectReturnedMixDescriptors(item, output)) {
|
|
867
|
+
return false
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
return true
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
if (!isMixinDescriptor(value)) {
|
|
874
|
+
return false
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
output.push(value)
|
|
878
|
+
return true
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
function isRemixElement(value: unknown): value is RemixElement {
|
|
882
|
+
if (!value || typeof value !== 'object') return false
|
|
883
|
+
return (value as { $rmx?: unknown }).$rmx === true
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
function isMixinDescriptor(value: unknown): value is AnyMixinDescriptor {
|
|
887
|
+
if (!value || typeof value !== 'object' || isRemixElement(value)) {
|
|
888
|
+
return false
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
let descriptor = value as { type?: unknown; args?: unknown }
|
|
892
|
+
return typeof descriptor.type === 'function' && Array.isArray(descriptor.args)
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function isMixinElement(value: unknown): value is MixinElement<Element, ElementProps> {
|
|
896
|
+
if (typeof value !== 'function') return false
|
|
897
|
+
return '__rmxMixinElementType' in value
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function normalizeMixinRunner(result: AnyMixinSetupResult, handle: AnyMixinHandle): AnyMixinRunner {
|
|
901
|
+
if (typeof result === 'function' && !isMixinElement(result)) {
|
|
902
|
+
return result as AnyMixinRunner
|
|
903
|
+
}
|
|
904
|
+
if (result === undefined) {
|
|
905
|
+
return () => handle.element
|
|
906
|
+
}
|
|
907
|
+
return () => result as AnyMixinRunnerResult
|
|
908
|
+
}
|