@kudzujs/core 0.17.0 → 0.17.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/framework/binding-runtime.js +36 -41
- package/framework/list-runtime.js +8 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Kudzu compiles ordinary React-shaped TypeScript and TSX into complete static HTM
|
|
|
14
14
|
|
|
15
15
|
> Experimental `0.17.x`: the compiler API and supported TSX surface may change.
|
|
16
16
|
|
|
17
|
-
**Current release: [0.17.
|
|
17
|
+
**Current release: [0.17.2](https://github.com/kudzujs/kudzu/releases/tag/v0.17.2).** Delayed evaluators cannot regain authority after DOM or source-state release, including same-node remount and state-ID reuse. Keyed conditions now settle correctly when row data and parent selection change in one turn. Native Chrome regressions cover both fixes; static siblings remain JavaScript-free. Generator `create-kudzu@0.1.163` targets core `^0.17.2`. Output sizes have mixed deltas; no speed improvement is claimed. Restoration latency and AI-cost/1.0 gates remain unresolved.
|
|
18
18
|
|
|
19
19
|
- [Documentation](https://kudzujs.cloud/docs)
|
|
20
20
|
- [Installation guide](https://kudzujs.cloud/docs#install)
|
|
@@ -5,8 +5,6 @@ import { serializeStyle } from "./style.js"
|
|
|
5
5
|
const imports = new Map()
|
|
6
6
|
const bindingTargets = new Map()
|
|
7
7
|
const conditionTargets = globalThis.__KUDZU_CONDITIONS__ && new Map()
|
|
8
|
-
const mountedBindings = new WeakSet()
|
|
9
|
-
const mountedConditions = globalThis.__KUDZU_CONDITIONS__ && new WeakSet()
|
|
10
8
|
const bindingRegistrations = new WeakMap()
|
|
11
9
|
const conditionRegistrations = globalThis.__KUDZU_CONDITIONS__ && new WeakMap()
|
|
12
10
|
const textDescriptors = globalThis.__KUDZU_TEXT_BINDINGS__ && typeof document !== "undefined" ? JSON.parse(document.body.dataset.kTextBindings ?? "[]") : []
|
|
@@ -44,7 +42,7 @@ function commitBindings(id) {
|
|
|
44
42
|
if (bindings) {
|
|
45
43
|
for (const binding of bindings) {
|
|
46
44
|
if (!binding.node.isConnected) bindings.delete(binding)
|
|
47
|
-
else patchBinding(binding.node, binding.target, binding.read())
|
|
45
|
+
else if (binding.read) patchBinding(binding.node, binding.target, binding.read())
|
|
48
46
|
}
|
|
49
47
|
}
|
|
50
48
|
if (!globalThis.__KUDZU_CONDITIONS__) return
|
|
@@ -67,8 +65,7 @@ if (typeof document !== "undefined") mountDom(document)
|
|
|
67
65
|
|
|
68
66
|
function mountBindings(root) {
|
|
69
67
|
if (globalThis.__KUDZU_ELEMENT_BINDINGS__ !== false) for (const node of matching(root, bindingSelector)) {
|
|
70
|
-
if (
|
|
71
|
-
mountedBindings.add(node)
|
|
68
|
+
if (bindingRegistrations.has(node)) continue
|
|
72
69
|
const registrations = []
|
|
73
70
|
bindingRegistrations.set(node, registrations)
|
|
74
71
|
const descriptors = []
|
|
@@ -85,23 +82,14 @@ function mountBindings(root) {
|
|
|
85
82
|
patchBinding(node, target, binding.read())
|
|
86
83
|
continue
|
|
87
84
|
}
|
|
88
|
-
|
|
89
|
-
if (!node.isConnected) return
|
|
90
|
-
const binding = { node, target, read: evaluator.read }
|
|
91
|
-
for (const id of evaluator.stateIds) {
|
|
92
|
-
register(bindingTargets, id, binding)
|
|
93
|
-
registrations.push([id, binding])
|
|
94
|
-
}
|
|
95
|
-
patchBinding(node, target, binding.read())
|
|
96
|
-
}).catch(error => console.error(error))
|
|
85
|
+
mountBindingEvaluator(node, target, descriptor, registrations)
|
|
97
86
|
}
|
|
98
87
|
}
|
|
99
88
|
if (globalThis.__KUDZU_TEXT_BINDINGS__) {
|
|
100
89
|
for (const node of textBindingStarts(root)) {
|
|
101
|
-
if (
|
|
90
|
+
if (bindingRegistrations.has(node)) continue
|
|
102
91
|
const descriptor = textDescriptors[Number(node.data.slice("k-text:".length))]
|
|
103
92
|
if (!descriptor) continue
|
|
104
|
-
mountedBindings.add(node)
|
|
105
93
|
const registrations = []
|
|
106
94
|
bindingRegistrations.set(node, registrations)
|
|
107
95
|
if (descriptor.state) {
|
|
@@ -111,47 +99,56 @@ function mountBindings(root) {
|
|
|
111
99
|
patchBinding(node, "text", binding.read())
|
|
112
100
|
continue
|
|
113
101
|
}
|
|
114
|
-
|
|
115
|
-
if (!node.isConnected) return
|
|
116
|
-
const binding = { node, target: "text", read: evaluator.read }
|
|
117
|
-
for (const id of evaluator.stateIds) {
|
|
118
|
-
register(bindingTargets, id, binding)
|
|
119
|
-
registrations.push([id, binding])
|
|
120
|
-
}
|
|
121
|
-
patchBinding(node, "text", binding.read())
|
|
122
|
-
}).catch(error => console.error(error))
|
|
102
|
+
mountBindingEvaluator(node, "text", descriptor, registrations)
|
|
123
103
|
}
|
|
124
104
|
}
|
|
125
105
|
}
|
|
126
106
|
|
|
107
|
+
function mountBindingEvaluator(node, target, descriptor, registrations) {
|
|
108
|
+
const binding = { node, target }
|
|
109
|
+
// Register dependencies before loading so state release also revokes pending work.
|
|
110
|
+
for (const id of bindingStateIds(descriptor)) {
|
|
111
|
+
register(bindingTargets, id, binding)
|
|
112
|
+
registrations.push([id, binding])
|
|
113
|
+
}
|
|
114
|
+
loadEvaluator(descriptor).then(evaluator => {
|
|
115
|
+
if (!node.isConnected || bindingRegistrations.get(node) !== registrations) return
|
|
116
|
+
binding.read = evaluator.read
|
|
117
|
+
patchBinding(node, target, binding.read())
|
|
118
|
+
}).catch(error => console.error(error))
|
|
119
|
+
}
|
|
120
|
+
|
|
127
121
|
function mountConditions(root) {
|
|
128
122
|
for (const start of matching(root, "template[data-k-if]")) {
|
|
129
|
-
if (
|
|
130
|
-
|
|
123
|
+
if (conditionRegistrations.has(start)) continue
|
|
124
|
+
// Reserve membership while the evaluator loads, before subscriptions exist.
|
|
125
|
+
conditionRegistrations.set(start, undefined)
|
|
131
126
|
const descriptor = JSON.parse(start.dataset.kIf)
|
|
132
127
|
const end = findEnd(start, descriptor.id)
|
|
133
128
|
const truthy = globalThis.__KUDZU_SVG_CONDITIONS__ && descriptor.svg ? start.dataset.kSvgTrue : start.content.querySelector("template[data-k-true]")
|
|
134
129
|
const falsy = globalThis.__KUDZU_SVG_CONDITIONS__ && descriptor.svg ? start.dataset.kSvgFalse : start.content.querySelector("template[data-k-false]")
|
|
135
130
|
if (!end || !truthy || !falsy) continue
|
|
136
131
|
const condition = { start, end, truthy, falsy, svg: globalThis.__KUDZU_SVG_CONDITIONS__ && descriptor.svg, kind: descriptor.kind, current: conditionKey(descriptor.kind, descriptor.initial), mount: globalThis.__KUDZU_CONDITION_MOUNTS__ !== false && descriptor.mount, owned: globalThis.__KUDZU_CONDITION_STATE__ !== false && descriptor.owned }
|
|
132
|
+
const registrations = []
|
|
133
|
+
const registration = { condition, registrations }
|
|
134
|
+
conditionRegistrations.set(start, registration)
|
|
135
|
+
for (const id of descriptor.state ? [descriptor.state] : bindingStateIds(descriptor)) {
|
|
136
|
+
register(conditionTargets, id, condition)
|
|
137
|
+
registrations.push([id, condition])
|
|
138
|
+
}
|
|
137
139
|
if (globalThis.__KUDZU_CONDITION_STATE__ !== false) mountConditionStates(condition, Boolean(descriptor.initial), false)
|
|
138
140
|
const mount = evaluator => {
|
|
139
|
-
if (!start.isConnected) return
|
|
141
|
+
if (!start.isConnected || conditionRegistrations.get(start) !== registration) return
|
|
140
142
|
condition.read = evaluator.read
|
|
141
|
-
const registrations = []
|
|
142
|
-
for (const id of evaluator.stateIds) {
|
|
143
|
-
register(conditionTargets, id, condition)
|
|
144
|
-
registrations.push([id, condition])
|
|
145
|
-
}
|
|
146
|
-
conditionRegistrations.set(start, { condition, registrations })
|
|
147
143
|
updateCondition(condition)
|
|
148
144
|
}
|
|
149
|
-
if (descriptor.state) mount({ read: () => browserState.get(descriptor.state)
|
|
145
|
+
if (descriptor.state) mount({ read: () => browserState.get(descriptor.state) })
|
|
150
146
|
else loadEvaluator(descriptor).then(mount).catch(error => console.error(error))
|
|
151
147
|
}
|
|
152
148
|
}
|
|
153
149
|
|
|
154
150
|
function updateCondition(condition) {
|
|
151
|
+
if (!condition.read) return
|
|
155
152
|
const value = condition.read()
|
|
156
153
|
const next = conditionKey(condition.kind, value)
|
|
157
154
|
if (next === condition.current) return
|
|
@@ -173,7 +170,7 @@ function updateCondition(condition) {
|
|
|
173
170
|
const select = globalThis.__KUDZU_VALUE_BINDINGS__ !== false && condition.start.closest("select[data-k-bind-value]")
|
|
174
171
|
if (select) {
|
|
175
172
|
for (const binding of new Set((bindingRegistrations.get(select) ?? []).map(([, entry]) => entry))) {
|
|
176
|
-
patchBinding(binding.node, binding.target, binding.read())
|
|
173
|
+
if (binding.read) patchBinding(binding.node, binding.target, binding.read())
|
|
177
174
|
}
|
|
178
175
|
}
|
|
179
176
|
}
|
|
@@ -182,13 +179,11 @@ function unmountBindings(root) {
|
|
|
182
179
|
if (globalThis.__KUDZU_ELEMENT_BINDINGS__ !== false) for (const node of matching(root, bindingSelector)) {
|
|
183
180
|
for (const [id, binding] of bindingRegistrations.get(node) ?? []) bindingTargets.get(id)?.delete(binding)
|
|
184
181
|
bindingRegistrations.delete(node)
|
|
185
|
-
mountedBindings.delete(node)
|
|
186
182
|
}
|
|
187
183
|
if (globalThis.__KUDZU_TEXT_BINDINGS__) {
|
|
188
184
|
for (const node of textBindingStarts(root)) {
|
|
189
185
|
for (const [id, binding] of bindingRegistrations.get(node) ?? []) bindingTargets.get(id)?.delete(binding)
|
|
190
186
|
bindingRegistrations.delete(node)
|
|
191
|
-
mountedBindings.delete(node)
|
|
192
187
|
}
|
|
193
188
|
}
|
|
194
189
|
}
|
|
@@ -199,14 +194,14 @@ function unmountConditions(root) {
|
|
|
199
194
|
for (const [id, condition] of registration?.registrations ?? []) conditionTargets.get(id)?.delete(condition)
|
|
200
195
|
if (globalThis.__KUDZU_CONDITION_STATE__ !== false && registration?.condition) unmountConditionStates(registration.condition, registration.condition.current === "true")
|
|
201
196
|
conditionRegistrations.delete(start)
|
|
202
|
-
mountedConditions.delete(start)
|
|
203
197
|
}
|
|
204
198
|
}
|
|
205
199
|
|
|
206
200
|
function releaseBindings(id) {
|
|
207
201
|
for (const binding of bindingTargets.get(id) ?? []) {
|
|
208
202
|
for (const [bindingId, entry] of bindingRegistrations.get(binding.node) ?? []) bindingTargets.get(bindingId)?.delete(entry)
|
|
209
|
-
|
|
203
|
+
// Keep mounted membership until unmount so released bindings cannot regain authority.
|
|
204
|
+
bindingRegistrations.set(binding.node, undefined)
|
|
210
205
|
}
|
|
211
206
|
bindingTargets.delete(id)
|
|
212
207
|
if (!globalThis.__KUDZU_CONDITIONS__) return
|
|
@@ -214,7 +209,7 @@ function releaseBindings(id) {
|
|
|
214
209
|
const registration = conditionRegistrations.get(condition.start)
|
|
215
210
|
for (const [conditionId, entry] of registration?.registrations ?? []) conditionTargets.get(conditionId)?.delete(entry)
|
|
216
211
|
if (globalThis.__KUDZU_CONDITION_STATE__ !== false) unmountConditionStates(condition, condition.current === "true")
|
|
217
|
-
conditionRegistrations.
|
|
212
|
+
conditionRegistrations.set(condition.start, undefined)
|
|
218
213
|
}
|
|
219
214
|
conditionTargets.delete(id)
|
|
220
215
|
}
|
|
@@ -650,13 +650,6 @@ function fillListParts(root, parts, item, revision, index = 0, previous) {
|
|
|
650
650
|
}
|
|
651
651
|
}
|
|
652
652
|
fillListExpressions(root, parts, item, revision, index)
|
|
653
|
-
if (__KUDZU_LIST_CONDITIONS__) {
|
|
654
|
-
for (const [marker, descriptor] of parts.conditions) {
|
|
655
|
-
evaluate(descriptor, item, index).then(value => {
|
|
656
|
-
if (revisions.get(root) === revision && marker.isConnected) updateListCondition(marker, descriptor.kind, value, item, index)
|
|
657
|
-
}).catch(error => console.error(error))
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
653
|
}
|
|
661
654
|
|
|
662
655
|
function fillListExpressions(root, parts, item, revision, index) {
|
|
@@ -676,6 +669,14 @@ function fillListExpressions(root, parts, item, revision, index) {
|
|
|
676
669
|
}
|
|
677
670
|
}
|
|
678
671
|
}
|
|
672
|
+
// Parent-state commits invalidate the shared row revision, including pending conditions.
|
|
673
|
+
if (__KUDZU_LIST_CONDITIONS__) {
|
|
674
|
+
for (const [marker, descriptor] of parts.conditions) {
|
|
675
|
+
evaluate(descriptor, item, index).then(value => {
|
|
676
|
+
if (revisions.get(root) === revision && marker.isConnected) updateListCondition(marker, descriptor.kind, value, item, index)
|
|
677
|
+
}).catch(error => console.error(error))
|
|
678
|
+
}
|
|
679
|
+
}
|
|
679
680
|
}
|
|
680
681
|
|
|
681
682
|
function updateListExpressions(list) {
|