@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.
package/constants.js CHANGED
@@ -1,56 +1,137 @@
1
1
  // Elements that should not have reactive bindings
2
- export const NON_REACTIVE_ELEMENTS = ["SCRIPT", "HEAD", "PRE"];
2
+ export const NON_REACTIVE_ELEMENTS = ['SCRIPT', 'HEAD', 'PRE'];
3
3
 
4
4
  // Attributes where the string value is meaningful (should NOT be removed when falsy)
5
5
  // All other attributes are treated as boolean-like (removed when falsy, present when truthy)
6
6
  export const VALUE_ATTRS = [
7
7
  // Global attributes
8
- 'class', 'style', 'id', 'title', 'lang', 'dir', 'tabindex', 'accesskey',
9
- 'slot', 'part', 'is', 'nonce', 'popover', 'anchor',
8
+ 'class',
9
+ 'style',
10
+ 'id',
11
+ 'title',
12
+ 'lang',
13
+ 'dir',
14
+ 'tabindex',
15
+ 'accesskey',
16
+ 'slot',
17
+ 'part',
18
+ 'is',
19
+ 'nonce',
20
+ 'popover',
21
+ 'anchor',
10
22
 
11
23
  // Enumerated (take specific string values, not truly boolean)
12
- 'contenteditable', 'draggable', 'spellcheck', 'translate', 'autocapitalize',
13
- 'inputmode', 'enterkeyhint', 'virtualkeyboardpolicy',
24
+ 'contenteditable',
25
+ 'draggable',
26
+ 'spellcheck',
27
+ 'translate',
28
+ 'autocapitalize',
29
+ 'inputmode',
30
+ 'enterkeyhint',
31
+ 'virtualkeyboardpolicy',
14
32
 
15
33
  // URLs and sources
16
- 'href', 'src', 'action', 'cite', 'data', 'poster', 'srcset', 'imagesrcset',
17
- 'formaction', 'ping', 'usemap', 'manifest', 'codebase',
34
+ 'href',
35
+ 'src',
36
+ 'action',
37
+ 'cite',
38
+ 'data',
39
+ 'poster',
40
+ 'srcset',
41
+ 'imagesrcset',
42
+ 'formaction',
43
+ 'ping',
44
+ 'usemap',
45
+ 'manifest',
46
+ 'codebase',
18
47
 
19
48
  // Form attributes
20
- 'name', 'type', 'value', 'placeholder', 'pattern', 'min', 'max', 'step',
21
- 'minlength', 'maxlength', 'size', 'accept', 'autocomplete', 'list', 'form',
22
- 'formmethod', 'formtarget', 'formenctype', 'wrap', 'method', 'enctype',
23
- 'for', 'dirname',
49
+ 'name',
50
+ 'type',
51
+ 'value',
52
+ 'placeholder',
53
+ 'pattern',
54
+ 'min',
55
+ 'max',
56
+ 'step',
57
+ 'minlength',
58
+ 'maxlength',
59
+ 'size',
60
+ 'accept',
61
+ 'autocomplete',
62
+ 'list',
63
+ 'form',
64
+ 'formmethod',
65
+ 'formtarget',
66
+ 'formenctype',
67
+ 'wrap',
68
+ 'method',
69
+ 'enctype',
70
+ 'for',
71
+ 'dirname',
24
72
 
25
73
  // Text/accessibility
26
- 'alt', 'label', 'summary', 'abbr',
74
+ 'alt',
75
+ 'label',
76
+ 'summary',
77
+ 'abbr',
27
78
 
28
79
  // Dimensions and layout
29
- 'width', 'height', 'cols', 'rows', 'span', 'rowspan', 'colspan',
30
- 'low', 'high', 'optimum',
80
+ 'width',
81
+ 'height',
82
+ 'cols',
83
+ 'rows',
84
+ 'span',
85
+ 'rowspan',
86
+ 'colspan',
87
+ 'low',
88
+ 'high',
89
+ 'optimum',
31
90
 
32
91
  // Link/resource hints
33
- 'target', 'rel', 'hreflang', 'download', 'as', 'media', 'type', 'charset',
34
- 'crossorigin', 'integrity', 'loading', 'decoding', 'fetchpriority',
35
- 'referrerpolicy', 'blocking', 'imagesizes', 'sizes',
92
+ 'target',
93
+ 'rel',
94
+ 'hreflang',
95
+ 'download',
96
+ 'as',
97
+ 'media',
98
+ 'type',
99
+ 'charset',
100
+ 'crossorigin',
101
+ 'integrity',
102
+ 'loading',
103
+ 'decoding',
104
+ 'fetchpriority',
105
+ 'referrerpolicy',
106
+ 'blocking',
107
+ 'imagesizes',
108
+ 'sizes',
36
109
 
37
110
  // Media
38
- 'preload', 'kind', 'srclang',
111
+ 'preload',
112
+ 'kind',
113
+ 'srclang',
39
114
 
40
115
  // Meta
41
- 'content', 'http-equiv',
116
+ 'content',
117
+ 'http-equiv',
42
118
 
43
119
  // iframe/embed
44
- 'sandbox', 'allow', 'srcdoc', 'credentialless',
120
+ 'sandbox',
121
+ 'allow',
122
+ 'srcdoc',
123
+ 'credentialless',
45
124
 
46
125
  // Table
47
- 'headers', 'scope',
126
+ 'headers',
127
+ 'scope',
48
128
 
49
129
  // Datetime
50
130
  'datetime',
51
131
 
52
132
  // Object/embed legacy
53
- 'coords', 'shape',
133
+ 'coords',
134
+ 'shape',
54
135
  ];
