@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/CHANGELOG.md +78 -0
- package/README.md +30 -23
- package/index.js +96 -2
- package/package.json +2 -1
- package/runtime/affected.js +33 -19
- package/runtime/component-state.js +63 -0
- package/runtime/component.js +39 -2
- package/runtime/conditionals.js +4 -4
- package/runtime/constants.js +13 -0
- package/runtime/hydrate.js +17 -20
- package/runtime/index.js +115 -49
- package/runtime/iterate.js +12 -4
- package/runtime/parse.js +48 -2
- package/runtime/scope.js +70 -0
- package/runtime/state.js +10 -5
- package/runtime/utils.js +72 -5
- /package/{runtime/vibe.css → vibe.css} +0 -0
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
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|