@remix-run/ui 0.4.0 → 0.6.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.
- package/README.md +74 -1
- package/dist/accordion/index.js +2 -2
- package/dist/accordion/primitives.d.ts +1 -1
- package/dist/accordion/primitives.js.map +1 -1
- package/dist/anchor/index.js.map +1 -1
- package/dist/animation/animate-layout-mixin.js +1 -1
- package/dist/animation/animate-layout-mixin.js.map +1 -1
- package/dist/animation/animate-mixins.js +2 -2
- package/dist/animation/animate-mixins.js.map +1 -1
- package/dist/animation/demos/color-interpolation.js +1 -1
- package/dist/animation/demos/color-interpolation.js.map +1 -1
- package/dist/animation/demos/drag-release.d.ts +1 -1
- package/dist/animation/demos/hold-to-confirm.d.ts +3 -3
- package/dist/animation/demos/material-ripple.js +1 -3
- package/dist/animation/demos/material-ripple.js.map +1 -1
- package/dist/animation/index.js +4 -4
- package/dist/animation/spring.js.map +1 -1
- package/dist/breadcrumbs/index.js +2 -2
- package/dist/button/index.js +25 -25
- package/dist/button/index.js.map +1 -1
- package/dist/checkbox/index.js +11 -9
- package/dist/checkbox/index.js.map +1 -1
- package/dist/combobox/index.js +4 -4
- package/dist/combobox/index.js.map +1 -1
- package/dist/combobox/primitives.d.ts +1 -1
- package/dist/combobox/primitives.js +5 -5
- package/dist/combobox/primitives.js.map +1 -1
- package/dist/dev/refresh.d.ts +1 -0
- package/dist/dev/refresh.js +2 -0
- package/dist/dev/refresh.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +14 -14
- package/dist/input/index.js +9 -9
- package/dist/input/index.js.map +1 -1
- package/dist/jsx-runtime.js +2 -2
- package/dist/listbox/index.js +2 -2
- package/dist/menu/hover-aim.js.map +1 -1
- package/dist/menu/index.js +3 -3
- package/dist/menu/primitives.d.ts +1 -1
- package/dist/menu/primitives.js +6 -6
- package/dist/menu/primitives.js.map +1 -1
- package/dist/popover/index.js +3 -3
- package/dist/popover/scroll-lock.js.map +1 -1
- package/dist/radio/index.js +7 -7
- package/dist/radio/index.js.map +1 -1
- package/dist/runtime/client-entry-boundary.d.ts +14 -0
- package/dist/runtime/client-entry-boundary.js +29 -0
- package/dist/runtime/client-entry-boundary.js.map +1 -0
- package/dist/runtime/component.d.ts +19 -4
- package/dist/runtime/component.js +39 -9
- package/dist/runtime/component.js.map +1 -1
- package/dist/runtime/core/attributes.js +36 -3
- package/dist/runtime/core/attributes.js.map +1 -1
- package/dist/runtime/core/children.d.ts +2 -0
- package/dist/runtime/core/children.js +26 -2
- package/dist/runtime/core/children.js.map +1 -1
- package/dist/runtime/core/props.js +2 -2
- package/dist/runtime/core/vnode.d.ts +3 -3
- package/dist/runtime/core/vnode.js +22 -1
- package/dist/runtime/core/vnode.js.map +1 -1
- package/dist/runtime/create-element.d.ts +2 -2
- package/dist/runtime/create-element.js +3 -2
- package/dist/runtime/create-element.js.map +1 -1
- package/dist/runtime/diff-dom.js +307 -220
- package/dist/runtime/diff-dom.js.map +1 -1
- package/dist/runtime/diff-props.js +3 -3
- package/dist/runtime/dom.d.ts +12 -0
- package/dist/runtime/element-function.d.ts +3 -0
- package/dist/runtime/element-function.js +2 -0
- package/dist/runtime/element-function.js.map +1 -0
- package/dist/runtime/form-navigation.d.ts +25 -0
- package/dist/runtime/form-navigation.js +132 -0
- package/dist/runtime/form-navigation.js.map +1 -0
- package/dist/runtime/frame-resolution.d.ts +5 -0
- package/dist/runtime/frame-resolution.js +10 -0
- package/dist/runtime/frame-resolution.js.map +1 -0
- package/dist/runtime/frame.d.ts +59 -15
- package/dist/runtime/frame.js +383 -114
- package/dist/runtime/frame.js.map +1 -1
- package/dist/runtime/jsx.d.ts +1 -1
- package/dist/runtime/jsx.js +1 -1
- package/dist/runtime/jsx.js.map +1 -1
- package/dist/runtime/key.d.ts +1 -0
- package/dist/runtime/key.js +2 -0
- package/dist/runtime/key.js.map +1 -0
- package/dist/runtime/mixins/attrs-mixin.js +2 -2
- package/dist/runtime/mixins/link-mixin.js +4 -3
- package/dist/runtime/mixins/link-mixin.js.map +1 -1
- package/dist/runtime/mixins/mixin.d.ts +15 -10
- package/dist/runtime/mixins/mixin.js +4 -4
- package/dist/runtime/mixins/mixin.js.map +1 -1
- package/dist/runtime/mixins/on-mixin.js +1 -1
- package/dist/runtime/mixins/ref-mixin.js +1 -1
- package/dist/runtime/module-preloader.d.ts +8 -0
- package/dist/runtime/module-preloader.js +95 -0
- package/dist/runtime/module-preloader.js.map +1 -0
- package/dist/runtime/navigation.d.ts +4 -1
- package/dist/runtime/navigation.js +157 -32
- package/dist/runtime/navigation.js.map +1 -1
- package/dist/runtime/reconcile.d.ts +11 -10
- package/dist/runtime/reconcile.js +549 -421
- package/dist/runtime/reconcile.js.map +1 -1
- package/dist/runtime/refresh.d.ts +28 -0
- package/dist/runtime/refresh.js +31 -0
- package/dist/runtime/refresh.js.map +1 -0
- package/dist/runtime/render.js +1 -1
- package/dist/runtime/render.js.map +1 -1
- package/dist/runtime/run.d.ts +3 -1
- package/dist/runtime/run.js +15 -7
- package/dist/runtime/run.js.map +1 -1
- package/dist/runtime/scheduler.js +8 -16
- package/dist/runtime/scheduler.js.map +1 -1
- package/dist/runtime/to-vnode.d.ts +2 -2
- package/dist/runtime/to-vnode.js +68 -20
- package/dist/runtime/to-vnode.js.map +1 -1
- package/dist/runtime/vdom.d.ts +4 -2
- package/dist/runtime/vdom.js +87 -42
- package/dist/runtime/vdom.js.map +1 -1
- package/dist/runtime/vnode.d.ts +138 -67
- package/dist/runtime/vnode.js +17 -12
- package/dist/runtime/vnode.js.map +1 -1
- package/dist/select/index.js +11 -11
- package/dist/select/index.js.map +1 -1
- package/dist/select/primitives.d.ts +1 -1
- package/dist/select/primitives.js +6 -6
- package/dist/server/stream.d.ts +2 -0
- package/dist/server/stream.js +74 -29
- package/dist/server/stream.js.map +1 -1
- package/dist/shared/focus-styles.d.ts +1 -1
- package/dist/shared/focus-styles.js +1 -1
- package/dist/shared/focus-styles.js.map +1 -1
- package/dist/shared/listbox-popover-styles.js +1 -1
- package/dist/shared/style-values.js +26 -26
- package/dist/shared/style-values.js.map +1 -1
- package/dist/style/css-mixin.js +3 -3
- package/dist/style/index.js +2 -2
- package/dist/style/style.js +25 -6
- package/dist/style/style.js.map +1 -1
- package/dist/style/stylesheet.d.ts +0 -1
- package/dist/style/stylesheet.js +13 -54
- package/dist/style/stylesheet.js.map +1 -1
- package/dist/tabs/index.js +11 -11
- package/dist/tabs/index.js.map +1 -1
- package/dist/tabs/primitives.d.ts +1 -1
- package/dist/test.js +1 -1
- package/dist/toggle/index.js +9 -9
- package/dist/toggle/index.js.map +1 -1
- package/dist/toggle/primitives.d.ts +1 -1
- package/package.json +10 -7
- package/src/animation/animate-mixins.ts +3 -3
- package/src/animation/demos/color-interpolation.tsx +1 -1
- package/src/button/index.ts +24 -24
- package/src/checkbox/index.ts +10 -8
- package/src/combobox/index.tsx +1 -1
- package/src/dev/refresh.ts +1 -0
- package/src/index.ts +1 -1
- package/src/input/index.ts +8 -8
- package/src/radio/index.ts +6 -5
- package/src/runtime/client-entry-boundary.ts +54 -0
- package/src/runtime/component.ts +65 -16
- package/src/runtime/core/attributes.ts +35 -1
- package/src/runtime/core/children.ts +24 -2
- package/src/runtime/core/vnode.ts +25 -4
- package/src/runtime/create-element.ts +6 -5
- package/src/runtime/demos/readme.demo.tsx +4 -4
- package/src/runtime/diff-dom.ts +370 -244
- package/src/runtime/dom.ts +331 -214
- package/src/runtime/element-function.ts +1 -0
- package/src/runtime/event-listeners.ts +1 -1
- package/src/runtime/form-navigation.ts +184 -0
- package/src/runtime/frame-resolution.ts +13 -0
- package/src/runtime/frame.ts +500 -124
- package/src/runtime/jsx.ts +3 -5
- package/src/runtime/key.ts +1 -0
- package/src/runtime/mixins/link-mixin.ts +1 -0
- package/src/runtime/mixins/mixin.ts +21 -18
- package/src/runtime/module-preloader.ts +115 -0
- package/src/runtime/navigation.ts +204 -30
- package/src/runtime/reconcile.ts +762 -759
- package/src/runtime/refresh.ts +46 -0
- package/src/runtime/run.ts +12 -2
- package/src/runtime/scheduler.ts +9 -29
- package/src/runtime/to-vnode.ts +85 -23
- package/src/runtime/vdom.ts +89 -70
- package/src/runtime/vnode.ts +192 -88
- package/src/select/index.tsx +9 -9
- package/src/server/README.md +13 -0
- package/src/server/stream.ts +93 -30
- package/src/shared/focus-styles.ts +1 -1
- package/src/shared/style-values.ts +26 -26
- package/src/style/style.ts +25 -6
- package/src/style/stylesheet.ts +32 -57
- package/src/tabs/index.tsx +12 -11
- package/src/test/README.md +151 -60
- package/src/toggle/index.tsx +9 -7
package/src/runtime/reconcile.ts
CHANGED
|
@@ -1,33 +1,43 @@
|
|
|
1
1
|
import type { Component, ComponentHandle, FrameContent, FrameHandle } from './component.ts'
|
|
2
|
-
import { createComponent
|
|
3
|
-
import type {
|
|
4
|
-
import { createFrame } from './frame.ts'
|
|
2
|
+
import { createComponent } from './component.ts'
|
|
3
|
+
import type { FrameRuntime } from './frame.ts'
|
|
4
|
+
import { createFrame, isFrameRuntime } from './frame.ts'
|
|
5
|
+
import { unwrapFrameResolution } from './frame-resolution.ts'
|
|
5
6
|
import { createRangeRoot } from './vdom.ts'
|
|
6
7
|
import type {
|
|
8
|
+
CommittedFragmentNode,
|
|
9
|
+
CommittedFrameNode,
|
|
10
|
+
CommittedNonRenderNode,
|
|
7
11
|
ComponentNode,
|
|
8
12
|
CommittedComponentNode,
|
|
9
13
|
CommittedHostNode,
|
|
10
14
|
CommittedTextNode,
|
|
15
|
+
ControlledReflectionState,
|
|
16
|
+
DirectEventBinding,
|
|
17
|
+
DirectEventState,
|
|
18
|
+
FrameNode,
|
|
11
19
|
HostNode,
|
|
20
|
+
MountingComponentNode,
|
|
21
|
+
ReconcileContext,
|
|
22
|
+
RuntimeHostProps,
|
|
12
23
|
TextNode,
|
|
13
|
-
|
|
14
|
-
|
|
24
|
+
VNodeInput,
|
|
25
|
+
VNodeParent,
|
|
26
|
+
CommittedVNode,
|
|
15
27
|
} from './vnode.ts'
|
|
16
28
|
import {
|
|
17
29
|
isCommittedComponentNode,
|
|
18
|
-
isComponentNode,
|
|
19
30
|
isCommittedHostNode,
|
|
20
31
|
isCommittedTextNode,
|
|
21
32
|
isFragmentNode,
|
|
22
33
|
isHostNode,
|
|
23
|
-
isNonRenderNode,
|
|
24
|
-
isTextNode,
|
|
25
34
|
findContextFromAncestry,
|
|
26
35
|
} from './vnode.ts'
|
|
27
36
|
import { invariant } from './invariant.ts'
|
|
28
37
|
import { patchHostProps } from './core/props.ts'
|
|
29
38
|
import type { StyleManager } from '../style/index.ts'
|
|
30
|
-
import type {
|
|
39
|
+
import type { ElementFunction } from './element-function.ts'
|
|
40
|
+
import type { Key } from './key.ts'
|
|
31
41
|
import { skipComments, logHydrationMismatch } from './client-entries.ts'
|
|
32
42
|
import type { Scheduler } from './scheduler.ts'
|
|
33
43
|
import { toVNode } from './to-vnode.ts'
|
|
@@ -44,6 +54,8 @@ import {
|
|
|
44
54
|
type MixinRuntimeState,
|
|
45
55
|
} from './mixins/mixin.ts'
|
|
46
56
|
import { isOnMixinDescriptor, type OnMixinDescriptor } from './mixins/on-mixin.ts'
|
|
57
|
+
import { createComponentErrorEvent } from './error-event.ts'
|
|
58
|
+
import { componentStalenessCheck } from './refresh.ts'
|
|
47
59
|
|
|
48
60
|
const SVG_NS = 'http://www.w3.org/2000/svg'
|
|
49
61
|
|
|
@@ -54,45 +66,41 @@ let activeSchedulerUpdateParents: ParentNode[] | undefined
|
|
|
54
66
|
|
|
55
67
|
// Compute SVG context for a node based on its parent and type.
|
|
56
68
|
// Returns true if the node is within an SVG subtree, false otherwise.
|
|
57
|
-
function getSvgContext(vParent:
|
|
69
|
+
function getSvgContext(vParent: VNodeParent, node: VNodeInput): boolean {
|
|
58
70
|
// Only host elements (strings) can affect SVG context
|
|
59
|
-
if (
|
|
71
|
+
if (node.kind === 'host') {
|
|
60
72
|
// svg element creates SVG context
|
|
61
|
-
if (
|
|
73
|
+
if (node.type === 'svg') return true
|
|
62
74
|
// foreignObject switches back to HTML context
|
|
63
|
-
if (
|
|
75
|
+
if (node.type === 'foreignObject') return false
|
|
64
76
|
}
|
|
65
77
|
// Otherwise inherit from parent
|
|
66
|
-
return vParent._svg
|
|
78
|
+
return vParent._svg
|
|
67
79
|
}
|
|
68
80
|
|
|
69
|
-
function getHostProps(node: HostNode | CommittedHostNode):
|
|
70
|
-
return node._mixedProps
|
|
81
|
+
function getHostProps(node: HostNode | CommittedHostNode): RuntimeHostProps {
|
|
82
|
+
return '_mixedProps' in node ? node._mixedProps : node.props
|
|
71
83
|
}
|
|
72
84
|
|
|
73
85
|
function markNodePersistedByMixins(node: CommittedHostNode, domParent: ParentNode, token: number) {
|
|
74
|
-
node.
|
|
75
|
-
node._persistedParentByMixins = domParent
|
|
76
|
-
node._persistedRemovalToken = token
|
|
86
|
+
node._persistence = { parent: domParent, token }
|
|
77
87
|
persistedMixinNodes.add(node)
|
|
78
|
-
bindMixinRuntime(node._mixState
|
|
88
|
+
bindMixinRuntime(node._mixState, undefined)
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
function unmarkNodePersistedByMixins(node: CommittedHostNode) {
|
|
82
|
-
node.
|
|
83
|
-
node._persistedParentByMixins = undefined
|
|
84
|
-
node._persistedRemovalToken = undefined
|
|
92
|
+
node._persistence = undefined
|
|
85
93
|
persistedMixinNodes.delete(node)
|
|
86
94
|
}
|
|
87
95
|
|
|
88
96
|
function findMatchingPersistedMixinNode(
|
|
89
97
|
type: string,
|
|
90
|
-
key:
|
|
98
|
+
key: Key | undefined,
|
|
91
99
|
domParent: ParentNode,
|
|
92
100
|
): CommittedHostNode | null {
|
|
93
101
|
if (key == null) return null
|
|
94
102
|
for (let node of persistedMixinNodes) {
|
|
95
|
-
if (node.
|
|
103
|
+
if (node._persistence?.parent !== domParent) continue
|
|
96
104
|
if (node.type !== type) continue
|
|
97
105
|
if (node.key !== key) continue
|
|
98
106
|
return node
|
|
@@ -100,33 +108,10 @@ function findMatchingPersistedMixinNode(
|
|
|
100
108
|
return null
|
|
101
109
|
}
|
|
102
110
|
|
|
103
|
-
type ControlledReflectionState = {
|
|
104
|
-
disposed: boolean
|
|
105
|
-
listenersAttached: boolean
|
|
106
|
-
pendingRestoreVersion: number
|
|
107
|
-
managesValue: boolean
|
|
108
|
-
managesChecked: boolean
|
|
109
|
-
hasControlledValue: boolean
|
|
110
|
-
controlledValue: unknown
|
|
111
|
-
hasControlledChecked: boolean
|
|
112
|
-
controlledChecked: unknown
|
|
113
|
-
onInput: () => void
|
|
114
|
-
onChange: () => void
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
type DirectEventBinding = {
|
|
118
|
-
type: string
|
|
119
|
-
handler: (event: Event, signal: AbortSignal) => void | Promise<void>
|
|
120
|
-
capture: boolean
|
|
121
|
-
stableHandler: ((event: Event) => void) | null
|
|
122
|
-
reentry: AbortController | null
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
type DirectEventState = {
|
|
126
|
-
bindings: DirectEventBinding[]
|
|
127
|
-
}
|
|
128
|
-
|
|
129
111
|
const EMPTY_DIRECT_EVENT_DESCRIPTORS: OnMixinDescriptor[] = []
|
|
112
|
+
const EMPTY_COMMITTED_CHILDREN: CommittedVNode[] = []
|
|
113
|
+
|
|
114
|
+
type HydrationCursor = { current: Node | null | undefined }
|
|
130
115
|
|
|
131
116
|
function shouldRestoreControlledReflectionOnInput(
|
|
132
117
|
node: CommittedHostNode,
|
|
@@ -144,7 +129,7 @@ function ensureControlledReflection(
|
|
|
144
129
|
node: CommittedHostNode,
|
|
145
130
|
scheduler: Scheduler,
|
|
146
131
|
): ControlledReflectionState {
|
|
147
|
-
let existing = node._controlledState
|
|
132
|
+
let existing = node._controlledState
|
|
148
133
|
if (existing) return existing
|
|
149
134
|
|
|
150
135
|
let state: ControlledReflectionState = {
|
|
@@ -178,8 +163,8 @@ function ensureControlledReflection(
|
|
|
178
163
|
return state
|
|
179
164
|
}
|
|
180
165
|
|
|
181
|
-
function syncControlledReflection(node: CommittedHostNode, props:
|
|
182
|
-
let state = node._controlledState
|
|
166
|
+
function syncControlledReflection(node: CommittedHostNode, props: RuntimeHostProps): void {
|
|
167
|
+
let state = node._controlledState
|
|
183
168
|
if (!state || state.disposed) return
|
|
184
169
|
|
|
185
170
|
state.managesValue = canManageValue(node.type, node._dom)
|
|
@@ -191,7 +176,7 @@ function syncControlledReflection(node: CommittedHostNode, props: ElementProps):
|
|
|
191
176
|
state.pendingRestoreVersion++
|
|
192
177
|
}
|
|
193
178
|
|
|
194
|
-
function shouldTrackControlledReflection(props:
|
|
179
|
+
function shouldTrackControlledReflection(props: RuntimeHostProps): boolean {
|
|
195
180
|
return hasControlledValueProp(props) || hasControlledCheckedProp(props)
|
|
196
181
|
}
|
|
197
182
|
|
|
@@ -222,7 +207,7 @@ function restoreControlledReflections(
|
|
|
222
207
|
}
|
|
223
208
|
|
|
224
209
|
function teardownControlledReflection(node: CommittedHostNode): void {
|
|
225
|
-
let state = node._controlledState
|
|
210
|
+
let state = node._controlledState
|
|
226
211
|
if (!state) return
|
|
227
212
|
state.disposed = true
|
|
228
213
|
state.pendingRestoreVersion++
|
|
@@ -233,16 +218,25 @@ function teardownControlledReflection(node: CommittedHostNode): void {
|
|
|
233
218
|
}
|
|
234
219
|
}
|
|
235
220
|
|
|
221
|
+
// See abandonDirectEventListeners: skips removeEventListener for discarded subtrees.
|
|
222
|
+
function abandonControlledReflection(node: CommittedHostNode): void {
|
|
223
|
+
let state = node._controlledState
|
|
224
|
+
if (!state) return
|
|
225
|
+
state.disposed = true
|
|
226
|
+
state.pendingRestoreVersion++
|
|
227
|
+
state.listenersAttached = false
|
|
228
|
+
}
|
|
229
|
+
|
|
236
230
|
function canManageValue(type: string, element: Element): boolean {
|
|
237
231
|
if (type === 'progress') return false
|
|
238
232
|
return canReflectProperty(element, 'value')
|
|
239
233
|
}
|
|
240
234
|
|
|
241
|
-
function hasControlledValueProp(props:
|
|
235
|
+
function hasControlledValueProp(props: RuntimeHostProps): boolean {
|
|
242
236
|
return 'value' in props && props.value !== undefined
|
|
243
237
|
}
|
|
244
238
|
|
|
245
|
-
function hasControlledCheckedProp(props:
|
|
239
|
+
function hasControlledCheckedProp(props: RuntimeHostProps): boolean {
|
|
246
240
|
return 'checked' in props && props.checked !== undefined
|
|
247
241
|
}
|
|
248
242
|
|
|
@@ -263,29 +257,28 @@ function setPropertyReflection(element: Element, key: string, value: unknown): v
|
|
|
263
257
|
element[key] = value == null ? '' : value
|
|
264
258
|
}
|
|
265
259
|
|
|
260
|
+
type ResolvedHostProps = {
|
|
261
|
+
props: RuntimeHostProps
|
|
262
|
+
mixState?: MixinRuntimeState
|
|
263
|
+
directEventDescriptors?: OnMixinDescriptor[]
|
|
264
|
+
}
|
|
265
|
+
|
|
266
266
|
function resolveNodeMixProps(
|
|
267
|
-
node: HostNode,
|
|
267
|
+
node: HostNode | CommittedHostNode,
|
|
268
|
+
parent: VNodeParent,
|
|
268
269
|
frame: FrameHandle,
|
|
269
270
|
scheduler: Scheduler,
|
|
270
271
|
state?: MixinRuntimeState,
|
|
271
|
-
):
|
|
272
|
+
): ResolvedHostProps {
|
|
272
273
|
let mix = node.props.mix
|
|
273
274
|
let directEventDescriptors = resolveDirectEventDescriptors(mix)
|
|
274
275
|
if (directEventDescriptors) {
|
|
275
|
-
if (state)
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
node._mixState = undefined
|
|
279
|
-
node._mixedProps = node.props
|
|
280
|
-
node._directEventDescriptors = directEventDescriptors
|
|
281
|
-
return node.props
|
|
276
|
+
if (state) teardownMixins(state)
|
|
277
|
+
return { props: node.props, directEventDescriptors }
|
|
282
278
|
}
|
|
283
279
|
|
|
284
|
-
node._directEventDescriptors = undefined
|
|
285
280
|
if (state == null && (mix == null || (Array.isArray(mix) && mix.length === 0))) {
|
|
286
|
-
node.
|
|
287
|
-
node._mixedProps = node.props
|
|
288
|
-
return node.props
|
|
281
|
+
return { props: node.props }
|
|
289
282
|
}
|
|
290
283
|
|
|
291
284
|
let resolved = resolveMixedProps({
|
|
@@ -297,17 +290,21 @@ function resolveNodeMixProps(
|
|
|
297
290
|
return undefined
|
|
298
291
|
}
|
|
299
292
|
|
|
300
|
-
return findContextFromAncestry(
|
|
293
|
+
return findContextFromAncestry(parent, type)
|
|
301
294
|
},
|
|
302
295
|
props: node.props,
|
|
303
296
|
state,
|
|
304
297
|
})
|
|
305
|
-
|
|
298
|
+
return { props: resolved.props, mixState: resolved.state }
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function applyResolvedHostProps(node: CommittedHostNode, resolved: ResolvedHostProps): void {
|
|
306
302
|
node._mixedProps = resolved.props
|
|
307
|
-
|
|
303
|
+
node._mixState = resolved.mixState
|
|
304
|
+
node._directEventDescriptors = resolved.directEventDescriptors
|
|
308
305
|
}
|
|
309
306
|
|
|
310
|
-
function resolveDirectEventDescriptors(mix:
|
|
307
|
+
function resolveDirectEventDescriptors(mix: RuntimeHostProps['mix']): OnMixinDescriptor[] | null {
|
|
311
308
|
if (!mix) return EMPTY_DIRECT_EVENT_DESCRIPTORS
|
|
312
309
|
if (!Array.isArray(mix)) {
|
|
313
310
|
return isOnMixinDescriptor(mix) ? [mix] : null
|
|
@@ -317,18 +314,18 @@ function resolveDirectEventDescriptors(mix: ElementProps['mix']): OnMixinDescrip
|
|
|
317
314
|
}
|
|
318
315
|
|
|
319
316
|
function areOnMixinDescriptors(descriptors: unknown[]): descriptors is OnMixinDescriptor[] {
|
|
320
|
-
for (let
|
|
321
|
-
if (!isOnMixinDescriptor(
|
|
317
|
+
for (let i = 0; i < descriptors.length; i++) {
|
|
318
|
+
if (!isOnMixinDescriptor(descriptors[i])) return false
|
|
322
319
|
}
|
|
323
320
|
return true
|
|
324
321
|
}
|
|
325
322
|
|
|
326
323
|
function enqueueMixinBindingUpdate(
|
|
327
|
-
this: MixinRuntimeBinding
|
|
324
|
+
this: MixinRuntimeBinding<CommittedHostNode>,
|
|
328
325
|
done: (signal: AbortSignal) => void,
|
|
329
326
|
): void {
|
|
330
|
-
let node = this.target
|
|
331
|
-
let state = node._mixState
|
|
327
|
+
let node = this.target
|
|
328
|
+
let state = node._mixState
|
|
332
329
|
this.scheduler.enqueueWork([
|
|
333
330
|
() => {
|
|
334
331
|
if (state?.aborted) {
|
|
@@ -338,11 +335,12 @@ function enqueueMixinBindingUpdate(
|
|
|
338
335
|
|
|
339
336
|
dispatchMixinBeforeUpdate(state)
|
|
340
337
|
let prevProps = getHostProps(node)
|
|
341
|
-
let
|
|
342
|
-
|
|
343
|
-
|
|
338
|
+
let resolved = resolveNodeMixProps(node, node._parent, this.frame, this.scheduler, state)
|
|
339
|
+
applyResolvedHostProps(node, resolved)
|
|
340
|
+
patchHostProps(prevProps, resolved.props, this.node)
|
|
341
|
+
if (node._controlledState || shouldTrackControlledReflection(resolved.props)) {
|
|
344
342
|
ensureControlledReflection(node, this.scheduler)
|
|
345
|
-
syncControlledReflection(node,
|
|
343
|
+
syncControlledReflection(node, resolved.props)
|
|
346
344
|
}
|
|
347
345
|
|
|
348
346
|
dispatchMixinCommit(state)
|
|
@@ -359,12 +357,12 @@ function bindNodeMixRuntime(
|
|
|
359
357
|
reclaimed: boolean = false,
|
|
360
358
|
parent?: ParentNode,
|
|
361
359
|
) {
|
|
362
|
-
let state = node._mixState
|
|
360
|
+
let state = node._mixState
|
|
363
361
|
bindMixinRuntime(
|
|
364
362
|
state,
|
|
365
363
|
{
|
|
366
364
|
node: node._dom,
|
|
367
|
-
parent: parent ?? (node._dom
|
|
365
|
+
parent: parent ?? getRequiredDomParent(node._dom),
|
|
368
366
|
key: node.key,
|
|
369
367
|
target: node,
|
|
370
368
|
frame,
|
|
@@ -375,12 +373,18 @@ function bindNodeMixRuntime(
|
|
|
375
373
|
)
|
|
376
374
|
}
|
|
377
375
|
|
|
378
|
-
function isHeadHostNode(node: HostNode): boolean {
|
|
376
|
+
function isHeadHostNode(node: HostNode | CommittedHostNode): boolean {
|
|
379
377
|
if (node.type === 'head') return true
|
|
380
378
|
if (node.type.length !== 4) return false
|
|
381
379
|
return node.type.toLowerCase() === 'head'
|
|
382
380
|
}
|
|
383
381
|
|
|
382
|
+
function getRequiredDomParent(node: Node): ParentNode {
|
|
383
|
+
let parent = node.parentNode
|
|
384
|
+
invariant(parent, 'Expected mounted host node to have a DOM parent')
|
|
385
|
+
return parent
|
|
386
|
+
}
|
|
387
|
+
|
|
384
388
|
function getDocumentHead(domParent: ParentNode): HTMLHeadElement | null {
|
|
385
389
|
if (domParent instanceof Document) {
|
|
386
390
|
return domParent.head
|
|
@@ -391,126 +395,199 @@ function getDocumentHead(domParent: ParentNode): HTMLHeadElement | null {
|
|
|
391
395
|
return null
|
|
392
396
|
}
|
|
393
397
|
|
|
398
|
+
function commitNonRenderNode(
|
|
399
|
+
node: VNodeInput & { kind: 'empty' },
|
|
400
|
+
parent: VNodeParent,
|
|
401
|
+
svg: boolean,
|
|
402
|
+
): CommittedNonRenderNode {
|
|
403
|
+
let committed = node as unknown as CommittedNonRenderNode
|
|
404
|
+
committed._parent = parent
|
|
405
|
+
committed._svg = svg
|
|
406
|
+
return committed
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function commitTextNode(
|
|
410
|
+
node: TextNode,
|
|
411
|
+
parent: VNodeParent,
|
|
412
|
+
svg: boolean,
|
|
413
|
+
dom: Text,
|
|
414
|
+
): CommittedTextNode {
|
|
415
|
+
let committed = node as unknown as CommittedTextNode
|
|
416
|
+
committed._parent = parent
|
|
417
|
+
committed._svg = svg
|
|
418
|
+
committed._dom = dom
|
|
419
|
+
return committed
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function beginFragmentNode(
|
|
423
|
+
node: VNodeInput & { kind: 'fragment' },
|
|
424
|
+
parent: VNodeParent,
|
|
425
|
+
svg: boolean,
|
|
426
|
+
): CommittedFragmentNode {
|
|
427
|
+
let committed = node as unknown as CommittedFragmentNode
|
|
428
|
+
committed._parent = parent
|
|
429
|
+
committed._svg = svg
|
|
430
|
+
committed._children = EMPTY_COMMITTED_CHILDREN
|
|
431
|
+
return committed
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function commitHostNode(
|
|
435
|
+
node: HostNode,
|
|
436
|
+
parent: VNodeParent,
|
|
437
|
+
svg: boolean,
|
|
438
|
+
dom: Element,
|
|
439
|
+
resolved: ResolvedHostProps,
|
|
440
|
+
children: CommittedVNode[] = EMPTY_COMMITTED_CHILDREN,
|
|
441
|
+
): CommittedHostNode {
|
|
442
|
+
let committed = node as unknown as CommittedHostNode
|
|
443
|
+
committed._parent = parent
|
|
444
|
+
committed._svg = svg
|
|
445
|
+
committed._dom = dom
|
|
446
|
+
committed._children = children
|
|
447
|
+
committed._mixedProps = resolved.props
|
|
448
|
+
committed._mixState = resolved.mixState
|
|
449
|
+
committed._directEventDescriptors = resolved.directEventDescriptors
|
|
450
|
+
return committed
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function beginComponentNode(
|
|
454
|
+
node: ComponentNode,
|
|
455
|
+
parent: VNodeParent,
|
|
456
|
+
svg: boolean,
|
|
457
|
+
handle: ComponentHandle,
|
|
458
|
+
context: ReconcileContext,
|
|
459
|
+
): MountingComponentNode {
|
|
460
|
+
let mounting = node as unknown as MountingComponentNode
|
|
461
|
+
mounting._parent = parent
|
|
462
|
+
mounting._svg = svg
|
|
463
|
+
mounting._handle = handle
|
|
464
|
+
mounting._context = context
|
|
465
|
+
return mounting
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function commitComponentNode(
|
|
469
|
+
node: MountingComponentNode,
|
|
470
|
+
content: CommittedVNode,
|
|
471
|
+
): CommittedComponentNode {
|
|
472
|
+
let committed = node as CommittedComponentNode
|
|
473
|
+
committed._content = content
|
|
474
|
+
return committed
|
|
475
|
+
}
|
|
476
|
+
|
|
394
477
|
export function diffVNodes(
|
|
395
|
-
curr:
|
|
396
|
-
next:
|
|
478
|
+
curr: CommittedVNode | null,
|
|
479
|
+
next: VNodeInput,
|
|
397
480
|
domParent: ParentNode,
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
styles: StyleManager,
|
|
401
|
-
vParent: VNode,
|
|
402
|
-
rootTarget: EventTarget,
|
|
481
|
+
vParent: VNodeParent,
|
|
482
|
+
context: ReconcileContext,
|
|
403
483
|
anchor?: Node,
|
|
404
|
-
|
|
405
|
-
):
|
|
406
|
-
next._parent = vParent // set parent for initial render context lookups
|
|
407
|
-
next._svg = getSvgContext(vParent, next.type)
|
|
408
|
-
|
|
409
|
-
// new
|
|
484
|
+
cursor?: HydrationCursor,
|
|
485
|
+
): CommittedVNode {
|
|
410
486
|
if (curr === null) {
|
|
411
|
-
return insert(
|
|
412
|
-
next,
|
|
413
|
-
domParent,
|
|
414
|
-
frame,
|
|
415
|
-
scheduler,
|
|
416
|
-
styles,
|
|
417
|
-
vParent,
|
|
418
|
-
rootTarget,
|
|
419
|
-
anchor,
|
|
420
|
-
rootCursor,
|
|
421
|
-
)
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
if (curr.type !== next.type) {
|
|
425
|
-
replace(curr, next, domParent, frame, scheduler, styles, vParent, rootTarget, anchor)
|
|
426
|
-
return rootCursor
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
if (isNonRenderNode(curr) && isNonRenderNode(next)) {
|
|
430
|
-
return rootCursor
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
if (isCommittedTextNode(curr) && isTextNode(next)) {
|
|
434
|
-
diffText(curr, next, vParent)
|
|
435
|
-
return rootCursor
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
if (isCommittedHostNode(curr) && isHostNode(next)) {
|
|
439
|
-
diffHost(curr, next, frame, scheduler, styles, vParent, rootTarget)
|
|
440
|
-
return rootCursor
|
|
487
|
+
return insert(next, domParent, vParent, context, anchor, cursor)
|
|
441
488
|
}
|
|
442
489
|
|
|
443
|
-
if (
|
|
444
|
-
|
|
445
|
-
|
|
490
|
+
if (
|
|
491
|
+
componentStalenessCheck !== null &&
|
|
492
|
+
curr.kind === 'component' &&
|
|
493
|
+
next.kind === 'component' &&
|
|
494
|
+
componentStalenessCheck(curr.type) === true
|
|
495
|
+
) {
|
|
496
|
+
return replace(curr, next, domParent, vParent, context, anchor)
|
|
446
497
|
}
|
|
447
498
|
|
|
448
|
-
if (
|
|
449
|
-
|
|
450
|
-
curr._children,
|
|
451
|
-
next._children,
|
|
452
|
-
domParent,
|
|
453
|
-
frame,
|
|
454
|
-
scheduler,
|
|
455
|
-
styles,
|
|
456
|
-
next,
|
|
457
|
-
rootTarget,
|
|
458
|
-
undefined,
|
|
459
|
-
anchor,
|
|
460
|
-
)
|
|
461
|
-
return rootCursor
|
|
499
|
+
if (curr.kind !== next.kind || curr.type !== next.type) {
|
|
500
|
+
return replace(curr, next, domParent, vParent, context, anchor)
|
|
462
501
|
}
|
|
463
502
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
503
|
+
switch (next.kind) {
|
|
504
|
+
case 'host': {
|
|
505
|
+
return diffHost(curr as CommittedHostNode, next, vParent, context)
|
|
506
|
+
}
|
|
507
|
+
case 'text': {
|
|
508
|
+
return diffText(curr as CommittedTextNode, next, vParent, getSvgContext(vParent, next))
|
|
509
|
+
}
|
|
510
|
+
case 'empty': {
|
|
511
|
+
return commitNonRenderNode(next, vParent, getSvgContext(vParent, next))
|
|
512
|
+
}
|
|
513
|
+
case 'fragment': {
|
|
514
|
+
let childInputs = next._children
|
|
515
|
+
let committed = beginFragmentNode(next, vParent, getSvgContext(vParent, next))
|
|
516
|
+
committed._children = diffChildren(
|
|
517
|
+
(curr as CommittedFragmentNode)._children,
|
|
518
|
+
childInputs,
|
|
519
|
+
domParent,
|
|
520
|
+
committed,
|
|
521
|
+
context,
|
|
522
|
+
undefined,
|
|
523
|
+
anchor,
|
|
524
|
+
)
|
|
525
|
+
return committed
|
|
526
|
+
}
|
|
527
|
+
case 'frame': {
|
|
528
|
+
return diffFrame(
|
|
529
|
+
curr as CommittedFrameNode,
|
|
530
|
+
next,
|
|
531
|
+
domParent,
|
|
532
|
+
vParent,
|
|
533
|
+
context,
|
|
534
|
+
getSvgContext(vParent, next),
|
|
535
|
+
anchor,
|
|
536
|
+
)
|
|
537
|
+
}
|
|
538
|
+
case 'component': {
|
|
539
|
+
return diffComponent(
|
|
540
|
+
curr as CommittedComponentNode,
|
|
541
|
+
next,
|
|
542
|
+
domParent,
|
|
543
|
+
vParent,
|
|
544
|
+
context,
|
|
545
|
+
getSvgContext(vParent, next),
|
|
546
|
+
anchor,
|
|
547
|
+
cursor,
|
|
548
|
+
)
|
|
549
|
+
}
|
|
467
550
|
}
|
|
468
|
-
|
|
469
|
-
invariant(false, 'Unexpected diff case')
|
|
470
551
|
}
|
|
471
552
|
|
|
472
553
|
function replace(
|
|
473
|
-
curr:
|
|
474
|
-
next:
|
|
554
|
+
curr: CommittedVNode,
|
|
555
|
+
next: VNodeInput,
|
|
475
556
|
domParent: ParentNode,
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
styles: StyleManager,
|
|
479
|
-
vParent: VNode,
|
|
480
|
-
rootTarget: EventTarget,
|
|
557
|
+
vParent: VNodeParent,
|
|
558
|
+
context: ReconcileContext,
|
|
481
559
|
anchor?: Node,
|
|
482
|
-
) {
|
|
560
|
+
): CommittedVNode {
|
|
483
561
|
let currAnchor = findFirstDomAnchor(curr)
|
|
484
562
|
if (currAnchor && currAnchor.parentNode === domParent) {
|
|
485
563
|
let replacementAnchor = document.createComment('rmx:replace')
|
|
486
564
|
domParent.insertBefore(replacementAnchor, currAnchor)
|
|
487
565
|
try {
|
|
488
|
-
remove(curr, domParent,
|
|
489
|
-
insert(next, domParent,
|
|
566
|
+
remove(curr, domParent, context)
|
|
567
|
+
return insert(next, domParent, vParent, context, replacementAnchor)
|
|
490
568
|
} finally {
|
|
491
569
|
replacementAnchor.parentNode?.removeChild(replacementAnchor)
|
|
492
570
|
}
|
|
493
|
-
return
|
|
494
571
|
}
|
|
495
572
|
|
|
496
573
|
let replacementAnchor = findNextSiblingDomAnchor(curr) ?? anchor
|
|
497
|
-
remove(curr, domParent,
|
|
498
|
-
insert(next, domParent,
|
|
574
|
+
remove(curr, domParent, context)
|
|
575
|
+
return insert(next, domParent, vParent, context, replacementAnchor)
|
|
499
576
|
}
|
|
500
577
|
|
|
501
578
|
function diffHost(
|
|
502
579
|
curr: CommittedHostNode,
|
|
503
580
|
next: HostNode,
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
) {
|
|
510
|
-
let mixState = curr._mixState as MixinRuntimeState | undefined
|
|
581
|
+
vParent: VNodeParent,
|
|
582
|
+
context: ReconcileContext,
|
|
583
|
+
): CommittedHostNode {
|
|
584
|
+
let { frame, scheduler, styles } = context
|
|
585
|
+
let mixState = curr._mixState
|
|
511
586
|
let currProps = getHostProps(curr)
|
|
512
|
-
let
|
|
513
|
-
let
|
|
587
|
+
let resolved = resolveNodeMixProps(next, vParent, frame, scheduler, mixState)
|
|
588
|
+
let childInputs = next._children
|
|
589
|
+
let nextProps = resolved.props
|
|
590
|
+
let nextMixState = resolved.mixState
|
|
514
591
|
let shouldDispatchMixinLifecycle =
|
|
515
592
|
(nextMixState?.runners.length ?? 0) > 0 && shouldDispatchInlineMixinLifecycle(curr._dom)
|
|
516
593
|
if (shouldDispatchMixinLifecycle) {
|
|
@@ -521,62 +598,49 @@ function diffHost(
|
|
|
521
598
|
if (nextProps.innerHTML != null) {
|
|
522
599
|
// innerHTML is set, update it if changed
|
|
523
600
|
if (currProps.innerHTML !== nextProps.innerHTML) {
|
|
524
|
-
curr._dom.innerHTML = nextProps.innerHTML
|
|
601
|
+
curr._dom.innerHTML = nextProps.innerHTML
|
|
525
602
|
}
|
|
526
603
|
} else if (currProps.innerHTML != null) {
|
|
527
604
|
// innerHTML was removed, clear it before adding children
|
|
528
605
|
curr._dom.innerHTML = ''
|
|
529
606
|
}
|
|
530
607
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
frame,
|
|
536
|
-
scheduler,
|
|
537
|
-
styles,
|
|
538
|
-
next,
|
|
539
|
-
rootTarget,
|
|
540
|
-
)
|
|
608
|
+
let committed = commitHostNode(next, vParent, getSvgContext(vParent, next), curr._dom, resolved)
|
|
609
|
+
committed._directEventState = curr._directEventState
|
|
610
|
+
committed._controlledState = curr._controlledState
|
|
611
|
+
committed._children = diffChildren(curr._children, childInputs, curr._dom, committed, context)
|
|
541
612
|
patchHostProps(currProps, nextProps, curr._dom)
|
|
542
613
|
|
|
543
|
-
|
|
544
|
-
next._parent = vParent
|
|
545
|
-
next._controller = curr._controller
|
|
546
|
-
next._directEventState = curr._directEventState
|
|
547
|
-
next._controlledState = curr._controlledState
|
|
548
|
-
syncDirectEventListeners(next as CommittedHostNode)
|
|
614
|
+
syncDirectEventListeners(committed)
|
|
549
615
|
|
|
550
|
-
if (
|
|
551
|
-
ensureControlledReflection(
|
|
552
|
-
syncControlledReflection(
|
|
616
|
+
if (committed._controlledState || shouldTrackControlledReflection(nextProps)) {
|
|
617
|
+
ensureControlledReflection(committed, scheduler)
|
|
618
|
+
syncControlledReflection(committed, nextProps)
|
|
553
619
|
}
|
|
554
620
|
|
|
555
|
-
if (
|
|
556
|
-
bindNodeMixRuntime(
|
|
621
|
+
if (committed._mixState) {
|
|
622
|
+
bindNodeMixRuntime(committed, frame, scheduler, styles)
|
|
557
623
|
}
|
|
558
624
|
if (shouldDispatchMixinLifecycle) {
|
|
559
625
|
scheduler.enqueueCommitPhase([() => dispatchMixinCommit(nextMixState)])
|
|
560
626
|
}
|
|
561
627
|
|
|
562
|
-
return
|
|
628
|
+
return committed
|
|
563
629
|
}
|
|
564
630
|
|
|
565
|
-
function setupHostNode(node:
|
|
566
|
-
node._dom = dom
|
|
631
|
+
function setupHostNode(node: CommittedHostNode, scheduler: Scheduler): void {
|
|
567
632
|
let props = getHostProps(node)
|
|
568
|
-
let committedNode = node as CommittedHostNode
|
|
569
633
|
|
|
570
|
-
syncDirectEventListeners(
|
|
634
|
+
syncDirectEventListeners(node)
|
|
571
635
|
|
|
572
636
|
if (shouldTrackControlledReflection(props)) {
|
|
573
|
-
ensureControlledReflection(
|
|
574
|
-
syncControlledReflection(
|
|
637
|
+
ensureControlledReflection(node, scheduler)
|
|
638
|
+
syncControlledReflection(node, props)
|
|
575
639
|
}
|
|
576
640
|
}
|
|
577
641
|
|
|
578
642
|
function syncDirectEventListeners(node: CommittedHostNode): void {
|
|
579
|
-
let descriptors = node._directEventDescriptors
|
|
643
|
+
let descriptors = node._directEventDescriptors
|
|
580
644
|
if (!descriptors) {
|
|
581
645
|
teardownDirectEventListeners(node)
|
|
582
646
|
return
|
|
@@ -586,7 +650,7 @@ function syncDirectEventListeners(node: CommittedHostNode): void {
|
|
|
586
650
|
return
|
|
587
651
|
}
|
|
588
652
|
|
|
589
|
-
let state = node._directEventState
|
|
653
|
+
let state = node._directEventState
|
|
590
654
|
if (!state) {
|
|
591
655
|
state = { bindings: [] }
|
|
592
656
|
node._directEventState = state
|
|
@@ -594,8 +658,12 @@ function syncDirectEventListeners(node: CommittedHostNode): void {
|
|
|
594
658
|
|
|
595
659
|
let bindings = state.bindings
|
|
596
660
|
for (let index = 0; index < descriptors.length; index++) {
|
|
597
|
-
|
|
598
|
-
|
|
661
|
+
// Indexed access instead of array destructuring: destructuring goes through
|
|
662
|
+
// the iterator protocol and allocates on every host-node update.
|
|
663
|
+
let args = descriptors[index].args
|
|
664
|
+
let type = args[0]
|
|
665
|
+
let handler = args[1]
|
|
666
|
+
let captureBoolean = args[2] ?? false
|
|
599
667
|
let binding = bindings[index]
|
|
600
668
|
|
|
601
669
|
if (!binding) {
|
|
@@ -659,7 +727,7 @@ function removeDirectEventBinding(dom: Element, binding: DirectEventBinding): vo
|
|
|
659
727
|
}
|
|
660
728
|
|
|
661
729
|
function teardownDirectEventListeners(node: CommittedHostNode): void {
|
|
662
|
-
let state = node._directEventState
|
|
730
|
+
let state = node._directEventState
|
|
663
731
|
if (!state) return
|
|
664
732
|
|
|
665
733
|
for (let binding of state.bindings) {
|
|
@@ -670,38 +738,56 @@ function teardownDirectEventListeners(node: CommittedHostNode): void {
|
|
|
670
738
|
node._directEventState = undefined
|
|
671
739
|
}
|
|
672
740
|
|
|
741
|
+
// Teardown for a node whose DOM subtree is being discarded wholesale: abort
|
|
742
|
+
// pending handler work but skip removeEventListener — the listeners die with
|
|
743
|
+
// the detached node, and removing them one-by-one dominates large teardowns.
|
|
744
|
+
function abandonDirectEventListeners(node: CommittedHostNode): void {
|
|
745
|
+
let state = node._directEventState
|
|
746
|
+
if (!state) return
|
|
747
|
+
|
|
748
|
+
for (let binding of state.bindings) {
|
|
749
|
+
binding.reentry?.abort(new DOMException('', 'AbortError'))
|
|
750
|
+
binding.reentry = null
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
state.bindings.length = 0
|
|
754
|
+
node._directEventState = undefined
|
|
755
|
+
}
|
|
756
|
+
|
|
673
757
|
function invokeDirectEventBinding(binding: DirectEventBinding, event: Event): void {
|
|
674
758
|
binding.reentry?.abort(new DOMException('', 'EventReentry'))
|
|
675
759
|
binding.reentry = new AbortController()
|
|
676
760
|
void binding.handler(event, binding.reentry.signal)
|
|
677
761
|
}
|
|
678
762
|
|
|
679
|
-
function diffText(
|
|
763
|
+
function diffText(
|
|
764
|
+
curr: CommittedTextNode,
|
|
765
|
+
next: TextNode,
|
|
766
|
+
vParent: VNodeParent,
|
|
767
|
+
svg: boolean,
|
|
768
|
+
): CommittedTextNode {
|
|
680
769
|
if (curr._text !== next._text) {
|
|
681
770
|
curr._dom.textContent = next._text
|
|
682
771
|
}
|
|
683
|
-
next
|
|
684
|
-
next._parent = vParent
|
|
772
|
+
return commitTextNode(next, vParent, svg, curr._dom)
|
|
685
773
|
}
|
|
686
774
|
|
|
687
775
|
function insert(
|
|
688
|
-
node:
|
|
776
|
+
node: VNodeInput,
|
|
689
777
|
domParent: ParentNode,
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
styles: StyleManager,
|
|
693
|
-
vParent: VNode,
|
|
694
|
-
rootTarget: EventTarget,
|
|
778
|
+
vParent: VNodeParent,
|
|
779
|
+
context: ReconcileContext,
|
|
695
780
|
anchor?: Node,
|
|
696
|
-
cursor?:
|
|
697
|
-
):
|
|
698
|
-
|
|
699
|
-
|
|
781
|
+
cursor?: HydrationCursor,
|
|
782
|
+
): CommittedVNode {
|
|
783
|
+
let { frame, scheduler, styles } = context
|
|
784
|
+
let svg = getSvgContext(vParent, node)
|
|
785
|
+
let hydrationNode = cursor?.current
|
|
700
786
|
|
|
701
787
|
// Stop hydration if cursor has reached the anchor (end boundary)
|
|
702
788
|
// Check BEFORE skipComments to prevent escaping range root markers
|
|
703
|
-
if (
|
|
704
|
-
|
|
789
|
+
if (hydrationNode && anchor && hydrationNode === anchor) {
|
|
790
|
+
hydrationNode = null
|
|
705
791
|
}
|
|
706
792
|
|
|
707
793
|
// Preserve frame-start markers for non-Frame nodes too, so a following <Frame>
|
|
@@ -709,297 +795,280 @@ function insert(
|
|
|
709
795
|
// claim its rmx:f marker during hydration instead of being re-inserted fresh.
|
|
710
796
|
// A rmx:f marker always belongs to a <Frame>, so no non-Frame node should
|
|
711
797
|
// consume one.
|
|
712
|
-
|
|
798
|
+
hydrationNode = skipCommentsExceptFrameStart(hydrationNode ?? null)
|
|
713
799
|
|
|
714
800
|
// Also check after skipComments in case we skipped past the anchor
|
|
715
|
-
if (
|
|
716
|
-
|
|
801
|
+
if (hydrationNode && anchor && hydrationNode === anchor) {
|
|
802
|
+
hydrationNode = null
|
|
717
803
|
}
|
|
804
|
+
if (cursor) cursor.current = hydrationNode
|
|
718
805
|
|
|
719
806
|
let doInsert = anchor
|
|
720
807
|
? (dom: Node) => domParent.insertBefore(dom, anchor)
|
|
721
808
|
: (dom: Node) => domParent.appendChild(dom)
|
|
722
809
|
|
|
723
|
-
if (
|
|
724
|
-
return
|
|
810
|
+
if (node.kind === 'empty') {
|
|
811
|
+
return commitNonRenderNode(node, vParent, svg)
|
|
725
812
|
}
|
|
726
813
|
|
|
727
|
-
if (
|
|
728
|
-
if (
|
|
729
|
-
node._parent = vParent
|
|
814
|
+
if (node.kind === 'text') {
|
|
815
|
+
if (hydrationNode instanceof Text) {
|
|
730
816
|
// Handle text node consolidation: server renders adjacent text as single node
|
|
731
817
|
// e.g., <span>Hello {world}</span> → server: "Hello world", client: ["Hello ", "world"]
|
|
732
|
-
if (
|
|
733
|
-
if (
|
|
818
|
+
if (hydrationNode.data !== node._text) {
|
|
819
|
+
if (
|
|
820
|
+
hydrationNode.data.startsWith(node._text) &&
|
|
821
|
+
node._text.length < hydrationNode.data.length
|
|
822
|
+
) {
|
|
734
823
|
// Consolidation case: split the text node at the boundary
|
|
735
824
|
// cursor becomes the first part (node._text), remainder is returned for next vnode
|
|
736
|
-
let remainder =
|
|
737
|
-
|
|
738
|
-
return
|
|
825
|
+
let remainder = hydrationNode.splitText(node._text.length)
|
|
826
|
+
if (cursor) cursor.current = remainder
|
|
827
|
+
return commitTextNode(node, vParent, svg, hydrationNode)
|
|
739
828
|
}
|
|
740
829
|
// Genuine mismatch - correct it
|
|
741
|
-
logHydrationMismatch('text mismatch',
|
|
742
|
-
|
|
830
|
+
logHydrationMismatch('text mismatch', hydrationNode.data, node._text)
|
|
831
|
+
hydrationNode.data = node._text
|
|
743
832
|
}
|
|
744
|
-
|
|
745
|
-
return
|
|
833
|
+
if (cursor) cursor.current = hydrationNode.nextSibling
|
|
834
|
+
return commitTextNode(node, vParent, svg, hydrationNode)
|
|
746
835
|
}
|
|
747
836
|
let dom = document.createTextNode(node._text)
|
|
748
|
-
node._dom = dom
|
|
749
|
-
node._parent = vParent
|
|
750
837
|
doInsert(dom)
|
|
751
|
-
return
|
|
838
|
+
return commitTextNode(node, vParent, svg, dom)
|
|
752
839
|
}
|
|
753
840
|
|
|
754
|
-
if (
|
|
755
|
-
let
|
|
841
|
+
if (node.kind === 'host') {
|
|
842
|
+
let resolved = resolveNodeMixProps(node, vParent, frame, scheduler)
|
|
843
|
+
let hostProps = resolved.props
|
|
844
|
+
let childInputs = node._children
|
|
756
845
|
|
|
757
846
|
if (isHeadHostNode(node)) {
|
|
758
847
|
let targetHead = getDocumentHead(domParent)
|
|
759
848
|
if (targetHead) {
|
|
760
|
-
let childCursor = cursor
|
|
761
|
-
if (
|
|
762
|
-
childCursor =
|
|
763
|
-
let nextCursor =
|
|
764
|
-
if (
|
|
765
|
-
while (
|
|
766
|
-
targetHead.appendChild(
|
|
849
|
+
let childCursor = cursor ? { current: hydrationNode } : undefined
|
|
850
|
+
if (hydrationNode instanceof Element && hydrationNode.tagName.toLowerCase() === 'head') {
|
|
851
|
+
if (childCursor) childCursor.current = hydrationNode.firstChild
|
|
852
|
+
let nextCursor = hydrationNode.nextSibling
|
|
853
|
+
if (hydrationNode !== targetHead) {
|
|
854
|
+
while (hydrationNode.firstChild) {
|
|
855
|
+
targetHead.appendChild(hydrationNode.firstChild)
|
|
767
856
|
}
|
|
768
|
-
|
|
857
|
+
hydrationNode.remove()
|
|
769
858
|
}
|
|
770
|
-
cursor = nextCursor
|
|
859
|
+
if (cursor) cursor.current = nextCursor
|
|
771
860
|
}
|
|
772
861
|
|
|
862
|
+
let committed = commitHostNode(node, vParent, svg, targetHead, resolved)
|
|
773
863
|
// Render explicit <head> children directly into document.head.
|
|
774
|
-
diffChildren(
|
|
864
|
+
committed._children = diffChildren(
|
|
775
865
|
null,
|
|
776
|
-
|
|
866
|
+
childInputs,
|
|
777
867
|
targetHead,
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
styles,
|
|
781
|
-
node,
|
|
782
|
-
rootTarget,
|
|
868
|
+
committed,
|
|
869
|
+
context,
|
|
783
870
|
childCursor,
|
|
784
871
|
)
|
|
785
872
|
patchHostProps({}, hostProps, targetHead)
|
|
786
|
-
setupHostNode(
|
|
787
|
-
if (
|
|
788
|
-
bindNodeMixRuntime(
|
|
873
|
+
setupHostNode(committed, scheduler)
|
|
874
|
+
if (committed._mixState) {
|
|
875
|
+
bindNodeMixRuntime(committed, frame, scheduler, styles)
|
|
789
876
|
}
|
|
790
|
-
return
|
|
877
|
+
return committed
|
|
791
878
|
}
|
|
792
879
|
}
|
|
793
880
|
|
|
794
881
|
// Check for matching mixin-persisted node that can be reclaimed
|
|
795
882
|
let persistedNode = findMatchingPersistedMixinNode(node.type, node.key, domParent)
|
|
796
883
|
if (persistedNode) {
|
|
797
|
-
reclaimPersistedMixinNode(persistedNode, node,
|
|
798
|
-
return cursor
|
|
884
|
+
return reclaimPersistedMixinNode(persistedNode, node, vParent, context)
|
|
799
885
|
}
|
|
800
886
|
|
|
801
|
-
if (
|
|
887
|
+
if (hydrationNode instanceof Element) {
|
|
802
888
|
// SVG elements have case-sensitive tag names (e.g. linearGradient, clipPath)
|
|
803
889
|
// HTML elements are case-insensitive, so we lowercase for comparison
|
|
804
|
-
let cursorTag =
|
|
890
|
+
let cursorTag = svg ? hydrationNode.tagName : hydrationNode.tagName.toLowerCase()
|
|
805
891
|
if (cursorTag === node.type) {
|
|
806
|
-
let nextCursor =
|
|
807
|
-
patchHostProps({}, hostProps,
|
|
892
|
+
let nextCursor = hydrationNode.nextSibling
|
|
893
|
+
patchHostProps({}, hostProps, hydrationNode)
|
|
894
|
+
let committed = commitHostNode(node, vParent, svg, hydrationNode, resolved)
|
|
808
895
|
|
|
809
896
|
// Handle innerHTML prop
|
|
810
897
|
if (hostProps.innerHTML != null) {
|
|
811
|
-
|
|
898
|
+
hydrationNode.innerHTML = hostProps.innerHTML
|
|
812
899
|
} else {
|
|
813
|
-
let childCursor =
|
|
900
|
+
let childCursor = { current: hydrationNode.firstChild }
|
|
814
901
|
// Ignore excess nodes - browser extensions may inject content
|
|
815
|
-
diffChildren(
|
|
902
|
+
committed._children = diffChildren(
|
|
816
903
|
null,
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
styles,
|
|
822
|
-
node,
|
|
823
|
-
rootTarget,
|
|
904
|
+
childInputs,
|
|
905
|
+
hydrationNode,
|
|
906
|
+
committed,
|
|
907
|
+
context,
|
|
824
908
|
childCursor,
|
|
825
909
|
)
|
|
826
910
|
}
|
|
827
911
|
|
|
828
|
-
setupHostNode(
|
|
829
|
-
if (
|
|
830
|
-
bindNodeMixRuntime(
|
|
912
|
+
setupHostNode(committed, scheduler)
|
|
913
|
+
if (committed._mixState) {
|
|
914
|
+
bindNodeMixRuntime(committed, frame, scheduler, styles)
|
|
831
915
|
}
|
|
832
|
-
|
|
916
|
+
if (cursor) cursor.current = nextCursor
|
|
917
|
+
return committed
|
|
833
918
|
} else {
|
|
834
919
|
// Type mismatch - try single-advance retry to handle browser extension injections
|
|
835
920
|
// at the start of containers. Skip this node and try the next sibling once.
|
|
836
|
-
let nextSibling = skipComments(
|
|
921
|
+
let nextSibling = skipComments(hydrationNode.nextSibling)
|
|
837
922
|
if (nextSibling instanceof Element) {
|
|
838
|
-
let nextTag =
|
|
923
|
+
let nextTag = svg ? nextSibling.tagName : nextSibling.tagName.toLowerCase()
|
|
839
924
|
if (nextTag === node.type) {
|
|
840
925
|
let nextCursor = nextSibling.nextSibling
|
|
841
926
|
// Found a match after skipping - adopt it and leave skipped node in place
|
|
842
927
|
patchHostProps({}, hostProps, nextSibling)
|
|
928
|
+
let committed = commitHostNode(node, vParent, svg, nextSibling, resolved)
|
|
843
929
|
|
|
844
930
|
if (hostProps.innerHTML != null) {
|
|
845
|
-
nextSibling.innerHTML = hostProps.innerHTML
|
|
931
|
+
nextSibling.innerHTML = hostProps.innerHTML
|
|
846
932
|
} else {
|
|
847
|
-
let childCursor = nextSibling.firstChild
|
|
848
|
-
diffChildren(
|
|
933
|
+
let childCursor = { current: nextSibling.firstChild }
|
|
934
|
+
committed._children = diffChildren(
|
|
849
935
|
null,
|
|
850
|
-
|
|
936
|
+
childInputs,
|
|
851
937
|
nextSibling,
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
styles,
|
|
855
|
-
node,
|
|
856
|
-
rootTarget,
|
|
938
|
+
committed,
|
|
939
|
+
context,
|
|
857
940
|
childCursor,
|
|
858
941
|
)
|
|
859
942
|
}
|
|
860
943
|
|
|
861
|
-
setupHostNode(
|
|
862
|
-
if (
|
|
863
|
-
bindNodeMixRuntime(
|
|
944
|
+
setupHostNode(committed, scheduler)
|
|
945
|
+
if (committed._mixState) {
|
|
946
|
+
bindNodeMixRuntime(committed, frame, scheduler, styles)
|
|
864
947
|
}
|
|
865
|
-
|
|
948
|
+
if (cursor) cursor.current = nextCursor
|
|
949
|
+
return committed
|
|
866
950
|
}
|
|
867
951
|
}
|
|
868
952
|
// Retry failed - log mismatch and create new element (don't remove mismatched nodes)
|
|
869
953
|
logHydrationMismatch('tag', cursorTag, node.type)
|
|
870
|
-
cursor = undefined // stop hydration for this tree
|
|
954
|
+
if (cursor) cursor.current = undefined // stop hydration for this tree
|
|
871
955
|
}
|
|
872
956
|
}
|
|
873
|
-
let dom = node.
|
|
874
|
-
? document.createElementNS(SVG_NS, node.type)
|
|
875
|
-
: document.createElement(node.type)
|
|
957
|
+
let dom = svg ? document.createElementNS(SVG_NS, node.type) : document.createElement(node.type)
|
|
876
958
|
patchHostProps({}, hostProps, dom)
|
|
959
|
+
let committed = commitHostNode(node, vParent, svg, dom, resolved)
|
|
877
960
|
|
|
878
961
|
// Handle innerHTML prop
|
|
879
962
|
if (hostProps.innerHTML != null) {
|
|
880
|
-
dom.innerHTML = hostProps.innerHTML
|
|
963
|
+
dom.innerHTML = hostProps.innerHTML
|
|
881
964
|
} else {
|
|
882
|
-
diffChildren(null,
|
|
965
|
+
committed._children = diffChildren(null, childInputs, dom, committed, context)
|
|
883
966
|
}
|
|
884
967
|
|
|
885
|
-
setupHostNode(
|
|
886
|
-
if (
|
|
887
|
-
bindNodeMixRuntime(
|
|
968
|
+
setupHostNode(committed, scheduler)
|
|
969
|
+
if (committed._mixState) {
|
|
970
|
+
bindNodeMixRuntime(committed, frame, scheduler, styles, false, domParent)
|
|
888
971
|
}
|
|
889
972
|
doInsert(dom)
|
|
890
|
-
return
|
|
973
|
+
return committed
|
|
891
974
|
}
|
|
892
975
|
|
|
893
|
-
if (
|
|
976
|
+
if (node.kind === 'fragment') {
|
|
977
|
+
let childInputs = node._children
|
|
978
|
+
let committed = beginFragmentNode(node, vParent, svg)
|
|
979
|
+
let children: Array<VNodeInput | CommittedVNode> = childInputs
|
|
894
980
|
// Insert fragment children in order before the same anchor
|
|
895
|
-
for (let
|
|
896
|
-
|
|
981
|
+
for (let i = 0; i < childInputs.length; i++) {
|
|
982
|
+
children[i] = insert(childInputs[i], domParent, committed, context, anchor, cursor)
|
|
897
983
|
}
|
|
898
|
-
|
|
984
|
+
committed._children = children as CommittedVNode[]
|
|
985
|
+
return committed
|
|
899
986
|
}
|
|
900
987
|
|
|
901
|
-
if (
|
|
902
|
-
return diffComponent(
|
|
903
|
-
null,
|
|
904
|
-
node,
|
|
905
|
-
frame,
|
|
906
|
-
scheduler,
|
|
907
|
-
styles,
|
|
908
|
-
domParent,
|
|
909
|
-
vParent,
|
|
910
|
-
rootTarget,
|
|
911
|
-
anchor,
|
|
912
|
-
cursor,
|
|
913
|
-
)
|
|
988
|
+
if (node.kind === 'component') {
|
|
989
|
+
return diffComponent(null, node, domParent, vParent, context, svg, anchor, cursor)
|
|
914
990
|
}
|
|
915
991
|
|
|
916
|
-
if (node.
|
|
917
|
-
return insertFrame(
|
|
918
|
-
node,
|
|
919
|
-
domParent,
|
|
920
|
-
frame,
|
|
921
|
-
scheduler,
|
|
922
|
-
styles,
|
|
923
|
-
vParent,
|
|
924
|
-
rootTarget,
|
|
925
|
-
anchor,
|
|
926
|
-
cursor,
|
|
927
|
-
)
|
|
992
|
+
if (node.kind === 'frame') {
|
|
993
|
+
return insertFrame(node, domParent, vParent, context, svg, anchor, cursor)
|
|
928
994
|
}
|
|
929
995
|
|
|
930
996
|
invariant(false, 'Unexpected node type')
|
|
931
997
|
}
|
|
932
998
|
|
|
933
999
|
function diffFrame(
|
|
934
|
-
curr:
|
|
935
|
-
next:
|
|
1000
|
+
curr: CommittedFrameNode,
|
|
1001
|
+
next: FrameNode,
|
|
936
1002
|
domParent: ParentNode,
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
vParent: VNode,
|
|
941
|
-
rootTarget: EventTarget,
|
|
1003
|
+
vParent: VNodeParent,
|
|
1004
|
+
context: ReconcileContext,
|
|
1005
|
+
svg: boolean,
|
|
942
1006
|
anchor?: Node,
|
|
943
|
-
):
|
|
1007
|
+
): CommittedFrameNode {
|
|
1008
|
+
let { frame } = context
|
|
944
1009
|
let currSrc = getFrameSrc(curr)
|
|
945
1010
|
let nextSrc = getFrameSrc(next)
|
|
946
1011
|
let currName = getFrameName(curr)
|
|
947
1012
|
let nextName = getFrameName(next)
|
|
948
1013
|
|
|
949
1014
|
if (currName !== nextName) {
|
|
950
|
-
let replaceAnchor = curr._rangeEnd
|
|
951
|
-
remove(curr, domParent,
|
|
952
|
-
|
|
953
|
-
return
|
|
1015
|
+
let replaceAnchor = curr._rangeEnd.nextSibling ?? anchor
|
|
1016
|
+
remove(curr, domParent, context)
|
|
1017
|
+
return insertFrame(next, domParent, vParent, context, svg, replaceAnchor)
|
|
954
1018
|
}
|
|
955
1019
|
|
|
956
1020
|
// If the frame hasn't resolved yet, preserve existing cancel/remount behavior
|
|
957
1021
|
// so pending streams from the old src cannot take over the new src.
|
|
958
|
-
if (currSrc !== nextSrc && !curr.
|
|
959
|
-
let replaceAnchor = curr._rangeEnd
|
|
960
|
-
remove(curr, domParent,
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1022
|
+
if (currSrc !== nextSrc && !curr._state.resolved) {
|
|
1023
|
+
let replaceAnchor = curr._rangeEnd.nextSibling ?? anchor
|
|
1024
|
+
remove(curr, domParent, context)
|
|
1025
|
+
return insertFrame(next, domParent, vParent, context, svg, replaceAnchor)
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
let committed = next as unknown as CommittedFrameNode
|
|
1029
|
+
Object.assign(committed, {
|
|
1030
|
+
_rangeStart: curr._rangeStart,
|
|
1031
|
+
_rangeEnd: curr._rangeEnd,
|
|
1032
|
+
_state: curr._state,
|
|
1033
|
+
_parent: vParent,
|
|
1034
|
+
_svg: svg,
|
|
1035
|
+
})
|
|
964
1036
|
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
next._frameFallbackRoot = curr._frameFallbackRoot
|
|
969
|
-
next._frameResolveToken = curr._frameResolveToken
|
|
970
|
-
next._frameResolved = curr._frameResolved
|
|
971
|
-
next._parent = vParent
|
|
1037
|
+
let frameRuntime = getFrameRuntime(frame)
|
|
1038
|
+
let frameInstance = committed._state.instance
|
|
1039
|
+
let serverFrameReload = frameRuntime?.serverFrameReload
|
|
972
1040
|
|
|
973
1041
|
if (currSrc !== nextSrc) {
|
|
974
|
-
let frameInstance = next._frameInstance as FrameInstance | undefined
|
|
975
1042
|
if (frameInstance) {
|
|
976
1043
|
frameInstance.handle.src = nextSrc
|
|
977
1044
|
}
|
|
978
1045
|
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
resolveClientFrame(next, runtime)
|
|
1046
|
+
if (frameRuntime) {
|
|
1047
|
+
resolveClientFrame(committed, frameRuntime, serverFrameReload)
|
|
982
1048
|
}
|
|
1049
|
+
} else if (frameRuntime && frameInstance && serverFrameReload) {
|
|
1050
|
+
// Reload client frames that have the same src during a render triggered by an ancestor frame reload
|
|
1051
|
+
resolveClientFrame(committed, frameRuntime, serverFrameReload)
|
|
983
1052
|
}
|
|
984
1053
|
|
|
985
|
-
if (!
|
|
986
|
-
|
|
1054
|
+
if (!committed._state.resolved && committed._state.fallbackRoot) {
|
|
1055
|
+
committed._state.fallbackRoot.render(committed.props.fallback ?? null)
|
|
987
1056
|
}
|
|
1057
|
+
return committed
|
|
988
1058
|
}
|
|
989
1059
|
|
|
990
1060
|
function insertFrame(
|
|
991
|
-
node:
|
|
1061
|
+
node: FrameNode,
|
|
992
1062
|
domParent: ParentNode,
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
vParent: VNode,
|
|
997
|
-
rootTarget: EventTarget,
|
|
1063
|
+
vParent: VNodeParent,
|
|
1064
|
+
context: ReconcileContext,
|
|
1065
|
+
svg: boolean,
|
|
998
1066
|
anchor?: Node,
|
|
999
|
-
cursor?:
|
|
1000
|
-
):
|
|
1067
|
+
cursor?: HydrationCursor,
|
|
1068
|
+
): CommittedFrameNode {
|
|
1069
|
+
let { frame, styles } = context
|
|
1001
1070
|
let runtime = getFrameRuntime(frame)
|
|
1002
|
-
if (!runtime ||
|
|
1071
|
+
if (!runtime || runtime.canResolveFrames === false) {
|
|
1003
1072
|
throw new Error(
|
|
1004
1073
|
'Cannot render <Frame /> without frame runtime. Use run() or pass frameInit to createRoot/createRangeRoot.',
|
|
1005
1074
|
)
|
|
@@ -1007,33 +1076,23 @@ function insertFrame(
|
|
|
1007
1076
|
|
|
1008
1077
|
// Hydration path: adopt server-rendered frame markers and reuse the existing
|
|
1009
1078
|
// frame instance created during createSubFrames().
|
|
1010
|
-
if (isFrameStartComment(cursor)) {
|
|
1011
|
-
let start = cursor
|
|
1079
|
+
if (isFrameStartComment(cursor?.current)) {
|
|
1080
|
+
let start = cursor.current
|
|
1012
1081
|
let end = findFrameEndComment(start)
|
|
1013
1082
|
if (end) {
|
|
1014
|
-
node._rangeStart = start
|
|
1015
|
-
node._rangeEnd = end
|
|
1016
|
-
node._parent = vParent
|
|
1017
|
-
node._frameResolveToken = 0
|
|
1018
|
-
node._frameResolveController = undefined
|
|
1019
|
-
node._frameFallbackRoot = undefined
|
|
1020
|
-
node._frameResolved = true
|
|
1021
|
-
|
|
1022
|
-
let frameId = getFrameIdFromComment(start)
|
|
1023
|
-
let marker = frameId ? runtime.data.f?.[frameId] : undefined
|
|
1024
|
-
let src = marker?.src ?? getFrameSrc(node)
|
|
1025
1083
|
let instance = runtime.frameInstances.get(start)
|
|
1084
|
+
let src = instance?.handle.src ?? getFrameSrc(node)
|
|
1026
1085
|
if (!instance) {
|
|
1027
1086
|
instance = createFrame([start, end], {
|
|
1028
1087
|
name: getFrameName(node),
|
|
1029
1088
|
src,
|
|
1030
|
-
marker: frameId && marker ? { ...marker, id: frameId } : undefined,
|
|
1031
1089
|
errorTarget: runtime.errorTarget,
|
|
1032
1090
|
loadModule: runtime.loadModule,
|
|
1033
1091
|
resolveFrame: runtime.resolveFrame,
|
|
1034
1092
|
pendingClientEntries: runtime.pendingClientEntries,
|
|
1035
1093
|
scheduler: runtime.scheduler,
|
|
1036
|
-
|
|
1094
|
+
styleManager: runtime.styleManager,
|
|
1095
|
+
data: {},
|
|
1037
1096
|
moduleCache: runtime.moduleCache,
|
|
1038
1097
|
moduleLoads: runtime.moduleLoads,
|
|
1039
1098
|
frameInstances: runtime.frameInstances,
|
|
@@ -1042,9 +1101,20 @@ function insertFrame(
|
|
|
1042
1101
|
runtime.frameInstances.set(start, instance)
|
|
1043
1102
|
}
|
|
1044
1103
|
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1104
|
+
cursor.current = end.nextSibling
|
|
1105
|
+
return Object.assign(node as unknown as CommittedFrameNode, {
|
|
1106
|
+
_rangeStart: start,
|
|
1107
|
+
_rangeEnd: end,
|
|
1108
|
+
_parent: vParent,
|
|
1109
|
+
_svg: svg,
|
|
1110
|
+
_state: {
|
|
1111
|
+
instance,
|
|
1112
|
+
resolveToken: 0,
|
|
1113
|
+
resolveController: undefined,
|
|
1114
|
+
fallbackRoot: undefined,
|
|
1115
|
+
resolved: true,
|
|
1116
|
+
},
|
|
1117
|
+
})
|
|
1048
1118
|
}
|
|
1049
1119
|
}
|
|
1050
1120
|
|
|
@@ -1057,18 +1127,11 @@ function insertFrame(
|
|
|
1057
1127
|
doInsert(start)
|
|
1058
1128
|
doInsert(end)
|
|
1059
1129
|
|
|
1060
|
-
node._rangeStart = start
|
|
1061
|
-
node._rangeEnd = end
|
|
1062
|
-
node._parent = vParent
|
|
1063
|
-
|
|
1064
1130
|
let fallbackRoot = createRangeRoot([start, end], {
|
|
1065
1131
|
frame,
|
|
1066
1132
|
styleManager: styles,
|
|
1067
1133
|
})
|
|
1068
|
-
fallbackRoot.render(node.props
|
|
1069
|
-
node._frameFallbackRoot = fallbackRoot
|
|
1070
|
-
node._frameResolved = false
|
|
1071
|
-
node._frameResolveToken = 0
|
|
1134
|
+
fallbackRoot.render(node.props.fallback ?? null)
|
|
1072
1135
|
|
|
1073
1136
|
let instance = createFrame([start, end], {
|
|
1074
1137
|
name: getFrameName(node),
|
|
@@ -1078,61 +1141,94 @@ function insertFrame(
|
|
|
1078
1141
|
resolveFrame: runtime.resolveFrame,
|
|
1079
1142
|
pendingClientEntries: runtime.pendingClientEntries,
|
|
1080
1143
|
scheduler: runtime.scheduler,
|
|
1081
|
-
|
|
1144
|
+
styleManager: runtime.styleManager,
|
|
1145
|
+
data: {},
|
|
1082
1146
|
moduleCache: runtime.moduleCache,
|
|
1083
1147
|
moduleLoads: runtime.moduleLoads,
|
|
1084
1148
|
frameInstances: runtime.frameInstances,
|
|
1085
1149
|
namedFrames: runtime.namedFrames,
|
|
1086
1150
|
})
|
|
1087
|
-
node._frameInstance = instance
|
|
1088
1151
|
runtime.frameInstances.set(start, instance)
|
|
1089
1152
|
|
|
1090
|
-
|
|
1153
|
+
let committed = Object.assign(node as unknown as CommittedFrameNode, {
|
|
1154
|
+
_rangeStart: start,
|
|
1155
|
+
_rangeEnd: end,
|
|
1156
|
+
_parent: vParent,
|
|
1157
|
+
_svg: svg,
|
|
1158
|
+
_state: {
|
|
1159
|
+
instance,
|
|
1160
|
+
fallbackRoot,
|
|
1161
|
+
resolved: false,
|
|
1162
|
+
resolveToken: 0,
|
|
1163
|
+
resolveController: undefined,
|
|
1164
|
+
},
|
|
1165
|
+
})
|
|
1166
|
+
resolveClientFrame(committed, runtime)
|
|
1091
1167
|
|
|
1092
|
-
return
|
|
1168
|
+
return committed
|
|
1093
1169
|
}
|
|
1094
1170
|
|
|
1095
|
-
function resolveClientFrame(
|
|
1171
|
+
function resolveClientFrame(
|
|
1172
|
+
node: CommittedFrameNode,
|
|
1173
|
+
runtime: FrameRuntime,
|
|
1174
|
+
serverFrameReload?: { signal: AbortSignal },
|
|
1175
|
+
): void {
|
|
1096
1176
|
let frameSrc = getFrameSrc(node)
|
|
1097
|
-
let
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
let token =
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
let
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
.
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1177
|
+
let state = node._state
|
|
1178
|
+
let instance = state.instance
|
|
1179
|
+
|
|
1180
|
+
let token = state.resolveToken + 1
|
|
1181
|
+
state.resolveToken = token
|
|
1182
|
+
state.resolveController?.abort()
|
|
1183
|
+
let reload = serverFrameReload
|
|
1184
|
+
? instance.beginClientFrameReloadForAncestorReload(serverFrameReload.signal)
|
|
1185
|
+
: undefined
|
|
1186
|
+
if (!reload) {
|
|
1187
|
+
instance.cancelReload()
|
|
1188
|
+
}
|
|
1189
|
+
let resolveController = reload?.controller ?? new AbortController()
|
|
1190
|
+
state.resolveController = resolveController
|
|
1191
|
+
|
|
1192
|
+
Promise.resolve()
|
|
1193
|
+
.then(() =>
|
|
1194
|
+
runtime.resolveFrame(frameSrc, {
|
|
1195
|
+
signal: resolveController.signal,
|
|
1196
|
+
target: getFrameName(node),
|
|
1197
|
+
}),
|
|
1198
|
+
)
|
|
1199
|
+
.then(async (resolution) => {
|
|
1200
|
+
if (state.resolveToken !== token || resolveController.signal.aborted) return
|
|
1201
|
+
let { content } = await unwrapFrameResolution(resolution)
|
|
1202
|
+
if (state.resolveToken !== token || resolveController.signal.aborted) return
|
|
1203
|
+
state.fallbackRoot?.dispose()
|
|
1204
|
+
state.fallbackRoot = undefined
|
|
1111
1205
|
let nextContent = asAbortableFrameContent(content, resolveController.signal)
|
|
1112
1206
|
await instance.render(nextContent, { signal: resolveController.signal })
|
|
1113
|
-
if (
|
|
1114
|
-
|
|
1207
|
+
if (state.resolveToken !== token || resolveController.signal.aborted) return
|
|
1208
|
+
state.resolved = true
|
|
1209
|
+
})
|
|
1210
|
+
.catch((error) => {
|
|
1211
|
+
if (reload && state.resolveToken === token && !resolveController.signal.aborted) {
|
|
1212
|
+
runtime.errorTarget.dispatchEvent(createComponentErrorEvent(error))
|
|
1213
|
+
}
|
|
1115
1214
|
})
|
|
1116
|
-
.catch(() => {})
|
|
1117
1215
|
.finally(() => {
|
|
1118
|
-
|
|
1119
|
-
|
|
1216
|
+
reload?.complete()
|
|
1217
|
+
if (state.resolveController === resolveController) {
|
|
1218
|
+
state.resolveController = undefined
|
|
1120
1219
|
}
|
|
1121
1220
|
})
|
|
1122
1221
|
}
|
|
1123
1222
|
|
|
1124
|
-
function disposeFrameResources(node:
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1223
|
+
function disposeFrameResources(node: CommittedFrameNode): void {
|
|
1224
|
+
let state = node._state
|
|
1225
|
+
state.resolveToken++
|
|
1226
|
+
state.resolveController?.abort()
|
|
1227
|
+
state.resolveController = undefined
|
|
1228
|
+
state.fallbackRoot?.dispose()
|
|
1229
|
+
state.fallbackRoot = undefined
|
|
1130
1230
|
|
|
1131
|
-
|
|
1132
|
-
if (frameInstance) {
|
|
1133
|
-
frameInstance.dispose()
|
|
1134
|
-
node._frameInstance = undefined
|
|
1135
|
-
}
|
|
1231
|
+
state.instance.dispose()
|
|
1136
1232
|
}
|
|
1137
1233
|
|
|
1138
1234
|
function asAbortableFrameContent(content: FrameContent, signal: AbortSignal): FrameContent {
|
|
@@ -1163,26 +1259,26 @@ function createAbortableReadableStream(
|
|
|
1163
1259
|
}
|
|
1164
1260
|
|
|
1165
1261
|
let removeAbortReadListener: undefined | (() => void)
|
|
1166
|
-
let abortRead = new Promise<
|
|
1262
|
+
let abortRead = new Promise<{ done: true; value: undefined }>((resolve) => {
|
|
1167
1263
|
if (signal.aborted) {
|
|
1168
|
-
resolve({ done: true, value: undefined
|
|
1264
|
+
resolve({ done: true, value: undefined })
|
|
1169
1265
|
return
|
|
1170
1266
|
}
|
|
1171
1267
|
let onAbortRead = () => {
|
|
1172
|
-
resolve({ done: true, value: undefined
|
|
1268
|
+
resolve({ done: true, value: undefined })
|
|
1173
1269
|
}
|
|
1174
1270
|
removeAbortReadListener = () => signal.removeEventListener('abort', onAbortRead)
|
|
1175
1271
|
signal.addEventListener('abort', onAbortRead, { once: true })
|
|
1176
1272
|
})
|
|
1177
1273
|
|
|
1178
|
-
let
|
|
1274
|
+
let result = await Promise.race([reader.read(), abortRead])
|
|
1179
1275
|
removeAbortReadListener?.()
|
|
1180
1276
|
|
|
1181
|
-
if (done) {
|
|
1277
|
+
if (result.done) {
|
|
1182
1278
|
controller.close()
|
|
1183
1279
|
return
|
|
1184
1280
|
}
|
|
1185
|
-
controller.enqueue(value)
|
|
1281
|
+
controller.enqueue(result.value)
|
|
1186
1282
|
},
|
|
1187
1283
|
cancel(reason) {
|
|
1188
1284
|
signal.removeEventListener('abort', onAbort)
|
|
@@ -1191,11 +1287,9 @@ function createAbortableReadableStream(
|
|
|
1191
1287
|
})
|
|
1192
1288
|
}
|
|
1193
1289
|
|
|
1194
|
-
function removeFrameDomRange(node:
|
|
1290
|
+
function removeFrameDomRange(node: CommittedFrameNode, domParent: ParentNode): void {
|
|
1195
1291
|
let start = node._rangeStart
|
|
1196
1292
|
let end = node._rangeEnd
|
|
1197
|
-
if (!(start instanceof Comment) || !(end instanceof Comment)) return
|
|
1198
|
-
|
|
1199
1293
|
let cursor: Node | null = start
|
|
1200
1294
|
while (cursor) {
|
|
1201
1295
|
let nextSibling: Node | null = cursor.nextSibling
|
|
@@ -1205,23 +1299,19 @@ function removeFrameDomRange(node: VNode, domParent: ParentNode): void {
|
|
|
1205
1299
|
if (cursor === end) break
|
|
1206
1300
|
cursor = nextSibling
|
|
1207
1301
|
}
|
|
1208
|
-
|
|
1209
|
-
node._rangeStart = undefined
|
|
1210
|
-
node._rangeEnd = undefined
|
|
1211
1302
|
}
|
|
1212
1303
|
|
|
1213
1304
|
function getFrameRuntime(frame: FrameHandle): FrameRuntime | undefined {
|
|
1214
|
-
|
|
1305
|
+
let runtime = frame.$runtime
|
|
1306
|
+
return isFrameRuntime(runtime) ? runtime : undefined
|
|
1215
1307
|
}
|
|
1216
1308
|
|
|
1217
|
-
function getFrameSrc(node:
|
|
1218
|
-
|
|
1219
|
-
invariant(typeof src === 'string' && src.length > 0, '<Frame /> requires a src prop')
|
|
1220
|
-
return src
|
|
1309
|
+
function getFrameSrc(node: FrameNode | CommittedFrameNode): string {
|
|
1310
|
+
return node.props.src
|
|
1221
1311
|
}
|
|
1222
1312
|
|
|
1223
|
-
function getFrameName(node:
|
|
1224
|
-
let name = node.props
|
|
1313
|
+
function getFrameName(node: FrameNode | CommittedFrameNode): string | undefined {
|
|
1314
|
+
let name = node.props.name
|
|
1225
1315
|
return typeof name === 'string' && name.length > 0 ? name : undefined
|
|
1226
1316
|
}
|
|
1227
1317
|
|
|
@@ -1238,17 +1328,15 @@ function skipCommentsExceptFrameStart(cursor: Node | null): Node | null {
|
|
|
1238
1328
|
}
|
|
1239
1329
|
|
|
1240
1330
|
function isFrameStartComment(node: Node | null | undefined): node is Comment {
|
|
1241
|
-
return node
|
|
1331
|
+
return isCommentNode(node) && node.data.trim().startsWith('rmx:f:')
|
|
1242
1332
|
}
|
|
1243
1333
|
|
|
1244
1334
|
function isFrameEndComment(node: Node | null | undefined): node is Comment {
|
|
1245
|
-
return node
|
|
1335
|
+
return isCommentNode(node) && node.data.trim() === '/rmx:f'
|
|
1246
1336
|
}
|
|
1247
1337
|
|
|
1248
|
-
function
|
|
1249
|
-
|
|
1250
|
-
if (!text.startsWith('rmx:f:')) return undefined
|
|
1251
|
-
return text.slice('rmx:f:'.length)
|
|
1338
|
+
function isCommentNode(node: Node | null | undefined): node is Comment {
|
|
1339
|
+
return node?.nodeType === Node.COMMENT_NODE
|
|
1252
1340
|
}
|
|
1253
1341
|
|
|
1254
1342
|
function findFrameEndComment(start: Comment): Comment | null {
|
|
@@ -1268,72 +1356,53 @@ function findFrameEndComment(start: Comment): Comment | null {
|
|
|
1268
1356
|
}
|
|
1269
1357
|
|
|
1270
1358
|
export function renderComponent(
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
next: ComponentNode,
|
|
1359
|
+
currContent: CommittedVNode | null,
|
|
1360
|
+
next: MountingComponentNode | CommittedComponentNode,
|
|
1274
1361
|
domParent: ParentNode,
|
|
1275
|
-
|
|
1276
|
-
scheduler: Scheduler,
|
|
1277
|
-
styles: StyleManager,
|
|
1278
|
-
rootTarget: EventTarget,
|
|
1279
|
-
vParent?: VNode,
|
|
1362
|
+
context: ReconcileContext,
|
|
1280
1363
|
anchor?: Node,
|
|
1281
|
-
cursor?:
|
|
1282
|
-
):
|
|
1283
|
-
|
|
1364
|
+
cursor?: HydrationCursor,
|
|
1365
|
+
): CommittedComponentNode {
|
|
1366
|
+
let { scheduler } = context
|
|
1367
|
+
let handle = next._handle
|
|
1368
|
+
if (handle.isRemoved()) {
|
|
1369
|
+
invariant('_content' in next, 'Expected removed component to be committed')
|
|
1370
|
+
return next
|
|
1371
|
+
}
|
|
1284
1372
|
|
|
1285
1373
|
let [element, tasks] = handle.render(next.props)
|
|
1286
|
-
let content = toVNode(element)
|
|
1287
|
-
let
|
|
1288
|
-
|
|
1289
|
-
content,
|
|
1290
|
-
domParent,
|
|
1291
|
-
frame,
|
|
1292
|
-
scheduler,
|
|
1293
|
-
styles,
|
|
1294
|
-
next,
|
|
1295
|
-
rootTarget,
|
|
1296
|
-
anchor,
|
|
1297
|
-
cursor,
|
|
1298
|
-
)
|
|
1299
|
-
next._content = content
|
|
1300
|
-
next._handle = handle
|
|
1301
|
-
next._parent = vParent
|
|
1302
|
-
|
|
1303
|
-
let committed = next as CommittedComponentNode
|
|
1304
|
-
handle.setScheduleUpdate(() => {
|
|
1305
|
-
scheduler.enqueue(committed, domParent)
|
|
1306
|
-
})
|
|
1374
|
+
let content = diffVNodes(currContent, toVNode(element), domParent, next, context, anchor, cursor)
|
|
1375
|
+
let committed = commitComponentNode(next, content)
|
|
1376
|
+
handle.setScheduleUpdate(scheduler, committed, domParent)
|
|
1307
1377
|
|
|
1308
1378
|
scheduler.enqueueTasks(tasks)
|
|
1309
1379
|
|
|
1310
|
-
return
|
|
1380
|
+
return committed
|
|
1311
1381
|
}
|
|
1312
1382
|
|
|
1313
1383
|
function diffComponent(
|
|
1314
1384
|
curr: CommittedComponentNode | null,
|
|
1315
1385
|
next: ComponentNode,
|
|
1316
|
-
frame: FrameHandle,
|
|
1317
|
-
scheduler: Scheduler,
|
|
1318
|
-
styles: StyleManager,
|
|
1319
1386
|
domParent: ParentNode,
|
|
1320
|
-
vParent:
|
|
1321
|
-
|
|
1387
|
+
vParent: VNodeParent,
|
|
1388
|
+
context: ReconcileContext,
|
|
1389
|
+
svg: boolean,
|
|
1322
1390
|
anchor?: Node,
|
|
1323
|
-
cursor?:
|
|
1324
|
-
):
|
|
1391
|
+
cursor?: HydrationCursor,
|
|
1392
|
+
): CommittedComponentNode {
|
|
1393
|
+
let { frame } = context
|
|
1325
1394
|
if (curr === null) {
|
|
1326
|
-
let componentId = vParent._pendingHydrationComponentId
|
|
1395
|
+
let componentId = vParent.kind === 'root' ? vParent._pendingHydrationComponentId : undefined
|
|
1327
1396
|
if (componentId) {
|
|
1328
|
-
vParent._pendingHydrationComponentId = undefined
|
|
1397
|
+
if (vParent.kind === 'root') vParent._pendingHydrationComponentId = undefined
|
|
1329
1398
|
} else {
|
|
1330
1399
|
componentId = `c${++idCounter}`
|
|
1331
1400
|
}
|
|
1332
|
-
|
|
1401
|
+
let handle = createComponent({
|
|
1333
1402
|
id: componentId,
|
|
1334
1403
|
frame,
|
|
1335
1404
|
type: next.type,
|
|
1336
|
-
getContext: (type:
|
|
1405
|
+
getContext: (type: ElementFunction) => findContextFromAncestry(vParent, type),
|
|
1337
1406
|
getFrameByName(name: string) {
|
|
1338
1407
|
let runtime = getFrameRuntime(frame)
|
|
1339
1408
|
return runtime?.namedFrames.get(name)
|
|
@@ -1343,123 +1412,102 @@ function diffComponent(
|
|
|
1343
1412
|
return runtime?.topFrame
|
|
1344
1413
|
},
|
|
1345
1414
|
})
|
|
1415
|
+
let mounting = beginComponentNode(next, vParent, svg, handle, context)
|
|
1346
1416
|
|
|
1347
|
-
return renderComponent(
|
|
1348
|
-
next._handle,
|
|
1349
|
-
null,
|
|
1350
|
-
next,
|
|
1351
|
-
domParent,
|
|
1352
|
-
frame,
|
|
1353
|
-
scheduler,
|
|
1354
|
-
styles,
|
|
1355
|
-
rootTarget,
|
|
1356
|
-
vParent,
|
|
1357
|
-
anchor,
|
|
1358
|
-
cursor,
|
|
1359
|
-
)
|
|
1417
|
+
return renderComponent(null, mounting, domParent, context, anchor, cursor)
|
|
1360
1418
|
}
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
return renderComponent(
|
|
1364
|
-
_handle,
|
|
1365
|
-
_content,
|
|
1366
|
-
next,
|
|
1367
|
-
domParent,
|
|
1368
|
-
frame,
|
|
1369
|
-
scheduler,
|
|
1370
|
-
styles,
|
|
1371
|
-
rootTarget,
|
|
1372
|
-
vParent,
|
|
1373
|
-
anchor,
|
|
1374
|
-
cursor,
|
|
1375
|
-
)
|
|
1419
|
+
let mounting = beginComponentNode(next, vParent, svg, curr._handle, context)
|
|
1420
|
+
return renderComponent(curr._content, mounting, domParent, context, anchor, cursor)
|
|
1376
1421
|
}
|
|
1377
1422
|
|
|
1378
1423
|
// Cleanup without DOM removal - used for descendants when parent DOM node is removed
|
|
1379
|
-
function cleanupDescendants(node:
|
|
1424
|
+
function cleanupDescendants(node: CommittedVNode, context: ReconcileContext): void {
|
|
1425
|
+
let { scheduler } = context
|
|
1380
1426
|
if (isCommittedTextNode(node)) {
|
|
1381
1427
|
return
|
|
1382
1428
|
}
|
|
1383
1429
|
|
|
1384
1430
|
if (isCommittedHostNode(node)) {
|
|
1385
|
-
|
|
1386
|
-
|
|
1431
|
+
let children = node._children
|
|
1432
|
+
for (let i = 0; i < children.length; i++) {
|
|
1433
|
+
cleanupDescendants(children[i], context)
|
|
1387
1434
|
}
|
|
1388
1435
|
|
|
1389
|
-
teardownMixins(node._mixState
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
if (node._controller) node._controller.abort()
|
|
1436
|
+
teardownMixins(node._mixState)
|
|
1437
|
+
abandonDirectEventListeners(node)
|
|
1438
|
+
abandonControlledReflection(node)
|
|
1393
1439
|
return
|
|
1394
1440
|
}
|
|
1395
1441
|
|
|
1396
1442
|
if (isFragmentNode(node)) {
|
|
1397
|
-
|
|
1398
|
-
|
|
1443
|
+
let children = node._children
|
|
1444
|
+
for (let i = 0; i < children.length; i++) {
|
|
1445
|
+
cleanupDescendants(children[i], context)
|
|
1399
1446
|
}
|
|
1400
1447
|
return
|
|
1401
1448
|
}
|
|
1402
1449
|
|
|
1403
1450
|
if (isCommittedComponentNode(node)) {
|
|
1404
|
-
cleanupDescendants(node._content,
|
|
1451
|
+
cleanupDescendants(node._content, context)
|
|
1405
1452
|
let tasks = node._handle.remove()
|
|
1406
1453
|
scheduler.enqueueTasks(tasks)
|
|
1407
1454
|
return
|
|
1408
1455
|
}
|
|
1409
1456
|
|
|
1410
|
-
if (node.
|
|
1457
|
+
if (node.kind === 'frame') {
|
|
1411
1458
|
disposeFrameResources(node)
|
|
1412
1459
|
return
|
|
1413
1460
|
}
|
|
1414
1461
|
}
|
|
1415
1462
|
|
|
1416
1463
|
export function remove(
|
|
1417
|
-
node:
|
|
1464
|
+
node: CommittedVNode,
|
|
1418
1465
|
domParent: ParentNode,
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1466
|
+
context: ReconcileContext,
|
|
1467
|
+
): void {
|
|
1468
|
+
let { scheduler } = context
|
|
1422
1469
|
if (isCommittedTextNode(node)) {
|
|
1423
1470
|
node._dom.parentNode?.removeChild(node._dom)
|
|
1424
1471
|
return
|
|
1425
1472
|
}
|
|
1426
1473
|
|
|
1427
1474
|
if (isCommittedHostNode(node)) {
|
|
1428
|
-
if (node.
|
|
1475
|
+
if (node._persistence) return
|
|
1429
1476
|
|
|
1430
|
-
let persistedRemoval = prepareMixinRemoval(node._mixState
|
|
1477
|
+
let persistedRemoval = prepareMixinRemoval(node._mixState)
|
|
1431
1478
|
if (persistedRemoval) {
|
|
1432
1479
|
let token = ++persistedRemovalToken
|
|
1433
1480
|
markNodePersistedByMixins(node, domParent, token)
|
|
1434
1481
|
void persistedRemoval
|
|
1435
1482
|
.catch(() => {})
|
|
1436
1483
|
.finally(() => {
|
|
1437
|
-
if (!node.
|
|
1438
|
-
if (node.
|
|
1484
|
+
if (!node._persistence) return
|
|
1485
|
+
if (node._persistence.token !== token) return
|
|
1439
1486
|
unmarkNodePersistedByMixins(node)
|
|
1440
|
-
performHostNodeRemoval(node, domParent,
|
|
1487
|
+
performHostNodeRemoval(node, domParent, context)
|
|
1441
1488
|
})
|
|
1442
1489
|
return
|
|
1443
1490
|
}
|
|
1444
|
-
performHostNodeRemoval(node, domParent,
|
|
1491
|
+
performHostNodeRemoval(node, domParent, context)
|
|
1445
1492
|
return
|
|
1446
1493
|
}
|
|
1447
1494
|
|
|
1448
1495
|
if (isFragmentNode(node)) {
|
|
1449
|
-
|
|
1450
|
-
|
|
1496
|
+
let children = node._children
|
|
1497
|
+
for (let i = 0; i < children.length; i++) {
|
|
1498
|
+
remove(children[i], domParent, context)
|
|
1451
1499
|
}
|
|
1452
1500
|
return
|
|
1453
1501
|
}
|
|
1454
1502
|
|
|
1455
1503
|
if (isCommittedComponentNode(node)) {
|
|
1456
|
-
remove(node._content, domParent,
|
|
1504
|
+
remove(node._content, domParent, context)
|
|
1457
1505
|
let tasks = node._handle.remove()
|
|
1458
1506
|
scheduler.enqueueTasks(tasks)
|
|
1459
1507
|
return
|
|
1460
1508
|
}
|
|
1461
1509
|
|
|
1462
|
-
if (node.
|
|
1510
|
+
if (node.kind === 'frame') {
|
|
1463
1511
|
disposeFrameResources(node)
|
|
1464
1512
|
removeFrameDomRange(node, domParent)
|
|
1465
1513
|
return
|
|
@@ -1470,63 +1518,53 @@ export function remove(
|
|
|
1470
1518
|
function performHostNodeRemoval(
|
|
1471
1519
|
node: CommittedHostNode,
|
|
1472
1520
|
domParent: ParentNode,
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1521
|
+
context: ReconcileContext,
|
|
1522
|
+
): void {
|
|
1523
|
+
let children = node._children
|
|
1476
1524
|
if (isHeadHostNode(node)) {
|
|
1477
|
-
for (let
|
|
1478
|
-
remove(
|
|
1525
|
+
for (let i = 0; i < children.length; i++) {
|
|
1526
|
+
remove(children[i], node._dom, context)
|
|
1479
1527
|
}
|
|
1480
1528
|
} else {
|
|
1481
1529
|
// Clean up all descendants first (before removing DOM subtree)
|
|
1482
|
-
for (let
|
|
1483
|
-
cleanupDescendants(
|
|
1530
|
+
for (let i = 0; i < children.length; i++) {
|
|
1531
|
+
cleanupDescendants(children[i], context)
|
|
1484
1532
|
}
|
|
1485
1533
|
}
|
|
1486
1534
|
|
|
1487
|
-
teardownMixins(node._mixState
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1535
|
+
teardownMixins(node._mixState)
|
|
1536
|
+
// Never remove the real document.head node when reconciling a <head> vnode,
|
|
1537
|
+
// and since it stays alive, detach its listeners for real.
|
|
1538
|
+
if (isHeadHostNode(node)) {
|
|
1539
|
+
teardownDirectEventListeners(node)
|
|
1540
|
+
teardownControlledReflection(node)
|
|
1541
|
+
} else {
|
|
1542
|
+
abandonDirectEventListeners(node)
|
|
1543
|
+
abandonControlledReflection(node)
|
|
1492
1544
|
node._dom.parentNode?.removeChild(node._dom)
|
|
1493
1545
|
}
|
|
1494
|
-
if (node._controller) node._controller.abort()
|
|
1495
1546
|
}
|
|
1496
1547
|
|
|
1497
1548
|
function diffChildren(
|
|
1498
|
-
curr:
|
|
1499
|
-
next:
|
|
1549
|
+
curr: CommittedVNode[] | null,
|
|
1550
|
+
next: VNodeInput[],
|
|
1500
1551
|
domParent: ParentNode,
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
vParent: VNode,
|
|
1505
|
-
rootTarget: EventTarget,
|
|
1506
|
-
cursor?: Node | null,
|
|
1552
|
+
vParent: VNodeParent,
|
|
1553
|
+
context: ReconcileContext,
|
|
1554
|
+
cursor?: HydrationCursor,
|
|
1507
1555
|
anchor?: Node,
|
|
1508
|
-
) {
|
|
1556
|
+
): CommittedVNode[] {
|
|
1509
1557
|
let hasKeys = hasKeyedChildren(next)
|
|
1558
|
+
let committed: Array<VNodeInput | CommittedVNode> = next
|
|
1510
1559
|
|
|
1511
1560
|
if (curr === null) {
|
|
1512
1561
|
if (hasKeys) {
|
|
1513
1562
|
warnDuplicateKeys(next)
|
|
1514
1563
|
}
|
|
1515
|
-
for (let
|
|
1516
|
-
|
|
1517
|
-
node,
|
|
1518
|
-
domParent,
|
|
1519
|
-
frame,
|
|
1520
|
-
scheduler,
|
|
1521
|
-
styles,
|
|
1522
|
-
vParent,
|
|
1523
|
-
rootTarget,
|
|
1524
|
-
anchor,
|
|
1525
|
-
cursor,
|
|
1526
|
-
)
|
|
1564
|
+
for (let i = 0; i < next.length; i++) {
|
|
1565
|
+
committed[i] = insert(next[i], domParent, vParent, context, anchor, cursor)
|
|
1527
1566
|
}
|
|
1528
|
-
|
|
1529
|
-
return cursor
|
|
1567
|
+
return committed as CommittedVNode[]
|
|
1530
1568
|
}
|
|
1531
1569
|
|
|
1532
1570
|
if (
|
|
@@ -1535,78 +1573,49 @@ function diffChildren(
|
|
|
1535
1573
|
!parentUsesInnerHTML(vParent) &&
|
|
1536
1574
|
canBulkClearChildren(curr)
|
|
1537
1575
|
) {
|
|
1538
|
-
for (let
|
|
1539
|
-
cleanupDescendants(
|
|
1576
|
+
for (let i = 0; i < curr.length; i++) {
|
|
1577
|
+
cleanupDescendants(curr[i], context)
|
|
1540
1578
|
}
|
|
1541
1579
|
domParent.textContent = ''
|
|
1542
|
-
|
|
1543
|
-
return
|
|
1580
|
+
return EMPTY_COMMITTED_CHILDREN
|
|
1544
1581
|
}
|
|
1545
1582
|
|
|
1546
1583
|
if (!hasKeys) {
|
|
1547
1584
|
for (let i = 0; i < next.length; i++) {
|
|
1548
1585
|
let currentNode = i < curr.length ? curr[i] : null
|
|
1549
|
-
diffVNodes(
|
|
1550
|
-
currentNode,
|
|
1551
|
-
next[i],
|
|
1552
|
-
domParent,
|
|
1553
|
-
frame,
|
|
1554
|
-
scheduler,
|
|
1555
|
-
styles,
|
|
1556
|
-
vParent,
|
|
1557
|
-
rootTarget,
|
|
1558
|
-
anchor,
|
|
1559
|
-
cursor,
|
|
1560
|
-
)
|
|
1586
|
+
committed[i] = diffVNodes(currentNode, next[i], domParent, vParent, context, anchor, cursor)
|
|
1561
1587
|
}
|
|
1562
1588
|
|
|
1563
1589
|
if (curr.length > next.length) {
|
|
1564
1590
|
for (let i = next.length; i < curr.length; i++) {
|
|
1565
1591
|
let node = curr[i]
|
|
1566
|
-
if (node) remove(node, domParent,
|
|
1592
|
+
if (node) remove(node, domParent, context)
|
|
1567
1593
|
}
|
|
1568
1594
|
}
|
|
1569
1595
|
|
|
1570
|
-
|
|
1571
|
-
return
|
|
1596
|
+
return committed as CommittedVNode[]
|
|
1572
1597
|
}
|
|
1573
1598
|
|
|
1574
|
-
patchKeyedChildren(
|
|
1575
|
-
curr,
|
|
1576
|
-
next,
|
|
1577
|
-
domParent,
|
|
1578
|
-
frame,
|
|
1579
|
-
scheduler,
|
|
1580
|
-
styles,
|
|
1581
|
-
vParent,
|
|
1582
|
-
rootTarget,
|
|
1583
|
-
cursor,
|
|
1584
|
-
anchor,
|
|
1585
|
-
)
|
|
1586
|
-
|
|
1587
|
-
return
|
|
1599
|
+
return patchKeyedChildren(curr, next, domParent, vParent, context, cursor, anchor)
|
|
1588
1600
|
}
|
|
1589
1601
|
|
|
1590
|
-
function parentUsesInnerHTML(parent:
|
|
1602
|
+
function parentUsesInnerHTML(parent: VNodeParent): boolean {
|
|
1591
1603
|
return isHostNode(parent) && getHostProps(parent).innerHTML != null
|
|
1592
1604
|
}
|
|
1593
1605
|
|
|
1594
|
-
function canBulkClearChildren(children:
|
|
1595
|
-
for (let
|
|
1596
|
-
if (!canBulkClearNode(
|
|
1606
|
+
function canBulkClearChildren(children: CommittedVNode[]): boolean {
|
|
1607
|
+
for (let i = 0; i < children.length; i++) {
|
|
1608
|
+
if (!canBulkClearNode(children[i])) return false
|
|
1597
1609
|
}
|
|
1598
1610
|
return true
|
|
1599
1611
|
}
|
|
1600
1612
|
|
|
1601
|
-
function canBulkClearNode(node:
|
|
1613
|
+
function canBulkClearNode(node: CommittedVNode): boolean {
|
|
1602
1614
|
if (isCommittedTextNode(node)) return true
|
|
1603
1615
|
|
|
1604
1616
|
if (isCommittedHostNode(node)) {
|
|
1605
1617
|
if (node._mixState) return false
|
|
1606
|
-
|
|
1607
|
-
if (!canBulkClearNode(child)) return false
|
|
1608
|
-
}
|
|
1609
|
-
return true
|
|
1618
|
+
return canBulkClearChildren(node._children)
|
|
1610
1619
|
}
|
|
1611
1620
|
|
|
1612
1621
|
if (isFragmentNode(node)) {
|
|
@@ -1620,16 +1629,16 @@ function canBulkClearNode(node: VNode): boolean {
|
|
|
1620
1629
|
return false
|
|
1621
1630
|
}
|
|
1622
1631
|
|
|
1623
|
-
function hasKeyedChildren(children:
|
|
1624
|
-
for (let
|
|
1625
|
-
if (
|
|
1632
|
+
function hasKeyedChildren(children: Array<{ key?: Key }>): boolean {
|
|
1633
|
+
for (let i = 0; i < children.length; i++) {
|
|
1634
|
+
if (children[i].key != null) return true
|
|
1626
1635
|
}
|
|
1627
1636
|
return false
|
|
1628
1637
|
}
|
|
1629
1638
|
|
|
1630
|
-
function warnDuplicateKeys(children:
|
|
1631
|
-
let seenKeys: Set<
|
|
1632
|
-
let duplicateKeys: Set<
|
|
1639
|
+
function warnDuplicateKeys(children: Array<{ key?: Key }>): void {
|
|
1640
|
+
let seenKeys: Set<Key> | undefined
|
|
1641
|
+
let duplicateKeys: Set<Key> | undefined
|
|
1633
1642
|
|
|
1634
1643
|
for (let node of children) {
|
|
1635
1644
|
if (node.key == null) continue
|
|
@@ -1648,7 +1657,7 @@ function warnDuplicateKeys(children: VNode[]): void {
|
|
|
1648
1657
|
}
|
|
1649
1658
|
|
|
1650
1659
|
if (duplicateKeys?.size) {
|
|
1651
|
-
let quotedKeys = Array.from(duplicateKeys, (key) => `"${key}"`)
|
|
1660
|
+
let quotedKeys = Array.from(duplicateKeys, (key) => `"${String(key)}"`)
|
|
1652
1661
|
console.warn(
|
|
1653
1662
|
`Duplicate keys detected in siblings: ${quotedKeys.join(', ')}. Keys should be unique.`,
|
|
1654
1663
|
)
|
|
@@ -1656,17 +1665,14 @@ function warnDuplicateKeys(children: VNode[]): void {
|
|
|
1656
1665
|
}
|
|
1657
1666
|
|
|
1658
1667
|
function patchKeyedChildren(
|
|
1659
|
-
curr:
|
|
1660
|
-
next:
|
|
1668
|
+
curr: CommittedVNode[],
|
|
1669
|
+
next: VNodeInput[],
|
|
1661
1670
|
domParent: ParentNode,
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
vParent: VNode,
|
|
1666
|
-
rootTarget: EventTarget,
|
|
1667
|
-
cursor?: Node | null,
|
|
1671
|
+
vParent: VNodeParent,
|
|
1672
|
+
context: ReconcileContext,
|
|
1673
|
+
cursor?: HydrationCursor,
|
|
1668
1674
|
anchor?: Node,
|
|
1669
|
-
):
|
|
1675
|
+
): CommittedVNode[] {
|
|
1670
1676
|
let matches =
|
|
1671
1677
|
matchKeyedChildrenInOrder(curr, next) ??
|
|
1672
1678
|
matchKeyedChildrenAfterSingleRemoval(curr, next) ??
|
|
@@ -1675,46 +1681,36 @@ function patchKeyedChildren(
|
|
|
1675
1681
|
warnDuplicateKeys(next)
|
|
1676
1682
|
matches = matchKeyedChildren(curr, next)
|
|
1677
1683
|
}
|
|
1684
|
+
let committed: Array<VNodeInput | CommittedVNode> = next
|
|
1678
1685
|
|
|
1679
1686
|
let matchAnalysis = analyzeKeyedChildMatches(curr.length, matches)
|
|
1680
1687
|
if (matchAnalysis.hasRemovals) {
|
|
1681
1688
|
let usedOldIndexes = new Uint8Array(curr.length)
|
|
1682
1689
|
|
|
1683
|
-
for (let
|
|
1684
|
-
|
|
1685
|
-
|
|
1690
|
+
for (let index = 0; index < matches.length; index++) {
|
|
1691
|
+
let oldIndex = matches[index]
|
|
1692
|
+
if (oldIndex >= 0) {
|
|
1693
|
+
usedOldIndexes[oldIndex] = 1
|
|
1686
1694
|
}
|
|
1687
1695
|
}
|
|
1688
1696
|
|
|
1689
1697
|
for (let oldIndex = 0; oldIndex < curr.length; oldIndex++) {
|
|
1690
1698
|
if (usedOldIndexes[oldIndex] === 0) {
|
|
1691
|
-
remove(curr[oldIndex], domParent,
|
|
1699
|
+
remove(curr[oldIndex], domParent, context)
|
|
1692
1700
|
}
|
|
1693
1701
|
}
|
|
1694
1702
|
}
|
|
1695
1703
|
|
|
1696
|
-
vParent._children = next
|
|
1697
|
-
|
|
1698
1704
|
for (let index = 0; index < next.length; index++) {
|
|
1699
|
-
let
|
|
1700
|
-
let oldNode =
|
|
1705
|
+
let oldIndex = matches[index]
|
|
1706
|
+
let oldNode = oldIndex >= 0 ? curr[oldIndex] : null
|
|
1701
1707
|
|
|
1702
|
-
diffVNodes(
|
|
1703
|
-
oldNode,
|
|
1704
|
-
next[index],
|
|
1705
|
-
domParent,
|
|
1706
|
-
frame,
|
|
1707
|
-
scheduler,
|
|
1708
|
-
styles,
|
|
1709
|
-
vParent,
|
|
1710
|
-
rootTarget,
|
|
1711
|
-
anchor,
|
|
1712
|
-
cursor,
|
|
1713
|
-
)
|
|
1708
|
+
committed[index] = diffVNodes(oldNode, next[index], domParent, vParent, context, anchor, cursor)
|
|
1714
1709
|
}
|
|
1710
|
+
let committedChildren = committed as CommittedVNode[]
|
|
1715
1711
|
|
|
1716
1712
|
if (matchAnalysis.canSkipPlacement) {
|
|
1717
|
-
return
|
|
1713
|
+
return committedChildren
|
|
1718
1714
|
}
|
|
1719
1715
|
|
|
1720
1716
|
let stableIndexes = lisMatches(matches)
|
|
@@ -1722,7 +1718,7 @@ function patchKeyedChildren(
|
|
|
1722
1718
|
let placementAnchor: Node | null = anchor ?? null
|
|
1723
1719
|
|
|
1724
1720
|
for (let index = next.length - 1; index >= 0; index--) {
|
|
1725
|
-
let nextNode =
|
|
1721
|
+
let nextNode = committedChildren[index]
|
|
1726
1722
|
let isStable = stableIndexes[stableCursor] === index
|
|
1727
1723
|
|
|
1728
1724
|
if (isStable) {
|
|
@@ -1733,10 +1729,14 @@ function patchKeyedChildren(
|
|
|
1733
1729
|
|
|
1734
1730
|
placementAnchor = findFirstDomAnchor(nextNode) ?? placementAnchor
|
|
1735
1731
|
}
|
|
1732
|
+
return committedChildren
|
|
1736
1733
|
}
|
|
1737
1734
|
|
|
1738
|
-
|
|
1739
|
-
|
|
1735
|
+
// Keyed child matches are arrays of old indexes (-1 = no match / new node),
|
|
1736
|
+
// parallel to `next`. Plain numbers instead of wrapper objects keep large
|
|
1737
|
+
// keyed diffs (1000-row tables) allocation-free.
|
|
1738
|
+
function matchKeyedChildren(curr: CommittedVNode[], next: VNodeInput[]): number[] {
|
|
1739
|
+
let oldKeyMap = new Map<Key, number>()
|
|
1740
1740
|
let usedOldIndexes = new Set<number>()
|
|
1741
1741
|
let unkeyedSearchStart = 0
|
|
1742
1742
|
|
|
@@ -1745,7 +1745,9 @@ function matchKeyedChildren(curr: VNode[], next: VNode[]): Array<{ oldIndex: num
|
|
|
1745
1745
|
if (key != null) oldKeyMap.set(key, index)
|
|
1746
1746
|
}
|
|
1747
1747
|
|
|
1748
|
-
|
|
1748
|
+
let matches: number[] = []
|
|
1749
|
+
for (let nextIndex = 0; nextIndex < next.length; nextIndex++) {
|
|
1750
|
+
let nextNode = next[nextIndex]
|
|
1749
1751
|
let oldIndex = -1
|
|
1750
1752
|
|
|
1751
1753
|
if (nextNode.key != null) {
|
|
@@ -1770,16 +1772,15 @@ function matchKeyedChildren(curr: VNode[], next: VNode[]): Array<{ oldIndex: num
|
|
|
1770
1772
|
}
|
|
1771
1773
|
|
|
1772
1774
|
if (oldIndex >= 0) usedOldIndexes.add(oldIndex)
|
|
1773
|
-
|
|
1774
|
-
}
|
|
1775
|
+
matches.push(oldIndex)
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
return matches
|
|
1775
1779
|
}
|
|
1776
1780
|
|
|
1777
|
-
function matchKeyedChildrenInOrder(
|
|
1778
|
-
curr: VNode[],
|
|
1779
|
-
next: VNode[],
|
|
1780
|
-
): Array<{ oldIndex: number }> | null {
|
|
1781
|
+
function matchKeyedChildrenInOrder(curr: CommittedVNode[], next: VNodeInput[]): number[] | null {
|
|
1781
1782
|
let length = Math.min(curr.length, next.length)
|
|
1782
|
-
let matches:
|
|
1783
|
+
let matches: number[] = []
|
|
1783
1784
|
|
|
1784
1785
|
for (let index = 0; index < length; index++) {
|
|
1785
1786
|
let nextNode = next[index]
|
|
@@ -1790,24 +1791,24 @@ function matchKeyedChildrenInOrder(
|
|
|
1790
1791
|
return null
|
|
1791
1792
|
}
|
|
1792
1793
|
|
|
1793
|
-
matches.push(
|
|
1794
|
+
matches.push(index)
|
|
1794
1795
|
}
|
|
1795
1796
|
|
|
1796
1797
|
for (let index = length; index < next.length; index++) {
|
|
1797
1798
|
if (next[index].key == null) return null
|
|
1798
|
-
matches.push(
|
|
1799
|
+
matches.push(-1)
|
|
1799
1800
|
}
|
|
1800
1801
|
|
|
1801
1802
|
return matches
|
|
1802
1803
|
}
|
|
1803
1804
|
|
|
1804
1805
|
function matchKeyedChildrenAfterSingleRemoval(
|
|
1805
|
-
curr:
|
|
1806
|
-
next:
|
|
1807
|
-
):
|
|
1806
|
+
curr: CommittedVNode[],
|
|
1807
|
+
next: VNodeInput[],
|
|
1808
|
+
): number[] | null {
|
|
1808
1809
|
if (curr.length !== next.length + 1) return null
|
|
1809
1810
|
|
|
1810
|
-
let matches:
|
|
1811
|
+
let matches: number[] = []
|
|
1811
1812
|
let oldIndex = 0
|
|
1812
1813
|
let skippedOldNode = false
|
|
1813
1814
|
|
|
@@ -1817,7 +1818,7 @@ function matchKeyedChildrenAfterSingleRemoval(
|
|
|
1817
1818
|
|
|
1818
1819
|
let oldNode = curr[oldIndex]
|
|
1819
1820
|
if (oldNode.key === nextNode.key && oldNode.type === nextNode.type) {
|
|
1820
|
-
matches.push(
|
|
1821
|
+
matches.push(oldIndex)
|
|
1821
1822
|
oldIndex++
|
|
1822
1823
|
continue
|
|
1823
1824
|
}
|
|
@@ -1831,7 +1832,7 @@ function matchKeyedChildrenAfterSingleRemoval(
|
|
|
1831
1832
|
return null
|
|
1832
1833
|
}
|
|
1833
1834
|
|
|
1834
|
-
matches.push(
|
|
1835
|
+
matches.push(oldIndex)
|
|
1835
1836
|
oldIndex++
|
|
1836
1837
|
}
|
|
1837
1838
|
|
|
@@ -1839,12 +1840,12 @@ function matchKeyedChildrenAfterSingleRemoval(
|
|
|
1839
1840
|
}
|
|
1840
1841
|
|
|
1841
1842
|
function matchKeyedChildrenAfterPairSwap(
|
|
1842
|
-
curr:
|
|
1843
|
-
next:
|
|
1844
|
-
):
|
|
1843
|
+
curr: CommittedVNode[],
|
|
1844
|
+
next: VNodeInput[],
|
|
1845
|
+
): number[] | null {
|
|
1845
1846
|
if (curr.length !== next.length) return null
|
|
1846
1847
|
|
|
1847
|
-
let matches:
|
|
1848
|
+
let matches: number[] = []
|
|
1848
1849
|
let firstMismatch = -1
|
|
1849
1850
|
let secondMismatch = -1
|
|
1850
1851
|
|
|
@@ -1854,7 +1855,7 @@ function matchKeyedChildrenAfterPairSwap(
|
|
|
1854
1855
|
|
|
1855
1856
|
let oldNode = curr[index]
|
|
1856
1857
|
if (oldNode.key === nextNode.key && oldNode.type === nextNode.type) {
|
|
1857
|
-
matches.push(
|
|
1858
|
+
matches.push(index)
|
|
1858
1859
|
continue
|
|
1859
1860
|
}
|
|
1860
1861
|
|
|
@@ -1865,7 +1866,7 @@ function matchKeyedChildrenAfterPairSwap(
|
|
|
1865
1866
|
} else {
|
|
1866
1867
|
return null
|
|
1867
1868
|
}
|
|
1868
|
-
matches.push(
|
|
1869
|
+
matches.push(-1)
|
|
1869
1870
|
}
|
|
1870
1871
|
|
|
1871
1872
|
if (firstMismatch === -1) return matches
|
|
@@ -1885,42 +1886,43 @@ function matchKeyedChildrenAfterPairSwap(
|
|
|
1885
1886
|
return null
|
|
1886
1887
|
}
|
|
1887
1888
|
|
|
1888
|
-
matches[firstMismatch] =
|
|
1889
|
-
matches[secondMismatch] =
|
|
1889
|
+
matches[firstMismatch] = secondMismatch
|
|
1890
|
+
matches[secondMismatch] = firstMismatch
|
|
1890
1891
|
return matches
|
|
1891
1892
|
}
|
|
1892
1893
|
|
|
1893
1894
|
function analyzeKeyedChildMatches(
|
|
1894
1895
|
currentLength: number,
|
|
1895
|
-
matches:
|
|
1896
|
+
matches: readonly number[],
|
|
1896
1897
|
): { hasRemovals: boolean; canSkipPlacement: boolean } {
|
|
1897
1898
|
let hasRemovals = matches.length !== currentLength
|
|
1898
1899
|
let canSkipPlacement = true
|
|
1899
1900
|
let lastOldIndex = -1
|
|
1900
1901
|
let sawNewNode = false
|
|
1901
1902
|
|
|
1902
|
-
for (let
|
|
1903
|
-
|
|
1903
|
+
for (let index = 0; index < matches.length; index++) {
|
|
1904
|
+
let oldIndex = matches[index]
|
|
1905
|
+
if (oldIndex < 0) {
|
|
1904
1906
|
hasRemovals = true
|
|
1905
1907
|
sawNewNode = true
|
|
1906
1908
|
continue
|
|
1907
1909
|
}
|
|
1908
1910
|
|
|
1909
|
-
if (sawNewNode ||
|
|
1911
|
+
if (sawNewNode || oldIndex < lastOldIndex) {
|
|
1910
1912
|
canSkipPlacement = false
|
|
1911
1913
|
}
|
|
1912
|
-
lastOldIndex =
|
|
1914
|
+
lastOldIndex = oldIndex
|
|
1913
1915
|
}
|
|
1914
1916
|
|
|
1915
1917
|
return { hasRemovals, canSkipPlacement }
|
|
1916
1918
|
}
|
|
1917
1919
|
|
|
1918
|
-
function lisMatches(matches:
|
|
1920
|
+
function lisMatches(matches: readonly number[]): number[] {
|
|
1919
1921
|
let predecessors = Array.from<number>({ length: matches.length })
|
|
1920
1922
|
let tails: number[] = []
|
|
1921
1923
|
|
|
1922
1924
|
for (let index = 0; index < matches.length; index++) {
|
|
1923
|
-
let value = matches[index]
|
|
1925
|
+
let value = matches[index] + 1
|
|
1924
1926
|
if (value === 0) continue
|
|
1925
1927
|
|
|
1926
1928
|
let low = 0
|
|
@@ -1929,7 +1931,7 @@ function lisMatches(matches: ReadonlyArray<{ oldIndex: number }>): number[] {
|
|
|
1929
1931
|
while (low < high) {
|
|
1930
1932
|
let middle = (low + high) >> 1
|
|
1931
1933
|
|
|
1932
|
-
if (matches[tails[middle]]
|
|
1934
|
+
if (matches[tails[middle]] + 1 < value) {
|
|
1933
1935
|
low = middle + 1
|
|
1934
1936
|
} else {
|
|
1935
1937
|
high = middle
|
|
@@ -1950,7 +1952,7 @@ function lisMatches(matches: ReadonlyArray<{ oldIndex: number }>): number[] {
|
|
|
1950
1952
|
return tails
|
|
1951
1953
|
}
|
|
1952
1954
|
|
|
1953
|
-
function placeVNode(node:
|
|
1955
|
+
function placeVNode(node: CommittedVNode, domParent: ParentNode, anchor: Node | null): void {
|
|
1954
1956
|
let firstDom = findFirstDomAnchor(node)
|
|
1955
1957
|
if (!firstDom || firstDom.parentNode !== domParent) return
|
|
1956
1958
|
|
|
@@ -1962,27 +1964,28 @@ function placeVNode(node: VNode, domParent: ParentNode, anchor: Node | null): vo
|
|
|
1962
1964
|
moveDomRange(domParent, firstDom, lastDom, anchor)
|
|
1963
1965
|
}
|
|
1964
1966
|
|
|
1965
|
-
export function findFirstDomAnchor(node:
|
|
1967
|
+
export function findFirstDomAnchor(node: CommittedVNode | null | undefined): Node | null {
|
|
1966
1968
|
if (!node) return null
|
|
1967
1969
|
if (isCommittedTextNode(node)) return node._dom
|
|
1968
1970
|
if (isCommittedHostNode(node)) return node._dom
|
|
1969
1971
|
if (isCommittedComponentNode(node)) return findFirstDomAnchor(node._content)
|
|
1970
|
-
if (node.
|
|
1972
|
+
if (node.kind === 'frame') return node._rangeStart
|
|
1971
1973
|
if (isFragmentNode(node)) {
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
+
let children = node._children
|
|
1975
|
+
for (let i = 0; i < children.length; i++) {
|
|
1976
|
+
let dom = findFirstDomAnchor(children[i])
|
|
1974
1977
|
if (dom) return dom
|
|
1975
1978
|
}
|
|
1976
1979
|
}
|
|
1977
1980
|
return null
|
|
1978
1981
|
}
|
|
1979
1982
|
|
|
1980
|
-
export function findLastDomAnchor(node:
|
|
1983
|
+
export function findLastDomAnchor(node: CommittedVNode | null | undefined): Node | null {
|
|
1981
1984
|
if (!node) return null
|
|
1982
1985
|
if (isCommittedTextNode(node)) return node._dom
|
|
1983
1986
|
if (isCommittedHostNode(node)) return node._dom
|
|
1984
1987
|
if (isCommittedComponentNode(node)) return findLastDomAnchor(node._content)
|
|
1985
|
-
if (node.
|
|
1988
|
+
if (node.kind === 'frame') return node._rangeEnd
|
|
1986
1989
|
if (isFragmentNode(node)) {
|
|
1987
1990
|
for (let i = node._children.length - 1; i >= 0; i--) {
|
|
1988
1991
|
let dom = findLastDomAnchor(node._children[i])
|
|
@@ -2020,20 +2023,27 @@ function shouldDispatchInlineMixinLifecycle(node: Node): boolean {
|
|
|
2020
2023
|
let parents = activeSchedulerUpdateParents
|
|
2021
2024
|
if (!parents?.length) return true
|
|
2022
2025
|
for (let parent of parents) {
|
|
2023
|
-
|
|
2026
|
+
if (!(parent instanceof Node)) continue
|
|
2027
|
+
let parentNode = parent
|
|
2024
2028
|
if (parentNode === node) return false
|
|
2025
2029
|
if (parentNode.contains(node)) return false
|
|
2026
2030
|
}
|
|
2027
2031
|
return true
|
|
2028
2032
|
}
|
|
2029
2033
|
|
|
2030
|
-
export function findNextSiblingDomAnchor(
|
|
2034
|
+
export function findNextSiblingDomAnchor(
|
|
2035
|
+
curr: CommittedVNode | MountingComponentNode,
|
|
2036
|
+
): Node | null {
|
|
2031
2037
|
let vParent = curr._parent
|
|
2032
|
-
if (
|
|
2038
|
+
if (vParent.kind === 'component') return findNextSiblingDomAnchor(vParent)
|
|
2033
2039
|
let children = vParent._children
|
|
2034
|
-
if (children.length === 0)
|
|
2040
|
+
if (children.length === 0) {
|
|
2041
|
+
if (vParent.kind === 'root') return vParent._rangeEnd ?? null
|
|
2042
|
+
if (vParent.kind === 'fragment') return findNextSiblingDomAnchor(vParent)
|
|
2043
|
+
return null
|
|
2044
|
+
}
|
|
2035
2045
|
|
|
2036
|
-
let idx = children.indexOf(curr)
|
|
2046
|
+
let idx = children.indexOf(curr as CommittedVNode)
|
|
2037
2047
|
if (idx === -1) return null
|
|
2038
2048
|
for (let i = idx + 1; i < children.length; i++) {
|
|
2039
2049
|
let dom = findFirstDomAnchor(children[i])
|
|
@@ -2044,60 +2054,53 @@ export function findNextSiblingDomAnchor(curr: VNode): Node | null {
|
|
|
2044
2054
|
return findNextSiblingDomAnchor(vParent)
|
|
2045
2055
|
}
|
|
2046
2056
|
|
|
2047
|
-
return null
|
|
2057
|
+
return vParent.kind === 'root' ? (vParent._rangeEnd ?? null) : null
|
|
2048
2058
|
}
|
|
2049
2059
|
|
|
2050
2060
|
function reclaimPersistedMixinNode(
|
|
2051
2061
|
persistedNode: CommittedHostNode,
|
|
2052
2062
|
newNode: HostNode,
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
): void {
|
|
2059
|
-
cancelPendingMixinRemoval(persistedNode._mixState as MixinRuntimeState | undefined)
|
|
2063
|
+
vParent: VNodeParent,
|
|
2064
|
+
context: ReconcileContext,
|
|
2065
|
+
): CommittedHostNode {
|
|
2066
|
+
let { frame, scheduler, styles } = context
|
|
2067
|
+
cancelPendingMixinRemoval(persistedNode._mixState)
|
|
2060
2068
|
unmarkNodePersistedByMixins(persistedNode)
|
|
2061
2069
|
|
|
2062
|
-
newNode._dom = persistedNode._dom
|
|
2063
|
-
newNode._parent = vParent
|
|
2064
|
-
newNode._controller = persistedNode._controller
|
|
2065
|
-
newNode._mixState = persistedNode._mixState
|
|
2066
|
-
newNode._directEventState = persistedNode._directEventState
|
|
2067
|
-
newNode._controlledState = persistedNode._controlledState
|
|
2068
|
-
|
|
2069
2070
|
let prevProps = getHostProps(persistedNode)
|
|
2070
|
-
let
|
|
2071
|
+
let childInputs = newNode._children
|
|
2072
|
+
let resolved = resolveNodeMixProps(newNode, vParent, frame, scheduler, persistedNode._mixState)
|
|
2073
|
+
let nextProps = resolved.props
|
|
2074
|
+
let committed = commitHostNode(
|
|
2071
2075
|
newNode,
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2076
|
+
vParent,
|
|
2077
|
+
getSvgContext(vParent, newNode),
|
|
2078
|
+
persistedNode._dom,
|
|
2079
|
+
resolved,
|
|
2075
2080
|
)
|
|
2081
|
+
committed._directEventState = persistedNode._directEventState
|
|
2082
|
+
committed._controlledState = persistedNode._controlledState
|
|
2076
2083
|
if (shouldDispatchInlineMixinLifecycle(persistedNode._dom)) {
|
|
2077
|
-
dispatchMixinBeforeUpdate(
|
|
2084
|
+
dispatchMixinBeforeUpdate(committed._mixState)
|
|
2078
2085
|
}
|
|
2079
2086
|
patchHostProps(prevProps, nextProps, persistedNode._dom)
|
|
2080
|
-
syncDirectEventListeners(
|
|
2081
|
-
ensureControlledReflection(
|
|
2082
|
-
syncControlledReflection(
|
|
2087
|
+
syncDirectEventListeners(committed)
|
|
2088
|
+
ensureControlledReflection(committed, scheduler)
|
|
2089
|
+
syncControlledReflection(committed, nextProps)
|
|
2083
2090
|
|
|
2084
|
-
diffChildren(
|
|
2091
|
+
committed._children = diffChildren(
|
|
2085
2092
|
persistedNode._children,
|
|
2086
|
-
|
|
2093
|
+
childInputs,
|
|
2087
2094
|
persistedNode._dom,
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
styles,
|
|
2091
|
-
newNode,
|
|
2092
|
-
rootTarget,
|
|
2095
|
+
committed,
|
|
2096
|
+
context,
|
|
2093
2097
|
)
|
|
2094
2098
|
|
|
2095
|
-
if (
|
|
2096
|
-
bindNodeMixRuntime(
|
|
2099
|
+
if (committed._mixState) {
|
|
2100
|
+
bindNodeMixRuntime(committed, frame, scheduler, styles, true)
|
|
2097
2101
|
}
|
|
2098
2102
|
if (shouldDispatchInlineMixinLifecycle(persistedNode._dom)) {
|
|
2099
|
-
scheduler.enqueueCommitPhase([
|
|
2100
|
-
() => dispatchMixinCommit(newNode._mixState as MixinRuntimeState | undefined),
|
|
2101
|
-
])
|
|
2103
|
+
scheduler.enqueueCommitPhase([() => dispatchMixinCommit(committed._mixState)])
|
|
2102
2104
|
}
|
|
2105
|
+
return committed
|
|
2103
2106
|
}
|