@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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { browserState, mountDom, notifyListItem, registerCommitter, registerMountHook, registerUnmountHook, unmountDom } from "./shared-runtime.js"
|
|
2
|
+
import { selectCollection } from "./collection-selector.js"
|
|
2
3
|
|
|
3
4
|
const listTargets = new Map()
|
|
4
5
|
const listRegistrations = new WeakMap()
|
|
@@ -7,11 +8,15 @@ const imports = __KUDZU_LIST_ASYNC_PARTS__ ? new Map() : undefined
|
|
|
7
8
|
const revisions = __KUDZU_LIST_ASYNC_PARTS__ ? new WeakMap() : undefined
|
|
8
9
|
const itemParts = new WeakMap()
|
|
9
10
|
const listItems = new WeakMap()
|
|
11
|
+
const listIndexes = __KUDZU_LIST_INDEXES__ ? new WeakMap() : undefined
|
|
12
|
+
const ownershipPaths = __KUDZU_LIST_ROW_HOOKS__ ? new WeakMap() : undefined
|
|
13
|
+
const rowReplacements = __KUDZU_LIST_ROW_HOOKS__ ? new WeakMap() : undefined
|
|
10
14
|
const ownedLists = __KUDZU_NESTED_LISTS__ ? new WeakMap() : undefined
|
|
11
15
|
const conditionOwners = __KUDZU_LIST_CONDITIONS__ ? new WeakMap() : undefined
|
|
12
16
|
const conditionTemplates = __KUDZU_LIST_CONDITIONS__ ? new WeakMap() : undefined
|
|
13
17
|
const itemPartsSelector = `[data-k-list-text]${__KUDZU_LIST_ATTRIBUTES__ ? ",[data-k-list-attrs]" : ""}${__KUDZU_LIST_EVENTS__ ? ",[data-k-list-events]" : ""}${__KUDZU_LIST_EXPRESSIONS__ ? ",[data-k-list-expression]" : ""}${__KUDZU_LIST_EXPRESSION_ATTRIBUTES__ ? ",[data-k-list-expression-attrs]" : ""}${__KUDZU_LIST_CONDITIONS__ ? ",[data-k-list-condition]" : ""}${__KUDZU_LIST_EFFECTS__ ? ",[data-k-effects]" : ""}`
|
|
14
18
|
const childPrototypes = __KUDZU_NESTED_LISTS__ ? new WeakMap() : undefined
|
|
19
|
+
const itemPartPlans = __KUDZU_NESTED_LISTS__ ? new WeakMap() : undefined
|
|
15
20
|
|
|
16
21
|
function commitLists(id) {
|
|
17
22
|
const lists = listTargets.get(id)
|
|
@@ -31,7 +36,7 @@ if (typeof document !== "undefined") mountDom(document)
|
|
|
31
36
|
function mountLists(root) {
|
|
32
37
|
let owner = root.nodeType === Node.ELEMENT_NODE ? root : root.parentElement
|
|
33
38
|
while (owner && !listItems.has(owner)) owner = owner.parentElement
|
|
34
|
-
if (owner) fillListParts(root, listItemParts(root), listItems.get(owner), 0)
|
|
39
|
+
if (owner) fillListParts(root, listItemParts(root), listItems.get(owner), 0, __KUDZU_LIST_INDEXES__ ? listIndexes.get(owner) ?? 0 : 0)
|
|
35
40
|
for (const start of matching(root, "template[data-k-list]")) {
|
|
36
41
|
if (mountedLists.has(start)) continue
|
|
37
42
|
mountedLists.add(start)
|
|
@@ -40,10 +45,11 @@ function mountLists(root) {
|
|
|
40
45
|
const roots = listRoots(start, end)
|
|
41
46
|
const nested = __KUDZU_NESTED_LISTS__ ? mountNestedPrototype(start, descriptor, roots) : undefined
|
|
42
47
|
const templateRoot = __KUDZU_NESTED_LISTS__ ? nested.templateRoot : start.content.firstElementChild
|
|
43
|
-
if (
|
|
48
|
+
if (__KUDZU_LIST_ROW_HOOKS__) for (let index = 0; index < roots.length; index++) initializeGeneralRowHooks(descriptor, descriptor.keys[index], roots[index], nested?.owner)
|
|
44
49
|
const parts = listItemPartPlan(templateRoot, descriptor.nested)
|
|
45
50
|
for (const root of roots) {
|
|
46
51
|
if (__KUDZU_LIST_CONDITIONS__ && descriptor.conditions) {
|
|
52
|
+
if (__KUDZU_NESTED_LISTS__ && descriptor.ownerField) itemPartPlans.set(root, parts)
|
|
47
53
|
const initialParts = listItemParts(root, descriptor.nested)
|
|
48
54
|
mapListConditionTemplates(parts.conditions, initialParts.conditions)
|
|
49
55
|
} else mapListItemParts(parts, root, descriptor.nested)
|
|
@@ -53,10 +59,11 @@ function mountLists(root) {
|
|
|
53
59
|
const list = {
|
|
54
60
|
start,
|
|
55
61
|
descriptor,
|
|
56
|
-
...(__KUDZU_NESTED_LISTS__ ? { templateRoot, ...(nested.
|
|
62
|
+
...(__KUDZU_NESTED_LISTS__ ? { templateRoot, ...(nested.childPrototypes?.size ? { childPrototypes: nested.childPrototypes } : {}) } : {}),
|
|
57
63
|
parts,
|
|
58
64
|
seedFields: __KUDZU_LIST_SEEDS__ && descriptor.seed && Object.keys(descriptor.seed),
|
|
59
65
|
roots: new Map(roots.map((node, index) => [keyToken(descriptor.keys[index]), node])),
|
|
66
|
+
...(__KUDZU_LIST_STABLE_FAST_PATHS__ ? { orderedRoots: roots } : {}),
|
|
60
67
|
values: new Map(),
|
|
61
68
|
items: undefined,
|
|
62
69
|
container: roots[0]?.parentNode,
|
|
@@ -65,8 +72,10 @@ function mountLists(root) {
|
|
|
65
72
|
}
|
|
66
73
|
if (__KUDZU_NESTED_LISTS__ && descriptor.ownerField) {
|
|
67
74
|
if (!list.owner) throw new Error("Nested keyed list has no parent row")
|
|
68
|
-
|
|
69
|
-
|
|
75
|
+
const lists = ownedLists.get(list.owner) ?? new Map()
|
|
76
|
+
if (lists.has(descriptor.id)) throw new Error(`Duplicate nested keyed list ID: ${descriptor.id}`)
|
|
77
|
+
lists.set(descriptor.id, list)
|
|
78
|
+
ownedLists.set(list.owner, lists)
|
|
70
79
|
listRegistrations.set(start, { list, owner: list.owner })
|
|
71
80
|
} else {
|
|
72
81
|
register(listTargets, descriptor.state, list)
|
|
@@ -84,41 +93,50 @@ function unregisterList(start) {
|
|
|
84
93
|
const registration = listRegistrations.get(start)
|
|
85
94
|
if (registration) {
|
|
86
95
|
if (__KUDZU_NESTED_LISTS__ && registration.owner) {
|
|
87
|
-
|
|
96
|
+
const lists = ownedLists.get(registration.owner)
|
|
97
|
+
if (lists?.get(registration.list.descriptor.id) === registration.list) lists.delete(registration.list.descriptor.id)
|
|
98
|
+
if (!lists?.size) ownedLists.delete(registration.owner)
|
|
88
99
|
} else {
|
|
89
100
|
const lists = listTargets.get(registration.state)
|
|
90
101
|
lists?.delete(registration.list)
|
|
91
102
|
if (!lists?.size) listTargets.delete(registration.state)
|
|
92
103
|
}
|
|
93
|
-
if (
|
|
104
|
+
if (__KUDZU_LIST_ROW_HOOKS__ && registration.list.descriptor.rowStates) for (const node of registration.list.roots.values()) deleteRowStates(registration.list.descriptor, ownershipPaths.get(node))
|
|
94
105
|
}
|
|
95
106
|
listRegistrations.delete(start)
|
|
96
107
|
mountedLists.delete(start)
|
|
97
108
|
}
|
|
98
109
|
|
|
99
110
|
function updateList(list) {
|
|
100
|
-
|
|
111
|
+
let items = __KUDZU_NESTED_LISTS__ && list.descriptor.ownerField
|
|
101
112
|
? listItems.get(list.owner)?.[list.descriptor.ownerField]
|
|
102
113
|
: browserState.get(list.descriptor.state)
|
|
114
|
+
if (__KUDZU_NESTED_LISTS__ && list.descriptor.ownerField && items == null) items = []
|
|
115
|
+
if (__KUDZU_COLLECTION_SELECTORS__) items = selectCollection(items, list.descriptor.selector)
|
|
103
116
|
if (!Array.isArray(items)) throw new Error(list.descriptor.ownerField ? `Nested keyed list property "${list.descriptor.ownerField}" must remain an array` : "Keyed list state must remain an array")
|
|
104
|
-
if (__KUDZU_NESTED_LISTS__ && (list.descriptor.
|
|
105
|
-
if (__KUDZU_NESTED_LISTS__ && list.descriptor.
|
|
106
|
-
if (list.descriptor.reducer && list.items && updateReducerList(list, items)) return
|
|
117
|
+
if (__KUDZU_NESTED_LISTS__ && (list.descriptor.children || list.descriptor.ownerField) && !list.descriptor.indexed && !list.descriptor.selector && list.descriptor.key !== null && list.items && updateNestedList(list, items)) return
|
|
118
|
+
if (__KUDZU_NESTED_LISTS__ && list.descriptor.children) validateChildLists(items, list.descriptor.children)
|
|
119
|
+
if (list.descriptor.reducer && !list.descriptor.selector && list.descriptor.key !== null && list.items && updateReducerList(list, items)) return
|
|
120
|
+
if (__KUDZU_LIST_STABLE_FAST_PATHS__ && list.descriptor.key !== null && !list.descriptor.indexed && !list.descriptor.selector && !list.descriptor.reducer && !list.descriptor.children && !list.descriptor.ownerField && list.items && updateStableList(list, items)) return
|
|
107
121
|
const entries = []
|
|
108
122
|
const keys = new Set()
|
|
109
123
|
const seen = new Set()
|
|
110
|
-
|
|
111
|
-
|
|
124
|
+
const referenceOnly = usesItemReferences(list)
|
|
125
|
+
for (const [index, item] of items.entries()) {
|
|
126
|
+
const key = list.descriptor.key === null ? index : item?.[list.descriptor.key]
|
|
112
127
|
if (!validListKey(key)) throw new Error(`Keyed list key "${list.descriptor.key}" must be a string or finite number`)
|
|
113
128
|
assertListItem(item)
|
|
114
|
-
const seededValue = __KUDZU_LIST_SEEDS__ ? list.seedFields && seededListValue(item, list.seedFields, list.descriptor.seed) : undefined
|
|
129
|
+
const seededValue = __KUDZU_LIST_SEEDS__ ? list.seedFields && seededListValue(item, list.seedFields, list.descriptor.seed, !referenceOnly) : undefined
|
|
115
130
|
if (seededValue === undefined) assertListValue(item, seen, true)
|
|
116
131
|
const token = keyToken(key)
|
|
117
132
|
if (keys.has(token)) throw new Error(`Duplicate keyed list key: ${String(key)}`)
|
|
118
133
|
keys.add(token)
|
|
119
|
-
entries.push({ item, key, token, value: seededValue === undefined ? JSON.stringify(item) : seededValue })
|
|
134
|
+
entries.push({ item, index, key, token, value: referenceOnly ? item : seededValue === undefined ? JSON.stringify(item) : seededValue })
|
|
120
135
|
}
|
|
121
|
-
if (list.
|
|
136
|
+
if (list.descriptor.key !== null && !list.descriptor.indexed && !list.descriptor.selector && (referenceOnly || list.values.size) && entries.every(entry => {
|
|
137
|
+
const root = list.roots.get(entry.token)
|
|
138
|
+
return !root || (referenceOnly ? listItems.get(root) === entry.item : list.values.get(entry.token) === entry.value)
|
|
139
|
+
})) {
|
|
122
140
|
const currentTokens = [...list.roots.keys()]
|
|
123
141
|
const nextTokens = entries.map(entry => entry.token)
|
|
124
142
|
if (nextTokens.length === currentTokens.length && nextTokens.every((token, index) => token === currentTokens[currentTokens.length - index - 1])) {
|
|
@@ -127,6 +145,7 @@ function updateList(list) {
|
|
|
127
145
|
reordered.append(...nextTokens.map(token => list.roots.get(token)))
|
|
128
146
|
parent.insertBefore(reordered, list.boundary)
|
|
129
147
|
list.roots = new Map(nextTokens.map(token => [token, list.roots.get(token)]))
|
|
148
|
+
if (__KUDZU_LIST_STABLE_FAST_PATHS__) list.orderedRoots = nextTokens.map(token => list.roots.get(token))
|
|
130
149
|
list.container ??= parent
|
|
131
150
|
return
|
|
132
151
|
}
|
|
@@ -136,12 +155,14 @@ function updateList(list) {
|
|
|
136
155
|
if (removed && nextTokens.every((token, index) => token === currentTokens[index >= removedIndex ? index + 1 : index])) {
|
|
137
156
|
removeListRoot(list, removed)
|
|
138
157
|
list.roots = new Map(nextTokens.map(token => [token, list.roots.get(token)]))
|
|
158
|
+
if (__KUDZU_LIST_STABLE_FAST_PATHS__) list.orderedRoots = nextTokens.map(token => list.roots.get(token))
|
|
139
159
|
return
|
|
140
160
|
}
|
|
141
161
|
}
|
|
142
162
|
if (nextTokens.length === currentTokens.length + 1 && currentTokens.every((token, index) => token === nextTokens[index])) {
|
|
143
163
|
const entry = entries.at(-1)
|
|
144
164
|
addListRoot(list, entry)
|
|
165
|
+
list.items = items
|
|
145
166
|
return
|
|
146
167
|
}
|
|
147
168
|
}
|
|
@@ -150,25 +171,25 @@ function updateList(list) {
|
|
|
150
171
|
const parent = list.container ?? list.start.parentNode
|
|
151
172
|
const additions = parent.ownerDocument.createDocumentFragment()
|
|
152
173
|
let added = false
|
|
153
|
-
for (const { item, key, token, value } of entries) {
|
|
174
|
+
for (const { item, index, key, token, value } of entries) {
|
|
154
175
|
let node = list.roots.get(token)
|
|
155
176
|
if (!node) {
|
|
156
177
|
node = (__KUDZU_NESTED_LISTS__ ? list.templateRoot : list.start.content.firstElementChild)?.cloneNode(true)
|
|
157
178
|
if (node?.dataset.kListRoot !== list.descriptor.id) node = undefined
|
|
158
179
|
if (!node) throw new Error("Keyed list template has no root element")
|
|
159
180
|
node.removeAttribute("data-k-list-root")
|
|
160
|
-
if (__KUDZU_NESTED_LISTS__ && list.
|
|
161
|
-
if (
|
|
181
|
+
if (__KUDZU_NESTED_LISTS__ && list.childPrototypes) childPrototypes.set(node, list.childPrototypes)
|
|
182
|
+
if (__KUDZU_LIST_ROW_HOOKS__) initializeGeneralRowHooks(list.descriptor, key, node, list.owner)
|
|
162
183
|
mapListItemParts(list.parts, node, list.descriptor.nested)
|
|
163
|
-
fillListItem(node, item, list.descriptor.nested)
|
|
184
|
+
fillListItem(node, item, list.descriptor.nested, index)
|
|
164
185
|
additions.append(node)
|
|
165
186
|
added = true
|
|
166
|
-
} else if (list.values.get(token) !== value) {
|
|
167
|
-
fillListItem(node, item, list.descriptor.nested)
|
|
187
|
+
} else if ((referenceOnly ? listItems.get(node) !== item : list.values.get(token) !== value) || list.descriptor.indexed || list.descriptor.key === null) {
|
|
188
|
+
fillListItem(node, item, list.descriptor.nested, index)
|
|
168
189
|
if (__KUDZU_LIST_ITEM_HOOKS__) notifyListItem(list.descriptor.state, node)
|
|
169
190
|
}
|
|
170
191
|
next.push([token, node])
|
|
171
|
-
values.set(token, value)
|
|
192
|
+
if (!referenceOnly) values.set(token, value)
|
|
172
193
|
}
|
|
173
194
|
for (const [token, node] of list.roots) {
|
|
174
195
|
if (keys.has(token)) continue
|
|
@@ -176,7 +197,7 @@ function updateList(list) {
|
|
|
176
197
|
unmountDom(node)
|
|
177
198
|
node.remove()
|
|
178
199
|
} else node.remove()
|
|
179
|
-
if (
|
|
200
|
+
if (__KUDZU_LIST_ROW_HOOKS__ && list.descriptor.rowStates) deleteRowStates(list.descriptor, ownershipPaths.get(node))
|
|
180
201
|
}
|
|
181
202
|
if (added) {
|
|
182
203
|
if (__KUDZU_LIST_MOUNTS__ && list.descriptor.mount) {
|
|
@@ -206,10 +227,102 @@ function updateList(list) {
|
|
|
206
227
|
}
|
|
207
228
|
}
|
|
208
229
|
list.roots = new Map(next)
|
|
230
|
+
if (__KUDZU_LIST_STABLE_FAST_PATHS__) list.orderedRoots = next.map(([, node]) => node)
|
|
209
231
|
list.values = values
|
|
210
232
|
list.items = items
|
|
211
233
|
}
|
|
212
234
|
|
|
235
|
+
/* stable-list-fast-path */
|
|
236
|
+
function updateStableList(list, items) {
|
|
237
|
+
const previous = list.items
|
|
238
|
+
const referenceOnly = usesItemReferences(list)
|
|
239
|
+
if (items.length < previous.length) return false
|
|
240
|
+
const appending = items.length > previous.length
|
|
241
|
+
const keys = new Set()
|
|
242
|
+
const seen = new Set()
|
|
243
|
+
const keyField = list.descriptor.key
|
|
244
|
+
const fragment = list.start.ownerDocument.createDocumentFragment()
|
|
245
|
+
let stable = true
|
|
246
|
+
for (let index = 0; index < items.length; index++) {
|
|
247
|
+
const item = items[index]
|
|
248
|
+
assertListItem(item)
|
|
249
|
+
const seededValue = __KUDZU_LIST_SEEDS__ ? list.seedFields && seededListValue(item, list.seedFields, list.descriptor.seed, !referenceOnly) : undefined
|
|
250
|
+
if (seededValue === undefined) assertListValue(item, seen, true)
|
|
251
|
+
const key = item[keyField]
|
|
252
|
+
if (!validListKey(key)) throw new Error(`Keyed list key "${keyField}" must be a string or finite number`)
|
|
253
|
+
const token = keyToken(key)
|
|
254
|
+
if (keys.has(token)) throw new Error(`Duplicate keyed list key: ${String(key)}`)
|
|
255
|
+
keys.add(token)
|
|
256
|
+
if (index < previous.length && (key !== previous[index][keyField] || appending && item !== previous[index])) stable = false
|
|
257
|
+
if (!stable || items.length === previous.length + 1 || index < previous.length) continue
|
|
258
|
+
let node = (__KUDZU_NESTED_LISTS__ ? list.templateRoot : list.start.content.firstElementChild)?.cloneNode(true)
|
|
259
|
+
if (node?.dataset.kListRoot !== list.descriptor.id) node = undefined
|
|
260
|
+
if (!node) throw new Error("Keyed list template has no root element")
|
|
261
|
+
node.removeAttribute("data-k-list-root")
|
|
262
|
+
if (__KUDZU_LIST_ROW_HOOKS__) initializeGeneralRowHooks(list.descriptor, key, node, list.owner)
|
|
263
|
+
if (list.parts.directFill) fillStructuralListParts(list.parts, node, item)
|
|
264
|
+
else fillListItem(node, item, list.descriptor.nested, index, mapListItemParts(list.parts, node, list.descriptor.nested))
|
|
265
|
+
fragment.append(node)
|
|
266
|
+
}
|
|
267
|
+
if (!stable) return false
|
|
268
|
+
if (appending) {
|
|
269
|
+
if (items.length === previous.length + 1) {
|
|
270
|
+
const item = items.at(-1)
|
|
271
|
+
const key = item[keyField]
|
|
272
|
+
const token = keyToken(key)
|
|
273
|
+
const seededValue = __KUDZU_LIST_SEEDS__ ? list.seedFields && seededListValue(item, list.seedFields, list.descriptor.seed, !referenceOnly) : undefined
|
|
274
|
+
addListRoot(list, { item, index: previous.length, key, token, value: referenceOnly ? item : seededValue === undefined ? JSON.stringify(item) : seededValue })
|
|
275
|
+
if (referenceOnly) list.values.clear()
|
|
276
|
+
list.items = items
|
|
277
|
+
return true
|
|
278
|
+
}
|
|
279
|
+
const parent = list.container ?? list.start.parentNode
|
|
280
|
+
let node = fragment.firstChild
|
|
281
|
+
for (let index = previous.length; node; index++) {
|
|
282
|
+
const item = items[index]
|
|
283
|
+
const token = keyToken(item[keyField])
|
|
284
|
+
list.roots.set(token, node)
|
|
285
|
+
list.orderedRoots.push(node)
|
|
286
|
+
if (!referenceOnly) {
|
|
287
|
+
const seededValue = __KUDZU_LIST_SEEDS__ ? list.seedFields && seededListValue(item, list.seedFields, list.descriptor.seed) : undefined
|
|
288
|
+
list.values.set(token, seededValue === undefined ? JSON.stringify(item) : seededValue)
|
|
289
|
+
}
|
|
290
|
+
node = node.nextSibling
|
|
291
|
+
}
|
|
292
|
+
const firstAdded = fragment.firstChild
|
|
293
|
+
parent.insertBefore(fragment, list.boundary)
|
|
294
|
+
if (referenceOnly) list.values.clear()
|
|
295
|
+
if (__KUDZU_LIST_MOUNTS__ && list.descriptor.mount) for (let node = firstAdded; node !== list.boundary; node = node.nextSibling) mountDom(node)
|
|
296
|
+
list.container ??= parent
|
|
297
|
+
list.items = items
|
|
298
|
+
return true
|
|
299
|
+
}
|
|
300
|
+
if (referenceOnly) {
|
|
301
|
+
for (let index = 0; index < items.length; index++) {
|
|
302
|
+
const item = items[index]
|
|
303
|
+
if (item !== previous[index]) fillListItem(list.orderedRoots[index], item, list.descriptor.nested, index)
|
|
304
|
+
}
|
|
305
|
+
} else {
|
|
306
|
+
for (let index = 0; index < items.length; index++) {
|
|
307
|
+
const item = items[index]
|
|
308
|
+
if (item === previous[index]) continue
|
|
309
|
+
const token = keyToken(item[keyField])
|
|
310
|
+
const seededValue = __KUDZU_LIST_SEEDS__ ? list.seedFields && seededListValue(item, list.seedFields, list.descriptor.seed) : undefined
|
|
311
|
+
const value = seededValue === undefined ? JSON.stringify(item) : seededValue
|
|
312
|
+
if (list.values.get(token) !== value) {
|
|
313
|
+
const node = list.orderedRoots[index]
|
|
314
|
+
fillListItem(node, item, list.descriptor.nested, index)
|
|
315
|
+
if (__KUDZU_LIST_ITEM_HOOKS__) notifyListItem(list.descriptor.state, node)
|
|
316
|
+
list.values.set(token, value)
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (referenceOnly) list.values.clear()
|
|
321
|
+
list.items = items
|
|
322
|
+
return true
|
|
323
|
+
}
|
|
324
|
+
/* stable-list-fast-path-end */
|
|
325
|
+
|
|
213
326
|
function updateNestedList(list, items) {
|
|
214
327
|
const previous = list.items
|
|
215
328
|
if (items === previous) return false
|
|
@@ -274,7 +387,7 @@ function nestedListEntry(list, item) {
|
|
|
274
387
|
const key = item?.[list.descriptor.key]
|
|
275
388
|
if (!validListKey(key)) throw new Error(`Keyed list key "${list.descriptor.key}" must be a string or finite number`)
|
|
276
389
|
assertListItem(item)
|
|
277
|
-
if (list.descriptor.
|
|
390
|
+
if (list.descriptor.children) validateChildLists([item], list.descriptor.children)
|
|
278
391
|
assertListValue(item, new Set(), true)
|
|
279
392
|
return { item, key, token: keyToken(key), value: JSON.stringify(item) }
|
|
280
393
|
}
|
|
@@ -326,20 +439,21 @@ function updateReducerList(list, items) {
|
|
|
326
439
|
return false
|
|
327
440
|
}
|
|
328
441
|
|
|
329
|
-
function addListRoot(list, { item, key, token, value }) {
|
|
442
|
+
function addListRoot(list, { item, index = list.roots.size, key, token, value }) {
|
|
330
443
|
let node = (__KUDZU_NESTED_LISTS__ ? list.templateRoot : list.start.content.firstElementChild)?.cloneNode(true)
|
|
331
444
|
if (node?.dataset.kListRoot !== list.descriptor.id) node = undefined
|
|
332
445
|
if (!node) throw new Error("Keyed list template has no root element")
|
|
333
446
|
node.removeAttribute("data-k-list-root")
|
|
334
|
-
if (__KUDZU_NESTED_LISTS__ && list.
|
|
335
|
-
if (
|
|
447
|
+
if (__KUDZU_NESTED_LISTS__ && list.childPrototypes) childPrototypes.set(node, list.childPrototypes)
|
|
448
|
+
if (__KUDZU_LIST_ROW_HOOKS__) initializeGeneralRowHooks(list.descriptor, key, node, list.owner)
|
|
336
449
|
mapListItemParts(list.parts, node, list.descriptor.nested)
|
|
337
|
-
fillListItem(node, item, list.descriptor.nested)
|
|
450
|
+
fillListItem(node, item, list.descriptor.nested, index)
|
|
338
451
|
const parent = list.container ?? list.start.parentNode
|
|
339
452
|
parent.insertBefore(node, list.boundary)
|
|
340
453
|
if (__KUDZU_LIST_MOUNTS__ && list.descriptor.mount) mountDom(node)
|
|
341
454
|
list.roots.set(token, node)
|
|
342
|
-
list.
|
|
455
|
+
if (__KUDZU_LIST_STABLE_FAST_PATHS__) list.orderedRoots.push(node)
|
|
456
|
+
if (!usesItemReferences(list)) list.values.set(token, value)
|
|
343
457
|
list.container ??= parent
|
|
344
458
|
}
|
|
345
459
|
|
|
@@ -347,24 +461,25 @@ function removeListRoot(list, token) {
|
|
|
347
461
|
const node = list.roots.get(token)
|
|
348
462
|
if (__KUDZU_LIST_MOUNTS__ && list.descriptor.mount) unmountDom(node)
|
|
349
463
|
node.remove()
|
|
350
|
-
if (
|
|
464
|
+
if (__KUDZU_LIST_ROW_HOOKS__ && list.descriptor.rowStates) deleteRowStates(list.descriptor, ownershipPaths.get(node))
|
|
351
465
|
list.roots.delete(token)
|
|
352
466
|
list.values.delete(token)
|
|
353
467
|
}
|
|
354
468
|
|
|
355
|
-
function fillListItem(root, item, nested = false) {
|
|
469
|
+
function fillListItem(root, item, nested = false, index = 0, parts = listItemParts(root, nested)) {
|
|
356
470
|
listItems.set(root, item)
|
|
471
|
+
if (__KUDZU_LIST_INDEXES__) listIndexes.set(root, index)
|
|
357
472
|
const revision = __KUDZU_LIST_ASYNC_PARTS__ ? (revisions.get(root) ?? 0) + 1 : 0
|
|
358
473
|
if (__KUDZU_LIST_ASYNC_PARTS__) revisions.set(root, revision)
|
|
359
|
-
|
|
360
|
-
fillListParts(root, parts, item, revision)
|
|
474
|
+
fillListParts(root, parts, item, revision, index)
|
|
361
475
|
if (__KUDZU_NESTED_LISTS__) {
|
|
362
|
-
const
|
|
363
|
-
if (child) updateList(child)
|
|
476
|
+
const children = ownedLists.get(root)
|
|
477
|
+
if (children) for (const child of children.values()) updateList(child)
|
|
364
478
|
}
|
|
479
|
+
if (__KUDZU_LIST_ROW_HOOKS__) replaceRowIds(root, rowReplacements.get(root))
|
|
365
480
|
}
|
|
366
481
|
|
|
367
|
-
function fillListParts(root, parts, item, revision) {
|
|
482
|
+
function fillListParts(root, parts, item, revision, index = 0) {
|
|
368
483
|
if (__KUDZU_LIST_EFFECTS__) for (const node of parts.effects) node.dataset.kEffectItem = JSON.stringify(item)
|
|
369
484
|
for (const [node, field] of parts.directTexts) {
|
|
370
485
|
const text = item?.[field]
|
|
@@ -387,31 +502,31 @@ function fillListParts(root, parts, item, revision) {
|
|
|
387
502
|
if (__KUDZU_LIST_EVENTS__) {
|
|
388
503
|
for (const [node, events] of parts.events) {
|
|
389
504
|
for (const [event, native] of JSON.parse(events)) {
|
|
390
|
-
native.scope = Object.fromEntries(Object.entries(native.scope).map(([name, value]) => [name, value?.type === "list-item" ? serializeItem(item) : value]))
|
|
505
|
+
native.scope = Object.fromEntries(Object.entries(native.scope).map(([name, value]) => [name, value?.type === "list-item" ? serializeItem(item) : value?.type === "list-index" ? index : value]))
|
|
391
506
|
node.dataset[`kNative${capitalize(event)}`] = JSON.stringify(native)
|
|
392
507
|
}
|
|
393
508
|
}
|
|
394
509
|
}
|
|
395
510
|
if (__KUDZU_LIST_EXPRESSIONS__) {
|
|
396
511
|
for (const [marker, descriptor] of parts.expressions) {
|
|
397
|
-
evaluate(descriptor, item).then(value => {
|
|
398
|
-
if (revisions.get(root) === revision &&
|
|
512
|
+
evaluate(descriptor, item, index).then(value => {
|
|
513
|
+
if (revisions.get(root) === revision && marker.isConnected) patchListText(marker, "template[data-k-list-expression-end]", value)
|
|
399
514
|
}).catch(error => console.error(error))
|
|
400
515
|
}
|
|
401
516
|
}
|
|
402
517
|
if (__KUDZU_LIST_EXPRESSION_ATTRIBUTES__) {
|
|
403
518
|
for (const [node, attributes] of parts.expressionAttributes) {
|
|
404
519
|
for (const [target, module, handler] of attributes) {
|
|
405
|
-
evaluate({ module, handler }, item).then(value => {
|
|
406
|
-
if (revisions.get(root) === revision &&
|
|
520
|
+
evaluate({ module, handler }, item, index).then(value => {
|
|
521
|
+
if (revisions.get(root) === revision && node.isConnected) patchBinding(node, target, value)
|
|
407
522
|
}).catch(error => console.error(error))
|
|
408
523
|
}
|
|
409
524
|
}
|
|
410
525
|
}
|
|
411
526
|
if (__KUDZU_LIST_CONDITIONS__) {
|
|
412
527
|
for (const [marker, descriptor] of parts.conditions) {
|
|
413
|
-
evaluate(descriptor, item).then(value => {
|
|
414
|
-
if (revisions.get(root) === revision &&
|
|
528
|
+
evaluate(descriptor, item, index).then(value => {
|
|
529
|
+
if (revisions.get(root) === revision && marker.isConnected) updateListCondition(marker, descriptor.kind, value, item, index)
|
|
415
530
|
}).catch(error => console.error(error))
|
|
416
531
|
}
|
|
417
532
|
}
|
|
@@ -423,7 +538,7 @@ function listItemParts(root, nested = false) {
|
|
|
423
538
|
parts = { directTexts: [], texts: [], attributes: [], events: [], expressions: [], expressionAttributes: [], conditions: [], effects: [] }
|
|
424
539
|
for (const node of nested ? ownedElements(root).filter(node => node.matches(itemPartsSelector)) : matching(root, itemPartsSelector)) {
|
|
425
540
|
if (node.hasAttribute("data-k-list-text")) (node.tagName === "TEMPLATE" ? parts.texts : parts.directTexts).push([node, node.dataset.kListText])
|
|
426
|
-
if (__KUDZU_LIST_ATTRIBUTES__ && node.hasAttribute("data-k-list-attrs")) parts.attributes.push([node, JSON.parse(node.dataset.kListAttrs)])
|
|
541
|
+
if (__KUDZU_LIST_ATTRIBUTES__ && node.hasAttribute("data-k-list-attrs")) parts.attributes.push([node, node.dataset.kListAttrs ? JSON.parse(node.dataset.kListAttrs) : undefined])
|
|
427
542
|
if (__KUDZU_LIST_EVENTS__ && node.hasAttribute("data-k-list-events")) parts.events.push([node, node.dataset.kListEvents])
|
|
428
543
|
if (__KUDZU_LIST_EXPRESSIONS__ && node.hasAttribute("data-k-list-expression")) parts.expressions.push([node, JSON.parse(node.dataset.kListExpression)])
|
|
429
544
|
if (__KUDZU_LIST_EXPRESSION_ATTRIBUTES__ && node.hasAttribute("data-k-list-expression-attrs")) parts.expressionAttributes.push([node, JSON.parse(node.dataset.kListExpressionAttrs)])
|
|
@@ -434,29 +549,71 @@ function listItemParts(root, nested = false) {
|
|
|
434
549
|
}
|
|
435
550
|
if (__KUDZU_LIST_EFFECTS__ && node.hasAttribute("data-k-effects")) parts.effects.push(node)
|
|
436
551
|
}
|
|
552
|
+
if (__KUDZU_NESTED_LISTS__) hydrateListItemParts(itemPartPlans.get(root), parts)
|
|
437
553
|
itemParts.set(root, parts)
|
|
438
554
|
return parts
|
|
439
555
|
}
|
|
440
556
|
|
|
557
|
+
function hydrateListItemParts(planned, initial) {
|
|
558
|
+
if (!planned) return
|
|
559
|
+
hydrateListItemPartValues(planned.directTexts, initial.directTexts, value => value === "")
|
|
560
|
+
if (__KUDZU_LIST_TEXT_RANGES__) hydrateListItemPartValues(planned.texts, initial.texts, value => value === "")
|
|
561
|
+
if (__KUDZU_LIST_ATTRIBUTES__) hydrateListItemPartValues(planned.attributes, initial.attributes, value => value === undefined)
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function hydrateListItemPartValues(planned, initial, missing) {
|
|
565
|
+
let index = 0
|
|
566
|
+
for (const entry of initial) {
|
|
567
|
+
if (!missing(entry[1])) continue
|
|
568
|
+
if (!planned[index]) throw new Error("Keyed list item markers do not match its template")
|
|
569
|
+
entry[1] = planned[index++][1]
|
|
570
|
+
}
|
|
571
|
+
if (index !== planned.length) throw new Error("Keyed list item markers do not match its template")
|
|
572
|
+
}
|
|
573
|
+
|
|
441
574
|
function listItemPartPlan(template, nested = false) {
|
|
442
575
|
const source = nested ? ownedElements(template) : [template, ...template.querySelectorAll("*")]
|
|
443
576
|
const indexes = new Map(source.map((node, index) => [node, index]))
|
|
444
577
|
const parts = listItemParts(template, nested)
|
|
578
|
+
const structural = !nested && !parts.texts.length && !parts.expressions.length && !parts.expressionAttributes.length && !parts.conditions.length && !parts.effects.length
|
|
579
|
+
const location = structural ? node => elementPath(template, node) : node => indexes.get(node)
|
|
445
580
|
return {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
581
|
+
structural,
|
|
582
|
+
directFill: structural && !parts.events.length && !__KUDZU_LIST_ASYNC_PARTS__ && !__KUDZU_NESTED_LISTS__ && !__KUDZU_LIST_INDEXES__ && !__KUDZU_LIST_ROW_HOOKS__,
|
|
583
|
+
directTexts: parts.directTexts.map(([node, field]) => [location(node), field]),
|
|
584
|
+
texts: __KUDZU_LIST_TEXT_RANGES__ ? parts.texts.map(([node, field]) => [location(node), field]) : [],
|
|
585
|
+
attributes: __KUDZU_LIST_ATTRIBUTES__ ? parts.attributes.map(([node, attributes]) => [location(node), attributes]) : [],
|
|
586
|
+
events: __KUDZU_LIST_EVENTS__ ? parts.events.map(([node, events]) => [location(node), events]) : [],
|
|
587
|
+
expressions: __KUDZU_LIST_EXPRESSIONS__ ? parts.expressions.map(([node, descriptor]) => [location(node), descriptor]) : [],
|
|
588
|
+
expressionAttributes: __KUDZU_LIST_EXPRESSION_ATTRIBUTES__ ? parts.expressionAttributes.map(([node, attributes]) => [location(node), attributes]) : [],
|
|
589
|
+
conditions: __KUDZU_LIST_CONDITIONS__ ? parts.conditions.map(([node, descriptor]) => [location(node), descriptor, node]) : [],
|
|
590
|
+
effects: __KUDZU_LIST_EFFECTS__ ? parts.effects.map(location) : []
|
|
454
591
|
}
|
|
455
592
|
}
|
|
456
593
|
|
|
457
594
|
function mapListItemParts(parts, root, nested = false) {
|
|
595
|
+
if (parts.structural) {
|
|
596
|
+
const target = path => {
|
|
597
|
+
if (!path.length) return root
|
|
598
|
+
let node = root.children[path[0]]
|
|
599
|
+
for (let index = 1; index < path.length; index++) node = node.children[path[index]]
|
|
600
|
+
return node
|
|
601
|
+
}
|
|
602
|
+
const mapped = {
|
|
603
|
+
directTexts: parts.directTexts.map(([path, field]) => [target(path), field]),
|
|
604
|
+
texts: [],
|
|
605
|
+
attributes: __KUDZU_LIST_ATTRIBUTES__ ? parts.attributes.map(([path, attributes]) => [target(path), attributes]) : [],
|
|
606
|
+
events: __KUDZU_LIST_EVENTS__ ? parts.events.map(([path, events]) => [target(path), events]) : [],
|
|
607
|
+
expressions: [],
|
|
608
|
+
expressionAttributes: [],
|
|
609
|
+
conditions: [],
|
|
610
|
+
effects: []
|
|
611
|
+
}
|
|
612
|
+
itemParts.set(root, mapped)
|
|
613
|
+
return mapped
|
|
614
|
+
}
|
|
458
615
|
const target = nested ? ownedElements(root) : [root, ...root.querySelectorAll("*")]
|
|
459
|
-
|
|
616
|
+
const mapped = {
|
|
460
617
|
directTexts: parts.directTexts.map(([index, field]) => [target[index], field]),
|
|
461
618
|
texts: __KUDZU_LIST_TEXT_RANGES__ ? parts.texts.map(([index, field]) => [target[index], field]) : [],
|
|
462
619
|
attributes: __KUDZU_LIST_ATTRIBUTES__ ? parts.attributes.map(([index, attributes]) => [target[index], attributes]) : [],
|
|
@@ -468,19 +625,73 @@ function mapListItemParts(parts, root, nested = false) {
|
|
|
468
625
|
return [target[index], descriptor]
|
|
469
626
|
}) : [],
|
|
470
627
|
effects: __KUDZU_LIST_EFFECTS__ ? parts.effects.map(index => target[index]) : []
|
|
471
|
-
}
|
|
628
|
+
}
|
|
629
|
+
itemParts.set(root, mapped)
|
|
630
|
+
return mapped
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function fillStructuralListParts(parts, root, item) {
|
|
634
|
+
for (let partIndex = 0; partIndex < parts.directTexts.length; partIndex++) {
|
|
635
|
+
const part = parts.directTexts[partIndex]
|
|
636
|
+
const path = part[0]
|
|
637
|
+
let node = root
|
|
638
|
+
for (let pathIndex = 0; pathIndex < path.length; pathIndex++) node = node.children[path[pathIndex]]
|
|
639
|
+
node.textContent = item[part[1]] ?? ""
|
|
640
|
+
}
|
|
641
|
+
if (__KUDZU_LIST_ATTRIBUTES__) for (let partIndex = 0; partIndex < parts.attributes.length; partIndex++) {
|
|
642
|
+
const part = parts.attributes[partIndex]
|
|
643
|
+
const path = part[0]
|
|
644
|
+
let node = root
|
|
645
|
+
for (let pathIndex = 0; pathIndex < path.length; pathIndex++) node = node.children[path[pathIndex]]
|
|
646
|
+
const attributes = part[1]
|
|
647
|
+
for (let index = 0; index < attributes.length; index++) patchBinding(node, attributes[index][0], item[attributes[index][1]])
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function elementPath(root, node) {
|
|
652
|
+
const path = []
|
|
653
|
+
while (node !== root) {
|
|
654
|
+
const parent = node.parentElement
|
|
655
|
+
path.push(Array.prototype.indexOf.call(parent.children, node))
|
|
656
|
+
node = parent
|
|
657
|
+
}
|
|
658
|
+
return path.reverse()
|
|
472
659
|
}
|
|
473
660
|
|
|
474
661
|
function mapListConditionTemplates(planned, initial) {
|
|
475
|
-
if (
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
662
|
+
if (!__KUDZU_DEEP_LIST_CONDITIONS__) {
|
|
663
|
+
if (planned.length !== initial.length) throw new Error("Keyed list condition markers do not match its template")
|
|
664
|
+
for (let index = 0; index < initial.length; index++) {
|
|
665
|
+
const marker = initial[index][0]
|
|
666
|
+
initial[index][1] ??= planned[index][1]
|
|
667
|
+
if (!marker.content.querySelector("template[data-k-list-true],template[data-k-list-false]")) conditionTemplates.set(marker, planned[index][2])
|
|
668
|
+
}
|
|
669
|
+
return
|
|
670
|
+
}
|
|
671
|
+
const templates = new Map()
|
|
672
|
+
const collect = root => {
|
|
673
|
+
for (const marker of root.querySelectorAll("template")) {
|
|
674
|
+
if (marker.hasAttribute("data-k-list-condition")) {
|
|
675
|
+
const descriptor = JSON.parse(marker.dataset.kListCondition)
|
|
676
|
+
templates.set(descriptor.handler, [descriptor, marker])
|
|
677
|
+
}
|
|
678
|
+
collect(marker.content)
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
for (const entry of planned) {
|
|
682
|
+
templates.set(entry[1].handler, [entry[1], entry[2]])
|
|
683
|
+
collect(entry[2].content)
|
|
684
|
+
}
|
|
685
|
+
for (const entry of initial) {
|
|
686
|
+
const marker = entry[0]
|
|
687
|
+
const template = templates.get(entry[1]?.handler ?? marker.dataset.kListConditionHandler)
|
|
688
|
+
if (!template) throw new Error("Keyed list condition marker has no matching template")
|
|
689
|
+
entry[1] ??= template[0]
|
|
690
|
+
if (!marker.content.querySelector("template[data-k-list-true],template[data-k-list-false]")) conditionTemplates.set(marker, template[1])
|
|
480
691
|
}
|
|
481
692
|
}
|
|
482
693
|
|
|
483
|
-
function updateListCondition(marker, kind, value, item) {
|
|
694
|
+
function updateListCondition(marker, kind, value, item, index) {
|
|
484
695
|
const current = listConditionKey(kind, value)
|
|
485
696
|
if (marker.dataset.kListCurrent === current) return
|
|
486
697
|
let end = marker.nextSibling
|
|
@@ -503,7 +714,7 @@ function updateListCondition(marker, kind, value, item) {
|
|
|
503
714
|
const nodes = [...fragment.childNodes]
|
|
504
715
|
const revision = (revisions.get(marker) ?? 0) + 1
|
|
505
716
|
revisions.set(marker, revision)
|
|
506
|
-
fillListParts(marker, listItemParts(fragment), item, revision)
|
|
717
|
+
fillListParts(marker, listItemParts(fragment), item, revision, index)
|
|
507
718
|
end.parentNode.insertBefore(fragment, end)
|
|
508
719
|
marker.dataset.kListCurrent = current
|
|
509
720
|
const owner = conditionOwners.get(marker)
|
|
@@ -519,19 +730,20 @@ function renderFalsy(value) {
|
|
|
519
730
|
return value === false || value == null || value === true ? "" : String(value)
|
|
520
731
|
}
|
|
521
732
|
|
|
522
|
-
function validateChildLists(items,
|
|
733
|
+
function validateChildLists(items, children) {
|
|
523
734
|
for (const item of items) {
|
|
524
|
-
const children
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
735
|
+
for (const child of children) {
|
|
736
|
+
const values = __KUDZU_COLLECTION_SELECTORS__ ? selectCollection(item?.[child.field], child.selector) : item?.[child.field] ?? []
|
|
737
|
+
const keys = new Set()
|
|
738
|
+
for (const entry of values) {
|
|
739
|
+
const key = child.key === null ? keys.size : entry?.[child.key]
|
|
740
|
+
if (!validListKey(key)) throw new Error(`Keyed list key "${child.key}" must be a string or finite number`)
|
|
741
|
+
assertListItem(entry)
|
|
742
|
+
assertListValue(entry, new Set(), true)
|
|
743
|
+
const token = keyToken(key)
|
|
744
|
+
if (keys.has(token)) throw new Error(`Duplicate keyed list key: ${String(key)}`)
|
|
745
|
+
keys.add(token)
|
|
746
|
+
}
|
|
535
747
|
}
|
|
536
748
|
}
|
|
537
749
|
}
|
|
@@ -544,24 +756,24 @@ function listOwner(start) {
|
|
|
544
756
|
|
|
545
757
|
function mountNestedPrototype(start, descriptor, roots) {
|
|
546
758
|
const owner = descriptor.ownerField ? listOwner(start) : undefined
|
|
547
|
-
const prototypeStart = owner ? childPrototypes.get(owner) : undefined
|
|
759
|
+
const prototypeStart = owner ? childPrototypes.get(owner)?.get(descriptor.id) : undefined
|
|
548
760
|
const templateRoot = prototypeStart?.content.firstElementChild ?? start.content.firstElementChild
|
|
549
761
|
if (!templateRoot) throw new Error("Nested keyed list has no shared row prototype")
|
|
550
|
-
const
|
|
551
|
-
if (
|
|
552
|
-
if (
|
|
762
|
+
const prototypes = descriptor.children && new Map(descriptor.children.map(child => [child.id, findChildPrototype(templateRoot, child.id)]))
|
|
763
|
+
if (prototypes && [...prototypes.values()].some(prototype => !prototype)) throw new Error("Keyed list template has no nested row prototype")
|
|
764
|
+
if (prototypes?.size) for (const root of roots) childPrototypes.set(root, prototypes)
|
|
553
765
|
if (prototypeStart && start !== prototypeStart) start.content.replaceChildren()
|
|
554
|
-
return { owner,
|
|
766
|
+
return { owner, childPrototypes: prototypes, templateRoot }
|
|
555
767
|
}
|
|
556
768
|
|
|
557
|
-
function findChildPrototype(root,
|
|
769
|
+
function findChildPrototype(root, id) {
|
|
558
770
|
for (const element of root.children) {
|
|
559
771
|
if (element.matches("template[data-k-list]")) {
|
|
560
|
-
if (JSON.parse(element.dataset.kList).
|
|
561
|
-
const nested = findChildPrototype(element.content,
|
|
772
|
+
if (JSON.parse(element.dataset.kList).id === id) return element
|
|
773
|
+
const nested = findChildPrototype(element.content, id)
|
|
562
774
|
if (nested) return nested
|
|
563
775
|
} else {
|
|
564
|
-
const nested = findChildPrototype(element,
|
|
776
|
+
const nested = findChildPrototype(element, id)
|
|
565
777
|
if (nested) return nested
|
|
566
778
|
}
|
|
567
779
|
}
|
|
@@ -633,14 +845,14 @@ function patchListText(marker, endSelector, value) {
|
|
|
633
845
|
end.before(marker.ownerDocument.createTextNode(text))
|
|
634
846
|
}
|
|
635
847
|
|
|
636
|
-
function evaluate(descriptor, item) {
|
|
848
|
+
function evaluate(descriptor, item, index) {
|
|
637
849
|
let module = imports.get(descriptor.module)
|
|
638
850
|
if (!module) {
|
|
639
851
|
module = import(descriptor.module)
|
|
640
852
|
imports.set(descriptor.module, module)
|
|
641
853
|
}
|
|
642
854
|
return module.then(exports => {
|
|
643
|
-
const value = exports[descriptor.handler](item)
|
|
855
|
+
const value = exports[descriptor.handler](item, index)
|
|
644
856
|
if (value && typeof value.then === "function") throw new Error("Derived keyed list item expressions must return synchronous values")
|
|
645
857
|
return value
|
|
646
858
|
})
|
|
@@ -668,7 +880,8 @@ function patchBinding(node, target, value) {
|
|
|
668
880
|
} else if (value == null || (value === false && !isStringBooleanAttribute(target))) {
|
|
669
881
|
node.removeAttribute(target)
|
|
670
882
|
} else {
|
|
671
|
-
|
|
883
|
+
const next = value === true && !isStringBooleanAttribute(target) ? "" : String(value)
|
|
884
|
+
if (node.getAttribute(target) !== next) node.setAttribute(target, next)
|
|
672
885
|
}
|
|
673
886
|
}
|
|
674
887
|
|
|
@@ -676,27 +889,56 @@ function keyToken(key) {
|
|
|
676
889
|
return `${typeof key}:${key}`
|
|
677
890
|
}
|
|
678
891
|
|
|
892
|
+
/* general-row-hooks */
|
|
893
|
+
function initializeGeneralRowHooks(descriptor, key, root, owner) {
|
|
894
|
+
const token = keyToken(key)
|
|
895
|
+
const path = [...(ownershipPaths.get(owner) ?? []), `${descriptor.id}=${token}`]
|
|
896
|
+
ownershipPaths.set(root, path)
|
|
897
|
+
const statePath = descriptor.ownerField ? path : [token]
|
|
898
|
+
root.dataset.kRowPath = encodeURIComponent(statePath.join("/"))
|
|
899
|
+
const replacements = new Map()
|
|
900
|
+
for (const state of descriptor.rowStates ?? []) {
|
|
901
|
+
const id = rowStateId(state.id, statePath)
|
|
902
|
+
if (!browserState.has(id)) browserState.set(id, __KUDZU_COMPLEX_LIST_ROW_STATE__ && state.initialValue !== null && typeof state.initialValue === "object" ? structuredClone(state.initialValue) : state.initialValue)
|
|
903
|
+
replacements.set(state.id, id)
|
|
904
|
+
}
|
|
905
|
+
if (__KUDZU_LIST_ROW_REFS__) for (const ref of descriptor.rowRefs ?? []) replacements.set(ref, rowStateId(ref, statePath))
|
|
906
|
+
for (const marker of descriptor.rowConditions ?? []) replacements.set(marker, rowStateId(marker, statePath))
|
|
907
|
+
rowReplacements.set(root, replacements)
|
|
908
|
+
replaceRowIds(root, replacements)
|
|
909
|
+
}
|
|
910
|
+
/* general-row-hooks-end */
|
|
911
|
+
|
|
679
912
|
function initializeRowStates(descriptor, key, root) {
|
|
680
913
|
const token = keyToken(key)
|
|
681
914
|
const replacements = new Map()
|
|
682
915
|
for (const state of descriptor.rowStates) {
|
|
683
|
-
const id =
|
|
916
|
+
const id = flatRowStateId(state.id, token)
|
|
684
917
|
if (!browserState.has(id)) browserState.set(id, state.initialValue)
|
|
685
918
|
replacements.set(state.id, id)
|
|
686
919
|
}
|
|
687
|
-
for (const marker of descriptor.rowConditions ?? []) replacements.set(marker,
|
|
920
|
+
for (const marker of descriptor.rowConditions ?? []) replacements.set(marker, flatRowStateId(marker, token))
|
|
688
921
|
if (root) replaceRowIds(root, replacements)
|
|
689
922
|
}
|
|
690
923
|
|
|
691
|
-
function
|
|
692
|
-
for (const state of descriptor.rowStates) browserState.delete(
|
|
924
|
+
function deleteFlatRowStates(descriptor, token) {
|
|
925
|
+
for (const state of descriptor.rowStates) browserState.delete(flatRowStateId(state.id, token))
|
|
693
926
|
}
|
|
694
927
|
|
|
695
|
-
function
|
|
928
|
+
function flatRowStateId(id, token) {
|
|
696
929
|
return id.replace("$k", encodeURIComponent(token))
|
|
697
930
|
}
|
|
698
931
|
|
|
932
|
+
function deleteRowStates(descriptor, path) {
|
|
933
|
+
const statePath = descriptor.ownerField ? path : [path.at(-1).slice(path.at(-1).indexOf("=") + 1)]
|
|
934
|
+
for (const state of descriptor.rowStates) browserState.delete(rowStateId(state.id, statePath))
|
|
935
|
+
}
|
|
936
|
+
function rowStateId(id, path) {
|
|
937
|
+
return id.replace("$k", encodeURIComponent(path.join("/")))
|
|
938
|
+
}
|
|
939
|
+
|
|
699
940
|
function replaceRowIds(root, replacements) {
|
|
941
|
+
if (!replacements) return
|
|
700
942
|
const replace = node => {
|
|
701
943
|
for (const attribute of [...node.attributes]) {
|
|
702
944
|
if (!attribute.name.startsWith("data-k-")) continue
|
|
@@ -719,7 +961,7 @@ function assertListItem(item) {
|
|
|
719
961
|
if (!item || Array.isArray(item) || prototype !== Object.prototype) throw new Error("Keyed list items must be ordinary plain objects")
|
|
720
962
|
}
|
|
721
963
|
|
|
722
|
-
function seededListValue(item, fields, seed) {
|
|
964
|
+
function seededListValue(item, fields, seed, serialize = true) {
|
|
723
965
|
const keys = Reflect.ownKeys(item)
|
|
724
966
|
if (fields.length === 1) {
|
|
725
967
|
const field = fields[0]
|
|
@@ -729,10 +971,10 @@ function seededListValue(item, fields, seed) {
|
|
|
729
971
|
if (!("value" in descriptor)) throw new Error("Keyed list items must not contain accessors")
|
|
730
972
|
const value = descriptor.value
|
|
731
973
|
const type = value === null ? "null" : typeof value
|
|
732
|
-
return type === seed[field] && !(type === "number" && (!Number.isFinite(value) || Object.is(value, -0))) ? value : undefined
|
|
974
|
+
return type === seed[field] && !(type === "number" && (!Number.isFinite(value) || Object.is(value, -0))) ? serialize ? value : true : undefined
|
|
733
975
|
}
|
|
734
976
|
if (keys.length !== fields.length || keys.some(key => typeof key !== "string" || !fields.includes(key))) return undefined
|
|
735
|
-
const values = []
|
|
977
|
+
const values = serialize ? [] : undefined
|
|
736
978
|
for (const field of fields) {
|
|
737
979
|
const descriptor = Object.getOwnPropertyDescriptor(item, field)
|
|
738
980
|
if (!descriptor.enumerable) throw new Error("Keyed list items must not contain non-enumerable properties")
|
|
@@ -740,9 +982,13 @@ function seededListValue(item, fields, seed) {
|
|
|
740
982
|
const value = descriptor.value
|
|
741
983
|
const type = value === null ? "null" : typeof value
|
|
742
984
|
if (type !== seed[field] || type === "number" && (!Number.isFinite(value) || Object.is(value, -0))) return undefined
|
|
743
|
-
values.push(value)
|
|
985
|
+
if (serialize) values.push(value)
|
|
744
986
|
}
|
|
745
|
-
return values.length === 1 ? values[0] : JSON.stringify(values)
|
|
987
|
+
return serialize ? values.length === 1 ? values[0] : JSON.stringify(values) : true
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
function usesItemReferences(list) {
|
|
991
|
+
return !list.descriptor.mount && !list.descriptor.effects && !list.descriptor.events && !list.descriptor.expressions && !list.descriptor.expressionAttributes && !list.descriptor.conditions
|
|
746
992
|
}
|
|
747
993
|
|
|
748
994
|
function assertListValue(value, seen, root = false) {
|