@ape-egg/vibe 1.1.2 → 1.2.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/runtime/utils.js CHANGED
@@ -3,18 +3,85 @@ let hashCounter = 0;
3
3
  export const hash = () => `_${hashCounter++}`;
4
4
 
5
5
  // Evaluate expression in the context of state
6
- export const evalInScope = (expr, state) => {
6
+ // Handles @[this.property] for component state and @[property] for global state
7
+ export const evalInScope = (expr, state, element = null) => {
7
8
  try {
8
9
  // Normalize whitespace - collapse newlines/spaces to single space (resilient to IDE formatting)
9
- const normalized = expr.replace(/\s+/g, ' ').trim();
10
- const keys = Object.keys(state);
11
- const values = Object.values(state);
12
- return new Function(...keys, `'use strict'; return (${normalized})`)(...values);
10
+ let normalized = expr.replace(/\s+/g, ' ').trim();
11
+
12
+ // If expression contains 'this.', replace with component state path
13
+ if (normalized.includes('this.') && element) {
14
+ const componentId = findComponentIdForElement(element);
15
+ if (componentId) {
16
+ // Replace this.property with $['componentId'].property
17
+ normalized = normalized.replace(/\bthis\.(\w+)/g, `$['${componentId}'].$1`);
18
+ }
19
+ }
20
+
21
+ // Add $ to scope for this.property references
22
+ const effectiveState = { ...state, $: state };
23
+
24
+ const keys = Object.keys(effectiveState);
25
+ const values = Object.values(effectiveState);
26
+
27
+ const result = new Function(...keys, `'use strict'; return (${normalized})`)(...values);
28
+
29
+ // If result is undefined and we're accessing a component property, try case-insensitive match
30
+ // This handles HTML lowercasing attribute names like @[this.iconName] -> @[this.iconname]
31
+ if (result === undefined) {
32
+ const componentMatch = normalized.match(/\$\['([^']+)'\]\.(\w+)/);
33
+ if (componentMatch) {
34
+ const [, componentId, propName] = componentMatch;
35
+ const componentState = state[componentId];
36
+ if (componentState) {
37
+ // Try case-insensitive property lookup
38
+ const actualKey = Object.keys(componentState).find(k => k.toLowerCase() === propName.toLowerCase());
39
+ if (actualKey) {
40
+ return componentState[actualKey];
41
+ }
42
+ }
43
+ }
44
+ }
45
+
46
+ return result;
13
47
  } catch (e) {
14
48
  return undefined;
15
49
  }
16
50
  };
17
51
 
52
+ // Helper to find component ID for an element
53
+ export const findComponentIdForElement = (element) => {
54
+ if (!element) return null;
55
+
56
+ // First check if element itself has the ID (tagged during parse)
57
+ if (element.hasAttribute('data-vibe-component-id')) {
58
+ return element.getAttribute('data-vibe-component-id');
59
+ }
60
+
61
+ // Check if any ancestor has the ID
62
+ const ancestor = element.closest('[data-vibe-component-id]');
63
+ if (ancestor) {
64
+ return ancestor.getAttribute('data-vibe-component-id');
65
+ }
66
+
67
+ return null;
68
+ };
69
+
70
+ // Helper to resolve this.property paths to componentId.property
71
+ // Used for component-scoped iterations, conditionals, etc.
72
+ export const resolveThisPath = (path, element) => {
73
+ if (!path.startsWith('this.')) {
74
+ return path;
75
+ }
76
+
77
+ const componentId = findComponentIdForElement(element);
78
+ if (componentId) {
79
+ return path.replace(/^this\./, `${componentId}.`);
80
+ }
81
+
82
+ return path;
83
+ };
84
+
18
85
  // Instead of using lodash-es as a dependency, we run our own deepMerge (mergeWith in lodash)
19
86
  export const deepMerge = (target, source) => {
20
87
  // Handle null/undefined
File without changes