@ape-egg/vibe 4.2.1 → 4.3.1
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/README.md +5 -5
- package/hot-module-refresh.js +0 -0
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/runtime/affected.js +56 -1
- package/runtime/conditionals.js +12 -0
- package/runtime/constants.js +1 -1
- package/runtime/index.js +21 -46
- package/runtime/iterate.js +76 -7
- package/runtime/manifest.js +42 -0
- package/runtime/parse.js +14 -11
- package/runtime/reconcile.js +488 -289
- package/spa.js +7 -2
package/runtime/reconcile.js
CHANGED
|
@@ -1,4 +1,29 @@
|
|
|
1
|
+
import parse, { rewriteHandler } from './parse.js';
|
|
2
|
+
import hydrate from './hydrate.js';
|
|
3
|
+
import affected, { ownBindingsOf } from './affected.js';
|
|
4
|
+
import { findNodeByElement, addToManifest, manifestPathOf } from './manifest.js';
|
|
5
|
+
import {
|
|
6
|
+
managedNodes,
|
|
7
|
+
branchNodeRegistry,
|
|
8
|
+
dispatchConditional,
|
|
9
|
+
remountBranch,
|
|
10
|
+
renderAllConditionals,
|
|
11
|
+
} from './conditionals.js';
|
|
12
|
+
import {
|
|
13
|
+
renderAllIterations,
|
|
14
|
+
createScopedState,
|
|
15
|
+
swapIterationTemplate,
|
|
16
|
+
rerenderIteration,
|
|
17
|
+
patchTreelessInstances,
|
|
18
|
+
instanceRange,
|
|
19
|
+
prepareTemplateComponents,
|
|
20
|
+
} from './iterate.js';
|
|
21
|
+
import { forceRemount, remountComponent, renderComponentTemplate } from './component.js';
|
|
22
|
+
import { eachComponentIn } from './component-registry.js';
|
|
1
23
|
import { parkFetchableSrc } from './iteration-utils.js';
|
|
24
|
+
import { stampInstanceScopes } from './loop-scope.js';
|
|
25
|
+
import { isInert } from './inert.js';
|
|
26
|
+
import { findComponentIdForElement } from './utils.js';
|
|
2
27
|
import {
|
|
3
28
|
ITERATION_START_REGEX,
|
|
4
29
|
CONDITIONAL_START_REGEX,
|
|
@@ -14,6 +39,7 @@ const isIterationStart = (n) =>
|
|
|
14
39
|
n.nodeType === COMMENT && ITERATION_START_REGEX.test(n.textContent.trim());
|
|
15
40
|
const isConditionalStart = (n) =>
|
|
16
41
|
n.nodeType === COMMENT && CONDITIONAL_START_REGEX.test(n.textContent.trim());
|
|
42
|
+
const isRegionStart = (n) => isIterationStart(n) || isConditionalStart(n);
|
|
17
43
|
const isRegionEnd = (n) => {
|
|
18
44
|
if (n.nodeType !== COMMENT) return false;
|
|
19
45
|
const t = n.textContent.trim();
|
|
@@ -22,10 +48,12 @@ const isRegionEnd = (n) => {
|
|
|
22
48
|
const isComponentWrapper = (n) =>
|
|
23
49
|
n.nodeType === ELEMENT &&
|
|
24
50
|
(n.tagName === 'COMPONENT' || (n.tagName === 'DIV' && n.classList?.contains('component')));
|
|
25
|
-
|
|
26
51
|
const isSlotElement = (n) =>
|
|
27
52
|
n.nodeType === ELEMENT &&
|
|
28
53
|
(n.tagName === 'SLOT' || (n.tagName === 'DIV' && n.classList?.contains('slot')));
|
|
54
|
+
const isScript = (n) => n.nodeType === ELEMENT && n.tagName === 'SCRIPT';
|
|
55
|
+
|
|
56
|
+
const hasBinding = (s) => /@\[.+?\]/.test(s);
|
|
29
57
|
|
|
30
58
|
const nodesMatch = (a, b) => {
|
|
31
59
|
if (!a || !b) return false;
|
|
@@ -49,6 +77,19 @@ const findRealignment = (liveNodes, li, sourceNodes, si) => {
|
|
|
49
77
|
return null;
|
|
50
78
|
};
|
|
51
79
|
|
|
80
|
+
const findRegionEnd = (nodes, startIdx) => {
|
|
81
|
+
let depth = 1;
|
|
82
|
+
for (let i = startIdx + 1; i < nodes.length; i++) {
|
|
83
|
+
const n = nodes[i];
|
|
84
|
+
if (isRegionStart(n)) depth++;
|
|
85
|
+
else if (isRegionEnd(n)) {
|
|
86
|
+
depth--;
|
|
87
|
+
if (depth === 0) return i;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return nodes.length - 1;
|
|
91
|
+
};
|
|
92
|
+
|
|
52
93
|
const trimWsEdges = (nodes) => {
|
|
53
94
|
let start = 0;
|
|
54
95
|
let end = nodes.length;
|
|
@@ -57,10 +98,6 @@ const trimWsEdges = (nodes) => {
|
|
|
57
98
|
return nodes.slice(start, end);
|
|
58
99
|
};
|
|
59
100
|
|
|
60
|
-
const firstElementChildOf = (parent) => {
|
|
61
|
-
for (const n of parent.children || []) return n;
|
|
62
|
-
return null;
|
|
63
|
-
};
|
|
64
101
|
const firstElementOfNodes = (nodes) => {
|
|
65
102
|
for (const n of nodes) if (n.nodeType === ELEMENT) return n;
|
|
66
103
|
return null;
|
|
@@ -69,87 +106,15 @@ const findOwnSlot = (el, srcChildren) => {
|
|
|
69
106
|
const srcFirst = firstElementOfNodes(srcChildren);
|
|
70
107
|
if (!srcFirst) return null;
|
|
71
108
|
for (const slot of el.querySelectorAll('slot')) {
|
|
72
|
-
|
|
73
|
-
if (liveFirst?.tagName === srcFirst.tagName) return slot;
|
|
109
|
+
if (slot.firstElementChild?.tagName === srcFirst.tagName) return slot;
|
|
74
110
|
}
|
|
75
111
|
return null;
|
|
76
112
|
};
|
|
77
113
|
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const updateBoundText = (live, newSrc, parent, log) => {
|
|
83
|
-
const oldSrc = boundSourceCache.get(live);
|
|
84
|
-
boundSourceCache.set(live, newSrc);
|
|
85
|
-
if (oldSrc === undefined || oldSrc === newSrc) return;
|
|
86
|
-
|
|
87
|
-
const oldBindings = oldSrc.match(/@\[[^\]]+\]/g) || [];
|
|
88
|
-
const newBindings = newSrc.match(/@\[[^\]]+\]/g) || [];
|
|
89
|
-
if (oldBindings.length !== newBindings.length) return;
|
|
90
|
-
for (let i = 0; i < oldBindings.length; i++) {
|
|
91
|
-
if (oldBindings[i] !== newBindings[i]) return;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const oldStatic = oldSrc.split(/@\[[^\]]+\]/);
|
|
95
|
-
const newStatic = newSrc.split(/@\[[^\]]+\]/);
|
|
96
|
-
|
|
97
|
-
const liveText = live.textContent;
|
|
98
|
-
const values = [];
|
|
99
|
-
let pos = 0;
|
|
100
|
-
for (let i = 0; i < oldStatic.length - 1; i++) {
|
|
101
|
-
const before = oldStatic[i];
|
|
102
|
-
const after = oldStatic[i + 1];
|
|
103
|
-
if (liveText.slice(pos, pos + before.length) !== before) return;
|
|
104
|
-
pos += before.length;
|
|
105
|
-
let endPos;
|
|
106
|
-
if (i === oldStatic.length - 2) {
|
|
107
|
-
endPos = liveText.length - after.length;
|
|
108
|
-
if (endPos < pos || liveText.slice(endPos) !== after) return;
|
|
109
|
-
} else {
|
|
110
|
-
endPos = liveText.indexOf(after, pos);
|
|
111
|
-
if (endPos < 0) return;
|
|
112
|
-
}
|
|
113
|
-
values.push(liveText.slice(pos, endPos));
|
|
114
|
-
pos = endPos;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
let result = newStatic[0];
|
|
118
|
-
for (let i = 0; i < values.length; i++) {
|
|
119
|
-
result += values[i] + newStatic[i + 1];
|
|
120
|
-
}
|
|
121
|
-
if (result === liveText) return;
|
|
122
|
-
|
|
123
|
-
live.textContent = result;
|
|
124
|
-
log.text++;
|
|
125
|
-
log.changes.push(`bound text in ${describe(parent)}: ${JSON.stringify(result.slice(0, 60))}`);
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const findRegionEnd = (nodes, startIdx, endLimit) => {
|
|
129
|
-
const limit = endLimit ?? nodes.length;
|
|
130
|
-
let depth = 1;
|
|
131
|
-
for (let i = startIdx + 1; i < limit; i++) {
|
|
132
|
-
const n = nodes[i];
|
|
133
|
-
if (isIterationStart(n) || isConditionalStart(n)) depth++;
|
|
134
|
-
else if (isRegionEnd(n)) {
|
|
135
|
-
depth--;
|
|
136
|
-
if (depth === 0) return i;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return limit - 1;
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const findElseMarker = (nodes, startIdx, endIdx) => {
|
|
143
|
-
let depth = 0;
|
|
144
|
-
for (let i = startIdx; i < endIdx; i++) {
|
|
145
|
-
const n = nodes[i];
|
|
146
|
-
if (n.nodeType !== COMMENT) continue;
|
|
147
|
-
const t = n.textContent.trim();
|
|
148
|
-
if (t.startsWith('if ') || t.startsWith('each ')) depth++;
|
|
149
|
-
else if (t === '/if' || t === '/each') depth--;
|
|
150
|
-
else if (depth === 0 && t === 'else') return i;
|
|
151
|
-
}
|
|
152
|
-
return -1;
|
|
114
|
+
const rangeBetween = (from, to) => {
|
|
115
|
+
const nodes = [];
|
|
116
|
+
for (let cur = from; cur && cur !== to; cur = cur.nextSibling) nodes.push(cur);
|
|
117
|
+
return nodes;
|
|
153
118
|
};
|
|
154
119
|
|
|
155
120
|
const PRESERVED_ATTRS = new Set([FOUC_CLASS_OR_ATTR]);
|
|
@@ -171,150 +136,159 @@ const describe = (el) => {
|
|
|
171
136
|
return el.tagName.toLowerCase() + id + cls;
|
|
172
137
|
};
|
|
173
138
|
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if (kept.length) live.setAttribute('class', kept.join(' '));
|
|
194
|
-
else live.removeAttribute('class');
|
|
195
|
-
log.attr++;
|
|
196
|
-
log.changes.push(`attr ${describe(live)} [class trimmed]`);
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
live.removeAttribute(attr.name);
|
|
200
|
-
log.attr++;
|
|
201
|
-
log.changes.push(`attr ${describe(live)} [-${attr.name}]`);
|
|
139
|
+
const sameRecord = (a, b) => {
|
|
140
|
+
const ak = Object.keys(a ?? {});
|
|
141
|
+
const bk = Object.keys(b ?? {});
|
|
142
|
+
return ak.length === bk.length && ak.every((k) => a[k] === b?.[k]);
|
|
143
|
+
};
|
|
144
|
+
const sameList = (a, b) => {
|
|
145
|
+
const al = a ?? [];
|
|
146
|
+
const bl = b ?? [];
|
|
147
|
+
return al.length === bl.length && al.every((v, i) => v === bl[i]);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const childrenByNode = (tree) => {
|
|
151
|
+
const map = new Map();
|
|
152
|
+
if (!tree?.children) return map;
|
|
153
|
+
for (const key in tree.children) {
|
|
154
|
+
const child = tree.children[key];
|
|
155
|
+
if (!child || typeof child !== 'object') continue;
|
|
156
|
+
const node = child.meta?.startComment ?? child.textNode ?? child.element;
|
|
157
|
+
if (node && !map.has(node)) map.set(node, { key, tree: child });
|
|
202
158
|
}
|
|
159
|
+
return map;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const nextKey = (tree, sample) => {
|
|
163
|
+
tree._nextChildIndex ??= Object.keys(tree.children).length;
|
|
164
|
+
return `${sample.slice(0, sample.lastIndexOf('_'))}_${tree._nextChildIndex++}`;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const attach = (parentTree, key, tree, parent, ctx) => {
|
|
168
|
+
const name = nextKey(parentTree, key);
|
|
169
|
+
if (tree.type === 'conditional') tree._key = name;
|
|
170
|
+
parentTree.children[name] = tree;
|
|
171
|
+
if (ctx.manifestBase === null) return;
|
|
172
|
+
const base = ctx.manifestBase ?? manifestPathOf(ctx.manifest, parent);
|
|
173
|
+
if (base !== null) addToManifest(tree, ctx.manifest, `${base}.${name}`);
|
|
203
174
|
};
|
|
204
175
|
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
176
|
+
const rehydrate = (tree, ctx) => {
|
|
177
|
+
const entries = ownBindingsOf(tree, ctx.state);
|
|
178
|
+
if (entries.length) hydrate(entries, ctx.state, ctx.manifest, ctx.state);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const mountFresh = (tree, ctx) => {
|
|
182
|
+
const entries = affected(tree, ctx.state, ctx.state);
|
|
183
|
+
if (entries.length) hydrate(entries, ctx.state, ctx.manifest, ctx.state);
|
|
184
|
+
renderAllIterations(tree, ctx.state, ctx.manifest, ctx.parentScope);
|
|
185
|
+
renderAllConditionals(tree, ctx.state, ctx.manifest, ctx.parentScope);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const materialize = (srcNodes, parent, ctx) => {
|
|
189
|
+
const box = document.createElement('div');
|
|
190
|
+
box._vibeComponentId = findComponentIdForElement(parent);
|
|
191
|
+
for (const n of srcNodes) box.appendChild(n.cloneNode(true));
|
|
192
|
+
prepareTemplateComponents(box, ctx.state);
|
|
193
|
+
const trees = parse(box, undefined, ctx.aliases).children;
|
|
194
|
+
return { nodes: [...box.childNodes], trees };
|
|
195
|
+
};
|
|
208
196
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
197
|
+
const adoptElement = (live, parent, oldTree, ctx) => {
|
|
198
|
+
if (!oldTree || isInert(live)) return null;
|
|
199
|
+
const tree = parse(live, undefined, ctx.aliases);
|
|
200
|
+
attach(oldTree, `${live.tagName.toLowerCase()}_0`, tree, parent, ctx);
|
|
201
|
+
return tree;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const reconcileList = (parent, liveNodes, srcNodes, oldTree, newTree, ctx, anchor = null) => {
|
|
205
|
+
const { log } = ctx;
|
|
206
|
+
const oldMap = childrenByNode(oldTree);
|
|
207
|
+
const newMap = childrenByNode(newTree);
|
|
208
|
+
const oldOf = (n) => oldMap.get(n)?.tree;
|
|
209
|
+
const newOf = (n) => newMap.get(n)?.tree;
|
|
210
|
+
let li = 0;
|
|
211
|
+
let si = 0;
|
|
212
|
+
let structural = 0;
|
|
213
|
+
|
|
214
|
+
const insert = (from, to, ref) => {
|
|
215
|
+
const { nodes, trees } = materialize(srcNodes.slice(from, to + 1).filter((n) => !isScript(n)), parent, ctx);
|
|
216
|
+
for (const n of nodes) {
|
|
217
|
+
if (oldTree && n.nodeType === ELEMENT) managedNodes.add(n);
|
|
218
|
+
parent.insertBefore(n, ref);
|
|
219
|
+
}
|
|
220
|
+
if (oldTree) {
|
|
221
|
+
for (const key in trees) {
|
|
222
|
+
attach(oldTree, key, trees[key], parent, ctx);
|
|
223
|
+
mountFresh(trees[key], ctx);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
log.insert += nodes.length;
|
|
227
|
+
log.changes.push(`insert ${nodes.map(describe).join(', ')} into ${describe(parent)}`);
|
|
228
|
+
structural++;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const remove = (from, to) => {
|
|
232
|
+
for (let k = from; k <= to; k++) {
|
|
233
|
+
const n = liveNodes[k];
|
|
234
|
+
if (isScript(n)) continue;
|
|
235
|
+
const hit = oldMap.get(n);
|
|
236
|
+
if (hit && oldTree) delete oldTree.children[hit.key];
|
|
237
|
+
n.remove();
|
|
238
|
+
log.remove++;
|
|
239
|
+
log.changes.push(`remove ${describe(n)} from ${describe(parent)}`);
|
|
240
|
+
}
|
|
241
|
+
structural++;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
while (li < liveNodes.length || si < srcNodes.length) {
|
|
245
|
+
const live = li < liveNodes.length ? liveNodes[li] : null;
|
|
246
|
+
const src = si < srcNodes.length ? srcNodes[si] : null;
|
|
212
247
|
|
|
213
248
|
if (!live) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
si++;
|
|
249
|
+
const end = isRegionStart(src) ? findRegionEnd(srcNodes, si) : si;
|
|
250
|
+
insert(si, end, anchor);
|
|
251
|
+
si = end + 1;
|
|
218
252
|
continue;
|
|
219
253
|
}
|
|
220
254
|
if (!src) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
li++;
|
|
255
|
+
const end = isRegionStart(live) ? findRegionEnd(liveNodes, li) : li;
|
|
256
|
+
remove(li, end);
|
|
257
|
+
li = end + 1;
|
|
225
258
|
continue;
|
|
226
259
|
}
|
|
227
260
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
reconcileConditionalRegion(liveParent, liveNodes, li, liveEnd, srcNodes, si, srcEnd, log, insideIteration);
|
|
241
|
-
} else if (isIterationStart(live) && isIterationStart(src)) {
|
|
242
|
-
reconcileIterationRegion(liveParent, liveNodes, li, liveEnd, srcNodes, si, srcEnd, log);
|
|
243
|
-
}
|
|
261
|
+
if (isScript(src) && !isScript(live)) { si++; continue; }
|
|
262
|
+
if (isScript(live) && !isScript(src)) { li++; continue; }
|
|
263
|
+
|
|
264
|
+
const liveRegion = isRegionStart(live);
|
|
265
|
+
const srcRegion = isRegionStart(src);
|
|
266
|
+
|
|
267
|
+
if (liveRegion && srcRegion && isIterationStart(live) === isIterationStart(src)) {
|
|
268
|
+
const liveEnd = findRegionEnd(liveNodes, li);
|
|
269
|
+
const srcEnd = findRegionEnd(srcNodes, si);
|
|
270
|
+
const region = { parent, liveStart: live, srcStart: src, oldT: oldOf(live), newT: newOf(src) };
|
|
271
|
+
if (isIterationStart(live)) reconcileIteration(region, ctx);
|
|
272
|
+
else reconcileConditional(region, ctx);
|
|
244
273
|
li = liveEnd + 1;
|
|
245
274
|
si = srcEnd + 1;
|
|
246
275
|
continue;
|
|
247
276
|
}
|
|
248
|
-
if (
|
|
249
|
-
const end = findRegionEnd(liveNodes, li
|
|
250
|
-
|
|
251
|
-
log.remove++;
|
|
252
|
-
liveNodes[k].remove();
|
|
253
|
-
}
|
|
254
|
-
log.changes.push(`remove vibe region from ${describe(liveParent)}`);
|
|
277
|
+
if (liveRegion) {
|
|
278
|
+
const end = findRegionEnd(liveNodes, li);
|
|
279
|
+
remove(li, end);
|
|
255
280
|
li = end + 1;
|
|
256
281
|
continue;
|
|
257
282
|
}
|
|
258
|
-
if (
|
|
259
|
-
const end = findRegionEnd(srcNodes, si
|
|
260
|
-
|
|
261
|
-
liveParent.insertBefore(srcNodes[k].cloneNode(true), live);
|
|
262
|
-
log.insert++;
|
|
263
|
-
}
|
|
264
|
-
log.changes.push(`insert vibe region into ${describe(liveParent)}`);
|
|
283
|
+
if (srcRegion) {
|
|
284
|
+
const end = findRegionEnd(srcNodes, si);
|
|
285
|
+
insert(si, end, live);
|
|
265
286
|
si = end + 1;
|
|
266
287
|
continue;
|
|
267
288
|
}
|
|
268
289
|
|
|
269
290
|
if (isComponentWrapper(live) && isComponentWrapper(src)) {
|
|
270
|
-
|
|
271
|
-
li++;
|
|
272
|
-
si++;
|
|
273
|
-
continue;
|
|
274
|
-
}
|
|
275
|
-
if (live._vibeProps && src.hasAttribute('src')) {
|
|
276
|
-
const srcProps = {};
|
|
277
|
-
for (const a of src.attributes) {
|
|
278
|
-
if (a.name === FOUC_CLASS_OR_ATTR) continue;
|
|
279
|
-
if (a.name === 'class') {
|
|
280
|
-
const kept = a.value.split(/\s+/).filter((t) => t && t !== FOUC_CLASS_OR_ATTR);
|
|
281
|
-
if (kept.length) srcProps.class = kept.join(' ');
|
|
282
|
-
continue;
|
|
283
|
-
}
|
|
284
|
-
srcProps[a.name] = a.value;
|
|
285
|
-
}
|
|
286
|
-
const liveProps = live._vibeProps;
|
|
287
|
-
const liveKeys = Object.keys(liveProps);
|
|
288
|
-
const srcKeys = Object.keys(srcProps);
|
|
289
|
-
const diff =
|
|
290
|
-
liveKeys.length !== srcKeys.length ||
|
|
291
|
-
liveKeys.some((k) => liveProps[k] !== srcProps[k]) ||
|
|
292
|
-
srcKeys.some((k) => !(k in liveProps));
|
|
293
|
-
|
|
294
|
-
if (diff) {
|
|
295
|
-
const fresh = document.createElement(src.tagName);
|
|
296
|
-
if (src.tagName === 'DIV') fresh.className = 'component';
|
|
297
|
-
for (const [name, value] of Object.entries(srcProps)) {
|
|
298
|
-
fresh.setAttribute(name, value);
|
|
299
|
-
}
|
|
300
|
-
fresh.setAttribute(FOUC_CLASS_OR_ATTR, '');
|
|
301
|
-
const slotHtml = src.innerHTML.trim();
|
|
302
|
-
fresh._vibeSlotContent = slotHtml;
|
|
303
|
-
fresh._vibePluginSlot = slotHtml;
|
|
304
|
-
live.replaceWith(fresh);
|
|
305
|
-
log.replace++;
|
|
306
|
-
log.changes.push(
|
|
307
|
-
`re-mount ${describe(live)} (prop change) live=${JSON.stringify(liveProps)} src=${JSON.stringify(srcProps)}`,
|
|
308
|
-
);
|
|
309
|
-
li++;
|
|
310
|
-
si++;
|
|
311
|
-
continue;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
const srcChildren = trimWsEdges([...src.childNodes]);
|
|
316
|
-
const liveSlot = findOwnSlot(live, srcChildren);
|
|
317
|
-
if (liveSlot) reconcileChildren(liveSlot, srcChildren, log, insideIteration);
|
|
291
|
+
reconcileComponent(live, src, oldOf(live), newOf(src), ctx);
|
|
318
292
|
li++;
|
|
319
293
|
si++;
|
|
320
294
|
continue;
|
|
@@ -327,14 +301,7 @@ const reconcileRange = (liveParent, liveNodes, liStart, liEnd, srcNodes, siStart
|
|
|
327
301
|
}
|
|
328
302
|
|
|
329
303
|
if (live.nodeType === TEXT && src.nodeType === TEXT) {
|
|
330
|
-
|
|
331
|
-
updateBoundText(live, src.textContent, liveParent, log);
|
|
332
|
-
} else if (live.textContent !== src.textContent) {
|
|
333
|
-
live.textContent = src.textContent;
|
|
334
|
-
log.text++;
|
|
335
|
-
const preview = src.textContent.trim().slice(0, 60);
|
|
336
|
-
log.changes.push(`text in ${describe(liveParent)}: ${JSON.stringify(preview)}`);
|
|
337
|
-
}
|
|
304
|
+
reconcileText(parent, live, src, oldTree, oldOf(live), ctx);
|
|
338
305
|
li++;
|
|
339
306
|
si++;
|
|
340
307
|
continue;
|
|
@@ -350,124 +317,356 @@ const reconcileRange = (liveParent, liveNodes, liStart, liEnd, srcNodes, siStart
|
|
|
350
317
|
continue;
|
|
351
318
|
}
|
|
352
319
|
|
|
353
|
-
if (live.nodeType === ELEMENT && src.nodeType === ELEMENT &&
|
|
354
|
-
|
|
355
|
-
reconcileAttributes(live, src, log);
|
|
356
|
-
reconcileChildren(live, [...src.childNodes], log, insideIteration);
|
|
320
|
+
if (live.nodeType === ELEMENT && src.nodeType === ELEMENT && live.tagName === src.tagName) {
|
|
321
|
+
reconcileElement(live, src, oldOf(live) ?? adoptElement(live, parent, oldTree, ctx), newOf(src), ctx);
|
|
357
322
|
li++;
|
|
358
323
|
si++;
|
|
359
324
|
continue;
|
|
360
325
|
}
|
|
361
326
|
|
|
362
327
|
const align = findRealignment(liveNodes, li, srcNodes, si);
|
|
363
|
-
if (align && li + align.dl <
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
if (n.nodeType === ELEMENT && n.tagName === 'SCRIPT') continue;
|
|
367
|
-
log.remove++;
|
|
368
|
-
log.changes.push(`remove (extra) ${describe(n)} from ${describe(liveParent)}`);
|
|
369
|
-
n.remove();
|
|
370
|
-
}
|
|
371
|
-
const anchor = liveNodes[li + align.dl];
|
|
372
|
-
for (let i = 0; i < align.ds; i++) {
|
|
373
|
-
const n = srcNodes[si + i];
|
|
374
|
-
if (n.nodeType === ELEMENT && n.tagName === 'SCRIPT') continue;
|
|
375
|
-
log.insert++;
|
|
376
|
-
log.changes.push(`insert (new) ${describe(n)} into ${describe(liveParent)}`);
|
|
377
|
-
liveParent.insertBefore(n.cloneNode(true), anchor);
|
|
378
|
-
}
|
|
328
|
+
if (align && li + align.dl < liveNodes.length && si + align.ds < srcNodes.length) {
|
|
329
|
+
if (align.dl) remove(li, li + align.dl - 1);
|
|
330
|
+
if (align.ds) insert(si, si + align.ds - 1, liveNodes[li + align.dl]);
|
|
379
331
|
li += align.dl;
|
|
380
332
|
si += align.ds;
|
|
381
333
|
continue;
|
|
382
334
|
}
|
|
383
335
|
|
|
384
336
|
log.replace++;
|
|
385
|
-
|
|
386
|
-
|
|
337
|
+
remove(li, li);
|
|
338
|
+
insert(si, si, li + 1 < liveNodes.length ? liveNodes[li + 1] : anchor);
|
|
387
339
|
li++;
|
|
388
340
|
si++;
|
|
389
341
|
}
|
|
390
|
-
};
|
|
391
342
|
|
|
392
|
-
|
|
393
|
-
const liveNodes = [...liveParent.childNodes];
|
|
394
|
-
reconcileRange(liveParent, liveNodes, 0, liveNodes.length, sourceNodes, 0, sourceNodes.length, log, insideIteration);
|
|
343
|
+
return structural;
|
|
395
344
|
};
|
|
396
345
|
|
|
397
|
-
const
|
|
398
|
-
const
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
346
|
+
const reconcileText = (parent, live, src, oldTree, oldT, ctx) => {
|
|
347
|
+
const { log } = ctx;
|
|
348
|
+
const template = src.textContent;
|
|
349
|
+
let tree = oldT;
|
|
350
|
+
if (!tree && oldTree) {
|
|
351
|
+
tree = { parsed: live.textContent, element: parent, textNode: live, children: {} };
|
|
352
|
+
attach(oldTree, 'text_0', tree, parent, ctx);
|
|
353
|
+
}
|
|
354
|
+
if (tree) {
|
|
355
|
+
if (tree.parsed !== template) {
|
|
356
|
+
tree.parsed = template;
|
|
357
|
+
rehydrate(tree, ctx);
|
|
358
|
+
log.text++;
|
|
359
|
+
log.changes.push(`text in ${describe(parent)}: ${JSON.stringify(template.trim().slice(0, 60))}`);
|
|
360
|
+
}
|
|
361
|
+
if (hasBinding(template)) return;
|
|
362
|
+
} else if (hasBinding(template)) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (live.textContent !== src.textContent) {
|
|
366
|
+
live.textContent = src.textContent;
|
|
367
|
+
if (!tree) {
|
|
368
|
+
log.text++;
|
|
369
|
+
log.changes.push(`text in ${describe(parent)}: ${JSON.stringify(template.trim().slice(0, 60))}`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
};
|
|
403
373
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
374
|
+
const reconcileAttributes = (live, src, oldT, newT, ctx) => {
|
|
375
|
+
const { log } = ctx;
|
|
376
|
+
let srcHasNameBinding = false;
|
|
377
|
+
for (const attr of src.attributes) {
|
|
378
|
+
if (attr.name.includes('@[')) { srcHasNameBinding = true; continue; }
|
|
379
|
+
if (attr.name.startsWith(DEFER_ATTR_PREFIX) || hasBinding(attr.value)) continue;
|
|
380
|
+
let next = attr.name === 'class' ? mergeClass(attr.value, live) : attr.value;
|
|
381
|
+
if (attr.name.startsWith('on')) next = rewriteHandler(next, ctx.detached ? null : live, ctx.aliases);
|
|
382
|
+
if (live.getAttribute(attr.name) !== next) {
|
|
383
|
+
live.setAttribute(attr.name, next);
|
|
384
|
+
log.attr++;
|
|
385
|
+
log.changes.push(`attr ${describe(live)} [${attr.name}=${JSON.stringify(next)}]`);
|
|
386
|
+
}
|
|
407
387
|
}
|
|
408
|
-
if (!
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
if (
|
|
415
|
-
|
|
388
|
+
if (!srcHasNameBinding) {
|
|
389
|
+
for (const attr of [...live.attributes]) {
|
|
390
|
+
if (src.hasAttribute(attr.name)) continue;
|
|
391
|
+
if (isPreservedAttr(attr.name)) continue;
|
|
392
|
+
if (src.hasAttribute('data-' + attr.name) || src.hasAttribute(DEFER_ATTR_PREFIX + attr.name)) continue;
|
|
393
|
+
if (newT?.attributes?.[attr.name] !== undefined) continue;
|
|
394
|
+
if (attr.name === 'class') {
|
|
395
|
+
const kept = [...live.classList].filter((t) => PRESERVED_CLASSES.has(t));
|
|
396
|
+
if (kept.length) live.setAttribute('class', kept.join(' '));
|
|
397
|
+
else live.removeAttribute('class');
|
|
398
|
+
log.attr++;
|
|
399
|
+
log.changes.push(`attr ${describe(live)} [class trimmed]`);
|
|
400
|
+
continue;
|
|
416
401
|
}
|
|
417
|
-
|
|
402
|
+
live.removeAttribute(attr.name);
|
|
403
|
+
live._vibeBoundAttrs?.delete(attr.name);
|
|
404
|
+
log.attr++;
|
|
405
|
+
log.changes.push(`attr ${describe(live)} [-${attr.name}]`);
|
|
418
406
|
}
|
|
419
407
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
408
|
+
|
|
409
|
+
if (!oldT || !newT) return;
|
|
410
|
+
if (sameRecord(oldT.attributes, newT.attributes) && sameList(oldT.nameBindings, newT.nameBindings)) return;
|
|
411
|
+
for (const nb of oldT.nameBindings ?? []) {
|
|
412
|
+
if (newT.nameBindings?.includes(nb)) continue;
|
|
413
|
+
const produced = live._vibeNameBindings?.get(nb);
|
|
414
|
+
if (produced) {
|
|
415
|
+
live.removeAttribute(produced);
|
|
416
|
+
live._vibeNameBindings.delete(nb);
|
|
428
417
|
}
|
|
429
418
|
}
|
|
430
|
-
if (
|
|
419
|
+
if (newT.attributes) oldT.attributes = newT.attributes;
|
|
420
|
+
else delete oldT.attributes;
|
|
421
|
+
if (newT.nameBindings) oldT.nameBindings = newT.nameBindings;
|
|
422
|
+
else delete oldT.nameBindings;
|
|
423
|
+
rehydrate(oldT, ctx);
|
|
424
|
+
log.attr++;
|
|
425
|
+
log.changes.push(`bound attrs ${describe(live)} ${JSON.stringify(newT.attributes ?? {})}`);
|
|
426
|
+
};
|
|
431
427
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
428
|
+
const reconcileRawHost = (live, src, oldT, ctx) => {
|
|
429
|
+
const textTree = Object.values(oldT.children).find((c) => c.textNode);
|
|
430
|
+
const srcText = src.childNodes.length === 1 && src.firstChild.nodeType === TEXT ? src.firstChild : null;
|
|
431
|
+
if (textTree && srcText) {
|
|
432
|
+
if (textTree.parsed !== srcText.textContent) {
|
|
433
|
+
textTree.parsed = srcText.textContent;
|
|
434
|
+
rehydrate(textTree, ctx);
|
|
435
|
+
ctx.log.text++;
|
|
436
|
+
ctx.log.changes.push(`raw html in ${describe(live)}: ${JSON.stringify(srcText.textContent.trim().slice(0, 60))}`);
|
|
437
|
+
}
|
|
438
|
+
return true;
|
|
439
|
+
}
|
|
440
|
+
for (const key in oldT.children) delete oldT.children[key];
|
|
441
|
+
live.textContent = '';
|
|
442
|
+
live._vibeRawHtml = false;
|
|
443
|
+
live._vibeRawHtmlValue = undefined;
|
|
444
|
+
return false;
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
const reconcileElement = (live, src, oldT, newT, ctx) => {
|
|
448
|
+
reconcileAttributes(live, src, oldT, newT, ctx);
|
|
449
|
+
if (isScript(live)) return;
|
|
450
|
+
if (live._vibeRawHtml && oldT && reconcileRawHost(live, src, oldT, ctx)) return;
|
|
451
|
+
reconcileList(live, [...live.childNodes], [...src.childNodes], oldT, newT, ctx);
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
const propsOf = (src) => {
|
|
455
|
+
const props = {};
|
|
456
|
+
for (const a of src.attributes) {
|
|
457
|
+
if (a.name !== 'src' && a.name !== 'key' && !a.name.startsWith(DEFER_ATTR_PREFIX)) props[a.name] = a.value;
|
|
458
|
+
}
|
|
459
|
+
return props;
|
|
439
460
|
};
|
|
440
461
|
|
|
441
|
-
const
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
462
|
+
const propsDiffer = (live, srcProps) => {
|
|
463
|
+
const liveProps = live._vibeRemountProps ?? {};
|
|
464
|
+
const exprs = live._vibeIterPropExprs ?? [];
|
|
465
|
+
for (const k of new Set([...Object.keys(liveProps), ...Object.keys(srcProps)])) {
|
|
466
|
+
if (liveProps[k] === srcProps[k]) continue;
|
|
467
|
+
const bound = srcProps[k]?.match(/^@\[(.+)\]$/);
|
|
468
|
+
if (bound && exprs.some((e) => e.attrName === k && e.expr === bound[1])) continue;
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
return false;
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
const reconcileComponent = (live, src, oldT, newT, ctx) => {
|
|
475
|
+
if (live._vibeMountedSrc === undefined) {
|
|
476
|
+
if (!live.hasAttribute('src')) reconcileElement(live, src, oldT, newT, ctx);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
if (live.hasAttribute('src')) return;
|
|
480
|
+
const { log } = ctx;
|
|
481
|
+
const srcProps = propsOf(src);
|
|
482
|
+
const literalSrc = src.getAttribute('src');
|
|
483
|
+
const srcChanged = !!literalSrc && !hasBinding(literalSrc) && literalSrc !== live._vibeMountedSrc;
|
|
484
|
+
if (srcChanged || propsDiffer(live, srcProps)) {
|
|
485
|
+
live._vibeRemountProps = srcProps;
|
|
486
|
+
live._vibeSlotContent = src.innerHTML.trim();
|
|
487
|
+
if (srcChanged) remountComponent(live, literalSrc);
|
|
488
|
+
else forceRemount(live);
|
|
489
|
+
log.replace++;
|
|
490
|
+
log.changes.push(`re-mount ${describe(live)} (${srcChanged ? 'src' : 'props'} changed)`);
|
|
491
|
+
return;
|
|
445
492
|
}
|
|
446
|
-
if (!
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
const tplEl = tplEls[instanceIdx % tplEls.length];
|
|
453
|
-
instanceIdx++;
|
|
454
|
-
if (liveEl.tagName !== tplEl.tagName) continue;
|
|
455
|
-
reconcileAttributes(liveEl, tplEl, log);
|
|
456
|
-
reconcileChildren(liveEl, [...tplEl.childNodes], log, true);
|
|
493
|
+
if (oldT && newT && !sameRecord(oldT.attributes, newT.attributes)) {
|
|
494
|
+
if (newT.attributes) oldT.attributes = newT.attributes;
|
|
495
|
+
else delete oldT.attributes;
|
|
496
|
+
rehydrate(oldT, ctx);
|
|
497
|
+
log.attr++;
|
|
498
|
+
log.changes.push(`bound attrs ${describe(live)} ${JSON.stringify(newT.attributes ?? {})}`);
|
|
457
499
|
}
|
|
500
|
+
const srcChildren = trimWsEdges([...src.childNodes]);
|
|
501
|
+
const liveSlot = findOwnSlot(live, srcChildren);
|
|
502
|
+
if (!liveSlot) return;
|
|
503
|
+
const wrapperTree = oldT ?? live._vibeTree ?? null;
|
|
504
|
+
reconcileList(liveSlot, [...liveSlot.childNodes], srcChildren, findNodeByElement(wrapperTree, liveSlot), newT, ctx);
|
|
458
505
|
};
|
|
459
506
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
507
|
+
const ITERATION_HEADER = ['arrayPath', 'itemAlias', 'indexAlias', 'keyExpr'];
|
|
508
|
+
|
|
509
|
+
const reconcileIteration = ({ parent, liveStart, srcStart, oldT, newT }, ctx) => {
|
|
510
|
+
if (!oldT || !newT) return;
|
|
511
|
+
const { log } = ctx;
|
|
512
|
+
const headerChanged = ITERATION_HEADER.some((k) => oldT.meta[k] !== newT.meta[k]);
|
|
513
|
+
const templateChanged = oldT.meta.template.element.innerHTML !== newT.meta.template.element.innerHTML;
|
|
514
|
+
if (!headerChanged && !templateChanged) return;
|
|
515
|
+
|
|
516
|
+
liveStart.textContent = srcStart.textContent;
|
|
517
|
+
swapIterationTemplate(oldT, newT.meta);
|
|
518
|
+
if (!oldT.runtime.templateRemoved) return;
|
|
519
|
+
|
|
520
|
+
if (headerChanged) {
|
|
521
|
+
rerenderIteration(oldT, ctx.state, ctx.manifest, ctx.parentScope);
|
|
522
|
+
log.replace++;
|
|
523
|
+
log.changes.push(`re-render iteration ${oldT.meta.arrayPath} (header changed)`);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const instances = oldT.runtime.instances;
|
|
528
|
+
if (!instances.length) return;
|
|
529
|
+
const { itemAlias, indexAlias, scopeAliases } = oldT.meta;
|
|
530
|
+
const rowBase = { ...ctx, aliases: new Set(scopeAliases), detached: true, manifestBase: null };
|
|
531
|
+
|
|
532
|
+
if (!instances[0].tree) {
|
|
533
|
+
patchTreelessInstances(oldT, ctx.state, ctx.manifest, ctx.parentScope, (liveRow, freshRow) =>
|
|
534
|
+
reconcileElement(liveRow, freshRow, null, null, rowBase),
|
|
535
|
+
);
|
|
536
|
+
log.changes.push(`patch ${instances.length} rows of ${oldT.meta.arrayPath}`);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
for (let i = 0; i < instances.length; i++) {
|
|
541
|
+
const inst = instances[i];
|
|
542
|
+
const item = inst.liveItem !== undefined ? inst.liveItem : inst.item;
|
|
543
|
+
const locals = { [itemAlias]: item, [indexAlias]: inst.index };
|
|
544
|
+
const rowCtx = {
|
|
545
|
+
...rowBase,
|
|
546
|
+
state: createScopedState(ctx.state, locals, ctx.parentScope),
|
|
547
|
+
parentScope: { ...ctx.parentScope, ...locals },
|
|
548
|
+
};
|
|
549
|
+
const { nodes, anchor } = instanceRange(oldT, i);
|
|
550
|
+
if (!nodes.length) continue;
|
|
551
|
+
const before = nodes[0].previousSibling;
|
|
552
|
+
const tpl = materialize([...oldT.meta.template.element.childNodes], parent, rowCtx);
|
|
553
|
+
const structural = reconcileList(parent, nodes, tpl.nodes, inst.tree, { children: tpl.trees }, rowCtx, anchor);
|
|
554
|
+
if (structural) {
|
|
555
|
+
inst.clonedNodes = rangeBetween(before ? before.nextSibling : parent.firstChild, anchor);
|
|
556
|
+
inst.element = inst.clonedNodes.find((n) => n.nodeType === ELEMENT) ?? inst.element;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
stampInstanceScopes(oldT, ctx.parentScope);
|
|
560
|
+
log.changes.push(`patch ${instances.length} rows of ${oldT.meta.arrayPath}`);
|
|
561
|
+
};
|
|
463
562
|
|
|
563
|
+
const branchHtml = (branch) => branch?.element.innerHTML ?? null;
|
|
564
|
+
|
|
565
|
+
const reconcileConditional = ({ parent, liveStart, srcStart, oldT, newT }, ctx) => {
|
|
566
|
+
if (!oldT || !newT) return;
|
|
567
|
+
const { log } = ctx;
|
|
568
|
+
const oldBranches = oldT.meta.branches;
|
|
569
|
+
const newBranches = newT.meta.branches;
|
|
570
|
+
const exprChanged = oldT.meta.expression !== newT.meta.expression;
|
|
571
|
+
const templateChanged =
|
|
572
|
+
branchHtml(oldBranches.if) !== branchHtml(newBranches.if) ||
|
|
573
|
+
branchHtml(oldBranches.else) !== branchHtml(newBranches.else);
|
|
574
|
+
if (!exprChanged && !templateChanged) return;
|
|
575
|
+
|
|
576
|
+
const side =
|
|
577
|
+
oldT.runtime.activeBranch === oldBranches.if ? 'if' :
|
|
578
|
+
oldT.runtime.activeBranch === oldBranches.else ? 'else' : null;
|
|
579
|
+
liveStart.textContent = srcStart.textContent;
|
|
580
|
+
oldT.meta.expression = newT.meta.expression;
|
|
581
|
+
oldT.meta.branches = newBranches;
|
|
582
|
+
if (side) {
|
|
583
|
+
oldT.runtime.activeBranch = newBranches[side];
|
|
584
|
+
if (oldT.runtime.activeInstance) oldT.runtime.activeInstance.branch = newBranches[side];
|
|
585
|
+
}
|
|
586
|
+
if (!oldT.runtime.templateRemoved) return;
|
|
587
|
+
|
|
588
|
+
const before = oldT.runtime.activeInstance;
|
|
589
|
+
if (side && !newBranches[side]) remountBranch(oldT, ctx.state, ctx.manifest, ctx.parentScope);
|
|
590
|
+
else dispatchConditional(oldT, ctx.state, ctx.manifest, ctx.parentScope);
|
|
591
|
+
const active = oldT.runtime.activeInstance;
|
|
592
|
+
if (active !== before) {
|
|
593
|
+
log.replace++;
|
|
594
|
+
log.changes.push(`re-mount branch of if ${oldT.meta.expression}`);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
if (!active || !templateChanged) return;
|
|
598
|
+
|
|
599
|
+
const parentPath = ctx.manifestBase === null ? null : manifestPathOf(ctx.manifest, parent);
|
|
600
|
+
const branchCtx = {
|
|
601
|
+
...ctx,
|
|
602
|
+
detached: true,
|
|
603
|
+
manifestBase: parentPath === null ? null : `${parentPath}.${oldT._key}`,
|
|
604
|
+
};
|
|
605
|
+
const { endComment } = oldT.meta;
|
|
606
|
+
const tpl = materialize([...oldT.runtime.activeBranch.element.childNodes], parent, branchCtx);
|
|
607
|
+
const nodes = rangeBetween(liveStart.nextSibling, endComment);
|
|
608
|
+
const structural = reconcileList(parent, nodes, tpl.nodes, active.parsedTree, { children: tpl.trees }, branchCtx, endComment);
|
|
609
|
+
if (structural) {
|
|
610
|
+
active.nodes = rangeBetween(liveStart.nextSibling, endComment);
|
|
611
|
+
active.nodes.forEach((n, i) => {
|
|
612
|
+
branchNodeRegistry.set(n, { nodes: active.nodes, index: i });
|
|
613
|
+
if (n.nodeType !== ELEMENT) return;
|
|
614
|
+
managedNodes.add(n);
|
|
615
|
+
if (oldT.meta.scopeAliases?.length) n.__vibeScope = ctx.state;
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
for (const key in oldT.children) delete oldT.children[key];
|
|
619
|
+
for (const key in active.parsedTree?.children ?? {}) oldT.children[key] = active.parsedTree.children[key];
|
|
620
|
+
log.changes.push(`patch branch of if ${oldT.meta.expression}`);
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
const scopeAround = (el) => {
|
|
624
|
+
for (let n = el; n; n = n.parentNode) if (n.__vibeScope) return n.__vibeScope;
|
|
625
|
+
return null;
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
const contextFor = (live, manifest, log) => {
|
|
629
|
+
const root = manifest.__live;
|
|
630
|
+
const overlay = scopeAround(live);
|
|
631
|
+
return {
|
|
632
|
+
manifest,
|
|
633
|
+
log,
|
|
634
|
+
state: overlay ? createScopedState(root, overlay) : root,
|
|
635
|
+
parentScope: overlay ?? {},
|
|
636
|
+
aliases: new Set(Object.keys(overlay ?? {})),
|
|
637
|
+
detached: !!overlay && live._vibeMountedSrc === undefined,
|
|
638
|
+
manifestBase: overlay ? null : undefined,
|
|
639
|
+
};
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
export const reconcile = (target, source, manifest) => {
|
|
643
|
+
const live = typeof target === 'string' ? document.querySelector(target) : target;
|
|
464
644
|
const log = { text: 0, attr: 0, insert: 0, remove: 0, replace: 0, changes: [] };
|
|
645
|
+
if (!live) return Promise.resolve(log);
|
|
646
|
+
|
|
465
647
|
const tpl = document.createElement('template');
|
|
466
648
|
tpl.innerHTML = typeof source === 'string' ? source : source.outerHTML;
|
|
467
649
|
parkFetchableSrc(tpl.content);
|
|
468
|
-
|
|
650
|
+
const box = document.createElement('div');
|
|
651
|
+
box._vibeComponentId = findComponentIdForElement(live);
|
|
652
|
+
box.append(...tpl.content.childNodes);
|
|
653
|
+
|
|
654
|
+
const ctx = contextFor(live, manifest, log);
|
|
655
|
+
const newTree = parse(box, undefined, ctx.aliases);
|
|
656
|
+
const oldTree = live._vibeTree ?? findNodeByElement(manifest.__tree, live);
|
|
657
|
+
reconcileList(live, [...live.childNodes], [...box.childNodes], oldTree, newTree, ctx);
|
|
469
658
|
|
|
470
659
|
return new Promise((resolve) => {
|
|
471
660
|
requestAnimationFrame(() => requestAnimationFrame(() => resolve(log)));
|
|
472
661
|
});
|
|
473
662
|
};
|
|
663
|
+
|
|
664
|
+
export const refreshComponent = (el, rawHtml, manifest) => {
|
|
665
|
+
const componentIds = [];
|
|
666
|
+
eachComponentIn(el, (node, id) => {
|
|
667
|
+
if (!componentIds.includes(id)) componentIds.push(id);
|
|
668
|
+
});
|
|
669
|
+
const html = renderComponentTemplate(rawHtml, el._vibeRemountProps ?? {}, el._vibeSlotContent ?? '', { componentIds });
|
|
670
|
+
el._vibeRawSource = rawHtml;
|
|
671
|
+
return reconcile(el, html, manifest);
|
|
672
|
+
};
|