@vidact/runtime 0.2.0-beta.6 → 0.2.0-beta.8
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 +13 -0
- package/dist/actions.js +1 -1
- package/dist/compiled/core.d.ts +17 -5
- package/dist/compiled/core.d.ts.map +1 -1
- package/dist/compiled/core.js +545 -155
- package/dist/compiled/core.js.map +1 -1
- package/dist/direct-dom.d.ts.map +1 -1
- package/dist/direct-dom.js +102 -11
- package/dist/direct-dom.js.map +1 -1
- package/dist/dom/events.d.ts +4 -1
- package/dist/dom/events.d.ts.map +1 -1
- package/dist/dom/events.js +66 -1
- package/dist/dom/events.js.map +1 -1
- package/dist/dom/namespace.js +1 -1
- package/dist/hydration-bridge.d.ts +4 -4
- package/dist/hydration-bridge.d.ts.map +1 -1
- package/dist/hydration-bridge.js.map +1 -1
- package/dist/hydration.d.ts +2 -2
- package/dist/hydration.d.ts.map +1 -1
- package/dist/hydration.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/keyed-list.d.ts +6 -2
- package/dist/keyed-list.d.ts.map +1 -1
- package/dist/keyed-list.js +122 -11
- package/dist/keyed-list.js.map +1 -1
- package/dist/metadata.js +1 -1
- package/dist/renderable.d.ts +2 -1
- package/dist/renderable.d.ts.map +1 -1
- package/dist/renderable.js +12 -1
- package/dist/renderable.js.map +1 -1
- package/dist/source-mask.d.ts +2 -1
- package/dist/source-mask.d.ts.map +1 -1
- package/dist/source-mask.js +16 -1
- package/dist/source-mask.js.map +1 -1
- package/dist/spread.js +2 -2
- package/dist/state-slot.d.ts +12 -1
- package/dist/state-slot.d.ts.map +1 -1
- package/dist/state-slot.js +54 -24
- package/dist/state-slot.js.map +1 -1
- package/package.json +3 -3
- package/src/compiled/core.ts +785 -179
- package/src/direct-dom.ts +136 -10
- package/src/dom/events.ts +86 -0
- package/src/hydration-bridge.ts +10 -4
- package/src/hydration.ts +5 -3
- package/src/index.ts +2 -0
- package/src/keyed-list.ts +168 -22
- package/src/renderable.ts +30 -0
- package/src/source-mask.ts +20 -0
- package/src/state-slot.ts +83 -27
package/src/compiled/core.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { COMPILED_DELEGATED_EVENT_INVOKE, retainCompiledEventRoot } from '../dom/events.ts'
|
|
1
2
|
import {
|
|
2
3
|
currentIntrinsicNamespace,
|
|
3
4
|
withIntrinsicNamespace,
|
|
@@ -44,8 +45,20 @@ import {
|
|
|
44
45
|
type RenderableProtocol,
|
|
45
46
|
} from '../renderable-protocol.ts'
|
|
46
47
|
import { scheduleDeferredTask, scheduleTask, type CancelScheduledTask } from '../scheduler.ts'
|
|
47
|
-
import {
|
|
48
|
-
|
|
48
|
+
import {
|
|
49
|
+
forEachSource,
|
|
50
|
+
intersectsSources,
|
|
51
|
+
isEmptySources,
|
|
52
|
+
unionSources,
|
|
53
|
+
type SourceMask,
|
|
54
|
+
} from '../source-mask.ts'
|
|
55
|
+
import {
|
|
56
|
+
createReadOnlyStateSlot,
|
|
57
|
+
createStateSlot,
|
|
58
|
+
replaceReadOnlyStateSlot,
|
|
59
|
+
type ReadOnlyStateCell,
|
|
60
|
+
type StateSlot,
|
|
61
|
+
} from '../state-slot.ts'
|
|
49
62
|
import { isSuspension, subscribeResource, useAsync } from './async-resource.ts'
|
|
50
63
|
import {
|
|
51
64
|
BINDING,
|
|
@@ -72,7 +85,28 @@ let retainedUiEnabled = typeof __VIDACT_RETAINED_UI__ !== 'undefined' && __VIDAC
|
|
|
72
85
|
let profilingEnabled = false
|
|
73
86
|
let activeOwnerCount = 0
|
|
74
87
|
let createdOwnerCount = 0
|
|
88
|
+
let createdUpdaterCount = 0
|
|
89
|
+
let createdSchedulerPlanCount = 0
|
|
75
90
|
const noop = (): void => {}
|
|
91
|
+
const COMPILED_EVENT_HANDLER = Symbol('vidact.compiled-event')
|
|
92
|
+
const COMPILED_INLINE_EVENT_OWNER = Symbol('vidact.compiled-event.owner')
|
|
93
|
+
const UNSET_BINDING = Symbol(DEV ? 'Vidact.UnsetBinding' : undefined)
|
|
94
|
+
|
|
95
|
+
type CompiledEventHandler = ((...arguments_: never[]) => void) & {
|
|
96
|
+
[COMPILED_EVENT_HANDLER]: true
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
type CompiledInlineEventHandler<Arguments extends unknown[]> = ((
|
|
100
|
+
...arguments_: Arguments
|
|
101
|
+
) => void) & {
|
|
102
|
+
[COMPILED_INLINE_EVENT_OWNER]: Owner
|
|
103
|
+
[COMPILED_DELEGATED_EVENT_INVOKE]: (...arguments_: Arguments) => void
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
type CompiledEventMetadata = {
|
|
107
|
+
[COMPILED_EVENT_HANDLER]?: true
|
|
108
|
+
[COMPILED_DELEGATED_EVENT_INVOKE]?: (...arguments_: never[]) => void
|
|
109
|
+
}
|
|
76
110
|
|
|
77
111
|
type ContextFrame = {
|
|
78
112
|
readonly context: CompiledContext<unknown>
|
|
@@ -98,7 +132,7 @@ type PortalPublication = readonly [commit: () => void, rollback: () => void]
|
|
|
98
132
|
|
|
99
133
|
type Owner = [
|
|
100
134
|
disposed: boolean,
|
|
101
|
-
cleanups:
|
|
135
|
+
cleanups: Array<() => void>,
|
|
102
136
|
context: ContextFrame | null,
|
|
103
137
|
rootIdentity: RootIdentity,
|
|
104
138
|
errorBoundary: ErrorBoundaryHandler | null,
|
|
@@ -139,11 +173,13 @@ type RetainedResource = {
|
|
|
139
173
|
readonly phase: number
|
|
140
174
|
}
|
|
141
175
|
|
|
176
|
+
type FlushTask = (() => void) | { readonly [1]: (sources?: SourceMask) => void }
|
|
177
|
+
|
|
142
178
|
type RetainedConnection = {
|
|
143
179
|
readonly parent: RetainedConnection | null
|
|
144
180
|
readonly children: Set<RetainedConnection>
|
|
145
181
|
readonly resources: Set<RetainedResource>
|
|
146
|
-
readonly deferredFlushes: Map<
|
|
182
|
+
readonly deferredFlushes: Map<FlushTask, CancelScheduledTask>
|
|
147
183
|
readonly afterFlush: Set<() => void>
|
|
148
184
|
visible: boolean
|
|
149
185
|
connected: boolean
|
|
@@ -161,13 +197,20 @@ type SourceOperations = readonly [
|
|
|
161
197
|
empty: (mask: SourceMask) => boolean,
|
|
162
198
|
intersects: (left: SourceMask, right: SourceMask) => boolean,
|
|
163
199
|
union: (left: SourceMask, right: SourceMask) => SourceMask,
|
|
200
|
+
visit: (mask: SourceMask, visitor: (index: number) => void) => void,
|
|
164
201
|
]
|
|
165
202
|
|
|
166
|
-
const wideSourceOperations: SourceOperations = [
|
|
203
|
+
const wideSourceOperations: SourceOperations = [
|
|
204
|
+
isEmptySources,
|
|
205
|
+
intersectsSources,
|
|
206
|
+
unionSources,
|
|
207
|
+
forEachSource,
|
|
208
|
+
]
|
|
167
209
|
const narrowSourceOperations: SourceOperations = [
|
|
168
210
|
(mask) => mask === 0,
|
|
169
211
|
(left, right) => ((left as number) & (right as number)) !== 0,
|
|
170
212
|
(left, right) => ((left as number) | (right as number)) >>> 0,
|
|
213
|
+
(mask, visitor) => forEachSource(mask, visitor),
|
|
171
214
|
]
|
|
172
215
|
|
|
173
216
|
type RenderValue = CompiledRenderValue
|
|
@@ -176,9 +219,18 @@ export function deferred(render: () => CompiledRenderValue): StructuralBinding {
|
|
|
176
219
|
return [
|
|
177
220
|
STRUCTURAL,
|
|
178
221
|
(parent, before) => {
|
|
179
|
-
const
|
|
180
|
-
if (
|
|
181
|
-
|
|
222
|
+
const value = render()
|
|
223
|
+
if (ownedStructuralBinding(value)?.[2] === 'slot') {
|
|
224
|
+
insertValue(parent, value, before)
|
|
225
|
+
return
|
|
226
|
+
}
|
|
227
|
+
const hydrated = borrowHydrationSlotRange(parent, true)
|
|
228
|
+
if (hydrated === undefined) {
|
|
229
|
+
insertValue(parent, value, before)
|
|
230
|
+
return
|
|
231
|
+
}
|
|
232
|
+
claimHydrationSlotRange(parent)
|
|
233
|
+
insertValue(parent, value, hydrated[1])
|
|
182
234
|
},
|
|
183
235
|
'slot',
|
|
184
236
|
]
|
|
@@ -453,7 +505,7 @@ export function recordCompiledDebugValue<Value>(
|
|
|
453
505
|
const remove = subscribeBinding(valueInput, () => {
|
|
454
506
|
frame.values[index] = read()
|
|
455
507
|
})
|
|
456
|
-
owner[1].
|
|
508
|
+
owner[1].push(remove)
|
|
457
509
|
}
|
|
458
510
|
}
|
|
459
511
|
|
|
@@ -810,6 +862,7 @@ function createPortalStructural(
|
|
|
810
862
|
const portalOwner = withRootIdentity(rootIdentity, () =>
|
|
811
863
|
withContextFrame(context, createOwner),
|
|
812
864
|
)
|
|
865
|
+
let releaseEventRoot: (() => void) | undefined
|
|
813
866
|
try {
|
|
814
867
|
withOwner(portalOwner, () => insertValue(fragment, children, end))
|
|
815
868
|
} catch (error) {
|
|
@@ -818,12 +871,15 @@ function createPortalStructural(
|
|
|
818
871
|
throw error
|
|
819
872
|
}
|
|
820
873
|
const rollback = (): void => {
|
|
874
|
+
releaseEventRoot?.()
|
|
875
|
+
releaseEventRoot = undefined
|
|
821
876
|
removeBetween(start, end)
|
|
822
877
|
start.remove()
|
|
823
878
|
end.remove()
|
|
824
879
|
}
|
|
825
880
|
const publication: PortalPublication = [
|
|
826
881
|
() => {
|
|
882
|
+
releaseEventRoot = retainCompiledEventRoot(container)
|
|
827
883
|
container.insertBefore(fragment, null)
|
|
828
884
|
try {
|
|
829
885
|
commitRangeRefs(start, end)
|
|
@@ -893,7 +949,7 @@ let transactionDepth = 0
|
|
|
893
949
|
let disposalCascadeDepth = 0
|
|
894
950
|
let drainingFlushes = false
|
|
895
951
|
let activePublication: PublicationOperation[] | null = null
|
|
896
|
-
const scheduledFlushes = new Set<
|
|
952
|
+
const scheduledFlushes = new Set<FlushTask>()
|
|
897
953
|
const disposalCascadeOwners = new Set<Owner>()
|
|
898
954
|
const scopeOwners = new WeakMap<CompiledScope, Owner>()
|
|
899
955
|
const scopeNamespaces = new WeakMap<CompiledScope, IntrinsicNamespace>()
|
|
@@ -903,6 +959,22 @@ const componentCommitOwners = new WeakMap<Comment, Owner>()
|
|
|
903
959
|
const pendingInsertionCommits = new WeakMap<Owner, Set<() => void>>()
|
|
904
960
|
const pendingOwnerCommits = new WeakMap<Owner, Set<() => void>>()
|
|
905
961
|
const pendingRootPortals = new WeakMap<RootIdentity, Set<PortalPublication>>()
|
|
962
|
+
let pendingRefCount = 0
|
|
963
|
+
let pendingInsertionOwnerCount = 0
|
|
964
|
+
let pendingOwnerCommitCount = 0
|
|
965
|
+
let visitedPublishedNodeCount = 0
|
|
966
|
+
|
|
967
|
+
function setPendingRef(element: Element, pending: PendingRef): void {
|
|
968
|
+
if (!pendingRefs.has(element)) pendingRefCount += 1
|
|
969
|
+
pendingRefs.set(element, pending)
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
function deletePendingRef(element: Element, expected?: PendingRef): boolean {
|
|
973
|
+
if (expected !== undefined && pendingRefs.get(element) !== expected) return false
|
|
974
|
+
if (!pendingRefs.delete(element)) return false
|
|
975
|
+
pendingRefCount -= 1
|
|
976
|
+
return true
|
|
977
|
+
}
|
|
906
978
|
|
|
907
979
|
export function createCompiledScope(): CompiledScope {
|
|
908
980
|
return createScope(wideSourceOperations)
|
|
@@ -912,6 +984,171 @@ export function createNarrowCompiledScope(): CompiledScope {
|
|
|
912
984
|
return createScope(narrowSourceOperations)
|
|
913
985
|
}
|
|
914
986
|
|
|
987
|
+
type CompilerKeyedUpdater = [
|
|
988
|
+
reads: SourceMask,
|
|
989
|
+
writes: SourceMask | undefined,
|
|
990
|
+
run: (active: SourceMask) => void,
|
|
991
|
+
active: boolean,
|
|
992
|
+
errorOwner: Owner,
|
|
993
|
+
context: ContextFrame | null,
|
|
994
|
+
]
|
|
995
|
+
|
|
996
|
+
/** Compiler-emitted map callbacks only need a compact, narrow row scheduler. */
|
|
997
|
+
class CompilerKeyedScope {
|
|
998
|
+
readonly owner = createOwner()
|
|
999
|
+
readonly updaters: Array<CompilerKeyedUpdater | undefined> = []
|
|
1000
|
+
pending: SourceMask = 0
|
|
1001
|
+
batchDepth = 0
|
|
1002
|
+
flushing = false
|
|
1003
|
+
hasWriter = false
|
|
1004
|
+
cachedUpdaterOrder: number[] | undefined;
|
|
1005
|
+
|
|
1006
|
+
[1](sources?: SourceMask): void {
|
|
1007
|
+
if (this.owner[0]) return
|
|
1008
|
+
if (sources === undefined) {
|
|
1009
|
+
this.flush()
|
|
1010
|
+
return
|
|
1011
|
+
}
|
|
1012
|
+
if (narrowSourceOperations[0](sources)) return
|
|
1013
|
+
const wasIdle = narrowSourceOperations[0](this.pending)
|
|
1014
|
+
this.pending = narrowSourceOperations[2](this.pending, sources)
|
|
1015
|
+
if (wasIdle && this.batchDepth === 0) this.schedule()
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
[3](): void {
|
|
1019
|
+
if (this.owner[0]) return
|
|
1020
|
+
try {
|
|
1021
|
+
disposeOwner(this.owner)
|
|
1022
|
+
} finally {
|
|
1023
|
+
this.pending = 0
|
|
1024
|
+
for (const updater of this.updaters) {
|
|
1025
|
+
if (updater !== undefined) updater[3] = false
|
|
1026
|
+
}
|
|
1027
|
+
this.updaters.length = 0
|
|
1028
|
+
this.cachedUpdaterOrder = undefined
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
constructor() {
|
|
1033
|
+
const scope = this as unknown as CompiledScope
|
|
1034
|
+
scopeOwners.set(scope, this.owner)
|
|
1035
|
+
scopeNamespaces.set(scope, currentIntrinsicNamespace())
|
|
1036
|
+
activeScopeCollector?.add(scope)
|
|
1037
|
+
if (activeScopeCollector !== null) activeConstructionOwner = this.owner
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
[0](reads: SourceMask, run: (active: SourceMask) => void, writes?: SourceMask): () => void {
|
|
1041
|
+
if (this.owner[0]) {
|
|
1042
|
+
throw new Error(DEV ? 'cannot add an updater to a disposed scope' : 'V002')
|
|
1043
|
+
}
|
|
1044
|
+
const errorOwner = activeOwner ?? this.owner
|
|
1045
|
+
const entry: CompilerKeyedUpdater = [
|
|
1046
|
+
reads,
|
|
1047
|
+
writes,
|
|
1048
|
+
run,
|
|
1049
|
+
true,
|
|
1050
|
+
errorOwner,
|
|
1051
|
+
activeContextFrame ?? errorOwner[2],
|
|
1052
|
+
]
|
|
1053
|
+
if (DEV) createdUpdaterCount += 1
|
|
1054
|
+
const index = this.updaters.length
|
|
1055
|
+
this.updaters.push(entry)
|
|
1056
|
+
this.hasWriter ||= writes !== undefined
|
|
1057
|
+
this.cachedUpdaterOrder = undefined
|
|
1058
|
+
if (activeOwner === this.owner) return noop
|
|
1059
|
+
const remove = (): void => {
|
|
1060
|
+
if (this.updaters[index] !== entry) return
|
|
1061
|
+
entry[3] = false
|
|
1062
|
+
this.updaters[index] = undefined
|
|
1063
|
+
this.cachedUpdaterOrder = undefined
|
|
1064
|
+
}
|
|
1065
|
+
activeOwner?.[1].push(remove)
|
|
1066
|
+
return remove
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
[2]<T>(operation: () => T): T {
|
|
1070
|
+
this.batchDepth += 1
|
|
1071
|
+
transactionDepth += 1
|
|
1072
|
+
try {
|
|
1073
|
+
return operation()
|
|
1074
|
+
} finally {
|
|
1075
|
+
this.batchDepth -= 1
|
|
1076
|
+
if (this.batchDepth === 0 && !narrowSourceOperations[0](this.pending)) this.schedule()
|
|
1077
|
+
transactionDepth -= 1
|
|
1078
|
+
if (transactionDepth === 0) drainFlushes()
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
private schedule(): void {
|
|
1083
|
+
if (retainedUiEnabled) scheduleOwnerFlush(this.owner, this)
|
|
1084
|
+
else scheduleFlush(this)
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
private flush(): void {
|
|
1088
|
+
if (this.flushing || narrowSourceOperations[0](this.pending)) return
|
|
1089
|
+
const operation = (): void => {
|
|
1090
|
+
this.flushing = true
|
|
1091
|
+
try {
|
|
1092
|
+
let pass = 0
|
|
1093
|
+
while (!narrowSourceOperations[0](this.pending)) {
|
|
1094
|
+
pass += 1
|
|
1095
|
+
if (pass > MAX_FLUSH_PASSES) {
|
|
1096
|
+
this.pending = 0
|
|
1097
|
+
throw new Error(DEV ? 'Vidact compiled scope did not stabilize' : 'V001')
|
|
1098
|
+
}
|
|
1099
|
+
let active = this.pending
|
|
1100
|
+
this.pending = 0
|
|
1101
|
+
const order = this.hasWriter
|
|
1102
|
+
? (this.cachedUpdaterOrder ??= topologicalUpdaterOrder(
|
|
1103
|
+
this.updaters as ReadonlyArray<CompiledUpdater | undefined>,
|
|
1104
|
+
narrowSourceOperations,
|
|
1105
|
+
))
|
|
1106
|
+
: undefined
|
|
1107
|
+
const count = order?.length ?? this.updaters.length
|
|
1108
|
+
for (let position = 0; position < count; position += 1) {
|
|
1109
|
+
const index = order?.[position] ?? position
|
|
1110
|
+
const updater = this.updaters[index]
|
|
1111
|
+
if (
|
|
1112
|
+
updater === undefined ||
|
|
1113
|
+
!updater[3] ||
|
|
1114
|
+
!narrowSourceOperations[1](active, updater[0])
|
|
1115
|
+
) {
|
|
1116
|
+
continue
|
|
1117
|
+
}
|
|
1118
|
+
try {
|
|
1119
|
+
const invoke = () =>
|
|
1120
|
+
withOwner(updater[4], () =>
|
|
1121
|
+
withContextFrame(updater[5], () =>
|
|
1122
|
+
withScopeNamespace(this as unknown as CompiledScope, () => updater[2](active)),
|
|
1123
|
+
),
|
|
1124
|
+
)
|
|
1125
|
+
if (profilingEnabled) measureProfileWork(this.owner, 'updater', invoke)
|
|
1126
|
+
else invoke()
|
|
1127
|
+
} catch (error) {
|
|
1128
|
+
if (!routeOwnerError(updater[4], error)) {
|
|
1129
|
+
failedOwner = updater[4]
|
|
1130
|
+
throw error
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
if (updater[1] !== undefined) {
|
|
1134
|
+
active = narrowSourceOperations[2](active, updater[1])
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
} finally {
|
|
1139
|
+
this.flushing = false
|
|
1140
|
+
}
|
|
1141
|
+
if (retainedUiEnabled) notifyRetainedFlush(this.owner[5] ?? null)
|
|
1142
|
+
}
|
|
1143
|
+
if (profilingEnabled) measureProfileWork(this.owner, 'scheduler', operation, true)
|
|
1144
|
+
else operation()
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
function createCompilerKeyedScope(): CompiledScope {
|
|
1149
|
+
return new CompilerKeyedScope() as unknown as CompiledScope
|
|
1150
|
+
}
|
|
1151
|
+
|
|
915
1152
|
export function runCompiledTransaction<T>(operation: () => T): T {
|
|
916
1153
|
transactionDepth += 1
|
|
917
1154
|
try {
|
|
@@ -926,12 +1163,13 @@ function createScope(operations: SourceOperations): CompiledScope {
|
|
|
926
1163
|
const namespace = currentIntrinsicNamespace()
|
|
927
1164
|
const owner = createOwner()
|
|
928
1165
|
const updaters: Array<CompiledUpdater | undefined> = []
|
|
929
|
-
|
|
930
|
-
|
|
1166
|
+
let freeUpdaterIndexes: number[] | undefined
|
|
1167
|
+
let addedDuringFlush: Set<CompiledUpdater> | undefined
|
|
931
1168
|
let batchDepth = 0
|
|
932
1169
|
let flushing = false
|
|
933
1170
|
let pending: SourceMask = 0
|
|
934
1171
|
let cachedUpdaterOrder: number[] | undefined
|
|
1172
|
+
let hasWriter = false
|
|
935
1173
|
|
|
936
1174
|
const flush = (): void => {
|
|
937
1175
|
if (owner[0] || flushing) return
|
|
@@ -939,7 +1177,9 @@ function createScope(operations: SourceOperations): CompiledScope {
|
|
|
939
1177
|
flushing = true
|
|
940
1178
|
try {
|
|
941
1179
|
let pass = 0
|
|
942
|
-
const updaterOrder =
|
|
1180
|
+
const updaterOrder = hasWriter
|
|
1181
|
+
? (cachedUpdaterOrder ??= topologicalUpdaterOrder(updaters, operations))
|
|
1182
|
+
: undefined
|
|
943
1183
|
while (!operations[0](pending)) {
|
|
944
1184
|
pass += 1
|
|
945
1185
|
if (pass > MAX_FLUSH_PASSES) {
|
|
@@ -949,11 +1189,13 @@ function createScope(operations: SourceOperations): CompiledScope {
|
|
|
949
1189
|
|
|
950
1190
|
let active = pending
|
|
951
1191
|
pending = 0
|
|
952
|
-
|
|
1192
|
+
const updaterCount = updaterOrder?.length ?? updaters.length
|
|
1193
|
+
for (let position = 0; position < updaterCount; position += 1) {
|
|
1194
|
+
const index = updaterOrder?.[position] ?? position
|
|
953
1195
|
const updater = updaters[index]
|
|
954
1196
|
if (
|
|
955
1197
|
updater === undefined ||
|
|
956
|
-
addedDuringFlush
|
|
1198
|
+
addedDuringFlush?.has(updater) ||
|
|
957
1199
|
!updater[3] ||
|
|
958
1200
|
!operations[1](active, updater[0])
|
|
959
1201
|
) {
|
|
@@ -966,10 +1208,10 @@ function createScope(operations: SourceOperations): CompiledScope {
|
|
|
966
1208
|
}
|
|
967
1209
|
if (updater[1] !== undefined) active = operations[2](active, updater[1])
|
|
968
1210
|
}
|
|
969
|
-
addedDuringFlush
|
|
1211
|
+
addedDuringFlush?.clear()
|
|
970
1212
|
}
|
|
971
1213
|
} finally {
|
|
972
|
-
addedDuringFlush
|
|
1214
|
+
addedDuringFlush?.clear()
|
|
973
1215
|
flushing = false
|
|
974
1216
|
}
|
|
975
1217
|
if (retainedUiEnabled) notifyRetainedFlush(owner[5] ?? null)
|
|
@@ -1002,25 +1244,28 @@ function createScope(operations: SourceOperations): CompiledScope {
|
|
|
1002
1244
|
},
|
|
1003
1245
|
true,
|
|
1004
1246
|
]
|
|
1005
|
-
|
|
1247
|
+
if (DEV) createdUpdaterCount += 1
|
|
1248
|
+
const reusableIndex = freeUpdaterIndexes?.pop()
|
|
1006
1249
|
const index = reusableIndex ?? updaters.length
|
|
1007
1250
|
updaters[index] = entry
|
|
1251
|
+
hasWriter ||= writes !== undefined
|
|
1008
1252
|
cachedUpdaterOrder = undefined
|
|
1009
|
-
if (flushing) addedDuringFlush.add(entry)
|
|
1253
|
+
if (flushing) (addedDuringFlush ??= new Set()).add(entry)
|
|
1010
1254
|
const remove = (): void => {
|
|
1011
1255
|
if (updaters[index] !== entry) return
|
|
1012
1256
|
entry[3] = false
|
|
1013
1257
|
updaters[index] = undefined
|
|
1014
1258
|
cachedUpdaterOrder = undefined
|
|
1015
|
-
freeUpdaterIndexes.push(index)
|
|
1259
|
+
;(freeUpdaterIndexes ??= []).push(index)
|
|
1016
1260
|
}
|
|
1017
|
-
if (activeOwner !== null && activeOwner !== owner) activeOwner[1].
|
|
1261
|
+
if (activeOwner !== null && activeOwner !== owner) activeOwner[1].push(remove)
|
|
1018
1262
|
return remove
|
|
1019
1263
|
},
|
|
1020
1264
|
(sources) => {
|
|
1021
1265
|
if (owner[0] || operations[0](sources)) return
|
|
1266
|
+
const wasIdle = operations[0](pending)
|
|
1022
1267
|
pending = operations[2](pending, sources)
|
|
1023
|
-
if (batchDepth === 0) {
|
|
1268
|
+
if (wasIdle && batchDepth === 0) {
|
|
1024
1269
|
if (retainedUiEnabled) scheduleOwnerFlush(owner, flush)
|
|
1025
1270
|
else scheduleFlush(flush)
|
|
1026
1271
|
}
|
|
@@ -1050,8 +1295,9 @@ function createScope(operations: SourceOperations): CompiledScope {
|
|
|
1050
1295
|
if (updater !== undefined) updater[3] = false
|
|
1051
1296
|
}
|
|
1052
1297
|
updaters.length = 0
|
|
1053
|
-
|
|
1054
|
-
|
|
1298
|
+
hasWriter = false
|
|
1299
|
+
if (freeUpdaterIndexes !== undefined) freeUpdaterIndexes.length = 0
|
|
1300
|
+
addedDuringFlush?.clear()
|
|
1055
1301
|
}
|
|
1056
1302
|
},
|
|
1057
1303
|
]
|
|
@@ -1066,20 +1312,40 @@ function topologicalUpdaterOrder(
|
|
|
1066
1312
|
updaters: ReadonlyArray<CompiledUpdater | undefined>,
|
|
1067
1313
|
operations: SourceOperations,
|
|
1068
1314
|
): number[] {
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1315
|
+
if (DEV) createdSchedulerPlanCount += 1
|
|
1316
|
+
const activeIndexes: number[] = []
|
|
1317
|
+
let hasWriter = false
|
|
1318
|
+
for (let index = 0; index < updaters.length; index += 1) {
|
|
1319
|
+
const updater = updaters[index]
|
|
1320
|
+
if (updater === undefined || !updater[3]) continue
|
|
1321
|
+
activeIndexes.push(index)
|
|
1322
|
+
if (updater[1] !== undefined) hasWriter = true
|
|
1323
|
+
}
|
|
1324
|
+
if (!hasWriter) return activeIndexes
|
|
1325
|
+
const readersBySource = new Map<number, number[]>()
|
|
1326
|
+
for (const readerIndex of activeIndexes) {
|
|
1327
|
+
operations[3](updaters[readerIndex]![0], (sourceIndex) => {
|
|
1328
|
+
const readers = readersBySource.get(sourceIndex)
|
|
1329
|
+
if (readers === undefined) readersBySource.set(sourceIndex, [readerIndex])
|
|
1330
|
+
else readers.push(readerIndex)
|
|
1331
|
+
})
|
|
1332
|
+
}
|
|
1072
1333
|
const edges = new Map<number, number[]>()
|
|
1073
1334
|
for (const writerIndex of activeIndexes) {
|
|
1074
1335
|
const writes = updaters[writerIndex]![1]
|
|
1075
1336
|
if (writes === undefined) continue
|
|
1076
|
-
const readers
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
readers.
|
|
1337
|
+
const readers = new Set<number>()
|
|
1338
|
+
operations[3](writes, (sourceIndex) => {
|
|
1339
|
+
for (const readerIndex of readersBySource.get(sourceIndex) ?? []) {
|
|
1340
|
+
if (writerIndex !== readerIndex) readers.add(readerIndex)
|
|
1080
1341
|
}
|
|
1342
|
+
})
|
|
1343
|
+
if (readers.size > 0) {
|
|
1344
|
+
edges.set(
|
|
1345
|
+
writerIndex,
|
|
1346
|
+
[...readers].toSorted((left, right) => left - right),
|
|
1347
|
+
)
|
|
1081
1348
|
}
|
|
1082
|
-
if (readers.length > 0) edges.set(writerIndex, readers)
|
|
1083
1349
|
}
|
|
1084
1350
|
|
|
1085
1351
|
const visitIndexes = new Int32Array(updaters.length).fill(-1)
|
|
@@ -1213,7 +1479,7 @@ export function createCompiledMemo<T>(
|
|
|
1213
1479
|
removeUpdater()
|
|
1214
1480
|
throw new Error(DEV ? 'createCompiledMemo received an unknown scope' : 'V003')
|
|
1215
1481
|
}
|
|
1216
|
-
owner[1].
|
|
1482
|
+
owner[1].push(removeUpdater)
|
|
1217
1483
|
return slot
|
|
1218
1484
|
}
|
|
1219
1485
|
|
|
@@ -1337,7 +1603,7 @@ export function createCompiledExternalStore<T>(
|
|
|
1337
1603
|
removeStoreUpdater()
|
|
1338
1604
|
throw error
|
|
1339
1605
|
}
|
|
1340
|
-
owner[1].
|
|
1606
|
+
owner[1].push(() => {
|
|
1341
1607
|
removeStoreUpdater()
|
|
1342
1608
|
deactivateStore()
|
|
1343
1609
|
})
|
|
@@ -1355,7 +1621,7 @@ export function createCompiledExternalStore<T>(
|
|
|
1355
1621
|
disposeResource()
|
|
1356
1622
|
throw error
|
|
1357
1623
|
}
|
|
1358
|
-
owner[1].
|
|
1624
|
+
owner[1].push(() => {
|
|
1359
1625
|
removeStoreUpdater()
|
|
1360
1626
|
disposeResource()
|
|
1361
1627
|
})
|
|
@@ -1457,6 +1723,42 @@ export function useCallback<T extends Function>(callback: T, _dependencies: read
|
|
|
1457
1723
|
return callback
|
|
1458
1724
|
}
|
|
1459
1725
|
|
|
1726
|
+
const ownedBlockFacades = new WeakMap<StructuralBinding, RenderableProtocol>()
|
|
1727
|
+
const ownedFacadeBlocks = new WeakMap<RenderableProtocol, StructuralBinding>()
|
|
1728
|
+
|
|
1729
|
+
function ownedStructuralBinding(value: unknown): StructuralBinding | undefined {
|
|
1730
|
+
if (isStructuralBinding(value)) return value
|
|
1731
|
+
return isRenderableProtocol(value) ? ownedFacadeBlocks.get(value) : undefined
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
/**
|
|
1735
|
+
* Owned blocks are arrays internally, which user-level data flow must never
|
|
1736
|
+
* observe: dependency code inspects children with `Array.isArray` and spreads
|
|
1737
|
+
* them. Prop reads wrap a block in a stable renderable facade that
|
|
1738
|
+
* materializes back to the block when rendered.
|
|
1739
|
+
*/
|
|
1740
|
+
function sanitizeCompiledPropValue<T>(value: T): T {
|
|
1741
|
+
if (!isStructuralBinding(value)) return value
|
|
1742
|
+
let facade = ownedBlockFacades.get(value)
|
|
1743
|
+
if (facade === undefined) {
|
|
1744
|
+
const capability = { props: {} } as unknown as RenderableProtocol
|
|
1745
|
+
Object.defineProperty(capability, RENDERABLE, {
|
|
1746
|
+
configurable: false,
|
|
1747
|
+
enumerable: false,
|
|
1748
|
+
value: {
|
|
1749
|
+
identity: value,
|
|
1750
|
+
input: {},
|
|
1751
|
+
reconcile: false,
|
|
1752
|
+
construct: () => value,
|
|
1753
|
+
},
|
|
1754
|
+
})
|
|
1755
|
+
facade = capability
|
|
1756
|
+
ownedBlockFacades.set(value, facade)
|
|
1757
|
+
ownedFacadeBlocks.set(facade, value)
|
|
1758
|
+
}
|
|
1759
|
+
return facade as T
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1460
1762
|
export function createCompiledProp<T>(
|
|
1461
1763
|
scope: CompiledScope,
|
|
1462
1764
|
sourceMask: SourceMask,
|
|
@@ -1467,7 +1769,9 @@ export function createCompiledProp<T>(
|
|
|
1467
1769
|
const upstream = isCompiledBinding(input) ? input : undefined
|
|
1468
1770
|
const read = (): T => {
|
|
1469
1771
|
const value = upstream === undefined ? (input as T) : upstream[1]()
|
|
1470
|
-
return
|
|
1772
|
+
return sanitizeCompiledPropValue(
|
|
1773
|
+
value === undefined && fallback !== undefined ? fallback() : value,
|
|
1774
|
+
)
|
|
1471
1775
|
}
|
|
1472
1776
|
const slot = createStateSlot<T>(scope[1], sourceMask, read(), assertWritable)
|
|
1473
1777
|
if (upstream !== undefined) {
|
|
@@ -1476,7 +1780,7 @@ export function createCompiledProp<T>(
|
|
|
1476
1780
|
if (owner === undefined) {
|
|
1477
1781
|
throw new Error(DEV ? 'createCompiledProp received an unknown scope' : 'V003')
|
|
1478
1782
|
}
|
|
1479
|
-
owner[1].
|
|
1783
|
+
owner[1].push(remove)
|
|
1480
1784
|
}
|
|
1481
1785
|
return slot
|
|
1482
1786
|
}
|
|
@@ -1492,7 +1796,10 @@ export function createCompiledRestProp(
|
|
|
1492
1796
|
Object.entries(input).filter(([name]) => !excluded.has(name))
|
|
1493
1797
|
const read = (): Record<string, unknown> =>
|
|
1494
1798
|
Object.fromEntries(
|
|
1495
|
-
entries().map(([name, value]) => [
|
|
1799
|
+
entries().map(([name, value]) => [
|
|
1800
|
+
name,
|
|
1801
|
+
sanitizeCompiledPropValue(isCompiledBinding(value) ? value[1]() : value),
|
|
1802
|
+
]),
|
|
1496
1803
|
)
|
|
1497
1804
|
const slot = createStateSlot(scope[1], sourceMask, read(), stateWriteGuard(scope))
|
|
1498
1805
|
const publish = (): void => {
|
|
@@ -1519,7 +1826,7 @@ export function createCompiledRestProp(
|
|
|
1519
1826
|
if (owner === undefined) {
|
|
1520
1827
|
throw new Error(DEV ? 'createCompiledRestProp received an unknown scope' : 'V003')
|
|
1521
1828
|
}
|
|
1522
|
-
owner[1].
|
|
1829
|
+
owner[1].push(() => {
|
|
1523
1830
|
for (const remove of removers) remove()
|
|
1524
1831
|
})
|
|
1525
1832
|
return slot
|
|
@@ -1579,8 +1886,9 @@ export function compiledEvent<Arguments extends unknown[]>(
|
|
|
1579
1886
|
throw new Error(DEV ? 'compiledEvent received an unknown scope' : 'V003')
|
|
1580
1887
|
}
|
|
1581
1888
|
const errorOwner = activeOwner ?? owner
|
|
1582
|
-
|
|
1583
|
-
|
|
1889
|
+
const listener = (...arguments_: Arguments) => {
|
|
1890
|
+
if (errorOwner[0]) return
|
|
1891
|
+
return runOwnerTask(errorOwner, () =>
|
|
1584
1892
|
withOwner(errorOwner, () =>
|
|
1585
1893
|
scope[2](() => {
|
|
1586
1894
|
const current = isCompiledBinding(handler) ? handler[1]() : handler
|
|
@@ -1589,6 +1897,43 @@ export function compiledEvent<Arguments extends unknown[]>(
|
|
|
1589
1897
|
}),
|
|
1590
1898
|
),
|
|
1591
1899
|
)
|
|
1900
|
+
}
|
|
1901
|
+
;(listener as unknown as CompiledEventHandler)[COMPILED_EVENT_HANDLER] = true
|
|
1902
|
+
return listener
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
export function compiledInlineEvent<Arguments extends unknown[]>(
|
|
1906
|
+
scope: CompiledScope,
|
|
1907
|
+
handler: (...arguments_: Arguments) => void,
|
|
1908
|
+
): (...arguments_: Arguments) => void {
|
|
1909
|
+
const owner = scopeOwners.get(scope)
|
|
1910
|
+
if (owner === undefined) {
|
|
1911
|
+
throw new Error(DEV ? 'compiledInlineEvent received an unknown scope' : 'V003')
|
|
1912
|
+
}
|
|
1913
|
+
const listener = handler as unknown as CompiledInlineEventHandler<Arguments>
|
|
1914
|
+
listener[COMPILED_INLINE_EVENT_OWNER] = activeOwner ?? owner
|
|
1915
|
+
listener[COMPILED_DELEGATED_EVENT_INVOKE] = invokeCompiledInlineEvent
|
|
1916
|
+
return listener
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
function invokeCompiledInlineEvent<Arguments extends unknown[]>(
|
|
1920
|
+
this: CompiledInlineEventHandler<Arguments>,
|
|
1921
|
+
...arguments_: Arguments
|
|
1922
|
+
): void {
|
|
1923
|
+
const owner = this[COMPILED_INLINE_EVENT_OWNER]
|
|
1924
|
+
if (owner[0]) return
|
|
1925
|
+
return runOwnerTask(owner, () =>
|
|
1926
|
+
withOwner(owner, () => runCompiledTransaction(() => this(...arguments_))),
|
|
1927
|
+
)
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
export function isCompiledEventHandler(value: unknown): value is EventListener {
|
|
1931
|
+
if (typeof value !== 'function') return false
|
|
1932
|
+
const handler = value as typeof value & CompiledEventMetadata
|
|
1933
|
+
return (
|
|
1934
|
+
handler[COMPILED_EVENT_HANDLER] === true ||
|
|
1935
|
+
typeof handler[COMPILED_DELEGATED_EVENT_INVOKE] === 'function'
|
|
1936
|
+
)
|
|
1592
1937
|
}
|
|
1593
1938
|
|
|
1594
1939
|
export interface CompiledTaskController {
|
|
@@ -1690,7 +2035,12 @@ export function choose(
|
|
|
1690
2035
|
return structural(
|
|
1691
2036
|
scope,
|
|
1692
2037
|
(parent, before) => {
|
|
1693
|
-
const
|
|
2038
|
+
const range = structuralRange(parent, before, 'choice', true, true)
|
|
2039
|
+
const markerlessHydration = range === undefined
|
|
2040
|
+
const start = range?.[0] ?? document.createComment(DEV ? 'vidact:choice' : '')
|
|
2041
|
+
const end = range?.[1] ?? document.createComment(DEV ? '/vidact:choice' : '')
|
|
2042
|
+
const hydrated = range?.[2] ?? true
|
|
2043
|
+
const ownsMarkers = range?.[3] ?? true
|
|
1694
2044
|
let selected = -1
|
|
1695
2045
|
let branchOwner: Owner | null = null
|
|
1696
2046
|
let branchNodes: readonly Node[] = []
|
|
@@ -1700,15 +2050,28 @@ export function choose(
|
|
|
1700
2050
|
const next =
|
|
1701
2051
|
mode === 'not-nullish' ? (value === null || value === undefined ? 1 : 0) : value ? 0 : 1
|
|
1702
2052
|
if (next === selected) return
|
|
1703
|
-
const currentParent = rangeParent(start, end, 'choice block')
|
|
1704
2053
|
const nextOwner = createOwner()
|
|
1705
|
-
const
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
2054
|
+
const render = next === 0 ? consequent : alternate
|
|
2055
|
+
let nodes: readonly Node[]
|
|
2056
|
+
if (markerlessHydration && selected === -1) {
|
|
2057
|
+
const [first, after, hydratedNodes] = stageMarkerlessHydrationRender(
|
|
2058
|
+
parent,
|
|
2059
|
+
before,
|
|
2060
|
+
render,
|
|
2061
|
+
nextOwner,
|
|
2062
|
+
)
|
|
2063
|
+
parent.insertBefore(start, first)
|
|
2064
|
+
parent.insertBefore(end, after)
|
|
2065
|
+
nodes = hydratedNodes
|
|
2066
|
+
} else {
|
|
2067
|
+
const currentParent = rangeParent(start, end, 'choice block')
|
|
2068
|
+
const [fragment, stagedNodes] =
|
|
2069
|
+
hydrated && selected === -1
|
|
2070
|
+
? withHydrationInsertion(currentParent, end, () => stageRender(render, nextOwner))
|
|
2071
|
+
: stageRender(render, nextOwner)
|
|
2072
|
+
currentParent.insertBefore(fragment, end)
|
|
2073
|
+
nodes = stagedNodes
|
|
2074
|
+
}
|
|
1712
2075
|
try {
|
|
1713
2076
|
commitPublishedNodes(nodes)
|
|
1714
2077
|
} catch (error) {
|
|
@@ -1736,8 +2099,10 @@ export function choose(
|
|
|
1736
2099
|
try {
|
|
1737
2100
|
disposePublished(branchOwner, branchNodes)
|
|
1738
2101
|
} finally {
|
|
1739
|
-
|
|
1740
|
-
|
|
2102
|
+
if (ownsMarkers) {
|
|
2103
|
+
start.remove()
|
|
2104
|
+
end.remove()
|
|
2105
|
+
}
|
|
1741
2106
|
}
|
|
1742
2107
|
})
|
|
1743
2108
|
},
|
|
@@ -1824,9 +2189,21 @@ export function keyed<T, K>(
|
|
|
1824
2189
|
values: () => readonly T[],
|
|
1825
2190
|
key: (value: T, index: number) => K,
|
|
1826
2191
|
render: (value: StateSlot<T>, index: StateSlot<number>, itemScope: CompiledScope) => RenderValue,
|
|
1827
|
-
|
|
2192
|
+
trackIndexOrAdditionalScope?: boolean | CompiledScope,
|
|
2193
|
+
additionalScopeOrReads?: CompiledScope | SourceMask,
|
|
1828
2194
|
additionalReads?: SourceMask,
|
|
1829
2195
|
): StructuralBinding {
|
|
2196
|
+
const compilerManaged = typeof trackIndexOrAdditionalScope === 'boolean'
|
|
2197
|
+
const trackIndex =
|
|
2198
|
+
typeof trackIndexOrAdditionalScope === 'boolean' ? trackIndexOrAdditionalScope : true
|
|
2199
|
+
const additionalScope =
|
|
2200
|
+
typeof trackIndexOrAdditionalScope === 'boolean'
|
|
2201
|
+
? (additionalScopeOrReads as CompiledScope | undefined)
|
|
2202
|
+
: trackIndexOrAdditionalScope
|
|
2203
|
+
const resolvedAdditionalReads =
|
|
2204
|
+
typeof trackIndexOrAdditionalScope === 'boolean'
|
|
2205
|
+
? additionalReads
|
|
2206
|
+
: (additionalScopeOrReads as SourceMask | undefined)
|
|
1830
2207
|
return structural(
|
|
1831
2208
|
scope,
|
|
1832
2209
|
(parent, before) => {
|
|
@@ -1837,27 +2214,56 @@ export function keyed<T, K>(
|
|
|
1837
2214
|
{
|
|
1838
2215
|
key,
|
|
1839
2216
|
render(value, index) {
|
|
1840
|
-
const
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
const
|
|
2217
|
+
const itemScope = compilerManaged
|
|
2218
|
+
? createCompilerKeyedScope()
|
|
2219
|
+
: createNarrowCompiledScope()
|
|
2220
|
+
const owner = scopeOwners.get(itemScope)!
|
|
2221
|
+
const valueSlot = compilerManaged
|
|
2222
|
+
? createReadOnlyStateSlot(itemScope, itemSource, value)
|
|
2223
|
+
: createCompiledState(itemScope, itemSource, value)
|
|
2224
|
+
const indexSlot = trackIndex
|
|
2225
|
+
? compilerManaged
|
|
2226
|
+
? createReadOnlyStateSlot(itemScope, indexSource, index)
|
|
2227
|
+
: createCompiledState(itemScope, indexSource, index)
|
|
2228
|
+
: undefined
|
|
1844
2229
|
try {
|
|
1845
2230
|
const nodes = withOwner(owner, () => {
|
|
1846
|
-
|
|
1847
|
-
|
|
2231
|
+
const rendered = render(
|
|
2232
|
+
valueSlot as StateSlot<T>,
|
|
2233
|
+
indexSlot as StateSlot<number>,
|
|
2234
|
+
itemScope,
|
|
2235
|
+
)
|
|
2236
|
+
return !isHydrating() &&
|
|
2237
|
+
rendered instanceof Node &&
|
|
2238
|
+
!(rendered instanceof DocumentFragment)
|
|
2239
|
+
? [rendered]
|
|
2240
|
+
: materialize(rendered)
|
|
1848
2241
|
})
|
|
1849
2242
|
return [
|
|
1850
2243
|
nodes,
|
|
1851
2244
|
(nextValue: T, nextIndex: number) => {
|
|
2245
|
+
if (
|
|
2246
|
+
Object.is(valueSlot.get(), nextValue) &&
|
|
2247
|
+
(!trackIndex || indexSlot!.get() === nextIndex)
|
|
2248
|
+
) {
|
|
2249
|
+
return
|
|
2250
|
+
}
|
|
1852
2251
|
itemScope[2](() => {
|
|
1853
|
-
|
|
1854
|
-
|
|
2252
|
+
if (compilerManaged) {
|
|
2253
|
+
replaceReadOnlyStateSlot(valueSlot as ReadOnlyStateCell<T>, nextValue)
|
|
2254
|
+
if (indexSlot !== undefined) {
|
|
2255
|
+
replaceReadOnlyStateSlot(indexSlot as ReadOnlyStateCell<number>, nextIndex)
|
|
2256
|
+
}
|
|
2257
|
+
} else {
|
|
2258
|
+
;(valueSlot as StateSlot<T>).set(nextValue)
|
|
2259
|
+
;(indexSlot as StateSlot<number> | undefined)?.set(nextIndex)
|
|
2260
|
+
}
|
|
1855
2261
|
})
|
|
1856
2262
|
},
|
|
1857
|
-
|
|
2263
|
+
itemScope,
|
|
1858
2264
|
] as const
|
|
1859
2265
|
} catch (error) {
|
|
1860
|
-
|
|
2266
|
+
itemScope[3]()
|
|
1861
2267
|
throw error
|
|
1862
2268
|
}
|
|
1863
2269
|
},
|
|
@@ -1874,7 +2280,13 @@ export function keyed<T, K>(
|
|
|
1874
2280
|
}
|
|
1875
2281
|
}
|
|
1876
2282
|
update()
|
|
1877
|
-
const removeUpdater = subscribe(
|
|
2283
|
+
const removeUpdater = subscribe(
|
|
2284
|
+
scope,
|
|
2285
|
+
reads,
|
|
2286
|
+
update,
|
|
2287
|
+
additionalScope,
|
|
2288
|
+
resolvedAdditionalReads,
|
|
2289
|
+
)
|
|
1878
2290
|
onCleanup(() => {
|
|
1879
2291
|
removeUpdater()
|
|
1880
2292
|
list.dispose()
|
|
@@ -1962,7 +2374,7 @@ export function compiledRoot(
|
|
|
1962
2374
|
componentCommitOwners.set(end, owner)
|
|
1963
2375
|
const fragment = document.createDocumentFragment()
|
|
1964
2376
|
if (hydratedRange === undefined) fragment.append(start, end)
|
|
1965
|
-
owner[1].
|
|
2377
|
+
owner[1].push(() => {
|
|
1966
2378
|
try {
|
|
1967
2379
|
removeBetween(start, end)
|
|
1968
2380
|
} finally {
|
|
@@ -2026,7 +2438,7 @@ export function adoptCompiledRoot(root: unknown): void {
|
|
|
2026
2438
|
if (range === undefined) return
|
|
2027
2439
|
const owner = scopeOwners.get(range[2])
|
|
2028
2440
|
if (activeOwner !== null && owner !== undefined && activeOwner !== owner) {
|
|
2029
|
-
activeOwner[1].
|
|
2441
|
+
activeOwner[1].push(range[2][3])
|
|
2030
2442
|
}
|
|
2031
2443
|
}
|
|
2032
2444
|
|
|
@@ -2067,7 +2479,9 @@ export function queueElementRef(element: Element, value: unknown): void {
|
|
|
2067
2479
|
if (!isRefValue(value)) {
|
|
2068
2480
|
throw new TypeError(DEV ? 'ref must be a callback or an object with current' : 'V006')
|
|
2069
2481
|
}
|
|
2070
|
-
|
|
2482
|
+
const pending: PendingRef = [activeOwner, value]
|
|
2483
|
+
setPendingRef(element, pending)
|
|
2484
|
+
activeOwner?.[1].push(() => deletePendingRef(element, pending))
|
|
2071
2485
|
}
|
|
2072
2486
|
|
|
2073
2487
|
export function useImperativeHandle<T>(
|
|
@@ -2091,7 +2505,7 @@ export function useImperativeHandle<T>(
|
|
|
2091
2505
|
DEV ? 'reactive useImperativeHandle dependencies are not supported yet' : 'V014',
|
|
2092
2506
|
)
|
|
2093
2507
|
}
|
|
2094
|
-
queueOwnerCommit(owner, () => owner[1].
|
|
2508
|
+
queueOwnerCommit(owner, () => owner[1].push(attachRef(ref, create())))
|
|
2095
2509
|
}
|
|
2096
2510
|
|
|
2097
2511
|
type EffectResult = void | (() => void)
|
|
@@ -2128,7 +2542,7 @@ export function useInsertionEffect(
|
|
|
2128
2542
|
? readProfiledEffectCleanup(owner, create)
|
|
2129
2543
|
: readEffectCleanup(create())
|
|
2130
2544
|
})
|
|
2131
|
-
lifetimeOwner[1].
|
|
2545
|
+
lifetimeOwner[1].push(() => cleanup())
|
|
2132
2546
|
return
|
|
2133
2547
|
}
|
|
2134
2548
|
let cleanup = noop
|
|
@@ -2155,7 +2569,7 @@ export function useInsertionEffect(
|
|
|
2155
2569
|
queueInsertionCommit(owner, () => {
|
|
2156
2570
|
if (!lifetimeOwner[0]) activate()
|
|
2157
2571
|
})
|
|
2158
|
-
lifetimeOwner[1].
|
|
2572
|
+
lifetimeOwner[1].push(disposeResource)
|
|
2159
2573
|
}
|
|
2160
2574
|
|
|
2161
2575
|
export function useEffect(create: () => EffectResult, dependencies?: readonly unknown[]): void {
|
|
@@ -2195,7 +2609,7 @@ function registerEffect(
|
|
|
2195
2609
|
run()
|
|
2196
2610
|
}
|
|
2197
2611
|
})
|
|
2198
|
-
lifetimeOwner[1].
|
|
2612
|
+
lifetimeOwner[1].push(() => {
|
|
2199
2613
|
generation += 1
|
|
2200
2614
|
if (passive) (DEV ? scheduleTask : queueMicrotask)(() => runOwnerTask(lifetimeOwner, cleanup))
|
|
2201
2615
|
else cleanup()
|
|
@@ -2244,7 +2658,7 @@ function registerEffect(
|
|
|
2244
2658
|
queueOwnerCommit(owner, () => {
|
|
2245
2659
|
if (!lifetimeOwner[0]) activate()
|
|
2246
2660
|
})
|
|
2247
|
-
lifetimeOwner[1].
|
|
2661
|
+
lifetimeOwner[1].push(disposeResource)
|
|
2248
2662
|
}
|
|
2249
2663
|
|
|
2250
2664
|
export function compiledLayoutEffect(
|
|
@@ -2293,7 +2707,7 @@ export function compiledInsertionEffect(
|
|
|
2293
2707
|
const removeUpdater = subscribe(scope, reads, () =>
|
|
2294
2708
|
stagePublication([noop, noop, undefined, run, -20]),
|
|
2295
2709
|
)
|
|
2296
|
-
lifetimeOwner[1].
|
|
2710
|
+
lifetimeOwner[1].push(() => {
|
|
2297
2711
|
removeUpdater()
|
|
2298
2712
|
cleanup()
|
|
2299
2713
|
})
|
|
@@ -2346,7 +2760,7 @@ export function compiledInsertionEffect(
|
|
|
2346
2760
|
const removeUpdater = subscribe(scope, reads, () => {
|
|
2347
2761
|
if (connected) stagePublication([noop, noop, undefined, run, -20])
|
|
2348
2762
|
})
|
|
2349
|
-
lifetimeOwner[1].
|
|
2763
|
+
lifetimeOwner[1].push(() => {
|
|
2350
2764
|
removeUpdater()
|
|
2351
2765
|
disposeResource()
|
|
2352
2766
|
})
|
|
@@ -2402,7 +2816,7 @@ export function compiledEffect(
|
|
|
2402
2816
|
if (passive) schedule()
|
|
2403
2817
|
else stagePublication([noop, noop, undefined, run, 20])
|
|
2404
2818
|
})
|
|
2405
|
-
lifetimeOwner[1].
|
|
2819
|
+
lifetimeOwner[1].push(() => {
|
|
2406
2820
|
generation += 1
|
|
2407
2821
|
removeUpdater()
|
|
2408
2822
|
if (passive) (DEV ? scheduleTask : queueMicrotask)(() => runOwnerTask(lifetimeOwner, cleanup))
|
|
@@ -2475,7 +2889,7 @@ export function compiledEffect(
|
|
|
2475
2889
|
if (passive) schedule()
|
|
2476
2890
|
else stagePublication([noop, noop, undefined, run, 20])
|
|
2477
2891
|
})
|
|
2478
|
-
lifetimeOwner[1].
|
|
2892
|
+
lifetimeOwner[1].push(() => {
|
|
2479
2893
|
removeUpdater()
|
|
2480
2894
|
disposeResource()
|
|
2481
2895
|
})
|
|
@@ -2594,7 +3008,7 @@ export function compiledImperativeHandle<T>(
|
|
|
2594
3008
|
])
|
|
2595
3009
|
}
|
|
2596
3010
|
const removeUpdater = subscribe(scope, reads, update)
|
|
2597
|
-
owner[1].
|
|
3011
|
+
owner[1].push(() => {
|
|
2598
3012
|
removeUpdater()
|
|
2599
3013
|
cleanup()
|
|
2600
3014
|
})
|
|
@@ -2605,6 +3019,7 @@ function queueOwnerCommit(owner: Owner, commit: () => void): void {
|
|
|
2605
3019
|
if (commits === undefined) {
|
|
2606
3020
|
commits = new Set()
|
|
2607
3021
|
pendingOwnerCommits.set(owner, commits)
|
|
3022
|
+
pendingOwnerCommitCount += 1
|
|
2608
3023
|
}
|
|
2609
3024
|
commits.add(commit)
|
|
2610
3025
|
}
|
|
@@ -2614,6 +3029,7 @@ function queueInsertionCommit(owner: Owner, commit: () => void): void {
|
|
|
2614
3029
|
if (commits === undefined) {
|
|
2615
3030
|
commits = new Set()
|
|
2616
3031
|
pendingInsertionCommits.set(owner, commits)
|
|
3032
|
+
pendingInsertionOwnerCount += 1
|
|
2617
3033
|
}
|
|
2618
3034
|
commits.add(commit)
|
|
2619
3035
|
}
|
|
@@ -2644,7 +3060,7 @@ export function mountCompiledRef(element: Element, value: CompiledBinding<unknow
|
|
|
2644
3060
|
// oxlint-disable-next-line unicorn/consistent-function-scoping -- This placeholder is replaced when the ref attaches.
|
|
2645
3061
|
let cleanup = noop
|
|
2646
3062
|
const pending: PendingRef = [activeOwner, current, (attached) => (cleanup = attached)]
|
|
2647
|
-
|
|
3063
|
+
setPendingRef(element, pending)
|
|
2648
3064
|
|
|
2649
3065
|
const removeUpdater = subscribeBinding(value, () => {
|
|
2650
3066
|
const next = value[1]()
|
|
@@ -2703,7 +3119,7 @@ export function mountCompiledRef(element: Element, value: CompiledBinding<unknow
|
|
|
2703
3119
|
})
|
|
2704
3120
|
onCleanup(() => {
|
|
2705
3121
|
removeUpdater()
|
|
2706
|
-
|
|
3122
|
+
deletePendingRef(element, pending)
|
|
2707
3123
|
cleanup()
|
|
2708
3124
|
})
|
|
2709
3125
|
}
|
|
@@ -2788,6 +3204,7 @@ export function mountCompiled(
|
|
|
2788
3204
|
throw new Error(DEV ? 'mountCompiled received an unknown component result' : 'V007')
|
|
2789
3205
|
}
|
|
2790
3206
|
const previous = [...host.childNodes]
|
|
3207
|
+
const releaseEventRoot = retainCompiledEventRoot(host)
|
|
2791
3208
|
try {
|
|
2792
3209
|
root[1](host, previous[0] ?? null)
|
|
2793
3210
|
const rootOwner = scopeOwners.get(range[2])
|
|
@@ -2803,6 +3220,7 @@ export function mountCompiled(
|
|
|
2803
3220
|
for (const node of previous) host.removeChild(node)
|
|
2804
3221
|
}
|
|
2805
3222
|
} catch (error) {
|
|
3223
|
+
releaseEventRoot()
|
|
2806
3224
|
try {
|
|
2807
3225
|
range[2][3]()
|
|
2808
3226
|
} catch {
|
|
@@ -2812,8 +3230,17 @@ export function mountCompiled(
|
|
|
2812
3230
|
rootIdentity.onUncaughtError(error)
|
|
2813
3231
|
return { dispose: noop }
|
|
2814
3232
|
}
|
|
3233
|
+
let disposed = false
|
|
2815
3234
|
return {
|
|
2816
|
-
dispose:
|
|
3235
|
+
dispose: () => {
|
|
3236
|
+
if (disposed) return
|
|
3237
|
+
disposed = true
|
|
3238
|
+
try {
|
|
3239
|
+
range[2][3]()
|
|
3240
|
+
} finally {
|
|
3241
|
+
releaseEventRoot()
|
|
3242
|
+
}
|
|
3243
|
+
},
|
|
2817
3244
|
}
|
|
2818
3245
|
}
|
|
2819
3246
|
|
|
@@ -2833,12 +3260,43 @@ export function mountCompiledBinding(parent: Node, value: CompiledBinding<unknow
|
|
|
2833
3260
|
mountCompiledBindingBefore(parent, value, null)
|
|
2834
3261
|
}
|
|
2835
3262
|
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
3263
|
+
type CompiledPropApply<T> =
|
|
3264
|
+
| ((next: T) => void | (() => void))
|
|
3265
|
+
| ((element: Element, name: string, next: T) => void | (() => void))
|
|
3266
|
+
|
|
3267
|
+
function applyCompiledPropValue<T>(
|
|
3268
|
+
apply: CompiledPropApply<T>,
|
|
3269
|
+
element: Element | undefined,
|
|
3270
|
+
name: string | undefined,
|
|
3271
|
+
next: T,
|
|
3272
|
+
): void | (() => void) {
|
|
3273
|
+
return element === undefined
|
|
3274
|
+
? (apply as (next: T) => void | (() => void))(next)
|
|
3275
|
+
: (apply as (element: Element, name: string, next: T) => void | (() => void))(
|
|
3276
|
+
element,
|
|
3277
|
+
name!,
|
|
3278
|
+
next,
|
|
3279
|
+
)
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
export function mountCompiledProp<T>(
|
|
3283
|
+
value: CompiledBinding<T>,
|
|
3284
|
+
apply: (next: T) => void | (() => void),
|
|
3285
|
+
): void
|
|
3286
|
+
export function mountCompiledProp<T>(
|
|
3287
|
+
value: CompiledBinding<T>,
|
|
3288
|
+
apply: (element: Element, name: string, next: T) => void | (() => void),
|
|
3289
|
+
element: Element,
|
|
3290
|
+
name: string,
|
|
3291
|
+
): void
|
|
3292
|
+
export function mountCompiledProp<T>(
|
|
3293
|
+
value: CompiledBinding<T>,
|
|
3294
|
+
apply: CompiledPropApply<T>,
|
|
3295
|
+
element?: Element,
|
|
3296
|
+
name?: string,
|
|
2839
3297
|
): void {
|
|
2840
3298
|
let current = value[1]()
|
|
2841
|
-
let cleanup = apply
|
|
3299
|
+
let cleanup = applyCompiledPropValue(apply, element, name, current)
|
|
2842
3300
|
const removeUpdater = subscribeBinding(value, () => {
|
|
2843
3301
|
const next = value[1]()
|
|
2844
3302
|
if (Object.is(next, current)) return
|
|
@@ -2848,10 +3306,10 @@ export function mountCompiledProp(
|
|
|
2848
3306
|
stagePublication([
|
|
2849
3307
|
() => {
|
|
2850
3308
|
try {
|
|
2851
|
-
nextCleanup = apply
|
|
3309
|
+
nextCleanup = applyCompiledPropValue(apply, element, name, next)
|
|
2852
3310
|
} catch (error) {
|
|
2853
3311
|
try {
|
|
2854
|
-
apply
|
|
3312
|
+
applyCompiledPropValue(apply, element, name, previous)
|
|
2855
3313
|
} catch {
|
|
2856
3314
|
// Preserve the setter error that aborted publication.
|
|
2857
3315
|
}
|
|
@@ -2865,7 +3323,7 @@ export function mountCompiledProp(
|
|
|
2865
3323
|
() => {
|
|
2866
3324
|
if (!committedNext) return
|
|
2867
3325
|
nextCleanup?.()
|
|
2868
|
-
cleanup = apply
|
|
3326
|
+
cleanup = applyCompiledPropValue(apply, element, name, previous)
|
|
2869
3327
|
current = previous
|
|
2870
3328
|
committedNext = false
|
|
2871
3329
|
},
|
|
@@ -2942,14 +3400,40 @@ function structuralRange(
|
|
|
2942
3400
|
parent: Node,
|
|
2943
3401
|
before: Node | null,
|
|
2944
3402
|
label: string,
|
|
2945
|
-
|
|
3403
|
+
borrowOuter?: boolean,
|
|
3404
|
+
allowMarkerlessHydration?: false,
|
|
3405
|
+
): readonly [start: Comment, end: Comment, hydrated: boolean, ownsMarkers: boolean]
|
|
3406
|
+
function structuralRange(
|
|
3407
|
+
parent: Node,
|
|
3408
|
+
before: Node | null,
|
|
3409
|
+
label: string,
|
|
3410
|
+
borrowOuter: boolean,
|
|
3411
|
+
allowMarkerlessHydration: true,
|
|
3412
|
+
): readonly [start: Comment, end: Comment, hydrated: boolean, ownsMarkers: boolean] | undefined
|
|
3413
|
+
function structuralRange(
|
|
3414
|
+
parent: Node,
|
|
3415
|
+
before: Node | null,
|
|
3416
|
+
label: string,
|
|
3417
|
+
borrowOuter = false,
|
|
3418
|
+
allowMarkerlessHydration = false,
|
|
3419
|
+
): readonly [start: Comment, end: Comment, hydrated: boolean, ownsMarkers: boolean] | undefined {
|
|
3420
|
+
const current = borrowHydrationSlotRange(parent, true)
|
|
3421
|
+
if (current !== undefined) {
|
|
3422
|
+
claimHydrationSlotRange(parent)
|
|
3423
|
+
return [current[0], current[1], true, true]
|
|
3424
|
+
}
|
|
3425
|
+
if (borrowOuter) {
|
|
3426
|
+
const outer = borrowHydrationSlotRange(parent, false)
|
|
3427
|
+
if (outer !== undefined) return [outer[0], outer[1], true, false]
|
|
3428
|
+
}
|
|
3429
|
+
if (allowMarkerlessHydration && hydrationCursor(parent) !== undefined) return undefined
|
|
2946
3430
|
const hydrated = claimHydrationSlotRange(parent)
|
|
2947
|
-
if (hydrated !== undefined) return [hydrated[0], hydrated[1], true]
|
|
3431
|
+
if (hydrated !== undefined) return [hydrated[0], hydrated[1], true, true]
|
|
2948
3432
|
const start = document.createComment(DEV ? `vidact:${label}` : '')
|
|
2949
3433
|
const end = document.createComment(DEV ? `/vidact:${label}` : '')
|
|
2950
3434
|
parent.insertBefore(start, before)
|
|
2951
3435
|
parent.insertBefore(end, before)
|
|
2952
|
-
return [start, end, false]
|
|
3436
|
+
return [start, end, false, true]
|
|
2953
3437
|
}
|
|
2954
3438
|
|
|
2955
3439
|
function subscribe(
|
|
@@ -2959,12 +3443,29 @@ function subscribe(
|
|
|
2959
3443
|
additionalScope?: CompiledScope,
|
|
2960
3444
|
additionalReads?: SourceMask,
|
|
2961
3445
|
): () => void {
|
|
2962
|
-
|
|
2963
|
-
if (
|
|
2964
|
-
|
|
2965
|
-
|
|
3446
|
+
let first: (() => void) | undefined
|
|
3447
|
+
if (!isEmptySources(reads)) {
|
|
3448
|
+
const remove = scope[0](reads, run)
|
|
3449
|
+
if (remove !== noop) first = remove
|
|
3450
|
+
}
|
|
3451
|
+
let second: (() => void) | undefined
|
|
3452
|
+
if (
|
|
3453
|
+
additionalScope !== undefined &&
|
|
3454
|
+
additionalReads !== undefined &&
|
|
3455
|
+
!isEmptySources(additionalReads)
|
|
3456
|
+
) {
|
|
3457
|
+
const additionalRun =
|
|
3458
|
+
scopeNamespaces.get(additionalScope) === scopeNamespaces.get(scope)
|
|
3459
|
+
? run
|
|
3460
|
+
: () => withScopeNamespace(scope, run)
|
|
3461
|
+
const remove = additionalScope[0](additionalReads, additionalRun)
|
|
3462
|
+
if (remove !== noop) second = remove
|
|
3463
|
+
}
|
|
3464
|
+
if (first === undefined) return second ?? noop
|
|
3465
|
+
if (second === undefined) return first
|
|
2966
3466
|
return () => {
|
|
2967
|
-
|
|
3467
|
+
first()
|
|
3468
|
+
second()
|
|
2968
3469
|
}
|
|
2969
3470
|
}
|
|
2970
3471
|
|
|
@@ -3007,7 +3508,7 @@ function createOwner(
|
|
|
3007
3508
|
if (profilingEnabled) {
|
|
3008
3509
|
const owner: Owner = [
|
|
3009
3510
|
false,
|
|
3010
|
-
|
|
3511
|
+
[],
|
|
3011
3512
|
context,
|
|
3012
3513
|
rootIdentity,
|
|
3013
3514
|
boundary,
|
|
@@ -3018,8 +3519,8 @@ function createOwner(
|
|
|
3018
3519
|
return owner
|
|
3019
3520
|
}
|
|
3020
3521
|
const owner: Owner = retainedUiEnabled
|
|
3021
|
-
? [false,
|
|
3022
|
-
: [false,
|
|
3522
|
+
? [false, [], context, rootIdentity, boundary, retainedConnection]
|
|
3523
|
+
: [false, [], context, rootIdentity, boundary]
|
|
3023
3524
|
if (DEV) owner[7] = activeConstructionOwner?.[7] ?? activeProfileName
|
|
3024
3525
|
return owner
|
|
3025
3526
|
}
|
|
@@ -3362,7 +3863,7 @@ function withRootIdentity<T>(rootIdentity: RootIdentity | null, operation: () =>
|
|
|
3362
3863
|
}
|
|
3363
3864
|
|
|
3364
3865
|
function onCleanup(cleanup: () => void): void {
|
|
3365
|
-
activeOwner?.[1].
|
|
3866
|
+
activeOwner?.[1].push(cleanup)
|
|
3366
3867
|
}
|
|
3367
3868
|
|
|
3368
3869
|
function disposeOwner(owner: Owner): void {
|
|
@@ -3371,21 +3872,21 @@ function disposeOwner(owner: Owner): void {
|
|
|
3371
3872
|
disposalCascadeDepth += 1
|
|
3372
3873
|
disposalCascadeOwners.add(owner)
|
|
3373
3874
|
if (DEV) activeOwnerCount -= 1
|
|
3374
|
-
pendingInsertionCommits.delete(owner)
|
|
3375
|
-
pendingOwnerCommits.delete(owner)
|
|
3376
|
-
const cleanups = [...owner[1]]
|
|
3377
|
-
owner[1].clear()
|
|
3875
|
+
if (pendingInsertionCommits.delete(owner)) pendingInsertionOwnerCount -= 1
|
|
3876
|
+
if (pendingOwnerCommits.delete(owner)) pendingOwnerCommitCount -= 1
|
|
3378
3877
|
let firstError: unknown
|
|
3379
3878
|
let hasError = false
|
|
3380
3879
|
try {
|
|
3381
|
-
for (let
|
|
3880
|
+
for (let remaining = owner[1].length; remaining > 0; remaining -= 1) {
|
|
3881
|
+
const cleanup = owner[1].pop()
|
|
3382
3882
|
try {
|
|
3383
|
-
|
|
3883
|
+
cleanup?.()
|
|
3384
3884
|
} catch (error) {
|
|
3385
3885
|
if (!hasError) firstError = error
|
|
3386
3886
|
hasError = true
|
|
3387
3887
|
}
|
|
3388
3888
|
}
|
|
3889
|
+
owner[1].length = 0
|
|
3389
3890
|
} finally {
|
|
3390
3891
|
disposalCascadeDepth -= 1
|
|
3391
3892
|
if (disposalCascadeDepth === 0) disposalCascadeOwners.clear()
|
|
@@ -3397,8 +3898,50 @@ function disposeOwner(owner: Owner): void {
|
|
|
3397
3898
|
export function readCompiledOwnerMetrics(): {
|
|
3398
3899
|
readonly active: number
|
|
3399
3900
|
readonly created: number
|
|
3901
|
+
readonly updaters: number
|
|
3902
|
+
readonly schedulerPlans: number
|
|
3400
3903
|
} {
|
|
3401
|
-
return {
|
|
3904
|
+
return {
|
|
3905
|
+
active: activeOwnerCount,
|
|
3906
|
+
created: createdOwnerCount,
|
|
3907
|
+
updaters: createdUpdaterCount,
|
|
3908
|
+
schedulerPlans: createdSchedulerPlanCount,
|
|
3909
|
+
}
|
|
3910
|
+
}
|
|
3911
|
+
|
|
3912
|
+
/** @internal Test-only publication traversal evidence. */
|
|
3913
|
+
export function readCompiledPublicationMetrics(): { readonly visitedNodes: number } {
|
|
3914
|
+
return { visitedNodes: visitedPublishedNodeCount }
|
|
3915
|
+
}
|
|
3916
|
+
|
|
3917
|
+
interface MountedBindingState {
|
|
3918
|
+
start: Comment | null
|
|
3919
|
+
end: Comment | null
|
|
3920
|
+
current: unknown
|
|
3921
|
+
currentOwner: Owner | null
|
|
3922
|
+
text: Text | null
|
|
3923
|
+
}
|
|
3924
|
+
|
|
3925
|
+
function ensureMountedBindingRange(state: MountedBindingState): readonly [Comment, Comment] {
|
|
3926
|
+
if (state.start !== null && state.end !== null) return [state.start, state.end]
|
|
3927
|
+
const anchor = state.text
|
|
3928
|
+
const parent = anchor?.parentNode
|
|
3929
|
+
if (anchor === null || parent === null || parent === undefined) {
|
|
3930
|
+
throw new Error(DEV ? 'markerless binding text is detached' : 'V010')
|
|
3931
|
+
}
|
|
3932
|
+
state.start = document.createComment(DEV ? 'vidact:binding' : '')
|
|
3933
|
+
state.end = document.createComment(DEV ? '/vidact:binding' : '')
|
|
3934
|
+
parent.insertBefore(state.start, anchor)
|
|
3935
|
+
parent.insertBefore(state.end, anchor.nextSibling)
|
|
3936
|
+
return [state.start, state.end]
|
|
3937
|
+
}
|
|
3938
|
+
|
|
3939
|
+
function clearMountedBinding(state: MountedBindingState): void {
|
|
3940
|
+
const [start, end] = ensureMountedBindingRange(state)
|
|
3941
|
+
const owner = state.currentOwner
|
|
3942
|
+
state.currentOwner = null
|
|
3943
|
+
state.text = null
|
|
3944
|
+
disposeRange(owner, start, end)
|
|
3402
3945
|
}
|
|
3403
3946
|
|
|
3404
3947
|
function mountCompiledBindingBefore(
|
|
@@ -3406,144 +3949,160 @@ function mountCompiledBindingBefore(
|
|
|
3406
3949
|
value: CompiledBinding<unknown>,
|
|
3407
3950
|
before: Node | null,
|
|
3408
3951
|
): void {
|
|
3409
|
-
const unset = Symbol(DEV ? 'Vidact.UnsetBinding' : undefined)
|
|
3410
3952
|
const initial = value[1]()
|
|
3411
3953
|
const scalarInitial = isScalarRenderValue(initial)
|
|
3954
|
+
const structuralInitial = ownedStructuralBinding(initial)
|
|
3412
3955
|
const hydratedTextRange = scalarInitial
|
|
3413
3956
|
? claimHydrationTextRange(parent, toText(initial))
|
|
3414
3957
|
: undefined
|
|
3415
3958
|
const borrowedHydrationRange =
|
|
3416
3959
|
isHydrating() && !scalarInitial
|
|
3417
|
-
? borrowHydrationSlotRange(parent,
|
|
3960
|
+
? borrowHydrationSlotRange(parent, structuralInitial?.[2] === 'slot')
|
|
3418
3961
|
: undefined
|
|
3419
3962
|
const hydratedStructuralRange =
|
|
3420
3963
|
isHydrating() && !scalarInitial
|
|
3421
3964
|
? (borrowedHydrationRange ?? claimHydrationSlotRange(parent))
|
|
3422
3965
|
: undefined
|
|
3423
3966
|
const hydratedRange = hydratedTextRange ?? hydratedStructuralRange
|
|
3424
|
-
const
|
|
3425
|
-
|
|
3426
|
-
|
|
3967
|
+
const markerlessScalar = hydratedRange === undefined && scalarInitial && !isHydrating()
|
|
3968
|
+
let start = hydratedRange?.[0] ?? null
|
|
3969
|
+
let end = hydratedRange?.[1] ?? null
|
|
3970
|
+
if (!markerlessScalar && hydratedRange === undefined) {
|
|
3971
|
+
start = document.createComment(DEV ? 'vidact:binding' : '')
|
|
3972
|
+
end = document.createComment(DEV ? '/vidact:binding' : '')
|
|
3427
3973
|
parent.insertBefore(start, before)
|
|
3428
3974
|
parent.insertBefore(end, before)
|
|
3429
3975
|
}
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
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)
|
|
3976
|
+
const state: MountedBindingState = {
|
|
3977
|
+
start,
|
|
3978
|
+
end,
|
|
3979
|
+
current: hydratedTextRange === undefined && !markerlessScalar ? UNSET_BINDING : initial,
|
|
3980
|
+
currentOwner: null,
|
|
3981
|
+
text: hydratedTextRange?.[2] ?? null,
|
|
3451
3982
|
}
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
currentOwner = null
|
|
3456
|
-
text = null
|
|
3457
|
-
disposeRange(owner, start, end)
|
|
3983
|
+
if (markerlessScalar) {
|
|
3984
|
+
state.text = document.createTextNode(toText(initial))
|
|
3985
|
+
parent.insertBefore(state.text, before)
|
|
3458
3986
|
}
|
|
3459
3987
|
const update = (): void => {
|
|
3460
3988
|
const next = value[1]()
|
|
3461
|
-
if (current !==
|
|
3989
|
+
if (state.current !== UNSET_BINDING && Object.is(next, state.current)) return
|
|
3462
3990
|
if (
|
|
3463
|
-
current !==
|
|
3464
|
-
isRenderableProtocol(current) &&
|
|
3991
|
+
state.current !== UNSET_BINDING &&
|
|
3992
|
+
isRenderableProtocol(state.current) &&
|
|
3465
3993
|
isRenderableProtocol(next) &&
|
|
3466
|
-
canReconcileRenderable(current) &&
|
|
3994
|
+
canReconcileRenderable(state.current) &&
|
|
3467
3995
|
canReconcileRenderable(next) &&
|
|
3468
|
-
Object.is(renderableIdentity(current), renderableIdentity(next))
|
|
3996
|
+
Object.is(renderableIdentity(state.current), renderableIdentity(next))
|
|
3469
3997
|
) {
|
|
3470
|
-
current = next
|
|
3998
|
+
state.current = next
|
|
3471
3999
|
return
|
|
3472
4000
|
}
|
|
3473
|
-
const currentParent = rangeParent(start, end, 'binding range')
|
|
3474
|
-
|
|
3475
4001
|
if (isScalarRenderValue(next)) {
|
|
3476
4002
|
const content = toText(next)
|
|
3477
|
-
if (text !== null) {
|
|
3478
|
-
const target = text
|
|
4003
|
+
if (state.text !== null) {
|
|
4004
|
+
const target = state.text
|
|
3479
4005
|
const previousContent = target.data
|
|
3480
|
-
const previous = current
|
|
4006
|
+
const previous = state.current
|
|
3481
4007
|
stagePublication([
|
|
3482
4008
|
() => {
|
|
3483
4009
|
if (target.data !== content) target.data = content
|
|
3484
|
-
current = next
|
|
4010
|
+
state.current = next
|
|
3485
4011
|
},
|
|
3486
4012
|
() => {
|
|
3487
4013
|
if (target.data !== previousContent) target.data = previousContent
|
|
3488
|
-
current = previous
|
|
4014
|
+
state.current = previous
|
|
3489
4015
|
},
|
|
3490
4016
|
])
|
|
3491
4017
|
return
|
|
3492
4018
|
}
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
currentParent
|
|
3496
|
-
|
|
4019
|
+
clearMountedBinding(state)
|
|
4020
|
+
const [rangeStart, rangeEnd] = ensureMountedBindingRange(state)
|
|
4021
|
+
const currentParent = rangeParent(rangeStart, rangeEnd, 'binding range')
|
|
4022
|
+
state.text = document.createTextNode(content)
|
|
4023
|
+
currentParent.insertBefore(state.text, rangeEnd)
|
|
4024
|
+
state.current = next
|
|
3497
4025
|
return
|
|
3498
4026
|
}
|
|
3499
4027
|
|
|
4028
|
+
const [rangeStart, rangeEnd] = ensureMountedBindingRange(state)
|
|
4029
|
+
const currentParent = rangeParent(rangeStart, rangeEnd, 'binding range')
|
|
3500
4030
|
const nextOwner = createOwner()
|
|
3501
4031
|
const stagedValue =
|
|
3502
4032
|
isRenderableProtocol(next) && canReconcileRenderable(next)
|
|
3503
|
-
?
|
|
4033
|
+
? stableRenderableBinding(value, next)
|
|
3504
4034
|
: (next as RenderValue)
|
|
3505
4035
|
const [fragment, staged] = stageValue(stagedValue as RenderValue, nextOwner)
|
|
3506
4036
|
try {
|
|
3507
|
-
|
|
4037
|
+
clearMountedBinding(state)
|
|
3508
4038
|
} catch (error) {
|
|
3509
4039
|
disposeOwner(nextOwner)
|
|
3510
4040
|
throw error
|
|
3511
4041
|
}
|
|
3512
|
-
currentOwner = nextOwner
|
|
3513
|
-
currentParent.insertBefore(fragment,
|
|
4042
|
+
state.currentOwner = nextOwner
|
|
4043
|
+
currentParent.insertBefore(fragment, rangeEnd)
|
|
3514
4044
|
commitPublishedNodes(staged)
|
|
3515
|
-
current = next
|
|
4045
|
+
state.current = next
|
|
3516
4046
|
}
|
|
3517
4047
|
|
|
3518
4048
|
if (hydratedStructuralRange !== undefined) {
|
|
4049
|
+
const rangeEnd = hydratedStructuralRange[1]
|
|
3519
4050
|
const nextOwner = createOwner()
|
|
3520
|
-
const [fragment, staged] = withHydrationInsertion(parent,
|
|
4051
|
+
const [fragment, staged] = withHydrationInsertion(parent, rangeEnd, () =>
|
|
3521
4052
|
stageRender(
|
|
3522
4053
|
() =>
|
|
3523
4054
|
(isRenderableProtocol(initial) && canReconcileRenderable(initial)
|
|
3524
|
-
?
|
|
4055
|
+
? stableRenderableBinding(value, initial)
|
|
3525
4056
|
: initial) as RenderValue,
|
|
3526
4057
|
nextOwner,
|
|
3527
4058
|
),
|
|
3528
4059
|
)
|
|
3529
|
-
parent.insertBefore(fragment,
|
|
4060
|
+
parent.insertBefore(fragment, rangeEnd)
|
|
3530
4061
|
commitPublishedNodes(staged)
|
|
3531
|
-
currentOwner = nextOwner
|
|
3532
|
-
current = initial
|
|
3533
|
-
} else if (hydratedRange === undefined) {
|
|
4062
|
+
state.currentOwner = nextOwner
|
|
4063
|
+
state.current = initial
|
|
4064
|
+
} else if (hydratedRange === undefined && !markerlessScalar) {
|
|
3534
4065
|
update()
|
|
3535
4066
|
}
|
|
3536
4067
|
const removeUpdater = subscribeBinding(value, update)
|
|
3537
4068
|
onCleanup(() => {
|
|
3538
4069
|
removeUpdater()
|
|
3539
|
-
|
|
4070
|
+
if (state.start === null || state.end === null) {
|
|
4071
|
+
state.text?.remove()
|
|
4072
|
+
state.text = null
|
|
4073
|
+
return
|
|
4074
|
+
}
|
|
4075
|
+
clearMountedBinding(state)
|
|
3540
4076
|
if (borrowedHydrationRange === undefined) {
|
|
3541
|
-
start.remove()
|
|
3542
|
-
end.remove()
|
|
4077
|
+
state.start.remove()
|
|
4078
|
+
state.end.remove()
|
|
3543
4079
|
}
|
|
3544
4080
|
})
|
|
3545
4081
|
}
|
|
3546
4082
|
|
|
4083
|
+
function stableRenderableBinding(
|
|
4084
|
+
value: CompiledBinding<unknown>,
|
|
4085
|
+
renderable: RenderableProtocol,
|
|
4086
|
+
): unknown {
|
|
4087
|
+
const identity = renderableIdentity(renderable)
|
|
4088
|
+
let previous = renderablePropsSnapshot(renderable)
|
|
4089
|
+
const input = binding(
|
|
4090
|
+
value[2],
|
|
4091
|
+
value[3],
|
|
4092
|
+
() => {
|
|
4093
|
+
const next = value[1]()
|
|
4094
|
+
if (!isRenderableProtocol(next) || !Object.is(renderableIdentity(next), identity)) {
|
|
4095
|
+
return previous
|
|
4096
|
+
}
|
|
4097
|
+
previous = renderablePropsSnapshot(next)
|
|
4098
|
+
return previous
|
|
4099
|
+
},
|
|
4100
|
+
value[4],
|
|
4101
|
+
value[5],
|
|
4102
|
+
)
|
|
4103
|
+
return materializeRenderableWithInput(renderable, input)
|
|
4104
|
+
}
|
|
4105
|
+
|
|
3547
4106
|
function materialize(value: RenderValue): Node[] {
|
|
3548
4107
|
const insertionPoint = hydrationInsertionPoint()
|
|
3549
4108
|
if (insertionPoint !== undefined) {
|
|
@@ -3617,6 +4176,40 @@ function stageRender(
|
|
|
3617
4176
|
}
|
|
3618
4177
|
}
|
|
3619
4178
|
|
|
4179
|
+
function stageMarkerlessHydrationRender(
|
|
4180
|
+
parent: Node,
|
|
4181
|
+
before: Node | null,
|
|
4182
|
+
render: () => CompiledRenderValue,
|
|
4183
|
+
owner: Owner,
|
|
4184
|
+
): readonly [first: Node | null, after: Node | null, nodes: readonly Node[]] {
|
|
4185
|
+
const first = hydrationCursor(parent)
|
|
4186
|
+
if (first === undefined) {
|
|
4187
|
+
throw new Error('markerless hydration render requires active hydration')
|
|
4188
|
+
}
|
|
4189
|
+
try {
|
|
4190
|
+
withHydrationInsertion(parent, before, () =>
|
|
4191
|
+
withOwner(owner, () => insertValue(parent, render(), before)),
|
|
4192
|
+
)
|
|
4193
|
+
} catch (error) {
|
|
4194
|
+
try {
|
|
4195
|
+
disposeOwner(owner)
|
|
4196
|
+
} catch {
|
|
4197
|
+
// Preserve the render or hydration error after every registered cleanup ran.
|
|
4198
|
+
}
|
|
4199
|
+
throw error
|
|
4200
|
+
}
|
|
4201
|
+
const after = hydrationCursor(parent)
|
|
4202
|
+
if (after === undefined) {
|
|
4203
|
+
disposeOwner(owner)
|
|
4204
|
+
throw new Error('markerless hydration render lost its hydration cursor')
|
|
4205
|
+
}
|
|
4206
|
+
const nodes: Node[] = []
|
|
4207
|
+
for (let node = first; node !== null && node !== after; node = node.nextSibling) {
|
|
4208
|
+
nodes.push(node)
|
|
4209
|
+
}
|
|
4210
|
+
return [first, after, nodes]
|
|
4211
|
+
}
|
|
4212
|
+
|
|
3620
4213
|
function insertValue(
|
|
3621
4214
|
parent: Node,
|
|
3622
4215
|
value: RenderValue,
|
|
@@ -3766,10 +4359,13 @@ function isRefValue(value: unknown): value is RefValue {
|
|
|
3766
4359
|
}
|
|
3767
4360
|
|
|
3768
4361
|
function claimPendingRefOwners(root: Node): void {
|
|
4362
|
+
if (pendingRefCount === 0) return
|
|
3769
4363
|
visitNodes(root, (node) => {
|
|
3770
4364
|
if (!(node instanceof Element)) return
|
|
3771
4365
|
const pending = pendingRefs.get(node)
|
|
3772
|
-
if (pending
|
|
4366
|
+
if (pending === undefined || pending[0] !== null) return
|
|
4367
|
+
pending[0] = activeOwner
|
|
4368
|
+
activeOwner?.[1].push(() => deletePendingRef(node, pending))
|
|
3773
4369
|
})
|
|
3774
4370
|
}
|
|
3775
4371
|
|
|
@@ -3787,9 +4383,15 @@ function commitPublishedNodesNow(nodes: readonly Node[]): void {
|
|
|
3787
4383
|
if (publicationRoot instanceof DocumentFragment && !(publicationRoot instanceof ShadowRoot)) {
|
|
3788
4384
|
return
|
|
3789
4385
|
}
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
4386
|
+
if (pendingInsertionOwnerCount > 0) {
|
|
4387
|
+
for (const node of nodes) commitNodeInsertions(node)
|
|
4388
|
+
}
|
|
4389
|
+
if (pendingRefCount > 0) {
|
|
4390
|
+
for (const node of nodes) commitNodeRefs(node)
|
|
4391
|
+
}
|
|
4392
|
+
if (pendingOwnerCommitCount > 0) {
|
|
4393
|
+
for (const node of nodes) commitNodeResources(node)
|
|
4394
|
+
}
|
|
3793
4395
|
}
|
|
3794
4396
|
|
|
3795
4397
|
function commitNodeInsertions(root: Node): void {
|
|
@@ -3803,12 +4405,12 @@ function commitNodeRefs(root: Node): void {
|
|
|
3803
4405
|
if (node instanceof Element) {
|
|
3804
4406
|
const pending = pendingRefs.get(node)
|
|
3805
4407
|
if (pending !== undefined) {
|
|
3806
|
-
|
|
4408
|
+
deletePendingRef(node, pending)
|
|
3807
4409
|
const owner = pending[0] ?? activeOwner
|
|
3808
4410
|
commitContextInsertions(owner?.[2] ?? null)
|
|
3809
4411
|
commitOwnerInsertions(owner ?? undefined)
|
|
3810
4412
|
const cleanup = attachRef(pending[1], node)
|
|
3811
|
-
if (pending[2] === undefined) owner?.[1].
|
|
4413
|
+
if (pending[2] === undefined) owner?.[1].push(cleanup)
|
|
3812
4414
|
else pending[2](cleanup)
|
|
3813
4415
|
}
|
|
3814
4416
|
}
|
|
@@ -3832,6 +4434,7 @@ function commitOwnerInsertions(owner: Owner | undefined): void {
|
|
|
3832
4434
|
const commits = pendingInsertionCommits.get(owner)
|
|
3833
4435
|
if (commits === undefined) return
|
|
3834
4436
|
pendingInsertionCommits.delete(owner)
|
|
4437
|
+
pendingInsertionOwnerCount -= 1
|
|
3835
4438
|
for (const commit of commits) commit()
|
|
3836
4439
|
}
|
|
3837
4440
|
|
|
@@ -3840,6 +4443,7 @@ function commitOwnerResources(owner: Owner | undefined): void {
|
|
|
3840
4443
|
const commits = pendingOwnerCommits.get(owner)
|
|
3841
4444
|
if (commits === undefined) return
|
|
3842
4445
|
pendingOwnerCommits.delete(owner)
|
|
4446
|
+
pendingOwnerCommitCount -= 1
|
|
3843
4447
|
for (const commit of commits) commit()
|
|
3844
4448
|
}
|
|
3845
4449
|
|
|
@@ -3854,6 +4458,7 @@ function commitRangeRefs(start: Node, end: Node): void {
|
|
|
3854
4458
|
}
|
|
3855
4459
|
|
|
3856
4460
|
function visitNodes(root: Node, visit: (node: Node) => void): void {
|
|
4461
|
+
visitedPublishedNodeCount += 1
|
|
3857
4462
|
visit(root)
|
|
3858
4463
|
for (const child of root.childNodes) visitNodes(child, visit)
|
|
3859
4464
|
}
|
|
@@ -3870,12 +4475,12 @@ function attachRef<T>(value: RefValue<T>, target: T): () => void {
|
|
|
3870
4475
|
}
|
|
3871
4476
|
}
|
|
3872
4477
|
|
|
3873
|
-
function scheduleFlush(flush:
|
|
4478
|
+
function scheduleFlush(flush: FlushTask): void {
|
|
3874
4479
|
scheduledFlushes.add(flush)
|
|
3875
4480
|
if (transactionDepth === 0) drainFlushes()
|
|
3876
4481
|
}
|
|
3877
4482
|
|
|
3878
|
-
function scheduleOwnerFlush(owner: Owner, flush:
|
|
4483
|
+
function scheduleOwnerFlush(owner: Owner, flush: FlushTask): void {
|
|
3879
4484
|
const connection = owner[5]
|
|
3880
4485
|
if (connection === null || connection === undefined || connection.connected) {
|
|
3881
4486
|
scheduleFlush(flush)
|
|
@@ -3894,7 +4499,7 @@ function drainFlushes(): void {
|
|
|
3894
4499
|
drainingFlushes = true
|
|
3895
4500
|
const publication: PublicationOperation[] = []
|
|
3896
4501
|
activePublication = publication
|
|
3897
|
-
const runs = new Map<
|
|
4502
|
+
const runs = new Map<FlushTask, number>()
|
|
3898
4503
|
let committing = false
|
|
3899
4504
|
let failure: unknown
|
|
3900
4505
|
let failed = false
|
|
@@ -3909,7 +4514,8 @@ function drainFlushes(): void {
|
|
|
3909
4514
|
throw new Error(DEV ? 'Vidact compiled scopes did not stabilize' : 'V011')
|
|
3910
4515
|
}
|
|
3911
4516
|
runs.set(flush, runCount)
|
|
3912
|
-
flush()
|
|
4517
|
+
if (typeof flush === 'function') flush()
|
|
4518
|
+
else flush[1]()
|
|
3913
4519
|
}
|
|
3914
4520
|
activePublication = null
|
|
3915
4521
|
committing = true
|