@mintjamsinc/ichigojs 0.1.83 → 0.1.85

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.
@@ -7781,13 +7781,20 @@ class VBindDirective {
7781
7781
  }
7782
7782
  /**
7783
7783
  * Updates a regular attribute.
7784
+ * An attribute that already holds the value is left alone: setting `src` on
7785
+ * an <iframe>, even to the same URL, loads its page again.
7784
7786
  */
7785
7787
  #updateAttribute(element, name, value) {
7786
7788
  if (value == null || value === false) {
7787
- element.removeAttribute(name);
7789
+ if (element.hasAttribute(name)) {
7790
+ element.removeAttribute(name);
7791
+ }
7788
7792
  }
7789
7793
  else {
7790
- element.setAttribute(name, String(value));
7794
+ const text = String(value);
7795
+ if (element.getAttribute(name) !== text) {
7796
+ element.setAttribute(name, text);
7797
+ }
7791
7798
  }
7792
7799
  }
7793
7800
  }
@@ -11313,6 +11320,25 @@ class VForDirective {
11313
11320
  * Previous iterations to detect changes
11314
11321
  */
11315
11322
  #previousIterations = [];
11323
+ /**
11324
+ * The source array watched for writes made directly to it, and the function
11325
+ * that stops watching it.
11326
+ *
11327
+ * A write such as `items[1] = x` is reported as a change of "items[1]", which
11328
+ * does not match this directive's dependency on "items", and the row's loop
11329
+ * variable holds the value it was rendered with. The directive therefore
11330
+ * watches the array itself and refreshes the rows that were written.
11331
+ */
11332
+ #watchedSource;
11333
+ #unwatchSource;
11334
+ /**
11335
+ * Positions written since the last refresh, and whether a write (such as one
11336
+ * to `length`) needs the whole list rendered again.
11337
+ */
11338
+ #writtenIndexes = new Set();
11339
+ #writtenWhole = false;
11340
+ #refreshScheduled = false;
11341
+ #destroyed = false;
11316
11342
  /**
11317
11343
  * @param context The context for parsing the directive.
11318
11344
  */
@@ -11429,6 +11455,10 @@ class VForDirective {
11429
11455
  * @inheritdoc
11430
11456
  */
11431
11457
  destroy() {
11458
+ this.#destroyed = true;
11459
+ this.#unwatchSource?.();
11460
+ this.#unwatchSource = undefined;
11461
+ this.#watchedSource = undefined;
11432
11462
  // Clean up all rendered items
11433
11463
  // First destroy all VNodes (calls @unmount hooks), then remove from DOM
11434
11464
  for (const { vNode } of this.#renderedItems) {
@@ -11460,6 +11490,10 @@ class VForDirective {
11460
11490
  }
11461
11491
  // Evaluate the source expression to get the data
11462
11492
  const sourceData = this.#sourceEvaluator.evaluate();
11493
+ // Every row is rendered from the source below, including those written in place
11494
+ this.#watchSource(sourceData);
11495
+ this.#writtenIndexes.clear();
11496
+ this.#writtenWhole = false;
11463
11497
  // Get iterations from the source data
11464
11498
  let iterations = this.#getIterations(sourceData);
11465
11499
  // If we don't have a key evaluator yet, try to create it
@@ -11853,6 +11887,89 @@ class VForDirective {
11853
11887
  }
11854
11888
  vNode.update();
11855
11889
  }
11890
+ /**
11891
+ * Watches the source for writes made directly to its elements. Only a
11892
+ * reactive array can be written that way; anything else is not watched.
11893
+ */
11894
+ #watchSource(source) {
11895
+ if (source === this.#watchedSource) {
11896
+ return;
11897
+ }
11898
+ this.#unwatchSource?.();
11899
+ this.#unwatchSource = undefined;
11900
+ this.#watchedSource = source;
11901
+ const path = Array.isArray(source) ? ReactiveProxy.getPath(source) : undefined;
11902
+ if (!path) {
11903
+ return;
11904
+ }
11905
+ this.#unwatchSource = ReactiveProxy.subscribe(source, (changedPath) => {
11906
+ this.#onSourceWritten(path, changedPath);
11907
+ });
11908
+ }
11909
+ /**
11910
+ * Notes a write made directly to the source array. A change below an
11911
+ * element (e.g. "items[1].name") is followed by the row's own bindings, and
11912
+ * a change of the whole array (e.g. by splice) by the usual dependency on it.
11913
+ */
11914
+ #onSourceWritten(path, changedPath) {
11915
+ if (!changedPath || !changedPath.startsWith(path + '[') || !changedPath.endsWith(']')) {
11916
+ return;
11917
+ }
11918
+ const property = changedPath.substring(path.length + 1, changedPath.length - 1);
11919
+ if (property.includes(']')) {
11920
+ return;
11921
+ }
11922
+ if (/^\d+$/.test(property)) {
11923
+ this.#writtenIndexes.add(Number(property));
11924
+ }
11925
+ else {
11926
+ // e.g. `items.length = 0`
11927
+ this.#writtenWhole = true;
11928
+ }
11929
+ // Writes made together are refreshed together, after the application's update
11930
+ if (!this.#refreshScheduled) {
11931
+ this.#refreshScheduled = true;
11932
+ queueMicrotask(() => {
11933
+ this.#refreshScheduled = false;
11934
+ this.#refreshWritten();
11935
+ });
11936
+ }
11937
+ }
11938
+ /**
11939
+ * Shows the elements written since the last refresh. A row whose position or
11940
+ * key no longer holds is not patched; the whole list is rendered again.
11941
+ */
11942
+ #refreshWritten() {
11943
+ if (this.#destroyed || (!this.#writtenWhole && this.#writtenIndexes.size === 0)) {
11944
+ return;
11945
+ }
11946
+ const indexes = [...this.#writtenIndexes];
11947
+ const whole = this.#writtenWhole;
11948
+ this.#writtenIndexes.clear();
11949
+ this.#writtenWhole = false;
11950
+ const source = this.#sourceEvaluator?.evaluate();
11951
+ if (whole || source !== this.#watchedSource || !Array.isArray(source) || source.length !== this.#renderedItems.length) {
11952
+ this.#render();
11953
+ return;
11954
+ }
11955
+ for (const index of indexes) {
11956
+ const rendered = this.#renderedItems[index];
11957
+ let context = { key: index, item: source[index], index };
11958
+ if (this.#evaluateKey && (this.#itemName || this.#itemDestructure)) {
11959
+ const itemBindings = new VBindings({
11960
+ parent: this.#vNode.bindings
11961
+ });
11962
+ this.#setItemBindings(itemBindings, context);
11963
+ context = { ...context, key: this.#evaluateKey(itemBindings) };
11964
+ }
11965
+ if (!rendered || context.key !== rendered.key) {
11966
+ this.#render();
11967
+ return;
11968
+ }
11969
+ this.#updateItemBindings(rendered.vNode, context);
11970
+ this.#previousIterations[index] = context;
11971
+ }
11972
+ }
11856
11973
  /**
11857
11974
  * Get iterations from various data types
11858
11975
  * Supports: Arrays, Objects, Sets, Maps, Iterables, Numbers, Strings
@@ -11862,7 +11979,8 @@ class VForDirective {
11862
11979
  return [];
11863
11980
  // Array
11864
11981
  if (Array.isArray(data)) {
11865
- return data.map((item, index) => ({
11982
+ // Array.from rather than map: a hole (e.g. after `items[5] = x` past the end) is a row of undefined
11983
+ return Array.from(data, (item, index) => ({
11866
11984
  item,
11867
11985
  index,
11868
11986
  key: index
@@ -11943,6 +12061,11 @@ class VHtmlDirective {
11943
12061
  * The expression evaluator for this directive.
11944
12062
  */
11945
12063
  #evaluator;
12064
+ /**
12065
+ * The HTML last set on the element. The same HTML is not set again: that
12066
+ * would replace the elements inside, and load their frames and images again.
12067
+ */
12068
+ #html;
11946
12069
  /**
11947
12070
  * @param context The context for parsing the directive.
11948
12071
  */
@@ -11993,10 +12116,14 @@ class VHtmlDirective {
11993
12116
  get dependentIdentifiers() {
11994
12117
  return identifiers;
11995
12118
  },
11996
- applyToDOM() {
12119
+ applyToDOM: () => {
11997
12120
  const element = vNode.node;
11998
- const htmlContent = evaluator.evaluate();
11999
- element.innerHTML = htmlContent ?? '';
12121
+ const htmlContent = evaluator.evaluate() ?? '';
12122
+ if (htmlContent === this.#html) {
12123
+ return;
12124
+ }
12125
+ this.#html = htmlContent;
12126
+ element.innerHTML = htmlContent;
12000
12127
  }
12001
12128
  };
12002
12129
  return updater;