@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.
@@ -7787,13 +7787,20 @@
7787
7787
  }
7788
7788
  /**
7789
7789
  * Updates a regular attribute.
7790
+ * An attribute that already holds the value is left alone: setting `src` on
7791
+ * an <iframe>, even to the same URL, loads its page again.
7790
7792
  */
7791
7793
  #updateAttribute(element, name, value) {
7792
7794
  if (value == null || value === false) {
7793
- element.removeAttribute(name);
7795
+ if (element.hasAttribute(name)) {
7796
+ element.removeAttribute(name);
7797
+ }
7794
7798
  }
7795
7799
  else {
7796
- element.setAttribute(name, String(value));
7800
+ const text = String(value);
7801
+ if (element.getAttribute(name) !== text) {
7802
+ element.setAttribute(name, text);
7803
+ }
7797
7804
  }
7798
7805
  }
7799
7806
  }
@@ -11319,6 +11326,25 @@
11319
11326
  * Previous iterations to detect changes
11320
11327
  */
11321
11328
  #previousIterations = [];
11329
+ /**
11330
+ * The source array watched for writes made directly to it, and the function
11331
+ * that stops watching it.
11332
+ *
11333
+ * A write such as `items[1] = x` is reported as a change of "items[1]", which
11334
+ * does not match this directive's dependency on "items", and the row's loop
11335
+ * variable holds the value it was rendered with. The directive therefore
11336
+ * watches the array itself and refreshes the rows that were written.
11337
+ */
11338
+ #watchedSource;
11339
+ #unwatchSource;
11340
+ /**
11341
+ * Positions written since the last refresh, and whether a write (such as one
11342
+ * to `length`) needs the whole list rendered again.
11343
+ */
11344
+ #writtenIndexes = new Set();
11345
+ #writtenWhole = false;
11346
+ #refreshScheduled = false;
11347
+ #destroyed = false;
11322
11348
  /**
11323
11349
  * @param context The context for parsing the directive.
11324
11350
  */
@@ -11435,6 +11461,10 @@
11435
11461
  * @inheritdoc
11436
11462
  */
11437
11463
  destroy() {
11464
+ this.#destroyed = true;
11465
+ this.#unwatchSource?.();
11466
+ this.#unwatchSource = undefined;
11467
+ this.#watchedSource = undefined;
11438
11468
  // Clean up all rendered items
11439
11469
  // First destroy all VNodes (calls @unmount hooks), then remove from DOM
11440
11470
  for (const { vNode } of this.#renderedItems) {
@@ -11466,6 +11496,10 @@
11466
11496
  }
11467
11497
  // Evaluate the source expression to get the data
11468
11498
  const sourceData = this.#sourceEvaluator.evaluate();
11499
+ // Every row is rendered from the source below, including those written in place
11500
+ this.#watchSource(sourceData);
11501
+ this.#writtenIndexes.clear();
11502
+ this.#writtenWhole = false;
11469
11503
  // Get iterations from the source data
11470
11504
  let iterations = this.#getIterations(sourceData);
11471
11505
  // If we don't have a key evaluator yet, try to create it
@@ -11859,6 +11893,89 @@
11859
11893
  }
11860
11894
  vNode.update();
11861
11895
  }
