@ape-egg/vibe 1.0.2 → 1.0.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.
@@ -4,7 +4,7 @@ import { ITERATION_START_REGEX, CONDITIONAL_START_REGEX } from './constants.js';
4
4
  // Resolve nested paths in state (e.g., "user.items" -> state.user.items)
5
5
  export const resolvePath = (obj, path) => {
6
6
  if (!path || !obj) return undefined;
7
- return path.split(".").reduce((acc, part) => acc?.[part], obj);
7
+ return path.split('.').reduce((acc, part) => acc?.[part], obj);
8
8
  };
9
9
 
10
10
  // Clone template element preserving structure
@@ -16,11 +16,11 @@ export const cloneTemplate = (templateElement) => {
16
16
  export const findEndComment = (nodes, startIndex) => {
17
17
  let depth = 1;
18
18
  for (let i = startIndex; i < nodes.length; i++) {
19
- if (nodes[i].nodeName === "#comment") {
19
+ if (nodes[i].nodeName === '#comment') {
20
20
  const text = nodes[i].textContent.trim();
21
21
  if (ITERATION_START_REGEX.test(text)) {
22
22
  depth++;
23
- } else if (text === "/each") {
23
+ } else if (text === '/each') {
24
24
  depth--;
25
25
  if (depth === 0) {
26
26
  return i;
@@ -28,7 +28,7 @@ export const findEndComment = (nodes, startIndex) => {
28
28
  }
29
29
  }
30
30
  }
31
- throw new Error("Unmatched <!-- each --> comment: missing <!-- /each -->");
31
+ throw new Error('Unmatched <!-- each --> comment: missing <!-- /each -->');
32
32
  };
33
33
 
34
34
  // Find matching <!-- /if --> and optional <!-- else --> with depth tracking
@@ -38,7 +38,7 @@ export const findConditionalEnd = (nodes, startIndex) => {
38
38
  let elseIndex = null;
39
39
 
40
40
  for (let i = startIndex; i < nodes.length; i++) {
41
- if (nodes[i].nodeName === "#comment") {
41
+ if (nodes[i].nodeName === '#comment') {
42
42
  const text = nodes[i].textContent.trim();
43
43
 
44
44
  // Check for nested if
@@ -46,11 +46,11 @@ export const findConditionalEnd = (nodes, startIndex) => {
46
46
  depth++;
47
47
  }
48
48
  // Check for else at current depth
49
- else if (text === "else" && depth === 1 && elseIndex === null) {
49
+ else if (text === 'else' && depth === 1 && elseIndex === null) {
50
50
  elseIndex = i;
51
51
  }
52
52
  // Check for /if
53
- else if (text === "/if") {
53
+ else if (text === '/if') {
54
54
  depth--;
55
55
  if (depth === 0) {
56
56
  return { elseIndex, endIndex: i };
@@ -59,13 +59,13 @@ export const findConditionalEnd = (nodes, startIndex) => {
59
59
  }
60
60
  }
61
61
 
62
- throw new Error("Unmatched <!-- if --> comment: missing <!-- /if -->");
62
+ throw new Error('Unmatched <!-- if --> comment: missing <!-- /if -->');
63
63
  };
64
64
 
65
65
  // Generate stable hash for objects
66
66
  export const stableHash = (obj) => {
67
- if (obj === null || obj === undefined) return "null";
68
- if (typeof obj !== "object") return String(obj);
67
+ if (obj === null || obj === undefined) return 'null';
68
+ if (typeof obj !== 'object') return String(obj);
69
69
 
70
70
  try {
71
71
  // Sort keys for stable hashing
@@ -96,7 +96,7 @@ export const deepEqual = (a, b) => {
96
96
  return a === b;
97
97
  }
98
98
 
99
- if (typeof a !== "object" || typeof b !== "object") {
99
+ if (typeof a !== 'object' || typeof b !== 'object') {
100
100
  return a === b;
101
101
  }
102
102
 
@@ -166,12 +166,12 @@ export const longestCommonSubsequence = (arr1, arr2) => {
166
166
  // Generate unique key for array items
167
167
  export const getItemKey = (item, index) => {
168
168
  // 1. If item has 'id' property, use it
169
- if (item && typeof item === "object" && "id" in item) {
169
+ if (item && typeof item === 'object' && 'id' in item) {
170
170
  return `id_${item.id}`;
171
171
  }
172
172
 
173
173
  // 2. If item is primitive, use value + index
174
- if (typeof item !== "object" || item === null) {
174
+ if (typeof item !== 'object' || item === null) {
175
175
  return `val_${item}_${index}`;
176
176
  }
177
177
 
@@ -204,7 +204,7 @@ export const computeDiff = (oldKeys, newKeys, oldArray, newArray) => {
204
204
  // Check if item content changed
205
205
  if (!deepEqual(oldArray[oldIdx], newArray[newIdx])) {
206
206
  operations.push({
207
- type: "UPDATE",
207
+ type: 'UPDATE',
208
208
  index: newIdx,
209
209
  oldIndex: oldIdx,
210
210
  item: newArray[newIdx],
@@ -220,7 +220,7 @@ export const computeDiff = (oldKeys, newKeys, oldArray, newArray) => {
220
220
  oldKeys.forEach((key, idx) => {
221
221
  if (!processedOld.has(idx)) {
222
222
  operations.push({
223
- type: "REMOVE",
223
+ type: 'REMOVE',
224
224
  index: idx,
225
225
  key,
226
226
  });
@@ -231,7 +231,7 @@ export const computeDiff = (oldKeys, newKeys, oldArray, newArray) => {
231
231
  newKeys.forEach((key, idx) => {
232
232
  if (!processedNew.has(idx)) {
233
233
  operations.push({
234
- type: "ADD",
234
+ type: 'ADD',
235
235
  index: idx,
236
236
  item: newArray[idx],
237
237
  key,
@@ -246,7 +246,7 @@ export const computeDiff = (oldKeys, newKeys, oldArray, newArray) => {
246
246
  return priority[a.type] - priority[b.type];
247
247
  }
248
248
  // For REMOVE, process from end to beginning
249
- if (a.type === "REMOVE") {
249
+ if (a.type === 'REMOVE') {
250
250
  return b.index - a.index;
251
251
  }
252
252
  // For others, process in order
package/link.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const recursive = (tree, results, tagChain) => {
2
- results[tagChain.join(".")] = tree.element;
2
+ results[tagChain.join('.')] = tree.element;
3
3
 
4
4
  if (tree.children && Object.keys(tree.children).length > 0) {
5
5
  Object.keys(tree.children).forEach((tag) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ape-egg/vibe",
3
- "version": "1.0.2",
3
+ "version": "1.0.5",
4
4
  "type": "module",
5
5
  "description": "Runtime-first reactivity",
6
6
  "main": "index.js",
package/parse.js CHANGED
@@ -1,18 +1,23 @@
1
- import { hash } from './utils.js'
2
- import { findEndComment, findConditionalEnd } from './iteration-utils.js'
3
- import { NON_REACTIVE_ELEMENTS, BINDING_REGEX, ITERATION_REGEX, CONDITIONAL_REGEX } from './constants.js'
1
+ import { hash } from './utils.js';
2
+ import { findEndComment, findConditionalEnd } from './iteration-utils.js';
3
+ import {
4
+ NON_REACTIVE_ELEMENTS,
5
+ BINDING_REGEX,
6
+ ITERATION_REGEX,
7
+ CONDITIONAL_REGEX,
8
+ } from './constants.js';
4
9
 
5
10
  const parseHTML = (children, rootKey = undefined) =>
6
11
  children.reduce((s, element, i) => {
7
- const { nodeName, textContent } = element
12
+ const { nodeName, textContent } = element;
8
13
  if (['#comment'].includes(nodeName)) {
9
- return `${s}${nodeName === '#comment' ? `asd` : textContent}`
14
+ return `${s}${nodeName === '#comment' ? `asd` : textContent}`;
10
15
  }
11
- const name = nodeName.startsWith('#') ? nodeName.slice(1) : nodeName
12
- const innerNodeIdentifier = `${name}_${i}`.toLowerCase()
16
+ const name = nodeName.startsWith('#') ? nodeName.slice(1) : nodeName;
17
+ const innerNodeIdentifier = `${name}_${i}`.toLowerCase();
13
18
 
14
- return rootKey || `${s}${`\$[${innerNodeIdentifier}]`}`
15
- }, '')
19
+ return rootKey || `${s}${`\$[${innerNodeIdentifier}]`}`;
20
+ }, '');
16
21
 
17
22
  const recursive = (children, rootKey = undefined, skipIndices = new Set()) => {
18
23
  let result = {};
@@ -44,7 +49,7 @@ const recursive = (children, rootKey = undefined, skipIndices = new Set()) => {
44
49
 
45
50
  // Create a temporary container for the template
46
51
  const templateContainer = document.createElement('div');
47
- templateNodes.forEach(node => {
52
+ templateNodes.forEach((node) => {
48
53
  templateContainer.appendChild(node.cloneNode(true));
49
54
  });
50
55
 
@@ -64,14 +69,14 @@ const recursive = (children, rootKey = undefined, skipIndices = new Set()) => {
64
69
  template: {
65
70
  parsed: parseHTML([...templateContainer.childNodes]),
66
71
  element: templateContainer,
67
- children: templateParsed
68
- }
72
+ children: templateParsed,
73
+ },
69
74
  },
70
75
  runtime: {
71
76
  instances: [],
72
- templateRemoved: false
77
+ templateRemoved: false,
73
78
  },
74
- children: {}
79
+ children: {},
75
80
  };
76
81
 
77
82
  // Mark template indices as processed
@@ -105,7 +110,7 @@ const recursive = (children, rootKey = undefined, skipIndices = new Set()) => {
105
110
 
106
111
  // Create temporary container for true branch
107
112
  const trueBranchContainer = document.createElement('div');
108
- trueBranchNodes.forEach(node => {
113
+ trueBranchNodes.forEach((node) => {
109
114
  trueBranchContainer.appendChild(node.cloneNode(true));
110
115
  });
111
116
 
@@ -118,7 +123,7 @@ const recursive = (children, rootKey = undefined, skipIndices = new Set()) => {
118
123
  if (elseIndex !== null) {
119
124
  const falseBranchNodes = Array.from(children).slice(elseIndex + 1, endIndex);
120
125
  falseBranchContainer = document.createElement('div');
121
- falseBranchNodes.forEach(node => {
126
+ falseBranchNodes.forEach((node) => {
122
127
  falseBranchContainer.appendChild(node.cloneNode(true));
123
128
  });
124
129
  falseBranchParsed = recursive([...falseBranchContainer.childNodes]);
@@ -137,21 +142,23 @@ const recursive = (children, rootKey = undefined, skipIndices = new Set()) => {
137
142
  if: {
138
143
  parsed: parseHTML([...trueBranchContainer.childNodes]),
139
144
  element: trueBranchContainer,
140
- children: trueBranchParsed
145
+ children: trueBranchParsed,
141
146
  },
142
- else: falseBranchContainer ? {
143
- parsed: parseHTML([...falseBranchContainer.childNodes]),
144
- element: falseBranchContainer,
145
- children: falseBranchParsed
146
- } : null
147
- }
147
+ else: falseBranchContainer
148
+ ? {
149
+ parsed: parseHTML([...falseBranchContainer.childNodes]),
150
+ element: falseBranchContainer,
151
+ children: falseBranchParsed,
152
+ }
153
+ : null,
154
+ },
148
155
  },
149
156
  runtime: {
150
157
  activeBranch: undefined,
151
158
  activeInstance: null,
152
- templateRemoved: false
159
+ templateRemoved: false,
153
160
  },
154
- children: {}
161
+ children: {},
155
162
  };
156
163
 
157
164
  // Mark template indices as processed
@@ -199,23 +206,23 @@ const recursive = (children, rootKey = undefined, skipIndices = new Set()) => {
199
206
  parsed,
200
207
  element,
201
208
  children: recursive(iteratableChildren),
202
- ...(hasAttributeBindings && { attributes })
209
+ ...(hasAttributeBindings && { attributes }),
203
210
  };
204
211
  } else {
205
212
  result[nodeIdentifier] = {
206
213
  parsed: innerHTML || textContent,
207
214
  element,
208
215
  children: {},
209
- ...(hasAttributeBindings && { attributes })
216
+ ...(hasAttributeBindings && { attributes }),
210
217
  };
211
218
  }
212
219
  }
213
220
 
214
221
  return result;
215
- }
222
+ };
216
223
 
217
224
  export default (root, rootKey = undefined) => {
218
- const { childNodes } = root
225
+ const { childNodes } = root;
219
226
 
220
227
  // Check for attribute bindings on the root element itself (only if element has attributes)
221
228
  let attributes = null;
@@ -235,6 +242,6 @@ export default (root, rootKey = undefined) => {
235
242
  parsed: parseHTML([...childNodes], rootKey),
236
243
  element: root,
237
244
  children: recursive(Array.from(childNodes), rootKey),
238
- ...(attributes && { attributes })
239
- }
240
- }
245
+ ...(attributes && { attributes }),
246
+ };
247
+ };
package/state.js CHANGED
@@ -1,12 +1,12 @@
1
1
  export default (state, rerender) =>
2
2
  new Proxy(state, {
3
3
  set(obj, prop, value) {
4
- const ref = Reflect.set(...arguments)
4
+ const ref = Reflect.set(...arguments);
5
5
  rerender({
6
6
  [prop]: value,
7
- })
7
+ });
8
8
  // console.info("state change", { obj, prop, value });
9
- return ref
9
+ return ref;
10
10
  },
11
11
  get(target, prop) {
12
12
  // if (typeof target[prop] === 'function') {
@@ -21,6 +21,6 @@ export default (state, rerender) =>
21
21
 
22
22
  // }
23
23
 
24
- return Reflect.get(...arguments)
24
+ return Reflect.get(...arguments);
25
25
  },
26
- })
26
+ });
package/utils.js CHANGED
@@ -2,6 +2,19 @@
2
2
  let hashCounter = 0;
3
3
  export const hash = () => `_${hashCounter++}`;
4
4
 
5
+ // Evaluate expression in the context of state
6
+ export const evalInScope = (expr, state) => {
7
+ try {
8
+ // 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);
13
+ } catch (e) {
14
+ return undefined;
15
+ }
16
+ };
17
+
5
18
  // Instead of using lodash-es as a dependency, we run our own deepMerge (mergeWith in lodash)
6
19
  export const deepMerge = (target, source) => {
7
20
  // Handle null/undefined
@@ -9,7 +22,7 @@ export const deepMerge = (target, source) => {
9
22
  if (target == null) return source;
10
23
 
11
24
  // If source is not an object, return it
12
- if (typeof source !== "object") return source;
25
+ if (typeof source !== 'object') return source;
13
26
 
14
27
  // If source is an array, replace target array (don't merge arrays)
15
28
  if (Array.isArray(source)) return source;
@@ -27,8 +40,8 @@ export const deepMerge = (target, source) => {
27
40
  if (
28
41
  targetValue != null &&
29
42
  sourceValue != null &&
30
- typeof targetValue === "object" &&
31
- typeof sourceValue === "object" &&
43
+ typeof targetValue === 'object' &&
44
+ typeof sourceValue === 'object' &&
32
45
  !Array.isArray(targetValue) &&
33
46
  !Array.isArray(sourceValue)
34
47
  ) {
@@ -42,4 +55,3 @@ export const deepMerge = (target, source) => {
42
55
 
43
56
  return result;
44
57
  };
45
-