veles 1.1.2 → 1.1.4

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/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { _ as onMount, a as identity, c as addPublicContext, d as popPublicContext, f as Fragment, g as hasCurrentLifecycleContext, h as createTextElement, i as getMountedNodeExecutedVersion, l as createContext, m as assignDomAttribute, n as callUnmountHandlers, o as renderTree, p as createElement, r as getExecutedComponentVelesNode, s as unique, t as callMountHandlers, u as getCurrentContext, v as onUnmount } from "./_utils-BnAXelPu.js";
1
+ import { _ as hasCurrentLifecycleContext, a as getMountedNodeExecutedVersion, c as unique, d as getCurrentContext, f as popPublicContext, g as createTextElement, h as assignDomAttribute, i as getExecutedVelesNodeSourceNode, l as addPublicContext, m as createElement, n as callUnmountHandlers, o as identity, p as Fragment, r as getExecutedComponentVelesNode, s as renderTree, t as callMountHandlers, u as createContext, v as onMount, y as onUnmount } from "./_utils-B1lbF1Al.js";
2
2
  //#region src/attach-component.ts
3
3
  /**
4
4
  * Attach Veles component tree to a regular HTML node.
@@ -65,69 +65,42 @@ function updateUseValueSelector({ value, selectorTrackingElement, newTrackingSel
65
65
  newVelesElementNode.parentVelesElement = parentVelesElementRendered;
66
66
  if ("executedVelesNode" in newVelesElementNode && newVelesElementNode.phantom) {
67
67
  const insertAllPhantomChildren = (adjacentNode) => {
68
+ const adjacentSourceNode = getExecutedVelesNodeSourceNode(adjacentNode);
68
69
  newVelesElementNode.childComponents.forEach((childComponentofPhantom) => {
69
- if ("executedVelesNode" in childComponentofPhantom) {
70
- adjacentNode.html.before(childComponentofPhantom.html);
71
- childComponentofPhantom.parentVelesElement = adjacentNode.parentVelesElement;
72
- } else {
73
- const velesElementNode = getExecutedComponentVelesNode(childComponentofPhantom);
74
- if (!velesElementNode) console.error("can't find HTML tree in a component chain");
75
- else {
76
- adjacentNode.html.before(velesElementNode.html);
77
- velesElementNode.parentVelesElement = adjacentNode.parentVelesElement;
78
- }
79
- }
70
+ const childNode = "executedVelesComponent" in childComponentofPhantom ? getExecutedComponentVelesNode(childComponentofPhantom) : childComponentofPhantom;
71
+ adjacentSourceNode.before(getExecutedVelesNodeSourceNode(childNode));
72
+ childNode.parentVelesElement = adjacentNode.parentVelesElement;
80
73
  });
81
74
  };
82
75
  if ("executedVelesNode" in oldVelesElementNode && oldVelesElementNode.phantom) {
83
76
  let isInserted = false;
84
77
  oldVelesElementNode.childComponents.forEach((childComponentofPhantom) => {
85
- if ("executedVelesNode" in childComponentofPhantom) {
86
- if (!isInserted) {
87
- insertAllPhantomChildren(childComponentofPhantom);
88
- isInserted = true;
89
- }
90
- childComponentofPhantom.html.remove();
91
- } else {
92
- const velesElementNode = getExecutedComponentVelesNode(childComponentofPhantom);
93
- if (!velesElementNode) console.error("can't find HTML tree in a component chain");
94
- else {
95
- if (!isInserted) {
96
- insertAllPhantomChildren(velesElementNode);
97
- isInserted = true;
98
- }
99
- velesElementNode.html.remove();
100
- }
78
+ const childNode = "executedVelesComponent" in childComponentofPhantom ? getExecutedComponentVelesNode(childComponentofPhantom) : childComponentofPhantom;
79
+ if (!isInserted) {
80
+ insertAllPhantomChildren(childNode);
81
+ isInserted = true;
101
82
  }
83
+ getExecutedVelesNodeSourceNode(childNode).remove();
102
84
  });
103
85
  } else {
104
86
  insertAllPhantomChildren(oldVelesElementNode);
105
- oldVelesElementNode.html.remove();
87
+ getExecutedVelesNodeSourceNode(oldVelesElementNode).remove();
106
88
  }
107
89
  } else if ("executedVelesNode" in oldVelesElementNode && oldVelesElementNode.phantom) {
108
90
  let isInserted = false;
109
91
  oldVelesElementNode.childComponents.forEach((childComponentofPhantom) => {
110
- if ("executedVelesNode" in childComponentofPhantom) {
111
- if (!isInserted) {
112
- childComponentofPhantom.html.before(newVelesElementNode.html);
113
- isInserted = true;
114
- }
115
- childComponentofPhantom.html.remove();
116
- } else {
117
- const velesElementNode = getExecutedComponentVelesNode(childComponentofPhantom);
118
- if (!velesElementNode) console.error("can't find HTML tree in a component chain");
119
- else {
120
- if (!isInserted) {
121
- velesElementNode.html.before(newVelesElementNode.html);
122
- isInserted = true;
123
- }
124
- velesElementNode.html.remove();
125
- }
92
+ const childSourceNode = getExecutedVelesNodeSourceNode("executedVelesComponent" in childComponentofPhantom ? getExecutedComponentVelesNode(childComponentofPhantom) : childComponentofPhantom);
93
+ if (!isInserted) {
94
+ childSourceNode.before(getExecutedVelesNodeSourceNode(newVelesElementNode));
95
+ isInserted = true;
126
96
  }
97
+ childSourceNode.remove();
127
98
  });
128
99
  } else try {
129
- if (parentVelesElementRendered.portal) parentVelesElementRendered.portal.replaceChild(newVelesElementNode.html, oldVelesElementNode.html);
130
- else parentVelesElementRendered.html.replaceChild(newVelesElementNode.html, oldVelesElementNode.html);
100
+ const newSourceNode = getExecutedVelesNodeSourceNode(newVelesElementNode);
101
+ const oldSourceNode = getExecutedVelesNodeSourceNode(oldVelesElementNode);
102
+ if (parentVelesElementRendered.portal) parentVelesElementRendered.portal.replaceChild(newSourceNode, oldSourceNode);
103
+ else parentVelesElementRendered.html.replaceChild(newSourceNode, oldSourceNode);
131
104
  } catch (e) {
132
105
  console.error("failed to update in renderSelected", e);
133
106
  }
@@ -204,7 +177,7 @@ function updateUseAttributeValue({ element, value }) {
204
177
  //#endregion
205
178
  //#region src/create-state/update-render-each-value.ts
206
179
  function updateUseValueIteratorValue({ value, trackingIterator, createState }) {
207
- const { cb, key, renderedElements, elementsByKey, wrapperComponent, selector, savedContext } = trackingIterator;
180
+ const { anchor, cb, key, renderedElements, elementsByKey, wrapperComponent, selector, savedContext } = trackingIterator;
208
181
  if (!wrapperComponent) {
209
182
  console.error("there is no wrapper component for the iterator");
210
183
  return;
@@ -218,19 +191,19 @@ function updateUseValueIteratorValue({ value, trackingIterator, createState }) {
218
191
  const elements = selector ? selector(value) : value;
219
192
  if (Array.isArray(elements)) {
220
193
  const newRenderedElements = [];
221
- const newElementsByKey = {};
222
- const renderedExistingElements = {};
194
+ const newElementsByKey = /* @__PURE__ */ new Map();
195
+ const renderedExistingElements = /* @__PURE__ */ new Set();
223
196
  elements.forEach((element, index) => {
224
- let calculatedKey = "";
197
+ let calculatedKey = null;
225
198
  if (typeof key === "string" && typeof element === "object" && element !== null && key in element) calculatedKey = element[key];
226
199
  else if (typeof key === "function") calculatedKey = key({
227
200
  element,
228
201
  index
229
202
  });
230
- if (!calculatedKey) return;
231
- const existingElement = elementsByKey[calculatedKey];
203
+ if (calculatedKey == null) return;
204
+ const existingElement = elementsByKey.get(calculatedKey);
232
205
  if (existingElement) {
233
- renderedExistingElements[calculatedKey] = true;
206
+ renderedExistingElements.add(calculatedKey);
234
207
  if (existingElement.elementState.get() !== element) existingElement.elementState.set(element);
235
208
  if (existingElement.indexState.get() !== index) existingElement.indexState.set(index);
236
209
  newRenderedElements.push([
@@ -238,12 +211,12 @@ function updateUseValueIteratorValue({ value, trackingIterator, createState }) {
238
211
  calculatedKey,
239
212
  existingElement.elementState
240
213
  ]);
241
- newElementsByKey[calculatedKey] = {
214
+ newElementsByKey.set(calculatedKey, {
242
215
  elementState: existingElement.elementState,
243
216
  indexState: existingElement.indexState,
244
217
  indexValue: index,
245
218
  node: existingElement.node
246
- };
219
+ });
247
220
  } else {
248
221
  const elementState = createState(element);
249
222
  const indexState = createState(index);
@@ -259,12 +232,12 @@ function updateUseValueIteratorValue({ value, trackingIterator, createState }) {
259
232
  calculatedKey,
260
233
  elementState
261
234
  ]);
262
- newElementsByKey[calculatedKey] = {
235
+ newElementsByKey.set(calculatedKey, {
263
236
  elementState,
264
237
  indexState,
265
238
  indexValue: index,
266
239
  node
267
- };
240
+ });
268
241
  }
269
242
  });
