@ape-egg/vibe 1.9.1 → 1.9.6
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/CHANGELOG.md +63 -0
- package/README.md +62 -111
- package/ROADMAP.md +6 -12
- package/compiler/native/vibe-compiler-darwin-arm64 +0 -0
- package/compiler/native/vibe-compiler-linux-x64 +0 -0
- package/compiler/src/compiler/PRE-RENDERING-IMPLEMENTATION.md +1 -1
- package/compiler/src/compiler/compile.rs +66 -6
- package/compiler/src/parser/html.rs +7 -1
- package/llms.txt +25 -0
- package/package.json +1 -1
- package/runtime/affected.js +141 -52
- package/runtime/component.js +125 -8
- package/runtime/conditionals.js +45 -3
- package/runtime/constants.js +12 -5
- package/runtime/hydrate.js +8 -11
- package/runtime/index.js +42 -1
- package/runtime/iterate.js +617 -71
- package/runtime/iteration-utils.js +9 -2
- package/runtime/loop-scope.js +161 -0
- package/runtime/parse.js +94 -20
- package/runtime/pre-compiled-iterations.js +12 -0
- package/runtime/state.js +45 -1
- package/runtime/utils.js +36 -1
package/runtime/hydrate.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { updateIteration } from './iterate.js';
|
|
2
2
|
import { updateConditional } from './conditionals.js';
|
|
3
3
|
import { VALUE_ATTRS, DOM_PROPERTIES, BINDING_REGEX, PURE_BINDING_REGEX } from './constants.js';
|
|
4
|
-
import { evalInScope } from './utils.js';
|
|
4
|
+
import { evalInScope, resolveCaseInsensitivePath } from './utils.js';
|
|
5
5
|
|
|
6
6
|
export default (affected, state, manifest = {}, oldState = {}) => {
|
|
7
7
|
affected.forEach((aff) => {
|
|
@@ -24,18 +24,15 @@ export default (affected, state, manifest = {}, oldState = {}) => {
|
|
|
24
24
|
if (aff.type === 'nameBinding') {
|
|
25
25
|
const { nameBinding, matchInner, element } = aff;
|
|
26
26
|
try {
|
|
27
|
-
// HTML lowercases attribute names, so we need case-insensitive lookup
|
|
28
|
-
// Try exact match first, then
|
|
27
|
+
// HTML lowercases attribute names, so we need case-insensitive lookup.
|
|
28
|
+
// Try exact match first, then walk the path case-insensitively if the
|
|
29
|
+
// exact lookup returned nothing — this recovers camelCase property
|
|
30
|
+
// names in dotted paths like `<icon @[fx.convertsIcon]>` (arrives at
|
|
31
|
+
// runtime as `@[fx.convertsicon]`).
|
|
29
32
|
let attrName = evalInScope(matchInner, effectiveState, element);
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// Find the property with case-insensitive match
|
|
34
|
-
const keys = Object.keys(effectiveState);
|
|
35
|
-
const matchingKey = keys.find(k => k.toLowerCase() === matchInner.toLowerCase());
|
|
36
|
-
if (matchingKey) {
|
|
37
|
-
attrName = effectiveState[matchingKey];
|
|
38
|
-
}
|
|
34
|
+
if (!attrName) {
|
|
35
|
+
attrName = resolveCaseInsensitivePath(effectiveState, matchInner);
|
|
39
36
|
}
|
|
40
37
|
|
|
41
38
|
// Track multiple name bindings per element (need a map of binding -> evaluated attr)
|
package/runtime/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import affected from './affected.js';
|
|
|
6
6
|
import { deepMerge, hash } from './utils.js';
|
|
7
7
|
import { renderAllIterations, setRenderAllConditionals, releaseOrphanedIterationProps } from './iterate.js';
|
|
8
8
|
import { renderAllConditionals, branchNodeRegistry, managedNodes } from './conditionals.js';
|
|
9
|
+
import { installScopeResolver } from './loop-scope.js';
|
|
9
10
|
import {
|
|
10
11
|
NON_REACTIVE_ELEMENTS,
|
|
11
12
|
PHASE_ATTACH,
|
|
@@ -327,6 +328,10 @@ let previousState = {};
|
|
|
327
328
|
const main = (s, config = {}, stringSelector = '') => {
|
|
328
329
|
const debug = !!config?.debug;
|
|
329
330
|
const verbose = !!config?.verbose;
|
|
331
|
+
globalThis.__vibeDebug = debug;
|
|
332
|
+
|
|
333
|
+
// Expose the global `$scope` resolver used by loop-scoped `on*` handlers.
|
|
334
|
+
installScopeResolver();
|
|
330
335
|
|
|
331
336
|
// Detect if running in compiler's headless browser
|
|
332
337
|
// When true: skip cleanup to preserve [vibe] attribute in compiled HTML
|
|
@@ -550,7 +555,17 @@ const main = (s, config = {}, stringSelector = '') => {
|
|
|
550
555
|
// while still detecting binding changes inside iteration instances.
|
|
551
556
|
const currentState = { ...previousState };
|
|
552
557
|
for (const prop of changedProps) {
|
|
553
|
-
|
|
558
|
+
// Distinguish "set to undefined" (key still present in $) from "deleted"
|
|
559
|
+
// (key absent from $). For deletions, removing from currentState matches
|
|
560
|
+
// the live proxy's shape — otherwise downstream consumers that pass
|
|
561
|
+
// currentState as `$` to evalInScope (e.g. iterations that re-render via
|
|
562
|
+
// bindings reading the root state) would see the deleted key as a phantom
|
|
563
|
+
// own-property with value `undefined`.
|
|
564
|
+
if (prop in $) {
|
|
565
|
+
currentState[prop] = extractPlainValue($[prop]);
|
|
566
|
+
} else {
|
|
567
|
+
delete currentState[prop];
|
|
568
|
+
}
|
|
554
569
|
}
|
|
555
570
|
|
|
556
571
|
// Find what changed (compare previousState vs currentState)
|
|
@@ -608,6 +623,11 @@ const main = (s, config = {}, stringSelector = '') => {
|
|
|
608
623
|
});
|
|
609
624
|
|
|
610
625
|
// Add hook subscription method (non-enumerable so it won't be spread/cloned with state)
|
|
626
|
+
// `configurable: true` lets a component script's scoped `$` Proxy (built in
|
|
627
|
+
// component.js) legally return a wrapped `.on` that auto-registers cleanup
|
|
628
|
+
// — the Proxy invariant rejects overriding non-configurable + non-writable
|
|
629
|
+
// data properties. Enumerable stays false so `on` doesn't leak into state
|
|
630
|
+
// snapshots or Object.keys($).
|
|
611
631
|
Object.defineProperty($, 'on', {
|
|
612
632
|
value: (event, callback) => {
|
|
613
633
|
if (hooks[event]) {
|
|
@@ -616,6 +636,7 @@ const main = (s, config = {}, stringSelector = '') => {
|
|
|
616
636
|
return () => (hooks[event] = hooks[event].filter((cb) => cb !== callback));
|
|
617
637
|
},
|
|
618
638
|
enumerable: false,
|
|
639
|
+
configurable: true,
|
|
619
640
|
});
|
|
620
641
|
|
|
621
642
|
// Promise that resolves after the ready hook fires and all ready callbacks
|
|
@@ -644,6 +665,13 @@ const main = (s, config = {}, stringSelector = '') => {
|
|
|
644
665
|
enumerable: false,
|
|
645
666
|
});
|
|
646
667
|
|
|
668
|
+
// Expose the live reactive proxy to the iteration stamper so loop-scoped
|
|
669
|
+
// `on*` handlers (`$scope`) resolve the SAME object identity the app sees via
|
|
670
|
+
// `$`, instead of the plain diff-snapshot clones iterations render against
|
|
671
|
+
// (see extractPlainValue below). Non-enumerable so it never shows up in the
|
|
672
|
+
// manifest's node-path entry iteration.
|
|
673
|
+
Object.defineProperty(manifest, '__live', { value: $, enumerable: false, configurable: true });
|
|
674
|
+
|
|
647
675
|
// Initial hydration - pass plain values so iteration can do reference comparison
|
|
648
676
|
const initialState = extractPlainValue($);
|
|
649
677
|
const affectedElements = affected(parsedTree, initialState, initialState);
|
|
@@ -860,6 +888,19 @@ const main = (s, config = {}, stringSelector = '') => {
|
|
|
860
888
|
iteratedCount += counts.iteratedCount;
|
|
861
889
|
evaluatedCount += counts.evaluatedCount;
|
|
862
890
|
|
|
891
|
+
// For inlined `<component>` wrappers belonging to an iteration row
|
|
892
|
+
// (marked by component.js's resolveIterationComponentProps transfer),
|
|
893
|
+
// stash the parsed tree on the wrapper. processCoreLoop has just run
|
|
894
|
+
// hydrate + renderAllConditionals + renderAllIterations on it, so
|
|
895
|
+
// `parsedNode` carries live `runtime.activeInstance` /
|
|
896
|
+
// `runtime.instances` data — exactly what `affected.js` needs to walk
|
|
897
|
+
// into branch / row content. Iterate.js's update path consumes this
|
|
898
|
+
// tree to re-hydrate bindings inside the inlined component on each
|
|
899
|
+
// row-scope change without rebuilding the wrapper's DOM.
|
|
900
|
+
if (node.nodeType === 1 && node._vibeIterPropExprs) {
|
|
901
|
+
node._vibeIterTree = parsedNode;
|
|
902
|
+
}
|
|
903
|
+
|
|
863
904
|
// Count elements vs nodes separately
|
|
864
905
|
if (node.nodeName.startsWith('#')) {
|
|
865
906
|
addedNodes++;
|