55
136
 
56
137
  // Properties that should be set directly on the DOM element (not as attributes)
package/hydrate.js CHANGED
@@ -1,6 +1,7 @@
1
- import { updateIteration } from './iterate.js'
2
- import { updateConditional } from './conditionals.js'
3
- import { VALUE_ATTRS, DOM_PROPERTIES, BINDING_REGEX, PURE_BINDING_REGEX } from './constants.js'
1
+ import { updateIteration } from './iterate.js';
2
+ import { updateConditional } from './conditionals.js';
3
+ import { VALUE_ATTRS, DOM_PROPERTIES, BINDING_REGEX, PURE_BINDING_REGEX } from './constants.js';
4
+ import { evalInScope } from './utils.js';
4
5
 
5
6
  // Keep track of old state for diffing
6
7
  let previousState = {};
@@ -9,19 +10,6 @@ export const setPreviousState = (state) => {
9
10
  previousState = { ...state };
10
11
  };
11
12
 
12
- // Evaluate expression in the context of state
13
- const evalInScope = (expr, state) => {
14
- try {
15
- const keys = Object.keys(state);
16
- const values = Object.values(state);
17
- // Create a function with state keys as parameters and evaluate the expression
18
- return new Function(...keys, `'use strict'; return (${expr})`)(...values);
19
- } catch (e) {
20
- // Fallback to undefined if evaluation fails
21
- return undefined;
22
- }
23
- };
24
-
25
13
  export default (affected, state, linkList = {}) => {
26
14
  affected.forEach((aff) => {
27
15
  // Handle iteration updates
@@ -40,12 +28,12 @@ export default (affected, state, linkList = {}) => {
40
28
  if (aff.type === 'attribute') {
41
29
  const { attrName, attrValue, element } = aff;
42
30
  try {
43
-
44
31
  // Check if this is a pure binding (e.g., value="@[inputValue]")
45
32
  const isPureBinding = attrValue.match(PURE_BINDING_REGEX);
46
33
  const isDomProperty = DOM_PROPERTIES.includes(attrName);
47
34
  // Value attrs keep their string value; everything else is boolean-like (removed when falsy)
48
- const isValueAttr = VALUE_ATTRS.includes(attrName) ||
35
+ const isValueAttr =
36
+ VALUE_ATTRS.includes(attrName) ||
49
37
  attrName.startsWith('data-') ||
50
38
  attrName.startsWith('aria-') ||
51
39
  attrName.startsWith('on');
@@ -97,4 +85,4 @@ export default (affected, state, linkList = {}) => {
97
85
 
98
86
  // Update previous state for next diff
99
87
  setPreviousState(state);
100
- }
88
+ };
package/index.js CHANGED
@@ -1,12 +1,12 @@
1
- import state from "./state.js";
2
- import parse from "./parse.js";
3
- import link from "./link.js";
4
- import hydrate, { setPreviousState } from "./hydrate.js";
5
- import affected from "./affected.js";
6
- import { deepMerge, hash } from "./utils.js";
7
- import { renderAllIterations, setRenderAllConditionals } from "./iterate.js";
8
- import { renderAllConditionals } from "./conditionals.js";
9
- import { NON_REACTIVE_ELEMENTS } from "./constants.js";
1
+ import state from './state.js';
2
+ import parse from './parse.js';
3
+ import link from './link.js';
4
+ import hydrate, { setPreviousState } from './hydrate.js';
5
+ import affected from './affected.js';
6
+ import { deepMerge, hash } from './utils.js';
7
+ import { renderAllIterations, setRenderAllConditionals } from './iterate.js';
8
+ import { renderAllConditionals } from './conditionals.js';
9
+ import { NON_REACTIVE_ELEMENTS } from './constants.js';
10
10
 
11
11
  // Wire up cross-module dependency after all modules are loaded
12
12
  setRenderAllConditionals(renderAllConditionals);
@@ -29,12 +29,12 @@ const isNonReactiveOrInside = (node) => {
29
29
  // Navigate tree using dot notation (handles .children at each level)
30
30
  const navigateTree = (tree, path) => {
31
31
  if (!path) return tree;
32
- return path.split(".").reduce((node, key) => node?.children?.[key], tree);
32
+ return path.split('.').reduce((node, key) => node?.children?.[key], tree);
33
33
  };
34
34
 
35
35
  // Get or create a node in the tree at the given path
36
36
  const ensureNode = (tree, path) => {
37
- const keys = path.split(".");
37
+ const keys = path.split('.');
38
38
  return keys.reduce((node, key) => {
39
39
  if (!node.children[key]) {
40
40
  node.children[key] = { children: {} };
@@ -43,14 +43,12 @@ const ensureNode = (tree, path) => {
43
43
  }, tree);
44
44
  };
45
45
 
46
- const main = (s, attrName = "vibe") => {
46
+ const main = (s, attrName = 'vibe') => {
47
47
  // Find element(s) with the specified attribute
48
48
  const elements = document.querySelectorAll(`[${attrName}]`);
49
49
 
50
50
  if (elements.length === 0) {
51
- console.info(
52
- `[vibe] No element found with attribute "${attrName}". Falling back to body.`,
53
- );
51
+ console.info(`[vibe] No element found with attribute "${attrName}". Falling back to body.`);
54
52
  } else if (elements.length > 1) {
55
53
  console.info(
56
54
  `[vibe] Multiple elements (${elements.length}) found with attribute "${attrName}". Hydrating the first one.`,
@@ -70,15 +68,23 @@ const main = (s, attrName = "vibe") => {
70
68
  afterDomMutation: [],
71
69
  };
72
70
 
73
- // Observer reference - set after observer is created
71
+ // Observer reference and callback - defined here so state handler can access processMutations
74
72
  let observer = null;
73
+ let processMutations = null;
75
74
 
76
75
  const $ = state(s, (newState) => {
77
76
  const mergedState = deepMerge($, newState);
78
77
  const affectedElements = affected(parsedTree, previousState, mergedState);
79
78
 
80
- if (observer) observer.disconnect();
79
+ // Capture pending mutations before disconnecting (takeRecords clears the queue)
80
+ let pendingMutations = [];
81
+ if (observer) {
82
+ pendingMutations = observer.takeRecords();
83
+ observer.disconnect();
84
+ }
85
+
81
86
  hydrate(affectedElements, mergedState, linkList);
87
+
82
88
  if (observer) {
83
89
  observer.observe(rootElement, {
84
90
  attributes: false,
@@ -86,13 +92,16 @@ const main = (s, attrName = "vibe") => {
86
92
  childList: true,
87
93
  subtree: true,
88
94
  });
95
+
96
+ // Process mutations that were pending before we disconnected
97
+ if (pendingMutations.length > 0 && processMutations) {
98
+ processMutations(pendingMutations);
99
+ }
89
100
  }
90
101
 
91
102
  const prev = structuredClone(previousState);
92
103
  previousState = { ...$, ...newState };
93
- hooks.afterUpdate.forEach((callback) =>
94
- callback(structuredClone({ ...$ }), prev),
95
- );
104
+ hooks.afterUpdate.forEach((callback) => callback(structuredClone({ ...$ }), prev));
96
105
  });
97
106
 
98
107
  // Add hook subscription method (non-enumerable so it won't be spread/cloned with state)
@@ -101,7 +110,7 @@ const main = (s, attrName = "vibe") => {
101
110
  if (hooks[event]) {
102
111
  hooks[event].push(callback);
103
112
  }
104
- return () => hooks[event] = hooks[event].filter((cb) => cb !== callback);
113
+ return () => (hooks[event] = hooks[event].filter((cb) => cb !== callback));
105
114
  },
106
115
  enumerable: false,
107
116
  });
@@ -116,7 +125,8 @@ const main = (s, attrName = "vibe") => {
116
125
  renderAllIterations(parsedTree, $, linkList);
117
126
  renderAllConditionals(parsedTree, $, linkList);
118
127
 
119
- observer = new MutationObserver((mutations) => {
128
+ // Define observer callback as named function so we can call it manually for pending mutations
129
+ processMutations = (mutations) => {
120
130
  // Early exit if no mutations to process (common case)
121
131
  if (mutations.length === 0) return;
122
132
 
@@ -124,11 +134,8 @@ const main = (s, attrName = "vibe") => {
124
134
  let parsedParents = null; // Lazy init - only create Set when needed
125
135
 
126
136
  mutations.forEach(({ addedNodes, removedNodes, target }) => {
127
- // Process removed nodes first (cleanup before additions)
128
137
  removedNodes.forEach((node) => {
129
- const entry = Object.entries(linkList).find(
130
- ([_, element]) => element === node,
131
- );
138
+ const entry = Object.entries(linkList).find(([_, element]) => element === node);
132
139
 
133
140
  // Skip nodes that aren't tracked (e.g., iteration-generated nodes or nodes outside reactive scope)
134
141
  if (!entry) return;
@@ -136,9 +143,9 @@ const main = (s, attrName = "vibe") => {
136
143
  const [dotAnnotation] = entry;
137
144
  delete linkList[dotAnnotation];
138
145
 
139
- const dotPath = dotAnnotation.split(".");
146
+ const dotPath = dotAnnotation.split('.');
140
147
  const name = dotPath.pop();
141
- const parentDotAnnotation = dotPath.join(".");
148
+ const parentDotAnnotation = dotPath.join('.');
142
149
 
143
150
  const picked = navigateTree(parsedTree, parentDotAnnotation);
144
151
 
@@ -165,9 +172,7 @@ const main = (s, attrName = "vibe") => {
165
172
  return;
166
173
  }
167
174
 
168
- const entry = Object.entries(linkList).find(
169
- ([_, element]) => element === target,
170
- );
175
+ const entry = Object.entries(linkList).find(([_, element]) => element === target);
171
176
 
172
177
  // If parent isn't tracked, this node is outside the reactive scope
173
178
  if (!entry) return;
@@ -206,6 +211,10 @@ const main = (s, attrName = "vibe") => {
206
211
 
207
212
  hydrate(affectedElements, $, linkList);
208
213
 
214
+ // Process iterations and conditionals in the newly added node
215
+ renderAllIterations(parsedNode, $, linkList);
216
+ renderAllConditionals(parsedNode, $, linkList);
217
+
209
218
  hadChanges = true;
210
219
  });
211
220
  });
@@ -214,7 +223,9 @@ const main = (s, attrName = "vibe") => {
214
223
  if (hadChanges) {
215
224
  hooks.afterDomMutation.forEach((callback) => callback());
216
225
  }
217
- });
226
+ };
227
+
228
+ observer = new MutationObserver(processMutations);
218
229
 
219
230
  observer.observe(rootElement, {
220
231
  attributes: false,
package/iterate.js CHANGED
@@ -32,7 +32,7 @@ const cloneTreeWithElements = (originalTree, clonedRoot) => {
32
32
  parsed: originalTree.parsed,
33
33
  element: clonedRoot,
34
34
  children: {},
35
- ...(originalTree.attributes && { attributes: originalTree.attributes })
35
+ ...(originalTree.attributes && { attributes: originalTree.attributes }),
36
36
  };
37
37
 
38
38
  if (!originalTree.children) return cloned;
@@ -50,7 +50,9 @@ const cloneTreeWithElements = (originalTree, clonedRoot) => {
50
50
 
51
51
  // Find corresponding comments in cloned DOM
52
52
  const clonedStart = findComment(clonedChildNodes, startComment.textContent);
53
- const clonedElse = elseComment ? findComment(clonedChildNodes, elseComment.textContent) : null;
53
+ const clonedElse = elseComment
54
+ ? findComment(clonedChildNodes, elseComment.textContent)
55
+ : null;
54
56
  const clonedEnd = findComment(clonedChildNodes, endComment.textContent);
55
57
 
56
58
  cloned.children[key] = {
@@ -60,14 +62,14 @@ const cloneTreeWithElements = (originalTree, clonedRoot) => {
60
62
  startComment: clonedStart || startComment,
61
63
  elseComment: clonedElse,
62
64
  endComment: clonedEnd || endComment,
63
- branches // Branch templates are reused (they're cloned during mount)
65
+ branches, // Branch templates are reused (they're cloned during mount)
64
66
  },
65
67
  runtime: {
66
68
  activeBranch: undefined,
67
69
  activeInstance: null,
68
- templateRemoved: false
70
+ templateRemoved: false,
69
71
  },
70
- children: {}
72
+ children: {},
71
73
  };
72
74
  continue;
73
75
  }
@@ -88,13 +90,13 @@ const cloneTreeWithElements = (originalTree, clonedRoot) => {
88
90
  indexAlias: child.meta.indexAlias,
89
91
  startComment: clonedStart || startComment,
90
92
  endComment: clonedEnd || endComment,
91
- template // Template is reused (cloned during render)
93
+ template, // Template is reused (cloned during render)
92
94
  },
93
95
  runtime: {
94
96
  instances: [],
95
- templateRemoved: false
97
+ templateRemoved: false,
96
98
  },
97
- children: {}
99
+ children: {},
98
100
  };
99
101
  continue;
100
102
  }
@@ -150,7 +152,7 @@ export const initializeBlock = (templateNodes, scopedState, cachedTree = null) =
150
152
  // Fallback for text-only templates
151
153
  if (!firstElement && clonedNodes.length > 0) {
152
154
  const container = document.createElement('span');
153
- clonedNodes.forEach(node => container.appendChild(node.cloneNode(true)));
155
+ clonedNodes.forEach((node) => container.appendChild(node.cloneNode(true)));
154
156
  firstElement = container;
155
157
  }
156
158
 
@@ -165,7 +167,7 @@ export const initializeBlock = (templateNodes, scopedState, cachedTree = null) =
165
167
  return {
166
168
  element: firstElement,
167
169
  tree,
168
- clonedNodes
170
+ clonedNodes,
169
171
  };
170
172
  };
171
173
 
@@ -222,7 +224,7 @@ export const createScopedState = (globalState, localVars, parentScope = {}) => {
222
224
  return { configurable: true, enumerable: true, value: parentScope[prop] };
223
225
  }
224
226
  return Reflect.getOwnPropertyDescriptor(target, prop);
225
- }
227
+ },
226
228
  });
227
229
  };
228
230
 
@@ -247,11 +249,14 @@ export const renderAllIterations = (tree, state, linkList, parentScope = {}) =>
247
249
 
248
250
  // Callback for rendering conditionals - set by conditionals.js to avoid circular import
249
251
  let _renderAllConditionals = () => {};
250
- export const setRenderAllConditionals = (fn) => { _renderAllConditionals = fn; };
252
+ export const setRenderAllConditionals = (fn) => {
253
+ _renderAllConditionals = fn;
254
+ };
251
255
 
252
256
  // Initial render of an iteration block
253
257
  export const renderIteration = (iterationNode, state, linkList, parentScope = {}) => {
254
- const { arrayPath, itemAlias, indexAlias, template, startComment, endComment } = iterationNode.meta;
258
+ const { arrayPath, itemAlias, indexAlias, template, startComment, endComment } =
259
+ iterationNode.meta;
255
260
 
256
261
  // Already rendered - updates go through updateIteration
257
262
  if (iterationNode.runtime.instances?.length > 0) return;
@@ -294,7 +299,7 @@ export const renderIteration = (iterationNode, state, linkList, parentScope = {}
294
299
  const { element, tree, clonedNodes } = initializeBlock(templateNodes, scopedState, template);
295
300
 
296
301
  // Insert cloned nodes
297
- clonedNodes.forEach(node => parent.insertBefore(node, endComment));
302
+ clonedNodes.forEach((node) => parent.insertBefore(node, endComment));
298
303
 
299
304
  // Recursively render nested iterations and conditionals
300
305
  if (tree) {
@@ -323,7 +328,11 @@ export const updateIteration = (iterationNode, newState, oldState, linkList, par
323
328
  const isFullToEmpty = oldArray.length > 0 && newArray.length === 0;
324
329
  const isLargeArray = newArray.length > 100 || oldArray.length > 100;
325
330
 
326
- if (window.__VIBE_FAST_ITERATION__ && fastPath.canUseFastPath(template) && (isEmptyToFull || isFullToEmpty || isLargeArray)) {
331
+ if (
332
+ window.__VIBE_FAST_ITERATION__ &&
333
+ fastPath.canUseFastPath(template) &&
334
+ (isEmptyToFull || isFullToEmpty || isLargeArray)
335
+ ) {
327
336
  fastPath.updateFast(iterationNode, newArray, newState, startComment, endComment);
328
337
  return;
329
338
  }
@@ -333,16 +342,26 @@ export const updateIteration = (iterationNode, newState, oldState, linkList, par
333
342
  const newKeys = newArray.map((item, i) => getItemKey(item, i));
334
343
  const operations = computeDiff(oldKeys, newKeys, oldArray, newArray);
335
344
 
336
- operations.forEach(op => {
345
+ operations.forEach((op) => {
337
346
  switch (op.type) {
338
- case 'REMOVE': removeInstance(iterationNode, op.index); break;
339
- case 'ADD': addInstance(iterationNode, op.item, op.index, newState, linkList, parentScope); break;
340
- case 'MOVE': moveInstance(iterationNode, op.from, op.to); break;
341
- case 'UPDATE': updateInstance(iterationNode, op.index, op.item, newState, linkList, parentScope); break;
347
+ case 'REMOVE':
348
+ removeInstance(iterationNode, op.index);
349
+ break;
350
+ case 'ADD':
351
+ addInstance(iterationNode, op.item, op.index, newState, linkList, parentScope);
352
+ break;
353
+ case 'MOVE':
354
+ moveInstance(iterationNode, op.from, op.to);
355
+ break;
356
+ case 'UPDATE':
357
+ updateInstance(iterationNode, op.index, op.item, newState, linkList, parentScope);
358
+ break;
342
359
  }
343
360
  });
344
361
 
345
- iterationNode.runtime.instances.forEach((inst, i) => { inst.index = i; });
362
+ iterationNode.runtime.instances.forEach((inst, i) => {
363
+ inst.index = i;
364
+ });
346
365
  };
347
366
 
348
367
  // Add a new instance at the specified index
@@ -356,12 +375,14 @@ const addInstance = (iterationNode, item, index, state, linkList, parentScope) =
356
375
  const { element, tree, clonedNodes } = initializeBlock(templateNodes, scopedState, template);
357
376
 
358
377
  // Find insertion point
359
- const insertBefore = index < iterationNode.runtime.instances.length
360
- ? iterationNode.runtime.instances[index].clonedNodes?.[0] || iterationNode.runtime.instances[index].element
361
- : endComment;
378
+ const insertBefore =
379
+ index < iterationNode.runtime.instances.length
380
+ ? iterationNode.runtime.instances[index].clonedNodes?.[0] ||
381
+ iterationNode.runtime.instances[index].element
382
+ : endComment;
362
383
 
363
384
  // Insert cloned nodes
364
- clonedNodes.forEach(node => startComment.parentNode.insertBefore(node, insertBefore));
385
+ clonedNodes.forEach((node) => startComment.parentNode.insertBefore(node, insertBefore));
365
386
 
366
387
  // Recursively render nested iterations and conditionals
367
388
  if (tree) {
@@ -380,7 +401,9 @@ const removeInstance = (iterationNode, index) => {
380
401
  const instance = iterationNode.runtime.instances[index];
381
402
 
382
403
  // Remove all cloned nodes from DOM
383
- (instance.clonedNodes || [instance.element]).forEach(node => node?.parentNode?.removeChild(node));
404
+ (instance.clonedNodes || [instance.element]).forEach((node) =>
405
+ node?.parentNode?.removeChild(node),
406
+ );
384
407
 
385
408
  iterationNode.runtime.instances.splice(index, 1);
386
409
  };
@@ -401,11 +424,11 @@ const moveInstance = (iterationNode, fromIndex, toIndex) => {
401
424
  // Find new insertion point
402
425
  const nextInstance = iterationNode.runtime.instances[toIndex + 1];
403
426
  const insertBefore = nextInstance
404
- ? (nextInstance.clonedNodes?.[0] || nextInstance.element)
427
+ ? nextInstance.clonedNodes?.[0] || nextInstance.element
405
428
  : iterationNode.meta.endComment;
406
429
 
407
430
  // Move all nodes
408
- nodes.forEach(node => parent.insertBefore(node, insertBefore));
431
+ nodes.forEach((node) => parent.insertBefore(node, insertBefore));
409
432
  };
410
433
 
411
434
  // Update an instance with new item data
@@ -426,8 +449,8 @@ const updateInstance = (iterationNode, index, newItem, state, linkList, parentSc
426
449
  const insertBefore = oldNodes[oldNodes.length - 1]?.nextSibling;
427
450
  const parent = oldNodes[0]?.parentNode;
428
451
 
429
- oldNodes.forEach(node => node?.parentNode?.removeChild(node));
430
- clonedNodes.forEach(node => parent.insertBefore(node, insertBefore));
452
+ oldNodes.forEach((node) => node?.parentNode?.removeChild(node));
453
+ clonedNodes.forEach((node) => parent.insertBefore(node, insertBefore));
431
454
 
432
455
  // Recursively render nested iterations and conditionals
433
456
  if (tree) {
@@ -447,5 +470,5 @@ export default {
447
470
  renderIteration,
448
471
  updateIteration,
449
472
  renderAllIterations,
450
- createScopedState
473
+ createScopedState,
451
474
  };