270
243
  const newChildRenderedComponents = [];
@@ -278,61 +251,65 @@ function updateUseValueIteratorValue({ value, trackingIterator, createState }) {
278
251
  newChildComponents.push(newRenderedElement[0]);
279
252
  if (positioningOffset[index]) offset = offset + positioningOffset[index];
280
253
  const [newNode, calculatedKey, _newState] = newRenderedElement;
281
- const existingElement = elementsByKey[calculatedKey];
254
+ const existingElement = elementsByKey.get(calculatedKey);
282
255
  if (existingElement) {
283
- const existingElementNode = getExecutedComponentVelesNode(getMountedNodeExecutedVersion(existingElement.node, "Existing iterator node is expected to be mounted"));
256
+ const existingElementSourceNode = getExecutedVelesNodeSourceNode(getExecutedComponentVelesNode(getMountedNodeExecutedVersion(existingElement.node, "Existing iterator node is expected to be mounted")));
284
257
  if (existingElement.indexValue + offset === index) {
285
- currentElement = existingElementNode.html;
258
+ currentElement = existingElementSourceNode;
286
259
  return;
287
260
  }
288
261
  if (existingElement.indexValue + offset > index) {
289
262
  if (currentElement) {
290
- currentElement.after(existingElementNode.html);
263
+ currentElement.after(existingElementSourceNode);
291
264
  positioningOffset[existingElement.indexValue + 1] = -1;
292
265
  } else {
293
266
  const firstRenderedElement = renderedElements[0]?.[0];
294
- if (firstRenderedElement?.executedVersion) getExecutedComponentVelesNode(firstRenderedElement.executedVersion).html.before(existingElementNode.html);
267
+ if (firstRenderedElement?.executedVersion) getExecutedVelesNodeSourceNode(getExecutedComponentVelesNode(firstRenderedElement.executedVersion)).before(existingElementSourceNode);
295
268
  }
296
- currentElement = existingElementNode.html;
269
+ currentElement = existingElementSourceNode;
297
270
  offset = offset + 1;
298
271
  } else {
299
272
  if (currentElement) {
300
- currentElement.after(existingElementNode.html);
273
+ currentElement.after(existingElementSourceNode);
301
274
  positioningOffset[existingElement.indexValue + 1] = 1;
302
275
  } else {
303
276
  const firstRenderedElement = renderedElements[0]?.[0];
304
- if (firstRenderedElement?.executedVersion) getExecutedComponentVelesNode(firstRenderedElement.executedVersion).html.before(existingElementNode.html);
277
+ if (firstRenderedElement?.executedVersion) getExecutedVelesNodeSourceNode(getExecutedComponentVelesNode(firstRenderedElement.executedVersion)).before(existingElementSourceNode);
305
278
  }
306
- currentElement = existingElementNode.html;
279
+ currentElement = existingElementSourceNode;
307
280
  offset = offset - 1;
308
281
  }
309
282
  } else {
310
283
  const newNodeExecutedVersion = getMountedNodeExecutedVersion(newNode, "New iterator node is expected to be mounted");
311
284
  const newNodeVelesElement = getExecutedComponentVelesNode(newNodeExecutedVersion);
285
+ const newNodeSourceNode = getExecutedVelesNodeSourceNode(newNodeVelesElement);
312
286
  newNodeVelesElement.parentVelesElement = parentVelesElement;
313
- if (currentElement) currentElement.after(newNodeVelesElement.html);
287
+ if (currentElement) currentElement.after(newNodeSourceNode);
314
288
  else {
315
289
  const firstRenderedElement = renderedElements[0]?.[0];
316
- if (firstRenderedElement?.executedVersion) getExecutedComponentVelesNode(firstRenderedElement.executedVersion).html.before(newNodeVelesElement.html);
317
- else parentVelesElement.html.prepend(newNodeVelesElement.html);
290
+ if (firstRenderedElement?.executedVersion) getExecutedVelesNodeSourceNode(getExecutedComponentVelesNode(firstRenderedElement.executedVersion)).before(newNodeSourceNode);
291
+ else anchor.html.before(newNodeSourceNode);
318
292
  }
319
293
  offset = offset + 1;
320
- currentElement = newNodeVelesElement.html;
294
+ currentElement = newNodeSourceNode;
321
295
  newElementsCount = newElementsCount + 1;
322
296
  callMountHandlers(newNodeExecutedVersion);
323
297
  }
324
298
  });
