@kudzujs/core 0.6.27 → 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.
@@ -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
+ }
@@ -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 = {
@@ -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
- owner = nextRenderId("e")
120
- renderContext.listEffectOwners.push(owner)
121
- list = true
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
- return { [refMarker]: true, id: nextRenderId("r"), current: null }
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 }
@@ -573,6 +583,7 @@ async function renderNode(node, namespace, selectValue = noSelectValue) {
573
583
  const owner = renderContext.listRoot ?? renderContext.listRowRoot
574
584
  if (owner) owner.conditions = true
575
585
  const previousBranch = renderContext.listConditionalBranch
586
+ if (owner && previousBranch) owner.nestedConditions = true
576
587
  renderContext.listConditionalBranch = true
577
588
  let truthy
578
589
  let falsy
@@ -587,8 +598,10 @@ async function renderNode(node, namespace, selectValue = noSelectValue) {
587
598
  ? ""
588
599
  : node.kind === "and" && !node.value ? escapeHtml(renderFalsy(node.value)) : node.value ? truthy : falsy
589
600
  const initial = renderContext.listTemplate ? "" : ` data-k-list-current="${escapeAttribute(key)}"`
590
- const shared = renderContext.listInitialMarkers && !renderContext.listTemplate && owner?.descriptor.ownerField
591
- const condition = shared ? " data-k-list-condition" : ` data-k-list-condition='${escapeJsonAttribute(descriptor)}'`
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)}'`
592
605
  const branches = shared ? "" : `<template data-k-list-true>${truthy}</template><template data-k-list-false>${falsy}</template>`
593
606
  return `<template${condition}${initial}>${branches}</template>${current}<template data-k-list-condition-end></template>`
594
607
  }
@@ -649,7 +662,7 @@ async function renderNode(node, namespace, selectValue = noSelectValue) {
649
662
  if (rawName === "children" || rawName === "key") continue
650
663
  if (rawName === "ref") {
651
664
  if (!value?.[refMarker]) throw new Error("ref must be created by useRef(null)")
652
- if (renderContext.listDepth) throw new Error("Refs are not supported in keyed lists")
665
+ if (renderContext.listDepth && !value.row) throw new Error("Refs in keyed lists must be declared by the keyed row component")
653
666
  attributes += ` data-k-ref="${value.id}"`
654
667
  continue
655
668
  }
@@ -681,7 +694,7 @@ async function renderNode(node, namespace, selectValue = noSelectValue) {
681
694
  const native = template
682
695
  attributes += ` data-k-native-${event}='${escapeJsonAttribute(native)}'`
683
696
  renderContext.events.push({ event, native })
684
- 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])
685
698
  renderContext.hasNativeBehaviors = true
686
699
  } else {
687
700
  throw new Error(`${rawName} must reference a compilable event handler`)
@@ -757,9 +770,14 @@ async function renderList(node, namespace, selectValue) {
757
770
  if (namespace) throw new Error(`Reactive keyed lists are not supported inside ${namespace}`)
758
771
  const ownerRoot = node.ownerField ? renderContext.listRoot ?? renderContext.listRowRoot : undefined
759
772
  const ownerTemplate = Boolean(node.ownerField && renderContext.listTemplate)
760
- const id = node.ownerField ? nextRowListId() : nextRenderId("l")
761
- const descriptor = { id, state: node.items.id, key: node.keyField, keys: node.values.map(item => item[node.keyField]), ...(node.ownerField ? { ownerField: node.ownerField } : {}), ...(node.items[reducerStateMarker] ? { reducer: true } : {}) }
762
- if (ownerTemplate) Object.assign(ownerRoot.descriptor, { child: { field: node.ownerField, key: node.keyField }, mount: true, nested: true })
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
+ }
763
781
  renderContext.listDepth++
764
782
  const previousListRoot = renderContext.listRoot
765
783
  const previousListRowRoot = renderContext.listRowRoot
@@ -768,23 +786,26 @@ async function renderList(node, namespace, selectValue) {
768
786
  const previousListFields = renderContext.listFields
769
787
  const previousListEffectOwners = renderContext.listEffectOwners
770
788
  const previousListRowStates = renderContext.listRowStates
789
+ const previousListRowRefs = renderContext.listRowRefs
771
790
  const previousListRowConditions = renderContext.listRowConditions
772
791
  const previousListRowLists = renderContext.listRowLists
773
792
  try {
774
793
  renderContext.listTemplate = true
775
- renderContext.listEffectOwners = []
776
- renderContext.listRowStates = []
777
- renderContext.listRowConditions = []
778
- renderContext.listRowLists = []
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 ?? []
779
799
  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 } }
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 } }
781
801
  renderContext.listRoot = templateRoot
782
802
  renderContext.listRowRoot = templateRoot
