@mintjamsinc/ichigojs 0.1.82 → 0.1.84

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.
@@ -10130,9 +10130,9 @@ class VNode {
10130
10130
  */
10131
10131
  #fragmentRange;
10132
10132
  /**
10133
- * Set by {@link destroy}. A destroyed clone stays in its parent's child
10134
- * list (v-if / v-for render new clones under the same parent), so walks
10135
- * over the children must skip it.
10133
+ * Set by {@link destroy}. A destroyed node also leaves its parent's child
10134
+ * list, but a walk that took its copy of the list before the node was
10135
+ * destroyed may still reach it, and must skip it.
10136
10136
  */
10137
10137
  #destroyed = false;
10138
10138
  /**
@@ -10302,7 +10302,8 @@ class VNode {
10302
10302
  // walk runs once the subtree is in the document, which the outlet
10303
10303
  // cannot know when it renders inside a detached v-if / v-for clone.
10304
10304
  this.#directiveManager?.directives?.forEach(d => d.expandComponents?.());
10305
- this.#childVNodes?.forEach(child => child.expandComponents());
10305
+ // A copy: expanding can render and destroy clones under this node.
10306
+ this.#childVNodes?.slice().forEach(child => child.expandComponents());
10306
10307
  }
10307
10308
  /**
10308
10309
  * The application instance associated with this virtual node.
@@ -10614,8 +10615,9 @@ class VNode {
10614
10615
  updater.applyToDOM();
10615
10616
  }
10616
10617
  }
10617
- // Recursively update child virtual nodes
10618
- this.#childVNodes?.forEach(childVNode => {
10618
+ // Recursively update child virtual nodes. A copy: an update can
10619
+ // destroy a v-if / v-for clone, which removes it from this list.
10620
+ this.#childVNodes?.slice().forEach(childVNode => {
10619
10621
  childVNode.forceUpdate();
10620
10622
  });
10621
10623
  // If no directive requires template preservation, call onUpdated for directives that do not templatize
@@ -10627,7 +10629,7 @@ class VNode {
10627
10629
  }
10628
10630
  else if (this.#nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
10629
10631
  // For document fragments, recursively force update child VNodes
10630
- this.#childVNodes?.forEach(childVNode => {
10632
+ this.#childVNodes?.slice().forEach(childVNode => {
10631
10633
  childVNode.forceUpdate();
10632
10634
  });
10633
10635
  }
@@ -10709,6 +10711,7 @@ class VNode {
10709
10711
  * 4. Unregister dependencies
10710
10712
  * 5. Clean up directive manager
10711
10713
  * 6. Call onUnmounted lifecycle hooks
10714
+ * 7. Leave the parent's child list
10712
10715
  */
10713
10716
  destroy() {
10714
10717
  this.#destroyed = true;
@@ -10735,9 +10738,9 @@ class VNode {
10735
10738
  }
10736
10739
  this.#userData.clear();
10737
10740
  }
10738
- // Recursively destroy child nodes
10741
+ // Recursively destroy child nodes (a copy: each one leaves this list)
10739
10742
  if (this.#childVNodes) {
10740
- for (const childVNode of this.#childVNodes) {
10743
+ for (const childVNode of this.#childVNodes.slice()) {
10741
10744
  try {
10742
10745
  childVNode.destroy();
10743
10746
  }
@@ -10765,6 +10768,15 @@ class VNode {
10765
10768
  d.onUnmounted?.();
10766
10769
  });
10767
10770
  }
10771
+ // Leave the parent's child list. v-if and v-for render every new clone
10772
+ // under the same parent VNode, so a destroyed clone left in the list
10773
+ // would keep its whole subtree — VNodes, DOM nodes, bindings — alive
10774
+ // for as long as the parent, growing with each toggle or row change.
10775
+ const siblings = this.#parentVNode ? this.#parentVNode.#childVNodes : undefined;
10776
+ const index = siblings ? siblings.indexOf(this) : -1;
10777
+ if (index !== -1) {
10778
+ siblings.splice(index, 1);
10779
+ }
10768
10780
  }
10769
10781
  }
10770
10782
 