325
299
  if (renderedElements.length === newRenderedElements.length + newElementsCount) {} else renderedElements.forEach(([oldNode, calculatedKey]) => {
326
- if (renderedExistingElements[calculatedKey] === true) return;
300
+ if (renderedExistingElements.has(calculatedKey)) return;
327
301
  else {
328
302
  const oldNodeExecutedVersion = getMountedNodeExecutedVersion(oldNode, "Removed iterator node is expected to be mounted");
329
- getExecutedComponentVelesNode(oldNodeExecutedVersion).html.remove();
303
+ getExecutedVelesNodeSourceNode(getExecutedComponentVelesNode(oldNodeExecutedVersion)).remove();
330
304
  callUnmountHandlers(oldNodeExecutedVersion);
331
305
  if ("executedVelesNode" in wrapperVelesElementNode) wrapperVelesElementNode.childComponents = wrapperVelesElementNode.childComponents.filter((childComponent) => childComponent !== oldNodeExecutedVersion);
332
306
  else throw new Error("Wrapper iterator element is a string");
333
307
  if ("velesNode" in wrapperComponent) wrapperComponent.childComponents = wrapperComponent.childComponents.filter((childComponent) => childComponent !== oldNode);
334
308
  }
335
309
  });
310
+ if (!anchor.executedVersion) throw new Error("Iterator anchor is expected to be mounted");
311
+ newChildRenderedComponents.push(anchor.executedVersion);
312
+ newChildComponents.push(anchor);
336
313
  if ("executedVelesNode" in wrapperVelesElementNode) wrapperVelesElementNode.childComponents = newChildRenderedComponents;
