@kudzujs/core 0.8.5 → 0.8.7

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
@@ -10,7 +10,7 @@ Kudzu compiles ordinary React-shaped TypeScript and TSX into complete static HTM
10
10
 
11
11
  > Experimental `0.8.x`: the compiler API and supported TSX surface may change.
12
12
 
13
- **Latest release: 0.8.5 - Owned timer actions.** A directly returned relative custom-hook callback may own one private timeout ref with latest-only replacement and effect cleanup, without adding a timer runtime. Read the [release notes](./RELEASES.md#085---owned-timer-actions) or open the [release page](https://kudzujs.cloud/releases/0.8.5).
13
+ **Latest release: 0.8.7 - Reactive keyed row selection.** Flat keyed rows may combine their current item or index with direct primitive parent state in pure text and attribute expressions, enabling selected classes and ARIA state without remounting rows. Read the [release notes](./RELEASES.md#087---reactive-keyed-row-selection) or open the [release page](https://kudzujs.cloud/releases/0.8.7).
14
14
 
15
15
  - [Documentation](https://kudzujs.cloud/docs)
16
16
  - [Installation guide](https://kudzujs.cloud/docs#install)
package/RELEASES.md CHANGED
@@ -1,5 +1,51 @@
1
1
  # Kudzu Releases
2
2
 
3
+ ## 0.8.7 - Reactive keyed row selection
4
+
5
+ Kudzu 0.8.7 lets a flat keyed row combine its current item or index with direct primitive parent state in pure text and attribute expressions.
6
+
7
+ ### New in 0.8.7
8
+
9
+ - Selected-row classes, `aria-current`, `aria-selected`, and similar pure expressions update directly after parent-state commits.
10
+ - Retained rows reevaluate only their expression text and attributes; list structure, handlers, and DOM identity remain untouched.
11
+ - Initial static HTML evaluates the same expression with build-time state values.
12
+ - Calculated SVG coverage verifies focus, Space, click, insertion, reorder, latest handlers, selected classes, ARIA state, and retained node identity.
13
+ - A React Notes migration restores its ordinary `activeId === note.id` selected-row source without imperative DOM code.
14
+ - Object/array parent state, arbitrary captures, nested-row parent state, structural conditions, mutation, and arbitrary calls remain diagnosed.
15
+ - The complete suite passes 134/134 tests.
16
+
17
+ ### Boundary
18
+
19
+ This support is limited to pure flat keyed-row text and attribute expressions over the current item/index plus direct primitive parent state. It adds no component rerenderer, VDOM, hydration, or general capture runtime.
20
+
21
+ ### Upgrade
22
+
23
+ ```bash
24
+ npm install @kudzujs/core@^0.8.7
25
+ ```
26
+
27
+ ## 0.8.6 - Responsive list reversals
28
+
29
+ Kudzu 0.8.6 fixes stale keyed-list bookkeeping after reverse and one-item removal fast paths.
30
+
31
+ ### Fixed in 0.8.6
32
+
33
+ - Reverse fast paths now store the latest logical item order after moving existing keyed DOM nodes.
34
+ - One-item removal fast paths update the same baseline before subsequent list actions.
35
+ - Repeated reverse clicks apply on the next microtask instead of requiring another delayed interaction.
36
+ - Existing keyed rows retain DOM identity and uncontrolled input values through every reorder.
37
+ - Chrome coverage verifies `3,2,1 → 1,2,3 → 3,2,1` across rapid actions, and the complete suite passes 134/134 tests.
38
+
39
+ ### Boundary
40
+
41
+ This is a bookkeeping correction in the existing keyed-list runtime. It adds no list capability, VDOM, hydration, or component runtime.
42
+
43
+ ### Upgrade
44
+
45
+ ```bash
46
+ npm install @kudzujs/core@^0.8.6
47
+ ```
48
+
3
49
  ## 0.8.5 - Owned timer actions
4
50
 
5
51
  Kudzu 0.8.5 compiles one proven private timeout ref pattern from a returned relative custom-hook action into existing state and effect ownership.
@@ -303,6 +303,7 @@ export async function build({ quiet = false, minify = true } = {}) {
303
303
  .replace("function fillListItem(root, item, nested = false, index = 0)", "function fillListItem(root, item, nested = false)")
304
304
  .replace("fillListParts(root, parts, item, revision, index, previous)", "fillListParts(root, parts, item, revision, previous)")
305
305
  .replace("function fillListParts(root, parts, item, revision, index = 0, previous)", "function fillListParts(root, parts, item, revision, previous)")
306
+ .replace("fillListExpressions(root, parts, item, revision, index)", "fillListExpressions(root, parts, item, revision)")
306
307
  .replaceAll('value?.type === "list-item" ? serializeItem(item) : value?.type === "list-index" ? index : value', 'value?.type === "list-item" ? serializeItem(item) : value')
307
308
  .replaceAll("evaluate(descriptor, item, index)", "evaluate(descriptor, item)")
308
309
  .replaceAll("evaluate({ module, handler }, item, index)", "evaluate({ module, handler }, item)")
@@ -311,6 +312,7 @@ export async function build({ quiet = false, minify = true } = {}) {
311
312
  .replace("fillListParts(marker, listItemParts(fragment), item, revision, index)", "fillListParts(marker, listItemParts(fragment), item, revision)")
312
313
  .replace("function evaluate(descriptor, item, index)", "function evaluate(descriptor, item)")
313
314
  .replace("exports[descriptor.handler](item, index)", "exports[descriptor.handler](item)")
315
+ .replace("exports[descriptor.handler](item, index, {", "exports[descriptor.handler](item, undefined, {")
314
316
  if (!hasCollectionSelectors) listRuntime = listRuntime.replaceAll(" && !list.descriptor.selector", "")
315
317
  if (!hasListIndexes) listRuntime = listRuntime
316
318
  .replaceAll(" && !list.descriptor.indexed", "")
@@ -3899,11 +3901,11 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
3899
3901
  }
3900
3902
 
3901
3903
  if (ts.isJsxExpression(node) && node.expression && listValues.has(node.expression)) {
3902
- return factory.updateJsxExpression(node, compileListValue(node.expression, listValues.get(node.expression), factory, listExpressions, handlerUrl))
3904
+ return factory.updateJsxExpression(node, compileListValue(node.expression, listValues.get(node.expression), factory, context, listExpressions, handlerUrl))
3903
3905
  }
3904
3906
 
3905
3907
  if (ts.isJsxAttribute(node) && node.initializer && ts.isJsxExpression(node.initializer) && node.initializer.expression && listValues.has(node.initializer.expression)) {
3906
- return factory.updateJsxAttribute(node, node.name, factory.createJsxExpression(undefined, compileListValue(node.initializer.expression, listValues.get(node.initializer.expression), factory, listExpressions, handlerUrl)))
3908
+ return factory.updateJsxAttribute(node, node.name, factory.createJsxExpression(undefined, compileListValue(node.initializer.expression, listValues.get(node.initializer.expression), factory, context, listExpressions, handlerUrl)))
3907
3909
  }
3908
3910
 
3909
3911
  if (ts.isJsxExpression(node) && node.initializer === undefined && node.expression && (ts.isJsxElement(node.parent) || ts.isJsxFragment(node.parent))) {
@@ -4509,9 +4511,12 @@ function validateKeyedList(parts, sourceFile, listValues, listEventItems, listCo
4509
4511
  return
4510
4512
  }
4511
4513
  if (referencesIdentifier(expression, item) || parts.index && referencesIdentifier(expression, parts.index)) {
4512
- validateListExpression(expression, item, node, fail, parts.index)
4514
+ const states = referencedStateNames(expression, setters)
4515
+ for (const rowState of rowStates) states.delete(rowState.state)
4516
+ if (parts.nested && states.size) fail(node, "Nested keyed list item expressions cannot read parent state")
4517
+ validateListExpression(expression, item, node, fail, parts.index, states)
4513
4518
  if (ts.isJsxAttribute(node.parent) && ["ref", "dangerouslysetinnerhtml"].includes(node.parent.name.text.toLowerCase())) fail(node, `Keyed list item ${node.parent.name.text} is not supported`)
4514
- listValues.set(node.expression, { item, index: parts.index })
4519
+ listValues.set(node.expression, { item, index: parts.index, states })
4515
4520
  return
4516
4521
  }
4517
4522
  }
@@ -4983,7 +4988,7 @@ const assignmentOperators = new Set([
4983
4988
  ts.SyntaxKind.QuestionQuestionEqualsToken
4984
4989
  ])
4985
4990
 
4986
- function validateListExpression(expression, item, source, fail, index) {
4991
+ function validateListExpression(expression, item, source, fail, index, states = new Set()) {
4987
4992
  const visit = node => {
4988
4993
  if (ts.isTypeNode(node)) return
4989
4994
  if (ts.isElementAccessExpression(node) && referencesIdentifier(node.expression, item)) {
@@ -5014,7 +5019,7 @@ function validateListExpression(expression, item, source, fail, index) {
5014
5019
  fail(source, "Derived keyed list item expressions cannot call arbitrary functions")
5015
5020
  }
5016
5021
  }
5017
- if (ts.isIdentifier(node) && isReferenceIdentifier(node) && !isJsxSyntaxIdentifier(node) && node.text !== item && node.text !== index && !pureListGlobals.has(node.text)) {
5022
+ if (ts.isIdentifier(node) && isReferenceIdentifier(node) && !isJsxSyntaxIdentifier(node) && node.text !== item && node.text !== index && !states.has(node.text) && !pureListGlobals.has(node.text)) {
5018
5023
  fail(source, `Derived keyed list item expression identifier "${node.text}" is not allowed`)
5019
5024
  }
5020
5025
  ts.forEachChild(node, visit)
@@ -5032,10 +5037,12 @@ function containsJsx(root) {
5032
5037
  return found
5033
5038
  }
5034
5039
 
5035
- function compileListExpression(read, expression, item, factory, listExpressions, handlerUrl, index) {
5040
+ function compileListExpression(read, expression, item, factory, listExpressions, handlerUrl, index, states = new Set()) {
5036
5041
  const exportName = `listExpression${listExpressions.length}`
5037
- listExpressions.push({ exportName, expression, item, index })
5038
- return factory.createCallExpression(factory.createIdentifier("__kListExpression"), undefined, [read, factory.createStringLiteral(handlerUrl), factory.createStringLiteral(exportName)])
5042
+ listExpressions.push({ exportName, expression, item, index, states })
5043
+ const arguments_ = [read, factory.createStringLiteral(handlerUrl), factory.createStringLiteral(exportName)]
5044
+ if (states.size) arguments_.push(factory.createArrayLiteralExpression([...states].map(name => factory.createArrayLiteralExpression([factory.createStringLiteral(name), factory.createIdentifier(name)]))))
5045
+ return factory.createCallExpression(factory.createIdentifier("__kListExpression"), undefined, arguments_)
5039
5046
  }
5040
5047
 
5041
5048
  function compileListConditional(entry, factory, listExpressions, handlerUrl) {
@@ -5048,11 +5055,17 @@ function compileListConditional(entry, factory, listExpressions, handlerUrl) {
5048
5055
  ])
5049
5056
  }
5050
5057
 
5051
- function compileListValue(expression, entry, factory, listExpressions, handlerUrl) {
5052
- const read = factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), expression)
5058
+ function compileListValue(expression, entry, factory, context, listExpressions, handlerUrl) {
5059
+ const rewrite = node => {
5060
+ if (ts.isShorthandPropertyAssignment(node) && entry.states?.has(node.name.text)) return factory.createPropertyAssignment(node.name, factory.createPropertyAccessExpression(node.name, "value"))
5061
+ if (ts.isIdentifier(node) && entry.states?.has(node.text) && isReferenceIdentifier(node)) return factory.createPropertyAccessExpression(node, "value")
5062
+ return ts.visitEachChild(node, rewrite, context)
5063
+ }
5064
+ const initial = entry.states?.size ? ts.visitNode(expression, rewrite) : expression
5065
+ const read = factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), initial)
5053
5066
  return entry.field
5054
5067
  ? factory.createCallExpression(factory.createIdentifier("__kListField"), undefined, [read, factory.createStringLiteral(entry.field)])
5055
- : compileListExpression(read, expression, entry.item, factory, listExpressions, handlerUrl, entry.index)
5068
+ : compileListExpression(read, expression, entry.item, factory, listExpressions, handlerUrl, entry.index, entry.states)
5056
5069
  }
5057
5070
 
5058
5071
  function directProperty(expression, objectName) {
@@ -6307,17 +6320,35 @@ function printReactiveBinding({ exportName, expression, captures, states }) {
6307
6320
  }
6308
6321
  }
6309
6322
 
6310
- function printListExpression({ exportName, expression, item, index }) {
6323
+ function printListExpression({ exportName, expression, item, index, states = new Set() }) {
6324
+ const factory = ts.factory
6325
+ const transformer = context => root => {
6326
+ const visitor = node => {
6327
+ if (ts.isShorthandPropertyAssignment(node) && states.has(node.name.text)) {
6328
+ return factory.createPropertyAssignment(node.name, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "get"), undefined, [factory.createStringLiteral(node.name.text)]))
6329
+ }
6330
+ if (ts.isIdentifier(node) && states.has(node.text) && isReferenceIdentifier(node) && !isShadowedByParameter(node, expression)) {
6331
+ return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "get"), undefined, [factory.createStringLiteral(node.text)])
6332
+ }
6333
+ return ts.visitEachChild(node, visitor, context)
6334
+ }
6335
+ return ts.visitNode(root, visitor)
6336
+ }
6337
+ const transformed = ts.transform(expression, [transformer])
6311
6338
  const declaration = ts.factory.createFunctionDeclaration(
6312
6339
  [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
6313
6340
  undefined,
6314
6341
  exportName,
6315
6342
  undefined,
6316
- [ts.factory.createParameterDeclaration(undefined, undefined, item), ts.factory.createParameterDeclaration(undefined, undefined, index ?? "__kIndex")],
6343
+ [ts.factory.createParameterDeclaration(undefined, undefined, item), ts.factory.createParameterDeclaration(undefined, undefined, index ?? "__kIndex"), ts.factory.createParameterDeclaration(undefined, undefined, "__k")],
6317
6344
  undefined,
6318
- ts.factory.createBlock([ts.factory.createReturnStatement(expression)], true)
6345
+ ts.factory.createBlock([ts.factory.createReturnStatement(transformed.transformed[0])], true)
6319
6346
  )
6320
- return ts.createPrinter().printNode(ts.EmitHint.Unspecified, declaration, expression.getSourceFile())
6347
+ try {
6348
+ return ts.createPrinter().printNode(ts.EmitHint.Unspecified, declaration, expression.getSourceFile())
6349
+ } finally {
6350
+ transformed.dispose()
6351
+ }
6321
6352
  }
6322
6353
 
6323
6354
  function scopeRead(factory, name) {
@@ -38,7 +38,7 @@ export function bindingValue(value: unknown): unknown
38
38
  export function conditional(kind: "and" | "ternary", value: unknown, truthy: () => unknown, falsy: () => unknown, module: string, handler: string, states: Array<[string, unknown]>, scope: Array<[string, unknown]>): unknown
39
39
  export function list(items: unknown, keyField: string | null, render: (item: unknown, index: number) => unknown, ownerField?: string, selector?: unknown[], indexed?: boolean, selectorStates?: Array<[string, unknown]>, staticCollection?: boolean): unknown
40
40
  export function listField(read: () => unknown, field: string): unknown
41
- export function listExpression(read: () => unknown, module: string, handler: string): unknown
41
+ export function listExpression(read: () => unknown, module: string, handler: string, states?: Array<[string, unknown]>): unknown
42
42
  export function listItem(): unknown
43
43
  export function listIndex(): unknown
44
44
  export function listConditional(kind: "and" | "ternary", read: () => unknown, truthy: () => unknown, falsy: () => unknown, module: string, handler: string): unknown
@@ -324,11 +324,17 @@ export function listField(read, field) {
324
324
  return { [listFieldMarker]: true, field, value: renderContext?.listTemplate ? undefined : read() }
325
325
  }
326
326
 
327
- export function listExpression(read, module, handler) {
327
+ export function listExpression(read, module, handler, states = []) {
328
328
  renderContext?.handlerModules.add(module)
329
+ const stateMap = Object.fromEntries(states.map(([name, state]) => {
330
+ if (!state?.[signalMarker] || !validEffectDependency(state.value)) throw new Error(`Derived keyed list item expression state ${JSON.stringify(name)} must be primitive Kudzu state`)
331
+ return [name, state.id]
332
+ }))
333
+ const owner = renderContext?.listRoot ?? renderContext?.listRowRoot
334
+ if (owner && states.length) owner.descriptor.expressionStates = [...new Set([...(owner.descriptor.expressionStates ?? []), ...Object.values(stateMap)])]
329
335
  const value = renderContext?.listTemplate ? undefined : read()
330
336
  if (value && typeof value.then === "function") throw new Error("Derived keyed list item expressions must return synchronous values")
331
- return { [listExpressionMarker]: true, module, handler, value }
337
+ return { [listExpressionMarker]: true, module, handler, states: stateMap, value }
332
338
  }
333
339
 
334
340
  export function listItem() {
@@ -659,7 +665,7 @@ async function renderNode(node, namespace, selectValue = noSelectValue) {
659
665
  return `<template${marker}></template>${escapeHtml(node.value ?? "")}<template data-k-list-text-end></template>`
660
666
  }
661
667
  if (node?.[listExpressionMarker]) {
662
- const descriptor = { module: node.module, handler: node.handler }
668
+ const descriptor = { module: node.module, handler: node.handler, ...(Object.keys(node.states).length ? { states: node.states } : {}) }
663
669
  const marker = renderContext.listTemplate || renderContext.listInitialMarkers || renderContext.listConditionalBranch ? ` data-k-list-expression='${escapeJsonAttribute(descriptor)}'` : ""
664
670
  return `<template${marker}></template>${escapeHtml(node.value ?? "")}<template data-k-list-expression-end></template>`
665
671
  }
@@ -812,7 +818,7 @@ async function renderNode(node, namespace, selectValue = noSelectValue) {
812
818
  }
813
819
  if (value?.[listExpressionMarker]) {
814
820
  attributes += renderAttribute(name, value.value)
815
- listExpressionAttributes.push([name, value.module, value.handler])
821
+ listExpressionAttributes.push([name, value.module, value.handler, ...(Object.keys(value.states).length ? [value.states] : [])])
816
822
  if (name === "style") renderContext.hasListStyles = true
817
823
  continue
818
824
  }
@@ -25,7 +25,10 @@ function commitLists(id) {
25
25
  if (!lists) return
26
26
  for (const list of lists) {
27
27
  if (!list.start.isConnected) unregisterList(list.start)
28
- else updateList(list)
28
+ else {
29
+ if (list.updateStates.has(id)) updateList(list)
30
+ if (list.expressionStates.has(id)) updateListExpressions(list)
31
+ }
29
32
  }
30
33
  }
31
34
 
@@ -76,6 +79,8 @@ function mountLists(root) {
76
79
  ...(__KUDZU_LIST_STABLE_FAST_PATHS__ ? { orderedRoots: roots } : {}),
77
80
  values: new Map(),
78
81
  items: undefined,
82
+ updateStates: new Set(),
83
+ expressionStates: new Set(descriptor.expressionStates ?? []),
79
84
  container: roots[0]?.parentNode,
80
85
  boundary: end,
81
86
  ...(__KUDZU_NESTED_LISTS__ && descriptor.ownerField ? { owner: nested.owner } : {})
@@ -93,20 +98,19 @@ function mountLists(root) {
93
98
  loadListEvaluator(descriptor.source).then(evaluator => {
94
99
  if (listLoads.get(start) !== load || !start.isConnected) return
95
100
  list.sourceEvaluator = evaluator
96
- const states = [...new Set([...evaluator.stateIds, ...(__KUDZU_COLLECTION_SELECTORS__ ? Object.values(descriptor.selectorStates ?? {}) : [])])]
101
+ const updateStates = [...new Set([...evaluator.stateIds, ...(__KUDZU_COLLECTION_SELECTORS__ ? Object.values(descriptor.selectorStates ?? {}) : [])])]
102
+ const states = [...new Set([...updateStates, ...list.expressionStates])]
103
+ list.updateStates = new Set(updateStates)
97
104
  for (const state of states) register(listTargets, state, list)
98
105
  listRegistrations.set(start, { states, list })
99
106
  updateList(list)
100
107
  }).catch(error => console.error(error))
101
108
  } else {
102
- if (__KUDZU_COLLECTION_SELECTORS__ && descriptor.selectorStates) {
103
- const states = [...new Set([descriptor.state, ...Object.values(descriptor.selectorStates)])]
104
- for (const state of states) register(listTargets, state, list)
105
- listRegistrations.set(start, { states, list })
106
- } else {
107
- register(listTargets, descriptor.state, list)
108
- listRegistrations.set(start, { state: descriptor.state, list })
109
- }
109
+ const updateStates = [descriptor.state, ...(__KUDZU_COLLECTION_SELECTORS__ ? Object.values(descriptor.selectorStates ?? {}) : [])]
110
+ const states = [...new Set([...updateStates, ...list.expressionStates])]
111
+ list.updateStates = new Set(updateStates)
112
+ for (const state of states) register(listTargets, state, list)
113
+ listRegistrations.set(start, { states, list })
110
114
  }
111
115
  if (!descriptor.source) updateList(list)
112
116
  }
@@ -125,7 +129,7 @@ function unregisterList(start) {
125
129
  if (lists?.get(registration.list.descriptor.id) === registration.list) lists.delete(registration.list.descriptor.id)
126
130
  if (!lists?.size) ownedLists.delete(registration.owner)
127
131
  } else {
128
- if ((__KUDZU_COLLECTION_SELECTORS__ || registration.list.descriptor.source) && registration.states) {
132
+ if (registration.states) {
129
133
  for (const state of registration.states) {
130
134
  const lists = listTargets.get(state)
131
135
  lists?.delete(registration.list)
@@ -198,6 +202,7 @@ function updateList(list) {
198
202
  list.roots = new Map(nextTokens.map(token => [token, list.roots.get(token)]))
199
203
  if (__KUDZU_LIST_STABLE_FAST_PATHS__) list.orderedRoots = nextTokens.map(token => list.roots.get(token))
200
204
  list.container ??= parent
205
+ list.items = items
201
206
  return
202
207
  }
203
208
  if (nextTokens.length === currentTokens.length - 1) {
@@ -207,6 +212,7 @@ function updateList(list) {
207
212
  removeListRoot(list, removed)
208
213
  list.roots = new Map(nextTokens.map(token => [token, list.roots.get(token)]))
209
214
  if (__KUDZU_LIST_STABLE_FAST_PATHS__) list.orderedRoots = nextTokens.map(token => list.roots.get(token))
215
+ list.items = items
210
216
  return
211
217
  }
212
218
  }
@@ -645,6 +651,17 @@ function fillListParts(root, parts, item, revision, index = 0, previous) {
645
651
  }
646
652
  }
647
653
  }
654
+ fillListExpressions(root, parts, item, revision, index)
655
+ if (__KUDZU_LIST_CONDITIONS__) {
656
+ for (const [marker, descriptor] of parts.conditions) {
657
+ evaluate(descriptor, item, index).then(value => {
658
+ if (revisions.get(root) === revision && marker.isConnected) updateListCondition(marker, descriptor.kind, value, item, index)
659
+ }).catch(error => console.error(error))
660
+ }
661
+ }
662
+ }
663
+
664
+ function fillListExpressions(root, parts, item, revision, index) {
648
665
  if (__KUDZU_LIST_EXPRESSIONS__) {
649
666
  for (const [marker, descriptor] of parts.expressions) {
650
667
  evaluate(descriptor, item, index).then(value => {
@@ -654,19 +671,20 @@ function fillListParts(root, parts, item, revision, index = 0, previous) {
654
671
  }
655
672
  if (__KUDZU_LIST_EXPRESSION_ATTRIBUTES__) {
656
673
  for (const [node, attributes] of parts.expressionAttributes) {
657
- for (const [target, module, handler] of attributes) {
658
- evaluate({ module, handler }, item, index).then(value => {
674
+ for (const [target, module, handler, states] of attributes) {
675
+ evaluate({ module, handler, states }, item, index).then(value => {
659
676
  if (revisions.get(root) === revision && node.isConnected) patchBinding(node, target, value)
660
677
  }).catch(error => console.error(error))
661
678
  }
662
679
  }
663
680
  }
664
- if (__KUDZU_LIST_CONDITIONS__) {
665
- for (const [marker, descriptor] of parts.conditions) {
666
- evaluate(descriptor, item, index).then(value => {
667
- if (revisions.get(root) === revision && marker.isConnected) updateListCondition(marker, descriptor.kind, value, item, index)
668
- }).catch(error => console.error(error))
669
- }
681
+ }
682
+
683
+ function updateListExpressions(list) {
684
+ for (const root of list.roots.values()) {
685
+ const revision = (revisions.get(root) ?? 0) + 1
686
+ revisions.set(root, revision)
687
+ fillListExpressions(root, listItemParts(root, list.descriptor.nested), listItems.get(root), revision, __KUDZU_LIST_INDEXES__ ? listIndexes.get(root) ?? 0 : 0)
670
688
  }
671
689
  }
672
690
 
@@ -997,7 +1015,7 @@ function evaluate(descriptor, item, index) {
997
1015
  imports.set(descriptor.module, module)
998
1016
  }
999
1017
  return module.then(exports => {
1000
- const value = exports[descriptor.handler](item, index)
1018
+ const value = exports[descriptor.handler](item, index, { get: name => browserState.get(descriptor.states?.[name]) })
1001
1019
  if (value && typeof value.then === "function") throw new Error("Derived keyed list item expressions must return synchronous values")
1002
1020
  return value
1003
1021
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kudzujs/core",
3
- "version": "0.8.5",
3
+ "version": "0.8.7",
4
4
  "description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
5
5
  "type": "module",
6
6
  "license": "MIT",