@@ -11301,6 +11313,25 @@ class VForDirective {
11301
11313
  * Previous iterations to detect changes
11302
11314
  */
11303
11315
  #previousIterations = [];
11316
+ /**
11317
+ * The source array watched for writes made directly to it, and the function
11318
+ * that stops watching it.
11319
+ *
11320
+ * A write such as `items[1] = x` is reported as a change of "items[1]", which
11321
+ * does not match this directive's dependency on "items", and the row's loop
11322
+ * variable holds the value it was rendered with. The directive therefore
11323
+ * watches the array itself and refreshes the rows that were written.
11324
+ */
11325
+ #watchedSource;
11326
+ #unwatchSource;
11327
+ /**
11328
+ * Positions written since the last refresh, and whether a write (such as one
11329
+ * to `length`) needs the whole list rendered again.
11330
+ */
11331
+ #writtenIndexes = new Set();
11332
+ #writtenWhole = false;
11333
+ #refreshScheduled = false;
11334
+ #destroyed = false;
11304
11335
  /**
11305
11336
  * @param context The context for parsing the directive.
11306
11337
  */
@@ -11417,6 +11448,10 @@ class VForDirective {
11417
11448
  * @inheritdoc
11418
11449
  */
11419
11450
  destroy() {
11451
+ this.#destroyed = true;
11452
+ this.#unwatchSource?.();
11453
+ this.#unwatchSource = undefined;
11454
+ this.#watchedSource = undefined;
11420
11455
  // Clean up all rendered items
11421
11456
  // First destroy all VNodes (calls @unmount hooks), then remove from DOM
11422
11457
  for (const { vNode } of this.#renderedItems) {
@@ -11448,6 +11483,10 @@ class VForDirective {
11448
11483
  }
11449
11484
  // Evaluate the source expression to get the data
11450
11485
  const sourceData = this.#sourceEvaluator.evaluate();
11486
+ // Every row is rendered from the source below, including those written in place
11487
+ this.#watchSource(sourceData);
11488
+ this.#writtenIndexes.clear();
11489
+ this.#writtenWhole = false;
11451
11490
  // Get iterations from the source data
11452
11491
  let iterations = this.#getIterations(sourceData);
11453
11492
  // If we don't have a key evaluator yet, try to create it
@@ -11841,6 +11880,89 @@ class VForDirective {
11841
11880
  }
11842
11881
  vNode.update();
11843
11882
  }
11883
+ /**
11884
+ * Watches the source for writes made directly to its elements. Only a
11885
+ * reactive array can be written that way; anything else is not watched.
11886
+ */
11887
+ #watchSource(source) {
11888
+ if (source === this.#watchedSource) {
11889
+ return;
11890
+ }
11891
+ this.#unwatchSource?.();
11892
+ this.#unwatchSource = undefined;
11893
+ this.#watchedSource = source;
11894
+ const path = Array.isArray(source) ? ReactiveProxy.getPath(source) : undefined;
11895
+ if (!path) {
11896
+ return;
11897
+ }
11898
+ this.#unwatchSource = ReactiveProxy.subscribe(source, (changedPath) => {
11899
+ this.#onSourceWritten(path, changedPath);
11900
+ });
11901
+ }
11902
+ /**
11903
+ * Notes a write made directly to the source array. A change below an
11904
+ * element (e.g. "items[1].name") is followed by the row's own bindings, and
11905
+ * a change of the whole array (e.g. by splice) by the usual dependency on it.
11906
+ */
11907
+ #onSourceWritten(path, changedPath) {
11908
+ if (!changedPath || !changedPath.startsWith(path + '[') || !changedPath.endsWith(']')) {
11909
+ return;
11910
+ }
11911
+ const property = changedPath.substring(path.length + 1, changedPath.length - 1);
11912
+ if (property.includes(']')) {
11913
+ return;
11914
+ }
11915
+ if (/^\d+$/.test(property)) {
11916
+ this.#writtenIndexes.add(Number(property));
11917
+ }
11918
+ else {
11919
+ // e.g. `items.length = 0`
11920
+ this.#writtenWhole = true;
11921
+ }
11922
+ // Writes made together are refreshed together, after the application's update
11923
+ if (!this.#refreshScheduled) {
11924
+ this.#refreshScheduled = true;
11925
+ queueMicrotask(() => {
11926
+ this.#refreshScheduled = false;
11927
+ this.#refreshWritten();
11928
+ });
11929
+ }
11930
+ }
11931
+ /**
11932
+ * Shows the elements written since the last refresh. A row whose position or
11933
+ * key no longer holds is not patched; the whole list is rendered again.
11934
+ */
11935
+ #refreshWritten() {
11936
+ if (this.#destroyed || (!this.#writtenWhole && this.#writtenIndexes.size === 0)) {
11937
+ return;
11938
+ }
11939
+ const indexes = [...this.#writtenIndexes];
11940
+ const whole = this.#writtenWhole;
11941
+ this.#writtenIndexes.clear();
11942
+ this.#writtenWhole = false;
11943
+ const source = this.#sourceEvaluator?.evaluate();
11944
+ if (whole || source !== this.#watchedSource || !Array.isArray(source) || source.length !== this.#renderedItems.length) {
11945
+ this.#render();
11946
+ return;
11947
+ }
11948
+ for (const index of indexes) {
11949
+ const rendered = this.#renderedItems[index];
11950
+ let context = { key: index, item: source[index], index };
11951
+ if (this.#evaluateKey && (this.#itemName || this.#itemDestructure)) {
11952
+ const itemBindings = new VBindings({
11953
+ parent: this.#vNode.bindings
11954
+ });
11955
+ this.#setItemBindings(itemBindings, context);
11956
+ context = { ...context, key: this.#evaluateKey(itemBindings) };
11957
+ }
11958
+ if (!rendered || context.key !== rendered.key) {
11959
+ this.#render();
11960
+ return;
11961
+ }
11962
+ this.#updateItemBindings(rendered.vNode, context);
11963
+ this.#previousIterations[index] = context;
11964
+ }
11965
+ }
11844
11966
  /**
11845
11967
  * Get iterations from various data types
11846
11968
  * Supports: Arrays, Objects, Sets, Maps, Iterables, Numbers, Strings
@@ -11850,7 +11972,8 @@ class VForDirective {
11850
11972
  return [];
11851
11973
  // Array
11852
11974
  if (Array.isArray(data)) {
11853
- return data.map((item, index) => ({
11975
+ // Array.from rather than map: a hole (e.g. after `items[5] = x` past the end) is a row of undefined
11976
+ return Array.from(data, (item, index) => ({
11854
11977
  item,
11855
11978
  index,
11856
11979
  key: index