@kudzujs/core 0.16.29 → 0.16.32
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
CHANGED
|
@@ -14,7 +14,7 @@ Kudzu compiles ordinary React-shaped TypeScript and TSX into complete static HTM
|
|
|
14
14
|
|
|
15
15
|
> Experimental `0.16.x`: the compiler API and supported TSX surface may change.
|
|
16
16
|
|
|
17
|
-
**Current release: [0.16.
|
|
17
|
+
**Current release: [0.16.32](https://github.com/kudzujs/kudzu/releases/tag/v0.16.32).** Write reusable components, native event handlers, and pure collection expressions; Kudzu specializes supported source into static content and direct browser updates. Runtime families whose branches own no state omit conditional state lifecycle code, removing 334 raw JavaScript bytes from the search fixture while preserving stateful remounts. AI-cost and 1.0 gates remain blocked.
|
|
18
18
|
|
|
19
19
|
- [Documentation](https://kudzujs.cloud/docs)
|
|
20
20
|
- [Installation guide](https://kudzujs.cloud/docs#install)
|
|
@@ -16,25 +16,25 @@ const bindingSelector = [...bindingTypes.map(target => `[data-k-bind-${target}]`
|
|
|
16
16
|
export function patchBinding(node, target, value) {
|
|
17
17
|
if (globalThis.__KUDZU_TEXT_BINDINGS__ && target === "text") {
|
|
18
18
|
patchText(node, value)
|
|
19
|
-
} else if (target === "disabled") {
|
|
19
|
+
} else if (globalThis.__KUDZU_DISABLED_BINDINGS__ !== false && target === "disabled") {
|
|
20
20
|
node.toggleAttribute("disabled", Boolean(value))
|
|
21
|
-
} else if (target === "checked") {
|
|
21
|
+
} else if (globalThis.__KUDZU_CHECKED_BINDINGS__ !== false && target === "checked") {
|
|
22
22
|
node.checked = Boolean(value)
|
|
23
|
-
} else if (target === "value") {
|
|
23
|
+
} else if (globalThis.__KUDZU_VALUE_BINDINGS__ !== false && target === "value") {
|
|
24
24
|
const next = value == null ? "" : String(value)
|
|
25
25
|
if (node.value !== next) node.value = next
|
|
26
26
|
} else if (target === "style") {
|
|
27
27
|
const style = serializeStyle(value)
|
|
28
28
|
if (style) node.setAttribute("style", style)
|
|
29
29
|
else node.removeAttribute("style")
|
|
30
|
-
} else if (target === "class" && (value == null || value === false)) {
|
|
30
|
+
} else if (globalThis.__KUDZU_CLASS_BINDINGS__ !== false && target === "class" && (value == null || value === false)) {
|
|
31
31
|
node.removeAttribute("class")
|
|
32
|
-
} else if (target === "class") {
|
|
32
|
+
} else if (globalThis.__KUDZU_CLASS_BINDINGS__ !== false && target === "class") {
|
|
33
33
|
if (node.namespaceURI === "http://www.w3.org/1999/xhtml") node.className = String(value)
|
|
34
34
|
else node.setAttribute("class", String(value))
|
|
35
|
-
} else if (value == null || (value === false && !isStringBooleanAttribute(target))) {
|
|
35
|
+
} else if (globalThis.__KUDZU_ATTRIBUTE_BINDINGS__ !== false && (value == null || (value === false && !isStringBooleanAttribute(target)))) {
|
|
36
36
|
node.removeAttribute(target)
|
|
37
|
-
} else {
|
|
37
|
+
} else if (globalThis.__KUDZU_ATTRIBUTE_BINDINGS__ !== false) {
|
|
38
38
|
node.setAttribute(target, value === true && !isStringBooleanAttribute(target) ? "" : String(value))
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -66,15 +66,15 @@ registerStateReleaseHook(releaseBindings)
|
|
|
66
66
|
if (typeof document !== "undefined") mountDom(document)
|
|
67
67
|
|
|
68
68
|
function mountBindings(root) {
|
|
69
|
-
for (const node of matching(root, bindingSelector)) {
|
|
69
|
+
if (globalThis.__KUDZU_ELEMENT_BINDINGS__ !== false) for (const node of matching(root, bindingSelector)) {
|
|
70
70
|
if (mountedBindings.has(node)) continue
|
|
71
71
|
mountedBindings.add(node)
|
|
72
72
|
const registrations = []
|
|
73
73
|
bindingRegistrations.set(node, registrations)
|
|
74
|
-
const descriptors = bindingTypes.flatMap(target => node.hasAttribute(`data-k-bind-${target}`)
|
|
74
|
+
const descriptors = globalThis.__KUDZU_PROPERTY_BINDINGS__ !== false ? bindingTypes.flatMap(target => node.hasAttribute(`data-k-bind-${target}`)
|
|
75
75
|
? [[target, JSON.parse(node.dataset[`kBind${capitalize(target)}`])]]
|
|
76
|
-
: [])
|
|
77
|
-
if (node.dataset.kBindAttrs) descriptors.push(...JSON.parse(node.dataset.kBindAttrs).map(({ target, ...descriptor }) => [target, descriptor]))
|
|
76
|
+
: []) : []
|
|
77
|
+
if (globalThis.__KUDZU_ATTRIBUTE_BINDINGS__ !== false && node.dataset.kBindAttrs) descriptors.push(...JSON.parse(node.dataset.kBindAttrs).map(({ target, ...descriptor }) => [target, descriptor]))
|
|
78
78
|
for (const [target, descriptor] of descriptors) {
|
|
79
79
|
if (descriptor.state) {
|
|
80
80
|
const binding = { node, target, read: Object.hasOwn(descriptor, "truthy") ? () => browserState.get(descriptor.state) ? descriptor.truthy : descriptor.falsy : () => browserState.get(descriptor.state) }
|
|
@@ -131,8 +131,8 @@ function mountConditions(root) {
|
|
|
131
131
|
const truthy = globalThis.__KUDZU_SVG_CONDITIONS__ && descriptor.svg ? start.dataset.kSvgTrue : start.content.querySelector("template[data-k-true]")
|
|
132
132
|
const falsy = globalThis.__KUDZU_SVG_CONDITIONS__ && descriptor.svg ? start.dataset.kSvgFalse : start.content.querySelector("template[data-k-false]")
|
|
133
133
|
if (!end || !truthy || !falsy) continue
|
|
134
|
-
const condition = { start, end, truthy, falsy, svg: globalThis.__KUDZU_SVG_CONDITIONS__ && descriptor.svg, kind: descriptor.kind, current: conditionKey(descriptor.kind, descriptor.initial), mount: descriptor.mount, owned: descriptor.owned }
|
|
135
|
-
mountConditionStates(condition, Boolean(descriptor.initial), false)
|
|
134
|
+
const condition = { start, end, truthy, falsy, svg: globalThis.__KUDZU_SVG_CONDITIONS__ && descriptor.svg, kind: descriptor.kind, current: conditionKey(descriptor.kind, descriptor.initial), mount: descriptor.mount, owned: globalThis.__KUDZU_CONDITION_STATE__ !== false && descriptor.owned }
|
|
135
|
+
if (globalThis.__KUDZU_CONDITION_STATE__ !== false) mountConditionStates(condition, Boolean(descriptor.initial), false)
|
|
136
136
|
const mount = evaluator => {
|
|
137
137
|
if (!start.isConnected) return
|
|
138
138
|
condition.read = evaluator.read
|
|
@@ -155,7 +155,7 @@ function updateCondition(condition) {
|
|
|
155
155
|
if (next === condition.current) return
|
|
156
156
|
const previous = condition.current === "true"
|
|
157
157
|
removeConditionRange(condition.start, condition.end, condition.mount)
|
|
158
|
-
unmountConditionStates(condition, previous)
|
|
158
|
+
if (globalThis.__KUDZU_CONDITION_STATE__ !== false) unmountConditionStates(condition, previous)
|
|
159
159
|
const truthy = Boolean(value)
|
|
160
160
|
const falseText = condition.kind === "and" && !truthy ? renderFalsy(value) : ""
|
|
161
161
|
const fragment = falseText
|
|
@@ -164,11 +164,11 @@ function updateCondition(condition) {
|
|
|
164
164
|
? svgFragment(condition.start, truthy ? condition.truthy : condition.falsy)
|
|
165
165
|
: (truthy ? condition.truthy : condition.falsy).content.cloneNode(true)
|
|
166
166
|
const nodes = condition.mount ? [...fragment.childNodes] : undefined
|
|
167
|
-
mountConditionStates(condition, truthy, true)
|
|
167
|
+
if (globalThis.__KUDZU_CONDITION_STATE__ !== false) mountConditionStates(condition, truthy, true)
|
|
168
168
|
condition.end.parentNode.insertBefore(fragment, condition.end)
|
|
169
169
|
condition.current = next
|
|
170
170
|
if (condition.mount) for (const node of nodes) mountDom(node)
|
|
171
|
-
const select = condition.start.closest("select[data-k-bind-value]")
|
|
171
|
+
const select = globalThis.__KUDZU_VALUE_BINDINGS__ !== false && condition.start.closest("select[data-k-bind-value]")
|
|
172
172
|
if (select) {
|
|
173
173
|
for (const binding of new Set((bindingRegistrations.get(select) ?? []).map(([, entry]) => entry))) {
|
|
174
174
|
patchBinding(binding.node, binding.target, binding.read())
|
|
@@ -177,7 +177,7 @@ function updateCondition(condition) {
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
function unmountBindings(root) {
|
|
180
|
-
for (const node of matching(root, bindingSelector)) {
|
|
180
|
+
if (globalThis.__KUDZU_ELEMENT_BINDINGS__ !== false) for (const node of matching(root, bindingSelector)) {
|
|
181
181
|
for (const [id, binding] of bindingRegistrations.get(node) ?? []) bindingTargets.get(id)?.delete(binding)
|
|
182
182
|
bindingRegistrations.delete(node)
|
|
183
183
|
mountedBindings.delete(node)
|
|
@@ -195,7 +195,7 @@ function unmountConditions(root) {
|
|
|
195
195
|
for (const start of matching(root, "template[data-k-if]")) {
|
|
196
196
|
const registration = conditionRegistrations.get(start)
|
|
197
197
|
for (const [id, condition] of registration?.registrations ?? []) conditionTargets.get(id)?.delete(condition)
|
|
198
|
-
if (registration?.condition) unmountConditionStates(registration.condition, registration.condition.current === "true")
|
|
198
|
+
if (globalThis.__KUDZU_CONDITION_STATE__ !== false && registration?.condition) unmountConditionStates(registration.condition, registration.condition.current === "true")
|
|
199
199
|
conditionRegistrations.delete(start)
|
|
200
200
|
mountedConditions.delete(start)
|
|
201
201
|
}
|
|
@@ -211,7 +211,7 @@ function releaseBindings(id) {
|
|
|
211
211
|
for (const condition of conditionTargets.get(id) ?? []) {
|
|
212
212
|
const registration = conditionRegistrations.get(condition.start)
|
|
213
213
|
for (const [conditionId, entry] of registration?.registrations ?? []) conditionTargets.get(conditionId)?.delete(entry)
|
|
214
|
-
unmountConditionStates(condition, condition.current === "true")
|
|
214
|
+
if (globalThis.__KUDZU_CONDITION_STATE__ !== false) unmountConditionStates(condition, condition.current === "true")
|
|
215
215
|
conditionRegistrations.delete(condition.start)
|
|
216
216
|
}
|
|
217
217
|
conditionTargets.delete(id)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { assertRouteBuildRecord } from "./route-build-record.mjs"
|
|
2
2
|
import { assertJsonSafe, assertRouteIR } from "./route-ir.mjs"
|
|
3
3
|
|
|
4
|
+
const propertyBindings = ["class", "disabled", "value", "checked", "style"]
|
|
5
|
+
|
|
4
6
|
export function usesRouteDependencyRuntime({ plan, navigable, hasBindings, hasLists }, validate = true) {
|
|
5
7
|
if (validate) assertRouteIR(plan)
|
|
6
8
|
const hasDependencies = plan.effects.some(effect => effect.dependencies?.length)
|
|
@@ -13,7 +15,7 @@ export function planRouteCapabilities(records, { navigationRouteCount = 0 } = {}
|
|
|
13
15
|
for (const plan of plans) assertRouteIR(plan, { concrete: true })
|
|
14
16
|
const commandEvents = new Set()
|
|
15
17
|
const nativeEvents = new Set()
|
|
16
|
-
const bindings = { count: 0, text: false, style: false, conditions: false, svgConditions: false }
|
|
18
|
+
const bindings = { count: 0, text: false, style: false, conditions: false, conditionState: false, svgConditions: false }
|
|
17
19
|
const lists = {
|
|
18
20
|
count: 0,
|
|
19
21
|
styleCount: 0,
|
|
@@ -52,6 +54,7 @@ export function planRouteCapabilities(records, { navigationRouteCount = 0 } = {}
|
|
|
52
54
|
bindings.text ||= plan.bindings.some(binding => binding.target === "text")
|
|
53
55
|
bindings.style ||= plan.bindings.some(binding => binding.target === "style")
|
|
54
56
|
bindings.conditions ||= plan.conditions.length > 0
|
|
57
|
+
bindings.conditionState ||= plan.conditions.some(condition => Object.values(condition.owned ?? {}).some(states => states.length > 0))
|
|
55
58
|
bindings.svgConditions ||= plan.conditions.some(condition => condition.svg)
|
|
56
59
|
effects.any ||= plan.effects.length > 0
|
|
57
60
|
effects.derivedDependencies ||= plan.effects.some(effect => effect.dependencyExpressions?.length)
|
|
@@ -92,6 +95,9 @@ export function planRouteCapabilities(records, { navigationRouteCount = 0 } = {}
|
|
|
92
95
|
dependencyStateSeeds: routeEntries.filter(route => route.hasStateSeed && route.usesDependencyRuntime).length
|
|
93
96
|
}
|
|
94
97
|
bindings.count = routeEntries.filter(route => route.hasBindings).length
|
|
98
|
+
const bindingTargets = new Set(plans.flatMap(plan => plan.bindings.map(binding => binding.target)))
|
|
99
|
+
bindings.properties = propertyBindings.filter(target => bindingTargets.has(target))
|
|
100
|
+
bindings.attributes = [...bindingTargets].some(target => target !== "text" && !propertyBindings.includes(target))
|
|
95
101
|
lists.count = routeEntries.filter(route => route.hasLists).length
|
|
96
102
|
lists.styleCount = routeEntries.filter(route => route.hasListStyles).length
|
|
97
103
|
lists.generalRowHooks ||= lists.rowRefs || lists.complexRowState
|
|
@@ -120,7 +126,10 @@ export function assertCapabilityIR(capabilityIR, records, options = {}) {
|
|
|
120
126
|
if (!sections.every(name => isRecord(capabilityIR[name]))) throw new Error("Invalid CapabilityIR v1 structure")
|
|
121
127
|
if (!["behaviors", "regularBehaviors", "regularStateSeeds", "dependencyStateSeeds"].every(name => isCount(capabilityIR.routes[name]))) throw new Error("Invalid CapabilityIR v1 route counts")
|
|
122
128
|
if (!["command", "native"].every(name => Array.isArray(capabilityIR.events[name]) && capabilityIR.events[name].every(event => typeof event === "string")) || typeof capabilityIR.events.hasNativeHandlers !== "boolean") throw new Error("Invalid CapabilityIR v1 events")
|
|
123
|
-
if (!isCount(capabilityIR.bindings.count) || !["text", "style", "conditions", "svgConditions"].every(name => typeof capabilityIR.bindings[name] === "boolean")) throw new Error("Invalid CapabilityIR v1 bindings")
|
|
129
|
+
if (!isCount(capabilityIR.bindings.count) || !["text", "style", "conditions", "conditionState", "svgConditions", "attributes"].every(name => typeof capabilityIR.bindings[name] === "boolean")) throw new Error("Invalid CapabilityIR v1 bindings")
|
|
130
|
+
if (capabilityIR.bindings.conditionState && !capabilityIR.bindings.conditions) throw new Error("CapabilityIR conditional state requires conditions")
|
|
131
|
+
if (!Array.isArray(capabilityIR.bindings.properties) || capabilityIR.bindings.properties.some(target => !propertyBindings.includes(target)) || new Set(capabilityIR.bindings.properties).size !== capabilityIR.bindings.properties.length) throw new Error("Invalid CapabilityIR v1 binding properties")
|
|
132
|
+
if (capabilityIR.bindings.style !== capabilityIR.bindings.properties.includes("style")) throw new Error("CapabilityIR binding style flag does not match its properties")
|
|
124
133
|
const listFlags = ["conditions", "svg", "deepConditions", "textRanges", "attributes", "events", "expressions", "expressionAttributes", "seeds", "effects", "rowHooks", "rowRefs", "complexRowState", "nested", "selectors", "calculated", "static", "indexes", "stableFastPaths", "generalRowHooks", "asyncParts", "mounts"]
|
|
125
134
|
if (!isCount(capabilityIR.lists.count) || !isCount(capabilityIR.lists.styleCount) || !listFlags.every(name => typeof capabilityIR.lists[name] === "boolean")) throw new Error("Invalid CapabilityIR v1 lists")
|
|
126
135
|
if (!["any", "derivedDependencies", "itemDependencies", "captures", "navigable", "navigableOwners"].every(name => typeof capabilityIR.effects[name] === "boolean") || !["nestedState", "setter"].every(name => typeof capabilityIR.captures[name] === "boolean") || !["shared", "dependency"].every(name => typeof capabilityIR.runtime[name] === "boolean")) throw new Error("Invalid CapabilityIR v1 effect, capture, or runtime flags")
|
|
@@ -27,8 +27,13 @@ export function generateEffectRuntime(source, capabilityIR) {
|
|
|
27
27
|
|
|
28
28
|
export function generateBindingRuntime(source, capabilityIR, navigable) {
|
|
29
29
|
assertCapabilityIR(capabilityIR)
|
|
30
|
+
const { properties, attributes } = capabilityIR.bindings
|
|
30
31
|
let runtime = replaceRequired(source, '"./shared-runtime.js"', '"./kudzu.js"', "shared runtime import", "binding-runtime.js")
|
|
31
32
|
runtime = replaceRequired(runtime, '"./serialization.js"', '"./kudzu-serialization.js"', "serialization import", "binding-runtime.js")
|
|
33
|
+
runtime = replaceRequired(runtime, /const bindingTypes = \[[^\n]+\]/, `const bindingTypes = ${JSON.stringify(properties)}`, "binding properties", "binding-runtime.js")
|
|
34
|
+
const selectors = [...properties.map(target => `[data-k-bind-${target}]`), ...(attributes ? ["[data-k-bind-attrs]"] : [])]
|
|
35
|
+
// The existing generic selector is smaller when all five property kinds are used.
|
|
36
|
+
if (properties.length !== 5 || !attributes) runtime = replaceRequired(runtime, /^const bindingSelector = .*$/m, `const bindingSelector = ${JSON.stringify(selectors.join(","))}`, "binding selectors", "binding-runtime.js")
|
|
32
37
|
runtime = capabilityIR.bindings.style
|
|
33
38
|
? replaceRequired(runtime, '"./style.js"', '"./kudzu-style.js"', "style import", "binding-runtime.js")
|
|
34
39
|
: replaceSequenceRequired(runtime, [
|
|
@@ -40,7 +45,15 @@ export function generateBindingRuntime(source, capabilityIR, navigable) {
|
|
|
40
45
|
source: runtime,
|
|
41
46
|
define: {
|
|
42
47
|
"globalThis.__KUDZU_TEXT_BINDINGS__": String(capabilityIR.bindings.text),
|
|
48
|
+
"globalThis.__KUDZU_ELEMENT_BINDINGS__": String(selectors.length > 0),
|
|
49
|
+
"globalThis.__KUDZU_PROPERTY_BINDINGS__": String(properties.length > 0),
|
|
50
|
+
"globalThis.__KUDZU_ATTRIBUTE_BINDINGS__": String(attributes),
|
|
51
|
+
"globalThis.__KUDZU_CLASS_BINDINGS__": String(properties.includes("class")),
|
|
52
|
+
"globalThis.__KUDZU_DISABLED_BINDINGS__": String(properties.includes("disabled")),
|
|
53
|
+
"globalThis.__KUDZU_CHECKED_BINDINGS__": String(properties.includes("checked")),
|
|
54
|
+
"globalThis.__KUDZU_VALUE_BINDINGS__": String(properties.includes("value")),
|
|
43
55
|
"globalThis.__KUDZU_CONDITIONS__": String(capabilityIR.bindings.conditions),
|
|
56
|
+
"globalThis.__KUDZU_CONDITION_STATE__": String(capabilityIR.bindings.conditionState),
|
|
44
57
|
"globalThis.__KUDZU_SVG_CONDITIONS__": String(capabilityIR.bindings.svgConditions),
|
|
45
58
|
"globalThis.__KUDZU_CAPTURE_STATE__": String(capabilityIR.captures.nestedState)
|
|
46
59
|
}
|