783
- const template = await renderNode(node.render({}), namespace, selectValue)
803
+ const template = await renderNode(node.render({}, 0), namespace, selectValue)
784
804
  if (template.includes("data-k-native-") || template.includes("data-k-effects=") || template.includes("data-k-list=")) descriptor.mount = true
785
805
  if (template.includes("data-k-list=")) descriptor.nested = true
786
806
  if (template.includes("data-k-effects=")) descriptor.effects = true
787
807
  if (templateRoot.conditions) descriptor.conditions = true
808
+ if (templateRoot.nestedConditions) descriptor.conditionHandlers = true
788
809
  if (template.includes("data-k-list-text-end")) descriptor.textRanges = true
789
810
  if (template.includes("data-k-list-attrs")) descriptor.attributes = true
790
811
  if (template.includes("data-k-list-events")) descriptor.events = true
@@ -795,17 +816,25 @@ async function renderList(node, namespace, selectValue) {
795
816
  if (renderContext.listRowConditions.length) descriptor.rowConditions = renderContext.listRowConditions.map(({ id }) => id)
796
817
  descriptor.mount = true
797
818
  }
798
- const seed = node.ownerField ? undefined : listSeed(node.values, renderContext.listFields)
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)
799
824
  if (seed) descriptor.seed = seed
800
825
  let current = ""
801
826
  renderContext.listTemplate = false
802
827
  renderContext.listInitialMarkers = Boolean(descriptor.conditions)
803
- for (const item of node.values) {
804
- renderContext.listRoot = { id, state: node.items.id, descriptor, key: item[node.keyField], template: false, effects: [], item, rowIndexes: { s: 0, c: 0, l: 0 } }
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 } }
805
831
  renderContext.listRowRoot = renderContext.listRoot
806
- 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
807
837
  }
808
- if (!node.ownerField || ownerTemplate) renderContext.lists.push(descriptor)
809
838
  renderContext.hasBehaviors = true
810
839
  renderContext.hasLists = true
811
840
  const prototype = node.ownerField && !ownerTemplate ? "" : template
@@ -818,23 +847,24 @@ async function renderList(node, namespace, selectValue) {
818
847
  renderContext.listFields = previousListFields
819
848
  renderContext.listEffectOwners = previousListEffectOwners
820
849
  renderContext.listRowStates = previousListRowStates
850
+ renderContext.listRowRefs = previousListRowRefs
821
851
  renderContext.listRowConditions = previousListRowConditions
822
852
  renderContext.listRowLists = previousListRowLists
823
853
  renderContext.listDepth--
824
854
  }
825
855
  }
826
856
 
827
- function nextRowListId() {
857
+ function nextRowList() {
828
858
  const root = renderContext.listRoot ?? renderContext.listRowRoot
829
859
  const index = root.rowIndexes.l++
830
860
  if (renderContext.listTemplate) {
831
- const id = nextRenderId("l")
832
- renderContext.listRowLists[index] = { id }
833
- return id
861
+ const entry = renderContext.listRowLists[index] ?? { id: nextRenderId("l"), rowLists: [], rowStates: [], rowRefs: [], rowConditions: [], effectOwners: [], planned: false }
862
+ renderContext.listRowLists[index] = entry
863
+ return entry
834
864
  }
835
865
  const entry = renderContext.listRowLists[index]
836
866
  if (!entry) throw new Error("Nested keyed lists must have the same order for every parent item")
837
- return entry.id
867
+ return entry
838
868
  }
839
869
 
840
870
  function nextRenderId(kind) {
@@ -846,19 +876,21 @@ function nextRenderId(kind) {
846
876
  function nextRowRenderId(kind, initialValue) {
847
877
  const root = renderContext.listRoot ?? renderContext.listRowRoot
848
878
  const index = root.rowIndexes[kind]++
849
- const entries = kind === "s" ? renderContext.listRowStates : renderContext.listRowConditions
879
+ const entries = kind === "s" ? renderContext.listRowStates : kind === "r" ? renderContext.listRowRefs : renderContext.listRowConditions
850
880
  if (renderContext.listTemplate) {
881
+ const entry = entries[index]
882
+ if (entry) return entry.id
851
883
  const id = `${nextRenderId(kind)}:$k`
852
884
  entries[index] = kind === "s" ? { id, initialValue } : { id }
853
885
  return id
854
886
  }
855
887
  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)
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}`])
858
890
  }
859
891
 
860
- function rowRenderId(id, key) {
861
- return id.replace("$k", encodeURIComponent(`${typeof key}:${key}`))
892
+ function rowRenderId(id, path) {
893
+ return id.replace("$k", encodeURIComponent(path.join("/")))
862
894
  }
863
895
 
864
896
  function optionValue(props) {