@kudzujs/core 0.6.26 → 0.6.27

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
@@ -426,12 +426,12 @@ The nested collection must be `parent.<field>`. Its child row may be intrinsic o
426
426
 
427
427
  Initial child rows remain complete HTML. Kudzu stores one child row prototype, including inert condition branches and descriptors, and reuses it across parent rows.
428
428
 
429
- - HTML: 676,086 B before sharing, 369,586 B after sharing.
430
- - Total deploy output: 693,806 B before sharing, 388,611 B after sharing.
431
- - Compressed-file sum: 23,920 B. The final prototype-sharing step saves 46,715 B raw over `v0.6.25` while adding 447 B gzip because the removed HTML compressed well and prototype lookup adds nested runtime code.
432
- - Initial JavaScript: 6.8 KB gzip. Routes without nested lists compile out prototype lookup.
429
+ - HTML: 676,086 B before sharing, 339,586 B after sharing.
430
+ - Total deploy output: 693,806 B before sharing, 359,035 B after sharing.
431
+ - Compressed-file sum: 24,067 B. Sharing text and attribute patch metadata saves 29,576 B raw over `v0.6.26` while adding 147 B gzip because the removed metadata compressed well.
432
+ - Initial JavaScript: 6.9 KB gzip. Routes without nested lists compile out prototype and marker lookup.
433
433
 
434
- In the matched 100-parent/1,000-child fixture, Kudzu measured 1.1/0.4/4.7/0.7 ms for child update and condition change, child reverse, parent reverse, and parent removal. Hand-written Astro/native measured 0.5/0.3/3.3/0.2 ms, Svelte 2.4/0.9/5.6/1.0 ms, Vue 4.0/2.3/5.1/2.2 ms, and React 8.1/4.1/6.7/3.6 ms. Kudzu and Astro emit initial rows while the CSR targets do not, so artifact sizes are not architecture-equivalent.
434
+ In the matched 100-parent/1,000-child fixture, Kudzu measured 1.4/0.5/5.4/0.7 ms for child update and condition change, child reverse, parent reverse, and parent removal. Hand-written Astro/native measured 0.5/0.3/3.6/0.2 ms, Svelte 2.2/0.9/5.4/0.9 ms, Vue 4.0/2.1/4.6/1.8 ms, and React 8.5/3.9/6.8/3.8 ms. Kudzu and Astro emit initial rows while the CSR targets do not, so artifact sizes are not architecture-equivalent.
435
435
 
436
436
  Each item must be an ordinary plain object with a unique string or finite-number key; nested data may contain only JSON-safe arrays, ordinary plain objects, and primitive values. Null-prototype objects are rejected to preserve JSON round-trip parity. The current syntax requires a local-state `.map`, one identifier callback parameter, one intrinsic JSX root or top-level local or relative-imported row component, and `key={item.<field>}`. State-backed list wrappers use one destructured props parameter, an intrinsic return root, no effects, and a direct local-state prop. Same-file wrappers must be unexported and state-backed at every call; relative default, named/aliased, and direct named re-export wrappers are specialized per qualifying call. Row components accept destructured projected props, top-level single-`const` calculations and inline effects before one intrinsic return. Effect dependencies inside a direct outer row may be empty, direct primitive Kudzu state identifiers, or direct `item.<field>` properties whose selected values remain JSON-safe primitives. Whole-item, computed, nested, derived, `__proto__`, `prototype`, and `constructor` dependencies are rejected. A list alias may only be rendered once and cannot be read by other JavaScript. Derived expressions must be pure and synchronous: item reads, literals, operators, templates, approved read-only string/array methods, deterministic `Math` methods, and `String`/`Number`/`Boolean` conversion are supported. Component state, imported helpers used inside calculations, browser globals, Promise values, mutation, arbitrary calls, and prototype-sensitive properties are rejected. Package, namespace, and star-export list wrappers, package or namespace row imports, same-file exported rows, reusable aliases, prop spreads/defaults/rest, children, conditions nested inside item conditions, component tags below a specialized row root, refs, and `dangerouslySetInnerHTML` remain unsupported. Keyed rows must be placed inside an explicit `<tbody>`, `<thead>`, or `<tfoot>`.
437
437
 
