@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 +1 -1
- package/RELEASES.md +46 -0
- package/framework/build.mjs +47 -16
- package/framework/core.d.ts +1 -1
- package/framework/core.mjs +10 -4
- package/framework/list-runtime.js +38 -20
- package/package.json +1 -1
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.
|
|
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.
|
package/framework/build.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
6345
|
+
ts.factory.createBlock([ts.factory.createReturnStatement(transformed.transformed[0])], true)
|
|
6319
6346
|
)
|
|
6320
|
-
|
|
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) {
|
package/framework/core.d.ts
CHANGED
|
@@ -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
|
package/framework/core.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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 (
|
|
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
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
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
|
})
|