@kudzujs/core 0.6.26 → 0.6.28
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/GOAL_A.md +1 -1
- package/README.md +53 -37
- package/framework/README.md +7 -3
- package/framework/binding-runtime.js +4 -2
- package/framework/build.mjs +397 -155
- package/framework/collection-selector.js +63 -0
- package/framework/core.d.ts +2 -1
- package/framework/core.mjs +80 -41
- package/framework/list-runtime.js +342 -96
- package/framework/native-runtime.js +13 -2
- package/framework/shared-runtime.js +18 -5
- package/package.json +1 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export function selectCollection(anchor, selector = []) {
|
|
2
|
+
let values = anchor == null ? [] : anchor
|
|
3
|
+
for (const operation of selector) {
|
|
4
|
+
if (operation[0] === "from") values = Array.from(values, operation[1] ? (item, index) => evaluateCollectionExpression(operation[1], item, index) : undefined)
|
|
5
|
+
else {
|
|
6
|
+
if (!Array.isArray(values)) throw new Error("Rendered collection source must remain an array")
|
|
7
|
+
if (operation[0] === "filter") values = values.filter((item, index) => evaluateCollectionExpression(operation[1], item, index))
|
|
8
|
+
else if (operation[0] === "flatMap") values = values.flatMap(item => item?.[operation[1]] ?? [])
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
if (!Array.isArray(values)) throw new Error("Rendered collection source must remain an array")
|
|
12
|
+
return values
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function evaluateCollectionExpression(expression, item, index) {
|
|
16
|
+
const [kind, ...parts] = expression
|
|
17
|
+
if (kind === "value") return parts[0]
|
|
18
|
+
if (kind === "undefined") return undefined
|
|
19
|
+
if (kind === "item") return item
|
|
20
|
+
if (kind === "index") return index
|
|
21
|
+
if (kind === "get") {
|
|
22
|
+
const object = evaluateCollectionExpression(parts[0], item, index)
|
|
23
|
+
return object == null && parts[2] ? undefined : object[parts[1]]
|
|
24
|
+
}
|
|
25
|
+
if (kind === "unary") {
|
|
26
|
+
const value = evaluateCollectionExpression(parts[1], item, index)
|
|
27
|
+
if (parts[0] === "!") return !value
|
|
28
|
+
if (parts[0] === "+") return +value
|
|
29
|
+
if (parts[0] === "-") return -value
|
|
30
|
+
if (parts[0] === "typeof") return typeof value
|
|
31
|
+
}
|
|
32
|
+
if (kind === "binary") {
|
|
33
|
+
const left = evaluateCollectionExpression(parts[1], item, index)
|
|
34
|
+
if (parts[0] === "&&") return left && evaluateCollectionExpression(parts[2], item, index)
|
|
35
|
+
if (parts[0] === "||") return left || evaluateCollectionExpression(parts[2], item, index)
|
|
36
|
+
if (parts[0] === "??") return left ?? evaluateCollectionExpression(parts[2], item, index)
|
|
37
|
+
const right = evaluateCollectionExpression(parts[2], item, index)
|
|
38
|
+
if (parts[0] === "===") return left === right
|
|
39
|
+
if (parts[0] === "!==") return left !== right
|
|
40
|
+
if (parts[0] === "==") return left == right
|
|
41
|
+
if (parts[0] === "!=") return left != right
|
|
42
|
+
if (parts[0] === "<") return left < right
|
|
43
|
+
if (parts[0] === "<=") return left <= right
|
|
44
|
+
if (parts[0] === ">") return left > right
|
|
45
|
+
if (parts[0] === ">=") return left >= right
|
|
46
|
+
if (parts[0] === "+") return left + right
|
|
47
|
+
if (parts[0] === "-") return left - right
|
|
48
|
+
if (parts[0] === "*") return left * right
|
|
49
|
+
if (parts[0] === "/") return left / right
|
|
50
|
+
if (parts[0] === "%") return left % right
|
|
51
|
+
}
|
|
52
|
+
if (kind === "conditional") return evaluateCollectionExpression(parts[0], item, index) ? evaluateCollectionExpression(parts[1], item, index) : evaluateCollectionExpression(parts[2], item, index)
|
|
53
|
+
if (kind === "array") return parts.map(value => evaluateCollectionExpression(value, item, index))
|
|
54
|
+
if (kind === "object") return Object.fromEntries(parts.map(([key, value]) => [key, evaluateCollectionExpression(value, item, index)]))
|
|
55
|
+
if (kind === "template") return parts[0].map((text, offset) => text + (offset < parts[1].length ? evaluateCollectionExpression(parts[1][offset], item, index) : "")).join("")
|
|
56
|
+
if (kind === "call") {
|
|
57
|
+
const receiver = evaluateCollectionExpression(parts[0], item, index)
|
|
58
|
+
return receiver[parts[1]](...parts.slice(2).map(value => evaluateCollectionExpression(value, item, index)))
|
|
59
|
+
}
|
|
60
|
+
if (kind === "global") return globalThis[parts[0]](...parts.slice(1).map(value => evaluateCollectionExpression(value, item, index)))
|
|
61
|
+
if (kind === "math") return Math[parts[0]](...parts.slice(1).map(value => evaluateCollectionExpression(value, item, index)))
|
|
62
|
+
throw new Error(`Unsupported rendered collection expression: ${String(kind)}`)
|
|
63
|
+
}
|
package/framework/core.d.ts
CHANGED
|
@@ -27,10 +27,11 @@ export function nativeBehavior(module: string, handler: string, states: Array<[s
|
|
|
27
27
|
export function binding(value: unknown, module: string, handler: string, states: Array<[string, unknown]>, scope: Array<[string, unknown]>): unknown
|
|
28
28
|
export function bindingValue(value: unknown): unknown
|
|
29
29
|
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
|
|
30
|
-
export function list(items: unknown, keyField: string, render: (item: unknown) => unknown, ownerField?: string): unknown
|
|
30
|
+
export function list(items: unknown, keyField: string | null, render: (item: unknown, index: number) => unknown, ownerField?: string, selector?: unknown[], indexed?: boolean): unknown
|
|
31
31
|
export function listField(read: () => unknown, field: string): unknown
|
|
32
32
|
export function listExpression(read: () => unknown, module: string, handler: string): unknown
|
|
33
33
|
export function listItem(): unknown
|
|
34
|
+
export function listIndex(): unknown
|
|
34
35
|
export function listConditional(kind: "and" | "ternary", read: () => unknown, truthy: () => unknown, falsy: () => unknown, module: string, handler: string): unknown
|
|
35
36
|
|
|
36
37
|
export type PageMetadata = {
|
package/framework/core.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { serializeStyle } from "./style.js"
|
|
2
|
+
import { selectCollection } from "./collection-selector.js"
|
|
2
3
|
|
|
3
4
|
const signalMarker = Symbol("kudzu.signal")
|
|
4
5
|
const setterMarker = Symbol("kudzu.setter")
|
|
@@ -12,6 +13,7 @@ const listMarker = Symbol("kudzu.list")
|
|
|
12
13
|
const listFieldMarker = Symbol("kudzu.listField")
|
|
13
14
|
const listExpressionMarker = Symbol("kudzu.listExpression")
|
|
14
15
|
const listItemMarker = Symbol("kudzu.listItem")
|
|
16
|
+
const listIndexMarker = Symbol("kudzu.listIndex")
|
|
15
17
|
const listConditionalMarker = Symbol("kudzu.listConditional")
|
|
16
18
|
const refMarker = Symbol("kudzu.ref")
|
|
17
19
|
const contextMarker = Symbol("kudzu.context")
|
|
@@ -116,9 +118,10 @@ export function useEffect(callback, dependencies, module, handler, states, scope
|
|
|
116
118
|
if (!effects) throw new Error(`${source} useEffect() inside keyed lists must belong to the direct row component`)
|
|
117
119
|
const index = effects.length
|
|
118
120
|
if (renderContext.listTemplate) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
const existing = renderContext.listEffectOwners[index]
|
|
122
|
+
owner = existing ?? nextRenderId("e")
|
|
123
|
+
renderContext.listEffectOwners[index] = owner
|
|
124
|
+
list = !existing
|
|
122
125
|
} else {
|
|
123
126
|
owner = renderContext.listEffectOwners[index]
|
|
124
127
|
if (!owner) throw new Error(`${source} Keyed row effects must have the same hook order for every item`)
|
|
@@ -146,7 +149,8 @@ function validEffectDependency(value) {
|
|
|
146
149
|
export function useRef(initialValue) {
|
|
147
150
|
if (!renderContext) throw new Error("useRef() can only run while rendering a Kudzu component")
|
|
148
151
|
if (initialValue !== null) throw new Error("Kudzu DOM refs must initialize with null")
|
|
149
|
-
|
|
152
|
+
const row = Boolean(renderContext.listRoot || renderContext.listRowRoot)
|
|
153
|
+
return { [refMarker]: true, id: row ? nextRowRenderId("r") : nextRenderId("r"), current: null, row }
|
|
150
154
|
}
|
|
151
155
|
|
|
152
156
|
export function createContext(defaultValue) {
|
|
@@ -214,7 +218,7 @@ export function stateConditional(kind, state, truthy, falsy) {
|
|
|
214
218
|
return { [conditionalMarker]: true, kind, value: state.value, truthy, falsy, state: state.id }
|
|
215
219
|
}
|
|
216
220
|
|
|
217
|
-
export function list(items, keyField, render, ownerField) {
|
|
221
|
+
export function list(items, keyField, render, ownerField, selector = [], indexed = false) {
|
|
218
222
|
if (!items?.[signalMarker] || !Array.isArray(items.value)) throw new Error("A keyed list must use local array state")
|
|
219
223
|
let values = items.value
|
|
220
224
|
if (ownerField) {
|
|
@@ -222,11 +226,12 @@ export function list(items, keyField, render, ownerField) {
|
|
|
222
226
|
if (!owner) throw new Error("A nested keyed list must be rendered inside a keyed row")
|
|
223
227
|
renderContext.listFields?.add(ownerField)
|
|
224
228
|
values = renderContext.listTemplate ? [] : owner.item?.[ownerField]
|
|
225
|
-
if (!Array.isArray(values)) throw new Error(`Nested keyed list property "${ownerField}" must remain an array`)
|
|
229
|
+
if (!Array.isArray(values) && values != null) throw new Error(`Nested keyed list property "${ownerField}" must remain an array`)
|
|
226
230
|
}
|
|
231
|
+
values = selectCollection(values, selector)
|
|
227
232
|
const keys = new Set()
|
|
228
|
-
for (const item of values) {
|
|
229
|
-
const key = item?.[keyField]
|
|
233
|
+
for (const [index, item] of values.entries()) {
|
|
234
|
+
const key = keyField === null ? index : item?.[keyField]
|
|
230
235
|
if (!validListKey(key)) throw new Error(`Keyed list key "${keyField}" must be a string or finite number`)
|
|
231
236
|
assertListItem(item)
|
|
232
237
|
assertListValue(item, new Set())
|
|
@@ -234,7 +239,7 @@ export function list(items, keyField, render, ownerField) {
|
|
|
234
239
|
if (keys.has(token)) throw new Error(`Duplicate keyed list key: ${String(key)}`)
|
|
235
240
|
keys.add(token)
|
|
236
241
|
}
|
|
237
|
-
return { [listMarker]: true, items, values, keyField, render, ownerField }
|
|
242
|
+
return { [listMarker]: true, items, values, keyField, render, ownerField, selector, indexed }
|
|
238
243
|
}
|
|
239
244
|
|
|
240
245
|
export function listField(read, field) {
|
|
@@ -252,6 +257,10 @@ export function listItem() {
|
|
|
252
257
|
return { [listItemMarker]: true }
|
|
253
258
|
}
|
|
254
259
|
|
|
260
|
+
export function listIndex() {
|
|
261
|
+
return { [listIndexMarker]: true }
|
|
262
|
+
}
|
|
263
|
+
|
|
255
264
|
export function listConditional(kind, read, truthy, falsy, module, handler) {
|
|
256
265
|
renderContext?.handlerModules.add(module)
|
|
257
266
|
return { [listConditionalMarker]: true, kind, value: renderContext?.listTemplate ? undefined : read(), truthy, falsy, module, handler }
|
|
@@ -320,6 +329,7 @@ function bindingDescriptor(value) {
|
|
|
320
329
|
|
|
321
330
|
function serializeCapture(name, value, seen) {
|
|
322
331
|
if (value?.[listItemMarker]) return { type: "list-item" }
|
|
332
|
+
if (value?.[listIndexMarker]) return { type: "list-index" }
|
|
323
333
|
if (value?.[refMarker]) return { type: "ref", id: value.id }
|
|
324
334
|
if (value?.[signalMarker]) return { type: "state", id: value.id }
|
|
325
335
|
if (typeof value === "function" && value[reducerDispatchMarker]) throw new Error(`Native capture "${name}" cannot contain a reducer dispatch`)
|
|
@@ -355,7 +365,7 @@ function serializeCapture(name, value, seen) {
|
|
|
355
365
|
}
|
|
356
366
|
|
|
357
367
|
export async function renderPage(component, metadata = {}, props = {}, layout) {
|
|
358
|
-
renderContext = { scoped: Boolean(layout), renderScope: layout ? "layout" : "route", counters: { layout: { s: 0, r: 0, c: 0, l: 0, e: 0, p: 0 }, route: { s: 0, r: 0, c: 0, l: 0, e: 0, p: 0 } }, nextState: 0, nextRef: 0, nextCondition: 0, nextList: 0, nextEffect: 0, nextParam: 0, conditionDepth: 0, listDepth: 0, listRoot: undefined, listRowRoot: undefined, listTemplate: false, listInitialMarkers: false, listConditionalBranch: false, listFields: undefined, listEffectOwners: [], listRowStates: [], listRowConditions: [], listRowLists: [], effectOwners: [], contexts: [], states: {}, textStates: new Set(), conditionStates: new Set(), events: [], effects: [], bindings: [], textBindings: [], conditions: [], lists: [], handlerModules: new Set(), runtimeParamNames: metadata.runtimeParams, paramEntries: [], params: undefined, hasBehaviors: false, hasNativeBehaviors: false, hasEffects: false, hasParams: false, hasBindings: false, hasLists: false, hasListStyles: false }
|
|
368
|
+
renderContext = { scoped: Boolean(layout), renderScope: layout ? "layout" : "route", counters: { layout: { s: 0, r: 0, c: 0, l: 0, e: 0, p: 0 }, route: { s: 0, r: 0, c: 0, l: 0, e: 0, p: 0 } }, nextState: 0, nextRef: 0, nextCondition: 0, nextList: 0, nextEffect: 0, nextParam: 0, conditionDepth: 0, listDepth: 0, listRoot: undefined, listRowRoot: undefined, listTemplate: false, listInitialMarkers: false, listConditionalBranch: false, listFields: undefined, listEffectOwners: [], listRowStates: [], listRowRefs: [], listRowConditions: [], listRowLists: [], effectOwners: [], contexts: [], states: {}, textStates: new Set(), conditionStates: new Set(), events: [], effects: [], bindings: [], textBindings: [], conditions: [], lists: [], handlerModules: new Set(), runtimeParamNames: metadata.runtimeParams, paramEntries: [], params: undefined, hasBehaviors: false, hasNativeBehaviors: false, hasEffects: false, hasParams: false, hasBindings: false, hasLists: false, hasListStyles: false }
|
|
359
369
|
|
|
360
370
|
try {
|
|
361
371
|
const page = { [routeScopeMarker]: true, component, props }
|
|
@@ -546,7 +556,9 @@ async function renderNode(node, namespace, selectValue = noSelectValue) {
|
|
|
546
556
|
if (node?.[listMarker]) return renderList(node, namespace, selectValue)
|
|
547
557
|
if (node?.[listFieldMarker]) {
|
|
548
558
|
if (renderContext.listTemplate || renderContext.listInitialMarkers || renderContext.listConditionalBranch) renderContext.listFields?.add(node.field)
|
|
549
|
-
const marker = renderContext.listTemplate || renderContext.listInitialMarkers || renderContext.listConditionalBranch
|
|
559
|
+
const marker = renderContext.listTemplate || renderContext.listInitialMarkers || renderContext.listConditionalBranch
|
|
560
|
+
? sharedInitialListMarker() ? " data-k-list-text" : ` data-k-list-text="${escapeAttribute(node.field)}"`
|
|
561
|
+
: ""
|
|
550
562
|
return `<template${marker}></template>${escapeHtml(node.value ?? "")}<template data-k-list-text-end></template>`
|
|
551
563
|
}
|
|
552
564
|
if (node?.[listExpressionMarker]) {
|
|
@@ -571,6 +583,7 @@ async function renderNode(node, namespace, selectValue = noSelectValue) {
|
|
|
571
583
|
const owner = renderContext.listRoot ?? renderContext.listRowRoot
|
|
572
584
|
if (owner) owner.conditions = true
|
|
573
585
|
const previousBranch = renderContext.listConditionalBranch
|
|
586
|
+
if (owner && previousBranch) owner.nestedConditions = true
|
|
574
587
|
renderContext.listConditionalBranch = true
|
|
575
588
|
let truthy
|
|
576
589
|
let falsy
|
|
@@ -585,8 +598,10 @@ async function renderNode(node, namespace, selectValue = noSelectValue) {
|
|
|
585
598
|
? ""
|
|
586
599
|
: node.kind === "and" && !node.value ? escapeHtml(renderFalsy(node.value)) : node.value ? truthy : falsy
|
|
587
600
|
const initial = renderContext.listTemplate ? "" : ` data-k-list-current="${escapeAttribute(key)}"`
|
|
588
|
-
const shared = renderContext.listInitialMarkers && !renderContext.listTemplate
|
|
589
|
-
const condition = shared
|
|
601
|
+
const shared = renderContext.listInitialMarkers && !renderContext.listTemplate
|
|
602
|
+
const condition = shared
|
|
603
|
+
? ` data-k-list-condition${owner.descriptor.conditionHandlers ? ` data-k-list-condition-handler="${escapeAttribute(node.handler)}"` : ""}`
|
|
604
|
+
: ` data-k-list-condition='${escapeJsonAttribute(descriptor)}'`
|
|
590
605
|
const branches = shared ? "" : `<template data-k-list-true>${truthy}</template><template data-k-list-false>${falsy}</template>`
|
|
591
606
|
return `<template${condition}${initial}>${branches}</template>${current}<template data-k-list-condition-end></template>`
|
|
592
607
|
}
|
|
@@ -647,7 +662,7 @@ async function renderNode(node, namespace, selectValue = noSelectValue) {
|
|
|
647
662
|
if (rawName === "children" || rawName === "key") continue
|
|
648
663
|
if (rawName === "ref") {
|
|
649
664
|
if (!value?.[refMarker]) throw new Error("ref must be created by useRef(null)")
|
|
650
|
-
if (renderContext.listDepth) throw new Error("Refs
|
|
665
|
+
if (renderContext.listDepth && !value.row) throw new Error("Refs in keyed lists must be declared by the keyed row component")
|
|
651
666
|
attributes += ` data-k-ref="${value.id}"`
|
|
652
667
|
continue
|
|
653
668
|
}
|
|
@@ -679,7 +694,7 @@ async function renderNode(node, namespace, selectValue = noSelectValue) {
|
|
|
679
694
|
const native = template
|
|
680
695
|
attributes += ` data-k-native-${event}='${escapeJsonAttribute(native)}'`
|
|
681
696
|
renderContext.events.push({ event, native })
|
|
682
|
-
if (renderContext.listDepth && Object.values(template.scope).some(entry => entry?.type === "list-item")) listEvents.push([event, template])
|
|
697
|
+
if (renderContext.listDepth && Object.values(template.scope).some(entry => entry?.type === "list-item" || entry?.type === "list-index")) listEvents.push([event, template])
|
|
683
698
|
renderContext.hasNativeBehaviors = true
|
|
684
699
|
} else {
|
|
685
700
|
throw new Error(`${rawName} must reference a compilable event handler`)
|
|
@@ -727,12 +742,12 @@ async function renderNode(node, namespace, selectValue = noSelectValue) {
|
|
|
727
742
|
}
|
|
728
743
|
|
|
729
744
|
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)}'`
|
|
745
|
+
if ((renderContext.listTemplate || renderContext.listInitialMarkers || renderContext.listConditionalBranch) && listAttributes.length) attributes += sharedInitialListMarker() ? " data-k-list-attrs" : ` data-k-list-attrs='${escapeJsonAttribute(listAttributes)}'`
|
|
731
746
|
if ((renderContext.listTemplate || renderContext.listInitialMarkers || renderContext.listConditionalBranch) && listExpressionAttributes.length) attributes += ` data-k-list-expression-attrs='${escapeJsonAttribute(listExpressionAttributes)}'`
|
|
732
747
|
if ((renderContext.listTemplate || renderContext.listInitialMarkers || renderContext.listConditionalBranch) && listEvents.length) attributes += ` data-k-list-events='${escapeJsonAttribute(listEvents)}'`
|
|
733
748
|
if ((renderContext.listTemplate || renderContext.listInitialMarkers || renderContext.listConditionalBranch) && directListText) {
|
|
734
749
|
renderContext.listFields?.add(directListText.field)
|
|
735
|
-
attributes += ` data-k-list-text="${escapeAttribute(directListText.field)}"`
|
|
750
|
+
attributes += sharedInitialListMarker() ? " data-k-list-text" : ` data-k-list-text="${escapeAttribute(directListText.field)}"`
|
|
736
751
|
}
|
|
737
752
|
|
|
738
753
|
if (tag === "option" && selectValue !== noSelectValue && String(optionValue(props)) === (selectValue == null ? "" : String(selectValue))) attributes += " selected"
|
|
@@ -746,13 +761,23 @@ async function renderNode(node, namespace, selectValue = noSelectValue) {
|
|
|
746
761
|
return `<${tag}${attributes}>${children}</${tag}>`
|
|
747
762
|
}
|
|
748
763
|
|
|
764
|
+
function sharedInitialListMarker() {
|
|
765
|
+
const owner = renderContext.listRoot ?? renderContext.listRowRoot
|
|
766
|
+
return renderContext.listInitialMarkers && !renderContext.listTemplate && !renderContext.listConditionalBranch && owner?.descriptor.ownerField
|
|
767
|
+
}
|
|
768
|
+
|
|
749
769
|
async function renderList(node, namespace, selectValue) {
|
|
750
770
|
if (namespace) throw new Error(`Reactive keyed lists are not supported inside ${namespace}`)
|
|
751
771
|
const ownerRoot = node.ownerField ? renderContext.listRoot ?? renderContext.listRowRoot : undefined
|
|
752
772
|
const ownerTemplate = Boolean(node.ownerField && renderContext.listTemplate)
|
|
753
|
-
const
|
|
754
|
-
const
|
|
755
|
-
|
|
773
|
+
const rowList = node.ownerField ? nextRowList() : undefined
|
|
774
|
+
const id = rowList?.id ?? nextRenderId("l")
|
|
775
|
+
const descriptor = { id, state: node.items.id, key: node.keyField, keys: node.values.map((item, index) => node.keyField === null ? index : item[node.keyField]), ...(node.ownerField ? { ownerField: node.ownerField } : {}), ...(node.selector.length ? { selector: node.selector } : {}), ...(node.indexed ? { indexed: true } : {}), ...(!node.selector.length && node.keyField !== null && node.items[reducerStateMarker] ? { reducer: true } : {}) }
|
|
776
|
+
if (ownerTemplate) {
|
|
777
|
+
ownerRoot.descriptor.children ??= []
|
|
778
|
+
ownerRoot.descriptor.children.push({ id, field: node.ownerField, key: node.keyField, ...(node.selector.length ? { selector: node.selector } : {}) })
|
|
779
|
+
Object.assign(ownerRoot.descriptor, { mount: true, nested: true })
|
|
780
|
+
}
|
|
756
781
|
renderContext.listDepth++
|
|
757
782
|
const previousListRoot = renderContext.listRoot
|
|
758
783
|
const previousListRowRoot = renderContext.listRowRoot
|
|
@@ -761,23 +786,26 @@ async function renderList(node, namespace, selectValue) {
|
|
|
761
786
|
const previousListFields = renderContext.listFields
|
|
762
787
|
const previousListEffectOwners = renderContext.listEffectOwners
|
|
763
788
|
const previousListRowStates = renderContext.listRowStates
|
|
789
|
+
const previousListRowRefs = renderContext.listRowRefs
|
|
764
790
|
const previousListRowConditions = renderContext.listRowConditions
|
|
765
791
|
const previousListRowLists = renderContext.listRowLists
|
|
766
792
|
try {
|
|
767
793
|
renderContext.listTemplate = true
|
|
768
|
-
renderContext.listEffectOwners = []
|
|
769
|
-
renderContext.listRowStates = []
|
|
770
|
-
renderContext.
|
|
771
|
-
renderContext.
|
|
794
|
+
renderContext.listEffectOwners = rowList?.effectOwners ?? []
|
|
795
|
+
renderContext.listRowStates = rowList?.rowStates ?? []
|
|
796
|
+
renderContext.listRowRefs = rowList?.rowRefs ?? []
|
|
797
|
+
renderContext.listRowConditions = rowList?.rowConditions ?? []
|
|
798
|
+
renderContext.listRowLists = rowList?.rowLists ?? []
|
|
772
799
|
renderContext.listFields = new Set([node.keyField])
|
|
773
|
-
const templateRoot = { id, state: node.items.id, descriptor, template: true, effects: [], item: {}, rowIndexes: { s: 0, c: 0, l: 0 } }
|
|
800
|
+
const templateRoot = { id, state: node.items.id, descriptor, template: true, effects: [], item: {}, path: ownerRoot?.path ?? [], rowIndexes: { s: 0, r: 0, c: 0, l: 0 } }
|
|
774
801
|
renderContext.listRoot = templateRoot
|
|
775
802
|
renderContext.listRowRoot = templateRoot
|
|
776
|
-
const template = await renderNode(node.render({}), namespace, selectValue)
|
|
803
|
+
const template = await renderNode(node.render({}, 0), namespace, selectValue)
|
|
777
804
|
if (template.includes("data-k-native-") || template.includes("data-k-effects=") || template.includes("data-k-list=")) descriptor.mount = true
|
|
778
805
|
if (template.includes("data-k-list=")) descriptor.nested = true
|
|
779
806
|
if (template.includes("data-k-effects=")) descriptor.effects = true
|
|
780
807
|
if (templateRoot.conditions) descriptor.conditions = true
|
|
808
|
+
if (templateRoot.nestedConditions) descriptor.conditionHandlers = true
|
|
781
809
|
if (template.includes("data-k-list-text-end")) descriptor.textRanges = true
|
|
782
810
|
if (template.includes("data-k-list-attrs")) descriptor.attributes = true
|
|
783
811
|
if (template.includes("data-k-list-events")) descriptor.events = true
|
|
@@ -788,17 +816,25 @@ async function renderList(node, namespace, selectValue) {
|
|
|
788
816
|
if (renderContext.listRowConditions.length) descriptor.rowConditions = renderContext.listRowConditions.map(({ id }) => id)
|
|
789
817
|
descriptor.mount = true
|
|
790
818
|
}
|
|
791
|
-
|
|
819
|
+
if (renderContext.listRowRefs.length) {
|
|
820
|
+
descriptor.rowRefs = renderContext.listRowRefs.map(({ id }) => id)
|
|
821
|
+
descriptor.mount = true
|
|
822
|
+
}
|
|
823
|
+
const seed = node.ownerField || node.selector.length || node.keyField === null ? undefined : listSeed(node.values, renderContext.listFields)
|
|
792
824
|
if (seed) descriptor.seed = seed
|
|
793
825
|
let current = ""
|
|
794
826
|
renderContext.listTemplate = false
|
|
795
827
|
renderContext.listInitialMarkers = Boolean(descriptor.conditions)
|
|
796
|
-
for (const item of node.values) {
|
|
797
|
-
|
|
828
|
+
for (const [index, item] of node.values.entries()) {
|
|
829
|
+
const key = node.keyField === null ? index : item[node.keyField]
|
|
830
|
+
renderContext.listRoot = { id, state: node.items.id, descriptor, key, template: false, effects: [], item, path: [...(ownerRoot?.path ?? []), `${id}=${typeof key}:${key}`], rowIndexes: { s: 0, r: 0, c: 0, l: 0 } }
|
|
798
831
|
renderContext.listRowRoot = renderContext.listRoot
|
|
799
|
-
current += await renderNode(node.render(item), namespace, selectValue)
|
|
832
|
+
current += await renderNode(node.render(item, index), namespace, selectValue)
|
|
833
|
+
}
|
|
834
|
+
if (!node.ownerField || ownerTemplate && !rowList.planned) {
|
|
835
|
+
renderContext.lists.push(descriptor)
|
|
836
|
+
if (rowList) rowList.planned = true
|
|
800
837
|
}
|
|
801
|
-
if (!node.ownerField || ownerTemplate) renderContext.lists.push(descriptor)
|
|
802
838
|
renderContext.hasBehaviors = true
|
|
803
839
|
renderContext.hasLists = true
|
|
804
840
|
const prototype = node.ownerField && !ownerTemplate ? "" : template
|
|
@@ -811,23 +847,24 @@ async function renderList(node, namespace, selectValue) {
|
|
|
811
847
|
renderContext.listFields = previousListFields
|
|
812
848
|
renderContext.listEffectOwners = previousListEffectOwners
|
|
813
849
|
renderContext.listRowStates = previousListRowStates
|
|
850
|
+
renderContext.listRowRefs = previousListRowRefs
|
|
814
851
|
renderContext.listRowConditions = previousListRowConditions
|
|
815
852
|
renderContext.listRowLists = previousListRowLists
|
|
816
853
|
renderContext.listDepth--
|
|
817
854
|
}
|
|
818
855
|
}
|
|
819
856
|
|
|
820
|
-
function
|
|
857
|
+
function nextRowList() {
|
|
821
858
|
const root = renderContext.listRoot ?? renderContext.listRowRoot
|
|
822
859
|
const index = root.rowIndexes.l++
|
|
823
860
|
if (renderContext.listTemplate) {
|
|
824
|
-
const
|
|
825
|
-
renderContext.listRowLists[index] =
|
|
826
|
-
return
|
|
861
|
+
const entry = renderContext.listRowLists[index] ?? { id: nextRenderId("l"), rowLists: [], rowStates: [], rowRefs: [], rowConditions: [], effectOwners: [], planned: false }
|
|
862
|
+
renderContext.listRowLists[index] = entry
|
|
863
|
+
return entry
|
|
827
864
|
}
|
|
828
865
|
const entry = renderContext.listRowLists[index]
|
|
829
866
|
if (!entry) throw new Error("Nested keyed lists must have the same order for every parent item")
|
|
830
|
-
return entry
|
|
867
|
+
return entry
|
|
831
868
|
}
|
|
832
869
|
|
|
833
870
|
function nextRenderId(kind) {
|
|
@@ -839,19 +876,21 @@ function nextRenderId(kind) {
|
|
|
839
876
|
function nextRowRenderId(kind, initialValue) {
|
|
840
877
|
const root = renderContext.listRoot ?? renderContext.listRowRoot
|
|
841
878
|
const index = root.rowIndexes[kind]++
|
|
842
|
-
const entries = kind === "s" ? renderContext.listRowStates : renderContext.listRowConditions
|
|
879
|
+
const entries = kind === "s" ? renderContext.listRowStates : kind === "r" ? renderContext.listRowRefs : renderContext.listRowConditions
|
|
843
880
|
if (renderContext.listTemplate) {
|
|
881
|
+
const entry = entries[index]
|
|
882
|
+
if (entry) return entry.id
|
|
844
883
|
const id = `${nextRenderId(kind)}:$k`
|
|
845
884
|
entries[index] = kind === "s" ? { id, initialValue } : { id }
|
|
846
885
|
return id
|
|
847
886
|
}
|
|
848
887
|
const entry = entries[index]
|
|
849
|
-
if (!entry) throw new Error(`Keyed row ${kind === "s" ? "state hooks" : "conditionals"} must have the same order for every item`)
|
|
850
|
-
return rowRenderId(entry.id, root.key)
|
|
888
|
+
if (!entry) throw new Error(`Keyed row ${kind === "s" ? "state hooks" : kind === "r" ? "ref hooks" : "conditionals"} must have the same order for every item`)
|
|
889
|
+
return rowRenderId(entry.id, root.descriptor.ownerField ? root.path : [`${typeof root.key}:${root.key}`])
|
|
851
890
|
}
|
|
852
891
|
|
|
853
|
-
function rowRenderId(id,
|
|
854
|
-
return id.replace("$k", encodeURIComponent(
|
|
892
|
+
function rowRenderId(id, path) {
|
|
893
|
+
return id.replace("$k", encodeURIComponent(path.join("/")))
|
|
855
894
|
}
|
|
856
895
|
|
|
857
896
|
function optionValue(props) {
|