@@ -546,7 +546,9 @@ async function renderNode(node, namespace, selectValue = noSelectValue) {
546
546
  if (node?.[listMarker]) return renderList(node, namespace, selectValue)
547
547
  if (node?.[listFieldMarker]) {
548
548
  if (renderContext.listTemplate || renderContext.listInitialMarkers || renderContext.listConditionalBranch) renderContext.listFields?.add(node.field)
549
- const marker = renderContext.listTemplate || renderContext.listInitialMarkers || renderContext.listConditionalBranch ? ` data-k-list-text="${escapeAttribute(node.field)}"` : ""
549
+ const marker = renderContext.listTemplate || renderContext.listInitialMarkers || renderContext.listConditionalBranch
550
+ ? sharedInitialListMarker() ? " data-k-list-text" : ` data-k-list-text="${escapeAttribute(node.field)}"`
551
+ : ""
550
552
  return `<template${marker}></template>${escapeHtml(node.value ?? "")}<template data-k-list-text-end></template>`
551
553
  }
552
554
  if (node?.[listExpressionMarker]) {
@@ -727,12 +729,12 @@ async function renderNode(node, namespace, selectValue = noSelectValue) {
727
729
  }
728
730
 
729
731
  if (attributeBindings.length) attributes += ` data-k-bind-attrs='${escapeJsonAttribute(attributeBindings)}'`
730
- if ((renderContext.listTemplate || renderContext.listInitialMarkers || renderContext.listConditionalBranch) && listAttributes.length) attributes += ` data-k-list-attrs='${escapeJsonAttribute(listAttributes)}'`
732
+ if ((renderContext.listTemplate || renderContext.listInitialMarkers || renderContext.listConditionalBranch) && listAttributes.length) attributes += sharedInitialListMarker() ? " data-k-list-attrs" : ` data-k-list-attrs='${escapeJsonAttribute(listAttributes)}'`
731
733
  if ((renderContext.listTemplate || renderContext.listInitialMarkers || renderContext.listConditionalBranch) && listExpressionAttributes.length) attributes += ` data-k-list-expression-attrs='${escapeJsonAttribute(listExpressionAttributes)}'`
732
734
  if ((renderContext.listTemplate || renderContext.listInitialMarkers || renderContext.listConditionalBranch) && listEvents.length) attributes += ` data-k-list-events='${escapeJsonAttribute(listEvents)}'`
733
735
  if ((renderContext.listTemplate || renderContext.listInitialMarkers || renderContext.listConditionalBranch) && directListText) {
734
736
  renderContext.listFields?.add(directListText.field)
735
- attributes += ` data-k-list-text="${escapeAttribute(directListText.field)}"`
737
+ attributes += sharedInitialListMarker() ? " data-k-list-text" : ` data-k-list-text="${escapeAttribute(directListText.field)}"`
736
738
  }
737
739
 
738
740
  if (tag === "option" && selectValue !== noSelectValue && String(optionValue(props)) === (selectValue == null ? "" : String(selectValue))) attributes += " selected"
@@ -746,6 +748,11 @@ async function renderNode(node, namespace, selectValue = noSelectValue) {
746
748
  return `<${tag}${attributes}>${children}</${tag}>`
747
749
  }
748
750
 
751
+ function sharedInitialListMarker() {
752
+ const owner = renderContext.listRoot ?? renderContext.listRowRoot
753
+ return renderContext.listInitialMarkers && !renderContext.listTemplate && !renderContext.listConditionalBranch && owner?.descriptor.ownerField
754
+ }
755
+
749
756
  async function renderList(node, namespace, selectValue) {
750
757
  if (namespace) throw new Error(`Reactive keyed lists are not supported inside ${namespace}`)
751
758
  const ownerRoot = node.ownerField ? renderContext.listRoot ?? renderContext.listRowRoot : undefined
@@ -12,6 +12,7 @@ const conditionOwners = __KUDZU_LIST_CONDITIONS__ ? new WeakMap() : undefined
12
12
  const conditionTemplates = __KUDZU_LIST_CONDITIONS__ ? new WeakMap() : undefined
13
13
  const itemPartsSelector = `[data-k-list-text]${__KUDZU_LIST_ATTRIBUTES__ ? ",[data-k-list-attrs]" : ""}${__KUDZU_LIST_EVENTS__ ? ",[data-k-list-events]" : ""}${__KUDZU_LIST_EXPRESSIONS__ ? ",[data-k-list-expression]" : ""}${__KUDZU_LIST_EXPRESSION_ATTRIBUTES__ ? ",[data-k-list-expression-attrs]" : ""}${__KUDZU_LIST_CONDITIONS__ ? ",[data-k-list-condition]" : ""}${__KUDZU_LIST_EFFECTS__ ? ",[data-k-effects]" : ""}`
14
14
  const childPrototypes = __KUDZU_NESTED_LISTS__ ? new WeakMap() : undefined
15
+ const itemPartPlans = __KUDZU_NESTED_LISTS__ ? new WeakMap() : undefined
15
16
 
16
17
  function commitLists(id) {
17
18
  const lists = listTargets.get(id)
@@ -44,6 +45,7 @@ function mountLists(root) {
44
45
  const parts = listItemPartPlan(templateRoot, descriptor.nested)
45
46
  for (const root of roots) {
46
47
  if (__KUDZU_LIST_CONDITIONS__ && descriptor.conditions) {
48
+ if (__KUDZU_NESTED_LISTS__ && descriptor.ownerField) itemPartPlans.set(root, parts)
47
49
  const initialParts = listItemParts(root, descriptor.nested)
48
50
  mapListConditionTemplates(parts.conditions, initialParts.conditions)
49
51
  } else mapListItemParts(parts, root, descriptor.nested)
@@ -423,7 +425,7 @@ function listItemParts(root, nested = false) {
423
425
  parts = { directTexts: [], texts: [], attributes: [], events: [], expressions: [], expressionAttributes: [], conditions: [], effects: [] }
424
426
  for (const node of nested ? ownedElements(root).filter(node => node.matches(itemPartsSelector)) : matching(root, itemPartsSelector)) {
425
427
  if (node.hasAttribute("data-k-list-text")) (node.tagName === "TEMPLATE" ? parts.texts : parts.directTexts).push([node, node.dataset.kListText])
426
- if (__KUDZU_LIST_ATTRIBUTES__ && node.hasAttribute("data-k-list-attrs")) parts.attributes.push([node, JSON.parse(node.dataset.kListAttrs)])
428
+ if (__KUDZU_LIST_ATTRIBUTES__ && node.hasAttribute("data-k-list-attrs")) parts.attributes.push([node, node.dataset.kListAttrs ? JSON.parse(node.dataset.kListAttrs) : undefined])
427
429
  if (__KUDZU_LIST_EVENTS__ && node.hasAttribute("data-k-list-events")) parts.events.push([node, node.dataset.kListEvents])
428
430
  if (__KUDZU_LIST_EXPRESSIONS__ && node.hasAttribute("data-k-list-expression")) parts.expressions.push([node, JSON.parse(node.dataset.kListExpression)])
429
431
  if (__KUDZU_LIST_EXPRESSION_ATTRIBUTES__ && node.hasAttribute("data-k-list-expression-attrs")) parts.expressionAttributes.push([node, JSON.parse(node.dataset.kListExpressionAttrs)])
@@ -434,10 +436,28 @@ function listItemParts(root, nested = false) {
434
436
  }
435
437
  if (__KUDZU_LIST_EFFECTS__ && node.hasAttribute("data-k-effects")) parts.effects.push(node)
436
438
  }
439
+ if (__KUDZU_NESTED_LISTS__) hydrateListItemParts(itemPartPlans.get(root), parts)
437
440
  itemParts.set(root, parts)
438
441
  return parts
439
442
  }
440
443
 
444
+ function hydrateListItemParts(planned, initial) {
445
+ if (!planned) return
446
+ hydrateListItemPartValues(planned.directTexts, initial.directTexts, value => value === "")
447
+ if (__KUDZU_LIST_TEXT_RANGES__) hydrateListItemPartValues(planned.texts, initial.texts, value => value === "")
448
+ if (__KUDZU_LIST_ATTRIBUTES__) hydrateListItemPartValues(planned.attributes, initial.attributes, value => value === undefined)
449
+ }
450
+
451
+ function hydrateListItemPartValues(planned, initial, missing) {
452
+ let index = 0
453
+ for (const entry of initial) {
454
+ if (!missing(entry[1])) continue
455
+ if (!planned[index]) throw new Error("Keyed list item markers do not match its template")
456
+ entry[1] = planned[index++][1]
457
+ }
458
+ if (index !== planned.length) throw new Error("Keyed list item markers do not match its template")
459
+ }
460
+
441
461
  function listItemPartPlan(template, nested = false) {
442
462
  const source = nested ? ownedElements(template) : [template, ...template.querySelectorAll("*")]
443
463
  const indexes = new Map(source.map((node, index) => [node, index]))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kudzujs/core",
3
- "version": "0.6.26",
3
+ "version": "0.6.27",
4
4
  "description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
5
5
  "type": "module",
6
6
  "license": "MIT",