@kudzujs/core 0.7.6 → 0.7.9
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 +75 -146
- package/GOAL_B.md +56 -92
- package/README.md +18 -13
- package/RELEASES.md +73 -0
- package/framework/README.md +3 -3
- package/framework/build.mjs +54 -26
- package/framework/collection-selector.js +23 -19
- package/framework/core.d.ts +2 -2
- package/framework/core.mjs +17 -5
- package/framework/dev-state.js +3 -3
- package/framework/list-runtime.js +123 -18
- package/package.json +1 -1
|
@@ -47,6 +47,7 @@ function mountLists(root) {
|
|
|
47
47
|
const templateRoot = __KUDZU_NESTED_LISTS__ ? nested.templateRoot : start.content.firstElementChild
|
|
48
48
|
if (__KUDZU_LIST_ROW_HOOKS__) for (let index = 0; index < roots.length; index++) initializeGeneralRowHooks(descriptor, descriptor.keys[index], roots[index], nested?.owner)
|
|
49
49
|
const parts = listItemPartPlan(templateRoot, descriptor.nested)
|
|
50
|
+
const staticRows = __KUDZU_STATIC_COLLECTIONS__ && descriptor.static && parts.directFill ? new Map() : undefined
|
|
50
51
|
for (const root of roots) {
|
|
51
52
|
if (__KUDZU_LIST_CONDITIONS__ && descriptor.conditions) {
|
|
52
53
|
if (__KUDZU_NESTED_LISTS__ && descriptor.ownerField) itemPartPlans.set(root, parts)
|
|
@@ -56,11 +57,16 @@ function mountLists(root) {
|
|
|
56
57
|
}
|
|
57
58
|
if (__KUDZU_LIST_SEEDS__ && descriptor.seed && !browserState.has(descriptor.state)) browserState.set(descriptor.state, roots.map((root, index) => seedListItem(root, descriptor, index)))
|
|
58
59
|
const items = browserState.get(descriptor.state)
|
|
60
|
+
const staticEntries = __KUDZU_STATIC_COLLECTIONS__ && descriptor.static && descriptor.key !== null && !descriptor.indexed && descriptor.selector?.every(operation => operation[0] === "filter") && parts.directFill
|
|
61
|
+
? cacheStaticListEntries(descriptor, items)
|
|
62
|
+
: undefined
|
|
59
63
|
const list = {
|
|
60
64
|
start,
|
|
61
65
|
descriptor,
|
|
62
66
|
...(__KUDZU_NESTED_LISTS__ ? { templateRoot, ...(nested.childPrototypes?.size ? { childPrototypes: nested.childPrototypes } : {}) } : {}),
|
|
63
67
|
parts,
|
|
68
|
+
...(__KUDZU_STATIC_COLLECTIONS__ && staticRows ? { staticRows } : {}),
|
|
69
|
+
...(__KUDZU_STATIC_COLLECTIONS__ && staticEntries ? { staticEntries, staticPositions: staticEntries.positions } : {}),
|
|
64
70
|
seedFields: __KUDZU_LIST_SEEDS__ && descriptor.seed && Object.keys(descriptor.seed),
|
|
65
71
|
roots: new Map(roots.map((node, index) => [keyToken(descriptor.keys[index]), node])),
|
|
66
72
|
...(__KUDZU_LIST_STABLE_FAST_PATHS__ ? { orderedRoots: roots } : {}),
|
|
@@ -78,8 +84,14 @@ function mountLists(root) {
|
|
|
78
84
|
ownedLists.set(list.owner, lists)
|
|
79
85
|
listRegistrations.set(start, { list, owner: list.owner })
|
|
80
86
|
} else {
|
|
81
|
-
|
|
82
|
-
|
|
87
|
+
if (__KUDZU_COLLECTION_SELECTORS__ && descriptor.selectorStates) {
|
|
88
|
+
const states = [...new Set([descriptor.state, ...Object.values(descriptor.selectorStates)])]
|
|
89
|
+
for (const state of states) register(listTargets, state, list)
|
|
90
|
+
listRegistrations.set(start, { states, list })
|
|
91
|
+
} else {
|
|
92
|
+
register(listTargets, descriptor.state, list)
|
|
93
|
+
listRegistrations.set(start, { state: descriptor.state, list })
|
|
94
|
+
}
|
|
83
95
|
}
|
|
84
96
|
updateList(list)
|
|
85
97
|
}
|
|
@@ -97,9 +109,17 @@ function unregisterList(start) {
|
|
|
97
109
|
if (lists?.get(registration.list.descriptor.id) === registration.list) lists.delete(registration.list.descriptor.id)
|
|
98
110
|
if (!lists?.size) ownedLists.delete(registration.owner)
|
|
99
111
|
} else {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
112
|
+
if (__KUDZU_COLLECTION_SELECTORS__ && registration.states) {
|
|
113
|
+
for (const state of registration.states) {
|
|
114
|
+
const lists = listTargets.get(state)
|
|
115
|
+
lists?.delete(registration.list)
|
|
116
|
+
if (!lists?.size) listTargets.delete(state)
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
const lists = listTargets.get(registration.state)
|
|
120
|
+
lists?.delete(registration.list)
|
|
121
|
+
if (!lists?.size) listTargets.delete(registration.state)
|
|
122
|
+
}
|
|
103
123
|
}
|
|
104
124
|
if (__KUDZU_LIST_ROW_HOOKS__ && registration.list.descriptor.rowStates) for (const node of registration.list.roots.values()) deleteRowStates(registration.list.descriptor, ownershipPaths.get(node))
|
|
105
125
|
}
|
|
@@ -112,12 +132,13 @@ function updateList(list) {
|
|
|
112
132
|
? listItems.get(list.owner)?.[list.descriptor.ownerField]
|
|
113
133
|
: browserState.get(list.descriptor.state)
|
|
114
134
|
if (__KUDZU_NESTED_LISTS__ && list.descriptor.ownerField && items == null) items = []
|
|
115
|
-
if (__KUDZU_COLLECTION_SELECTORS__) items = selectCollection(items, list.descriptor.selector)
|
|
135
|
+
if (__KUDZU_COLLECTION_SELECTORS__) items = selectCollection(items, list.descriptor.selector, name => browserState.get(list.descriptor.selectorStates?.[name]))
|
|
116
136
|
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")
|
|
117
137
|
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
138
|
if (__KUDZU_NESTED_LISTS__ && list.descriptor.children) validateChildLists(items, list.descriptor.children)
|
|
119
139
|
if (list.descriptor.reducer && !list.descriptor.selector && list.descriptor.key !== null && list.items && updateReducerList(list, items)) return
|
|
120
140
|
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
|
|
141
|
+
if (__KUDZU_STATIC_COLLECTIONS__ && list.staticEntries && updateStaticFilterList(list, items)) return
|
|
121
142
|
const entries = []
|
|
122
143
|
const keys = new Set()
|
|
123
144
|
const seen = new Set()
|
|
@@ -174,14 +195,18 @@ function updateList(list) {
|
|
|
174
195
|
for (const { item, index, key, token, value } of entries) {
|
|
175
196
|
let node = list.roots.get(token)
|
|
176
197
|
if (!node) {
|
|
177
|
-
|
|
178
|
-
|
|
198
|
+
const staticRoot = __KUDZU_STATIC_COLLECTIONS__ && list.staticRows?.get(token)?.cloneNode(true)
|
|
199
|
+
node = staticRoot ?? (__KUDZU_NESTED_LISTS__ ? list.templateRoot : list.start.content.firstElementChild)?.cloneNode(true)
|
|
200
|
+
if (!staticRoot && node?.dataset.kListRoot !== list.descriptor.id) node = undefined
|
|
179
201
|
if (!node) throw new Error("Keyed list template has no root element")
|
|
180
202
|
node.removeAttribute("data-k-list-root")
|
|
181
203
|
if (__KUDZU_NESTED_LISTS__ && list.childPrototypes) childPrototypes.set(node, list.childPrototypes)
|
|
182
204
|
if (__KUDZU_LIST_ROW_HOOKS__) initializeGeneralRowHooks(list.descriptor, key, node, list.owner)
|
|
183
|
-
|
|
184
|
-
|
|
205
|
+
if (staticRoot) listItems.set(node, item)
|
|
206
|
+
else if (list.parts.directFill) {
|
|
207
|
+
listItems.set(node, item)
|
|
208
|
+
fillStructuralListParts(list.parts, node, item)
|
|
209
|
+
} else fillListItem(node, item, list.descriptor.nested, index, mapListItemParts(list.parts, node, list.descriptor.nested))
|
|
185
210
|
additions.append(node)
|
|
186
211
|
added = true
|
|
187
212
|
} else if ((referenceOnly ? listItems.get(node) !== item : list.values.get(token) !== value) || list.descriptor.indexed || list.descriptor.key === null) {
|
|
@@ -199,20 +224,38 @@ function updateList(list) {
|
|
|
199
224
|
} else node.remove()
|
|
200
225
|
if (__KUDZU_LIST_ROW_HOOKS__ && list.descriptor.rowStates) deleteRowStates(list.descriptor, ownershipPaths.get(node))
|
|
201
226
|
}
|
|
227
|
+
let ordered = false
|
|
202
228
|
if (added) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
229
|
+
const addedNodes = __KUDZU_LIST_MOUNTS__ && list.descriptor.mount ? [...additions.childNodes] : undefined
|
|
230
|
+
let seenAddition = false
|
|
231
|
+
let interleaved = false
|
|
232
|
+
for (const [, node] of next) {
|
|
233
|
+
if (node.parentNode === additions) seenAddition = true
|
|
234
|
+
else if (seenAddition) {
|
|
235
|
+
interleaved = true
|
|
236
|
+
break
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (interleaved) {
|
|
240
|
+
const run = parent.ownerDocument.createDocumentFragment()
|
|
241
|
+
for (const [, node] of next) {
|
|
242
|
+
if (node.parentNode === additions) run.append(node)
|
|
243
|
+
else if (run.firstChild) parent.insertBefore(run, node)
|
|
244
|
+
}
|
|
245
|
+
if (run.firstChild) parent.insertBefore(run, list.boundary)
|
|
246
|
+
ordered = true
|
|
207
247
|
} else parent.insertBefore(additions, list.boundary)
|
|
248
|
+
if (addedNodes) for (const node of addedNodes) mountDom(node)
|
|
208
249
|
list.container ??= parent
|
|
209
250
|
}
|
|
210
251
|
let anchor = list.boundary
|
|
211
252
|
let misplaced = 0
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
253
|
+
if (!ordered) {
|
|
254
|
+
for (let index = next.length - 1; index >= 0; index--) {
|
|
255
|
+
const node = next[index][1]
|
|
256
|
+
if (node.nextSibling !== anchor) misplaced++
|
|
257
|
+
anchor = node
|
|
258
|
+
}
|
|
216
259
|
}
|
|
217
260
|
if (misplaced > next.length / 2) {
|
|
218
261
|
const reordered = parent.ownerDocument.createDocumentFragment()
|
|
@@ -232,6 +275,68 @@ function updateList(list) {
|
|
|
232
275
|
list.items = items
|
|
233
276
|
}
|
|
234
277
|
|
|
278
|
+
function updateStaticFilterList(list, items) {
|
|
279
|
+
const entries = new Array(items.length)
|
|
280
|
+
for (let index = 0; index < items.length; index++) {
|
|
281
|
+
const entry = list.staticEntries.get(items[index])
|
|
282
|
+
if (!entry || !list.roots.has(entry.token) && !list.staticRows.has(entry.token)) return false
|
|
283
|
+
entries[index] = entry
|
|
284
|
+
}
|
|
285
|
+
let selectedIndex = 0
|
|
286
|
+
for (const [token, node] of list.roots) {
|
|
287
|
+
const position = list.staticPositions.get(token)
|
|
288
|
+
while (entries[selectedIndex]?.position < position) selectedIndex++
|
|
289
|
+
if (entries[selectedIndex]?.token === token) {
|
|
290
|
+
selectedIndex++
|
|
291
|
+
continue
|
|
292
|
+
}
|
|
293
|
+
list.staticRows.set(token, node)
|
|
294
|
+
node.remove()
|
|
295
|
+
}
|
|
296
|
+
const parent = list.container ?? list.start.parentNode
|
|
297
|
+
const run = parent.ownerDocument.createDocumentFragment()
|
|
298
|
+
const next = new Array(entries.length)
|
|
299
|
+
for (let index = 0; index < entries.length; index++) {
|
|
300
|
+
const entry = entries[index]
|
|
301
|
+
let node = list.roots.get(entry.token)
|
|
302
|
+
if (!node) {
|
|
303
|
+
node = list.staticRows.get(entry.token).cloneNode(true)
|
|
304
|
+
listItems.set(node, entry.item)
|
|
305
|
+
run.append(node)
|
|
306
|
+
} else if (run.firstChild) parent.insertBefore(run, node)
|
|
307
|
+
next[index] = [entry.token, node]
|
|
308
|
+
}
|
|
309
|
+
if (run.firstChild) parent.insertBefore(run, list.boundary)
|
|
310
|
+
list.roots = new Map(next)
|
|
311
|
+
if (__KUDZU_LIST_STABLE_FAST_PATHS__) list.orderedRoots = next.map(([, node]) => node)
|
|
312
|
+
list.values.clear()
|
|
313
|
+
list.items = items
|
|
314
|
+
list.container ??= parent
|
|
315
|
+
return true
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function cacheStaticListEntries(descriptor, items) {
|
|
319
|
+
if (!Array.isArray(items)) return undefined
|
|
320
|
+
const entries = new WeakMap()
|
|
321
|
+
const positions = new Map()
|
|
322
|
+
const keys = new Set()
|
|
323
|
+
const seen = new Set()
|
|
324
|
+
for (let position = 0; position < items.length; position++) {
|
|
325
|
+
const item = items[position]
|
|
326
|
+
assertListItem(item)
|
|
327
|
+
assertListValue(item, seen, true)
|
|
328
|
+
const key = item[descriptor.key]
|
|
329
|
+
if (!validListKey(key)) throw new Error(`Keyed list key "${descriptor.key}" must be a string or finite number`)
|
|
330
|
+
const token = keyToken(key)
|
|
331
|
+
if (keys.has(token)) throw new Error(`Duplicate keyed list key: ${String(key)}`)
|
|
332
|
+
keys.add(token)
|
|
333
|
+
entries.set(item, { item, key, token, value: item, position })
|
|
334
|
+
positions.set(token, position)
|
|
335
|
+
}
|
|
336
|
+
entries.positions = positions
|
|
337
|
+
return entries
|
|
338
|
+
}
|
|
339
|
+
|
|
235
340
|
/* stable-list-fast-path */
|
|
236
341
|
function updateStableList(list, items) {
|
|
237
342
|
const previous = list.items
|