@ape-egg/vibe 4.2.0 → 4.3.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/README.md +2 -2
- 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 +90 -10
- package/runtime/manifest.js +42 -0
- package/runtime/parse.js +14 -11
- package/runtime/reconcile.js +488 -289
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Vibe
|
|
2
2
|
|
|
3
|
-
**Version 4.
|
|
3
|
+
**Version 4.3.0** — Runtime-first reactivity for plain HTML. Drop a script tag into any page and get reactive bindings, control flow, and URL-loaded components — no build step required. Compile later if you want; the compiler is a separate, optional package (`@ape-egg/vibe-compiler`).
|
|
4
4
|
|
|
5
5
|
## Security model & CSP
|
|
6
6
|
|
|
@@ -312,7 +312,7 @@ Pre-boot, state accumulates and boot is queued as always — on a fresh document
|
|
|
312
312
|
|
|
313
313
|
### Subtree Reconciliation (advanced)
|
|
314
314
|
|
|
315
|
-
`$.reconcile(el, html)`
|
|
315
|
+
`$.reconcile(el, html)` is the template-aware subtree reconciliation the vite plugin's HMR path runs on: the fresh `html` is parsed by the same parser that built the page, and every paired node updates the runtime's knowledge before the DOM — a changed text or attribute template is re-hydrated in place, an iteration or conditional swaps its stored template so later renders follow it, structural inserts are hydrated under the right scope — while DOM identity, focus, selection and scroll position survive. The underscore-prefixed helpers beside it (`$._refreshComponent(el, rawHtml)`, `$._remountComponent(el)`, `$._renderComponent(...)`) are tooling plumbing, not application API; all of it may still evolve.
|
|
316
316
|
|
|
317
317
|
### Dehydrate
|
|
318
318
|
|
package/hot-module-refresh.js
CHANGED
|
Binary file
|
package/llms.txt
CHANGED
|
@@ -338,7 +338,7 @@ A Promise that resolves once vibe has finished initial parse + hydrate + compone
|
|
|
338
338
|
|
|
339
339
|
### `$.reconcile(el, html)` (advanced)
|
|
340
340
|
|
|
341
|
-
|
|
341
|
+
Template-aware subtree reconciliation used by `@ape-egg/vite-plugin-vibe` for surgical HMR: the fresh `html` is parsed by the same parser that built the page and diffed against `el`'s children together with the runtime's own tree, so a changed text or attribute template is re-hydrated in place, an iteration or conditional swaps its stored template (later renders follow it), and DOM identity, focus, selection and scroll position survive; component wrappers re-mount only when their src or props changed. Treat as plumbing — shape may evolve. (`$._refreshComponent`, `$._remountComponent` and other underscore-prefixed `$` members are tooling internals, not API.)
|
|
342
342
|
|
|
343
343
|
## Scoped Variables in Iterations
|
|
344
344
|
|
package/package.json
CHANGED
package/runtime/affected.js
CHANGED
|
@@ -67,7 +67,7 @@ const nameBindingsOf = (tree) => {
|
|
|
67
67
|
if (tree._nbSrc === tree.nameBindings && tree._nbEl === tree.element) return tree._nb;
|
|
68
68
|
unsubscribeBindings(tree._nb);
|
|
69
69
|
const out = [];
|
|
70
|
-
for (const nameBinding of tree.nameBindings) {
|
|
70
|
+
for (const nameBinding of tree.nameBindings ?? []) {
|
|
71
71
|
BINDING_REGEX.lastIndex = 0;
|
|
72
72
|
let m;
|
|
73
73
|
while ((m = BINDING_REGEX.exec(nameBinding))) {
|
|
@@ -87,6 +87,61 @@ const evaluateCondition = (expression, state, element = null) =>
|
|
|
87
87
|
export const nodeSubscriberOf = (tree, kind) =>
|
|
88
88
|
(tree._sub ??= { kind, node: tree, anchor: tree.meta.startComment });
|
|
89
89
|
|
|
90
|
+
export const ownBindingsOf = (tree, scope) => {
|
|
91
|
+
const out = [];
|
|
92
|
+
for (const m of textBindingsOf(tree)) {
|
|
93
|
+
out.push({
|
|
94
|
+
matchOuter: m.outer,
|
|
95
|
+
matchInner: m.inner,
|
|
96
|
+
input: m.input,
|
|
97
|
+
element: tree.element,
|
|
98
|
+
textNode: tree.textNode,
|
|
99
|
+
scopedState: scope,
|
|
100
|
+
binding: m,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
for (const { attrName, matches } of attrBindingsOf(tree)) {
|
|
104
|
+
const attrValue = tree.attributes[attrName];
|
|
105
|
+
if (!matches.length) {
|
|
106
|
+
out.push({
|
|
107
|
+
type: "attribute",
|
|
108
|
+
attrName,
|
|
109
|
+
attrValue,
|
|
110
|
+
matchOuter: null,
|
|
111
|
+
matchInner: null,
|
|
112
|
+
element: tree.element,
|
|
113
|
+
scopedState: scope,
|
|
114
|
+
binding: null,
|
|
115
|
+
});
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
for (const m of matches) {
|
|
119
|
+
out.push({
|
|
120
|
+
type: "attribute",
|
|
121
|
+
attrName,
|
|
122
|
+
attrValue,
|
|
123
|
+
matchOuter: m.outer,
|
|
124
|
+
matchInner: m.inner,
|
|
125
|
+
element: tree.element,
|
|
126
|
+
scopedState: scope,
|
|
127
|
+
binding: m,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
for (const m of nameBindingsOf(tree)) {
|
|
132
|
+
out.push({
|
|
133
|
+
type: "nameBinding",
|
|
134
|
+
nameBinding: m.nameBinding,
|
|
135
|
+
matchOuter: m.outer,
|
|
136
|
+
matchInner: m.inner,
|
|
137
|
+
element: tree.element,
|
|
138
|
+
scopedState: scope,
|
|
139
|
+
binding: m,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
};
|
|
144
|
+
|
|
90
145
|
const isIdentChar = (c) =>
|
|
91
146
|
(c >= "a" && c <= "z") ||
|
|
92
147
|
(c >= "A" && c <= "Z") ||
|
package/runtime/conditionals.js
CHANGED
|
@@ -262,6 +262,18 @@ export const dispatchConditional = (node, newState, manifest, parentScope = {})
|
|
|
262
262
|
}
|
|
263
263
|
};
|
|
264
264
|
|
|
265
|
+
export const remountBranch = (node, state, manifest, parentScope = {}) => {
|
|
266
|
+
if (!node.runtime.templateRemoved) return;
|
|
267
|
+
const sub = nodeSubscriberOf(node, 'conditional');
|
|
268
|
+
sub.lastScope = state;
|
|
269
|
+
beginTracking(sub, overlayKeysOf(state));
|
|
270
|
+
const result = evaluateCondition(node.meta.expression, state, node.meta.startComment?.parentElement);
|
|
271
|
+
endTracking();
|
|
272
|
+
const branch = result ? node.meta.branches.if : node.meta.branches.else;
|
|
273
|
+
mountBranch(node, branch, state, manifest, parentScope);
|
|
274
|
+
node.runtime.activeBranch = branch;
|
|
275
|
+
};
|
|
276
|
+
|
|
265
277
|
export const updateConditional = (node, newState, oldState, manifest, parentScope = {}) => {
|
|
266
278
|
const { expression, branches, startComment } = node.meta;
|
|
267
279
|
|
package/runtime/constants.js
CHANGED
package/runtime/index.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import state, { silentSet } from './state.js';
|
|
2
2
|
import parse from './parse.js';
|
|
3
|
-
import createManifest, {
|
|
3
|
+
import createManifest, {
|
|
4
|
+
setManifestEntry,
|
|
5
|
+
manifestPathOf,
|
|
6
|
+
removeManifestSubtree,
|
|
7
|
+
findNodeByElement,
|
|
8
|
+
addToManifest,
|
|
9
|
+
} from './manifest.js';
|
|
4
10
|
import hydrate from './hydrate.js';
|
|
5
11
|
import affected from './affected.js';
|
|
6
12
|
import { deepMerge, hash, setRootState } from './utils.js';
|
|
@@ -25,13 +31,13 @@ import {
|
|
|
25
31
|
STAGED_ATTR,
|
|
26
32
|
FOUC_CLASS_OR_ATTR,
|
|
27
33
|
} from './constants.js';
|
|
28
|
-
import { processComponent, abortComponentFetch, collectComponentIds, releaseOrphanedComponentState, renderComponentTemplate, executeCompiledComponentScripts } from './component.js';
|
|
34
|
+
import { processComponent, abortComponentFetch, collectComponentIds, releaseOrphanedComponentState, renderComponentTemplate, executeCompiledComponentScripts, forceRemount } from './component.js';
|
|
29
35
|
import { configureComponentCache, clearComponentCache } from './component-cache.js';
|
|
30
36
|
import { debugLog } from './debug.js';
|
|
31
37
|
import { pruneDisconnected, beginFlush } from './tracking.js';
|
|
32
38
|
import dispatchFlush from './dispatch.js';
|
|
33
39
|
import { shouldCleanup, cleanup } from './cleanup.js';
|
|
34
|
-
import { reconcile } from './reconcile.js';
|
|
40
|
+
import { reconcile, refreshComponent } from './reconcile.js';
|
|
35
41
|
import {
|
|
36
42
|
buildHyperspeedManifest,
|
|
37
43
|
hyperspeedManifest,
|
|
@@ -66,38 +72,6 @@ const navigateTree = (tree, path) => {
|
|
|
66
72
|
return path.split('.').filter(k => k).reduce((node, key) => node?.children?.[key], tree);
|
|
67
73
|
};
|
|
68
74
|
|
|
69
|
-
const findNodeByElement = (tree, target) => {
|
|
70
|
-
if (!target) return null;
|
|
71
|
-
const seen = new Set();
|
|
72
|
-
const walk = (node) => {
|
|
73
|
-
if (!node || typeof node !== 'object' || seen.has(node)) return null;
|
|
74
|
-
seen.add(node);
|
|
75
|
-
if (node.element === target) return node;
|
|
76
|
-
if (node.children) {
|
|
77
|
-
for (const key in node.children) {
|
|
78
|
-
const hit = walk(node.children[key]);
|
|
79
|
-
if (hit) return hit;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
const branchTree = node.runtime?.activeInstance?.parsedTree;
|
|
83
|
-
if (branchTree) {
|
|
84
|
-
const hit = walk(branchTree);
|
|
85
|
-
if (hit) return hit;
|
|
86
|
-
}
|
|
87
|
-
const instances = node.runtime?.instances;
|
|
88
|
-
if (instances) {
|
|
89
|
-
for (let i = 0; i < instances.length; i++) {
|
|
90
|
-
if (instances[i]?.tree) {
|
|
91
|
-
const hit = walk(instances[i].tree);
|
|
92
|
-
if (hit) return hit;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return null;
|
|
97
|
-
};
|
|
98
|
-
return walk(tree);
|
|
99
|
-
};
|
|
100
|
-
|
|
101
75
|
const ensureNode = (tree, path) => {
|
|
102
76
|
const keys = path.split('.').filter(k => k);
|
|
103
77
|
return keys.reduce((node, key) => {
|
|
@@ -108,16 +82,6 @@ const ensureNode = (tree, path) => {
|
|
|
108
82
|
}, tree);
|
|
109
83
|
};
|
|
110
84
|
|
|
111
|
-
const addToManifest = (tree, manifest, dotPath) => {
|
|
112
|
-
setManifestEntry(manifest, dotPath, tree.element);
|
|
113
|
-
|
|
114
|
-
if (tree.children && Object.keys(tree.children).length > 0) {
|
|
115
|
-
Object.keys(tree.children).forEach((childKey) => {
|
|
116
|
-
addToManifest(tree.children[childKey], manifest, `${dotPath}.${childKey}`);
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
|
|
121
85
|
const processCoreLoop = (node, parsedNode, state, manifest, isNewNode, debug) => {
|
|
122
86
|
let hydratedCount = 0;
|
|
123
87
|
let iteratedCount = 0;
|
|
@@ -593,7 +557,17 @@ const main = (s, config = {}, stringSelector = '') => {
|
|
|
593
557
|
});
|
|
594
558
|
|
|
595
559
|
Object.defineProperty($, 'reconcile', {
|
|
596
|
-
value: reconcile,
|
|
560
|
+
value: (target, source) => reconcile(target, source, manifest),
|
|
561
|
+
enumerable: false,
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
Object.defineProperty($, '_refreshComponent', {
|
|
565
|
+
value: (el, rawHtml) => refreshComponent(el, rawHtml, manifest),
|
|
566
|
+
enumerable: false,
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
Object.defineProperty($, '_remountComponent', {
|
|
570
|
+
value: (el) => forceRemount(el, debug),
|
|
597
571
|
enumerable: false,
|
|
598
572
|
});
|
|
599
573
|
|
|
@@ -779,6 +753,7 @@ const main = (s, config = {}, stringSelector = '') => {
|
|
|
779
753
|
const dotAnnotation = manifestPathOf(manifest, target);
|
|
780
754
|
|
|
781
755
|
const parsedNode = parse(node);
|
|
756
|
+
node._vibeTree = parsedNode;
|
|
782
757
|
|
|
783
758
|
if (parsedNode.stats?.skipped) {
|
|
784
759
|
totalSkipped += parsedNode.stats.skipped;
|
package/runtime/iterate.js
CHANGED
|
@@ -23,6 +23,8 @@ import {
|
|
|
23
23
|
ITER_PROP_PATH,
|
|
24
24
|
COMPONENT_ID_ATTR,
|
|
25
25
|
COMPONENT_SRC_SELECTOR,
|
|
26
|
+
DEFER_ATTR_PREFIX,
|
|
27
|
+
INTERNAL_ATTR_PREFIX,
|
|
26
28
|
} from './constants.js';
|
|
27
29
|
|
|
28
30
|
import * as compiled from './pre-compiled-iterations.js';
|
|
@@ -71,6 +73,14 @@ const BATCH_ATTR_BINDING_REGEX = new RegExp(
|
|
|
71
73
|
String.raw`(\s)([\w-]+)="@\[(${BATCH_BINDING_INNER})\]"`,
|
|
72
74
|
'g',
|
|
73
75
|
);
|
|
76
|
+
const DEFERRED_ATTR_REGEX = new RegExp(
|
|
77
|
+
String.raw`(\s)${DEFER_ATTR_PREFIX}(?!${INTERNAL_ATTR_PREFIX.slice(DEFER_ATTR_PREFIX.length)})(?=[\w-])`,
|
|
78
|
+
'g',
|
|
79
|
+
);
|
|
80
|
+
const realAttrName = (name) =>
|
|
81
|
+
name.startsWith(DEFER_ATTR_PREFIX) && !name.startsWith(INTERNAL_ATTR_PREFIX)
|
|
82
|
+
? name.slice(DEFER_ATTR_PREFIX.length)
|
|
83
|
+
: name;
|
|
74
84
|
|
|
75
85
|
const mapTagSpans = (html, fn) => {
|
|
76
86
|
let out = '';
|
|
@@ -131,13 +141,14 @@ export const compileBatchFn = (template, itemAlias, indexAlias, stateKeys, ancho
|
|
|
131
141
|
const attrs = [...el.attributes];
|
|
132
142
|
for (let a = 0; a < attrs.length; a++) {
|
|
133
143
|
const attr = attrs[a];
|
|
134
|
-
|
|
144
|
+
const prop = realAttrName(attr.name);
|
|
145
|
+
if (!DOM_PROPERTIES.includes(prop)) continue;
|
|
135
146
|
const m = attr.value.match(PURE_BINDING_REGEX);
|
|
136
147
|
if (!m) continue;
|
|
137
148
|
let expr = m[1];
|
|
138
149
|
if (componentId) expr = expr.replace(THIS_PROP_REGEX, `$['${componentId}'].$1`);
|
|
139
150
|
indexes.push(domPropertyWrites.length);
|
|
140
|
-
domPropertyWrites.push({ prop
|
|
151
|
+
domPropertyWrites.push({ prop, expr });
|
|
141
152
|
}
|
|
142
153
|
if (indexes.length > 0) {
|
|
143
154
|
el.setAttribute(BATCH_ATTR, indexes.join(','));
|
|
@@ -172,7 +183,7 @@ export const compileBatchFn = (template, itemAlias, indexAlias, stateKeys, ancho
|
|
|
172
183
|
let code = escaped;
|
|
173
184
|
|
|
174
185
|
let needsCiWalker = false;
|
|
175
|
-
code = mapTagSpans(code, (tag) => tag.replace(BATCH_NAME_BINDING_REGEX, (_, _ws, expr) => {
|
|
186
|
+
code = mapTagSpans(code, (tag) => tag.replace(DEFERRED_ATTR_REGEX, '$1').replace(BATCH_NAME_BINDING_REGEX, (_, _ws, expr) => {
|
|
176
187
|
let decExpr = decodeEntities(expr);
|
|
177
188
|
if (decExpr.includes('[') || decExpr.includes('(')) {
|
|
178
189
|
return nameBindingEmit(decExpr);
|
|
@@ -679,6 +690,16 @@ const isolateInlinedComponentIds = (container) => {
|
|
|
679
690
|
return true;
|
|
680
691
|
};
|
|
681
692
|
|
|
693
|
+
export const prepareTemplateComponents = (container, scopedState) => {
|
|
694
|
+
const components = container.querySelectorAll(COMPONENT_SRC_SELECTOR);
|
|
695
|
+
const scopeKeys = overlayKeysOf(scopedState);
|
|
696
|
+
for (let i = 0; i < components.length; i++) {
|
|
697
|
+
const el = components[i];
|
|
698
|
+
if (el._vibeSlotContent === undefined) el._vibeSlotContent = el.innerHTML;
|
|
699
|
+
if (scopeKeys) resolveScopedSrc(el, scopedState, scopeKeys);
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
|
|
682
703
|
export const initializeBlock = (templateNodes, scopedState, cachedTree = null, componentId = null, aliasSet = undefined, isolateComponents = false) => {
|
|
683
704
|
let tree;
|
|
684
705
|
let clonedNodes = [];
|
|
@@ -697,13 +718,7 @@ export const initializeBlock = (templateNodes, scopedState, cachedTree = null, c
|
|
|
697
718
|
}
|
|
698
719
|
}
|
|
699
720
|
|
|
700
|
-
|
|
701
|
-
const scopeKeys = overlayKeysOf(scopedState);
|
|
702
|
-
for (let i = 0; i < components.length; i++) {
|
|
703
|
-
const el = components[i];
|
|
704
|
-
if (el._vibeSlotContent === undefined) el._vibeSlotContent = el.innerHTML;
|
|
705
|
-
if (scopeKeys) resolveScopedSrc(el, scopedState, scopeKeys);
|
|
706
|
-
}
|
|
721
|
+
prepareTemplateComponents(parseContainer, scopedState);
|
|
707
722
|
|
|
708
723
|
if (isolateComponents && isolateInlinedComponentIds(parseContainer)) {
|
|
709
724
|
executeCompiledComponentScriptsIn([...parseContainer.childNodes]);
|
|
@@ -1016,6 +1031,71 @@ export const updateIteration = (iterationNode, newState, oldState, manifest, par
|
|
|
1016
1031
|
stampScopes(iterationNode, manifest, parentScope);
|
|
1017
1032
|
};
|
|
1018
1033
|
|
|
1034
|
+
export const swapIterationTemplate = (iterationNode, meta) => {
|
|
1035
|
+
const { arrayPath, itemAlias, indexAlias, keyExpr, hasScopedHandlers, scopeAliases, template } = meta;
|
|
1036
|
+
Object.assign(iterationNode.meta, { arrayPath, itemAlias, indexAlias, keyExpr, hasScopedHandlers, scopeAliases, template });
|
|
1037
|
+
delete iterationNode.compiled;
|
|
1038
|
+
const rt = iterationNode.runtime;
|
|
1039
|
+
rt.batchFn = null;
|
|
1040
|
+
rt.lastBatchHtml = undefined;
|
|
1041
|
+
rt.domPropertyWrites = null;
|
|
1042
|
+
rt.stateKeys = null;
|
|
1043
|
+
};
|
|
1044
|
+
|
|
1045
|
+
export const rerenderIteration = (iterationNode, state, manifest, parentScope = {}) => {
|
|
1046
|
+
if (!iterationNode.runtime.templateRemoved) return;
|
|
1047
|
+
const { arrayPath, startComment } = iterationNode.meta;
|
|
1048
|
+
const resolvedExpr = resolveThisPath(arrayPath, startComment.parentElement);
|
|
1049
|
+
const trackSub = nodeSubscriberOf(iterationNode, 'iteration');
|
|
1050
|
+
trackSub.lastScope = state;
|
|
1051
|
+
beginTracking(trackSub, overlayKeysOf(state));
|
|
1052
|
+
const array = evalInScope(resolvedExpr, state, startComment.parentElement) ?? resolvePath(state, resolvedExpr);
|
|
1053
|
+
endTracking();
|
|
1054
|
+
bulkReplace(iterationNode, Array.isArray(array) ? array : [], state, manifest, parentScope);
|
|
1055
|
+
};
|
|
1056
|
+
|
|
1057
|
+
export const instanceRange = (iterationNode, index) => {
|
|
1058
|
+
const parent = iterationNode.meta.startComment.parentNode;
|
|
1059
|
+
const start = findInstanceAnchor(iterationNode.runtime.instances[index], parent);
|
|
1060
|
+
const anchor = resolveInsertBefore(iterationNode, index + 1, parent);
|
|
1061
|
+
const nodes = [];
|
|
1062
|
+
for (let cur = start; cur && cur !== anchor; cur = cur.nextSibling) nodes.push(cur);
|
|
1063
|
+
return { nodes, anchor };
|
|
1064
|
+
};
|
|
1065
|
+
|
|
1066
|
+
export const patchTreelessInstances = (iterationNode, state, manifest, parentScope, patchRow) => {
|
|
1067
|
+
const { itemAlias, indexAlias, template, startComment } = iterationNode.meta;
|
|
1068
|
+
const rt = iterationNode.runtime;
|
|
1069
|
+
const array = rt.instances.map((inst) => inst.item);
|
|
1070
|
+
|
|
1071
|
+
if (!canUseBatchRender(template)) {
|
|
1072
|
+
bulkReplace(iterationNode, array, state, manifest, parentScope);
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
const stateKeys = Object.keys(state);
|
|
1077
|
+
const compiledBatch = compileBatchFn(template, itemAlias, indexAlias, stateKeys, startComment.parentNode);
|
|
1078
|
+
rt.batchFn = compiledBatch.fn;
|
|
1079
|
+
rt.domPropertyWrites = compiledBatch.domPropertyWrites;
|
|
1080
|
+
rt.stateKeys = stateKeys;
|
|
1081
|
+
const html = rt.batchFn(array, ...stateKeys.map((k) => state[k]), state);
|
|
1082
|
+
rt.lastBatchHtml = html;
|
|
1083
|
+
|
|
1084
|
+
batchParseTemplate.innerHTML = html;
|
|
1085
|
+
const frag = batchParseTemplate.content;
|
|
1086
|
+
markBoundValues(frag, html);
|
|
1087
|
+
const fresh = [...frag.children];
|
|
1088
|
+
if (fresh.length !== rt.instances.length) {
|
|
1089
|
+
bulkReplace(iterationNode, array, state, manifest, parentScope);
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
const freshInstances = fresh.map((element, i) => ({ element, item: array[i], index: i }));
|
|
1094
|
+
applyDomPropertyWrites(freshInstances, array, state, itemAlias, indexAlias, rt.domPropertyWrites);
|
|
1095
|
+
for (let i = 0; i < fresh.length; i++) patchRow(rt.instances[i].element, fresh[i]);
|
|
1096
|
+
stampScopes(iterationNode, manifest, parentScope);
|
|
1097
|
+
};
|
|
1098
|
+
|
|
1019
1099
|
const releaseRemovedSubtrees = (removedRoots, manifest) => {
|
|
1020
1100
|
if (!manifest || removedRoots.length === 0) return;
|
|
1021
1101
|
const tree = manifest.__tree;
|
package/runtime/manifest.js
CHANGED
|
@@ -47,6 +47,48 @@ export const removeManifestSubtree = (manifest, dotPath) => {
|
|
|
47
47
|
}
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
+
export const findNodeByElement = (tree, target) => {
|
|
51
|
+
if (!target) return null;
|
|
52
|
+
const seen = new Set();
|
|
53
|
+
const walk = (node) => {
|
|
54
|
+
if (!node || typeof node !== 'object' || seen.has(node)) return null;
|
|
55
|
+
seen.add(node);
|
|
56
|
+
if (node.element === target) return node;
|
|
57
|
+
if (node.children) {
|
|
58
|
+
for (const key in node.children) {
|
|
59
|
+
const hit = walk(node.children[key]);
|
|
60
|
+
if (hit) return hit;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const branchTree = node.runtime?.activeInstance?.parsedTree;
|
|
64
|
+
if (branchTree) {
|
|
65
|
+
const hit = walk(branchTree);
|
|
66
|
+
if (hit) return hit;
|
|
67
|
+
}
|
|
68
|
+
const instances = node.runtime?.instances;
|
|
69
|
+
if (instances) {
|
|
70
|
+
for (let i = 0; i < instances.length; i++) {
|
|
71
|
+
if (instances[i]?.tree) {
|
|
72
|
+
const hit = walk(instances[i].tree);
|
|
73
|
+
if (hit) return hit;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
};
|
|
79
|
+
return walk(tree);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const addToManifest = (tree, manifest, dotPath) => {
|
|
83
|
+
setManifestEntry(manifest, dotPath, tree.element);
|
|
84
|
+
|
|
85
|
+
if (tree.children && Object.keys(tree.children).length > 0) {
|
|
86
|
+
Object.keys(tree.children).forEach((childKey) => {
|
|
87
|
+
addToManifest(tree.children[childKey], manifest, `${dotPath}.${childKey}`);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
50
92
|
const recursive = (tree, results, tagChain) => {
|
|
51
93
|
setManifestEntry(results, tagChain.join('.'), tree.element);
|
|
52
94
|
|
package/runtime/parse.js
CHANGED
|
@@ -26,6 +26,19 @@ import './this-scope.js';
|
|
|
26
26
|
|
|
27
27
|
const findComponentIdForElement = (element) => closestComponentId(element);
|
|
28
28
|
|
|
29
|
+
export const rewriteHandler = (value, element, aliasSet) => {
|
|
30
|
+
let v = value;
|
|
31
|
+
if (v.includes('this.')) {
|
|
32
|
+
const componentId = findComponentIdForElement(element);
|
|
33
|
+
if (componentId) {
|
|
34
|
+
v = v.replace(STATE_THIS_PROP_REGEX, (_, prop) => `$['${componentId}'].${prop}`);
|
|
35
|
+
v = v.replace(THIS_PROP_REGEX, (_, prop) => `$this(this).${prop}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (aliasSet && aliasSet.size > 0) v = rewriteHandlerAliases(v, aliasSet);
|
|
39
|
+
return v;
|
|
40
|
+
};
|
|
41
|
+
|
|
29
42
|
const warnedStaticKeys = new Set();
|
|
30
43
|
const warnStaticKey = (key) => {
|
|
31
44
|
if (warnedStaticKeys.has(key)) return;
|
|
@@ -97,17 +110,7 @@ const captureAttributeBindings = (element, aliasSet) => {
|
|
|
97
110
|
}
|
|
98
111
|
|
|
99
112
|
if (attr.name.startsWith('on')) {
|
|
100
|
-
|
|
101
|
-
if (v.includes('this.')) {
|
|
102
|
-
const componentId = findComponentIdForElement(element);
|
|
103
|
-
if (componentId) {
|
|
104
|
-
v = v.replace(STATE_THIS_PROP_REGEX, (_, prop) => `$['${componentId}'].${prop}`);
|
|
105
|
-
v = v.replace(THIS_PROP_REGEX, (_, prop) => `$this(this).${prop}`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
if (aliasSet && aliasSet.size > 0) {
|
|
109
|
-
v = rewriteHandlerAliases(v, aliasSet);
|
|
110
|
-
}
|
|
113
|
+
const v = rewriteHandler(attr.value, element, aliasSet);
|
|
111
114
|
if (v !== attr.value) element.setAttribute(attr.name, v);
|
|
112
115
|
}
|
|
113
116
|
|