11896
+ /**
11897
+ * Watches the source for writes made directly to its elements. Only a
11898
+ * reactive array can be written that way; anything else is not watched.
11899
+ */
11900
+ #watchSource(source) {
11901
+ if (source === this.#watchedSource) {
11902
+ return;
11903
+ }
11904
+ this.#unwatchSource?.();
11905
+ this.#unwatchSource = undefined;
11906
+ this.#watchedSource = source;
11907
+ const path = Array.isArray(source) ? ReactiveProxy.getPath(source) : undefined;
11908
+ if (!path) {
11909
+ return;
11910
+ }
11911
+ this.#unwatchSource = ReactiveProxy.subscribe(source, (changedPath) => {
11912
+ this.#onSourceWritten(path, changedPath);
11913
+ });
11914
+ }
11915
+ /**
11916
+ * Notes a write made directly to the source array. A change below an
11917
+ * element (e.g. "items[1].name") is followed by the row's own bindings, and
11918
+ * a change of the whole array (e.g. by splice) by the usual dependency on it.
11919
+ */
11920
+ #onSourceWritten(path, changedPath) {
11921
+ if (!changedPath || !changedPath.startsWith(path + '[') || !changedPath.endsWith(']')) {
11922
+ return;
11923
+ }
11924
+ const property = changedPath.substring(path.length + 1, changedPath.length - 1);
11925
+ if (property.includes(']')) {
11926
+ return;
11927
+ }
11928
+ if (/^\d+$/.test(property)) {
11929
+ this.#writtenIndexes.add(Number(property));
11930
+ }
11931
+ else {
11932
+ // e.g. `items.length = 0`
11933
+ this.#writtenWhole = true;
11934
+ }
11935
+ // Writes made together are refreshed together, after the application's update
11936
+ if (!this.#refreshScheduled) {
11937
+ this.#refreshScheduled = true;
11938
+ queueMicrotask(() => {
11939
+ this.#refreshScheduled = false;
11940
+ this.#refreshWritten();
11941
+ });
11942
+ }
11943
+ }
11944
+ /**
11945
+ * Shows the elements written since the last refresh. A row whose position or
11946
+ * key no longer holds is not patched; the whole list is rendered again.
11947
+ */
11948
+ #refreshWritten() {
11949
+ if (this.#destroyed || (!this.#writtenWhole && this.#writtenIndexes.size === 0)) {
11950
+ return;
11951
+ }
11952
+ const indexes = [...this.#writtenIndexes];
11953
+ const whole = this.#writtenWhole;
11954
+ this.#writtenIndexes.clear();
11955
+ this.#writtenWhole = false;
11956
+ const source = this.#sourceEvaluator?.evaluate();
11957
+ if (whole || source !== this.#watchedSource || !Array.isArray(source) || source.length !== this.#renderedItems.length) {
11958
+ this.#render();
11959
+ return;
11960
+ }
11961
+ for (const index of indexes) {
11962
+ const rendered = this.#renderedItems[index];
11963
+ let context = { key: index, item: source[index], index };
11964
+ if (this.#evaluateKey && (this.#itemName || this.#itemDestructure)) {
11965
+ const itemBindings = new VBindings({
11966
+ parent: this.#vNode.bindings
11967
+ });
11968
+ this.#setItemBindings(itemBindings, context);
11969
+ context = { ...context, key: this.#evaluateKey(itemBindings) };
11970
+ }
11971
+ if (!rendered || context.key !== rendered.key) {
11972
+ this.#render();
11973
+ return;
11974
+ }
11975
+ this.#updateItemBindings(rendered.vNode, context);
11976
+ this.#previousIterations[index] = context;
11977
+ }
11978
+ }
11862
11979
  /**
11863
11980
  * Get iterations from various data types
11864
11981
  * Supports: Arrays, Objects, Sets, Maps, Iterables, Numbers, Strings
@@ -11868,7 +11985,8 @@
11868
11985
  return [];
11869
11986
  // Array
11870
11987
  if (Array.isArray(data)) {
11871
- return data.map((item, index) => ({
11988
+ // Array.from rather than map: a hole (e.g. after `items[5] = x` past the end) is a row of undefined
11989
+ return Array.from(data, (item, index) => ({
11872
11990
  item,
11873
11991
  index,
11874
11992
  key: index
@@ -11949,6 +12067,11 @@
11949
12067
  * The expression evaluator for this directive.
11950
12068
  */
11951
12069
  #evaluator;
12070
+ /**
12071
+ * The HTML last set on the element. The same HTML is not set again: that
12072
+ * would replace the elements inside, and load their frames and images again.
12073
+ */
12074
+ #html;
11952
12075
  /**
11953
12076
  * @param context The context for parsing the directive.
11954
12077
  */
@@ -11999,10 +12122,14 @@
11999
12122
  get dependentIdentifiers() {
12000
12123
  return identifiers;
12001
12124
  },
12002
- applyToDOM() {
12125
+ applyToDOM: () => {
12003
12126
  const element = vNode.node;
12004
- const htmlContent = evaluator.evaluate();
12005
- element.innerHTML = htmlContent ?? '';
12127
+ const htmlContent = evaluator.evaluate() ?? '';
12128
+ if (htmlContent === this.#html) {
12129
+ return;
12130
+ }
12131
+ this.#html = htmlContent;
12132
+ element.innerHTML = htmlContent;
12006
12133
  }
12007
12134
  };
12008
12135
  return updater;