@ape-egg/vibe 1.9.1 → 1.9.5

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.
@@ -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 try finding a case-insensitive match
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
- // If exact match failed and expression is a simple property (no dots/brackets)
32
- if (!attrName && !matchInner.includes('.') && !matchInner.includes('[')) {
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
- currentState[prop] = extractPlainValue($[prop]);
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
@@ -860,6 +881,19 @@ const main = (s, config = {}, stringSelector = '') => {
860
881
  iteratedCount += counts.iteratedCount;
861
882
  evaluatedCount += counts.evaluatedCount;
862
883
 
884
+ // For inlined `<component>` wrappers belonging to an iteration row
885
+ // (marked by component.js's resolveIterationComponentProps transfer),
886
+ // stash the parsed tree on the wrapper. processCoreLoop has just run
887
+ // hydrate + renderAllConditionals + renderAllIterations on it, so
888
+ // `parsedNode` carries live `runtime.activeInstance` /
889
+ // `runtime.instances` data — exactly what `affected.js` needs to walk
890
+ // into branch / row content. Iterate.js's update path consumes this
891
+ // tree to re-hydrate bindings inside the inlined component on each
892
+ // row-scope change without rebuilding the wrapper's DOM.
893
+ if (node.nodeType === 1 && node._vibeIterPropExprs) {
894
+ node._vibeIterTree = parsedNode;
895
+ }
896
+
863
897
  // Count elements vs nodes separately
864
898
  if (node.nodeName.startsWith('#')) {
865
899
  addedNodes++;