@vidact/runtime 0.2.0-beta.0 → 0.2.0-beta.5
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/CHANGELOG.md +52 -0
- package/README.md +1 -1
- package/dist/actions.d.ts +32 -30
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +251 -266
- package/dist/actions.js.map +1 -1
- package/dist/async.d.ts +20 -17
- package/dist/async.d.ts.map +1 -1
- package/dist/async.js +18 -17
- package/dist/async.js.map +1 -1
- package/dist/compiled/async-resource.d.ts +9 -6
- package/dist/compiled/async-resource.d.ts.map +1 -1
- package/dist/compiled/async-resource.js +69 -77
- package/dist/compiled/async-resource.js.map +1 -1
- package/dist/compiled/core.d.ts +136 -147
- package/dist/compiled/core.d.ts.map +1 -1
- package/dist/compiled/core.js +2672 -2985
- package/dist/compiled/core.js.map +1 -1
- package/dist/compiled/types.d.ts +44 -56
- package/dist/compiled/types.d.ts.map +1 -1
- package/dist/compiled/types.js +10 -6
- package/dist/compiled/types.js.map +1 -1
- package/dist/compiled/validation.d.ts +4 -1
- package/dist/compiled/validation.d.ts.map +1 -1
- package/dist/compiled/validation.js +17 -20
- package/dist/compiled/validation.js.map +1 -1
- package/dist/component-spread.d.ts +5 -3
- package/dist/component-spread.d.ts.map +1 -1
- package/dist/component-spread.js +49 -67
- package/dist/component-spread.js.map +1 -1
- package/dist/concurrent.d.ts +20 -18
- package/dist/concurrent.d.ts.map +1 -1
- package/dist/concurrent.js +191 -210
- package/dist/concurrent.js.map +1 -1
- package/dist/direct-dom.d.ts +17 -12
- package/dist/direct-dom.d.ts.map +1 -1
- package/dist/direct-dom.js +187 -221
- package/dist/direct-dom.js.map +1 -1
- package/dist/dom/attributes.d.ts +9 -6
- package/dist/dom/attributes.d.ts.map +1 -1
- package/dist/dom/attributes.js +65 -47
- package/dist/dom/attributes.js.map +1 -1
- package/dist/dom/events.d.ts +5 -2
- package/dist/dom/events.d.ts.map +1 -1
- package/dist/dom/events.js +53 -65
- package/dist/dom/events.js.map +1 -1
- package/dist/dom/forms.d.ts +9 -6
- package/dist/dom/forms.d.ts.map +1 -1
- package/dist/dom/forms.js +152 -198
- package/dist/dom/forms.js.map +1 -1
- package/dist/dom/intrinsic.d.ts +17 -15
- package/dist/dom/intrinsic.d.ts.map +1 -1
- package/dist/dom/intrinsic.js +48 -50
- package/dist/dom/intrinsic.js.map +1 -1
- package/dist/dom/namespace.d.ts +4 -1
- package/dist/dom/namespace.d.ts.map +1 -1
- package/dist/dom/namespace.js +25 -28
- package/dist/dom/namespace.js.map +1 -1
- package/dist/dom/namespaces.d.ts +11 -8
- package/dist/dom/namespaces.d.ts.map +1 -1
- package/dist/dom/namespaces.js +14 -12
- package/dist/dom/namespaces.js.map +1 -1
- package/dist/dom/properties.d.ts +18 -16
- package/dist/dom/properties.d.ts.map +1 -1
- package/dist/dom/properties.js +273 -296
- package/dist/dom/properties.js.map +1 -1
- package/dist/dom/style-properties.d.ts +4 -1
- package/dist/dom/style-properties.d.ts.map +1 -1
- package/dist/dom/style-properties.js +49 -45
- package/dist/dom/style-properties.js.map +1 -1
- package/dist/dom/styles.d.ts +5 -2
- package/dist/dom/styles.d.ts.map +1 -1
- package/dist/dom/styles.js +47 -67
- package/dist/dom/styles.js.map +1 -1
- package/dist/dom-forms.d.ts +4 -1
- package/dist/dom-forms.d.ts.map +1 -1
- package/dist/dom-forms.js +16 -13
- package/dist/dom-forms.js.map +1 -1
- package/dist/dom-styles.d.ts +4 -1
- package/dist/dom-styles.d.ts.map +1 -1
- package/dist/dom-styles.js +10 -7
- package/dist/dom-styles.js.map +1 -1
- package/dist/framework-hydrate.d.ts +20 -17
- package/dist/framework-hydrate.d.ts.map +1 -1
- package/dist/framework-hydrate.js +128 -146
- package/dist/framework-hydrate.js.map +1 -1
- package/dist/framework-protocol.d.ts +27 -25
- package/dist/framework-protocol.d.ts.map +1 -1
- package/dist/framework-protocol.js +173 -211
- package/dist/framework-protocol.js.map +1 -1
- package/dist/framework.d.ts +34 -32
- package/dist/framework.d.ts.map +1 -1
- package/dist/framework.js +74 -93
- package/dist/framework.js.map +1 -1
- package/dist/hydrate.d.ts +11 -8
- package/dist/hydrate.d.ts.map +1 -1
- package/dist/hydrate.js +13 -9
- package/dist/hydrate.js.map +1 -1
- package/dist/hydration-bridge.d.ts +70 -58
- package/dist/hydration-bridge.d.ts.map +1 -1
- package/dist/hydration-bridge.js +77 -66
- package/dist/hydration-bridge.js.map +1 -1
- package/dist/hydration.d.ts +54 -28
- package/dist/hydration.d.ts.map +1 -1
- package/dist/hydration.js +450 -463
- package/dist/hydration.js.map +1 -1
- package/dist/index.d.ts +13 -13
- package/dist/index.js +11 -13
- package/dist/indexed-list.d.ts +8 -5
- package/dist/indexed-list.d.ts.map +1 -1
- package/dist/indexed-list.js +14 -7
- package/dist/indexed-list.js.map +1 -1
- package/dist/jsx-runtime.d.ts +10 -8
- package/dist/jsx-runtime.d.ts.map +1 -1
- package/dist/jsx-runtime.js +28 -23
- package/dist/jsx-runtime.js.map +1 -1
- package/dist/keyed-list.d.ts +13 -14
- package/dist/keyed-list.d.ts.map +1 -1
- package/dist/keyed-list.js +132 -160
- package/dist/keyed-list.js.map +1 -1
- package/dist/metadata.d.ts +6 -3
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +131 -164
- package/dist/metadata.js.map +1 -1
- package/dist/nested-prop.d.ts +4 -2
- package/dist/nested-prop.d.ts.map +1 -1
- package/dist/nested-prop.js +17 -16
- package/dist/nested-prop.js.map +1 -1
- package/dist/profiling.d.ts +11 -8
- package/dist/profiling.d.ts.map +1 -1
- package/dist/profiling.js +17 -17
- package/dist/profiling.js.map +1 -1
- package/dist/protocol.d.ts +5 -2
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +8 -5
- package/dist/protocol.js.map +1 -1
- package/dist/raw-html.d.ts +5 -2
- package/dist/raw-html.d.ts.map +1 -1
- package/dist/raw-html.js +86 -146
- package/dist/raw-html.js.map +1 -1
- package/dist/ref.d.ts +8 -3
- package/dist/ref.d.ts.map +1 -1
- package/dist/ref.js +10 -2
- package/dist/ref.js.map +1 -1
- package/dist/renderable-protocol.d.ts +26 -8
- package/dist/renderable-protocol.d.ts.map +1 -1
- package/dist/renderable-protocol.js +30 -9
- package/dist/renderable-protocol.js.map +1 -1
- package/dist/renderable.d.ts +27 -22
- package/dist/renderable.d.ts.map +1 -1
- package/dist/renderable.js +141 -133
- package/dist/renderable.js.map +1 -1
- package/dist/retained-ui.d.ts +9 -6
- package/dist/retained-ui.d.ts.map +1 -1
- package/dist/retained-ui.js +12 -10
- package/dist/retained-ui.js.map +1 -1
- package/dist/root.d.ts +18 -15
- package/dist/root.d.ts.map +1 -1
- package/dist/root.js +73 -85
- package/dist/root.js.map +1 -1
- package/dist/scheduler.d.ts +10 -8
- package/dist/scheduler.d.ts.map +1 -1
- package/dist/scheduler.js +97 -105
- package/dist/scheduler.js.map +1 -1
- package/dist/server-framework.d.ts +45 -42
- package/dist/server-framework.d.ts.map +1 -1
- package/dist/server-framework.js +209 -217
- package/dist/server-framework.js.map +1 -1
- package/dist/server.d.ts +130 -128
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +603 -710
- package/dist/server.js.map +1 -1
- package/dist/shared/promise.d.ts +6 -3
- package/dist/shared/promise.d.ts.map +1 -1
- package/dist/shared/promise.js +21 -19
- package/dist/shared/promise.js.map +1 -1
- package/dist/source-mask.d.ts +9 -6
- package/dist/source-mask.d.ts.map +1 -1
- package/dist/source-mask.js +45 -63
- package/dist/source-mask.js.map +1 -1
- package/dist/spread.d.ts +5 -2
- package/dist/spread.d.ts.map +1 -1
- package/dist/spread.js +79 -99
- package/dist/spread.js.map +1 -1
- package/dist/state-slot.d.ts +18 -15
- package/dist/state-slot.d.ts.map +1 -1
- package/dist/state-slot.js +43 -42
- package/dist/state-slot.js.map +1 -1
- package/dist/testing.d.ts +9 -6
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +16 -12
- package/dist/testing.js.map +1 -1
- package/package.json +8 -7
- package/src/actions.ts +415 -0
- package/src/async.ts +78 -0
- package/src/compiled/async-resource.ts +113 -0
- package/src/compiled/core.ts +4041 -0
- package/src/compiled/types.ts +96 -0
- package/src/compiled/validation.ts +23 -0
- package/src/component-spread.ts +86 -0
- package/src/concurrent.ts +310 -0
- package/src/direct-dom.ts +365 -0
- package/src/dom/attributes.ts +55 -0
- package/src/dom/events.ts +93 -0
- package/src/dom/forms.ts +220 -0
- package/src/dom/intrinsic.ts +94 -0
- package/src/dom/namespace.ts +30 -0
- package/src/dom/namespaces.ts +14 -0
- package/src/dom/properties.ts +355 -0
- package/src/dom/style-properties.ts +46 -0
- package/src/dom/styles.ts +86 -0
- package/src/dom-forms.ts +23 -0
- package/src/dom-styles.ts +11 -0
- package/src/env.d.ts +6 -0
- package/src/framework-hydrate.ts +224 -0
- package/src/framework-protocol.ts +335 -0
- package/src/framework.ts +161 -0
- package/src/hydrate.ts +38 -0
- package/src/hydration-bridge.ts +196 -0
- package/src/hydration.ts +642 -0
- package/src/index.ts +92 -0
- package/src/indexed-list.ts +20 -0
- package/src/jsx-runtime.ts +55 -0
- package/src/keyed-list.ts +233 -0
- package/src/metadata.ts +212 -0
- package/src/nested-prop.ts +27 -0
- package/src/profiling.ts +43 -0
- package/src/protocol.ts +8 -0
- package/src/raw-html.ts +206 -0
- package/src/ref.ts +13 -0
- package/src/renderable-protocol.ts +63 -0
- package/src/renderable.ts +283 -0
- package/src/retained-ui.ts +19 -0
- package/src/root.ts +122 -0
- package/src/scheduler.ts +136 -0
- package/src/server-framework.ts +397 -0
- package/src/server.ts +1160 -0
- package/src/shared/promise.ts +28 -0
- package/src/source-mask.ts +83 -0
- package/src/spread.ts +131 -0
- package/src/state-slot.ts +76 -0
- package/src/testing.ts +27 -0
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,4041 @@
|
|
|
1
|
+
import {
|
|
2
|
+
currentIntrinsicNamespace,
|
|
3
|
+
withIntrinsicNamespace,
|
|
4
|
+
type IntrinsicNamespace,
|
|
5
|
+
} from '../dom/intrinsic.ts'
|
|
6
|
+
import {
|
|
7
|
+
HydrationMismatch,
|
|
8
|
+
beginHydration,
|
|
9
|
+
borrowHydrationSlotRange,
|
|
10
|
+
claimHydrationArrayRange,
|
|
11
|
+
claimHydrationComponentMount,
|
|
12
|
+
claimHydrationComponentRange,
|
|
13
|
+
claimHydrationNode,
|
|
14
|
+
claimHydrationSlotRange,
|
|
15
|
+
claimHydrationSuspenseFallback,
|
|
16
|
+
claimHydrationText,
|
|
17
|
+
claimHydrationTextRange,
|
|
18
|
+
hydrationRangeParent,
|
|
19
|
+
hydrationRootMarkers,
|
|
20
|
+
finishHydration,
|
|
21
|
+
finishHydrationArrayRange,
|
|
22
|
+
hydrationCursor,
|
|
23
|
+
hydrationFragmentChildren,
|
|
24
|
+
hydrationInsertionPoint,
|
|
25
|
+
isHydrating,
|
|
26
|
+
isHydrationMismatch,
|
|
27
|
+
noteHydrationStructuralParent,
|
|
28
|
+
skipHydrationRange,
|
|
29
|
+
withoutHydration,
|
|
30
|
+
withHydrationComponentRange,
|
|
31
|
+
withHydrationInsertion,
|
|
32
|
+
withHydrationCursor,
|
|
33
|
+
} from '../hydration-bridge.ts'
|
|
34
|
+
import { createIndexedList } from '../indexed-list.ts'
|
|
35
|
+
import { createKeyedList } from '../keyed-list.ts'
|
|
36
|
+
import {
|
|
37
|
+
canReconcileRenderable,
|
|
38
|
+
isRenderableProtocol,
|
|
39
|
+
materializeRenderable,
|
|
40
|
+
materializeRenderableWithInput,
|
|
41
|
+
RENDERABLE,
|
|
42
|
+
renderableIdentity,
|
|
43
|
+
renderablePropsSnapshot,
|
|
44
|
+
type RenderableProtocol,
|
|
45
|
+
} from '../renderable-protocol.ts'
|
|
46
|
+
import { scheduleDeferredTask, scheduleTask, type CancelScheduledTask } from '../scheduler.ts'
|
|
47
|
+
import { intersectsSources, isEmptySources, unionSources, type SourceMask } from '../source-mask.ts'
|
|
48
|
+
import { createStateSlot, type StateSlot } from '../state-slot.ts'
|
|
49
|
+
import { isSuspension, subscribeResource, useAsync } from './async-resource.ts'
|
|
50
|
+
import {
|
|
51
|
+
BINDING,
|
|
52
|
+
COMPONENT_SPREAD_SOURCE as componentSpreadSourceSymbol,
|
|
53
|
+
CONTEXT,
|
|
54
|
+
STRUCTURAL,
|
|
55
|
+
type AsyncResource,
|
|
56
|
+
type CompiledBinding,
|
|
57
|
+
type CompiledComponentResult,
|
|
58
|
+
type CompiledContext,
|
|
59
|
+
type CompiledErrorHandler,
|
|
60
|
+
type CompiledRenderValue,
|
|
61
|
+
type CompiledScope,
|
|
62
|
+
type ProfilerOnRender,
|
|
63
|
+
type StructuralBinding,
|
|
64
|
+
} from './types.ts'
|
|
65
|
+
import { hasInvalidChild } from './validation.ts'
|
|
66
|
+
|
|
67
|
+
export const COMPONENT_SPREAD_SOURCE = componentSpreadSourceSymbol
|
|
68
|
+
|
|
69
|
+
const MAX_FLUSH_PASSES = 100
|
|
70
|
+
export const DEV = typeof __VIDACT_DEV__ === 'undefined' || __VIDACT_DEV__
|
|
71
|
+
let retainedUiEnabled = typeof __VIDACT_RETAINED_UI__ !== 'undefined' && __VIDACT_RETAINED_UI__
|
|
72
|
+
let profilingEnabled = false
|
|
73
|
+
let activeOwnerCount = 0
|
|
74
|
+
let createdOwnerCount = 0
|
|
75
|
+
const noop = (): void => {}
|
|
76
|
+
|
|
77
|
+
type ContextFrame = {
|
|
78
|
+
readonly context: CompiledContext<unknown>
|
|
79
|
+
readonly input: unknown
|
|
80
|
+
readonly parent: ContextFrame | null
|
|
81
|
+
readonly owner: Owner | null
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
type ErrorBoundaryHandler = {
|
|
85
|
+
readonly handle: (error: unknown) => void
|
|
86
|
+
readonly parent: ErrorBoundaryHandler | null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
type RootIdentity = {
|
|
90
|
+
mounted: boolean
|
|
91
|
+
nextId: number
|
|
92
|
+
readonly prefix: string
|
|
93
|
+
readonly onCaughtError: CompiledErrorHandler | undefined
|
|
94
|
+
readonly onUncaughtError: CompiledErrorHandler | undefined
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
type PortalPublication = readonly [commit: () => void, rollback: () => void]
|
|
98
|
+
|
|
99
|
+
type Owner = [
|
|
100
|
+
disposed: boolean,
|
|
101
|
+
cleanups: Set<() => void>,
|
|
102
|
+
context: ContextFrame | null,
|
|
103
|
+
rootIdentity: RootIdentity,
|
|
104
|
+
errorBoundary: ErrorBoundaryHandler | null,
|
|
105
|
+
retainedConnection?: RetainedConnection | null,
|
|
106
|
+
profileContext?: ProfileContext | null,
|
|
107
|
+
debugName?: string | null,
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
type DebugOwnerFrame = {
|
|
111
|
+
readonly name: string
|
|
112
|
+
readonly parent: DebugOwnerFrame | null
|
|
113
|
+
readonly values: unknown[]
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
type ProfileContext = {
|
|
117
|
+
readonly frame: DebugOwnerFrame | null
|
|
118
|
+
readonly boundary: ProfileBoundary | null
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
type ProfileBoundary = {
|
|
122
|
+
readonly parent: ProfileBoundary | null
|
|
123
|
+
id: string
|
|
124
|
+
onRender: ProfilerOnRender
|
|
125
|
+
baseDuration: number
|
|
126
|
+
mounted: boolean
|
|
127
|
+
pendingDuration: number
|
|
128
|
+
pendingStart: number
|
|
129
|
+
scheduled: boolean
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
type RetainedResource = {
|
|
133
|
+
readonly owner: Owner
|
|
134
|
+
readonly connect: () => void
|
|
135
|
+
readonly disconnect: () => void
|
|
136
|
+
active: boolean
|
|
137
|
+
connected: boolean
|
|
138
|
+
disposed: boolean
|
|
139
|
+
readonly phase: number
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
type RetainedConnection = {
|
|
143
|
+
readonly parent: RetainedConnection | null
|
|
144
|
+
readonly children: Set<RetainedConnection>
|
|
145
|
+
readonly resources: Set<RetainedResource>
|
|
146
|
+
readonly deferredFlushes: Map<() => void, CancelScheduledTask>
|
|
147
|
+
readonly afterFlush: Set<() => void>
|
|
148
|
+
visible: boolean
|
|
149
|
+
connected: boolean
|
|
150
|
+
disposed: boolean
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
type CompiledUpdater = [
|
|
154
|
+
reads: SourceMask,
|
|
155
|
+
writes: SourceMask | undefined,
|
|
156
|
+
run: (active: SourceMask) => void,
|
|
157
|
+
active: boolean,
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
type SourceOperations = readonly [
|
|
161
|
+
empty: (mask: SourceMask) => boolean,
|
|
162
|
+
intersects: (left: SourceMask, right: SourceMask) => boolean,
|
|
163
|
+
union: (left: SourceMask, right: SourceMask) => SourceMask,
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
const wideSourceOperations: SourceOperations = [isEmptySources, intersectsSources, unionSources]
|
|
167
|
+
const narrowSourceOperations: SourceOperations = [
|
|
168
|
+
(mask) => mask === 0,
|
|
169
|
+
(left, right) => ((left as number) & (right as number)) !== 0,
|
|
170
|
+
(left, right) => ((left as number) | (right as number)) >>> 0,
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
type RenderValue = CompiledRenderValue
|
|
174
|
+
|
|
175
|
+
export function deferred(render: () => CompiledRenderValue): StructuralBinding {
|
|
176
|
+
return [
|
|
177
|
+
STRUCTURAL,
|
|
178
|
+
(parent, before) => {
|
|
179
|
+
const hydrated = claimHydrationSlotRange(parent)
|
|
180
|
+
if (hydrated === undefined) insertValue(parent, render(), before)
|
|
181
|
+
else insertValue(parent, render(), hydrated[1])
|
|
182
|
+
},
|
|
183
|
+
'slot',
|
|
184
|
+
]
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** @internal */
|
|
188
|
+
export function retainedActivity(
|
|
189
|
+
modeInput: 'visible' | 'hidden' | CompiledBinding<'visible' | 'hidden'>,
|
|
190
|
+
render: () => CompiledRenderValue,
|
|
191
|
+
): StructuralBinding {
|
|
192
|
+
const context = activeContextFrame ?? activeOwner?.[2] ?? null
|
|
193
|
+
const rootIdentity = activeOwner?.[3] ?? activeRootIdentity
|
|
194
|
+
if (rootIdentity === null) {
|
|
195
|
+
throw new Error(DEV ? 'Activity must run during compiled root construction' : 'V033')
|
|
196
|
+
}
|
|
197
|
+
const parentErrorBoundary = activeOwner?.[4] ?? null
|
|
198
|
+
const parentConnection = activeOwner?.[5] ?? null
|
|
199
|
+
let mounted = false
|
|
200
|
+
return [
|
|
201
|
+
STRUCTURAL,
|
|
202
|
+
(parent, before) => {
|
|
203
|
+
if (mounted) throw new Error(DEV ? 'compiled Activity is already mounted' : 'V008')
|
|
204
|
+
mounted = true
|
|
205
|
+
const lifetimeOwner = activeOwner
|
|
206
|
+
if (lifetimeOwner === null) {
|
|
207
|
+
throw new Error(DEV ? 'Activity must mount inside an owned compiled root' : 'V033')
|
|
208
|
+
}
|
|
209
|
+
const readMode = (): 'visible' | 'hidden' =>
|
|
210
|
+
validateActivityMode(isCompiledBinding(modeInput) ? modeInput[1]() : modeInput)
|
|
211
|
+
let currentMode = readMode()
|
|
212
|
+
const connection = createRetainedConnection(parentConnection, currentMode === 'visible')
|
|
213
|
+
const childOwner = createOwner(context, rootIdentity, parentErrorBoundary, connection)
|
|
214
|
+
const hydratedRange = claimHydrationSlotRange(parent)
|
|
215
|
+
const start = hydratedRange?.[0] ?? document.createComment(DEV ? 'vidact:activity' : '')
|
|
216
|
+
const end = hydratedRange?.[1] ?? document.createComment(DEV ? '/vidact:activity' : '')
|
|
217
|
+
if (hydratedRange === undefined) {
|
|
218
|
+
parent.insertBefore(start, before)
|
|
219
|
+
parent.insertBefore(end, before)
|
|
220
|
+
}
|
|
221
|
+
const currentParent = rangeParent(start, end, 'Activity boundary')
|
|
222
|
+
const hiddenDisplays = new Map<Element, readonly [string, string]>()
|
|
223
|
+
const hiddenTexts = new Map<Text, Comment>()
|
|
224
|
+
const heldText = document.createDocumentFragment()
|
|
225
|
+
let restoreHydratedDisplays = hydratedRange !== undefined && currentMode === 'hidden'
|
|
226
|
+
|
|
227
|
+
const conceal = (): void => {
|
|
228
|
+
for (const node of nodesBetween(start, end)) {
|
|
229
|
+
if (node instanceof Element && 'style' in node) {
|
|
230
|
+
const styled = node as HTMLElement | SVGElement
|
|
231
|
+
const previous = hiddenDisplays.get(node)
|
|
232
|
+
const hydratedDisplay = restoreHydratedDisplays
|
|
233
|
+
? readServerHiddenDisplay(node)
|
|
234
|
+
: undefined
|
|
235
|
+
if (previous === undefined && hydratedDisplay !== undefined) {
|
|
236
|
+
hiddenDisplays.set(node, hydratedDisplay)
|
|
237
|
+
} else if (
|
|
238
|
+
previous === undefined ||
|
|
239
|
+
styled.style.display !== 'none' ||
|
|
240
|
+
styled.style.getPropertyPriority('display') !== 'important'
|
|
241
|
+
) {
|
|
242
|
+
hiddenDisplays.set(node, [
|
|
243
|
+
styled.style.display,
|
|
244
|
+
styled.style.getPropertyPriority('display'),
|
|
245
|
+
])
|
|
246
|
+
}
|
|
247
|
+
styled.style.setProperty('display', 'none', 'important')
|
|
248
|
+
} else if (node instanceof Text && !hiddenTexts.has(node)) {
|
|
249
|
+
const placeholder = document.createComment(DEV ? 'vidact:activity-text' : '')
|
|
250
|
+
node.replaceWith(placeholder)
|
|
251
|
+
heldText.append(node)
|
|
252
|
+
hiddenTexts.set(node, placeholder)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
restoreHydratedDisplays = false
|
|
256
|
+
}
|
|
257
|
+
const reveal = (): void => {
|
|
258
|
+
for (const [element, [display, priority]] of hiddenDisplays) {
|
|
259
|
+
const styled = element as HTMLElement | SVGElement
|
|
260
|
+
if (display === '') styled.style.removeProperty('display')
|
|
261
|
+
else styled.style.setProperty('display', display, priority)
|
|
262
|
+
}
|
|
263
|
+
hiddenDisplays.clear()
|
|
264
|
+
for (const [text, placeholder] of hiddenTexts) {
|
|
265
|
+
if (placeholder.parentNode !== null) placeholder.replaceWith(text)
|
|
266
|
+
}
|
|
267
|
+
hiddenTexts.clear()
|
|
268
|
+
}
|
|
269
|
+
const setMode = (nextMode: 'visible' | 'hidden'): void => {
|
|
270
|
+
if (nextMode === currentMode) return
|
|
271
|
+
currentMode = nextMode
|
|
272
|
+
if (nextMode === 'hidden') {
|
|
273
|
+
connection.visible = false
|
|
274
|
+
updateRetainedConnection(connection)
|
|
275
|
+
conceal()
|
|
276
|
+
} else {
|
|
277
|
+
reveal()
|
|
278
|
+
connection.visible = true
|
|
279
|
+
updateRetainedConnection(connection)
|
|
280
|
+
concealDisconnectedDescendants(connection)
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
connection.afterFlush.add(conceal)
|
|
285
|
+
let stagedNodes: readonly Node[] = []
|
|
286
|
+
try {
|
|
287
|
+
const [fragment, nodes] =
|
|
288
|
+
hydratedRange === undefined
|
|
289
|
+
? stageRender(render, childOwner)
|
|
290
|
+
: withHydrationInsertion(currentParent, end, () => stageRender(render, childOwner))
|
|
291
|
+
stagedNodes = nodes
|
|
292
|
+
currentParent.insertBefore(fragment, end)
|
|
293
|
+
if (currentMode === 'hidden') conceal()
|
|
294
|
+
commitPublishedNodes(nodes)
|
|
295
|
+
} catch (error) {
|
|
296
|
+
disposePublished(childOwner, stagedNodes)
|
|
297
|
+
disposeRetainedConnection(connection)
|
|
298
|
+
throw error
|
|
299
|
+
}
|
|
300
|
+
const removeModeUpdater = isCompiledBinding(modeInput)
|
|
301
|
+
? subscribeBinding(modeInput, () => setMode(readMode()))
|
|
302
|
+
: noop
|
|
303
|
+
onCleanup(() => {
|
|
304
|
+
removeModeUpdater()
|
|
305
|
+
reveal()
|
|
306
|
+
try {
|
|
307
|
+
disposeRange(childOwner, start, end)
|
|
308
|
+
} finally {
|
|
309
|
+
disposeRetainedConnection(connection)
|
|
310
|
+
start.remove()
|
|
311
|
+
end.remove()
|
|
312
|
+
}
|
|
313
|
+
})
|
|
314
|
+
},
|
|
315
|
+
'slot',
|
|
316
|
+
]
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** @internal */
|
|
320
|
+
export function enableRetainedUi(): void {
|
|
321
|
+
retainedUiEnabled = true
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** @internal */
|
|
325
|
+
export function enableProfiling(): void {
|
|
326
|
+
profilingEnabled = DEV
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** @internal */
|
|
330
|
+
export function profiled(
|
|
331
|
+
idInput: string | CompiledBinding<string>,
|
|
332
|
+
onRenderInput: ProfilerOnRender | CompiledBinding<ProfilerOnRender>,
|
|
333
|
+
render: () => CompiledRenderValue,
|
|
334
|
+
): StructuralBinding {
|
|
335
|
+
if (!DEV) return deferred(render)
|
|
336
|
+
const context = activeContextFrame ?? activeOwner?.[2] ?? null
|
|
337
|
+
const rootIdentity = activeOwner?.[3] ?? activeRootIdentity
|
|
338
|
+
if (rootIdentity === null) {
|
|
339
|
+
throw new Error(DEV ? 'Profiler must run during compiled root construction' : 'V035')
|
|
340
|
+
}
|
|
341
|
+
const parentErrorBoundary = activeOwner?.[4] ?? null
|
|
342
|
+
const parentConnection = activeOwner?.[5] ?? null
|
|
343
|
+
const parentProfile = activeOwner?.[6] ?? null
|
|
344
|
+
let mounted = false
|
|
345
|
+
return [
|
|
346
|
+
STRUCTURAL,
|
|
347
|
+
(parent, before) => {
|
|
348
|
+
if (mounted) throw new Error(DEV ? 'compiled Profiler is already mounted' : 'V008')
|
|
349
|
+
mounted = true
|
|
350
|
+
const readId = (): string => {
|
|
351
|
+
const id = isCompiledBinding(idInput) ? idInput[1]() : idInput
|
|
352
|
+
if (typeof id !== 'string') throw new TypeError('Profiler id must be a string')
|
|
353
|
+
return id
|
|
354
|
+
}
|
|
355
|
+
const readOnRender = (): ProfilerOnRender => {
|
|
356
|
+
const onRender = isCompiledBinding(onRenderInput) ? onRenderInput[1]() : onRenderInput
|
|
357
|
+
if (typeof onRender !== 'function') {
|
|
358
|
+
throw new TypeError('Profiler onRender must be a function')
|
|
359
|
+
}
|
|
360
|
+
return onRender
|
|
361
|
+
}
|
|
362
|
+
const boundary: ProfileBoundary = {
|
|
363
|
+
parent: parentProfile?.boundary ?? null,
|
|
364
|
+
id: readId(),
|
|
365
|
+
onRender: readOnRender(),
|
|
366
|
+
baseDuration: 0,
|
|
367
|
+
mounted: false,
|
|
368
|
+
pendingDuration: 0,
|
|
369
|
+
pendingStart: 0,
|
|
370
|
+
scheduled: false,
|
|
371
|
+
}
|
|
372
|
+
const childOwner = createOwner(context, rootIdentity, parentErrorBoundary, parentConnection, {
|
|
373
|
+
frame: parentProfile?.frame ?? null,
|
|
374
|
+
boundary,
|
|
375
|
+
})
|
|
376
|
+
const hydratedRange = claimHydrationSlotRange(parent)
|
|
377
|
+
const start = hydratedRange?.[0] ?? document.createComment(DEV ? 'vidact:profiler' : '')
|
|
378
|
+
const end = hydratedRange?.[1] ?? document.createComment(DEV ? '/vidact:profiler' : '')
|
|
379
|
+
if (hydratedRange === undefined) {
|
|
380
|
+
parent.insertBefore(start, before)
|
|
381
|
+
parent.insertBefore(end, before)
|
|
382
|
+
}
|
|
383
|
+
const currentParent = rangeParent(start, end, 'Profiler boundary')
|
|
384
|
+
let stagedNodes: readonly Node[] = []
|
|
385
|
+
const started = profileNow()
|
|
386
|
+
try {
|
|
387
|
+
const [fragment, nodes] =
|
|
388
|
+
hydratedRange === undefined
|
|
389
|
+
? stageRender(render, childOwner)
|
|
390
|
+
: withHydrationInsertion(currentParent, end, () => stageRender(render, childOwner))
|
|
391
|
+
stagedNodes = nodes
|
|
392
|
+
currentParent.insertBefore(fragment, end)
|
|
393
|
+
commitPublishedNodes(nodes)
|
|
394
|
+
} catch (error) {
|
|
395
|
+
disposePublished(childOwner, stagedNodes)
|
|
396
|
+
throw error
|
|
397
|
+
}
|
|
398
|
+
const finished = profileNow()
|
|
399
|
+
boundary.baseDuration = finished - started
|
|
400
|
+
boundary.mounted = true
|
|
401
|
+
emitProfileMeasure(childOwner, 'range', started, finished)
|
|
402
|
+
runOwnerTask(childOwner, () =>
|
|
403
|
+
boundary.onRender(
|
|
404
|
+
boundary.id,
|
|
405
|
+
'mount',
|
|
406
|
+
boundary.baseDuration,
|
|
407
|
+
boundary.baseDuration,
|
|
408
|
+
started,
|
|
409
|
+
finished,
|
|
410
|
+
),
|
|
411
|
+
)
|
|
412
|
+
const removeIdUpdater = isCompiledBinding(idInput)
|
|
413
|
+
? subscribeBinding(idInput, () => {
|
|
414
|
+
boundary.id = readId()
|
|
415
|
+
})
|
|
416
|
+
: noop
|
|
417
|
+
const removeCallbackUpdater = isCompiledBinding(onRenderInput)
|
|
418
|
+
? subscribeBinding(onRenderInput, () => {
|
|
419
|
+
boundary.onRender = readOnRender()
|
|
420
|
+
})
|
|
421
|
+
: noop
|
|
422
|
+
onCleanup(() => {
|
|
423
|
+
removeCallbackUpdater()
|
|
424
|
+
removeIdUpdater()
|
|
425
|
+
disposeRange(childOwner, start, end)
|
|
426
|
+
})
|
|
427
|
+
},
|
|
428
|
+
'slot',
|
|
429
|
+
]
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/** @internal */
|
|
433
|
+
export function recordCompiledDebugValue<Value>(
|
|
434
|
+
valueInput: Value | CompiledBinding<Value>,
|
|
435
|
+
format?: (value: Value) => unknown,
|
|
436
|
+
): void {
|
|
437
|
+
if (!profilingEnabled) return
|
|
438
|
+
const owner = activeConstructionOwner ?? activeOwner
|
|
439
|
+
if (owner === null) {
|
|
440
|
+
throw new Error(DEV ? 'useDebugValue must run inside a compiled component' : 'V036')
|
|
441
|
+
}
|
|
442
|
+
const frame = ensureProfileContext(owner).frame
|
|
443
|
+
if (frame === null) {
|
|
444
|
+
throw new Error(DEV ? 'useDebugValue must run inside a compiled component' : 'V036')
|
|
445
|
+
}
|
|
446
|
+
const read = (): unknown => {
|
|
447
|
+
const value = isCompiledBinding(valueInput) ? valueInput[1]() : valueInput
|
|
448
|
+
return format === undefined ? value : format(value)
|
|
449
|
+
}
|
|
450
|
+
const index = frame.values.length
|
|
451
|
+
frame.values.push(read())
|
|
452
|
+
if (isCompiledBinding(valueInput)) {
|
|
453
|
+
const remove = subscribeBinding(valueInput, () => {
|
|
454
|
+
frame.values[index] = read()
|
|
455
|
+
})
|
|
456
|
+
owner[1].add(remove)
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/** @internal */
|
|
461
|
+
export function captureCompiledOwnerStack(): string | null {
|
|
462
|
+
if (!profilingEnabled) return null
|
|
463
|
+
const owner = activeConstructionOwner ?? activeOwner
|
|
464
|
+
let frame = owner === null ? null : ensureProfileContext(owner).frame
|
|
465
|
+
if (frame === null) return null
|
|
466
|
+
const lines: string[] = []
|
|
467
|
+
while (frame !== null) {
|
|
468
|
+
const values = frame.values.length === 0 ? '' : ` [${frame.values.map(String).join(', ')}]`
|
|
469
|
+
lines.push(`\n at ${frame.name}${values}`)
|
|
470
|
+
frame = frame.parent
|
|
471
|
+
}
|
|
472
|
+
return lines.join('')
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function validateActivityMode(mode: unknown): 'visible' | 'hidden' {
|
|
476
|
+
if (mode === 'visible' || mode === 'hidden') return mode
|
|
477
|
+
throw new TypeError(DEV ? 'Activity mode must be "visible" or "hidden"' : 'V034')
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function readServerHiddenDisplay(element: Element): readonly [string, string] | undefined {
|
|
481
|
+
const cssText = element.getAttribute('style')
|
|
482
|
+
const hiddenDeclaration = 'display:none!important'
|
|
483
|
+
if (cssText === null || !cssText.endsWith(hiddenDeclaration)) return undefined
|
|
484
|
+
let authoredCssText = cssText.slice(0, -hiddenDeclaration.length)
|
|
485
|
+
if (authoredCssText.endsWith(';')) authoredCssText = authoredCssText.slice(0, -1)
|
|
486
|
+
const style = document.createElement('div').style
|
|
487
|
+
style.cssText = authoredCssText
|
|
488
|
+
return [style.display, style.getPropertyPriority('display')]
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export function errorBoundary(
|
|
492
|
+
render: () => CompiledRenderValue,
|
|
493
|
+
fallback: (error: unknown, reset: () => void) => CompiledRenderValue,
|
|
494
|
+
onError?: CompiledErrorHandler,
|
|
495
|
+
): StructuralBinding {
|
|
496
|
+
const context = activeContextFrame ?? activeOwner?.[2] ?? null
|
|
497
|
+
const rootIdentity = activeOwner?.[3] ?? activeRootIdentity
|
|
498
|
+
if (rootIdentity === null) {
|
|
499
|
+
throw new Error(DEV ? 'errorBoundary must run during compiled root construction' : 'V028')
|
|
500
|
+
}
|
|
501
|
+
const parentBoundary = activeOwner?.[4] ?? null
|
|
502
|
+
let mounted = false
|
|
503
|
+
return [
|
|
504
|
+
STRUCTURAL,
|
|
505
|
+
(parent, before) => {
|
|
506
|
+
if (mounted) throw new Error(DEV ? 'compiled error boundary is already mounted' : 'V008')
|
|
507
|
+
mounted = true
|
|
508
|
+
const hydratedRange = claimHydrationSlotRange(parent)
|
|
509
|
+
const start = hydratedRange?.[0] ?? document.createComment(DEV ? 'vidact:error' : '')
|
|
510
|
+
const end = hydratedRange?.[1] ?? document.createComment(DEV ? '/vidact:error' : '')
|
|
511
|
+
if (hydratedRange === undefined) {
|
|
512
|
+
parent.insertBefore(start, before)
|
|
513
|
+
parent.insertBefore(end, before)
|
|
514
|
+
}
|
|
515
|
+
let currentOwner: Owner | null = null
|
|
516
|
+
let currentNodes: readonly Node[] = []
|
|
517
|
+
let failed = false
|
|
518
|
+
|
|
519
|
+
const publish = (
|
|
520
|
+
read: () => CompiledRenderValue,
|
|
521
|
+
boundary: ErrorBoundaryHandler | null,
|
|
522
|
+
): void => {
|
|
523
|
+
const currentParent = rangeParent(start, end, 'error boundary')
|
|
524
|
+
const nextOwner = createOwner(context, rootIdentity, boundary)
|
|
525
|
+
const [fragment, nodes] =
|
|
526
|
+
hydratedRange === undefined
|
|
527
|
+
? stageRender(read, nextOwner)
|
|
528
|
+
: withHydrationInsertion(currentParent, end, () => stageRender(read, nextOwner))
|
|
529
|
+
try {
|
|
530
|
+
currentParent.insertBefore(fragment, end)
|
|
531
|
+
commitPublishedNodes(nodes)
|
|
532
|
+
} catch (error) {
|
|
533
|
+
disposePublished(nextOwner, nodes)
|
|
534
|
+
throw error
|
|
535
|
+
}
|
|
536
|
+
const previousOwner = currentOwner
|
|
537
|
+
const previousNodes = currentNodes
|
|
538
|
+
currentOwner = nextOwner
|
|
539
|
+
currentNodes = nodes
|
|
540
|
+
disposePublished(previousOwner, previousNodes)
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const reset = (): void => {
|
|
544
|
+
if (!failed) return
|
|
545
|
+
try {
|
|
546
|
+
failed = false
|
|
547
|
+
publish(render, boundary)
|
|
548
|
+
} catch (error) {
|
|
549
|
+
recover(error)
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
const recover = (error: unknown): void => {
|
|
553
|
+
failed = true
|
|
554
|
+
publish(() => fallback(error, reset), parentBoundary)
|
|
555
|
+
onError?.(error)
|
|
556
|
+
rootIdentity.onCaughtError?.(error)
|
|
557
|
+
}
|
|
558
|
+
const boundary: ErrorBoundaryHandler = { handle: recover, parent: parentBoundary }
|
|
559
|
+
|
|
560
|
+
try {
|
|
561
|
+
publish(render, boundary)
|
|
562
|
+
} catch (error) {
|
|
563
|
+
recover(error)
|
|
564
|
+
}
|
|
565
|
+
onCleanup(() => {
|
|
566
|
+
try {
|
|
567
|
+
disposePublished(currentOwner, currentNodes)
|
|
568
|
+
} finally {
|
|
569
|
+
start.remove()
|
|
570
|
+
end.remove()
|
|
571
|
+
}
|
|
572
|
+
})
|
|
573
|
+
},
|
|
574
|
+
'slot',
|
|
575
|
+
]
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
export function suspense(
|
|
579
|
+
render: () => CompiledRenderValue,
|
|
580
|
+
fallback: () => CompiledRenderValue,
|
|
581
|
+
): StructuralBinding {
|
|
582
|
+
const context = activeContextFrame ?? activeOwner?.[2] ?? null
|
|
583
|
+
const rootIdentity = activeOwner?.[3] ?? activeRootIdentity
|
|
584
|
+
if (rootIdentity === null) {
|
|
585
|
+
throw new Error(DEV ? 'Suspense must run during compiled root construction' : 'V032')
|
|
586
|
+
}
|
|
587
|
+
const parentErrorBoundary = activeOwner?.[4] ?? null
|
|
588
|
+
let mounted = false
|
|
589
|
+
return [
|
|
590
|
+
STRUCTURAL,
|
|
591
|
+
(parent, before) => {
|
|
592
|
+
if (mounted) throw new Error(DEV ? 'compiled Suspense boundary is already mounted' : 'V008')
|
|
593
|
+
mounted = true
|
|
594
|
+
const lifetimeOwner = activeOwner
|
|
595
|
+
if (lifetimeOwner === null) {
|
|
596
|
+
throw new Error(DEV ? 'Suspense must mount inside an owned compiled root' : 'V032')
|
|
597
|
+
}
|
|
598
|
+
const hydratedRange = claimHydrationSlotRange(parent)
|
|
599
|
+
const start = hydratedRange?.[0] ?? document.createComment(DEV ? 'vidact:suspense' : '')
|
|
600
|
+
const end = hydratedRange?.[1] ?? document.createComment(DEV ? '/vidact:suspense' : '')
|
|
601
|
+
if (hydratedRange === undefined) {
|
|
602
|
+
parent.insertBefore(start, before)
|
|
603
|
+
parent.insertBefore(end, before)
|
|
604
|
+
}
|
|
605
|
+
const pendingMarker =
|
|
606
|
+
hydratedRange === undefined ? undefined : claimHydrationSuspenseFallback(parent)
|
|
607
|
+
let currentOwner: Owner | null = null
|
|
608
|
+
let currentNodes: readonly Node[] = []
|
|
609
|
+
let currentKind: 'content' | 'fallback' | null = null
|
|
610
|
+
let removeResourceListener = noop
|
|
611
|
+
let generation = 0
|
|
612
|
+
let boundary: ErrorBoundaryHandler
|
|
613
|
+
// Server content is on screen but the client suspended while hydrating it (a lazy
|
|
614
|
+
// chunk still loading, say). The server nodes are kept until the next publish,
|
|
615
|
+
// which renders client-side and replaces them — the same shape as a claimed
|
|
616
|
+
// pending fallback, without ever showing the fallback over content the visitor
|
|
617
|
+
// can already see.
|
|
618
|
+
let dehydrated = false
|
|
619
|
+
|
|
620
|
+
const publish = (
|
|
621
|
+
read: () => CompiledRenderValue,
|
|
622
|
+
kind: 'content' | 'fallback',
|
|
623
|
+
replaceServerNodes = false,
|
|
624
|
+
): void => {
|
|
625
|
+
const currentParent = rangeParent(start, end, 'Suspense boundary')
|
|
626
|
+
const nextOwner = createOwner(
|
|
627
|
+
context,
|
|
628
|
+
rootIdentity,
|
|
629
|
+
kind === 'content' ? boundary : parentErrorBoundary,
|
|
630
|
+
)
|
|
631
|
+
const serverNodes = replaceServerNodes ? nodesBetween(start, end) : []
|
|
632
|
+
const [fragment, nodes] = replaceServerNodes
|
|
633
|
+
? withoutHydration(() => stageRender(read, nextOwner))
|
|
634
|
+
: hydratedRange === undefined || currentKind !== null
|
|
635
|
+
? stageRender(read, nextOwner)
|
|
636
|
+
: withHydrationInsertion(currentParent, end, () => stageRender(read, nextOwner))
|
|
637
|
+
try {
|
|
638
|
+
currentParent.insertBefore(fragment, end)
|
|
639
|
+
commitPublishedNodes(nodes)
|
|
640
|
+
} catch (error) {
|
|
641
|
+
disposePublished(nextOwner, nodes)
|
|
642
|
+
throw error
|
|
643
|
+
}
|
|
644
|
+
const previousOwner = currentOwner
|
|
645
|
+
const previousNodes = currentNodes
|
|
646
|
+
currentOwner = nextOwner
|
|
647
|
+
currentNodes = nodes
|
|
648
|
+
currentKind = kind
|
|
649
|
+
disposePublished(previousOwner, previousNodes)
|
|
650
|
+
for (const node of serverNodes) node.parentNode?.removeChild(node)
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const beginAttempt = (): number => {
|
|
654
|
+
const attemptGeneration = ++generation
|
|
655
|
+
removeResourceListener()
|
|
656
|
+
removeResourceListener = noop
|
|
657
|
+
return attemptGeneration
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const suspend = (failure: unknown, attemptGeneration: number): void => {
|
|
661
|
+
if (!isSuspension(failure)) throw failure
|
|
662
|
+
const retry = (): void => {
|
|
663
|
+
if (lifetimeOwner[0] || attemptGeneration !== generation) return
|
|
664
|
+
runOwnerTask(lifetimeOwner, () => attempt(false))
|
|
665
|
+
}
|
|
666
|
+
removeResourceListener = subscribeResource(failure.resource, retry)
|
|
667
|
+
if (
|
|
668
|
+
currentKind === null &&
|
|
669
|
+
hydratedRange !== undefined &&
|
|
670
|
+
pendingMarker === undefined &&
|
|
671
|
+
isHydrating()
|
|
672
|
+
) {
|
|
673
|
+
dehydrated = true
|
|
674
|
+
skipHydrationRange(start, end)
|
|
675
|
+
return
|
|
676
|
+
}
|
|
677
|
+
if (currentKind !== 'fallback') {
|
|
678
|
+
try {
|
|
679
|
+
publish(fallback, 'fallback', dehydrated)
|
|
680
|
+
dehydrated = false
|
|
681
|
+
if (pendingMarker !== undefined) {
|
|
682
|
+
currentNodes = [pendingMarker, ...currentNodes]
|
|
683
|
+
}
|
|
684
|
+
} catch (fallbackError) {
|
|
685
|
+
removeResourceListener()
|
|
686
|
+
removeResourceListener = noop
|
|
687
|
+
throw fallbackError
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const attempt = (initial: boolean): void => {
|
|
693
|
+
const attemptGeneration = beginAttempt()
|
|
694
|
+
try {
|
|
695
|
+
publish(render, 'content', (initial && pendingMarker !== undefined) || dehydrated)
|
|
696
|
+
dehydrated = false
|
|
697
|
+
} catch (error) {
|
|
698
|
+
if (!isSuspension(error)) {
|
|
699
|
+
if (initial || !routeOwnerError(lifetimeOwner, error)) throw error
|
|
700
|
+
return
|
|
701
|
+
}
|
|
702
|
+
suspend(error, attemptGeneration)
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
boundary = {
|
|
707
|
+
handle: (error) => suspend(error, beginAttempt()),
|
|
708
|
+
parent: parentErrorBoundary,
|
|
709
|
+
}
|
|
710
|
+
attempt(true)
|
|
711
|
+
onCleanup(() => {
|
|
712
|
+
generation += 1
|
|
713
|
+
removeResourceListener()
|
|
714
|
+
try {
|
|
715
|
+
disposePublished(currentOwner, currentNodes)
|
|
716
|
+
} finally {
|
|
717
|
+
start.remove()
|
|
718
|
+
end.remove()
|
|
719
|
+
}
|
|
720
|
+
})
|
|
721
|
+
},
|
|
722
|
+
'slot',
|
|
723
|
+
]
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
type PortalRenderableProps = {
|
|
727
|
+
readonly children: CompiledRenderValue | readonly CompiledRenderValue[]
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const keyedPortalIdentities = new WeakMap<ParentNode, Map<string | number | bigint, object>>()
|
|
731
|
+
|
|
732
|
+
export function createPortal(
|
|
733
|
+
children: CompiledRenderValue | readonly CompiledRenderValue[],
|
|
734
|
+
container: ParentNode,
|
|
735
|
+
key?: string | number | bigint | null,
|
|
736
|
+
): CompiledRenderValue {
|
|
737
|
+
if (!(container instanceof Node) || typeof container.insertBefore !== 'function') {
|
|
738
|
+
throw new TypeError(DEV ? 'createPortal requires a DOM container' : 'V026')
|
|
739
|
+
}
|
|
740
|
+
const input: PortalRenderableProps = { children }
|
|
741
|
+
const capability = { props: input } as RenderableProtocol & {
|
|
742
|
+
readonly props: PortalRenderableProps
|
|
743
|
+
}
|
|
744
|
+
Object.defineProperty(capability, RENDERABLE, {
|
|
745
|
+
configurable: false,
|
|
746
|
+
enumerable: false,
|
|
747
|
+
value: {
|
|
748
|
+
identity: portalIdentity(container, key),
|
|
749
|
+
input,
|
|
750
|
+
reconcile: true,
|
|
751
|
+
construct: (currentInput: PortalRenderableProps | CompiledBinding<PortalRenderableProps>) =>
|
|
752
|
+
createPortalStructural(portalChildren(currentInput), container),
|
|
753
|
+
},
|
|
754
|
+
})
|
|
755
|
+
return capability as unknown as CompiledRenderValue
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function portalIdentity(
|
|
759
|
+
container: ParentNode,
|
|
760
|
+
key: string | number | bigint | null | undefined,
|
|
761
|
+
): object {
|
|
762
|
+
if (key == null) return container
|
|
763
|
+
let identities = keyedPortalIdentities.get(container)
|
|
764
|
+
if (identities === undefined) {
|
|
765
|
+
identities = new Map()
|
|
766
|
+
keyedPortalIdentities.set(container, identities)
|
|
767
|
+
}
|
|
768
|
+
let identity = identities.get(key)
|
|
769
|
+
if (identity === undefined) {
|
|
770
|
+
identity = {}
|
|
771
|
+
identities.set(key, identity)
|
|
772
|
+
}
|
|
773
|
+
return identity
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function portalChildren(
|
|
777
|
+
input: PortalRenderableProps | CompiledBinding<PortalRenderableProps>,
|
|
778
|
+
): CompiledRenderValue | readonly CompiledRenderValue[] {
|
|
779
|
+
if (!isCompiledBinding(input)) return input.children
|
|
780
|
+
return binding(
|
|
781
|
+
input[2],
|
|
782
|
+
input[3],
|
|
783
|
+
() => input[1]().children,
|
|
784
|
+
input[4],
|
|
785
|
+
input[5],
|
|
786
|
+
) as CompiledBinding<CompiledRenderValue>
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function createPortalStructural(
|
|
790
|
+
children: CompiledRenderValue | readonly CompiledRenderValue[],
|
|
791
|
+
container: ParentNode,
|
|
792
|
+
): StructuralBinding {
|
|
793
|
+
const context = activeContextFrame ?? activeOwner?.[2] ?? null
|
|
794
|
+
const rootIdentity = activeOwner?.[3] ?? activeRootIdentity
|
|
795
|
+
if (rootIdentity === null) {
|
|
796
|
+
throw new Error(DEV ? 'createPortal must run during compiled root construction' : 'V027')
|
|
797
|
+
}
|
|
798
|
+
let mounted = false
|
|
799
|
+
return [
|
|
800
|
+
STRUCTURAL,
|
|
801
|
+
(logicalParent, before) => {
|
|
802
|
+
if (mounted) throw new Error(DEV ? 'compiled portal is already mounted' : 'V008')
|
|
803
|
+
mounted = true
|
|
804
|
+
const logicalMarker = document.createComment(DEV ? 'vidact:portal' : '')
|
|
805
|
+
logicalParent.insertBefore(logicalMarker, before)
|
|
806
|
+
const start = document.createComment(DEV ? 'vidact:portal:start' : '')
|
|
807
|
+
const end = document.createComment(DEV ? 'vidact:portal:end' : '')
|
|
808
|
+
const fragment = document.createDocumentFragment()
|
|
809
|
+
fragment.append(start, end)
|
|
810
|
+
const portalOwner = withRootIdentity(rootIdentity, () =>
|
|
811
|
+
withContextFrame(context, createOwner),
|
|
812
|
+
)
|
|
813
|
+
try {
|
|
814
|
+
withOwner(portalOwner, () => insertValue(fragment, children, end))
|
|
815
|
+
} catch (error) {
|
|
816
|
+
disposeOwner(portalOwner)
|
|
817
|
+
logicalMarker.remove()
|
|
818
|
+
throw error
|
|
819
|
+
}
|
|
820
|
+
const rollback = (): void => {
|
|
821
|
+
removeBetween(start, end)
|
|
822
|
+
start.remove()
|
|
823
|
+
end.remove()
|
|
824
|
+
}
|
|
825
|
+
const publication: PortalPublication = [
|
|
826
|
+
() => {
|
|
827
|
+
container.insertBefore(fragment, null)
|
|
828
|
+
try {
|
|
829
|
+
commitRangeRefs(start, end)
|
|
830
|
+
} catch (error) {
|
|
831
|
+
rollback()
|
|
832
|
+
throw error
|
|
833
|
+
}
|
|
834
|
+
},
|
|
835
|
+
rollback,
|
|
836
|
+
]
|
|
837
|
+
if (rootIdentity.mounted) {
|
|
838
|
+
stagePublication([publication[0], publication[1], () => disposeOwner(portalOwner)])
|
|
839
|
+
} else {
|
|
840
|
+
let pending = pendingRootPortals.get(rootIdentity)
|
|
841
|
+
if (pending === undefined) {
|
|
842
|
+
pending = new Set()
|
|
843
|
+
pendingRootPortals.set(rootIdentity, pending)
|
|
844
|
+
}
|
|
845
|
+
pending.add(publication)
|
|
846
|
+
}
|
|
847
|
+
onCleanup(() => {
|
|
848
|
+
pendingRootPortals.get(rootIdentity)?.delete(publication)
|
|
849
|
+
try {
|
|
850
|
+
disposeOwner(portalOwner)
|
|
851
|
+
} finally {
|
|
852
|
+
publication[1]()
|
|
853
|
+
logicalMarker.remove()
|
|
854
|
+
}
|
|
855
|
+
})
|
|
856
|
+
},
|
|
857
|
+
]
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
type RefValue<T = Element> =
|
|
861
|
+
| ((value: T | null) => void | (() => void))
|
|
862
|
+
| { current: unknown }
|
|
863
|
+
| null
|
|
864
|
+
| undefined
|
|
865
|
+
|
|
866
|
+
type PendingRef = [owner: Owner | null, value: RefValue, attached?: (cleanup: () => void) => void]
|
|
867
|
+
|
|
868
|
+
type NodePosition = readonly [node: Node, parent: Node | null, nextSibling: Node | null]
|
|
869
|
+
|
|
870
|
+
type PublicationOperation = readonly [
|
|
871
|
+
commit: () => void,
|
|
872
|
+
rollback: () => void,
|
|
873
|
+
abort?: (() => void) | undefined,
|
|
874
|
+
finalize?: (() => void) | undefined,
|
|
875
|
+
priority?: number | undefined,
|
|
876
|
+
errorOwner?: Owner | null | undefined,
|
|
877
|
+
]
|
|
878
|
+
|
|
879
|
+
export type CompiledPropTransition = PublicationOperation
|
|
880
|
+
|
|
881
|
+
type ComponentRange = readonly [start: Comment, end: Comment, scope: CompiledScope]
|
|
882
|
+
|
|
883
|
+
let activeOwner: Owner | null = null
|
|
884
|
+
let activeContextFrame: ContextFrame | null = null
|
|
885
|
+
let activeRootIdentity: RootIdentity | null = null
|
|
886
|
+
let nextClientRoot = 0
|
|
887
|
+
let activeScopeCollector: Set<CompiledScope> | null = null
|
|
888
|
+
let activeConstructionOwner: Owner | null = null
|
|
889
|
+
let activeProfileName: string | null = null
|
|
890
|
+
let activeErrorOwner: Owner | null = null
|
|
891
|
+
let failedOwner: Owner | null = null
|
|
892
|
+
let transactionDepth = 0
|
|
893
|
+
let disposalCascadeDepth = 0
|
|
894
|
+
let drainingFlushes = false
|
|
895
|
+
let activePublication: PublicationOperation[] | null = null
|
|
896
|
+
const scheduledFlushes = new Set<() => void>()
|
|
897
|
+
const disposalCascadeOwners = new Set<Owner>()
|
|
898
|
+
const scopeOwners = new WeakMap<CompiledScope, Owner>()
|
|
899
|
+
const scopeNamespaces = new WeakMap<CompiledScope, IntrinsicNamespace>()
|
|
900
|
+
const componentRanges = new WeakMap<CompiledComponentResult, ComponentRange>()
|
|
901
|
+
const pendingRefs = new WeakMap<Element, PendingRef>()
|
|
902
|
+
const componentCommitOwners = new WeakMap<Comment, Owner>()
|
|
903
|
+
const pendingInsertionCommits = new WeakMap<Owner, Set<() => void>>()
|
|
904
|
+
const pendingOwnerCommits = new WeakMap<Owner, Set<() => void>>()
|
|
905
|
+
const pendingRootPortals = new WeakMap<RootIdentity, Set<PortalPublication>>()
|
|
906
|
+
|
|
907
|
+
export function createCompiledScope(): CompiledScope {
|
|
908
|
+
return createScope(wideSourceOperations)
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
export function createNarrowCompiledScope(): CompiledScope {
|
|
912
|
+
return createScope(narrowSourceOperations)
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
export function runCompiledTransaction<T>(operation: () => T): T {
|
|
916
|
+
transactionDepth += 1
|
|
917
|
+
try {
|
|
918
|
+
return operation()
|
|
919
|
+
} finally {
|
|
920
|
+
transactionDepth -= 1
|
|
921
|
+
if (transactionDepth === 0) drainFlushes()
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
function createScope(operations: SourceOperations): CompiledScope {
|
|
926
|
+
const namespace = currentIntrinsicNamespace()
|
|
927
|
+
const owner = createOwner()
|
|
928
|
+
const updaters: Array<CompiledUpdater | undefined> = []
|
|
929
|
+
const freeUpdaterIndexes: number[] = []
|
|
930
|
+
const addedDuringFlush = new Set<CompiledUpdater>()
|
|
931
|
+
let batchDepth = 0
|
|
932
|
+
let flushing = false
|
|
933
|
+
let pending: SourceMask = 0
|
|
934
|
+
let cachedUpdaterOrder: number[] | undefined
|
|
935
|
+
|
|
936
|
+
const flush = (): void => {
|
|
937
|
+
if (owner[0] || flushing) return
|
|
938
|
+
const operation = (): void => {
|
|
939
|
+
flushing = true
|
|
940
|
+
try {
|
|
941
|
+
let pass = 0
|
|
942
|
+
const updaterOrder = (cachedUpdaterOrder ??= topologicalUpdaterOrder(updaters, operations))
|
|
943
|
+
while (!operations[0](pending)) {
|
|
944
|
+
pass += 1
|
|
945
|
+
if (pass > MAX_FLUSH_PASSES) {
|
|
946
|
+
pending = 0
|
|
947
|
+
throw new Error(DEV ? 'Vidact compiled scope did not stabilize' : 'V001')
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
let active = pending
|
|
951
|
+
pending = 0
|
|
952
|
+
for (const index of updaterOrder) {
|
|
953
|
+
const updater = updaters[index]
|
|
954
|
+
if (
|
|
955
|
+
updater === undefined ||
|
|
956
|
+
addedDuringFlush.has(updater) ||
|
|
957
|
+
!updater[3] ||
|
|
958
|
+
!operations[1](active, updater[0])
|
|
959
|
+
) {
|
|
960
|
+
continue
|
|
961
|
+
}
|
|
962
|
+
if (profilingEnabled) {
|
|
963
|
+
measureProfileWork(owner, 'updater', () => updater[2](active))
|
|
964
|
+
} else {
|
|
965
|
+
updater[2](active)
|
|
966
|
+
}
|
|
967
|
+
if (updater[1] !== undefined) active = operations[2](active, updater[1])
|
|
968
|
+
}
|
|
969
|
+
addedDuringFlush.clear()
|
|
970
|
+
}
|
|
971
|
+
} finally {
|
|
972
|
+
addedDuringFlush.clear()
|
|
973
|
+
flushing = false
|
|
974
|
+
}
|
|
975
|
+
if (retainedUiEnabled) notifyRetainedFlush(owner[5] ?? null)
|
|
976
|
+
}
|
|
977
|
+
if (profilingEnabled) measureProfileWork(owner, 'scheduler', operation, true)
|
|
978
|
+
else operation()
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
const scope: CompiledScope = [
|
|
982
|
+
(reads, run, writes) => {
|
|
983
|
+
if (owner[0]) {
|
|
984
|
+
throw new Error(DEV ? 'cannot add an updater to a disposed scope' : 'V002')
|
|
985
|
+
}
|
|
986
|
+
const errorOwner = activeOwner ?? owner
|
|
987
|
+
const context = activeContextFrame ?? errorOwner[2]
|
|
988
|
+
const entry: CompiledUpdater = [
|
|
989
|
+
reads,
|
|
990
|
+
writes,
|
|
991
|
+
(active) => {
|
|
992
|
+
try {
|
|
993
|
+
withOwner(errorOwner, () =>
|
|
994
|
+
withContextFrame(context, () => withScopeNamespace(scope, () => run(active))),
|
|
995
|
+
)
|
|
996
|
+
} catch (error) {
|
|
997
|
+
if (!routeOwnerError(errorOwner, error)) {
|
|
998
|
+
failedOwner = errorOwner
|
|
999
|
+
throw error
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
},
|
|
1003
|
+
true,
|
|
1004
|
+
]
|
|
1005
|
+
const reusableIndex = freeUpdaterIndexes.pop()
|
|
1006
|
+
const index = reusableIndex ?? updaters.length
|
|
1007
|
+
updaters[index] = entry
|
|
1008
|
+
cachedUpdaterOrder = undefined
|
|
1009
|
+
if (flushing) addedDuringFlush.add(entry)
|
|
1010
|
+
const remove = (): void => {
|
|
1011
|
+
if (updaters[index] !== entry) return
|
|
1012
|
+
entry[3] = false
|
|
1013
|
+
updaters[index] = undefined
|
|
1014
|
+
cachedUpdaterOrder = undefined
|
|
1015
|
+
freeUpdaterIndexes.push(index)
|
|
1016
|
+
}
|
|
1017
|
+
if (activeOwner !== null && activeOwner !== owner) activeOwner[1].add(remove)
|
|
1018
|
+
return remove
|
|
1019
|
+
},
|
|
1020
|
+
(sources) => {
|
|
1021
|
+
if (owner[0] || operations[0](sources)) return
|
|
1022
|
+
pending = operations[2](pending, sources)
|
|
1023
|
+
if (batchDepth === 0) {
|
|
1024
|
+
if (retainedUiEnabled) scheduleOwnerFlush(owner, flush)
|
|
1025
|
+
else scheduleFlush(flush)
|
|
1026
|
+
}
|
|
1027
|
+
},
|
|
1028
|
+
<T>(operation: () => T): T => {
|
|
1029
|
+
batchDepth += 1
|
|
1030
|
+
transactionDepth += 1
|
|
1031
|
+
try {
|
|
1032
|
+
return operation()
|
|
1033
|
+
} finally {
|
|
1034
|
+
batchDepth -= 1
|
|
1035
|
+
if (batchDepth === 0 && !operations[0](pending)) {
|
|
1036
|
+
if (retainedUiEnabled) scheduleOwnerFlush(owner, flush)
|
|
1037
|
+
else scheduleFlush(flush)
|
|
1038
|
+
}
|
|
1039
|
+
transactionDepth -= 1
|
|
1040
|
+
if (transactionDepth === 0) drainFlushes()
|
|
1041
|
+
}
|
|
1042
|
+
},
|
|
1043
|
+
() => {
|
|
1044
|
+
if (owner[0]) return
|
|
1045
|
+
try {
|
|
1046
|
+
disposeOwner(owner)
|
|
1047
|
+
} finally {
|
|
1048
|
+
pending = 0
|
|
1049
|
+
for (const updater of updaters) {
|
|
1050
|
+
if (updater !== undefined) updater[3] = false
|
|
1051
|
+
}
|
|
1052
|
+
updaters.length = 0
|
|
1053
|
+
freeUpdaterIndexes.length = 0
|
|
1054
|
+
addedDuringFlush.clear()
|
|
1055
|
+
}
|
|
1056
|
+
},
|
|
1057
|
+
]
|
|
1058
|
+
scopeOwners.set(scope, owner)
|
|
1059
|
+
scopeNamespaces.set(scope, namespace)
|
|
1060
|
+
activeScopeCollector?.add(scope)
|
|
1061
|
+
if (activeScopeCollector !== null) activeConstructionOwner = owner
|
|
1062
|
+
return scope
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
function topologicalUpdaterOrder(
|
|
1066
|
+
updaters: ReadonlyArray<CompiledUpdater | undefined>,
|
|
1067
|
+
operations: SourceOperations,
|
|
1068
|
+
): number[] {
|
|
1069
|
+
const activeIndexes = updaters.flatMap((updater, index) =>
|
|
1070
|
+
updater === undefined || !updater[3] ? [] : [index],
|
|
1071
|
+
)
|
|
1072
|
+
const edges = new Map<number, number[]>()
|
|
1073
|
+
for (const writerIndex of activeIndexes) {
|
|
1074
|
+
const writes = updaters[writerIndex]![1]
|
|
1075
|
+
if (writes === undefined) continue
|
|
1076
|
+
const readers: number[] = []
|
|
1077
|
+
for (const readerIndex of activeIndexes) {
|
|
1078
|
+
if (writerIndex !== readerIndex && operations[1](writes, updaters[readerIndex]![0])) {
|
|
1079
|
+
readers.push(readerIndex)
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
if (readers.length > 0) edges.set(writerIndex, readers)
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
const visitIndexes = new Int32Array(updaters.length).fill(-1)
|
|
1086
|
+
const lowLinks = new Int32Array(updaters.length)
|
|
1087
|
+
const stack: number[] = []
|
|
1088
|
+
const onStack = new Uint8Array(updaters.length)
|
|
1089
|
+
const components: number[][] = []
|
|
1090
|
+
let nextVisitIndex = 0
|
|
1091
|
+
|
|
1092
|
+
const connect = (updaterIndex: number): void => {
|
|
1093
|
+
const visitIndex = nextVisitIndex++
|
|
1094
|
+
visitIndexes[updaterIndex] = visitIndex
|
|
1095
|
+
lowLinks[updaterIndex] = visitIndex
|
|
1096
|
+
stack.push(updaterIndex)
|
|
1097
|
+
onStack[updaterIndex] = 1
|
|
1098
|
+
|
|
1099
|
+
for (const readerIndex of edges.get(updaterIndex) ?? []) {
|
|
1100
|
+
if (visitIndexes[readerIndex] === -1) {
|
|
1101
|
+
connect(readerIndex)
|
|
1102
|
+
lowLinks[updaterIndex] = Math.min(lowLinks[updaterIndex]!, lowLinks[readerIndex]!)
|
|
1103
|
+
} else if (onStack[readerIndex] === 1) {
|
|
1104
|
+
lowLinks[updaterIndex] = Math.min(lowLinks[updaterIndex]!, visitIndexes[readerIndex]!)
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
if (lowLinks[updaterIndex] !== visitIndexes[updaterIndex]) return
|
|
1109
|
+
const component: number[] = []
|
|
1110
|
+
let member: number
|
|
1111
|
+
do {
|
|
1112
|
+
member = stack.pop()!
|
|
1113
|
+
onStack[member] = 0
|
|
1114
|
+
component.push(member)
|
|
1115
|
+
} while (member !== updaterIndex)
|
|
1116
|
+
component.sort((left, right) => left - right)
|
|
1117
|
+
components.push(component)
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
for (const updaterIndex of activeIndexes) {
|
|
1121
|
+
if (visitIndexes[updaterIndex] === -1) connect(updaterIndex)
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
const componentByUpdater = new Int32Array(updaters.length).fill(-1)
|
|
1125
|
+
const componentEdges = components.map(() => new Set<number>())
|
|
1126
|
+
const componentIndegrees = new Uint32Array(components.length)
|
|
1127
|
+
const componentFirstIndexes = components.map((component) => component[0]!)
|
|
1128
|
+
for (let componentIndex = 0; componentIndex < components.length; componentIndex += 1) {
|
|
1129
|
+
for (const updaterIndex of components[componentIndex]!) {
|
|
1130
|
+
componentByUpdater[updaterIndex] = componentIndex
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
for (const [writerIndex, readers] of edges) {
|
|
1134
|
+
const writerComponent = componentByUpdater[writerIndex]!
|
|
1135
|
+
for (const readerIndex of readers) {
|
|
1136
|
+
const readerComponent = componentByUpdater[readerIndex]!
|
|
1137
|
+
if (
|
|
1138
|
+
writerComponent === readerComponent ||
|
|
1139
|
+
componentEdges[writerComponent]!.has(readerComponent)
|
|
1140
|
+
) {
|
|
1141
|
+
continue
|
|
1142
|
+
}
|
|
1143
|
+
componentEdges[writerComponent]!.add(readerComponent)
|
|
1144
|
+
componentIndegrees[readerComponent] = componentIndegrees[readerComponent]! + 1
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
const ready = components
|
|
1149
|
+
.map((_, index) => index)
|
|
1150
|
+
.filter((index) => componentIndegrees[index] === 0)
|
|
1151
|
+
.sort((left, right) => componentFirstIndexes[left]! - componentFirstIndexes[right]!)
|
|
1152
|
+
const ordered: number[] = []
|
|
1153
|
+
while (ready.length > 0) {
|
|
1154
|
+
const componentIndex = ready.shift()!
|
|
1155
|
+
ordered.push(...components[componentIndex]!)
|
|
1156
|
+
for (const readerComponent of componentEdges[componentIndex]!) {
|
|
1157
|
+
componentIndegrees[readerComponent] = componentIndegrees[readerComponent]! - 1
|
|
1158
|
+
if (componentIndegrees[readerComponent] === 0) {
|
|
1159
|
+
ready.push(readerComponent)
|
|
1160
|
+
ready.sort((left, right) => componentFirstIndexes[left]! - componentFirstIndexes[right]!)
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
return ordered
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
export function createCompiledState<T>(
|
|
1168
|
+
scope: CompiledScope,
|
|
1169
|
+
sourceMask: SourceMask,
|
|
1170
|
+
initialValue: T | (() => T),
|
|
1171
|
+
): StateSlot<T> {
|
|
1172
|
+
const assertWritable = stateWriteGuard(scope)
|
|
1173
|
+
const value = typeof initialValue === 'function' ? (initialValue as () => T)() : initialValue
|
|
1174
|
+
return createStateSlot(scope[1], sourceMask, value, assertWritable)
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
export function createCompiledReducer<State, Action, Initial>(
|
|
1178
|
+
scope: CompiledScope,
|
|
1179
|
+
sourceMask: SourceMask,
|
|
1180
|
+
reducer: (state: State, action: Action) => State,
|
|
1181
|
+
initialArg: Initial,
|
|
1182
|
+
initialize?: (initialArg: Initial) => State,
|
|
1183
|
+
): { readonly get: () => State; readonly set: (action: Action) => void } {
|
|
1184
|
+
const state = createStateSlot<State>(
|
|
1185
|
+
scope[1],
|
|
1186
|
+
sourceMask,
|
|
1187
|
+
initialize === undefined ? (initialArg as unknown as State) : initialize(initialArg),
|
|
1188
|
+
stateWriteGuard(scope),
|
|
1189
|
+
)
|
|
1190
|
+
return {
|
|
1191
|
+
get: state.get,
|
|
1192
|
+
set: (action) => state.set((previous) => reducer(previous, action)),
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
export function createCompiledMemo<T>(
|
|
1197
|
+
scope: CompiledScope,
|
|
1198
|
+
reads: SourceMask,
|
|
1199
|
+
writes: SourceMask,
|
|
1200
|
+
evaluate: () => T,
|
|
1201
|
+
readDependencies: () => readonly unknown[],
|
|
1202
|
+
): StateSlot<T> {
|
|
1203
|
+
let currentDependencies = readDependencies()
|
|
1204
|
+
const slot = createStateSlot(scope[1], writes, evaluate(), stateWriteGuard(scope))
|
|
1205
|
+
const removeUpdater = scope[0](reads, () => {
|
|
1206
|
+
const nextDependencies = readDependencies()
|
|
1207
|
+
if (equalDependencies(nextDependencies, currentDependencies)) return
|
|
1208
|
+
slot.replace(evaluate())
|
|
1209
|
+
currentDependencies = nextDependencies
|
|
1210
|
+
})
|
|
1211
|
+
const owner = scopeOwners.get(scope)
|
|
1212
|
+
if (owner === undefined) {
|
|
1213
|
+
removeUpdater()
|
|
1214
|
+
throw new Error(DEV ? 'createCompiledMemo received an unknown scope' : 'V003')
|
|
1215
|
+
}
|
|
1216
|
+
owner[1].add(removeUpdater)
|
|
1217
|
+
return slot
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
export function createContext<T>(defaultValue: T): CompiledContext<T> {
|
|
1221
|
+
let context: CompiledContext<T>
|
|
1222
|
+
const provider = ((props: {
|
|
1223
|
+
readonly children?: CompiledRenderValue | readonly CompiledRenderValue[]
|
|
1224
|
+
readonly value: T | CompiledBinding<T>
|
|
1225
|
+
}): StructuralBinding =>
|
|
1226
|
+
provideContext(context, props.value, props.children)) as CompiledContext<T>
|
|
1227
|
+
context = provider
|
|
1228
|
+
context.Provider = provider
|
|
1229
|
+
Object.defineProperty(context, CONTEXT, { value: defaultValue })
|
|
1230
|
+
return context
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
/** @internal */
|
|
1234
|
+
export function runWithCompiledContext<Value, Result>(
|
|
1235
|
+
context: CompiledContext<Value>,
|
|
1236
|
+
value: Value | CompiledBinding<Value>,
|
|
1237
|
+
operation: () => Result,
|
|
1238
|
+
): Result {
|
|
1239
|
+
if (!(CONTEXT in context)) {
|
|
1240
|
+
throw new Error(DEV ? 'runWithCompiledContext received an unknown context' : 'V003')
|
|
1241
|
+
}
|
|
1242
|
+
const parent = activeContextFrame ?? activeOwner?.[2] ?? null
|
|
1243
|
+
return withContextFrame(
|
|
1244
|
+
{
|
|
1245
|
+
context: context as CompiledContext<unknown>,
|
|
1246
|
+
input: value,
|
|
1247
|
+
parent,
|
|
1248
|
+
owner: activeOwner,
|
|
1249
|
+
},
|
|
1250
|
+
operation,
|
|
1251
|
+
)
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
export function createCompiledContext<T>(
|
|
1255
|
+
scope: CompiledScope,
|
|
1256
|
+
sourceMask: SourceMask,
|
|
1257
|
+
context: CompiledContext<T>,
|
|
1258
|
+
): StateSlot<T> {
|
|
1259
|
+
const input = contextInput(scope, context)
|
|
1260
|
+
return createCompiledProp(scope, sourceMask, input)
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
export function createCompiledExternalStore<T>(
|
|
1264
|
+
scope: CompiledScope,
|
|
1265
|
+
sourceMask: SourceMask,
|
|
1266
|
+
storeSubscribe:
|
|
1267
|
+
| ((onStoreChange: () => void) => () => void)
|
|
1268
|
+
| CompiledBinding<readonly [(onStoreChange: () => void) => () => void, () => T, (() => T)?]>,
|
|
1269
|
+
getSnapshot?: () => T,
|
|
1270
|
+
_getServerSnapshot?: () => T,
|
|
1271
|
+
): StateSlot<T> {
|
|
1272
|
+
const owner = scopeOwners.get(scope)
|
|
1273
|
+
if (owner === undefined) {
|
|
1274
|
+
throw new Error(DEV ? 'createCompiledExternalStore received an unknown scope' : 'V003')
|
|
1275
|
+
}
|
|
1276
|
+
const reactiveStore = isCompiledBinding(storeSubscribe) ? storeSubscribe : undefined
|
|
1277
|
+
let currentSubscribe: (onStoreChange: () => void) => () => void
|
|
1278
|
+
let currentGetSnapshot!: () => T
|
|
1279
|
+
const readStore = (): void => {
|
|
1280
|
+
const store = reactiveStore?.[1]()
|
|
1281
|
+
currentSubscribe = store?.[0] ?? (storeSubscribe as (onStoreChange: () => void) => () => void)
|
|
1282
|
+
currentGetSnapshot = store?.[1] ?? (getSnapshot as () => T)
|
|
1283
|
+
if (typeof currentSubscribe !== 'function' || typeof currentGetSnapshot !== 'function') {
|
|
1284
|
+
throw new TypeError(
|
|
1285
|
+
DEV ? 'external store requires subscribe and getSnapshot functions' : 'V020',
|
|
1286
|
+
)
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
readStore()
|
|
1290
|
+
const slot = createStateSlot(scope[1], sourceMask, currentGetSnapshot(), stateWriteGuard(scope))
|
|
1291
|
+
let active = false
|
|
1292
|
+
let unsubscribe = noop
|
|
1293
|
+
const checkSnapshot = (): void => {
|
|
1294
|
+
if (!active) return
|
|
1295
|
+
const nextSnapshot = currentGetSnapshot()
|
|
1296
|
+
scope[2](() => slot.replace(nextSnapshot))
|
|
1297
|
+
}
|
|
1298
|
+
const activateStore = (): void => {
|
|
1299
|
+
readStore()
|
|
1300
|
+
active = true
|
|
1301
|
+
try {
|
|
1302
|
+
const remove = currentSubscribe(() => runOwnerTask(owner, checkSnapshot))
|
|
1303
|
+
if (typeof remove !== 'function') {
|
|
1304
|
+
throw new TypeError(
|
|
1305
|
+
DEV ? 'external store subscribe must return an unsubscribe function' : 'V020',
|
|
1306
|
+
)
|
|
1307
|
+
}
|
|
1308
|
+
unsubscribe = remove
|
|
1309
|
+
checkSnapshot()
|
|
1310
|
+
} catch (error) {
|
|
1311
|
+
active = false
|
|
1312
|
+
unsubscribe()
|
|
1313
|
+
unsubscribe = noop
|
|
1314
|
+
throw error
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
const deactivateStore = (): void => {
|
|
1318
|
+
active = false
|
|
1319
|
+
unsubscribe()
|
|
1320
|
+
unsubscribe = noop
|
|
1321
|
+
}
|
|
1322
|
+
const reconnect = (): void => {
|
|
1323
|
+
if (!active) {
|
|
1324
|
+
readStore()
|
|
1325
|
+
return
|
|
1326
|
+
}
|
|
1327
|
+
deactivateStore()
|
|
1328
|
+
activateStore()
|
|
1329
|
+
}
|
|
1330
|
+
const removeStoreUpdater =
|
|
1331
|
+
reactiveStore === undefined ? noop : subscribeBinding(reactiveStore, reconnect)
|
|
1332
|
+
|
|
1333
|
+
if (!retainedUiEnabled) {
|
|
1334
|
+
try {
|
|
1335
|
+
activateStore()
|
|
1336
|
+
} catch (error) {
|
|
1337
|
+
removeStoreUpdater()
|
|
1338
|
+
throw error
|
|
1339
|
+
}
|
|
1340
|
+
owner[1].add(() => {
|
|
1341
|
+
removeStoreUpdater()
|
|
1342
|
+
deactivateStore()
|
|
1343
|
+
})
|
|
1344
|
+
return slot
|
|
1345
|
+
}
|
|
1346
|
+
const [activateResource, disposeResource] = createRetainedResource(
|
|
1347
|
+
owner,
|
|
1348
|
+
activateStore,
|
|
1349
|
+
deactivateStore,
|
|
1350
|
+
0,
|
|
1351
|
+
)
|
|
1352
|
+
try {
|
|
1353
|
+
activateResource()
|
|
1354
|
+
} catch (error) {
|
|
1355
|
+
disposeResource()
|
|
1356
|
+
throw error
|
|
1357
|
+
}
|
|
1358
|
+
owner[1].add(() => {
|
|
1359
|
+
removeStoreUpdater()
|
|
1360
|
+
disposeResource()
|
|
1361
|
+
})
|
|
1362
|
+
return slot
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
export function createCompiledEffectEvent<Arguments extends unknown[], Result>(
|
|
1366
|
+
scope: CompiledScope,
|
|
1367
|
+
callback: (...arguments_: Arguments) => Result,
|
|
1368
|
+
): (...arguments_: Arguments) => Result {
|
|
1369
|
+
const owner = scopeOwners.get(scope)
|
|
1370
|
+
if (owner === undefined) {
|
|
1371
|
+
throw new Error(DEV ? 'createCompiledEffectEvent received an unknown scope' : 'V003')
|
|
1372
|
+
}
|
|
1373
|
+
return (...arguments_) => {
|
|
1374
|
+
if (owner[0]) throw new Error(DEV ? 'cannot call an effect event after disposal' : 'V022')
|
|
1375
|
+
return callback(...arguments_)
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
export function createCompiledId(scope: CompiledScope): string {
|
|
1380
|
+
const owner = scopeOwners.get(scope)
|
|
1381
|
+
if (owner === undefined) {
|
|
1382
|
+
throw new Error(DEV ? 'createCompiledId received an unknown scope' : 'V003')
|
|
1383
|
+
}
|
|
1384
|
+
const id = `:${owner[3].prefix}r${owner[3].nextId}:`
|
|
1385
|
+
owner[3].nextId += 1
|
|
1386
|
+
return id
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
export function useContext<T>(context: CompiledContext<T>): T {
|
|
1390
|
+
if (activeConstructionOwner === null) {
|
|
1391
|
+
throw new Error(DEV ? 'useContext must run during compiled component construction' : 'V019')
|
|
1392
|
+
}
|
|
1393
|
+
const input = contextInputFromFrame(activeConstructionOwner[2], context)
|
|
1394
|
+
return isCompiledBinding(input) ? (input[1]() as T) : (input as T)
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
export function use<T>(context: CompiledContext<T>): T {
|
|
1398
|
+
return useContext(context)
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
export function createCompiledAsync<Value>(
|
|
1402
|
+
scope: CompiledScope,
|
|
1403
|
+
reads: SourceMask,
|
|
1404
|
+
writes: SourceMask,
|
|
1405
|
+
evaluate: () => CompiledContext<Value> | AsyncResource<Value> | PromiseLike<Value>,
|
|
1406
|
+
): StateSlot<Value> {
|
|
1407
|
+
const input = evaluate()
|
|
1408
|
+
if (isCompiledContext<Value>(input)) return createCompiledContext(scope, writes, input)
|
|
1409
|
+
const slot = createCompiledState(scope, writes, useAsync(input))
|
|
1410
|
+
scope[0](reads, () => slot.replace(useAsync(evaluate())))
|
|
1411
|
+
return slot
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
export function Suspense(props: {
|
|
1415
|
+
readonly children?: (() => CompiledRenderValue) | readonly [() => CompiledRenderValue] | undefined
|
|
1416
|
+
readonly fallback: () => CompiledRenderValue
|
|
1417
|
+
}): StructuralBinding {
|
|
1418
|
+
const render = Array.isArray(props.children) ? props.children[0] : props.children
|
|
1419
|
+
if (typeof render !== 'function' || typeof props.fallback !== 'function') {
|
|
1420
|
+
throw new TypeError(
|
|
1421
|
+
DEV
|
|
1422
|
+
? `Suspense children and fallback must be compiler-generated render functions; received ${typeof render} and ${typeof props.fallback}`
|
|
1423
|
+
: 'V031',
|
|
1424
|
+
)
|
|
1425
|
+
}
|
|
1426
|
+
return suspense(render, props.fallback)
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
export function useSyncExternalStore<T>(
|
|
1430
|
+
_subscribe: (onStoreChange: () => void) => () => void,
|
|
1431
|
+
_getSnapshot: () => T,
|
|
1432
|
+
_getServerSnapshot?: () => T,
|
|
1433
|
+
): T {
|
|
1434
|
+
throw new Error(DEV ? 'useSyncExternalStore requires compiler lowering' : 'V021')
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
export function useEffectEvent<Arguments extends unknown[], Result>(
|
|
1438
|
+
callback: (...arguments_: Arguments) => Result,
|
|
1439
|
+
): (...arguments_: Arguments) => Result {
|
|
1440
|
+
return callback
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
export function useId(): string {
|
|
1444
|
+
if (activeConstructionOwner === null) {
|
|
1445
|
+
throw new Error(DEV ? 'useId must run during compiled component construction' : 'V023')
|
|
1446
|
+
}
|
|
1447
|
+
const id = `:${activeConstructionOwner[3].prefix}r${activeConstructionOwner[3].nextId}:`
|
|
1448
|
+
activeConstructionOwner[3].nextId += 1
|
|
1449
|
+
return id
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
export function useMemo<T>(factory: () => T, _dependencies: readonly unknown[]): T {
|
|
1453
|
+
return factory()
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
export function useCallback<T extends Function>(callback: T, _dependencies: readonly unknown[]): T {
|
|
1457
|
+
return callback
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
export function createCompiledProp<T>(
|
|
1461
|
+
scope: CompiledScope,
|
|
1462
|
+
sourceMask: SourceMask,
|
|
1463
|
+
input: T | CompiledBinding<T>,
|
|
1464
|
+
fallback?: () => T,
|
|
1465
|
+
): StateSlot<T> {
|
|
1466
|
+
const assertWritable = stateWriteGuard(scope)
|
|
1467
|
+
const upstream = isCompiledBinding(input) ? input : undefined
|
|
1468
|
+
const read = (): T => {
|
|
1469
|
+
const value = upstream === undefined ? (input as T) : upstream[1]()
|
|
1470
|
+
return value === undefined && fallback !== undefined ? fallback() : value
|
|
1471
|
+
}
|
|
1472
|
+
const slot = createStateSlot<T>(scope[1], sourceMask, read(), assertWritable)
|
|
1473
|
+
if (upstream !== undefined) {
|
|
1474
|
+
const remove = subscribeBinding(upstream, () => slot.replace(read()))
|
|
1475
|
+
const owner = scopeOwners.get(scope)
|
|
1476
|
+
if (owner === undefined) {
|
|
1477
|
+
throw new Error(DEV ? 'createCompiledProp received an unknown scope' : 'V003')
|
|
1478
|
+
}
|
|
1479
|
+
owner[1].add(remove)
|
|
1480
|
+
}
|
|
1481
|
+
return slot
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
export function createCompiledRestProp(
|
|
1485
|
+
scope: CompiledScope,
|
|
1486
|
+
sourceMask: SourceMask,
|
|
1487
|
+
input: Record<string, unknown>,
|
|
1488
|
+
excludedNames: readonly string[],
|
|
1489
|
+
): StateSlot<Record<string, unknown>> {
|
|
1490
|
+
const excluded = new Set(excludedNames)
|
|
1491
|
+
const entries = (): Array<[string, unknown]> =>
|
|
1492
|
+
Object.entries(input).filter(([name]) => !excluded.has(name))
|
|
1493
|
+
const read = (): Record<string, unknown> =>
|
|
1494
|
+
Object.fromEntries(
|
|
1495
|
+
entries().map(([name, value]) => [name, isCompiledBinding(value) ? value[1]() : value]),
|
|
1496
|
+
)
|
|
1497
|
+
const slot = createStateSlot(scope[1], sourceMask, read(), stateWriteGuard(scope))
|
|
1498
|
+
const publish = (): void => {
|
|
1499
|
+
const previous = slot.get()
|
|
1500
|
+
const next = read()
|
|
1501
|
+
const names = Object.keys(next)
|
|
1502
|
+
if (
|
|
1503
|
+
names.length === Object.keys(previous).length &&
|
|
1504
|
+
names.every((name) => Object.hasOwn(previous, name) && Object.is(previous[name], next[name]))
|
|
1505
|
+
) {
|
|
1506
|
+
return
|
|
1507
|
+
}
|
|
1508
|
+
slot.replace(next)
|
|
1509
|
+
}
|
|
1510
|
+
const componentSpreadSource = (input as Record<PropertyKey, unknown>)[COMPONENT_SPREAD_SOURCE]
|
|
1511
|
+
const upstreams = new Set(
|
|
1512
|
+
entries()
|
|
1513
|
+
.map(([, value]) => value)
|
|
1514
|
+
.filter(isCompiledBinding),
|
|
1515
|
+
)
|
|
1516
|
+
if (isCompiledBinding(componentSpreadSource)) upstreams.add(componentSpreadSource)
|
|
1517
|
+
const removers = [...upstreams].map((upstream) => subscribeBinding(upstream, publish))
|
|
1518
|
+
const owner = scopeOwners.get(scope)
|
|
1519
|
+
if (owner === undefined) {
|
|
1520
|
+
throw new Error(DEV ? 'createCompiledRestProp received an unknown scope' : 'V003')
|
|
1521
|
+
}
|
|
1522
|
+
owner[1].add(() => {
|
|
1523
|
+
for (const remove of removers) remove()
|
|
1524
|
+
})
|
|
1525
|
+
return slot
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
/** @internal */
|
|
1529
|
+
export function objectRest(
|
|
1530
|
+
input: Record<PropertyKey, unknown>,
|
|
1531
|
+
excludedNames: readonly PropertyKey[],
|
|
1532
|
+
): Record<PropertyKey, unknown> {
|
|
1533
|
+
const excluded = new Set(excludedNames)
|
|
1534
|
+
const output: Record<PropertyKey, unknown> = {}
|
|
1535
|
+
for (const key of Reflect.ownKeys(input)) {
|
|
1536
|
+
if (excluded.has(key) || !Object.getOwnPropertyDescriptor(input, key)?.enumerable) continue
|
|
1537
|
+
Object.defineProperty(output, key, {
|
|
1538
|
+
configurable: true,
|
|
1539
|
+
enumerable: true,
|
|
1540
|
+
value: input[key],
|
|
1541
|
+
writable: true,
|
|
1542
|
+
})
|
|
1543
|
+
}
|
|
1544
|
+
return output
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
function stateWriteGuard(scope: CompiledScope): () => void {
|
|
1548
|
+
const owner = scopeOwners.get(scope)
|
|
1549
|
+
if (owner === undefined) {
|
|
1550
|
+
throw new Error(DEV ? 'compiled state received an unknown scope' : 'V003')
|
|
1551
|
+
}
|
|
1552
|
+
return () => {
|
|
1553
|
+
if (owner[0] && !disposalCascadeOwners.has(owner)) {
|
|
1554
|
+
throw new Error(DEV ? 'cannot update state after disposal' : 'V012')
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
export function binding<T>(
|
|
1560
|
+
scope: CompiledScope,
|
|
1561
|
+
reads: SourceMask,
|
|
1562
|
+
evaluate: () => T,
|
|
1563
|
+
additionalScope?: CompiledScope,
|
|
1564
|
+
additionalReads?: SourceMask,
|
|
1565
|
+
): CompiledBinding<T> {
|
|
1566
|
+
return [BINDING, evaluate, scope, reads, additionalScope, additionalReads]
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
export function compiledEvent<Arguments extends unknown[]>(
|
|
1570
|
+
scope: CompiledScope,
|
|
1571
|
+
handler:
|
|
1572
|
+
| ((...arguments_: Arguments) => void)
|
|
1573
|
+
| CompiledBinding<((...arguments_: Arguments) => void) | null | undefined>
|
|
1574
|
+
| null
|
|
1575
|
+
| undefined,
|
|
1576
|
+
): (...arguments_: Arguments) => void {
|
|
1577
|
+
const owner = scopeOwners.get(scope)
|
|
1578
|
+
if (owner === undefined) {
|
|
1579
|
+
throw new Error(DEV ? 'compiledEvent received an unknown scope' : 'V003')
|
|
1580
|
+
}
|
|
1581
|
+
const errorOwner = activeOwner ?? owner
|
|
1582
|
+
return (...arguments_) =>
|
|
1583
|
+
runOwnerTask(errorOwner, () =>
|
|
1584
|
+
withOwner(errorOwner, () =>
|
|
1585
|
+
scope[2](() => {
|
|
1586
|
+
const current = isCompiledBinding(handler) ? handler[1]() : handler
|
|
1587
|
+
if (typeof current !== 'function') return
|
|
1588
|
+
return current(...arguments_)
|
|
1589
|
+
}),
|
|
1590
|
+
),
|
|
1591
|
+
)
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
export interface CompiledTaskController {
|
|
1595
|
+
readonly disposed: () => boolean
|
|
1596
|
+
readonly report: (error: unknown) => void
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
export function captureCompiledTask(scope?: CompiledScope): CompiledTaskController {
|
|
1600
|
+
const owner = scope === undefined ? activeOwner : scopeOwners.get(scope)
|
|
1601
|
+
if (owner === null || owner === undefined) {
|
|
1602
|
+
throw new Error(DEV ? 'compiled task must be captured during owned construction' : 'V034')
|
|
1603
|
+
}
|
|
1604
|
+
return {
|
|
1605
|
+
disposed: () => owner[0],
|
|
1606
|
+
report: (error) =>
|
|
1607
|
+
runOwnerTask(owner, () => {
|
|
1608
|
+
throw error
|
|
1609
|
+
}),
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
export function when(
|
|
1614
|
+
scope: CompiledScope,
|
|
1615
|
+
reads: SourceMask,
|
|
1616
|
+
condition: () => unknown,
|
|
1617
|
+
render: () => RenderValue,
|
|
1618
|
+
additionalScope?: CompiledScope,
|
|
1619
|
+
additionalReads?: SourceMask,
|
|
1620
|
+
): StructuralBinding {
|
|
1621
|
+
return structural(
|
|
1622
|
+
scope,
|
|
1623
|
+
(parent, before) => {
|
|
1624
|
+
const [start, end, hydrated] = structuralRange(parent, before, 'when')
|
|
1625
|
+
let branchOwner: Owner | null = null
|
|
1626
|
+
let mounted = false
|
|
1627
|
+
let hydrationPending = hydrated
|
|
1628
|
+
|
|
1629
|
+
const update = (): void => {
|
|
1630
|
+
const next = Boolean(condition())
|
|
1631
|
+
if (hydrationPending && !next) {
|
|
1632
|
+
claimHydrationText(parent, '')
|
|
1633
|
+
hydrationPending = false
|
|
1634
|
+
return
|
|
1635
|
+
}
|
|
1636
|
+
if (next === mounted) return
|
|
1637
|
+
const currentParent = rangeParent(start, end, 'conditional block')
|
|
1638
|
+
if (!next) {
|
|
1639
|
+
const owner = branchOwner
|
|
1640
|
+
branchOwner = null
|
|
1641
|
+
mounted = false
|
|
1642
|
+
disposeRange(owner, start, end)
|
|
1643
|
+
return
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
const nextOwner = createOwner()
|
|
1647
|
+
const [fragment, staged] = hydrationPending
|
|
1648
|
+
? withHydrationInsertion(currentParent, end, () => stageRender(render, nextOwner))
|
|
1649
|
+
: stageRender(render, nextOwner)
|
|
1650
|
+
hydrationPending = false
|
|
1651
|
+
currentParent.insertBefore(fragment, end)
|
|
1652
|
+
commitPublishedNodes(staged)
|
|
1653
|
+
branchOwner = nextOwner
|
|
1654
|
+
mounted = true
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
const removeUpdater = subscribe(scope, reads, update, additionalScope, additionalReads)
|
|
1658
|
+
try {
|
|
1659
|
+
update()
|
|
1660
|
+
} catch (error) {
|
|
1661
|
+
removeUpdater()
|
|
1662
|
+
throw error
|
|
1663
|
+
}
|
|
1664
|
+
onCleanup(() => {
|
|
1665
|
+
removeUpdater()
|
|
1666
|
+
try {
|
|
1667
|
+
disposeRange(branchOwner, start, end)
|
|
1668
|
+
} finally {
|
|
1669
|
+
start.remove()
|
|
1670
|
+
end.remove()
|
|
1671
|
+
}
|
|
1672
|
+
})
|
|
1673
|
+
},
|
|
1674
|
+
'slot',
|
|
1675
|
+
)
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
export type ChoiceMode = 'truthy' | 'not-nullish'
|
|
1679
|
+
|
|
1680
|
+
export function choose(
|
|
1681
|
+
scope: CompiledScope,
|
|
1682
|
+
reads: SourceMask,
|
|
1683
|
+
mode: ChoiceMode,
|
|
1684
|
+
select: () => unknown,
|
|
1685
|
+
consequent: () => CompiledRenderValue,
|
|
1686
|
+
alternate: () => CompiledRenderValue,
|
|
1687
|
+
additionalScope?: CompiledScope,
|
|
1688
|
+
additionalReads?: SourceMask,
|
|
1689
|
+
): StructuralBinding {
|
|
1690
|
+
return structural(
|
|
1691
|
+
scope,
|
|
1692
|
+
(parent, before) => {
|
|
1693
|
+
const [start, end, hydrated] = structuralRange(parent, before, 'choice')
|
|
1694
|
+
let selected = -1
|
|
1695
|
+
let branchOwner: Owner | null = null
|
|
1696
|
+
let branchNodes: readonly Node[] = []
|
|
1697
|
+
|
|
1698
|
+
const update = (): void => {
|
|
1699
|
+
const value = select()
|
|
1700
|
+
const next =
|
|
1701
|
+
mode === 'not-nullish' ? (value === null || value === undefined ? 1 : 0) : value ? 0 : 1
|
|
1702
|
+
if (next === selected) return
|
|
1703
|
+
const currentParent = rangeParent(start, end, 'choice block')
|
|
1704
|
+
const nextOwner = createOwner()
|
|
1705
|
+
const [fragment, nodes] =
|
|
1706
|
+
hydrated && selected === -1
|
|
1707
|
+
? withHydrationInsertion(currentParent, end, () =>
|
|
1708
|
+
stageRender(next === 0 ? consequent : alternate, nextOwner),
|
|
1709
|
+
)
|
|
1710
|
+
: stageRender(next === 0 ? consequent : alternate, nextOwner)
|
|
1711
|
+
currentParent.insertBefore(fragment, end)
|
|
1712
|
+
try {
|
|
1713
|
+
commitPublishedNodes(nodes)
|
|
1714
|
+
} catch (error) {
|
|
1715
|
+
disposePublished(nextOwner, nodes)
|
|
1716
|
+
throw error
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
const previousOwner = branchOwner
|
|
1720
|
+
const previousNodes = branchNodes
|
|
1721
|
+
branchOwner = nextOwner
|
|
1722
|
+
branchNodes = nodes
|
|
1723
|
+
selected = next
|
|
1724
|
+
disposePublished(previousOwner, previousNodes)
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
const removeUpdater = subscribe(scope, reads, update, additionalScope, additionalReads)
|
|
1728
|
+
try {
|
|
1729
|
+
update()
|
|
1730
|
+
} catch (error) {
|
|
1731
|
+
removeUpdater()
|
|
1732
|
+
throw error
|
|
1733
|
+
}
|
|
1734
|
+
onCleanup(() => {
|
|
1735
|
+
removeUpdater()
|
|
1736
|
+
try {
|
|
1737
|
+
disposePublished(branchOwner, branchNodes)
|
|
1738
|
+
} finally {
|
|
1739
|
+
start.remove()
|
|
1740
|
+
end.remove()
|
|
1741
|
+
}
|
|
1742
|
+
})
|
|
1743
|
+
},
|
|
1744
|
+
'slot',
|
|
1745
|
+
)
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
export function dispatch(
|
|
1749
|
+
scope: CompiledScope,
|
|
1750
|
+
reads: SourceMask,
|
|
1751
|
+
type: () => unknown,
|
|
1752
|
+
key: () => unknown,
|
|
1753
|
+
render: () => CompiledRenderValue,
|
|
1754
|
+
additionalScope?: CompiledScope,
|
|
1755
|
+
additionalReads?: SourceMask,
|
|
1756
|
+
): StructuralBinding {
|
|
1757
|
+
return structural(
|
|
1758
|
+
scope,
|
|
1759
|
+
(parent, before) => {
|
|
1760
|
+
const [start, end, hydrated] = structuralRange(parent, before, 'dispatch')
|
|
1761
|
+
const unset = Symbol('Vidact.UnsetIdentity')
|
|
1762
|
+
let currentType: unknown = unset
|
|
1763
|
+
let currentKey: unknown = unset
|
|
1764
|
+
let currentOwner: Owner | null = null
|
|
1765
|
+
let currentNodes: readonly Node[] = []
|
|
1766
|
+
|
|
1767
|
+
const update = (): void => {
|
|
1768
|
+
const nextType = type()
|
|
1769
|
+
const nextKey = key()
|
|
1770
|
+
if (
|
|
1771
|
+
currentType !== unset &&
|
|
1772
|
+
Object.is(nextType, currentType) &&
|
|
1773
|
+
Object.is(nextKey, currentKey)
|
|
1774
|
+
) {
|
|
1775
|
+
return
|
|
1776
|
+
}
|
|
1777
|
+
const currentParent = rangeParent(start, end, 'dispatch block')
|
|
1778
|
+
const nextOwner = createOwner()
|
|
1779
|
+
const [fragment, nodes] =
|
|
1780
|
+
hydrated && currentType === unset
|
|
1781
|
+
? withHydrationInsertion(currentParent, end, () => stageRender(render, nextOwner))
|
|
1782
|
+
: stageRender(render, nextOwner)
|
|
1783
|
+
currentParent.insertBefore(fragment, end)
|
|
1784
|
+
try {
|
|
1785
|
+
commitPublishedNodes(nodes)
|
|
1786
|
+
} catch (error) {
|
|
1787
|
+
disposePublished(nextOwner, nodes)
|
|
1788
|
+
throw error
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
const previousOwner = currentOwner
|
|
1792
|
+
const previousNodes = currentNodes
|
|
1793
|
+
currentType = nextType
|
|
1794
|
+
currentKey = nextKey
|
|
1795
|
+
currentOwner = nextOwner
|
|
1796
|
+
currentNodes = nodes
|
|
1797
|
+
disposePublished(previousOwner, previousNodes)
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
const removeUpdater = subscribe(scope, reads, update, additionalScope, additionalReads)
|
|
1801
|
+
try {
|
|
1802
|
+
update()
|
|
1803
|
+
} catch (error) {
|
|
1804
|
+
removeUpdater()
|
|
1805
|
+
throw error
|
|
1806
|
+
}
|
|
1807
|
+
onCleanup(() => {
|
|
1808
|
+
removeUpdater()
|
|
1809
|
+
try {
|
|
1810
|
+
disposePublished(currentOwner, currentNodes)
|
|
1811
|
+
} finally {
|
|
1812
|
+
start.remove()
|
|
1813
|
+
end.remove()
|
|
1814
|
+
}
|
|
1815
|
+
})
|
|
1816
|
+
},
|
|
1817
|
+
'slot',
|
|
1818
|
+
)
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
export function keyed<T, K>(
|
|
1822
|
+
scope: CompiledScope,
|
|
1823
|
+
reads: SourceMask,
|
|
1824
|
+
values: () => readonly T[],
|
|
1825
|
+
key: (value: T, index: number) => K,
|
|
1826
|
+
render: (value: StateSlot<T>, index: StateSlot<number>, itemScope: CompiledScope) => RenderValue,
|
|
1827
|
+
additionalScope?: CompiledScope,
|
|
1828
|
+
additionalReads?: SourceMask,
|
|
1829
|
+
): StructuralBinding {
|
|
1830
|
+
return structural(
|
|
1831
|
+
scope,
|
|
1832
|
+
(parent, before) => {
|
|
1833
|
+
const itemSource = 1
|
|
1834
|
+
const indexSource = 2
|
|
1835
|
+
const list = createKeyedList(
|
|
1836
|
+
parent,
|
|
1837
|
+
{
|
|
1838
|
+
key,
|
|
1839
|
+
render(value, index) {
|
|
1840
|
+
const owner = createOwner()
|
|
1841
|
+
const itemScope = createNarrowCompiledScope()
|
|
1842
|
+
const valueSlot = createCompiledState(itemScope, itemSource, value)
|
|
1843
|
+
const indexSlot = createCompiledState(itemScope, indexSource, index)
|
|
1844
|
+
try {
|
|
1845
|
+
const nodes = withOwner(owner, () => {
|
|
1846
|
+
onCleanup(itemScope[3])
|
|
1847
|
+
return materialize(render(valueSlot, indexSlot, itemScope))
|
|
1848
|
+
})
|
|
1849
|
+
return [
|
|
1850
|
+
nodes,
|
|
1851
|
+
(nextValue: T, nextIndex: number) => {
|
|
1852
|
+
itemScope[2](() => {
|
|
1853
|
+
valueSlot.set(nextValue)
|
|
1854
|
+
indexSlot.set(nextIndex)
|
|
1855
|
+
})
|
|
1856
|
+
},
|
|
1857
|
+
() => disposeOwner(owner),
|
|
1858
|
+
] as const
|
|
1859
|
+
} catch (error) {
|
|
1860
|
+
disposeOwner(owner)
|
|
1861
|
+
throw error
|
|
1862
|
+
}
|
|
1863
|
+
},
|
|
1864
|
+
},
|
|
1865
|
+
before,
|
|
1866
|
+
)
|
|
1867
|
+
const update = (): void => {
|
|
1868
|
+
try {
|
|
1869
|
+
commitPublishedNodes(list.update(values()))
|
|
1870
|
+
} catch (error) {
|
|
1871
|
+
const currentParent = list.parent()
|
|
1872
|
+
if (currentParent !== null) commitPendingRefs(currentParent)
|
|
1873
|
+
throw error
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
update()
|
|
1877
|
+
const removeUpdater = subscribe(scope, reads, update, additionalScope, additionalReads)
|
|
1878
|
+
onCleanup(() => {
|
|
1879
|
+
removeUpdater()
|
|
1880
|
+
list.dispose()
|
|
1881
|
+
})
|
|
1882
|
+
},
|
|
1883
|
+
'array',
|
|
1884
|
+
)
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
export function indexed<T>(
|
|
1888
|
+
scope: CompiledScope,
|
|
1889
|
+
reads: SourceMask,
|
|
1890
|
+
values: () => readonly T[],
|
|
1891
|
+
render: (value: StateSlot<T>, index: StateSlot<number>, itemScope: CompiledScope) => RenderValue,
|
|
1892
|
+
additionalScope?: CompiledScope,
|
|
1893
|
+
additionalReads?: SourceMask,
|
|
1894
|
+
): StructuralBinding {
|
|
1895
|
+
return structural(
|
|
1896
|
+
scope,
|
|
1897
|
+
(parent, before) => {
|
|
1898
|
+
const itemSource = 1
|
|
1899
|
+
const indexSource = 2
|
|
1900
|
+
const list = createIndexedList<T>(
|
|
1901
|
+
parent,
|
|
1902
|
+
{
|
|
1903
|
+
render(value, index) {
|
|
1904
|
+
const owner = createOwner()
|
|
1905
|
+
const itemScope = createNarrowCompiledScope()
|
|
1906
|
+
const valueSlot = createCompiledState(itemScope, itemSource, value)
|
|
1907
|
+
const indexSlot = createCompiledState(itemScope, indexSource, index)
|
|
1908
|
+
try {
|
|
1909
|
+
const nodes = withOwner(owner, () => {
|
|
1910
|
+
onCleanup(itemScope[3])
|
|
1911
|
+
return materialize(render(valueSlot, indexSlot, itemScope))
|
|
1912
|
+
})
|
|
1913
|
+
return [
|
|
1914
|
+
nodes,
|
|
1915
|
+
(nextValue: T, nextIndex: number) => {
|
|
1916
|
+
itemScope[2](() => {
|
|
1917
|
+
valueSlot.set(nextValue)
|
|
1918
|
+
indexSlot.set(nextIndex)
|
|
1919
|
+
})
|
|
1920
|
+
},
|
|
1921
|
+
() => disposeOwner(owner),
|
|
1922
|
+
] as const
|
|
1923
|
+
} catch (error) {
|
|
1924
|
+
disposeOwner(owner)
|
|
1925
|
+
throw error
|
|
1926
|
+
}
|
|
1927
|
+
},
|
|
1928
|
+
},
|
|
1929
|
+
before,
|
|
1930
|
+
)
|
|
1931
|
+
const update = (): void => {
|
|
1932
|
+
try {
|
|
1933
|
+
commitPublishedNodes(list.update(values()))
|
|
1934
|
+
} catch (error) {
|
|
1935
|
+
const currentParent = list.parent()
|
|
1936
|
+
if (currentParent !== null) commitPendingRefs(currentParent)
|
|
1937
|
+
throw error
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
update()
|
|
1941
|
+
const removeUpdater = subscribe(scope, reads, update, additionalScope, additionalReads)
|
|
1942
|
+
onCleanup(() => {
|
|
1943
|
+
removeUpdater()
|
|
1944
|
+
list.dispose()
|
|
1945
|
+
})
|
|
1946
|
+
},
|
|
1947
|
+
'array',
|
|
1948
|
+
)
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
export function compiledRoot(
|
|
1952
|
+
scope: CompiledScope,
|
|
1953
|
+
render: () => CompiledRenderValue,
|
|
1954
|
+
): CompiledComponentResult {
|
|
1955
|
+
const owner = scopeOwners.get(scope)
|
|
1956
|
+
if (owner === undefined) {
|
|
1957
|
+
throw new Error(DEV ? 'compiledRoot received an unknown scope' : 'V004')
|
|
1958
|
+
}
|
|
1959
|
+
const hydratedRange = claimHydrationComponentRange()
|
|
1960
|
+
const start = hydratedRange?.[0] ?? document.createComment(DEV ? 'vidact:component' : '')
|
|
1961
|
+
const end = hydratedRange?.[1] ?? document.createComment(DEV ? '/vidact:component' : '')
|
|
1962
|
+
componentCommitOwners.set(end, owner)
|
|
1963
|
+
const fragment = document.createDocumentFragment()
|
|
1964
|
+
if (hydratedRange === undefined) fragment.append(start, end)
|
|
1965
|
+
owner[1].add(() => {
|
|
1966
|
+
try {
|
|
1967
|
+
removeBetween(start, end)
|
|
1968
|
+
} finally {
|
|
1969
|
+
start.remove()
|
|
1970
|
+
end.remove()
|
|
1971
|
+
}
|
|
1972
|
+
})
|
|
1973
|
+
|
|
1974
|
+
try {
|
|
1975
|
+
const renderParent = hydrationRangeParent(start, end) ?? fragment
|
|
1976
|
+
const insertRender = () =>
|
|
1977
|
+
withOwner(owner, () =>
|
|
1978
|
+
withScopeNamespace(scope, () => insertValue(renderParent, render(), end)),
|
|
1979
|
+
)
|
|
1980
|
+
const insert = () => {
|
|
1981
|
+
if (hydratedRange === undefined) insertRender()
|
|
1982
|
+
else
|
|
1983
|
+
withHydrationComponentRange(hydratedRange, () =>
|
|
1984
|
+
withHydrationCursor(renderParent, start.nextSibling, insertRender),
|
|
1985
|
+
)
|
|
1986
|
+
}
|
|
1987
|
+
if (profilingEnabled) measureProfileWork(owner, 'range', insert)
|
|
1988
|
+
else insert()
|
|
1989
|
+
} catch (error) {
|
|
1990
|
+
try {
|
|
1991
|
+
scope[3]()
|
|
1992
|
+
} catch {
|
|
1993
|
+
// Preserve the render error that made this component unmountable.
|
|
1994
|
+
}
|
|
1995
|
+
throw error
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
let mounted = false
|
|
1999
|
+
const component: CompiledComponentResult = [
|
|
2000
|
+
STRUCTURAL,
|
|
2001
|
+
(parent, before) => {
|
|
2002
|
+
if (mounted) throw new Error(DEV ? 'compiled component is already mounted' : 'V005')
|
|
2003
|
+
mounted = true
|
|
2004
|
+
adoptCompiledRoot(component)
|
|
2005
|
+
try {
|
|
2006
|
+
if (!claimHydrationComponentMount(parent, start, end)) {
|
|
2007
|
+
parent.insertBefore(fragment, before)
|
|
2008
|
+
}
|
|
2009
|
+
} catch (error) {
|
|
2010
|
+
try {
|
|
2011
|
+
scope[3]()
|
|
2012
|
+
} catch {
|
|
2013
|
+
// Preserve the insertion error that made this component unmountable.
|
|
2014
|
+
}
|
|
2015
|
+
throw error
|
|
2016
|
+
}
|
|
2017
|
+
},
|
|
2018
|
+
]
|
|
2019
|
+
componentRanges.set(component, [start, end, scope])
|
|
2020
|
+
return component
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
export function adoptCompiledRoot(root: unknown): void {
|
|
2024
|
+
if (!isCompiledComponentResult(root)) return
|
|
2025
|
+
const range = componentRanges.get(root)
|
|
2026
|
+
if (range === undefined) return
|
|
2027
|
+
const owner = scopeOwners.get(range[2])
|
|
2028
|
+
if (activeOwner !== null && owner !== undefined && activeOwner !== owner) {
|
|
2029
|
+
activeOwner[1].add(range[2][3])
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
export function constructCompiledComponent<T extends CompiledRenderValue>(
|
|
2034
|
+
component: () => T,
|
|
2035
|
+
componentType?: unknown,
|
|
2036
|
+
): T {
|
|
2037
|
+
const previousCollector = activeScopeCollector
|
|
2038
|
+
const previousConstructionOwner = activeConstructionOwner
|
|
2039
|
+
const previousProfileName = activeProfileName
|
|
2040
|
+
const scopes = new Set<CompiledScope>()
|
|
2041
|
+
activeScopeCollector = scopes
|
|
2042
|
+
activeConstructionOwner = null
|
|
2043
|
+
const profileType = componentType as
|
|
2044
|
+
| { readonly name?: string; readonly displayName?: string }
|
|
2045
|
+
| undefined
|
|
2046
|
+
if (DEV) activeProfileName = profileType?.displayName ?? profileType?.name ?? 'Anonymous'
|
|
2047
|
+
try {
|
|
2048
|
+
return component()
|
|
2049
|
+
} catch (error) {
|
|
2050
|
+
// oxlint-disable-next-line unicorn/no-array-reverse - scopes is already copied to avoid mutation during iteration
|
|
2051
|
+
for (const scope of [...scopes].reverse()) {
|
|
2052
|
+
try {
|
|
2053
|
+
scope[3]()
|
|
2054
|
+
} catch {
|
|
2055
|
+
// Preserve the construction error that made this component unmountable.
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
throw error
|
|
2059
|
+
} finally {
|
|
2060
|
+
activeScopeCollector = previousCollector
|
|
2061
|
+
activeConstructionOwner = previousConstructionOwner
|
|
2062
|
+
if (DEV) activeProfileName = previousProfileName
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
export function queueElementRef(element: Element, value: unknown): void {
|
|
2067
|
+
if (!isRefValue(value)) {
|
|
2068
|
+
throw new TypeError(DEV ? 'ref must be a callback or an object with current' : 'V006')
|
|
2069
|
+
}
|
|
2070
|
+
pendingRefs.set(element, [activeOwner, value])
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
export function useImperativeHandle<T>(
|
|
2074
|
+
ref: RefValue<T>,
|
|
2075
|
+
create: () => T,
|
|
2076
|
+
dependencies?: readonly unknown[],
|
|
2077
|
+
): void {
|
|
2078
|
+
const owner = activeConstructionOwner
|
|
2079
|
+
if (owner === null) {
|
|
2080
|
+
throw new Error(
|
|
2081
|
+
DEV ? 'useImperativeHandle must run during compiled component construction' : 'V013',
|
|
2082
|
+
)
|
|
2083
|
+
}
|
|
2084
|
+
if (!isRefValue(ref)) {
|
|
2085
|
+
throw new TypeError(
|
|
2086
|
+
DEV ? 'imperative ref must be null, a callback, or an object with current' : 'V006',
|
|
2087
|
+
)
|
|
2088
|
+
}
|
|
2089
|
+
if (dependencies !== undefined && dependencies.length !== 0) {
|
|
2090
|
+
throw new Error(
|
|
2091
|
+
DEV ? 'reactive useImperativeHandle dependencies are not supported yet' : 'V014',
|
|
2092
|
+
)
|
|
2093
|
+
}
|
|
2094
|
+
queueOwnerCommit(owner, () => owner[1].add(attachRef(ref, create())))
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
type EffectResult = void | (() => void)
|
|
2098
|
+
|
|
2099
|
+
export function useLayoutEffect(
|
|
2100
|
+
create: () => EffectResult,
|
|
2101
|
+
dependencies?: readonly unknown[],
|
|
2102
|
+
): void {
|
|
2103
|
+
registerEffect(create, dependencies, false)
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
export function useInsertionEffect(
|
|
2107
|
+
create: () => EffectResult,
|
|
2108
|
+
dependencies?: readonly unknown[],
|
|
2109
|
+
): void {
|
|
2110
|
+
const owner = activeConstructionOwner
|
|
2111
|
+
if (owner === null) {
|
|
2112
|
+
throw new Error(
|
|
2113
|
+
DEV ? 'insertion effects must run during compiled component construction' : 'V016',
|
|
2114
|
+
)
|
|
2115
|
+
}
|
|
2116
|
+
if (dependencies !== undefined && dependencies.length !== 0) {
|
|
2117
|
+
throw new Error(
|
|
2118
|
+
DEV ? 'reactive insertion effect dependencies require compiler lowering' : 'V017',
|
|
2119
|
+
)
|
|
2120
|
+
}
|
|
2121
|
+
const lifetimeOwner = activeOwner ?? owner
|
|
2122
|
+
if (!retainedUiEnabled) {
|
|
2123
|
+
let cleanup = noop
|
|
2124
|
+
queueInsertionCommit(owner, () => {
|
|
2125
|
+
if (lifetimeOwner[0]) return
|
|
2126
|
+
cleanup()
|
|
2127
|
+
cleanup = profilingEnabled
|
|
2128
|
+
? readProfiledEffectCleanup(owner, create)
|
|
2129
|
+
: readEffectCleanup(create())
|
|
2130
|
+
})
|
|
2131
|
+
lifetimeOwner[1].add(() => cleanup())
|
|
2132
|
+
return
|
|
2133
|
+
}
|
|
2134
|
+
let cleanup = noop
|
|
2135
|
+
const [activate, disposeResource] = createRetainedResource(
|
|
2136
|
+
lifetimeOwner,
|
|
2137
|
+
() => {
|
|
2138
|
+
stagePublication([
|
|
2139
|
+
noop,
|
|
2140
|
+
noop,
|
|
2141
|
+
undefined,
|
|
2142
|
+
() => {
|
|
2143
|
+
cleanup = profilingEnabled
|
|
2144
|
+
? readProfiledEffectCleanup(owner, create)
|
|
2145
|
+
: readEffectCleanup(create())
|
|
2146
|
+
},
|
|
2147
|
+
-20,
|
|
2148
|
+
])
|
|
2149
|
+
},
|
|
2150
|
+
() => {
|
|
2151
|
+
cleanup()
|
|
2152
|
+
cleanup = noop
|
|
2153
|
+
},
|
|
2154
|
+
)
|
|
2155
|
+
queueInsertionCommit(owner, () => {
|
|
2156
|
+
if (!lifetimeOwner[0]) activate()
|
|
2157
|
+
})
|
|
2158
|
+
lifetimeOwner[1].add(disposeResource)
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
export function useEffect(create: () => EffectResult, dependencies?: readonly unknown[]): void {
|
|
2162
|
+
registerEffect(create, dependencies, true)
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
function registerEffect(
|
|
2166
|
+
create: () => EffectResult,
|
|
2167
|
+
dependencies: readonly unknown[] | undefined,
|
|
2168
|
+
passive: boolean,
|
|
2169
|
+
): void {
|
|
2170
|
+
const owner = activeConstructionOwner
|
|
2171
|
+
if (owner === null) {
|
|
2172
|
+
throw new Error(DEV ? 'effects must run during compiled component construction' : 'V016')
|
|
2173
|
+
}
|
|
2174
|
+
if (dependencies !== undefined && dependencies.length !== 0) {
|
|
2175
|
+
throw new Error(DEV ? 'reactive effect dependencies require compiler lowering' : 'V017')
|
|
2176
|
+
}
|
|
2177
|
+
const lifetimeOwner = activeOwner ?? owner
|
|
2178
|
+
if (!retainedUiEnabled) {
|
|
2179
|
+
let cleanup = noop
|
|
2180
|
+
let generation = 0
|
|
2181
|
+
const run = (): void => {
|
|
2182
|
+
cleanup()
|
|
2183
|
+
cleanup = profilingEnabled
|
|
2184
|
+
? readProfiledEffectCleanup(owner, create)
|
|
2185
|
+
: readEffectCleanup(create())
|
|
2186
|
+
}
|
|
2187
|
+
queueOwnerCommit(owner, () => {
|
|
2188
|
+
if (lifetimeOwner[0]) return
|
|
2189
|
+
if (passive) {
|
|
2190
|
+
const scheduled = ++generation
|
|
2191
|
+
;(DEV ? scheduleTask : queueMicrotask)(() => {
|
|
2192
|
+
if (!lifetimeOwner[0] && scheduled === generation) runOwnerTask(lifetimeOwner, run)
|
|
2193
|
+
})
|
|
2194
|
+
} else {
|
|
2195
|
+
run()
|
|
2196
|
+
}
|
|
2197
|
+
})
|
|
2198
|
+
lifetimeOwner[1].add(() => {
|
|
2199
|
+
generation += 1
|
|
2200
|
+
if (passive) (DEV ? scheduleTask : queueMicrotask)(() => runOwnerTask(lifetimeOwner, cleanup))
|
|
2201
|
+
else cleanup()
|
|
2202
|
+
})
|
|
2203
|
+
return
|
|
2204
|
+
}
|
|
2205
|
+
let cleanup = noop
|
|
2206
|
+
let generation = 0
|
|
2207
|
+
const [activate, disposeResource] = createRetainedResource(
|
|
2208
|
+
lifetimeOwner,
|
|
2209
|
+
() => {
|
|
2210
|
+
stagePublication([
|
|
2211
|
+
noop,
|
|
2212
|
+
noop,
|
|
2213
|
+
undefined,
|
|
2214
|
+
() => {
|
|
2215
|
+
if (passive) {
|
|
2216
|
+
const scheduled = ++generation
|
|
2217
|
+
;(DEV ? scheduleTask : queueMicrotask)(() => {
|
|
2218
|
+
if (!lifetimeOwner[0] && scheduled === generation) {
|
|
2219
|
+
runOwnerTask(lifetimeOwner, () => {
|
|
2220
|
+
cleanup = profilingEnabled
|
|
2221
|
+
? readProfiledEffectCleanup(owner, create)
|
|
2222
|
+
: readEffectCleanup(create())
|
|
2223
|
+
})
|
|
2224
|
+
}
|
|
2225
|
+
})
|
|
2226
|
+
} else {
|
|
2227
|
+
cleanup = profilingEnabled
|
|
2228
|
+
? readProfiledEffectCleanup(owner, create)
|
|
2229
|
+
: readEffectCleanup(create())
|
|
2230
|
+
}
|
|
2231
|
+
},
|
|
2232
|
+
passive ? 40 : 20,
|
|
2233
|
+
])
|
|
2234
|
+
},
|
|
2235
|
+
() => {
|
|
2236
|
+
generation += 1
|
|
2237
|
+
const previousCleanup = cleanup
|
|
2238
|
+
cleanup = noop
|
|
2239
|
+
if (passive)
|
|
2240
|
+
(DEV ? scheduleTask : queueMicrotask)(() => runOwnerTask(lifetimeOwner, previousCleanup))
|
|
2241
|
+
else previousCleanup()
|
|
2242
|
+
},
|
|
2243
|
+
)
|
|
2244
|
+
queueOwnerCommit(owner, () => {
|
|
2245
|
+
if (!lifetimeOwner[0]) activate()
|
|
2246
|
+
})
|
|
2247
|
+
lifetimeOwner[1].add(disposeResource)
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
export function compiledLayoutEffect(
|
|
2251
|
+
scope: CompiledScope,
|
|
2252
|
+
reads: SourceMask,
|
|
2253
|
+
readCreate: () => () => EffectResult,
|
|
2254
|
+
readDependencies?: () => readonly unknown[],
|
|
2255
|
+
): void {
|
|
2256
|
+
compiledEffect(scope, reads, readCreate, readDependencies, false)
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
export function compiledInsertionEffect(
|
|
2260
|
+
scope: CompiledScope,
|
|
2261
|
+
reads: SourceMask,
|
|
2262
|
+
readCreate: () => () => EffectResult,
|
|
2263
|
+
readDependencies?: () => readonly unknown[],
|
|
2264
|
+
): void {
|
|
2265
|
+
const owner = activeConstructionOwner
|
|
2266
|
+
if (owner === null || scopeOwners.get(scope) !== owner) {
|
|
2267
|
+
throw new Error(DEV ? 'compiled insertion effects must run in their component scope' : 'V016')
|
|
2268
|
+
}
|
|
2269
|
+
const lifetimeOwner = activeOwner ?? owner
|
|
2270
|
+
if (!retainedUiEnabled) {
|
|
2271
|
+
let mounted = false
|
|
2272
|
+
let currentDependencies: readonly unknown[] | undefined
|
|
2273
|
+
let cleanup = noop
|
|
2274
|
+
const run = (): void => {
|
|
2275
|
+
const nextDependencies = readDependencies?.()
|
|
2276
|
+
if (
|
|
2277
|
+
mounted &&
|
|
2278
|
+
readDependencies !== undefined &&
|
|
2279
|
+
equalDependencies(nextDependencies, currentDependencies)
|
|
2280
|
+
) {
|
|
2281
|
+
return
|
|
2282
|
+
}
|
|
2283
|
+
cleanup()
|
|
2284
|
+
cleanup = profilingEnabled
|
|
2285
|
+
? readProfiledEffectCleanup(owner, () => readCreate()())
|
|
2286
|
+
: readEffectCleanup(readCreate()())
|
|
2287
|
+
currentDependencies = nextDependencies
|
|
2288
|
+
mounted = true
|
|
2289
|
+
}
|
|
2290
|
+
queueInsertionCommit(owner, () => {
|
|
2291
|
+
if (!lifetimeOwner[0]) run()
|
|
2292
|
+
})
|
|
2293
|
+
const removeUpdater = subscribe(scope, reads, () =>
|
|
2294
|
+
stagePublication([noop, noop, undefined, run, -20]),
|
|
2295
|
+
)
|
|
2296
|
+
lifetimeOwner[1].add(() => {
|
|
2297
|
+
removeUpdater()
|
|
2298
|
+
cleanup()
|
|
2299
|
+
})
|
|
2300
|
+
return
|
|
2301
|
+
}
|
|
2302
|
+
let mounted = false
|
|
2303
|
+
let connected = false
|
|
2304
|
+
let currentDependencies: readonly unknown[] | undefined
|
|
2305
|
+
let cleanup = noop
|
|
2306
|
+
const run = (): void => {
|
|
2307
|
+
const nextDependencies = readDependencies?.()
|
|
2308
|
+
if (
|
|
2309
|
+
mounted &&
|
|
2310
|
+
readDependencies !== undefined &&
|
|
2311
|
+
equalDependencies(nextDependencies, currentDependencies)
|
|
2312
|
+
) {
|
|
2313
|
+
return
|
|
2314
|
+
}
|
|
2315
|
+
cleanup()
|
|
2316
|
+
cleanup = profilingEnabled
|
|
2317
|
+
? readProfiledEffectCleanup(owner, () => readCreate()())
|
|
2318
|
+
: readEffectCleanup(readCreate()())
|
|
2319
|
+
currentDependencies = nextDependencies
|
|
2320
|
+
mounted = true
|
|
2321
|
+
}
|
|
2322
|
+
const [activate, disposeResource] = createRetainedResource(
|
|
2323
|
+
lifetimeOwner,
|
|
2324
|
+
() => {
|
|
2325
|
+
stagePublication([
|
|
2326
|
+
noop,
|
|
2327
|
+
noop,
|
|
2328
|
+
undefined,
|
|
2329
|
+
() => {
|
|
2330
|
+
connected = true
|
|
2331
|
+
run()
|
|
2332
|
+
},
|
|
2333
|
+
-20,
|
|
2334
|
+
])
|
|
2335
|
+
},
|
|
2336
|
+
() => {
|
|
2337
|
+
connected = false
|
|
2338
|
+
cleanup()
|
|
2339
|
+
cleanup = noop
|
|
2340
|
+
mounted = false
|
|
2341
|
+
},
|
|
2342
|
+
)
|
|
2343
|
+
queueInsertionCommit(owner, () => {
|
|
2344
|
+
if (!lifetimeOwner[0]) activate()
|
|
2345
|
+
})
|
|
2346
|
+
const removeUpdater = subscribe(scope, reads, () => {
|
|
2347
|
+
if (connected) stagePublication([noop, noop, undefined, run, -20])
|
|
2348
|
+
})
|
|
2349
|
+
lifetimeOwner[1].add(() => {
|
|
2350
|
+
removeUpdater()
|
|
2351
|
+
disposeResource()
|
|
2352
|
+
})
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
export function compiledEffect(
|
|
2356
|
+
scope: CompiledScope,
|
|
2357
|
+
reads: SourceMask,
|
|
2358
|
+
readCreate: () => () => EffectResult,
|
|
2359
|
+
readDependencies?: () => readonly unknown[],
|
|
2360
|
+
passive = true,
|
|
2361
|
+
): void {
|
|
2362
|
+
const owner = activeConstructionOwner
|
|
2363
|
+
if (owner === null || scopeOwners.get(scope) !== owner) {
|
|
2364
|
+
throw new Error(DEV ? 'compiled effects must run in their component scope' : 'V016')
|
|
2365
|
+
}
|
|
2366
|
+
const lifetimeOwner = activeOwner ?? owner
|
|
2367
|
+
if (!retainedUiEnabled) {
|
|
2368
|
+
let mounted = false
|
|
2369
|
+
let generation = 0
|
|
2370
|
+
let currentDependencies: readonly unknown[] | undefined
|
|
2371
|
+
let cleanup = noop
|
|
2372
|
+
const run = (): void => {
|
|
2373
|
+
const nextDependencies = readDependencies?.()
|
|
2374
|
+
if (
|
|
2375
|
+
mounted &&
|
|
2376
|
+
readDependencies !== undefined &&
|
|
2377
|
+
equalDependencies(nextDependencies, currentDependencies)
|
|
2378
|
+
) {
|
|
2379
|
+
return
|
|
2380
|
+
}
|
|
2381
|
+
cleanup()
|
|
2382
|
+
cleanup = profilingEnabled
|
|
2383
|
+
? readProfiledEffectCleanup(owner, () => readCreate()())
|
|
2384
|
+
: readEffectCleanup(readCreate()())
|
|
2385
|
+
currentDependencies = nextDependencies
|
|
2386
|
+
mounted = true
|
|
2387
|
+
}
|
|
2388
|
+
const schedule = (): void => {
|
|
2389
|
+
if (!passive) {
|
|
2390
|
+
run()
|
|
2391
|
+
return
|
|
2392
|
+
}
|
|
2393
|
+
const scheduled = ++generation
|
|
2394
|
+
;(DEV ? scheduleTask : queueMicrotask)(() => {
|
|
2395
|
+
if (!lifetimeOwner[0] && scheduled === generation) runOwnerTask(lifetimeOwner, run)
|
|
2396
|
+
})
|
|
2397
|
+
}
|
|
2398
|
+
queueOwnerCommit(owner, () => {
|
|
2399
|
+
if (!lifetimeOwner[0]) schedule()
|
|
2400
|
+
})
|
|
2401
|
+
const removeUpdater = subscribe(scope, reads, () => {
|
|
2402
|
+
if (passive) schedule()
|
|
2403
|
+
else stagePublication([noop, noop, undefined, run, 20])
|
|
2404
|
+
})
|
|
2405
|
+
lifetimeOwner[1].add(() => {
|
|
2406
|
+
generation += 1
|
|
2407
|
+
removeUpdater()
|
|
2408
|
+
if (passive) (DEV ? scheduleTask : queueMicrotask)(() => runOwnerTask(lifetimeOwner, cleanup))
|
|
2409
|
+
else cleanup()
|
|
2410
|
+
})
|
|
2411
|
+
return
|
|
2412
|
+
}
|
|
2413
|
+
let mounted = false
|
|
2414
|
+
let connected = false
|
|
2415
|
+
let generation = 0
|
|
2416
|
+
let currentDependencies: readonly unknown[] | undefined
|
|
2417
|
+
let cleanup = noop
|
|
2418
|
+
const run = (): void => {
|
|
2419
|
+
const nextDependencies = readDependencies?.()
|
|
2420
|
+
if (
|
|
2421
|
+
mounted &&
|
|
2422
|
+
readDependencies !== undefined &&
|
|
2423
|
+
equalDependencies(nextDependencies, currentDependencies)
|
|
2424
|
+
) {
|
|
2425
|
+
return
|
|
2426
|
+
}
|
|
2427
|
+
cleanup()
|
|
2428
|
+
cleanup = profilingEnabled
|
|
2429
|
+
? readProfiledEffectCleanup(owner, () => readCreate()())
|
|
2430
|
+
: readEffectCleanup(readCreate()())
|
|
2431
|
+
currentDependencies = nextDependencies
|
|
2432
|
+
mounted = true
|
|
2433
|
+
}
|
|
2434
|
+
const schedule = (): void => {
|
|
2435
|
+
if (!connected) return
|
|
2436
|
+
if (!passive) {
|
|
2437
|
+
run()
|
|
2438
|
+
return
|
|
2439
|
+
}
|
|
2440
|
+
const scheduled = ++generation
|
|
2441
|
+
;(DEV ? scheduleTask : queueMicrotask)(() => {
|
|
2442
|
+
if (!lifetimeOwner[0] && scheduled === generation) runOwnerTask(lifetimeOwner, run)
|
|
2443
|
+
})
|
|
2444
|
+
}
|
|
2445
|
+
const [activate, disposeResource] = createRetainedResource(
|
|
2446
|
+
lifetimeOwner,
|
|
2447
|
+
() => {
|
|
2448
|
+
stagePublication([
|
|
2449
|
+
noop,
|
|
2450
|
+
noop,
|
|
2451
|
+
undefined,
|
|
2452
|
+
() => {
|
|
2453
|
+
connected = true
|
|
2454
|
+
schedule()
|
|
2455
|
+
},
|
|
2456
|
+
passive ? 40 : 20,
|
|
2457
|
+
])
|
|
2458
|
+
},
|
|
2459
|
+
() => {
|
|
2460
|
+
connected = false
|
|
2461
|
+
generation += 1
|
|
2462
|
+
const previousCleanup = cleanup
|
|
2463
|
+
cleanup = noop
|
|
2464
|
+
mounted = false
|
|
2465
|
+
if (passive)
|
|
2466
|
+
(DEV ? scheduleTask : queueMicrotask)(() => runOwnerTask(lifetimeOwner, previousCleanup))
|
|
2467
|
+
else previousCleanup()
|
|
2468
|
+
},
|
|
2469
|
+
)
|
|
2470
|
+
queueOwnerCommit(owner, () => {
|
|
2471
|
+
if (!lifetimeOwner[0]) activate()
|
|
2472
|
+
})
|
|
2473
|
+
const removeUpdater = subscribe(scope, reads, () => {
|
|
2474
|
+
if (!connected) return
|
|
2475
|
+
if (passive) schedule()
|
|
2476
|
+
else stagePublication([noop, noop, undefined, run, 20])
|
|
2477
|
+
})
|
|
2478
|
+
lifetimeOwner[1].add(() => {
|
|
2479
|
+
removeUpdater()
|
|
2480
|
+
disposeResource()
|
|
2481
|
+
})
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
function readEffectCleanup(result: EffectResult): () => void {
|
|
2485
|
+
if (result === undefined) return noop
|
|
2486
|
+
if (typeof result === 'function') return result
|
|
2487
|
+
throw new TypeError(DEV ? 'an effect must return a cleanup function or undefined' : 'V018')
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
export function compiledImperativeHandle<T>(
|
|
2491
|
+
scope: CompiledScope,
|
|
2492
|
+
reads: SourceMask,
|
|
2493
|
+
readRef: () => RefValue<T>,
|
|
2494
|
+
create: () => T,
|
|
2495
|
+
readDependencies?: () => readonly unknown[],
|
|
2496
|
+
): void {
|
|
2497
|
+
const owner = activeConstructionOwner
|
|
2498
|
+
if (owner === null || scopeOwners.get(scope) !== owner) {
|
|
2499
|
+
throw new Error(
|
|
2500
|
+
DEV ? 'compiledImperativeHandle must run in its compiled component scope' : 'V013',
|
|
2501
|
+
)
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
let mounted = false
|
|
2505
|
+
let currentRef: RefValue<T>
|
|
2506
|
+
let currentHandle: T
|
|
2507
|
+
let currentDependencies: readonly unknown[] | undefined
|
|
2508
|
+
// oxlint-disable-next-line unicorn/consistent-function-scoping -- This placeholder is replaced by the committed resource cleanup.
|
|
2509
|
+
let cleanup = noop
|
|
2510
|
+
|
|
2511
|
+
const commitInitial = (): void => {
|
|
2512
|
+
currentRef = readImperativeRef(readRef())
|
|
2513
|
+
currentDependencies = readDependencies?.()
|
|
2514
|
+
currentHandle = create()
|
|
2515
|
+
cleanup = attachRef(currentRef, currentHandle)
|
|
2516
|
+
mounted = true
|
|
2517
|
+
}
|
|
2518
|
+
queueOwnerCommit(owner, commitInitial)
|
|
2519
|
+
|
|
2520
|
+
const update = (): void => {
|
|
2521
|
+
if (!mounted) return
|
|
2522
|
+
const nextRef = readImperativeRef(readRef())
|
|
2523
|
+
const nextDependencies = readDependencies?.()
|
|
2524
|
+
if (
|
|
2525
|
+
Object.is(nextRef, currentRef) &&
|
|
2526
|
+
readDependencies !== undefined &&
|
|
2527
|
+
equalDependencies(nextDependencies, currentDependencies)
|
|
2528
|
+
) {
|
|
2529
|
+
return
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
const previousRef = currentRef
|
|
2533
|
+
const previousHandle = currentHandle
|
|
2534
|
+
const previousDependencies = currentDependencies
|
|
2535
|
+
const previousCleanup = cleanup
|
|
2536
|
+
let nextCleanup: (() => void) | undefined
|
|
2537
|
+
let finalized = false
|
|
2538
|
+
stagePublication([
|
|
2539
|
+
noop,
|
|
2540
|
+
() => {
|
|
2541
|
+
if (!finalized) return
|
|
2542
|
+
nextCleanup?.()
|
|
2543
|
+
cleanup = attachRef(previousRef, previousHandle)
|
|
2544
|
+
currentRef = previousRef
|
|
2545
|
+
currentHandle = previousHandle
|
|
2546
|
+
currentDependencies = previousDependencies
|
|
2547
|
+
finalized = false
|
|
2548
|
+
},
|
|
2549
|
+
undefined,
|
|
2550
|
+
() => {
|
|
2551
|
+
const nextHandle = create()
|
|
2552
|
+
if (Object.is(nextRef, previousRef)) {
|
|
2553
|
+
try {
|
|
2554
|
+
previousCleanup()
|
|
2555
|
+
} catch (error) {
|
|
2556
|
+
try {
|
|
2557
|
+
cleanup = attachRef(previousRef, previousHandle)
|
|
2558
|
+
} catch {
|
|
2559
|
+
// Preserve the cleanup error that aborted publication.
|
|
2560
|
+
}
|
|
2561
|
+
throw error
|
|
2562
|
+
}
|
|
2563
|
+
try {
|
|
2564
|
+
nextCleanup = attachRef(nextRef, nextHandle)
|
|
2565
|
+
} catch (error) {
|
|
2566
|
+
try {
|
|
2567
|
+
cleanup = attachRef(previousRef, previousHandle)
|
|
2568
|
+
} catch {
|
|
2569
|
+
// Preserve the next-ref attachment error.
|
|
2570
|
+
}
|
|
2571
|
+
throw error
|
|
2572
|
+
}
|
|
2573
|
+
} else {
|
|
2574
|
+
nextCleanup = attachRef(nextRef, nextHandle)
|
|
2575
|
+
try {
|
|
2576
|
+
previousCleanup()
|
|
2577
|
+
} catch (error) {
|
|
2578
|
+
nextCleanup()
|
|
2579
|
+
try {
|
|
2580
|
+
cleanup = attachRef(previousRef, previousHandle)
|
|
2581
|
+
} catch {
|
|
2582
|
+
// Preserve the previous-ref cleanup error.
|
|
2583
|
+
}
|
|
2584
|
+
throw error
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
cleanup = nextCleanup
|
|
2588
|
+
currentRef = nextRef
|
|
2589
|
+
currentHandle = nextHandle
|
|
2590
|
+
currentDependencies = nextDependencies
|
|
2591
|
+
finalized = true
|
|
2592
|
+
},
|
|
2593
|
+
10,
|
|
2594
|
+
])
|
|
2595
|
+
}
|
|
2596
|
+
const removeUpdater = subscribe(scope, reads, update)
|
|
2597
|
+
owner[1].add(() => {
|
|
2598
|
+
removeUpdater()
|
|
2599
|
+
cleanup()
|
|
2600
|
+
})
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
function queueOwnerCommit(owner: Owner, commit: () => void): void {
|
|
2604
|
+
let commits = pendingOwnerCommits.get(owner)
|
|
2605
|
+
if (commits === undefined) {
|
|
2606
|
+
commits = new Set()
|
|
2607
|
+
pendingOwnerCommits.set(owner, commits)
|
|
2608
|
+
}
|
|
2609
|
+
commits.add(commit)
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
function queueInsertionCommit(owner: Owner, commit: () => void): void {
|
|
2613
|
+
let commits = pendingInsertionCommits.get(owner)
|
|
2614
|
+
if (commits === undefined) {
|
|
2615
|
+
commits = new Set()
|
|
2616
|
+
pendingInsertionCommits.set(owner, commits)
|
|
2617
|
+
}
|
|
2618
|
+
commits.add(commit)
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
function readImperativeRef<T>(ref: RefValue<T>): RefValue<T> {
|
|
2622
|
+
if (!isRefValue(ref)) {
|
|
2623
|
+
throw new TypeError(
|
|
2624
|
+
DEV ? 'imperative ref must be null, a callback, or an object with current' : 'V006',
|
|
2625
|
+
)
|
|
2626
|
+
}
|
|
2627
|
+
return ref
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
function equalDependencies(
|
|
2631
|
+
next: readonly unknown[] | undefined,
|
|
2632
|
+
previous: readonly unknown[] | undefined,
|
|
2633
|
+
): boolean {
|
|
2634
|
+
if (next === undefined || previous === undefined || next.length !== previous.length) return false
|
|
2635
|
+
return next.every((value, index) => Object.is(value, previous[index]))
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
export function mountCompiledRef(element: Element, value: CompiledBinding<unknown>): void {
|
|
2639
|
+
const initial = value[1]()
|
|
2640
|
+
if (!isRefValue(initial)) {
|
|
2641
|
+
throw new TypeError(DEV ? 'ref must be null, a callback, or an object with current' : 'V006')
|
|
2642
|
+
}
|
|
2643
|
+
let current: RefValue = initial
|
|
2644
|
+
// oxlint-disable-next-line unicorn/consistent-function-scoping -- This placeholder is replaced when the ref attaches.
|
|
2645
|
+
let cleanup = noop
|
|
2646
|
+
const pending: PendingRef = [activeOwner, current, (attached) => (cleanup = attached)]
|
|
2647
|
+
pendingRefs.set(element, pending)
|
|
2648
|
+
|
|
2649
|
+
const removeUpdater = subscribeBinding(value, () => {
|
|
2650
|
+
const next = value[1]()
|
|
2651
|
+
if (Object.is(next, current)) return
|
|
2652
|
+
if (!isRefValue(next)) {
|
|
2653
|
+
throw new TypeError(DEV ? 'ref must be null, a callback, or an object with current' : 'V006')
|
|
2654
|
+
}
|
|
2655
|
+
const previous = current
|
|
2656
|
+
const previousCleanup = cleanup
|
|
2657
|
+
let nextCleanup: (() => void) | undefined
|
|
2658
|
+
let previousDetached = false
|
|
2659
|
+
let committed = false
|
|
2660
|
+
stagePublication([
|
|
2661
|
+
() => {
|
|
2662
|
+
try {
|
|
2663
|
+
previousCleanup()
|
|
2664
|
+
previousDetached = true
|
|
2665
|
+
} catch (error) {
|
|
2666
|
+
try {
|
|
2667
|
+
cleanup = attachRef(previous, element)
|
|
2668
|
+
} catch {
|
|
2669
|
+
// Preserve the previous-ref cleanup error.
|
|
2670
|
+
}
|
|
2671
|
+
throw error
|
|
2672
|
+
}
|
|
2673
|
+
try {
|
|
2674
|
+
nextCleanup = attachRef(next, element)
|
|
2675
|
+
} catch (error) {
|
|
2676
|
+
try {
|
|
2677
|
+
cleanup = attachRef(previous, element)
|
|
2678
|
+
previousDetached = false
|
|
2679
|
+
} catch {
|
|
2680
|
+
// Preserve the next-ref attachment error.
|
|
2681
|
+
}
|
|
2682
|
+
throw error
|
|
2683
|
+
}
|
|
2684
|
+
cleanup = nextCleanup
|
|
2685
|
+
current = next
|
|
2686
|
+
committed = true
|
|
2687
|
+
},
|
|
2688
|
+
() => {
|
|
2689
|
+
if (!committed) {
|
|
2690
|
+
nextCleanup?.()
|
|
2691
|
+
if (previousDetached) {
|
|
2692
|
+
cleanup = attachRef(previous, element)
|
|
2693
|
+
previousDetached = false
|
|
2694
|
+
}
|
|
2695
|
+
return
|
|
2696
|
+
}
|
|
2697
|
+
cleanup()
|
|
2698
|
+
cleanup = attachRef(previous, element)
|
|
2699
|
+
current = previous
|
|
2700
|
+
committed = false
|
|
2701
|
+
},
|
|
2702
|
+
])
|
|
2703
|
+
})
|
|
2704
|
+
onCleanup(() => {
|
|
2705
|
+
removeUpdater()
|
|
2706
|
+
if (pendingRefs.get(element) === pending) pendingRefs.delete(element)
|
|
2707
|
+
cleanup()
|
|
2708
|
+
})
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
export function registerCompiledCleanup(cleanup: () => void): void {
|
|
2712
|
+
onCleanup(cleanup)
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
export interface MountCompiledOptions {
|
|
2716
|
+
readonly identifierPrefix?: string
|
|
2717
|
+
readonly onCaughtError?: CompiledErrorHandler
|
|
2718
|
+
readonly onRecoverableError?: CompiledErrorHandler
|
|
2719
|
+
readonly onUncaughtError?: CompiledErrorHandler
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
export function hydrateCompiled(
|
|
2723
|
+
component: () => CompiledComponentResult,
|
|
2724
|
+
host: ParentNode,
|
|
2725
|
+
options?: MountCompiledOptions,
|
|
2726
|
+
): { dispose: () => void } {
|
|
2727
|
+
let endHydration: (() => void) | undefined
|
|
2728
|
+
let hydratedMount: { dispose: () => void } | undefined
|
|
2729
|
+
try {
|
|
2730
|
+
endHydration = beginHydration(host)
|
|
2731
|
+
hydratedMount = mountCompiled(component, host, options)
|
|
2732
|
+
finishHydration()
|
|
2733
|
+
const rootMarkers = hydrationRootMarkers()
|
|
2734
|
+
return {
|
|
2735
|
+
dispose: () => {
|
|
2736
|
+
try {
|
|
2737
|
+
hydratedMount?.dispose()
|
|
2738
|
+
} finally {
|
|
2739
|
+
removeBetween(rootMarkers[0], rootMarkers[1])
|
|
2740
|
+
rootMarkers[0].remove()
|
|
2741
|
+
rootMarkers[1].remove()
|
|
2742
|
+
}
|
|
2743
|
+
},
|
|
2744
|
+
}
|
|
2745
|
+
} catch (error) {
|
|
2746
|
+
if (!isHydrationMismatch(error)) throw error
|
|
2747
|
+
try {
|
|
2748
|
+
hydratedMount?.dispose()
|
|
2749
|
+
} catch {
|
|
2750
|
+
// Preserve and report the mismatch that forced recovery.
|
|
2751
|
+
}
|
|
2752
|
+
hydratedMount = undefined
|
|
2753
|
+
endHydration?.()
|
|
2754
|
+
endHydration = undefined
|
|
2755
|
+
options?.onRecoverableError?.(error)
|
|
2756
|
+
return mountCompiled(component, host, options)
|
|
2757
|
+
} finally {
|
|
2758
|
+
endHydration?.()
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2762
|
+
export function mountCompiled(
|
|
2763
|
+
component: () => CompiledComponentResult,
|
|
2764
|
+
host: ParentNode,
|
|
2765
|
+
options?: MountCompiledOptions,
|
|
2766
|
+
): { dispose: () => void } {
|
|
2767
|
+
const hydrating = isHydrating()
|
|
2768
|
+
const previousRootIdentity = activeRootIdentity
|
|
2769
|
+
const rootIdentity = createRootIdentity(
|
|
2770
|
+
options?.identifierPrefix,
|
|
2771
|
+
options?.onCaughtError,
|
|
2772
|
+
options?.onUncaughtError,
|
|
2773
|
+
)
|
|
2774
|
+
activeRootIdentity = rootIdentity
|
|
2775
|
+
let root: CompiledComponentResult
|
|
2776
|
+
try {
|
|
2777
|
+
root = constructCompiledComponent(component)
|
|
2778
|
+
} catch (error) {
|
|
2779
|
+
if (isHydrationMismatch(error)) throw error
|
|
2780
|
+
if (rootIdentity.onUncaughtError === undefined) throw error
|
|
2781
|
+
rootIdentity.onUncaughtError(error)
|
|
2782
|
+
return { dispose: noop }
|
|
2783
|
+
} finally {
|
|
2784
|
+
activeRootIdentity = previousRootIdentity
|
|
2785
|
+
}
|
|
2786
|
+
const range = componentRanges.get(root)
|
|
2787
|
+
if (range === undefined) {
|
|
2788
|
+
throw new Error(DEV ? 'mountCompiled received an unknown component result' : 'V007')
|
|
2789
|
+
}
|
|
2790
|
+
const previous = [...host.childNodes]
|
|
2791
|
+
try {
|
|
2792
|
+
root[1](host, previous[0] ?? null)
|
|
2793
|
+
const rootOwner = scopeOwners.get(range[2])
|
|
2794
|
+
const mountedRootIdentity = rootOwner?.[3]
|
|
2795
|
+
if (mountedRootIdentity !== undefined) {
|
|
2796
|
+
mountedRootIdentity.mounted = true
|
|
2797
|
+
commitRootPortals(mountedRootIdentity)
|
|
2798
|
+
}
|
|
2799
|
+
commitOwnerInsertions(rootOwner)
|
|
2800
|
+
commitRangeRefs(range[0], range[1])
|
|
2801
|
+
commitOwnerResources(rootOwner)
|
|
2802
|
+
if (!hydrating) {
|
|
2803
|
+
for (const node of previous) host.removeChild(node)
|
|
2804
|
+
}
|
|
2805
|
+
} catch (error) {
|
|
2806
|
+
try {
|
|
2807
|
+
range[2][3]()
|
|
2808
|
+
} catch {
|
|
2809
|
+
// Preserve the mount error while still running every component cleanup.
|
|
2810
|
+
}
|
|
2811
|
+
if (isHydrationMismatch(error) || rootIdentity.onUncaughtError === undefined) throw error
|
|
2812
|
+
rootIdentity.onUncaughtError(error)
|
|
2813
|
+
return { dispose: noop }
|
|
2814
|
+
}
|
|
2815
|
+
return {
|
|
2816
|
+
dispose: range[2][3],
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2820
|
+
export function isCompiledBinding(value: unknown): value is CompiledBinding<unknown> {
|
|
2821
|
+
return Array.isArray(value) && value[0] === BINDING
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
export function isStructuralBinding(value: unknown): value is StructuralBinding {
|
|
2825
|
+
return Array.isArray(value) && value[0] === STRUCTURAL
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
export function isCompiledComponentResult(value: unknown): value is CompiledComponentResult {
|
|
2829
|
+
return isStructuralBinding(value) && componentRanges.has(value)
|
|
2830
|
+
}
|
|
2831
|
+
|
|
2832
|
+
export function mountCompiledBinding(parent: Node, value: CompiledBinding<unknown>): void {
|
|
2833
|
+
mountCompiledBindingBefore(parent, value, null)
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2836
|
+
export function mountCompiledProp(
|
|
2837
|
+
value: CompiledBinding<unknown>,
|
|
2838
|
+
apply: (next: unknown) => void | (() => void),
|
|
2839
|
+
): void {
|
|
2840
|
+
let current = value[1]()
|
|
2841
|
+
let cleanup = apply(current)
|
|
2842
|
+
const removeUpdater = subscribeBinding(value, () => {
|
|
2843
|
+
const next = value[1]()
|
|
2844
|
+
if (Object.is(next, current)) return
|
|
2845
|
+
const previous = current
|
|
2846
|
+
let nextCleanup: void | (() => void)
|
|
2847
|
+
let committedNext = false
|
|
2848
|
+
stagePublication([
|
|
2849
|
+
() => {
|
|
2850
|
+
try {
|
|
2851
|
+
nextCleanup = apply(next)
|
|
2852
|
+
} catch (error) {
|
|
2853
|
+
try {
|
|
2854
|
+
apply(previous)
|
|
2855
|
+
} catch {
|
|
2856
|
+
// Preserve the setter error that aborted publication.
|
|
2857
|
+
}
|
|
2858
|
+
throw error
|
|
2859
|
+
}
|
|
2860
|
+
committedNext = true
|
|
2861
|
+
cleanup?.()
|
|
2862
|
+
cleanup = nextCleanup
|
|
2863
|
+
current = next
|
|
2864
|
+
},
|
|
2865
|
+
() => {
|
|
2866
|
+
if (!committedNext) return
|
|
2867
|
+
nextCleanup?.()
|
|
2868
|
+
cleanup = apply(previous)
|
|
2869
|
+
current = previous
|
|
2870
|
+
committedNext = false
|
|
2871
|
+
},
|
|
2872
|
+
])
|
|
2873
|
+
})
|
|
2874
|
+
onCleanup(() => {
|
|
2875
|
+
removeUpdater()
|
|
2876
|
+
cleanup?.()
|
|
2877
|
+
})
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
export function mountCompiledPropTransition<T>(
|
|
2881
|
+
value: CompiledBinding<T>,
|
|
2882
|
+
initialize: (initial: T) => void,
|
|
2883
|
+
prepare: (next: T, previous: T) => CompiledPropTransition | undefined,
|
|
2884
|
+
): void {
|
|
2885
|
+
let current = value[1]()
|
|
2886
|
+
initialize(current)
|
|
2887
|
+
const removeUpdater = subscribeBinding(value, () => {
|
|
2888
|
+
const next = value[1]()
|
|
2889
|
+
if (Object.is(next, current)) return
|
|
2890
|
+
const previous = current
|
|
2891
|
+
const transition = prepare(next, previous)
|
|
2892
|
+
let attempted = false
|
|
2893
|
+
stagePublication([
|
|
2894
|
+
() => {
|
|
2895
|
+
attempted = true
|
|
2896
|
+
transition?.[0]()
|
|
2897
|
+
current = next
|
|
2898
|
+
},
|
|
2899
|
+
() => {
|
|
2900
|
+
if (!attempted) return
|
|
2901
|
+
try {
|
|
2902
|
+
transition?.[1]()
|
|
2903
|
+
} finally {
|
|
2904
|
+
current = previous
|
|
2905
|
+
attempted = false
|
|
2906
|
+
}
|
|
2907
|
+
},
|
|
2908
|
+
transition?.[2],
|
|
2909
|
+
transition?.[3],
|
|
2910
|
+
transition?.[4],
|
|
2911
|
+
])
|
|
2912
|
+
})
|
|
2913
|
+
onCleanup(removeUpdater)
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
function structural(
|
|
2917
|
+
scope: CompiledScope,
|
|
2918
|
+
mount: StructuralBinding[1],
|
|
2919
|
+
hydrationKind?: StructuralBinding[2],
|
|
2920
|
+
): StructuralBinding {
|
|
2921
|
+
// Only lists carry an array marker; a slot-kind structural must not bias the claim.
|
|
2922
|
+
if (hydrationKind === 'array') noteHydrationStructuralParent()
|
|
2923
|
+
let mounted = false
|
|
2924
|
+
const owner = activeOwner ?? scopeOwners.get(scope)!
|
|
2925
|
+
const context = activeContextFrame ?? owner[2]
|
|
2926
|
+
const mountOnce: StructuralBinding[1] = (parent, before) => {
|
|
2927
|
+
if (mounted) throw new Error(DEV ? 'compiled block is already mounted' : 'V008')
|
|
2928
|
+
mounted = true
|
|
2929
|
+
const operation = () =>
|
|
2930
|
+
withOwner(owner, () =>
|
|
2931
|
+
withContextFrame(context, () => withScopeNamespace(scope, () => mount(parent, before))),
|
|
2932
|
+
)
|
|
2933
|
+
if (profilingEnabled) measureProfileWork(owner, 'range', operation)
|
|
2934
|
+
else operation()
|
|
2935
|
+
}
|
|
2936
|
+
return hydrationKind === undefined
|
|
2937
|
+
? [STRUCTURAL, mountOnce]
|
|
2938
|
+
: [STRUCTURAL, mountOnce, hydrationKind]
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
function structuralRange(
|
|
2942
|
+
parent: Node,
|
|
2943
|
+
before: Node | null,
|
|
2944
|
+
label: string,
|
|
2945
|
+
): readonly [start: Comment, end: Comment, hydrated: boolean] {
|
|
2946
|
+
const hydrated = claimHydrationSlotRange(parent)
|
|
2947
|
+
if (hydrated !== undefined) return [hydrated[0], hydrated[1], true]
|
|
2948
|
+
const start = document.createComment(DEV ? `vidact:${label}` : '')
|
|
2949
|
+
const end = document.createComment(DEV ? `/vidact:${label}` : '')
|
|
2950
|
+
parent.insertBefore(start, before)
|
|
2951
|
+
parent.insertBefore(end, before)
|
|
2952
|
+
return [start, end, false]
|
|
2953
|
+
}
|
|
2954
|
+
|
|
2955
|
+
function subscribe(
|
|
2956
|
+
scope: CompiledScope,
|
|
2957
|
+
reads: SourceMask,
|
|
2958
|
+
run: () => void,
|
|
2959
|
+
additionalScope?: CompiledScope,
|
|
2960
|
+
additionalReads?: SourceMask,
|
|
2961
|
+
): () => void {
|
|
2962
|
+
const removers = [scope[0](reads, run)]
|
|
2963
|
+
if (additionalScope !== undefined && additionalReads !== undefined) {
|
|
2964
|
+
removers.push(additionalScope[0](additionalReads, () => withScopeNamespace(scope, run)))
|
|
2965
|
+
}
|
|
2966
|
+
return () => {
|
|
2967
|
+
for (const remove of removers) remove()
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
|
|
2971
|
+
function subscribeBinding(compiledBinding: CompiledBinding<unknown>, run: () => void): () => void {
|
|
2972
|
+
return subscribe(
|
|
2973
|
+
compiledBinding[2],
|
|
2974
|
+
compiledBinding[3],
|
|
2975
|
+
run,
|
|
2976
|
+
compiledBinding[4],
|
|
2977
|
+
compiledBinding[5],
|
|
2978
|
+
)
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2981
|
+
function withScopeNamespace<Result>(scope: CompiledScope, operation: () => Result): Result {
|
|
2982
|
+
return withIntrinsicNamespace(scopeNamespaces.get(scope), operation)
|
|
2983
|
+
}
|
|
2984
|
+
|
|
2985
|
+
function createOwner(
|
|
2986
|
+
context = activeContextFrame ?? activeOwner?.[2] ?? null,
|
|
2987
|
+
rootIdentity = activeOwner?.[3] ?? activeRootIdentity ?? createRootIdentity(),
|
|
2988
|
+
boundary = activeOwner?.[4] ?? null,
|
|
2989
|
+
retainedConnection = activeOwner?.[5] ?? null,
|
|
2990
|
+
profileContext = activeOwner?.[6] ?? activeConstructionOwner?.[6] ?? null,
|
|
2991
|
+
): Owner {
|
|
2992
|
+
if (DEV) {
|
|
2993
|
+
activeOwnerCount += 1
|
|
2994
|
+
createdOwnerCount += 1
|
|
2995
|
+
}
|
|
2996
|
+
let nextProfile = profileContext
|
|
2997
|
+
if (profilingEnabled && activeProfileName !== null && activeConstructionOwner === null) {
|
|
2998
|
+
nextProfile = {
|
|
2999
|
+
frame: {
|
|
3000
|
+
name: activeProfileName,
|
|
3001
|
+
parent: profileContext?.frame ?? null,
|
|
3002
|
+
values: [],
|
|
3003
|
+
},
|
|
3004
|
+
boundary: profileContext?.boundary ?? null,
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
if (profilingEnabled) {
|
|
3008
|
+
const owner: Owner = [
|
|
3009
|
+
false,
|
|
3010
|
+
new Set(),
|
|
3011
|
+
context,
|
|
3012
|
+
rootIdentity,
|
|
3013
|
+
boundary,
|
|
3014
|
+
retainedUiEnabled ? retainedConnection : null,
|
|
3015
|
+
nextProfile,
|
|
3016
|
+
]
|
|
3017
|
+
if (DEV) owner[7] = activeConstructionOwner?.[7] ?? activeProfileName
|
|
3018
|
+
return owner
|
|
3019
|
+
}
|
|
3020
|
+
const owner: Owner = retainedUiEnabled
|
|
3021
|
+
? [false, new Set(), context, rootIdentity, boundary, retainedConnection]
|
|
3022
|
+
: [false, new Set(), context, rootIdentity, boundary]
|
|
3023
|
+
if (DEV) owner[7] = activeConstructionOwner?.[7] ?? activeProfileName
|
|
3024
|
+
return owner
|
|
3025
|
+
}
|
|
3026
|
+
|
|
3027
|
+
function ensureProfileContext(owner: Owner): ProfileContext {
|
|
3028
|
+
const current = owner[6]
|
|
3029
|
+
if (current?.frame !== null && current?.frame !== undefined) return current
|
|
3030
|
+
const parent =
|
|
3031
|
+
current ?? (activeOwner !== null && activeOwner !== owner ? (activeOwner[6] ?? null) : null)
|
|
3032
|
+
const next = {
|
|
3033
|
+
frame: {
|
|
3034
|
+
name: owner[7] ?? activeProfileName ?? 'Anonymous',
|
|
3035
|
+
parent: parent?.frame ?? null,
|
|
3036
|
+
values: [],
|
|
3037
|
+
},
|
|
3038
|
+
boundary: parent?.boundary ?? null,
|
|
3039
|
+
}
|
|
3040
|
+
owner[6] = next
|
|
3041
|
+
return next
|
|
3042
|
+
}
|
|
3043
|
+
|
|
3044
|
+
function profileNow(): number {
|
|
3045
|
+
return typeof performance === 'undefined' ? Date.now() : performance.now()
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
function measureProfileWork<Result>(
|
|
3049
|
+
owner: Owner,
|
|
3050
|
+
kind: 'effect' | 'range' | 'scheduler' | 'updater',
|
|
3051
|
+
operation: () => Result,
|
|
3052
|
+
contribute = false,
|
|
3053
|
+
): Result {
|
|
3054
|
+
const started = profileNow()
|
|
3055
|
+
try {
|
|
3056
|
+
return operation()
|
|
3057
|
+
} finally {
|
|
3058
|
+
const finished = profileNow()
|
|
3059
|
+
emitProfileMeasure(owner, kind, started, finished)
|
|
3060
|
+
if (contribute) recordProfileCommit(owner, started, finished)
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
function emitProfileMeasure(
|
|
3065
|
+
owner: Owner,
|
|
3066
|
+
kind: 'effect' | 'range' | 'scheduler' | 'updater',
|
|
3067
|
+
started: number,
|
|
3068
|
+
finished: number,
|
|
3069
|
+
): void {
|
|
3070
|
+
if (typeof performance === 'undefined' || typeof performance.measure !== 'function') return
|
|
3071
|
+
const component = owner[6]?.frame?.name ?? 'Anonymous'
|
|
3072
|
+
performance.measure(`vidact.${kind}:${component}`, { start: started, end: finished })
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3075
|
+
function recordProfileCommit(owner: Owner, started: number, finished: number): void {
|
|
3076
|
+
let boundary = owner[6]?.boundary ?? null
|
|
3077
|
+
while (boundary !== null) {
|
|
3078
|
+
queueProfileBoundaryCommit(boundary, owner, started, finished)
|
|
3079
|
+
boundary = boundary.parent
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
|
|
3083
|
+
function queueProfileBoundaryCommit(
|
|
3084
|
+
boundary: ProfileBoundary,
|
|
3085
|
+
owner: Owner,
|
|
3086
|
+
started: number,
|
|
3087
|
+
finished: number,
|
|
3088
|
+
): void {
|
|
3089
|
+
if (!boundary.mounted) return
|
|
3090
|
+
boundary.pendingDuration += finished - started
|
|
3091
|
+
boundary.pendingStart =
|
|
3092
|
+
boundary.pendingStart === 0 ? started : Math.min(boundary.pendingStart, started)
|
|
3093
|
+
if (boundary.scheduled) return
|
|
3094
|
+
boundary.scheduled = true
|
|
3095
|
+
stagePublication([
|
|
3096
|
+
noop,
|
|
3097
|
+
noop,
|
|
3098
|
+
undefined,
|
|
3099
|
+
() => {
|
|
3100
|
+
const actualDuration = boundary.pendingDuration
|
|
3101
|
+
const startTime = boundary.pendingStart
|
|
3102
|
+
boundary.pendingDuration = 0
|
|
3103
|
+
boundary.pendingStart = 0
|
|
3104
|
+
boundary.scheduled = false
|
|
3105
|
+
boundary.onRender(
|
|
3106
|
+
boundary.id,
|
|
3107
|
+
'update',
|
|
3108
|
+
actualDuration,
|
|
3109
|
+
boundary.baseDuration,
|
|
3110
|
+
startTime,
|
|
3111
|
+
profileNow(),
|
|
3112
|
+
)
|
|
3113
|
+
},
|
|
3114
|
+
100,
|
|
3115
|
+
owner,
|
|
3116
|
+
])
|
|
3117
|
+
}
|
|
3118
|
+
|
|
3119
|
+
function readProfiledEffectCleanup(owner: Owner, evaluate: () => EffectResult): () => void {
|
|
3120
|
+
const cleanup = measureProfileWork(owner, 'effect', () => readEffectCleanup(evaluate()))
|
|
3121
|
+
if (cleanup === noop) return cleanup
|
|
3122
|
+
return () => measureProfileWork(owner, 'effect', cleanup)
|
|
3123
|
+
}
|
|
3124
|
+
|
|
3125
|
+
function createRetainedConnection(
|
|
3126
|
+
parent: RetainedConnection | null,
|
|
3127
|
+
visible: boolean,
|
|
3128
|
+
): RetainedConnection {
|
|
3129
|
+
const connection: RetainedConnection = {
|
|
3130
|
+
parent,
|
|
3131
|
+
children: new Set(),
|
|
3132
|
+
resources: new Set(),
|
|
3133
|
+
deferredFlushes: new Map(),
|
|
3134
|
+
afterFlush: new Set(),
|
|
3135
|
+
visible,
|
|
3136
|
+
connected: visible && (parent?.connected ?? true),
|
|
3137
|
+
disposed: false,
|
|
3138
|
+
}
|
|
3139
|
+
parent?.children.add(connection)
|
|
3140
|
+
return connection
|
|
3141
|
+
}
|
|
3142
|
+
|
|
3143
|
+
function createRetainedResource(
|
|
3144
|
+
owner: Owner,
|
|
3145
|
+
connect: () => void,
|
|
3146
|
+
disconnect: () => void,
|
|
3147
|
+
phase = 1,
|
|
3148
|
+
): readonly [activate: () => void, dispose: () => void] {
|
|
3149
|
+
const connection = owner[5]
|
|
3150
|
+
const resource: RetainedResource = {
|
|
3151
|
+
owner,
|
|
3152
|
+
connect,
|
|
3153
|
+
disconnect,
|
|
3154
|
+
active: false,
|
|
3155
|
+
connected: false,
|
|
3156
|
+
disposed: false,
|
|
3157
|
+
phase,
|
|
3158
|
+
}
|
|
3159
|
+
connection?.resources.add(resource)
|
|
3160
|
+
const activate = (): void => {
|
|
3161
|
+
if (resource.disposed) return
|
|
3162
|
+
resource.active = true
|
|
3163
|
+
reconcileRetainedResource(resource, connection?.connected ?? true)
|
|
3164
|
+
}
|
|
3165
|
+
const dispose = (): void => {
|
|
3166
|
+
if (resource.disposed) return
|
|
3167
|
+
resource.disposed = true
|
|
3168
|
+
resource.active = false
|
|
3169
|
+
connection?.resources.delete(resource)
|
|
3170
|
+
reconcileRetainedResource(resource, false)
|
|
3171
|
+
}
|
|
3172
|
+
return [activate, dispose]
|
|
3173
|
+
}
|
|
3174
|
+
|
|
3175
|
+
function reconcileRetainedResource(resource: RetainedResource, connected: boolean): void {
|
|
3176
|
+
const nextConnected = !resource.disposed && resource.active && connected
|
|
3177
|
+
if (resource.connected === nextConnected) return
|
|
3178
|
+
resource.connected = nextConnected
|
|
3179
|
+
try {
|
|
3180
|
+
runOwnerTask(resource.owner, nextConnected ? resource.connect : resource.disconnect)
|
|
3181
|
+
} catch (error) {
|
|
3182
|
+
resource.connected = false
|
|
3183
|
+
throw error
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
|
|
3187
|
+
function updateRetainedConnection(connection: RetainedConnection): void {
|
|
3188
|
+
if (connection.disposed) return
|
|
3189
|
+
const nextConnected = connection.visible && (connection.parent?.connected ?? true)
|
|
3190
|
+
if (connection.connected === nextConnected) return
|
|
3191
|
+
connection.connected = nextConnected
|
|
3192
|
+
if (nextConnected) {
|
|
3193
|
+
for (const [flush, cancel] of connection.deferredFlushes) {
|
|
3194
|
+
cancel()
|
|
3195
|
+
scheduleFlush(flush)
|
|
3196
|
+
}
|
|
3197
|
+
connection.deferredFlushes.clear()
|
|
3198
|
+
for (const resource of [...connection.resources].toSorted(
|
|
3199
|
+
(left, right) => left.phase - right.phase,
|
|
3200
|
+
)) {
|
|
3201
|
+
reconcileRetainedResource(resource, true)
|
|
3202
|
+
}
|
|
3203
|
+
} else {
|
|
3204
|
+
for (const resource of [...connection.resources].toReversed()) {
|
|
3205
|
+
reconcileRetainedResource(resource, false)
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
for (const child of connection.children) updateRetainedConnection(child)
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
function concealDisconnectedDescendants(connection: RetainedConnection): void {
|
|
3212
|
+
for (const child of connection.children) {
|
|
3213
|
+
if (!child.connected) {
|
|
3214
|
+
for (const conceal of child.afterFlush) conceal()
|
|
3215
|
+
}
|
|
3216
|
+
concealDisconnectedDescendants(child)
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
|
|
3220
|
+
function disposeRetainedConnection(connection: RetainedConnection): void {
|
|
3221
|
+
if (connection.disposed) return
|
|
3222
|
+
connection.disposed = true
|
|
3223
|
+
connection.parent?.children.delete(connection)
|
|
3224
|
+
for (const cancel of connection.deferredFlushes.values()) cancel()
|
|
3225
|
+
connection.deferredFlushes.clear()
|
|
3226
|
+
connection.afterFlush.clear()
|
|
3227
|
+
for (const child of connection.children) disposeRetainedConnection(child)
|
|
3228
|
+
connection.children.clear()
|
|
3229
|
+
for (const resource of [...connection.resources].toReversed()) {
|
|
3230
|
+
resource.disposed = true
|
|
3231
|
+
resource.active = false
|
|
3232
|
+
reconcileRetainedResource(resource, false)
|
|
3233
|
+
}
|
|
3234
|
+
connection.resources.clear()
|
|
3235
|
+
}
|
|
3236
|
+
|
|
3237
|
+
function notifyRetainedFlush(connection: RetainedConnection | null): void {
|
|
3238
|
+
for (let current = connection; current !== null; current = current.parent) {
|
|
3239
|
+
if (!current.connected) {
|
|
3240
|
+
for (const notify of current.afterFlush) notify()
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
|
|
3245
|
+
function createRootIdentity(
|
|
3246
|
+
identifierPrefix?: string,
|
|
3247
|
+
onCaughtError?: CompiledErrorHandler,
|
|
3248
|
+
onUncaughtError?: CompiledErrorHandler,
|
|
3249
|
+
): RootIdentity {
|
|
3250
|
+
return {
|
|
3251
|
+
mounted: false,
|
|
3252
|
+
nextId: 0,
|
|
3253
|
+
prefix: identifierPrefix ?? `v${nextClientRoot++}-`,
|
|
3254
|
+
onCaughtError,
|
|
3255
|
+
onUncaughtError,
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
|
|
3259
|
+
function commitRootPortals(rootIdentity: RootIdentity): void {
|
|
3260
|
+
const pending = pendingRootPortals.get(rootIdentity)
|
|
3261
|
+
if (pending === undefined) return
|
|
3262
|
+
pendingRootPortals.delete(rootIdentity)
|
|
3263
|
+
const committed: PortalPublication[] = []
|
|
3264
|
+
try {
|
|
3265
|
+
for (const publication of pending) {
|
|
3266
|
+
publication[0]()
|
|
3267
|
+
committed.push(publication)
|
|
3268
|
+
}
|
|
3269
|
+
} catch (error) {
|
|
3270
|
+
for (const publication of committed.toReversed()) publication[1]()
|
|
3271
|
+
throw error
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3275
|
+
function provideContext<T>(
|
|
3276
|
+
context: CompiledContext<T>,
|
|
3277
|
+
input: T | CompiledBinding<T>,
|
|
3278
|
+
children: CompiledRenderValue | readonly CompiledRenderValue[] | undefined,
|
|
3279
|
+
): StructuralBinding {
|
|
3280
|
+
const parentContext = activeContextFrame ?? activeOwner?.[2] ?? null
|
|
3281
|
+
let mounted = false
|
|
3282
|
+
return [
|
|
3283
|
+
STRUCTURAL,
|
|
3284
|
+
(parent, before) => {
|
|
3285
|
+
if (mounted) throw new Error(DEV ? 'context provider is already mounted' : 'V008')
|
|
3286
|
+
mounted = true
|
|
3287
|
+
withContextFrame(
|
|
3288
|
+
{
|
|
3289
|
+
context: context as CompiledContext<unknown>,
|
|
3290
|
+
input,
|
|
3291
|
+
parent: parentContext,
|
|
3292
|
+
owner: activeOwner,
|
|
3293
|
+
},
|
|
3294
|
+
() => insertValue(parent, children, before),
|
|
3295
|
+
)
|
|
3296
|
+
},
|
|
3297
|
+
'transparent',
|
|
3298
|
+
]
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
function contextInput<T>(
|
|
3302
|
+
scope: CompiledScope,
|
|
3303
|
+
context: CompiledContext<T>,
|
|
3304
|
+
): T | CompiledBinding<T> {
|
|
3305
|
+
const owner = scopeOwners.get(scope)
|
|
3306
|
+
if (owner === undefined) {
|
|
3307
|
+
throw new Error(DEV ? 'createCompiledContext received an unknown scope' : 'V003')
|
|
3308
|
+
}
|
|
3309
|
+
return contextInputFromFrame(owner[2], context)
|
|
3310
|
+
}
|
|
3311
|
+
|
|
3312
|
+
function contextInputFromFrame<T>(
|
|
3313
|
+
frame: ContextFrame | null,
|
|
3314
|
+
context: CompiledContext<T>,
|
|
3315
|
+
): T | CompiledBinding<T> {
|
|
3316
|
+
if (typeof context !== 'function' || !(CONTEXT in context)) {
|
|
3317
|
+
throw new TypeError(DEV ? 'useContext requires a context created by createContext' : 'V018')
|
|
3318
|
+
}
|
|
3319
|
+
for (let current = frame; current !== null; current = current.parent) {
|
|
3320
|
+
if (current.context === context) return current.input as T | CompiledBinding<T>
|
|
3321
|
+
}
|
|
3322
|
+
return context[CONTEXT]
|
|
3323
|
+
}
|
|
3324
|
+
|
|
3325
|
+
function withOwner<T>(owner: Owner, operation: () => T): T {
|
|
3326
|
+
const previous = activeOwner
|
|
3327
|
+
const previousContext = activeContextFrame
|
|
3328
|
+
const previousRootIdentity = activeRootIdentity
|
|
3329
|
+
const previousErrorOwner = activeErrorOwner
|
|
3330
|
+
activeOwner = owner
|
|
3331
|
+
activeContextFrame = owner[2]
|
|
3332
|
+
activeRootIdentity = owner[3]
|
|
3333
|
+
activeErrorOwner = owner
|
|
3334
|
+
try {
|
|
3335
|
+
return operation()
|
|
3336
|
+
} finally {
|
|
3337
|
+
activeOwner = previous
|
|
3338
|
+
activeContextFrame = previousContext
|
|
3339
|
+
activeRootIdentity = previousRootIdentity
|
|
3340
|
+
activeErrorOwner = previousErrorOwner
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
|
|
3344
|
+
function withContextFrame<T>(frame: ContextFrame | null, operation: () => T): T {
|
|
3345
|
+
const previous = activeContextFrame
|
|
3346
|
+
activeContextFrame = frame
|
|
3347
|
+
try {
|
|
3348
|
+
return operation()
|
|
3349
|
+
} finally {
|
|
3350
|
+
activeContextFrame = previous
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
|
|
3354
|
+
function withRootIdentity<T>(rootIdentity: RootIdentity | null, operation: () => T): T {
|
|
3355
|
+
const previous = activeRootIdentity
|
|
3356
|
+
activeRootIdentity = rootIdentity
|
|
3357
|
+
try {
|
|
3358
|
+
return operation()
|
|
3359
|
+
} finally {
|
|
3360
|
+
activeRootIdentity = previous
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
function onCleanup(cleanup: () => void): void {
|
|
3365
|
+
activeOwner?.[1].add(cleanup)
|
|
3366
|
+
}
|
|
3367
|
+
|
|
3368
|
+
function disposeOwner(owner: Owner): void {
|
|
3369
|
+
if (owner[0]) return
|
|
3370
|
+
owner[0] = true
|
|
3371
|
+
disposalCascadeDepth += 1
|
|
3372
|
+
disposalCascadeOwners.add(owner)
|
|
3373
|
+
if (DEV) activeOwnerCount -= 1
|
|
3374
|
+
pendingInsertionCommits.delete(owner)
|
|
3375
|
+
pendingOwnerCommits.delete(owner)
|
|
3376
|
+
const cleanups = [...owner[1]]
|
|
3377
|
+
owner[1].clear()
|
|
3378
|
+
let firstError: unknown
|
|
3379
|
+
let hasError = false
|
|
3380
|
+
try {
|
|
3381
|
+
for (let index = cleanups.length - 1; index >= 0; index -= 1) {
|
|
3382
|
+
try {
|
|
3383
|
+
cleanups[index]?.()
|
|
3384
|
+
} catch (error) {
|
|
3385
|
+
if (!hasError) firstError = error
|
|
3386
|
+
hasError = true
|
|
3387
|
+
}
|
|
3388
|
+
}
|
|
3389
|
+
} finally {
|
|
3390
|
+
disposalCascadeDepth -= 1
|
|
3391
|
+
if (disposalCascadeDepth === 0) disposalCascadeOwners.clear()
|
|
3392
|
+
}
|
|
3393
|
+
if (hasError) throw firstError
|
|
3394
|
+
}
|
|
3395
|
+
|
|
3396
|
+
/** @internal Test-only allocation and retention evidence. */
|
|
3397
|
+
export function readCompiledOwnerMetrics(): {
|
|
3398
|
+
readonly active: number
|
|
3399
|
+
readonly created: number
|
|
3400
|
+
} {
|
|
3401
|
+
return { active: activeOwnerCount, created: createdOwnerCount }
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3404
|
+
function mountCompiledBindingBefore(
|
|
3405
|
+
parent: Node,
|
|
3406
|
+
value: CompiledBinding<unknown>,
|
|
3407
|
+
before: Node | null,
|
|
3408
|
+
): void {
|
|
3409
|
+
const unset = Symbol(DEV ? 'Vidact.UnsetBinding' : undefined)
|
|
3410
|
+
const initial = value[1]()
|
|
3411
|
+
const scalarInitial = isScalarRenderValue(initial)
|
|
3412
|
+
const hydratedTextRange = scalarInitial
|
|
3413
|
+
? claimHydrationTextRange(parent, toText(initial))
|
|
3414
|
+
: undefined
|
|
3415
|
+
const borrowedHydrationRange =
|
|
3416
|
+
isHydrating() && !scalarInitial
|
|
3417
|
+
? borrowHydrationSlotRange(parent, isStructuralBinding(initial) && initial[2] === 'slot')
|
|
3418
|
+
: undefined
|
|
3419
|
+
const hydratedStructuralRange =
|
|
3420
|
+
isHydrating() && !scalarInitial
|
|
3421
|
+
? (borrowedHydrationRange ?? claimHydrationSlotRange(parent))
|
|
3422
|
+
: undefined
|
|
3423
|
+
const hydratedRange = hydratedTextRange ?? hydratedStructuralRange
|
|
3424
|
+
const start = hydratedRange?.[0] ?? document.createComment(DEV ? 'vidact:binding' : '')
|
|
3425
|
+
const end = hydratedRange?.[1] ?? document.createComment(DEV ? '/vidact:binding' : '')
|
|
3426
|
+
if (hydratedRange === undefined) {
|
|
3427
|
+
parent.insertBefore(start, before)
|
|
3428
|
+
parent.insertBefore(end, before)
|
|
3429
|
+
}
|
|
3430
|
+
let current: unknown = hydratedTextRange === undefined ? unset : initial
|
|
3431
|
+
let currentOwner: Owner | null = null
|
|
3432
|
+
let text: Text | null = hydratedTextRange?.[2] ?? null
|
|
3433
|
+
const stableRenderable = (renderable: RenderableProtocol): unknown => {
|
|
3434
|
+
const identity = renderableIdentity(renderable)
|
|
3435
|
+
let previous = renderablePropsSnapshot(renderable)
|
|
3436
|
+
const input = binding(
|
|
3437
|
+
value[2],
|
|
3438
|
+
value[3],
|
|
3439
|
+
() => {
|
|
3440
|
+
const next = value[1]()
|
|
3441
|
+
if (!isRenderableProtocol(next) || !Object.is(renderableIdentity(next), identity)) {
|
|
3442
|
+
return previous
|
|
3443
|
+
}
|
|
3444
|
+
previous = renderablePropsSnapshot(next)
|
|
3445
|
+
return previous
|
|
3446
|
+
},
|
|
3447
|
+
value[4],
|
|
3448
|
+
value[5],
|
|
3449
|
+
)
|
|
3450
|
+
return materializeRenderableWithInput(renderable, input)
|
|
3451
|
+
}
|
|
3452
|
+
|
|
3453
|
+
const clear = (): void => {
|
|
3454
|
+
const owner = currentOwner
|
|
3455
|
+
currentOwner = null
|
|
3456
|
+
text = null
|
|
3457
|
+
disposeRange(owner, start, end)
|
|
3458
|
+
}
|
|
3459
|
+
const update = (): void => {
|
|
3460
|
+
const next = value[1]()
|
|
3461
|
+
if (current !== unset && Object.is(next, current)) return
|
|
3462
|
+
if (
|
|
3463
|
+
current !== unset &&
|
|
3464
|
+
isRenderableProtocol(current) &&
|
|
3465
|
+
isRenderableProtocol(next) &&
|
|
3466
|
+
canReconcileRenderable(current) &&
|
|
3467
|
+
canReconcileRenderable(next) &&
|
|
3468
|
+
Object.is(renderableIdentity(current), renderableIdentity(next))
|
|
3469
|
+
) {
|
|
3470
|
+
current = next
|
|
3471
|
+
return
|
|
3472
|
+
}
|
|
3473
|
+
const currentParent = rangeParent(start, end, 'binding range')
|
|
3474
|
+
|
|
3475
|
+
if (isScalarRenderValue(next)) {
|
|
3476
|
+
const content = toText(next)
|
|
3477
|
+
if (text !== null) {
|
|
3478
|
+
const target = text
|
|
3479
|
+
const previousContent = target.data
|
|
3480
|
+
const previous = current
|
|
3481
|
+
stagePublication([
|
|
3482
|
+
() => {
|
|
3483
|
+
if (target.data !== content) target.data = content
|
|
3484
|
+
current = next
|
|
3485
|
+
},
|
|
3486
|
+
() => {
|
|
3487
|
+
if (target.data !== previousContent) target.data = previousContent
|
|
3488
|
+
current = previous
|
|
3489
|
+
},
|
|
3490
|
+
])
|
|
3491
|
+
return
|
|
3492
|
+
}
|
|
3493
|
+
clear()
|
|
3494
|
+
text = document.createTextNode(content)
|
|
3495
|
+
currentParent.insertBefore(text, end)
|
|
3496
|
+
current = next
|
|
3497
|
+
return
|
|
3498
|
+
}
|
|
3499
|
+
|
|
3500
|
+
const nextOwner = createOwner()
|
|
3501
|
+
const stagedValue =
|
|
3502
|
+
isRenderableProtocol(next) && canReconcileRenderable(next)
|
|
3503
|
+
? stableRenderable(next)
|
|
3504
|
+
: (next as RenderValue)
|
|
3505
|
+
const [fragment, staged] = stageValue(stagedValue as RenderValue, nextOwner)
|
|
3506
|
+
try {
|
|
3507
|
+
clear()
|
|
3508
|
+
} catch (error) {
|
|
3509
|
+
disposeOwner(nextOwner)
|
|
3510
|
+
throw error
|
|
3511
|
+
}
|
|
3512
|
+
currentOwner = nextOwner
|
|
3513
|
+
currentParent.insertBefore(fragment, end)
|
|
3514
|
+
commitPublishedNodes(staged)
|
|
3515
|
+
current = next
|
|
3516
|
+
}
|
|
3517
|
+
|
|
3518
|
+
if (hydratedStructuralRange !== undefined) {
|
|
3519
|
+
const nextOwner = createOwner()
|
|
3520
|
+
const [fragment, staged] = withHydrationInsertion(parent, end, () =>
|
|
3521
|
+
stageRender(
|
|
3522
|
+
() =>
|
|
3523
|
+
(isRenderableProtocol(initial) && canReconcileRenderable(initial)
|
|
3524
|
+
? stableRenderable(initial)
|
|
3525
|
+
: initial) as RenderValue,
|
|
3526
|
+
nextOwner,
|
|
3527
|
+
),
|
|
3528
|
+
)
|
|
3529
|
+
parent.insertBefore(fragment, end)
|
|
3530
|
+
commitPublishedNodes(staged)
|
|
3531
|
+
currentOwner = nextOwner
|
|
3532
|
+
current = initial
|
|
3533
|
+
} else if (hydratedRange === undefined) {
|
|
3534
|
+
update()
|
|
3535
|
+
}
|
|
3536
|
+
const removeUpdater = subscribeBinding(value, update)
|
|
3537
|
+
onCleanup(() => {
|
|
3538
|
+
removeUpdater()
|
|
3539
|
+
clear()
|
|
3540
|
+
if (borrowedHydrationRange === undefined) {
|
|
3541
|
+
start.remove()
|
|
3542
|
+
end.remove()
|
|
3543
|
+
}
|
|
3544
|
+
})
|
|
3545
|
+
}
|
|
3546
|
+
|
|
3547
|
+
function materialize(value: RenderValue): Node[] {
|
|
3548
|
+
const insertionPoint = hydrationInsertionPoint()
|
|
3549
|
+
if (insertionPoint !== undefined) {
|
|
3550
|
+
const [parent, before] = insertionPoint
|
|
3551
|
+
const first = hydrationCursor(parent)
|
|
3552
|
+
if (first === undefined) throw new Error('hydration insertion point requires active hydration')
|
|
3553
|
+
insertValue(parent, value, before)
|
|
3554
|
+
const after = hydrationCursor(parent)
|
|
3555
|
+
const nodes: Node[] = []
|
|
3556
|
+
for (let node = first; node !== null && node !== after; node = node.nextSibling) {
|
|
3557
|
+
nodes.push(node)
|
|
3558
|
+
}
|
|
3559
|
+
return nodes
|
|
3560
|
+
}
|
|
3561
|
+
const fragment = document.createDocumentFragment()
|
|
3562
|
+
const moves: NodePosition[] = []
|
|
3563
|
+
try {
|
|
3564
|
+
insertValue(fragment, value, null, moves)
|
|
3565
|
+
} catch (error) {
|
|
3566
|
+
restoreNodePositions(moves)
|
|
3567
|
+
throw error
|
|
3568
|
+
}
|
|
3569
|
+
return [...fragment.childNodes]
|
|
3570
|
+
}
|
|
3571
|
+
|
|
3572
|
+
function stageValue(
|
|
3573
|
+
value: RenderValue,
|
|
3574
|
+
owner: Owner,
|
|
3575
|
+
): readonly [fragment: DocumentFragment, nodes: readonly Node[]] {
|
|
3576
|
+
const insertionPoint = hydrationInsertionPoint()
|
|
3577
|
+
if (insertionPoint !== undefined) {
|
|
3578
|
+
const [parent, before] = insertionPoint
|
|
3579
|
+
const first = hydrationCursor(parent)
|
|
3580
|
+
if (first === undefined) throw new Error('hydration insertion point requires active hydration')
|
|
3581
|
+
withOwner(owner, () => insertValue(parent, value, before))
|
|
3582
|
+
const nodes: Node[] = []
|
|
3583
|
+
for (let node = first; node !== null && node !== before; node = node.nextSibling) {
|
|
3584
|
+
nodes.push(node)
|
|
3585
|
+
}
|
|
3586
|
+
return [document.createDocumentFragment(), nodes]
|
|
3587
|
+
}
|
|
3588
|
+
const fragment = document.createDocumentFragment()
|
|
3589
|
+
const moves: NodePosition[] = []
|
|
3590
|
+
try {
|
|
3591
|
+
withOwner(owner, () => insertValue(fragment, value, null, moves))
|
|
3592
|
+
} catch (error) {
|
|
3593
|
+
restoreNodePositions(moves)
|
|
3594
|
+
try {
|
|
3595
|
+
disposeOwner(owner)
|
|
3596
|
+
} catch {
|
|
3597
|
+
// Preserve the staging error; cleanup still ran every registered disposer.
|
|
3598
|
+
}
|
|
3599
|
+
throw error
|
|
3600
|
+
}
|
|
3601
|
+
return [fragment, [...fragment.childNodes]]
|
|
3602
|
+
}
|
|
3603
|
+
|
|
3604
|
+
function stageRender(
|
|
3605
|
+
render: () => CompiledRenderValue,
|
|
3606
|
+
owner: Owner,
|
|
3607
|
+
): readonly [fragment: DocumentFragment, nodes: readonly Node[]] {
|
|
3608
|
+
try {
|
|
3609
|
+
return stageValue(withOwner(owner, render), owner)
|
|
3610
|
+
} catch (error) {
|
|
3611
|
+
try {
|
|
3612
|
+
disposeOwner(owner)
|
|
3613
|
+
} catch {
|
|
3614
|
+
// Preserve the render or staging error after every registered cleanup ran.
|
|
3615
|
+
}
|
|
3616
|
+
throw error
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3619
|
+
|
|
3620
|
+
function insertValue(
|
|
3621
|
+
parent: Node,
|
|
3622
|
+
value: RenderValue,
|
|
3623
|
+
before: Node | null,
|
|
3624
|
+
moves?: NodePosition[],
|
|
3625
|
+
): void {
|
|
3626
|
+
if (value === null || value === undefined || typeof value === 'boolean') {
|
|
3627
|
+
claimHydrationText(parent, '')
|
|
3628
|
+
return
|
|
3629
|
+
}
|
|
3630
|
+
if (isStructuralBinding(value)) {
|
|
3631
|
+
if (isHydrating() && !isCompiledComponentResult(value) && value[2] === undefined) {
|
|
3632
|
+
throw new HydrationMismatch('structural binding hydration markers are not available')
|
|
3633
|
+
}
|
|
3634
|
+
adoptCompiledRoot(value)
|
|
3635
|
+
value[1](parent, before)
|
|
3636
|
+
return
|
|
3637
|
+
}
|
|
3638
|
+
if (isCompiledBinding(value)) {
|
|
3639
|
+
mountCompiledBindingBefore(parent, value, before)
|
|
3640
|
+
return
|
|
3641
|
+
}
|
|
3642
|
+
if (isRenderableProtocol(value)) {
|
|
3643
|
+
insertValue(parent, materializeRenderable(value) as RenderValue, before, moves)
|
|
3644
|
+
return
|
|
3645
|
+
}
|
|
3646
|
+
if (Array.isArray(value)) {
|
|
3647
|
+
if (hasInvalidChild(value)) {
|
|
3648
|
+
throw new TypeError(
|
|
3649
|
+
DEV ? 'unsupported compiled child value; expected a DOM node or owned block' : 'V009',
|
|
3650
|
+
)
|
|
3651
|
+
}
|
|
3652
|
+
const hydratedRange = claimHydrationArrayRange(parent)
|
|
3653
|
+
for (const item of value) insertValue(parent, item, before, moves)
|
|
3654
|
+
if (hydratedRange !== undefined) finishHydrationArrayRange(parent, hydratedRange[1])
|
|
3655
|
+
return
|
|
3656
|
+
}
|
|
3657
|
+
if (value instanceof DocumentFragment) {
|
|
3658
|
+
const hydrationChildren = hydrationFragmentChildren(value)
|
|
3659
|
+
if (hydrationChildren !== undefined) {
|
|
3660
|
+
for (const child of hydrationChildren) {
|
|
3661
|
+
insertValue(parent, child as RenderValue, before, moves)
|
|
3662
|
+
}
|
|
3663
|
+
return
|
|
3664
|
+
}
|
|
3665
|
+
// oxlint-disable-next-line unicorn/no-useless-spread -- Snapshot the live NodeList before moving nodes.
|
|
3666
|
+
for (const child of [...value.childNodes]) insertValue(parent, child, before, moves)
|
|
3667
|
+
return
|
|
3668
|
+
}
|
|
3669
|
+
if (value instanceof Node) {
|
|
3670
|
+
moves?.push([value, value.parentNode, value.nextSibling])
|
|
3671
|
+
adoptCompiledRoot(value)
|
|
3672
|
+
claimPendingRefOwners(value)
|
|
3673
|
+
if (!claimHydrationNode(parent, value)) parent.insertBefore(value, before)
|
|
3674
|
+
if (!(parent instanceof DocumentFragment)) commitPendingRefs(value)
|
|
3675
|
+
return
|
|
3676
|
+
}
|
|
3677
|
+
if (typeof value === 'object' || typeof value === 'function' || typeof value === 'symbol') {
|
|
3678
|
+
throw new TypeError(
|
|
3679
|
+
DEV ? 'unsupported compiled child value; expected a DOM node or owned block' : 'V009',
|
|
3680
|
+
)
|
|
3681
|
+
}
|
|
3682
|
+
const content = String(value)
|
|
3683
|
+
if (claimHydrationText(parent, content) === undefined) {
|
|
3684
|
+
parent.insertBefore(document.createTextNode(content), before)
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
|
|
3688
|
+
function restoreNodePositions(positions: readonly NodePosition[]): void {
|
|
3689
|
+
for (let index = positions.length - 1; index >= 0; index -= 1) {
|
|
3690
|
+
const position = positions[index]
|
|
3691
|
+
if (position === undefined) continue
|
|
3692
|
+
if (position[1] === null) {
|
|
3693
|
+
position[0].parentNode?.removeChild(position[0])
|
|
3694
|
+
continue
|
|
3695
|
+
}
|
|
3696
|
+
const before = position[2]?.parentNode === position[1] ? position[2] : null
|
|
3697
|
+
position[1].insertBefore(position[0], before)
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
|
|
3701
|
+
function removeBetween(start: Node, end: Node): void {
|
|
3702
|
+
let node = start.nextSibling
|
|
3703
|
+
while (node !== null && node !== end) {
|
|
3704
|
+
const next = node.nextSibling
|
|
3705
|
+
node.parentNode?.removeChild(node)
|
|
3706
|
+
node = next
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
|
|
3710
|
+
function nodesBetween(start: Node, end: Node): Node[] {
|
|
3711
|
+
const nodes: Node[] = []
|
|
3712
|
+
for (let node = start.nextSibling; node !== null && node !== end; node = node.nextSibling) {
|
|
3713
|
+
nodes.push(node)
|
|
3714
|
+
}
|
|
3715
|
+
return nodes
|
|
3716
|
+
}
|
|
3717
|
+
|
|
3718
|
+
function rangeParent(start: Node, end: Node, description: string): Node {
|
|
3719
|
+
const parent = end.parentNode
|
|
3720
|
+
if (parent === null || start.parentNode !== parent) {
|
|
3721
|
+
throw new Error(DEV ? `cannot update a detached ${description}` : 'V010')
|
|
3722
|
+
}
|
|
3723
|
+
return parent
|
|
3724
|
+
}
|
|
3725
|
+
|
|
3726
|
+
function disposeRange(owner: Owner | null, start: Node, end: Node): void {
|
|
3727
|
+
try {
|
|
3728
|
+
if (owner !== null) disposeOwner(owner)
|
|
3729
|
+
} finally {
|
|
3730
|
+
removeBetween(start, end)
|
|
3731
|
+
}
|
|
3732
|
+
}
|
|
3733
|
+
|
|
3734
|
+
function disposePublished(owner: Owner | null, nodes: readonly Node[]): void {
|
|
3735
|
+
try {
|
|
3736
|
+
if (owner !== null) disposeOwner(owner)
|
|
3737
|
+
} finally {
|
|
3738
|
+
for (const node of nodes) node.parentNode?.removeChild(node)
|
|
3739
|
+
}
|
|
3740
|
+
}
|
|
3741
|
+
|
|
3742
|
+
function toText(value: unknown): string {
|
|
3743
|
+
return value === null || value === undefined || typeof value === 'boolean' ? '' : String(value)
|
|
3744
|
+
}
|
|
3745
|
+
|
|
3746
|
+
function isScalarRenderValue(
|
|
3747
|
+
value: unknown,
|
|
3748
|
+
): value is string | number | bigint | boolean | null | undefined {
|
|
3749
|
+
return (
|
|
3750
|
+
value === null ||
|
|
3751
|
+
value === undefined ||
|
|
3752
|
+
typeof value === 'string' ||
|
|
3753
|
+
typeof value === 'number' ||
|
|
3754
|
+
typeof value === 'bigint' ||
|
|
3755
|
+
typeof value === 'boolean'
|
|
3756
|
+
)
|
|
3757
|
+
}
|
|
3758
|
+
|
|
3759
|
+
function isRefValue(value: unknown): value is RefValue {
|
|
3760
|
+
return (
|
|
3761
|
+
value === null ||
|
|
3762
|
+
value === undefined ||
|
|
3763
|
+
typeof value === 'function' ||
|
|
3764
|
+
(typeof value === 'object' && value !== null && 'current' in value)
|
|
3765
|
+
)
|
|
3766
|
+
}
|
|
3767
|
+
|
|
3768
|
+
function claimPendingRefOwners(root: Node): void {
|
|
3769
|
+
visitNodes(root, (node) => {
|
|
3770
|
+
if (!(node instanceof Element)) return
|
|
3771
|
+
const pending = pendingRefs.get(node)
|
|
3772
|
+
if (pending !== undefined && pending[0] === null) pending[0] = activeOwner
|
|
3773
|
+
})
|
|
3774
|
+
}
|
|
3775
|
+
|
|
3776
|
+
function commitPendingRefs(root: Node): void {
|
|
3777
|
+
if (!isHydrating()) commitPublishedNodesNow([root])
|
|
3778
|
+
}
|
|
3779
|
+
|
|
3780
|
+
function commitPublishedNodes(nodes: readonly Node[]): void {
|
|
3781
|
+
if (isHydrating()) return
|
|
3782
|
+
commitPublishedNodesNow(nodes)
|
|
3783
|
+
}
|
|
3784
|
+
|
|
3785
|
+
function commitPublishedNodesNow(nodes: readonly Node[]): void {
|
|
3786
|
+
const publicationRoot = nodes[0]?.getRootNode()
|
|
3787
|
+
if (publicationRoot instanceof DocumentFragment && !(publicationRoot instanceof ShadowRoot)) {
|
|
3788
|
+
return
|
|
3789
|
+
}
|
|
3790
|
+
for (const node of nodes) commitNodeInsertions(node)
|
|
3791
|
+
for (const node of nodes) commitNodeRefs(node)
|
|
3792
|
+
for (const node of nodes) commitNodeResources(node)
|
|
3793
|
+
}
|
|
3794
|
+
|
|
3795
|
+
function commitNodeInsertions(root: Node): void {
|
|
3796
|
+
visitNodes(root, (node) => {
|
|
3797
|
+
if (node instanceof Comment) commitOwnerInsertions(componentCommitOwners.get(node))
|
|
3798
|
+
})
|
|
3799
|
+
}
|
|
3800
|
+
|
|
3801
|
+
function commitNodeRefs(root: Node): void {
|
|
3802
|
+
visitNodes(root, (node) => {
|
|
3803
|
+
if (node instanceof Element) {
|
|
3804
|
+
const pending = pendingRefs.get(node)
|
|
3805
|
+
if (pending !== undefined) {
|
|
3806
|
+
pendingRefs.delete(node)
|
|
3807
|
+
const owner = pending[0] ?? activeOwner
|
|
3808
|
+
commitContextInsertions(owner?.[2] ?? null)
|
|
3809
|
+
commitOwnerInsertions(owner ?? undefined)
|
|
3810
|
+
const cleanup = attachRef(pending[1], node)
|
|
3811
|
+
if (pending[2] === undefined) owner?.[1].add(cleanup)
|
|
3812
|
+
else pending[2](cleanup)
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
3815
|
+
})
|
|
3816
|
+
}
|
|
3817
|
+
|
|
3818
|
+
function commitContextInsertions(frame: ContextFrame | null): void {
|
|
3819
|
+
if (frame === null) return
|
|
3820
|
+
commitContextInsertions(frame.parent)
|
|
3821
|
+
commitOwnerInsertions(frame.owner ?? undefined)
|
|
3822
|
+
}
|
|
3823
|
+
|
|
3824
|
+
function commitNodeResources(root: Node): void {
|
|
3825
|
+
visitNodes(root, (node) => {
|
|
3826
|
+
if (node instanceof Comment) commitOwnerResources(componentCommitOwners.get(node))
|
|
3827
|
+
})
|
|
3828
|
+
}
|
|
3829
|
+
|
|
3830
|
+
function commitOwnerInsertions(owner: Owner | undefined): void {
|
|
3831
|
+
if (owner === undefined || owner[0]) return
|
|
3832
|
+
const commits = pendingInsertionCommits.get(owner)
|
|
3833
|
+
if (commits === undefined) return
|
|
3834
|
+
pendingInsertionCommits.delete(owner)
|
|
3835
|
+
for (const commit of commits) commit()
|
|
3836
|
+
}
|
|
3837
|
+
|
|
3838
|
+
function commitOwnerResources(owner: Owner | undefined): void {
|
|
3839
|
+
if (owner === undefined || owner[0]) return
|
|
3840
|
+
const commits = pendingOwnerCommits.get(owner)
|
|
3841
|
+
if (commits === undefined) return
|
|
3842
|
+
pendingOwnerCommits.delete(owner)
|
|
3843
|
+
for (const commit of commits) commit()
|
|
3844
|
+
}
|
|
3845
|
+
|
|
3846
|
+
function commitRangeRefs(start: Node, end: Node): void {
|
|
3847
|
+
const nodes: Node[] = []
|
|
3848
|
+
let node = start.nextSibling
|
|
3849
|
+
while (node !== null && node !== end) {
|
|
3850
|
+
nodes.push(node)
|
|
3851
|
+
node = node.nextSibling
|
|
3852
|
+
}
|
|
3853
|
+
commitPublishedNodesNow(nodes)
|
|
3854
|
+
}
|
|
3855
|
+
|
|
3856
|
+
function visitNodes(root: Node, visit: (node: Node) => void): void {
|
|
3857
|
+
visit(root)
|
|
3858
|
+
for (const child of root.childNodes) visitNodes(child, visit)
|
|
3859
|
+
}
|
|
3860
|
+
|
|
3861
|
+
function attachRef<T>(value: RefValue<T>, target: T): () => void {
|
|
3862
|
+
if (value === null || value === undefined) return noop
|
|
3863
|
+
if (typeof value === 'function') {
|
|
3864
|
+
const cleanup = value(target)
|
|
3865
|
+
return typeof cleanup === 'function' ? cleanup : () => value(null)
|
|
3866
|
+
}
|
|
3867
|
+
value.current = target
|
|
3868
|
+
return () => {
|
|
3869
|
+
if (value.current === target) value.current = null
|
|
3870
|
+
}
|
|
3871
|
+
}
|
|
3872
|
+
|
|
3873
|
+
function scheduleFlush(flush: () => void): void {
|
|
3874
|
+
scheduledFlushes.add(flush)
|
|
3875
|
+
if (transactionDepth === 0) drainFlushes()
|
|
3876
|
+
}
|
|
3877
|
+
|
|
3878
|
+
function scheduleOwnerFlush(owner: Owner, flush: () => void): void {
|
|
3879
|
+
const connection = owner[5]
|
|
3880
|
+
if (connection === null || connection === undefined || connection.connected) {
|
|
3881
|
+
scheduleFlush(flush)
|
|
3882
|
+
return
|
|
3883
|
+
}
|
|
3884
|
+
if (connection.deferredFlushes.has(flush)) return
|
|
3885
|
+
const cancel = scheduleDeferredTask(() => {
|
|
3886
|
+
connection.deferredFlushes.delete(flush)
|
|
3887
|
+
scheduleFlush(flush)
|
|
3888
|
+
})
|
|
3889
|
+
connection.deferredFlushes.set(flush, cancel)
|
|
3890
|
+
}
|
|
3891
|
+
|
|
3892
|
+
function drainFlushes(): void {
|
|
3893
|
+
if (drainingFlushes || transactionDepth > 0) return
|
|
3894
|
+
drainingFlushes = true
|
|
3895
|
+
const publication: PublicationOperation[] = []
|
|
3896
|
+
activePublication = publication
|
|
3897
|
+
const runs = new Map<() => void, number>()
|
|
3898
|
+
let committing = false
|
|
3899
|
+
let failure: unknown
|
|
3900
|
+
let failed = false
|
|
3901
|
+
try {
|
|
3902
|
+
while (scheduledFlushes.size > 0) {
|
|
3903
|
+
const flush = scheduledFlushes.values().next().value
|
|
3904
|
+
if (flush === undefined) break
|
|
3905
|
+
scheduledFlushes.delete(flush)
|
|
3906
|
+
const runCount = (runs.get(flush) ?? 0) + 1
|
|
3907
|
+
if (runCount > MAX_FLUSH_PASSES) {
|
|
3908
|
+
scheduledFlushes.clear()
|
|
3909
|
+
throw new Error(DEV ? 'Vidact compiled scopes did not stabilize' : 'V011')
|
|
3910
|
+
}
|
|
3911
|
+
runs.set(flush, runCount)
|
|
3912
|
+
flush()
|
|
3913
|
+
}
|
|
3914
|
+
activePublication = null
|
|
3915
|
+
committing = true
|
|
3916
|
+
commitPublication(publication)
|
|
3917
|
+
} catch (error) {
|
|
3918
|
+
activePublication = null
|
|
3919
|
+
if (!committing) abortPublication(publication)
|
|
3920
|
+
failure = error
|
|
3921
|
+
failed = true
|
|
3922
|
+
} finally {
|
|
3923
|
+
activePublication = null
|
|
3924
|
+
drainingFlushes = false
|
|
3925
|
+
}
|
|
3926
|
+
if (failed) {
|
|
3927
|
+
const owner = failedOwner
|
|
3928
|
+
failedOwner = null
|
|
3929
|
+
if (!routeOwnerError(owner, failure)) throw failure
|
|
3930
|
+
}
|
|
3931
|
+
if (scheduledFlushes.size > 0) drainFlushes()
|
|
3932
|
+
}
|
|
3933
|
+
|
|
3934
|
+
function stagePublication(operation: PublicationOperation): void {
|
|
3935
|
+
if (activePublication === null) {
|
|
3936
|
+
operation[0]()
|
|
3937
|
+
try {
|
|
3938
|
+
operation[3]?.()
|
|
3939
|
+
} catch (error) {
|
|
3940
|
+
operation[1]()
|
|
3941
|
+
throw error
|
|
3942
|
+
}
|
|
3943
|
+
return
|
|
3944
|
+
}
|
|
3945
|
+
activePublication.push([
|
|
3946
|
+
operation[0],
|
|
3947
|
+
operation[1],
|
|
3948
|
+
operation[2],
|
|
3949
|
+
operation[3],
|
|
3950
|
+
operation[4],
|
|
3951
|
+
operation[5] ?? activeErrorOwner,
|
|
3952
|
+
])
|
|
3953
|
+
}
|
|
3954
|
+
|
|
3955
|
+
function commitPublication(operations: readonly PublicationOperation[]): void {
|
|
3956
|
+
const ordered = operations.some((operation) => operation[4] !== undefined)
|
|
3957
|
+
? operations.toSorted((left, right) => (left[4] ?? 0) - (right[4] ?? 0))
|
|
3958
|
+
: operations
|
|
3959
|
+
const applied: PublicationOperation[] = []
|
|
3960
|
+
let current: PublicationOperation | undefined
|
|
3961
|
+
try {
|
|
3962
|
+
for (const operation of ordered) {
|
|
3963
|
+
current = operation
|
|
3964
|
+
applied.push(operation)
|
|
3965
|
+
operation[0]()
|
|
3966
|
+
}
|
|
3967
|
+
} catch (error) {
|
|
3968
|
+
for (let index = applied.length - 1; index >= 0; index -= 1) {
|
|
3969
|
+
try {
|
|
3970
|
+
applied[index]?.[1]()
|
|
3971
|
+
} catch {
|
|
3972
|
+
// Preserve the publication error while attempting every inverse.
|
|
3973
|
+
}
|
|
3974
|
+
}
|
|
3975
|
+
for (const operation of ordered.slice(applied.length)) {
|
|
3976
|
+
try {
|
|
3977
|
+
operation[2]?.()
|
|
3978
|
+
} catch {
|
|
3979
|
+
// Preserve the publication error while disposing every staged value.
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3982
|
+
failedOwner = current?.[5] ?? null
|
|
3983
|
+
throw error
|
|
3984
|
+
}
|
|
3985
|
+
try {
|
|
3986
|
+
for (const operation of ordered) {
|
|
3987
|
+
current = operation
|
|
3988
|
+
operation[3]?.()
|
|
3989
|
+
}
|
|
3990
|
+
} catch (error) {
|
|
3991
|
+
for (let index = applied.length - 1; index >= 0; index -= 1) {
|
|
3992
|
+
try {
|
|
3993
|
+
applied[index]?.[1]()
|
|
3994
|
+
} catch {
|
|
3995
|
+
// Preserve the finalization error while attempting every inverse.
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
failedOwner = current?.[5] ?? null
|
|
3999
|
+
throw error
|
|
4000
|
+
}
|
|
4001
|
+
}
|
|
4002
|
+
|
|
4003
|
+
function runOwnerTask(owner: Owner, operation: () => void): void {
|
|
4004
|
+
try {
|
|
4005
|
+
operation()
|
|
4006
|
+
} catch (error) {
|
|
4007
|
+
if (!routeOwnerError(owner, error)) throw error
|
|
4008
|
+
}
|
|
4009
|
+
}
|
|
4010
|
+
|
|
4011
|
+
function routeOwnerError(owner: Owner | null, failure: unknown): boolean {
|
|
4012
|
+
let error = failure
|
|
4013
|
+
let boundary = owner?.[4] ?? null
|
|
4014
|
+
while (boundary !== null) {
|
|
4015
|
+
try {
|
|
4016
|
+
boundary.handle(error)
|
|
4017
|
+
return true
|
|
4018
|
+
} catch (nextError) {
|
|
4019
|
+
error = nextError
|
|
4020
|
+
boundary = boundary.parent
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
4023
|
+
const onUncaughtError = owner?.[3].onUncaughtError
|
|
4024
|
+
if (onUncaughtError === undefined) return false
|
|
4025
|
+
onUncaughtError(error)
|
|
4026
|
+
return true
|
|
4027
|
+
}
|
|
4028
|
+
|
|
4029
|
+
export function isCompiledContext<Value>(value: unknown): value is CompiledContext<Value> {
|
|
4030
|
+
return typeof value === 'function' && CONTEXT in value
|
|
4031
|
+
}
|
|
4032
|
+
|
|
4033
|
+
function abortPublication(operations: readonly PublicationOperation[]): void {
|
|
4034
|
+
for (let index = operations.length - 1; index >= 0; index -= 1) {
|
|
4035
|
+
try {
|
|
4036
|
+
operations[index]?.[2]?.()
|
|
4037
|
+
} catch {
|
|
4038
|
+
// Preserve the computation error while disposing every staged value.
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
}
|