@kudzujs/core 0.6.25 → 0.6.26

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
@@ -422,7 +422,16 @@ function ItemCard({ item, onSelect }: {
422
422
 
423
423
  The nested collection must be `parent.<field>`. Its child row may be intrinsic or a same-file or relative-imported component that specializes to one intrinsic root. Child handlers receive the latest child item, and one level of child-local `&&` or ternary JSX conditions patches bounded DOM without remounting the child row. A second child list, third nesting level, computed collection, parent-item capture from the child row, conditions inside child conditions, child effects, child component tags below the specialized row root, and child row-local state remain unsupported.
424
424
 
425
- In the matched 100-parent/1,000-child component benchmark, a selected child text and condition update measured 1.2 ms, child reverse 0.4 ms, parent reverse 4.6 ms, and parent removal 0.6 ms. Svelte measured 2.2/0.8/5.4/1.1 ms, Vue 4.3/2.3/5.0/2.0 ms, React 8.9/4.2/7.4/4.1 ms, and hand-written Astro/native 0.5/0.3/3.1/0.1 ms. Kudzu shipped 6.6 KB initial JavaScript gzip and complete initial HTML. Sharing inert child-condition branches and descriptors through each live child-list prototype reduced HTML from 676,086 B to 417,086 B and total deploy output from 693,806 B to 435,326 B raw; the sum of gzip-compressed files is 23,473 B. The CSR targets omit initial rows, so their artifact sizes are not architecture-equivalent.
425
+ #### Nested list output
426
+
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
+
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.
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.
426
435
 
427
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>`.
428
437
 
@@ -801,7 +801,8 @@ async function renderList(node, namespace, selectValue) {
801
801
  if (!node.ownerField || ownerTemplate) renderContext.lists.push(descriptor)
802
802
  renderContext.hasBehaviors = true
803
803
  renderContext.hasLists = true
804
- return `<template data-k-list='${escapeJsonAttribute(descriptor)}'>${template}</template>${current}<template data-k-list-end="${id}"></template>`
804
+ const prototype = node.ownerField && !ownerTemplate ? "" : template
805
+ return `<template data-k-list='${escapeJsonAttribute(descriptor)}'>${prototype}</template>${current}<template data-k-list-end="${id}"></template>`
805
806
  } finally {
806
807
  renderContext.listRoot = previousListRoot
807
808
  renderContext.listRowRoot = previousListRowRoot
@@ -11,6 +11,7 @@ const ownedLists = __KUDZU_NESTED_LISTS__ ? new WeakMap() : undefined
11
11
  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
+ const childPrototypes = __KUDZU_NESTED_LISTS__ ? new WeakMap() : undefined
14
15
 
15
16
  function commitLists(id) {
16
17
  const lists = listTargets.get(id)
@@ -37,8 +38,9 @@ function mountLists(root) {
37
38
  const descriptor = JSON.parse(start.dataset.kList)
38
39
  const end = findEnd(start, descriptor.id)
39
40
  const roots = listRoots(start, end)
41
+ const nested = __KUDZU_NESTED_LISTS__ ? mountNestedPrototype(start, descriptor, roots) : undefined
42
+ const templateRoot = __KUDZU_NESTED_LISTS__ ? nested.templateRoot : start.content.firstElementChild
40
43
  if (__KUDZU_LIST_ROW_STATES__ && descriptor.rowStates) for (let index = 0; index < roots.length; index++) initializeRowStates(descriptor, descriptor.keys[index])
41
- const templateRoot = start.content.firstElementChild
42
44
  const parts = listItemPartPlan(templateRoot, descriptor.nested)
43
45
  for (const root of roots) {
44
46
  if (__KUDZU_LIST_CONDITIONS__ && descriptor.conditions) {
@@ -51,6 +53,7 @@ function mountLists(root) {
51
53
  const list = {
52
54
  start,
53
55
  descriptor,
56
+ ...(__KUDZU_NESTED_LISTS__ ? { templateRoot, ...(nested.childPrototype ? { childPrototype: nested.childPrototype } : {}) } : {}),
54
57
  parts,
55
58
  seedFields: __KUDZU_LIST_SEEDS__ && descriptor.seed && Object.keys(descriptor.seed),
56
59
  roots: new Map(roots.map((node, index) => [keyToken(descriptor.keys[index]), node])),
@@ -58,7 +61,7 @@ function mountLists(root) {
58
61
  items: undefined,
59
62
  container: roots[0]?.parentNode,
60
63
  boundary: end,
61
- ...(__KUDZU_NESTED_LISTS__ && descriptor.ownerField ? { owner: listOwner(start) } : {})
64
+ ...(__KUDZU_NESTED_LISTS__ && descriptor.ownerField ? { owner: nested.owner } : {})
62
65
  }
63
66
  if (__KUDZU_NESTED_LISTS__ && descriptor.ownerField) {
64
67
  if (!list.owner) throw new Error("Nested keyed list has no parent row")
@@ -150,10 +153,11 @@ function updateList(list) {
150
153
  for (const { item, key, token, value } of entries) {
151
154
  let node = list.roots.get(token)
152
155
  if (!node) {
153
- node = list.start.content.firstElementChild?.cloneNode(true)
156
+ node = (__KUDZU_NESTED_LISTS__ ? list.templateRoot : list.start.content.firstElementChild)?.cloneNode(true)
154
157
  if (node?.dataset.kListRoot !== list.descriptor.id) node = undefined
155
158
  if (!node) throw new Error("Keyed list template has no root element")
156
159
  node.removeAttribute("data-k-list-root")
160
+ if (__KUDZU_NESTED_LISTS__ && list.childPrototype) childPrototypes.set(node, list.childPrototype)
157
161
  if (__KUDZU_LIST_ROW_STATES__ && list.descriptor.rowStates) initializeRowStates(list.descriptor, key, node)
158
162
  mapListItemParts(list.parts, node, list.descriptor.nested)
159
163
  fillListItem(node, item, list.descriptor.nested)
@@ -323,10 +327,11 @@ function updateReducerList(list, items) {
323
327
  }
324
328
 
325
329
  function addListRoot(list, { item, key, token, value }) {
326
- let node = list.start.content.firstElementChild?.cloneNode(true)
330
+ let node = (__KUDZU_NESTED_LISTS__ ? list.templateRoot : list.start.content.firstElementChild)?.cloneNode(true)
327
331
  if (node?.dataset.kListRoot !== list.descriptor.id) node = undefined
328
332
  if (!node) throw new Error("Keyed list template has no root element")
329
333
  node.removeAttribute("data-k-list-root")
334
+ if (__KUDZU_NESTED_LISTS__ && list.childPrototype) childPrototypes.set(node, list.childPrototype)
330
335
  if (__KUDZU_LIST_ROW_STATES__ && list.descriptor.rowStates) initializeRowStates(list.descriptor, key, node)
331
336
  mapListItemParts(list.parts, node, list.descriptor.nested)
332
337
  fillListItem(node, item, list.descriptor.nested)
@@ -537,6 +542,31 @@ function listOwner(start) {
537
542
  return owner
538
543
  }
539
544
 
545
+ function mountNestedPrototype(start, descriptor, roots) {
546
+ const owner = descriptor.ownerField ? listOwner(start) : undefined
547
+ const prototypeStart = owner ? childPrototypes.get(owner) : undefined
548
+ const templateRoot = prototypeStart?.content.firstElementChild ?? start.content.firstElementChild
549
+ if (!templateRoot) throw new Error("Nested keyed list has no shared row prototype")
550
+ const childPrototype = descriptor.child ? findChildPrototype(templateRoot, descriptor.child.field) : undefined
551
+ if (descriptor.child && !childPrototype) throw new Error("Keyed list template has no nested row prototype")
552
+ if (childPrototype) for (const root of roots) childPrototypes.set(root, childPrototype)
553
+ if (prototypeStart && start !== prototypeStart) start.content.replaceChildren()
554
+ return { owner, childPrototype, templateRoot }
555
+ }
556
+
557
+ function findChildPrototype(root, field) {
558
+ for (const element of root.children) {
559
+ if (element.matches("template[data-k-list]")) {
560
+ if (JSON.parse(element.dataset.kList).ownerField === field) return element
561
+ const nested = findChildPrototype(element.content, field)
562
+ if (nested) return nested
563
+ } else {
564
+ const nested = findChildPrototype(element, field)
565
+ if (nested) return nested
566
+ }
567
+ }
568
+ }
569
+
540
570
  function ownedElements(root) {
541
571
  const elements = []
542
572
  const visit = node => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kudzujs/core",
3
- "version": "0.6.25",
3
+ "version": "0.6.26",
4
4
  "description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
5
5
  "type": "module",
6
6
  "license": "MIT",