337
314
  if ("velesNode" in wrapperComponent) wrapperComponent.childComponents = newChildComponents;
338
315
  trackingIterator.renderedElements = newRenderedElements;
@@ -369,6 +346,113 @@ function triggerUpdates({ value, createState, trackers, get }) {
369
346
  });
370
347
  }
371
348
  //#endregion
349
+ //#region src/create-state/min-heap.ts
350
+ /**
351
+ * A stable min-heap. Values with the same priority are returned in
352
+ * insertion order.
353
+ */
354
+ var MinHeap = class {
355
+ constructor(compareValues) {
356
+ this._entries = [];
357
+ this._insertionOrder = 0;
358
+ this._compareValues = compareValues;
359
+ }
360
+ get size() {
361
+ return this._entries.length;
362
+ }
363
+ push(value) {
364
+ const entry = {
365
+ value,
366
+ insertionOrder: this._insertionOrder++
367
+ };
368
+ let index = this._entries.length;
369
+ this._entries.push(entry);
370
+ while (index > 0) {
371
+ const parentIndex = Math.floor((index - 1) / 2);
372
+ const parentEntry = this._entries[parentIndex];
373
+ if (this.compareEntries(parentEntry, entry) <= 0) break;
374
+ this._entries[index] = parentEntry;
375
+ index = parentIndex;
376
+ }
377
+ this._entries[index] = entry;
378
+ }
379
+ pop() {
380
+ const firstEntry = this._entries[0];
381
+ const lastEntry = this._entries.pop();
382
+ if (!firstEntry || !lastEntry) return void 0;
383
+ if (this._entries.length === 0) return firstEntry.value;
384
+ let index = 0;
385
+ while (true) {
386
+ const leftChildIndex = index * 2 + 1;
387
+ const rightChildIndex = leftChildIndex + 1;
388
+ let smallestIndex = index;
389
+ if (leftChildIndex < this._entries.length && this.compareEntries(this._entries[leftChildIndex], lastEntry) < 0) smallestIndex = leftChildIndex;
390
+ if (rightChildIndex < this._entries.length && this.compareEntries(this._entries[rightChildIndex], smallestIndex === index ? lastEntry : this._entries[leftChildIndex]) < 0) smallestIndex = rightChildIndex;
391
+ if (smallestIndex === index) break;
392
+ this._entries[index] = this._entries[smallestIndex];
393
+ index = smallestIndex;
394
+ }
395
+ this._entries[index] = lastEntry;
396
+ return firstEntry.value;
397
+ }
398
+ compareEntries(entry1, entry2) {
399
+ const valueComparison = this._compareValues(entry1.value, entry2.value);
400
+ return valueComparison === 0 ? entry1.insertionOrder - entry2.insertionOrder : valueComparison;
401
+ }
402
+ };
403
+ //#endregion
404
+ //#region src/create-state/state-scheduler.ts
405
+ /**
406
+ * Coordinates notifications produced by one synchronous state transaction.
407
+ *
408
+ * A subscriber can synchronously update another state. That nested update adds
409
+ * its notifications to this same scheduler, allowing an older notification for
410
+ * the same state to be replaced before it is delivered.
411
+ */
412
+ var StateScheduler = class {
413
+ constructor() {
414
+ this._notifications = new MinHeap((notification1, notification2) => notification1.rank - notification2.rank);
415
+ this._notificationsByKey = /* @__PURE__ */ new Map();
416
+ }
417
+ scheduleNotification(notification) {
418
+ const existingNotification = this._notificationsByKey.get(notification.key);
419
+ if (existingNotification) {
420
+ existingNotification.value = notification.value;
421
+ return;
422
+ }
423
+ this._notificationsByKey.set(notification.key, notification);
424
+ this._notifications.push(notification);
425
+ }
426
+ deliverNotifications() {
427
+ while (this._notifications.size > 0) {
428
+ const notification = this._notifications.pop();
429
+ if (this._notificationsByKey.get(notification.key) !== notification) continue;
430
+ this._notificationsByKey.delete(notification.key);
431
+ notification.notify(notification.value, notification.previousValue);
432
+ }
433
+ }
434
+ };
435
+ let activeScheduler;
436
+ /**
437
+ * Top-level state updates own the scheduler and synchronously drain it. Updates
438
+ * made by subscribers reuse the active scheduler, so they cannot deliver a
439
+ * nested notification batch ahead of notifications already waiting to run.
440
+ */
441
+ function runWithStateScheduler(runUpdate) {
442
+ if (activeScheduler) {
443
+ runUpdate(activeScheduler);
444
+ return;
445
+ }
446
+ const scheduler = new StateScheduler();
447
+ activeScheduler = scheduler;
448
+ try {
449
+ runUpdate(scheduler);
450
+ scheduler.deliverNotifications();
451
+ } finally {
452
+ activeScheduler = void 0;
453
+ }
454
+ }
455
+ //#endregion
372
456
  //#region src/create-state/state-core.ts
