@barefootjs/client 0.26.4 → 0.28.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/dist/reactive.d.ts.map +1 -1
- package/dist/runtime/claim-slots.d.ts +222 -0
- package/dist/runtime/claim-slots.d.ts.map +1 -0
- package/dist/runtime/component.d.ts +40 -18
- package/dist/runtime/component.d.ts.map +1 -1
- package/dist/runtime/dynamic-text.d.ts +24 -1
- package/dist/runtime/dynamic-text.d.ts.map +1 -1
- package/dist/runtime/index.d.ts +4 -5
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +467 -259
- package/dist/runtime/loop-markers.d.ts +26 -0
- package/dist/runtime/loop-markers.d.ts.map +1 -0
- package/dist/runtime/map-array-lazy.d.ts +164 -0
- package/dist/runtime/map-array-lazy.d.ts.map +1 -0
- package/dist/runtime/map-array.d.ts +35 -0
- package/dist/runtime/map-array.d.ts.map +1 -1
- package/dist/runtime/qsa-item.d.ts +7 -0
- package/dist/runtime/qsa-item.d.ts.map +1 -1
- package/dist/runtime/registry.d.ts.map +1 -1
- package/dist/runtime/standalone.js +455 -248
- package/package.json +2 -2
- package/src/reactive.ts +2 -1
- package/src/runtime/claim-slots.ts +647 -0
- package/src/runtime/component.ts +153 -70
- package/src/runtime/dynamic-text.ts +24 -1
- package/src/runtime/index.ts +20 -7
- package/src/runtime/insert.ts +1 -1
- package/src/runtime/loop-markers.ts +100 -0
- package/src/runtime/map-array-lazy.ts +470 -0
- package/src/runtime/map-array.ts +68 -11
- package/src/runtime/qsa-item.ts +9 -3
- package/src/runtime/registry.ts +5 -3
- package/dist/runtime/client-marker.d.ts +0 -21
- package/dist/runtime/client-marker.d.ts.map +0 -1
- package/dist/runtime/list.d.ts +0 -21
- package/dist/runtime/list.d.ts.map +0 -1
- package/dist/runtime/patch-slot-range.d.ts +0 -47
- package/dist/runtime/patch-slot-range.d.ts.map +0 -1
- package/dist/runtime/reconcile-elements.d.ts +0 -44
- package/dist/runtime/reconcile-elements.d.ts.map +0 -1
- package/src/runtime/client-marker.ts +0 -46
- package/src/runtime/list.ts +0 -47
- package/src/runtime/patch-slot-range.ts +0 -105
- package/src/runtime/reconcile-elements.ts +0 -391
|
@@ -1,391 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BarefootJS - Element-based List Reconciliation
|
|
3
|
-
*
|
|
4
|
-
* Key-based DOM reconciliation for component-based list rendering.
|
|
5
|
-
* Used when renderItem returns HTMLElement (via createComponent).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { hydratedScopes } from './hydration-state.ts'
|
|
9
|
-
import { BF_SCOPE, BF_SLOT, BF_COND, BF_KEY, BF_LOOP_START, BF_LOOP_END, loopStartMarker, loopEndMarker } from '@barefootjs/shared'
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Find loop boundary comment markers in a container.
|
|
13
|
-
*
|
|
14
|
-
* `markerId` scopes the lookup to `<!--bf-loop:<id>-->` / `<!--bf-/loop:<id>-->`
|
|
15
|
-
* so sibling loops under the same parent disambiguate (#1087). Without an id,
|
|
16
|
-
* accepts the legacy unscoped form too — used by tests that build containers
|
|
17
|
-
* without compiler-emitted markers.
|
|
18
|
-
*/
|
|
19
|
-
function findLoopMarkers(
|
|
20
|
-
container: HTMLElement,
|
|
21
|
-
markerId?: string,
|
|
22
|
-
): { startMarker: Comment | null; endMarker: Comment | null } {
|
|
23
|
-
let startMarker: Comment | null = null
|
|
24
|
-
let endMarker: Comment | null = null
|
|
25
|
-
if (markerId) {
|
|
26
|
-
const startVal = loopStartMarker(markerId)
|
|
27
|
-
const endVal = loopEndMarker(markerId)
|
|
28
|
-
for (const node of Array.from(container.childNodes)) {
|
|
29
|
-
if (node.nodeType !== Node.COMMENT_NODE) continue
|
|
30
|
-
const value = (node as Comment).nodeValue
|
|
31
|
-
if (value === startVal) startMarker = node as Comment
|
|
32
|
-
else if (value === endVal) endMarker = node as Comment
|
|
33
|
-
}
|
|
34
|
-
} else {
|
|
35
|
-
const startPrefix = `${BF_LOOP_START}:`
|
|
36
|
-
const endPrefix = `${BF_LOOP_END}:`
|
|
37
|
-
for (const node of Array.from(container.childNodes)) {
|
|
38
|
-
if (node.nodeType !== Node.COMMENT_NODE) continue
|
|
39
|
-
const value = (node as Comment).nodeValue ?? ''
|
|
40
|
-
if (!startMarker && (value === BF_LOOP_START || value.startsWith(startPrefix))) {
|
|
41
|
-
startMarker = node as Comment
|
|
42
|
-
} else if (!endMarker && (value === BF_LOOP_END || value.startsWith(endPrefix))) {
|
|
43
|
-
endMarker = node as Comment
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
if (startMarker && endMarker) return { startMarker, endMarker }
|
|
48
|
-
return { startMarker: null, endMarker: null }
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** Get all Element nodes between start and end comment markers. */
|
|
52
|
-
function getElementsBetweenMarkers(start: Comment, end: Comment): Element[] {
|
|
53
|
-
const elements: Element[] = []
|
|
54
|
-
let node: Node | null = start.nextSibling
|
|
55
|
-
while (node && node !== end) {
|
|
56
|
-
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
57
|
-
elements.push(node as Element)
|
|
58
|
-
}
|
|
59
|
-
node = node.nextSibling
|
|
60
|
-
}
|
|
61
|
-
return elements
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** Remove all nodes between start and end comment markers (preserves the markers). */
|
|
65
|
-
function removeElementsBetweenMarkers(start: Comment, end: Comment): void {
|
|
66
|
-
let node: Node | null = start.nextSibling
|
|
67
|
-
while (node && node !== end) {
|
|
68
|
-
const next: Node | null = node.nextSibling
|
|
69
|
-
node.parentNode?.removeChild(node)
|
|
70
|
-
node = next
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Get loop children from a container, respecting bf-loop boundary markers.
|
|
76
|
-
* When markers are present, returns only elements between them.
|
|
77
|
-
* When absent, returns all children (backward compatible).
|
|
78
|
-
* Exported for use by compiler-generated hydration code.
|
|
79
|
-
*/
|
|
80
|
-
export function getLoopChildren(container: HTMLElement, markerId?: string): HTMLElement[] {
|
|
81
|
-
const { startMarker, endMarker } = findLoopMarkers(container, markerId)
|
|
82
|
-
if (startMarker && endMarker) {
|
|
83
|
-
return getElementsBetweenMarkers(startMarker, endMarker) as HTMLElement[]
|
|
84
|
-
}
|
|
85
|
-
return Array.from(container.children) as HTMLElement[]
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Like {@link getLoopChildren}, but returns every node between the loop
|
|
90
|
-
* boundary markers — Comments (per-item `<!--bf-loop-i-->` markers) and
|
|
91
|
-
* text included. The branch-clearing path needs to remove the per-item
|
|
92
|
-
* marker comments alongside elements; otherwise stale markers would
|
|
93
|
-
* accumulate when a branch swap forces mapArray to start over (#1212).
|
|
94
|
-
*/
|
|
95
|
-
export function getLoopNodes(container: HTMLElement, markerId?: string): Node[] {
|
|
96
|
-
const { startMarker, endMarker } = findLoopMarkers(container, markerId)
|
|
97
|
-
const nodes: Node[] = []
|
|
98
|
-
if (startMarker && endMarker) {
|
|
99
|
-
let node: Node | null = startMarker.nextSibling
|
|
100
|
-
while (node && node !== endMarker) {
|
|
101
|
-
nodes.push(node)
|
|
102
|
-
node = node.nextSibling
|
|
103
|
-
}
|
|
104
|
-
return nodes
|
|
105
|
-
}
|
|
106
|
-
return Array.from(container.childNodes)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Ensure loop boundary markers exist in a container for SSR-rendered content.
|
|
111
|
-
* SSR HTML doesn't include markers, so we insert them during hydration.
|
|
112
|
-
* Uses itemCount to identify the last N children as loop items (rest are siblings).
|
|
113
|
-
*/
|
|
114
|
-
export function ensureLoopMarkers(container: HTMLElement, itemCount: number, markerId?: string): void {
|
|
115
|
-
// Already has markers
|
|
116
|
-
const { startMarker } = findLoopMarkers(container, markerId)
|
|
117
|
-
if (startMarker) return
|
|
118
|
-
|
|
119
|
-
const children = Array.from(container.children)
|
|
120
|
-
if (children.length === 0) return
|
|
121
|
-
|
|
122
|
-
// Loop items are the LAST itemCount children (siblings come first in HTML order)
|
|
123
|
-
const loopStartIdx = Math.max(0, children.length - itemCount)
|
|
124
|
-
const firstLoopChild = children[loopStartIdx]
|
|
125
|
-
|
|
126
|
-
const start = document.createComment(markerId ? loopStartMarker(markerId) : BF_LOOP_START)
|
|
127
|
-
const end = document.createComment(markerId ? loopEndMarker(markerId) : BF_LOOP_END)
|
|
128
|
-
container.insertBefore(start, firstLoopChild)
|
|
129
|
-
container.appendChild(end)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Reconcile a list container using HTMLElement mode (for createComponent).
|
|
134
|
-
* Reuses existing elements by key, creates new elements as needed.
|
|
135
|
-
*
|
|
136
|
-
* @param container - The parent element containing list items
|
|
137
|
-
* @param items - Array of items to render
|
|
138
|
-
* @param getKey - Function to extract a unique key from each item (or null to use index)
|
|
139
|
-
* @param renderItem - Function that returns an HTMLElement for each item
|
|
140
|
-
* @param firstElement - Pre-created element for first item (avoids duplicate creation when caller already rendered item 0)
|
|
141
|
-
*/
|
|
142
|
-
export function reconcileElements<T>(
|
|
143
|
-
container: HTMLElement | null,
|
|
144
|
-
items: T[],
|
|
145
|
-
getKey: ((item: T, index: number) => string) | null,
|
|
146
|
-
renderItem: (item: T, index: number) => HTMLElement,
|
|
147
|
-
firstElement?: HTMLElement,
|
|
148
|
-
markerId?: string,
|
|
149
|
-
): void {
|
|
150
|
-
if (!container || !items) return
|
|
151
|
-
|
|
152
|
-
// Find loop boundary markers if present.
|
|
153
|
-
// When markers exist, only elements between <!--bf-loop--> and <!--/bf-loop-->
|
|
154
|
-
// participate in reconciliation — siblings outside the range are preserved.
|
|
155
|
-
const { startMarker, endMarker } = findLoopMarkers(container, markerId)
|
|
156
|
-
|
|
157
|
-
// Collect existing keyed elements (only within loop range if markers exist)
|
|
158
|
-
const existingByKey = new Map<string, HTMLElement>()
|
|
159
|
-
let hasKeyedChildren = false
|
|
160
|
-
const loopChildren = startMarker
|
|
161
|
-
? getElementsBetweenMarkers(startMarker, endMarker!)
|
|
162
|
-
: Array.from(container.children)
|
|
163
|
-
for (const child of loopChildren) {
|
|
164
|
-
const el = child as HTMLElement
|
|
165
|
-
const key = el.dataset?.key
|
|
166
|
-
if (key !== undefined) {
|
|
167
|
-
existingByKey.set(key, el)
|
|
168
|
-
hasKeyedChildren = true
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// When no keyed children exist (initial SSR render or all-unkeyed container),
|
|
173
|
-
// use the simple clear-and-replace path. Non-keyed children in this case are
|
|
174
|
-
// SSR-rendered loop items that haven't been through hydration yet.
|
|
175
|
-
if (!hasKeyedChildren) {
|
|
176
|
-
if (items.length === 0) {
|
|
177
|
-
if (startMarker) {
|
|
178
|
-
removeElementsBetweenMarkers(startMarker, endMarker!)
|
|
179
|
-
} else {
|
|
180
|
-
container.innerHTML = ''
|
|
181
|
-
}
|
|
182
|
-
return
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const fragment = document.createDocumentFragment()
|
|
186
|
-
for (let i = 0; i < items.length; i++) {
|
|
187
|
-
const el = (i === 0 && firstElement) ? firstElement : renderItem(items[i], i)
|
|
188
|
-
const key = getKey ? getKey(items[i], i) : String(i)
|
|
189
|
-
if (!el.dataset.key) el.setAttribute(BF_KEY, key)
|
|
190
|
-
fragment.appendChild(el)
|
|
191
|
-
}
|
|
192
|
-
if (startMarker) {
|
|
193
|
-
removeElementsBetweenMarkers(startMarker, endMarker!)
|
|
194
|
-
endMarker!.parentNode!.insertBefore(fragment, endMarker)
|
|
195
|
-
} else {
|
|
196
|
-
container.innerHTML = ''
|
|
197
|
-
container.appendChild(fragment)
|
|
198
|
-
}
|
|
199
|
-
return
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Insert anchor: end marker (if present) or first non-keyed sibling after keyed region.
|
|
203
|
-
let insertAnchor: Node | null = endMarker ?? null
|
|
204
|
-
if (!startMarker) {
|
|
205
|
-
let foundKeyed = false
|
|
206
|
-
for (const child of Array.from(container.childNodes)) {
|
|
207
|
-
if (child.nodeType === Node.ELEMENT_NODE && (child as HTMLElement).dataset.key !== undefined) {
|
|
208
|
-
foundKeyed = true
|
|
209
|
-
} else if (foundKeyed) {
|
|
210
|
-
insertAnchor = child
|
|
211
|
-
break
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// --- Phase 1: Detect focus (before ANY DOM mutation) ---
|
|
217
|
-
// Only text inputs have ongoing user state (cursor, selection, typed text)
|
|
218
|
-
// that must survive reconciliation. Button focus has no state to preserve.
|
|
219
|
-
let focusedKey: string | null = null
|
|
220
|
-
const activeEl = document.activeElement
|
|
221
|
-
if (activeEl && activeEl !== document.body) {
|
|
222
|
-
const tag = activeEl.tagName
|
|
223
|
-
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT'
|
|
224
|
-
|| (activeEl as HTMLElement).isContentEditable) {
|
|
225
|
-
for (const [key, el] of existingByKey) {
|
|
226
|
-
if (el.contains(activeEl)) {
|
|
227
|
-
focusedKey = key
|
|
228
|
-
break
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// --- Phase 2: Build desired element list ---
|
|
235
|
-
// For each item, decide: reuse existing (focus), create new, or skip.
|
|
236
|
-
// Track old elements to remove explicitly — no bulk remove-all.
|
|
237
|
-
const desiredElements: HTMLElement[] = []
|
|
238
|
-
const toRemove: Element[] = []
|
|
239
|
-
let focusTarget: FocusTransferInfo | null = null
|
|
240
|
-
|
|
241
|
-
for (let i = 0; i < items.length; i++) {
|
|
242
|
-
const item = items[i]
|
|
243
|
-
const key = getKey ? getKey(item, i) : String(i)
|
|
244
|
-
const createEl = () => (i === 0 && firstElement) ? firstElement : renderItem(item, i)
|
|
245
|
-
|
|
246
|
-
const existing = existingByKey.get(key)
|
|
247
|
-
if (existing) {
|
|
248
|
-
existingByKey.delete(key)
|
|
249
|
-
|
|
250
|
-
if (existing.getAttribute(BF_SCOPE) && !hydratedScopes.has(existing)) {
|
|
251
|
-
// Uninitialized SSR element — replace with client-rendered element
|
|
252
|
-
const newEl = createEl()
|
|
253
|
-
if (!newEl.dataset.key) newEl.setAttribute(BF_KEY, key)
|
|
254
|
-
desiredElements.push(newEl)
|
|
255
|
-
toRemove.push(existing)
|
|
256
|
-
} else if (focusedKey === key) {
|
|
257
|
-
// Element contains a focused text input. Create the new element (with
|
|
258
|
-
// updated inner loops, conditionals, etc.), copy input state now,
|
|
259
|
-
// defer focus() to after DOM insertion to avoid flicker.
|
|
260
|
-
const newEl = createEl()
|
|
261
|
-
if (!newEl.dataset.key) newEl.setAttribute(BF_KEY, key)
|
|
262
|
-
focusTarget = prepareInputTransfer(existing, newEl)
|
|
263
|
-
desiredElements.push(newEl)
|
|
264
|
-
toRemove.push(existing)
|
|
265
|
-
} else {
|
|
266
|
-
// Normal update — use new element
|
|
267
|
-
const newEl = createEl()
|
|
268
|
-
if (!newEl.dataset.key) newEl.setAttribute(BF_KEY, key)
|
|
269
|
-
desiredElements.push(newEl)
|
|
270
|
-
toRemove.push(existing)
|
|
271
|
-
}
|
|
272
|
-
} else {
|
|
273
|
-
// Brand new key
|
|
274
|
-
const el = createEl()
|
|
275
|
-
if (!el.dataset.key) el.setAttribute(BF_KEY, key)
|
|
276
|
-
desiredElements.push(el)
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Remaining entries in existingByKey are orphans (key no longer in items)
|
|
281
|
-
for (const el of existingByKey.values()) {
|
|
282
|
-
toRemove.push(el)
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// --- Phase 3: Remove old elements ---
|
|
286
|
-
for (const el of toRemove) {
|
|
287
|
-
if (el.parentNode) el.remove()
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// --- Phase 4: Insert/move desired elements in correct order ---
|
|
291
|
-
// insertBefore moves already-connected elements; inserts new ones.
|
|
292
|
-
for (const el of desiredElements) {
|
|
293
|
-
container.insertBefore(el, insertAnchor)
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// --- Phase 5: Restore focus synchronously (element is now in DOM) ---
|
|
297
|
-
if (focusTarget) {
|
|
298
|
-
focusTarget.target.focus()
|
|
299
|
-
if (typeof focusTarget.selectionStart === 'number') {
|
|
300
|
-
focusTarget.target.selectionStart = focusTarget.selectionStart
|
|
301
|
-
focusTarget.target.selectionEnd = focusTarget.selectionEnd
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
interface FocusTransferInfo {
|
|
307
|
-
target: HTMLInputElement
|
|
308
|
-
selectionStart: number | null
|
|
309
|
-
selectionEnd: number | null
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Prepare focus transfer: copy value + selection state from old focused input
|
|
314
|
-
* to the matching input in newEl. Returns info needed to call focus() later
|
|
315
|
-
* (after the new element is inserted into the DOM).
|
|
316
|
-
*/
|
|
317
|
-
function prepareInputTransfer(oldEl: HTMLElement, newEl: HTMLElement): FocusTransferInfo | null {
|
|
318
|
-
const focused = oldEl.contains(document.activeElement) ? document.activeElement as HTMLInputElement : null
|
|
319
|
-
if (!focused) return null
|
|
320
|
-
|
|
321
|
-
const tag = focused.tagName
|
|
322
|
-
const oldInputs = Array.from(oldEl.querySelectorAll(tag))
|
|
323
|
-
const idx = oldInputs.indexOf(focused)
|
|
324
|
-
if (idx < 0) return null
|
|
325
|
-
|
|
326
|
-
const newInputs = Array.from(newEl.querySelectorAll(tag)) as HTMLInputElement[]
|
|
327
|
-
const target = newInputs[idx]
|
|
328
|
-
if (!target) return null
|
|
329
|
-
|
|
330
|
-
target.value = focused.value
|
|
331
|
-
return {
|
|
332
|
-
target,
|
|
333
|
-
selectionStart: focused.selectionStart,
|
|
334
|
-
selectionEnd: focused.selectionEnd,
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* Sync reactive DOM state from a source element to a target element.
|
|
340
|
-
* Copies class names, replaces conditional elements, and syncs text content.
|
|
341
|
-
*/
|
|
342
|
-
export function syncElementState(target: HTMLElement, source: HTMLElement): void {
|
|
343
|
-
// Sync class list (for reactive classes like 'done' on TodoItem)
|
|
344
|
-
target.className = source.className
|
|
345
|
-
|
|
346
|
-
// First, sync conditional elements by replacing them entirely
|
|
347
|
-
const sourceCondSlots = Array.from(source.querySelectorAll(`[${BF_COND}]`))
|
|
348
|
-
for (const sourceCondSlot of sourceCondSlots) {
|
|
349
|
-
const condId = (sourceCondSlot as HTMLElement).getAttribute(BF_COND)
|
|
350
|
-
if (condId) {
|
|
351
|
-
const targetCondSlot = target.querySelector(`[${BF_COND}="${condId}"]`)
|
|
352
|
-
if (targetCondSlot) {
|
|
353
|
-
targetCondSlot.replaceWith(sourceCondSlot)
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// Then sync text content of bf slots that are NOT inside conditional elements.
|
|
359
|
-
// Use querySelectorAll on BOTH source and target, then match by position index
|
|
360
|
-
// within each slot ID group. This handles multiple component instances that share
|
|
361
|
-
// the same internal slot ID (e.g., multiple Badge components each with bf="s0").
|
|
362
|
-
const sourceSlots = source.querySelectorAll(`[${BF_SLOT}]`)
|
|
363
|
-
const targetSlotsByID = new Map<string, Element[]>()
|
|
364
|
-
const targetAllSlots = target.querySelectorAll(`[${BF_SLOT}]`)
|
|
365
|
-
for (const targetSlot of Array.from(targetAllSlots)) {
|
|
366
|
-
const id = (targetSlot as HTMLElement).getAttribute(BF_SLOT)
|
|
367
|
-
if (id) {
|
|
368
|
-
if (!targetSlotsByID.has(id)) targetSlotsByID.set(id, [])
|
|
369
|
-
targetSlotsByID.get(id)!.push(targetSlot)
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// Track which index we're at for each slot ID
|
|
374
|
-
const slotIndexCounters = new Map<string, number>()
|
|
375
|
-
|
|
376
|
-
for (const sourceSlot of Array.from(sourceSlots)) {
|
|
377
|
-
const slotId = (sourceSlot as HTMLElement).getAttribute(BF_SLOT)
|
|
378
|
-
if (slotId) {
|
|
379
|
-
if (sourceSlot.closest(`[${BF_COND}]`)) continue
|
|
380
|
-
const idx = slotIndexCounters.get(slotId) ?? 0
|
|
381
|
-
slotIndexCounters.set(slotId, idx + 1)
|
|
382
|
-
const targets = targetSlotsByID.get(slotId)
|
|
383
|
-
const targetSlot = targets?.[idx]
|
|
384
|
-
if (targetSlot && sourceSlot.textContent !== null) {
|
|
385
|
-
if (sourceSlot.children.length === 0) {
|
|
386
|
-
targetSlot.textContent = sourceSlot.textContent
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
}
|