@kudzujs/core 0.8.7 → 0.8.8
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 +26 -0
- package/framework/build.mjs +30 -4
- 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.8 - Conditional keyed map roots.** Expression-bodied keyed maps may return `condition && <Row />` or `condition ? <Row /> : null`; Kudzu lowers the condition to its existing filter path so omitted rows clean up and re-entry starts fresh. Read the [release notes](./RELEASES.md#088---conditional-keyed-map-roots) or open the [release page](https://kudzujs.cloud/releases/0.8.8).
|
|
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,31 @@
|
|
|
1
1
|
# Kudzu Releases
|
|
2
2
|
|
|
3
|
+
## 0.8.8 - Conditional keyed map roots
|
|
4
|
+
|
|
5
|
+
Kudzu 0.8.8 compiles ordinary expression-bodied conditional keyed maps through the existing pure collection selector path.
|
|
6
|
+
|
|
7
|
+
### New in 0.8.8
|
|
8
|
+
|
|
9
|
+
- One-parameter keyed maps may return `condition && <Row />` or `condition ? <Row /> : null`.
|
|
10
|
+
- Top-level conditions may combine the current item with direct primitive parent state; nested maps support item-only conditions.
|
|
11
|
+
- Omitted rows own no DOM or hooks. True-to-false transitions release row state, effects, and refs; re-entry creates fresh ownership.
|
|
12
|
+
- Retained siblings preserve keyed DOM identity through condition changes, insertion, and reorder.
|
|
13
|
+
- Same-file and relative row components remain compiler-specialized before the existing list runtime receives the normalized filter.
|
|
14
|
+
- Imported build-known item-only conditions still fold to complete zero-JavaScript HTML.
|
|
15
|
+
- The React Notes migration restores its ordinary `notes.map(note => activeId === note.id && <Editor />)` source shape.
|
|
16
|
+
- Map indexes, alternate JSX fallbacks, block-bodied conditional maps, arbitrary captures, and impure predicates remain diagnosed.
|
|
17
|
+
- The complete suite passes 134/134 tests.
|
|
18
|
+
|
|
19
|
+
### Boundary
|
|
20
|
+
|
|
21
|
+
Conditional map callbacks must be synchronous expression arrows with exactly one item parameter. Indexes are rejected because implicit filtering changes their meaning. The feature adds no runtime capability, VDOM, hydration, or component rerenderer.
|
|
22
|
+
|
|
23
|
+
### Upgrade
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @kudzujs/core@^0.8.8
|
|
27
|
+
```
|
|
28
|
+
|
|
3
29
|
## 0.8.7 - Reactive keyed row selection
|
|
4
30
|
|
|
5
31
|
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.
|
package/framework/build.mjs
CHANGED
|
@@ -4125,7 +4125,7 @@ function keyedListParts(expression, setters, declarations, fail, aliases = new S
|
|
|
4125
4125
|
const value = unwrapExpression(expression)
|
|
4126
4126
|
const directFrom = isArrayFromCall(value) && value.arguments.length === 2 && containsJsx(value.arguments[1])
|
|
4127
4127
|
if (!directFrom && (!ts.isCallExpression(value) || value.arguments.length !== 1 || !ts.isPropertyAccessExpression(value.expression) || value.expression.name.text !== "map")) return undefined
|
|
4128
|
-
|
|
4128
|
+
let collection = renderedCollectionSource(directFrom ? value.arguments[0] : value.expression.expression, setters, declarations, fail, aliases, importedCollections, new Set(setters.values()), importedCollectionTransforms, factory, context, calculatedCollection, staticCollection)
|
|
4129
4129
|
if (!collection?.state && !collection?.calculation) return undefined
|
|
4130
4130
|
if (directFrom) collection.selector.push(["from", undefined])
|
|
4131
4131
|
let callback = directFrom ? value.arguments[1] : value.arguments[0]
|
|
@@ -4144,6 +4144,8 @@ function keyedListParts(expression, setters, declarations, fail, aliases = new S
|
|
|
4144
4144
|
ts.setParentRecursive(callback, false)
|
|
4145
4145
|
callback.parent = value
|
|
4146
4146
|
}
|
|
4147
|
+
const conditional = conditionalKeyedMapRoot(callback, root, parameters, collection, fail, new Set(setters.values()), factory, value)
|
|
4148
|
+
if (conditional) ({ callback, root, collection } = conditional)
|
|
4147
4149
|
if (!ts.isJsxElement(root) && !ts.isJsxSelfClosingElement(root)) fail(callback.body, "Keyed list map callback must return one JSX element")
|
|
4148
4150
|
const attributes = ts.isJsxElement(root) ? root.openingElement.attributes : root.attributes
|
|
4149
4151
|
const key = attributes.properties.find(attribute => ts.isJsxAttribute(attribute) && ts.isIdentifier(attribute.name) && attribute.name.text === "key")
|
|
@@ -4157,11 +4159,13 @@ function keyedListParts(expression, setters, declarations, fail, aliases = new S
|
|
|
4157
4159
|
function nestedKeyedListParts(expression, parentItem, fail) {
|
|
4158
4160
|
const value = unwrapExpression(expression)
|
|
4159
4161
|
if (!ts.isCallExpression(value) || value.arguments.length !== 1 || !ts.isPropertyAccessExpression(value.expression) || value.expression.name.text !== "map") return undefined
|
|
4160
|
-
|
|
4162
|
+
let collection = renderedCollectionSource(value.expression.expression, new Map(), undefined, fail, new Set())
|
|
4161
4163
|
if (!collection?.ownerField || collection.parentItem !== parentItem) return undefined
|
|
4162
|
-
|
|
4164
|
+
let callback = value.arguments[0]
|
|
4163
4165
|
const parameters = collectionParameters(callback, "Nested keyed list map", fail)
|
|
4164
|
-
|
|
4166
|
+
let root = unwrapExpression(callback.body)
|
|
4167
|
+
const conditional = conditionalKeyedMapRoot(callback, root, parameters, collection, fail, new Set(), ts.factory, value)
|
|
4168
|
+
if (conditional) ({ callback, root, collection } = conditional)
|
|
4165
4169
|
if (!ts.isJsxElement(root) && !ts.isJsxSelfClosingElement(root)) fail(callback.body, "Nested keyed list map callback must return one JSX element")
|
|
4166
4170
|
const attributes = ts.isJsxElement(root) ? root.openingElement.attributes : root.attributes
|
|
4167
4171
|
const key = attributes.properties.find(attribute => ts.isJsxAttribute(attribute) && ts.isIdentifier(attribute.name) && attribute.name.text === "key")
|
|
@@ -4172,6 +4176,28 @@ function nestedKeyedListParts(expression, parentItem, fail) {
|
|
|
4172
4176
|
return { ...collection, callback, root, item: parameters.item, index: parameters.index, indexed: Boolean(parameters.index), keyField: positional ? null : keyField }
|
|
4173
4177
|
}
|
|
4174
4178
|
|
|
4179
|
+
function conditionalKeyedMapRoot(callback, root, parameters, collection, fail, stateNames, factory, parent) {
|
|
4180
|
+
let condition
|
|
4181
|
+
let rendered
|
|
4182
|
+
if (ts.isBinaryExpression(root) && root.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken && (ts.isJsxElement(unwrapExpression(root.right)) || ts.isJsxSelfClosingElement(unwrapExpression(root.right)))) {
|
|
4183
|
+
condition = root.left
|
|
4184
|
+
rendered = unwrapExpression(root.right)
|
|
4185
|
+
} else if (ts.isConditionalExpression(root) && (ts.isJsxElement(unwrapExpression(root.whenTrue)) || ts.isJsxSelfClosingElement(unwrapExpression(root.whenTrue)))) {
|
|
4186
|
+
if (unwrapExpression(root.whenFalse).kind !== ts.SyntaxKind.NullKeyword) fail(root.whenFalse, "Conditional keyed map callbacks require condition ? <Element> : null")
|
|
4187
|
+
condition = root.condition
|
|
4188
|
+
rendered = unwrapExpression(root.whenTrue)
|
|
4189
|
+
} else {
|
|
4190
|
+
return undefined
|
|
4191
|
+
}
|
|
4192
|
+
if (parameters.index) fail(callback.parameters[1], "Conditional keyed map callbacks cannot use a map index because filtering changes index semantics; use an explicit filter(...).map((item, index) => ...) when a filtered index is intended")
|
|
4193
|
+
const selectorStates = new Set(collection.selectorStates)
|
|
4194
|
+
const selector = collectionExpression(condition, parameters, fail, stateNames, selectorStates)
|
|
4195
|
+
const normalized = factory.updateArrowFunction(callback, callback.modifiers, callback.typeParameters, callback.parameters, callback.type, callback.equalsGreaterThanToken, rendered)
|
|
4196
|
+
ts.setParentRecursive(normalized, false)
|
|
4197
|
+
normalized.parent = parent
|
|
4198
|
+
return { callback: normalized, root: rendered, collection: { ...collection, selector: [...collection.selector, ["filter", selector]], selectorStates } }
|
|
4199
|
+
}
|
|
4200
|
+
|
|
4175
4201
|
function renderedCollectionSource(expression, setters, declarations, fail, aliases, importedCollections = new Set(), stateNames = new Set(), importedCollectionTransforms = new Map(), factory = ts.factory, context, calculatedCollection, staticCollection) {
|
|
4176
4202
|
const value = unwrapExpression(expression)
|
|
4177
4203
|
if (ts.isIdentifier(value)) {
|