@kudzujs/core 0.16.29 → 0.16.30

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.29](https://github.com/kudzujs/kudzu/releases/tag/v0.16.29).** Write reusable components, native event handlers, and pure collection expressions; Kudzu specializes supported source into static content and direct browser updates, excluding unused style and conditional capabilities.
17
+ **Current release: [0.16.30](https://github.com/kudzujs/kudzu/releases/tag/v0.16.30).** Write reusable components, native event handlers, and pure collection expressions; Kudzu specializes supported source into static content and direct browser updates, retaining only required binding targets and capabilities.
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) }
@@ -168,7 +168,7 @@ function updateCondition(condition) {
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)
@@ -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)
@@ -92,6 +94,9 @@ export function planRouteCapabilities(records, { navigationRouteCount = 0 } = {}
92
94
  dependencyStateSeeds: routeEntries.filter(route => route.hasStateSeed && route.usesDependencyRuntime).length
93
95
  }
94
96
  bindings.count = routeEntries.filter(route => route.hasBindings).length
97
+ const bindingTargets = new Set(plans.flatMap(plan => plan.bindings.map(binding => binding.target)))
98
+ bindings.properties = propertyBindings.filter(target => bindingTargets.has(target))
99
+ bindings.attributes = [...bindingTargets].some(target => target !== "text" && !propertyBindings.includes(target))
95
100
  lists.count = routeEntries.filter(route => route.hasLists).length
96
101
  lists.styleCount = routeEntries.filter(route => route.hasListStyles).length
97
102
  lists.generalRowHooks ||= lists.rowRefs || lists.complexRowState
@@ -120,7 +125,9 @@ export function assertCapabilityIR(capabilityIR, records, options = {}) {
120
125
  if (!sections.every(name => isRecord(capabilityIR[name]))) throw new Error("Invalid CapabilityIR v1 structure")
121
126
  if (!["behaviors", "regularBehaviors", "regularStateSeeds", "dependencyStateSeeds"].every(name => isCount(capabilityIR.routes[name]))) throw new Error("Invalid CapabilityIR v1 route counts")
122
127
  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")
128
+ if (!isCount(capabilityIR.bindings.count) || !["text", "style", "conditions", "svgConditions", "attributes"].every(name => typeof capabilityIR.bindings[name] === "boolean")) throw new Error("Invalid CapabilityIR v1 bindings")
129
+ 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")
130
+ if (capabilityIR.bindings.style !== capabilityIR.bindings.properties.includes("style")) throw new Error("CapabilityIR binding style flag does not match its properties")
124
131
  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
132
  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
133
  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,6 +45,13 @@ 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),
44
56
  "globalThis.__KUDZU_SVG_CONDITIONS__": String(capabilityIR.bindings.svgConditions),
45
57
  "globalThis.__KUDZU_CAPTURE_STATE__": String(capabilityIR.captures.nestedState)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kudzujs/core",
3
- "version": "0.16.29",
3
+ "version": "0.16.30",
4
4
  "description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
5
5
  "type": "module",
6
6
  "license": "MIT",