373
457
  const emptyValue = Symbol("veles-state-core-empty");
374
458
  const defaultEquality = (value1, value2) => value1 === value2;
@@ -379,8 +463,10 @@ var StateCore = class StateCore {
379
463
  this._dirty = false;
380
464
  this._children = /* @__PURE__ */ new Set();
381
465
  this._value = initialValue;
382
- this._parents = new Set(options.parents);
466
+ const parents = options.parents || [];
467
+ this._parents = new Set(parents);
383
468
  this._compute = options.compute;
469
+ this._rank = parents.reduce((rank, parent) => Math.max(rank, parent._rank + 1), 0);
384
470
  this._dirty = Boolean(options.dirty);
385
471
  this._equality = options.equality || defaultEquality;
386
472
  }
@@ -399,14 +485,16 @@ var StateCore = class StateCore {
399
485
  }
400
486
  set(newValue) {
401
487
  if (this._equality(this._value, newValue)) return;
402
- const prevValue = this._value;
403
- this._prevValue = prevValue;
404
- this._value = newValue;
405
- this.notifySubscribers(newValue, prevValue);
406
- this._children.forEach((child) => {
407
- child._dirty = true;
488
+ runWithStateScheduler((scheduler) => {
489
+ const prevValue = this._value;
490
+ this._prevValue = prevValue;
491
+ this._value = newValue;
492
+ this.scheduleNotification(scheduler, newValue, prevValue);
493
+ this._children.forEach((child) => {
494
+ child._dirty = true;
495
+ });
496
+ this.flush(scheduler);
408
497
  });
409
- this.flush();
410
498
  }
411
499
  update(fn) {
412
500
  const newValue = fn(this._value);
@@ -493,37 +581,62 @@ var StateCore = class StateCore {
493
581
  this._dirty = false;
494
582
  return { changed };
495
583
  }
496
- flush() {
497
- const queue = Array.from(this._children);
498
- const queued = new Set(queue);
499
- while (queue.length > 0) {
500
- const child = queue.shift();
584
+ flush(scheduler) {
585
+ /**
586
+ * We utilize a min-heap to ensure that we don't trigger notifications
587
+ * before all the previous necessary work is done. Otherwise it can
588
+ * happen too early, and we'd need to recursively check that every parent
589
+ * is not dirty and schedule it again.
590
+ *
591
+ * Here we achieve this by assinging a rank to each core state, which is
592
+ * determined by the highest rank of any of the parent + 1.
593
+ *
594
+ * After that, we process them at the same rank, ensuring that "short" branches
595
+ * are not executed before all their parents are done with their recomputation.
596
+ */
597
+ const queue = new MinHeap((node1, node2) => node1._rank - node2._rank);
598
+ const queued = /* @__PURE__ */ new Set();
599
+ const enqueue = (node) => {
600
+ if (queued.has(node)) return;
601
+ queue.push(node);
602
+ queued.add(node);
603
+ };
604
+ this._children.forEach(enqueue);
605
+ while (queue.size > 0) {
606
+ const child = queue.pop();
501
607
  queued.delete(child);
502
608
  if (!child._dirty) continue;
503
- if (child.hasDirtyParents()) {
504
- queue.push(child);
505
- queued.add(child);
609
+ let shouldRescheduleChild = false;
610
+ child._parents.forEach((parentNode) => {
611
+ if (parentNode._dirty) {
612
+ enqueue(parentNode);
613
+ shouldRescheduleChild = true;
614
+ }
615
+ });
616
+ if (shouldRescheduleChild) {
617
+ enqueue(child);
506
618
  continue;
507
619
  }
508
620
  const { changed } = child.recompute();
509
621
  const value = child._value;
510
622
  if (!changed) continue;
511
- child.notifySubscribers(value, child._prevValue);
623
+ child.scheduleNotification(scheduler, value, child._prevValue);
512
624
  child._children.forEach((grandchild) => {
513
625
  grandchild._dirty = true;
514
- if (!queued.has(grandchild)) {
515
- queue.push(grandchild);
516
- queued.add(grandchild);
517
- }
626
+ enqueue(grandchild);
518
627
  });
519
628
  }
520
629
  }
521
- hasDirtyParents() {
522
- let hasDirtyParent = false;
523
- this._parents.forEach((parent) => {
524
- if (parent._dirty) hasDirtyParent = true;
630
+ scheduleNotification(scheduler, value, previousValue) {
631
+ scheduler.scheduleNotification({
632
+ key: this,
633
+ rank: this._rank,
634
+ value,
635
+ previousValue,
636
+ notify: (notificationValue, notificationPreviousValue) => {
637
+ this.notifySubscribers(notificationValue, notificationPreviousValue);
638
+ }
525
639
  });
526
- return hasDirtyParent;
527
640
  }
528
641
  notifySubscribers(value, prevValue) {
529
642
  if (value === emptyValue) return;
@@ -644,7 +757,10 @@ function createStateFromCore(core, subscribeCallback) {
644
757
  trackingParams.savedContext = currentContext;
645
758
  const wrapperComponent = createElement((_props, componentAPI) => {
646
759
  const children = [];
647
- const elementsByKey = {};
760
+ const anchor = createTextElement("");
761
+ anchor.needExecutedVersion = true;
762
+ trackingParams.anchor = anchor;
763
+ const elementsByKey = /* @__PURE__ */ new Map();
648
764
  const stateValue = core.get();
649
765
  const elements = options.selector ? options.selector(stateValue) : stateValue;
650
766
  if (!Array.isArray(elements)) {
@@ -652,7 +768,7 @@ function createStateFromCore(core, subscribeCallback) {
652
768
  return null;
653
769
  }
654
770
  elements.forEach((element, index) => {
655
- let calculatedKey = "";
771
+ let calculatedKey = null;
656
772
  if (typeof options.key === "string" && typeof element === "object" && element !== null && options.key in element) calculatedKey = element[options.key];
657
773
  else if (typeof options.key === "function") calculatedKey = options.key({
658
774
  element,
@@ -660,18 +776,18 @@ function createStateFromCore(core, subscribeCallback) {
660
776
  });
661
777
  const elementState = createState(element);
662
778
  const indexState = createState(index);
663
- if (!calculatedKey) return;
779
+ if (calculatedKey == null) return;
664
780
  let node = cb({
665
781
  elementState,
666
782
  indexState
667
783
  });
668
784
  node.needExecutedVersion = true;
669
- elementsByKey[calculatedKey] = {
785
+ elementsByKey.set(calculatedKey, {
670
786
  node,
671
787
  indexState,
672
788
  indexValue: index,
673
789
  elementState
674
- };
790
+ });
675
791
  children.push([
676
792
  node,
677
793
  calculatedKey,
@@ -688,7 +804,7 @@ function createStateFromCore(core, subscribeCallback) {
688
804
  });
689
805
  return createElement("div", {
690
806
  phantom: true,
691
- children: children.map((child) => child[0])
807
+ children: [...children.map((child) => child[0]), anchor]
692
808
  });
693
809
  });
694
810
  wrapperComponent.needExecutedVersion = true;
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require__utils = require("./_utils-DbrPydEE.cjs");
2
+ const require__utils = require("./_utils-CXVRQTuz.cjs");
3
3
  exports.Fragment = require__utils.Fragment;
4
4
  exports.jsx = require__utils.createElement;
5
5
  exports.jsxDEV = require__utils.createElement;
@@ -1,2 +1,2 @@
1
- import { c as JSX, n as createElement, t as Fragment } from "./fragment-C16zCm8L.cjs";
1
+ import { c as JSX, n as createElement, t as Fragment } from "./fragment-COsp_SDE.cjs";
2
2
  export { Fragment, type JSX, createElement as jsx, createElement as jsxDEV, createElement as jsxs };
@@ -1,2 +1,2 @@
1
- import { c as JSX, n as createElement, t as Fragment } from "./fragment-yC0-T2O-.js";
1
+ import { c as JSX, n as createElement, t as Fragment } from "./fragment-CPcYB5D5.js";
2
2
  export { Fragment, type JSX, createElement as jsx, createElement as jsxDEV, createElement as jsxs };
@@ -1,2 +1,2 @@
1
- import { f as Fragment, p as createElement } from "./_utils-BnAXelPu.js";
1
+ import { m as createElement, p as Fragment } from "./_utils-B1lbF1Al.js";
2
2
  export { Fragment, createElement as jsx, createElement as jsxDEV, createElement as jsxs };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veles",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "description": "UI library with main focus on performance",
5
5
  "keywords": [
6
6
  "JavaScript",