@kudzujs/core 0.6.27 → 0.7.0
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 +72 -38
- package/RELEASES.md +44 -0
- package/framework/README.md +9 -3
- package/framework/binding-runtime.js +4 -2
- package/framework/build.mjs +417 -156
- package/framework/collection-selector.js +63 -0
- package/framework/core.d.ts +6 -1
- package/framework/core.mjs +73 -38
- package/framework/list-runtime.js +321 -95
- package/framework/native-runtime.js +13 -2
- package/framework/shared-runtime.js +18 -5
- package/package.json +2 -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
|
@@ -3,6 +3,7 @@ export type Reducer<State, Action> = (state: State, action: Action) => State
|
|
|
3
3
|
export type Dispatch<Action> = (action: Action) => void
|
|
4
4
|
export type EffectCleanup = () => void | Promise<void>
|
|
5
5
|
export type EffectDependency = string | number | boolean | null
|
|
6
|
+
export const Fragment: unique symbol
|
|
6
7
|
|
|
7
8
|
export function useState<T>(initialValue: T): [T, StateSetter<T>]
|
|
8
9
|
export function useReducer<State, Action>(reducer: Reducer<State, Action>, initialValue: State): [State, Dispatch<Action>]
|
|
@@ -22,15 +23,19 @@ export interface Context<T> {
|
|
|
22
23
|
export function createContext<T>(defaultValue: T): Context<T>
|
|
23
24
|
export function useContext<T>(context: Context<T>): T
|
|
24
25
|
|
|
26
|
+
declare const React: { Fragment: typeof Fragment }
|
|
27
|
+
export default React
|
|
28
|
+
|
|
25
29
|
export function behavior(commands: Array<["add" | "set" | "log", unknown, unknown]>): unknown
|
|
26
30
|
export function nativeBehavior(module: string, handler: string, states: Array<[string, unknown]>, scope: Array<[string, unknown]>): unknown
|
|
27
31
|
export function binding(value: unknown, module: string, handler: string, states: Array<[string, unknown]>, scope: Array<[string, unknown]>): unknown
|
|
28
32
|
export function bindingValue(value: unknown): unknown
|
|
29
33
|
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
|
|
34
|
+
export function list(items: unknown, keyField: string | null, render: (item: unknown, index: number) => unknown, ownerField?: string, selector?: unknown[], indexed?: boolean): unknown
|
|
31
35
|
export function listField(read: () => unknown, field: string): unknown
|
|
32
36
|
export function listExpression(read: () => unknown, module: string, handler: string): unknown
|
|
33
37
|
export function listItem(): unknown
|
|
38
|
+
export function listIndex(): unknown
|
|
34
39
|
export function listConditional(kind: "and" | "ternary", read: () => unknown, truthy: () => unknown, falsy: () => unknown, module: string, handler: string): unknown
|
|
35
40
|
|
|
36
41
|
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,12 +13,14 @@ 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")
|
|
18
20
|
const contextProviderMarker = Symbol("kudzu.contextProvider")
|
|
19
21
|
const routeScopeMarker = Symbol("kudzu.routeScope")
|
|
20
22
|
const noSelectValue = Symbol("kudzu.no-select-value")
|
|
23
|
+
export const Fragment = Symbol.for("kudzu.fragment")
|
|
21
24
|
const svgAttributeAliases = {
|
|
22
25
|
clipRule: "clip-rule",
|
|
23
26
|
colorInterpolation: "color-interpolation",
|
|
@@ -116,9 +119,10 @@ export function useEffect(callback, dependencies, module, handler, states, scope
|
|
|
116
119
|
if (!effects) throw new Error(`${source} useEffect() inside keyed lists must belong to the direct row component`)
|
|
117
120
|
const index = effects.length
|
|
118
121
|
if (renderContext.listTemplate) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
+
const existing = renderContext.listEffectOwners[index]
|
|
123
|
+
owner = existing ?? nextRenderId("e")
|
|
124
|
+
renderContext.listEffectOwners[index] = owner
|
|
125
|
+
list = !existing
|
|
122
126
|
} else {
|
|
123
127
|
owner = renderContext.listEffectOwners[index]
|
|
124
128
|
if (!owner) throw new Error(`${source} Keyed row effects must have the same hook order for every item`)
|
|
@@ -146,7 +150,8 @@ function validEffectDependency(value) {
|
|
|
146
150
|
export function useRef(initialValue) {
|
|
147
151
|
if (!renderContext) throw new Error("useRef() can only run while rendering a Kudzu component")
|
|
148
152
|
if (initialValue !== null) throw new Error("Kudzu DOM refs must initialize with null")
|
|
149
|
-
|
|
153
|
+
const row = Boolean(renderContext.listRoot || renderContext.listRowRoot)
|
|
154
|
+
return { [refMarker]: true, id: row ? nextRowRenderId("r") : nextRenderId("r"), current: null, row }
|
|
150
155
|
}
|
|
151
156
|
|
|
152
157
|
export function createContext(defaultValue) {
|
|
@@ -166,6 +171,8 @@ export function useContext(context) {
|
|
|
166
171
|
return context.defaultValue
|
|
167
172
|
}
|
|
168
173
|
|
|
174
|
+
export default { Fragment }
|
|
175
|
+
|
|
169
176
|
export function behavior(commands) {
|
|
170
177
|
return {
|
|
171
178
|
[behaviorMarker]: true,
|
|
@@ -214,7 +221,7 @@ export function stateConditional(kind, state, truthy, falsy) {
|
|
|
214
221
|
return { [conditionalMarker]: true, kind, value: state.value, truthy, falsy, state: state.id }
|
|
215
222
|
}
|
|
216
223
|
|
|
217
|
-
export function list(items, keyField, render, ownerField) {
|
|
224
|
+
export function list(items, keyField, render, ownerField, selector = [], indexed = false) {
|
|
218
225
|
if (!items?.[signalMarker] || !Array.isArray(items.value)) throw new Error("A keyed list must use local array state")
|
|
219
226
|
let values = items.value
|
|
220
227
|
if (ownerField) {
|
|
@@ -222,11 +229,12 @@ export function list(items, keyField, render, ownerField) {
|
|
|
222
229
|
if (!owner) throw new Error("A nested keyed list must be rendered inside a keyed row")
|
|
223
230
|
renderContext.listFields?.add(ownerField)
|
|
224
231
|
values = renderContext.listTemplate ? [] : owner.item?.[ownerField]
|
|
225
|
-
if (!Array.isArray(values)) throw new Error(`Nested keyed list property "${ownerField}" must remain an array`)
|
|
232
|
+
if (!Array.isArray(values) && values != null) throw new Error(`Nested keyed list property "${ownerField}" must remain an array`)
|
|
226
233
|
}
|
|
234
|
+
values = selectCollection(values, selector)
|
|
227
235
|
const keys = new Set()
|
|
228
|
-
for (const item of values) {
|
|
229
|
-
const key = item?.[keyField]
|
|
236
|
+
for (const [index, item] of values.entries()) {
|
|
237
|
+
const key = keyField === null ? index : item?.[keyField]
|
|
230
238
|
if (!validListKey(key)) throw new Error(`Keyed list key "${keyField}" must be a string or finite number`)
|
|
231
239
|
assertListItem(item)
|
|
232
240
|
assertListValue(item, new Set())
|
|
@@ -234,7 +242,7 @@ export function list(items, keyField, render, ownerField) {
|
|
|
234
242
|
if (keys.has(token)) throw new Error(`Duplicate keyed list key: ${String(key)}`)
|
|
235
243
|
keys.add(token)
|
|
236
244
|
}
|
|
237
|
-
return { [listMarker]: true, items, values, keyField, render, ownerField }
|
|
245
|
+
return { [listMarker]: true, items, values, keyField, render, ownerField, selector, indexed }
|
|
238
246
|
}
|
|
239
247
|
|
|
240
248
|
export function listField(read, field) {
|
|
@@ -252,6 +260,10 @@ export function listItem() {
|
|
|
252
260
|
return { [listItemMarker]: true }
|
|
253
261
|
}
|
|
254
262
|
|
|
263
|
+
export function listIndex() {
|
|
264
|
+
return { [listIndexMarker]: true }
|
|
265
|
+
}
|
|
266
|
+
|
|
255
267
|
export function listConditional(kind, read, truthy, falsy, module, handler) {
|
|
256
268
|
renderContext?.handlerModules.add(module)
|
|
257
269
|
return { [listConditionalMarker]: true, kind, value: renderContext?.listTemplate ? undefined : read(), truthy, falsy, module, handler }
|
|
@@ -320,6 +332,7 @@ function bindingDescriptor(value) {
|
|
|
320
332
|
|
|
321
333
|
function serializeCapture(name, value, seen) {
|
|
322
334
|
if (value?.[listItemMarker]) return { type: "list-item" }
|
|
335
|
+
if (value?.[listIndexMarker]) return { type: "list-index" }
|
|
323
336
|
if (value?.[refMarker]) return { type: "ref", id: value.id }
|
|
324
337
|
if (value?.[signalMarker]) return { type: "state", id: value.id }
|
|
325
338
|
if (typeof value === "function" && value[reducerDispatchMarker]) throw new Error(`Native capture "${name}" cannot contain a reducer dispatch`)
|
|
@@ -355,7 +368,7 @@ function serializeCapture(name, value, seen) {
|
|
|
355
368
|
}
|
|
356
369
|
|
|
357
370
|
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 }
|
|
371
|
+
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
372
|
|
|
360
373
|
try {
|
|
361
374
|
const page = { [routeScopeMarker]: true, component, props }
|
|
@@ -573,6 +586,7 @@ async function renderNode(node, namespace, selectValue = noSelectValue) {
|
|
|
573
586
|
const owner = renderContext.listRoot ?? renderContext.listRowRoot
|
|
574
587
|
if (owner) owner.conditions = true
|
|
575
588
|
const previousBranch = renderContext.listConditionalBranch
|
|
589
|
+
if (owner && previousBranch) owner.nestedConditions = true
|
|
576
590
|
renderContext.listConditionalBranch = true
|
|
577
591
|
let truthy
|
|
578
592
|
let falsy
|
|
@@ -587,8 +601,10 @@ async function renderNode(node, namespace, selectValue = noSelectValue) {
|
|
|
587
601
|
? ""
|
|
588
602
|
: node.kind === "and" && !node.value ? escapeHtml(renderFalsy(node.value)) : node.value ? truthy : falsy
|
|
589
603
|
const initial = renderContext.listTemplate ? "" : ` data-k-list-current="${escapeAttribute(key)}"`
|
|
590
|
-
const shared = renderContext.listInitialMarkers && !renderContext.listTemplate
|
|
591
|
-
const condition = shared
|
|
604
|
+
const shared = renderContext.listInitialMarkers && !renderContext.listTemplate
|
|
605
|
+
const condition = shared
|
|
606
|
+
? ` data-k-list-condition${owner.descriptor.conditionHandlers ? ` data-k-list-condition-handler="${escapeAttribute(node.handler)}"` : ""}`
|
|
607
|
+
: ` data-k-list-condition='${escapeJsonAttribute(descriptor)}'`
|
|
592
608
|
const branches = shared ? "" : `<template data-k-list-true>${truthy}</template><template data-k-list-false>${falsy}</template>`
|
|
593
609
|
return `<template${condition}${initial}>${branches}</template>${current}<template data-k-list-condition-end></template>`
|
|
594
610
|
}
|
|
@@ -649,7 +665,7 @@ async function renderNode(node, namespace, selectValue = noSelectValue) {
|
|
|
649
665
|
if (rawName === "children" || rawName === "key") continue
|
|
650
666
|
if (rawName === "ref") {
|
|
651
667
|
if (!value?.[refMarker]) throw new Error("ref must be created by useRef(null)")
|
|
652
|
-
if (renderContext.listDepth) throw new Error("Refs
|
|
668
|
+
if (renderContext.listDepth && !value.row) throw new Error("Refs in keyed lists must be declared by the keyed row component")
|
|
653
669
|
attributes += ` data-k-ref="${value.id}"`
|
|
654
670
|
continue
|
|
655
671
|
}
|
|
@@ -681,7 +697,7 @@ async function renderNode(node, namespace, selectValue = noSelectValue) {
|
|
|
681
697
|
const native = template
|
|
682
698
|
attributes += ` data-k-native-${event}='${escapeJsonAttribute(native)}'`
|
|
683
699
|
renderContext.events.push({ event, native })
|
|
684
|
-
if (renderContext.listDepth && Object.values(template.scope).some(entry => entry?.type === "list-item")) listEvents.push([event, template])
|
|
700
|
+
if (renderContext.listDepth && Object.values(template.scope).some(entry => entry?.type === "list-item" || entry?.type === "list-index")) listEvents.push([event, template])
|
|
685
701
|
renderContext.hasNativeBehaviors = true
|
|
686
702
|
} else {
|
|
687
703
|
throw new Error(`${rawName} must reference a compilable event handler`)
|
|
@@ -757,9 +773,14 @@ async function renderList(node, namespace, selectValue) {
|
|
|
757
773
|
if (namespace) throw new Error(`Reactive keyed lists are not supported inside ${namespace}`)
|
|
758
774
|
const ownerRoot = node.ownerField ? renderContext.listRoot ?? renderContext.listRowRoot : undefined
|
|
759
775
|
const ownerTemplate = Boolean(node.ownerField && renderContext.listTemplate)
|
|
760
|
-
const
|
|
761
|
-
const
|
|
762
|
-
|
|
776
|
+
const rowList = node.ownerField ? nextRowList() : undefined
|
|
777
|
+
const id = rowList?.id ?? nextRenderId("l")
|
|
778
|
+
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 } : {}) }
|
|
779
|
+
if (ownerTemplate) {
|
|
780
|
+
ownerRoot.descriptor.children ??= []
|
|
781
|
+
ownerRoot.descriptor.children.push({ id, field: node.ownerField, key: node.keyField, ...(node.selector.length ? { selector: node.selector } : {}) })
|
|
782
|
+
Object.assign(ownerRoot.descriptor, { mount: true, nested: true })
|
|
783
|
+
}
|
|
763
784
|
renderContext.listDepth++
|
|
764
785
|
const previousListRoot = renderContext.listRoot
|
|
765
786
|
const previousListRowRoot = renderContext.listRowRoot
|
|
@@ -768,23 +789,26 @@ async function renderList(node, namespace, selectValue) {
|
|
|
768
789
|
const previousListFields = renderContext.listFields
|
|
769
790
|
const previousListEffectOwners = renderContext.listEffectOwners
|
|
770
791
|
const previousListRowStates = renderContext.listRowStates
|
|
792
|
+
const previousListRowRefs = renderContext.listRowRefs
|
|
771
793
|
const previousListRowConditions = renderContext.listRowConditions
|
|
772
794
|
const previousListRowLists = renderContext.listRowLists
|
|
773
795
|
try {
|
|
774
796
|
renderContext.listTemplate = true
|
|
775
|
-
renderContext.listEffectOwners = []
|
|
776
|
-
renderContext.listRowStates = []
|
|
777
|
-
renderContext.
|
|
778
|
-
renderContext.
|
|
797
|
+
renderContext.listEffectOwners = rowList?.effectOwners ?? []
|
|
798
|
+
renderContext.listRowStates = rowList?.rowStates ?? []
|
|
799
|
+
renderContext.listRowRefs = rowList?.rowRefs ?? []
|
|
800
|
+
renderContext.listRowConditions = rowList?.rowConditions ?? []
|
|
801
|
+
renderContext.listRowLists = rowList?.rowLists ?? []
|
|
779
802
|
renderContext.listFields = new Set([node.keyField])
|
|
780
|
-
const templateRoot = { id, state: node.items.id, descriptor, template: true, effects: [], item: {}, rowIndexes: { s: 0, c: 0, l: 0 } }
|
|
803
|
+
const templateRoot = { id, state: node.items.id, descriptor, template: true, effects: [], item: {}, path: ownerRoot?.path ?? [], rowIndexes: { s: 0, r: 0, c: 0, l: 0 } }
|
|
781
804
|
renderContext.listRoot = templateRoot
|
|
782
805
|
renderContext.listRowRoot = templateRoot
|
|
783
|
-
const template = await renderNode(node.render({}), namespace, selectValue)
|
|
806
|
+
const template = await renderNode(node.render({}, 0), namespace, selectValue)
|
|
784
807
|
if (template.includes("data-k-native-") || template.includes("data-k-effects=") || template.includes("data-k-list=")) descriptor.mount = true
|
|
785
808
|
if (template.includes("data-k-list=")) descriptor.nested = true
|
|
786
809
|
if (template.includes("data-k-effects=")) descriptor.effects = true
|
|
787
810
|
if (templateRoot.conditions) descriptor.conditions = true
|
|
811
|
+
if (templateRoot.nestedConditions) descriptor.conditionHandlers = true
|
|
788
812
|
if (template.includes("data-k-list-text-end")) descriptor.textRanges = true
|
|
789
813
|
if (template.includes("data-k-list-attrs")) descriptor.attributes = true
|
|
790
814
|
if (template.includes("data-k-list-events")) descriptor.events = true
|
|
@@ -795,17 +819,25 @@ async function renderList(node, namespace, selectValue) {
|
|
|
795
819
|
if (renderContext.listRowConditions.length) descriptor.rowConditions = renderContext.listRowConditions.map(({ id }) => id)
|
|
796
820
|
descriptor.mount = true
|
|
797
821
|
}
|
|
798
|
-
|
|
822
|
+
if (renderContext.listRowRefs.length) {
|
|
823
|
+
descriptor.rowRefs = renderContext.listRowRefs.map(({ id }) => id)
|
|
824
|
+
descriptor.mount = true
|
|
825
|
+
}
|
|
826
|
+
const seed = node.ownerField || node.selector.length || node.keyField === null ? undefined : listSeed(node.values, renderContext.listFields)
|
|
799
827
|
if (seed) descriptor.seed = seed
|
|
800
828
|
let current = ""
|
|
801
829
|
renderContext.listTemplate = false
|
|
802
830
|
renderContext.listInitialMarkers = Boolean(descriptor.conditions)
|
|
803
|
-
for (const item of node.values) {
|
|
804
|
-
|
|
831
|
+
for (const [index, item] of node.values.entries()) {
|
|
832
|
+
const key = node.keyField === null ? index : item[node.keyField]
|
|
833
|
+
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 } }
|
|
805
834
|
renderContext.listRowRoot = renderContext.listRoot
|
|
806
|
-
current += await renderNode(node.render(item), namespace, selectValue)
|
|
835
|
+
current += await renderNode(node.render(item, index), namespace, selectValue)
|
|
836
|
+
}
|
|
837
|
+
if (!node.ownerField || ownerTemplate && !rowList.planned) {
|
|
838
|
+
renderContext.lists.push(descriptor)
|
|
839
|
+
if (rowList) rowList.planned = true
|
|
807
840
|
}
|
|
808
|
-
if (!node.ownerField || ownerTemplate) renderContext.lists.push(descriptor)
|
|
809
841
|
renderContext.hasBehaviors = true
|
|
810
842
|
renderContext.hasLists = true
|
|
811
843
|
const prototype = node.ownerField && !ownerTemplate ? "" : template
|
|
@@ -818,23 +850,24 @@ async function renderList(node, namespace, selectValue) {
|
|
|
818
850
|
renderContext.listFields = previousListFields
|
|
819
851
|
renderContext.listEffectOwners = previousListEffectOwners
|
|
820
852
|
renderContext.listRowStates = previousListRowStates
|
|
853
|
+
renderContext.listRowRefs = previousListRowRefs
|
|
821
854
|
renderContext.listRowConditions = previousListRowConditions
|
|
822
855
|
renderContext.listRowLists = previousListRowLists
|
|
823
856
|
renderContext.listDepth--
|
|
824
857
|
}
|
|
825
858
|
}
|
|
826
859
|
|
|
827
|
-
function
|
|
860
|
+
function nextRowList() {
|
|
828
861
|
const root = renderContext.listRoot ?? renderContext.listRowRoot
|
|
829
862
|
const index = root.rowIndexes.l++
|
|
830
863
|
if (renderContext.listTemplate) {
|
|
831
|
-
const
|
|
832
|
-
renderContext.listRowLists[index] =
|
|
833
|
-
return
|
|
864
|
+
const entry = renderContext.listRowLists[index] ?? { id: nextRenderId("l"), rowLists: [], rowStates: [], rowRefs: [], rowConditions: [], effectOwners: [], planned: false }
|
|
865
|
+
renderContext.listRowLists[index] = entry
|
|
866
|
+
return entry
|
|
834
867
|
}
|
|
835
868
|
const entry = renderContext.listRowLists[index]
|
|
836
869
|
if (!entry) throw new Error("Nested keyed lists must have the same order for every parent item")
|
|
837
|
-
return entry
|
|
870
|
+
return entry
|
|
838
871
|
}
|
|
839
872
|
|
|
840
873
|
function nextRenderId(kind) {
|
|
@@ -846,19 +879,21 @@ function nextRenderId(kind) {
|
|
|
846
879
|
function nextRowRenderId(kind, initialValue) {
|
|
847
880
|
const root = renderContext.listRoot ?? renderContext.listRowRoot
|
|
848
881
|
const index = root.rowIndexes[kind]++
|
|
849
|
-
const entries = kind === "s" ? renderContext.listRowStates : renderContext.listRowConditions
|
|
882
|
+
const entries = kind === "s" ? renderContext.listRowStates : kind === "r" ? renderContext.listRowRefs : renderContext.listRowConditions
|
|
850
883
|
if (renderContext.listTemplate) {
|
|
884
|
+
const entry = entries[index]
|
|
885
|
+
if (entry) return entry.id
|
|
851
886
|
const id = `${nextRenderId(kind)}:$k`
|
|
852
887
|
entries[index] = kind === "s" ? { id, initialValue } : { id }
|
|
853
888
|
return id
|
|
854
889
|
}
|
|
855
890
|
const entry = entries[index]
|
|
856
|
-
if (!entry) throw new Error(`Keyed row ${kind === "s" ? "state hooks" : "conditionals"} must have the same order for every item`)
|
|
857
|
-
return rowRenderId(entry.id, root.key)
|
|
891
|
+
if (!entry) throw new Error(`Keyed row ${kind === "s" ? "state hooks" : kind === "r" ? "ref hooks" : "conditionals"} must have the same order for every item`)
|
|
892
|
+
return rowRenderId(entry.id, root.descriptor.ownerField ? root.path : [`${typeof root.key}:${root.key}`])
|
|
858
893
|
}
|
|
859
894
|
|
|
860
|
-
function rowRenderId(id,
|
|
861
|
-
return id.replace("$k", encodeURIComponent(
|
|
895
|
+
function rowRenderId(id, path) {
|
|
896
|
+
return id.replace("$k", encodeURIComponent(path.join("/")))
|
|
862
897
|
}
|
|
863
898
|
|
|
864
899
|
function optionValue(props) {
|