@schukai/monster 4.70.0 → 4.70.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
 
4
4
 
5
+ ## [4.70.1] - 2026-01-03
6
+
7
+ ### Bug Fixes
8
+
9
+ - timing issues [#359](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/359)
10
+
11
+
12
+
5
13
  ## [4.70.0] - 2026-01-03
6
14
 
7
15
  ### Add Features
package/package.json CHANGED
@@ -1 +1 @@
1
- {"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.4","@popperjs/core":"^2.11.8"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.70.0"}
1
+ {"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.4","@popperjs/core":"^2.11.8"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.70.1"}
@@ -155,9 +155,8 @@ class DataSet extends CustomElement {
155
155
  refresh() {
156
156
  // makes sure that handleDataSourceChanges is called
157
157
  return new Promise((resolve) => {
158
- this.setOption("data", {});
159
158
  queueMicrotask(() => {
160
- handleDataSourceChanges.call(this);
159
+ handleDataSourceChanges.call(this, true);
161
160
  resolve();
162
161
  });
163
162
  });
@@ -61,6 +61,7 @@ const originValuesSymbol = Symbol("originValues");
61
61
  const badgeElementSymbol = Symbol("badgeElement");
62
62
  const saveInFlightSymbol = Symbol("saveInFlight");
63
63
  const pendingResetSymbol = Symbol("pendingReset");
64
+ const fetchInFlightSymbol = Symbol("fetchInFlight");
64
65
 
65
66
  /**
66
67
  * A save button component
@@ -186,8 +187,21 @@ class SaveButton extends CustomElement {
186
187
  self[pendingResetSymbol] = true;
187
188
  return;
188
189
  }
190
+ self[fetchInFlightSymbol] = true;
189
191
  self[originValuesSymbol] = null;
190
192
  });
193
+ element.addEventListener("monster-datasource-fetched", () => {
194
+ self[fetchInFlightSymbol] = false;
195
+ if (!self[originValuesSymbol]) {
196
+ self[originValuesSymbol] = clone(
197
+ self[datasourceLinkedElementSymbol].data,
198
+ );
199
+ updateChangesState.call(self);
200
+ }
201
+ });
202
+ element.addEventListener("monster-datasource-error", () => {
203
+ self[fetchInFlightSymbol] = false;
204
+ });
191
205
  }
192
206
 
193
207
  this[datasourceLinkedElementSymbol] = element;
@@ -199,92 +213,16 @@ class SaveButton extends CustomElement {
199
213
 
200
214
  element.datasource.attachObserver(
201
215
  new Observer(function () {
216
+ if (self[fetchInFlightSymbol] === true) {
217
+ return;
218
+ }
202
219
  if (!self[originValuesSymbol]) {
203
220
  self[originValuesSymbol] = clone(
204
221
  self[datasourceLinkedElementSymbol].data,
205
222
  );
206
223
  }
207
224
 
208
- const currentValues = this.getRealSubject();
209
- const ignoreChanges = self.getOption("ignoreChanges");
210
-
211
- const result = diff(self[originValuesSymbol], currentValues);
212
- if (
213
- self.getOption("logLevel") === "debug" ||
214
- location.search.includes("logLevel=debug")
215
- ) {
216
- console.groupCollapsed("SaveButton");
217
- console.log(
218
- "originValues",
219
- JSON.parse(JSON.stringify(currentValues)),
220
- );
221
- console.log("result of diff", result);
222
- console.log("ignoreChanges", ignoreChanges);
223
-
224
- if (isArray(result) && result.length > 0) {
225
- const formattedDiff = result.map((change) => ({
226
- Operator: change?.operator,
227
- Path: change?.path?.join("."),
228
- "First Value": change?.first?.value,
229
- "First Type": change?.first?.type,
230
- "Second Value": change?.second?.value,
231
- "Second Type": change?.second?.type,
232
- }));
233
-
234
- console.table(formattedDiff);
235
- } else {
236
- console.log("There are no changes to save");
237
- }
238
- console.groupEnd();
239
- }
240
-
241
- if (isArray(ignoreChanges) && ignoreChanges.length > 0) {
242
- const itemsToRemove = [];
243
- for (const item of result) {
244
- for (const ignorePattern of ignoreChanges) {
245
- const p = new RegExp(ignorePattern);
246
-
247
- let matchPath = item.path;
248
- if (isArray(item.path)) {
249
- matchPath = item.path.join(".");
250
- }
251
-
252
- if (p.test(matchPath)) {
253
- itemsToRemove.push(item);
254
- break;
255
- }
256
- }
257
- }
258
-
259
- for (const itemToRemove of itemsToRemove) {
260
- const index = result.indexOf(itemToRemove);
261
- if (index > -1) {
262
- result.splice(index, 1);
263
- }
264
- }
265
- }
266
-
267
- if (isArray(result) && result.length > 0) {
268
- self[stateButtonElementSymbol].setState("changed");
269
- self[stateButtonElementSymbol].setOption("disabled", false);
270
- self.setOption("changes", result.length);
271
- self.setOption(
272
- "classes.badge",
273
- new TokenList(self.getOption("classes.badge"))
274
- .remove("hidden")
275
- .toString(),
276
- );
277
- } else {
278
- self[stateButtonElementSymbol].removeState();
279
- self[stateButtonElementSymbol].setOption("disabled", true);
280
- self.setOption("changes", 0);
281
- self.setOption(
282
- "classes.badge",
283
- new TokenList(self.getOption("classes.badge"))
284
- .add("hidden")
285
- .toString(),
286
- );
287
- }
225
+ updateChangesState.call(self);
288
226
  }),
289
227
  );
290
228
  }
@@ -440,6 +378,91 @@ function initEventHandler() {
440
378
  });
441
379
  }
442
380
 
381
+ /**
382
+ * @private
383
+ */
384
+ function updateChangesState() {
385
+ const currentValues = this[datasourceLinkedElementSymbol]?.datasource?.get();
386
+ const ignoreChanges = this.getOption("ignoreChanges");
387
+ let result = diff(this[originValuesSymbol], currentValues);
388
+
389
+ if (
390
+ this.getOption("logLevel") === "debug" ||
391
+ location.search.includes("logLevel=debug")
392
+ ) {
393
+ console.groupCollapsed("SaveButton");
394
+ console.log(
395
+ "originValues",
396
+ JSON.parse(JSON.stringify(this[originValuesSymbol])),
397
+ );
398
+ console.log("currentValues", JSON.parse(JSON.stringify(currentValues)));
399
+ console.log("result of diff", result);
400
+ console.log("ignoreChanges", ignoreChanges);
401
+
402
+ if (isArray(result) && result.length > 0) {
403
+ const formattedDiff = result.map((change) => ({
404
+ Operator: change?.operator,
405
+ Path: change?.path?.join("."),
406
+ "First Value": change?.first?.value,
407
+ "First Type": change?.first?.type,
408
+ "Second Value": change?.second?.value,
409
+ "Second Type": change?.second?.type,
410
+ }));
411
+
412
+ console.table(formattedDiff);
413
+ } else {
414
+ console.log("There are no changes to save");
415
+ }
416
+ console.groupEnd();
417
+ }
418
+
419
+ if (isArray(ignoreChanges) && ignoreChanges.length > 0) {
420
+ const itemsToRemove = [];
421
+ for (const item of result) {
422
+ for (const ignorePattern of ignoreChanges) {
423
+ const p = new RegExp(ignorePattern);
424
+
425
+ let matchPath = item.path;
426
+ if (isArray(item.path)) {
427
+ matchPath = item.path.join(".");
428
+ }
429
+
430
+ if (p.test(matchPath)) {
431
+ itemsToRemove.push(item);
432
+ break;
433
+ }
434
+ }
435
+ }
436
+
437
+ for (const itemToRemove of itemsToRemove) {
438
+ const index = result.indexOf(itemToRemove);
439
+ if (index > -1) {
440
+ result.splice(index, 1);
441
+ }
442
+ }
443
+ }
444
+
445
+ if (isArray(result) && result.length > 0) {
446
+ this[stateButtonElementSymbol].setState("changed");
447
+ this[stateButtonElementSymbol].setOption("disabled", false);
448
+ this.setOption("changes", result.length);
449
+ this.setOption(
450
+ "classes.badge",
451
+ new TokenList(this.getOption("classes.badge"))
452
+ .remove("hidden")
453
+ .toString(),
454
+ );
455
+ } else {
456
+ this[stateButtonElementSymbol].removeState();
457
+ this[stateButtonElementSymbol].setOption("disabled", true);
458
+ this.setOption("changes", 0);
459
+ this.setOption(
460
+ "classes.badge",
461
+ new TokenList(this.getOption("classes.badge")).add("hidden").toString(),
462
+ );
463
+ }
464
+ }
465
+
443
466
  /**
444
467
  * @param {Object} options
445
468
  * @deprecated 2024-12-31
@@ -16,6 +16,7 @@ import { diff } from "../../data/diff.mjs";
16
16
  import { Pathfinder } from "../../data/pathfinder.mjs";
17
17
  import { isObject, isPrimitive } from "../../types/is.mjs";
18
18
  import { Observer } from "../../types/observer.mjs";
19
+ import { clone } from "../../util/clone.mjs";
19
20
 
20
21
  export { handleDataSourceChanges, datasourceLinkedElementSymbol };
21
22
 
@@ -29,7 +30,7 @@ const dataChangeVersionSymbol = Symbol("dataChangeVersion");
29
30
  /**
30
31
  * @private
31
32
  */
32
- function handleDataSourceChanges() {
33
+ function handleDataSourceChanges(force = false) {
33
34
  if (!this[datasourceLinkedElementSymbol]) {
34
35
  return;
35
36
  }
@@ -69,15 +70,20 @@ function handleDataSourceChanges() {
69
70
  data = [];
70
71
  }
71
72
 
72
- const result = diff(data, actualDataAsObj);
73
- if (result.length === 0) {
74
- return;
75
- }
76
-
77
73
  queueMicrotask(() => {
78
74
  if (this[dataChangeVersionSymbol] !== version) {
79
75
  return;
80
76
  }
77
+ if (force) {
78
+ this.setOption("data", clone(data));
79
+ return;
80
+ }
81
+
82
+ const result = diff(data, actualDataAsObj);
83
+ if (result.length === 0) {
84
+ return;
85
+ }
86
+
81
87
  this.setOption("data", data);
82
88
  });
83
89
  }
@@ -252,6 +252,11 @@ const runLookupOnceSymbol = Symbol("runLookupOnce");
252
252
  * @type {symbol}
253
253
  */
254
254
  const cleanupOptionsListSymbol = Symbol("cleanupOptionsList");
255
+ const optionsVersionSymbol = Symbol("optionsVersion");
256
+ const pendingSelectionSymbol = Symbol("pendingSelection");
257
+ const selectionSyncScheduledSymbol = Symbol("selectionSyncScheduled");
258
+ const optionsSnapshotSymbol = Symbol("optionsSnapshot");
259
+ const selectionVersionSymbol = Symbol("selectionVersion");
255
260
 
256
261
  /**
257
262
  * @private
@@ -950,6 +955,77 @@ function processAndApplyPaginationData(data) {
950
955
  }
951
956
  }
952
957
 
958
+ /**
959
+ * @private
960
+ * @returns {number}
961
+ */
962
+ function bumpOptionsVersion() {
963
+ if (!isInteger(this[optionsVersionSymbol])) {
964
+ this[optionsVersionSymbol] = 0;
965
+ }
966
+ this[optionsVersionSymbol] += 1;
967
+ return this[optionsVersionSymbol];
968
+ }
969
+
970
+ /**
971
+ * @private
972
+ * @returns {*}
973
+ */
974
+ function getSelectionSyncState() {
975
+ const attrValue = this.getAttribute("value");
976
+ const selection = this.getOption("selection");
977
+ const options = this.getOption("options");
978
+ const optionsLength = Array.isArray(options) ? options.length : 0;
979
+ return { attrValue, selection, optionsLength };
980
+ }
981
+
982
+ /**
983
+ * @private
984
+ * @param {number} version
985
+ */
986
+ function scheduleSelectionSync(version) {
987
+ const state = getSelectionSyncState.call(this);
988
+ const selectionIsEmpty =
989
+ Array.isArray(state.selection) && state.selection.length === 0;
990
+
991
+ if (state.attrValue === null && selectionIsEmpty) {
992
+ return;
993
+ }
994
+
995
+ const pending = {
996
+ version,
997
+ selectionVersion: this[selectionVersionSymbol] || 0,
998
+ value: state.attrValue !== null ? state.attrValue : state.selection,
999
+ };
1000
+ this[pendingSelectionSymbol] = pending;
1001
+
1002
+ if (this[selectionSyncScheduledSymbol] === true) {
1003
+ return;
1004
+ }
1005
+ this[selectionSyncScheduledSymbol] = true;
1006
+
1007
+ queueMicrotask(() => {
1008
+ this[selectionSyncScheduledSymbol] = false;
1009
+ const current = this[pendingSelectionSymbol];
1010
+ if (!current) {
1011
+ return;
1012
+ }
1013
+ if (current.version !== this[optionsVersionSymbol]) {
1014
+ return;
1015
+ }
1016
+ if (current.selectionVersion !== (this[selectionVersionSymbol] || 0)) {
1017
+ return;
1018
+ }
1019
+
1020
+ setSelection
1021
+ .call(this, current.value)
1022
+ .then(() => {})
1023
+ .catch((e) => {
1024
+ addErrorAttribute(this, e);
1025
+ });
1026
+ });
1027
+ }
1028
+
953
1029
  /**
954
1030
  * @private
955
1031
  * @param data
@@ -1011,8 +1087,9 @@ function importOptionsIntern(data) {
1011
1087
  const map = buildMap(data, selector, labelTemplate, valueTemplate, filter);
1012
1088
 
1013
1089
  let options = [];
1090
+ const currentOptions = this.getOption("options");
1014
1091
  if (this[cleanupOptionsListSymbol] !== true) {
1015
- options = this.getOption("options", []);
1092
+ options = currentOptions ?? [];
1016
1093
  }
1017
1094
 
1018
1095
  this[cleanupOptionsListSymbol] = false;
@@ -1092,17 +1169,10 @@ function importOptionsIntern(data) {
1092
1169
  options,
1093
1170
  });
1094
1171
 
1095
- setTimeout(() => {
1096
- const attrValue = this.getAttribute("value");
1097
- const selection =
1098
- attrValue !== null ? attrValue : this.getOption("selection");
1099
- setSelection
1100
- .call(this, selection)
1101
- .then(() => {})
1102
- .catch((e) => {
1103
- addErrorAttribute(this, e);
1104
- });
1105
- }, 10);
1172
+ if (options === currentOptions) {
1173
+ const version = bumpOptionsVersion.call(this);
1174
+ scheduleSelectionSync.call(this, version);
1175
+ }
1106
1176
 
1107
1177
  return this;
1108
1178
  }
@@ -1675,24 +1745,13 @@ function fetchIt(url, controlOptions) {
1675
1745
 
1676
1746
  this[lastFetchedDataSymbol] = map;
1677
1747
 
1678
- let result;
1679
- const selection = this.getOption("selection");
1680
- let newValue = [];
1681
- if (selection) {
1682
- newValue = selection;
1683
- } else if (this.hasAttribute("value")) {
1684
- newValue = this.getAttribute("value");
1685
- }
1686
-
1687
- result = setSelection.call(this, newValue);
1688
-
1689
1748
  queueMicrotask(() => {
1690
1749
  checkOptionState.call(this);
1691
1750
  setTotalText.call(this);
1692
1751
  updatePopper.call(this);
1693
1752
  setStatusOrRemoveBadges.call(this, "closed");
1694
1753
 
1695
- resolve(result);
1754
+ resolve();
1696
1755
  });
1697
1756
 
1698
1757
  return;
@@ -1831,6 +1890,7 @@ function getSummaryTemplate() {
1831
1890
  */
1832
1891
  function parseSlotsToOptions() {
1833
1892
  let options = this.getOption("options");
1893
+ const currentOptions = options;
1834
1894
  if (!isIterable(options)) {
1835
1895
  options = [];
1836
1896
  }
@@ -1858,6 +1918,8 @@ function parseSlotsToOptions() {
1858
1918
  });
1859
1919
 
1860
1920
  this.setOption("options", options);
1921
+ const version = bumpOptionsVersion.call(this);
1922
+ scheduleSelectionSync.call(this, version);
1861
1923
  }
1862
1924
 
1863
1925
  /**
@@ -2044,6 +2106,13 @@ function initOptionObserver() {
2044
2106
 
2045
2107
  self.attachObserver(
2046
2108
  new Observer(function () {
2109
+ const options = self.getOption("options");
2110
+ if (options !== self[optionsSnapshotSymbol]) {
2111
+ self[optionsSnapshotSymbol] = options;
2112
+ const version = bumpOptionsVersion.call(self);
2113
+ scheduleSelectionSync.call(self, version);
2114
+ }
2115
+
2047
2116
  new Processing(() => {
2048
2117
  try {
2049
2118
  self.updateI18n();
@@ -2785,6 +2854,11 @@ function gatherState() {
2785
2854
  throw new Error("no shadow-root is defined");
2786
2855
  }
2787
2856
 
2857
+ const inputElements = this.shadowRoot.querySelectorAll(`input[type=${type}]`);
2858
+ if (inputElements.length === 0) {
2859
+ return this;
2860
+ }
2861
+
2788
2862
  let filteredSelection = [];
2789
2863
  if (type === "radio") {
2790
2864
  const selection = [];
@@ -2802,7 +2876,7 @@ function gatherState() {
2802
2876
  filteredSelection = selection;
2803
2877
  } else {
2804
2878
  const selection = [...this.getOption("selection", [])];
2805
- const allElements = this.shadowRoot.querySelectorAll(`input[type=${type}]`);
2879
+ const allElements = inputElements;
2806
2880
  const checkedElements = this.shadowRoot.querySelectorAll(
2807
2881
  `input[type=${type}]:checked`,
2808
2882
  );
@@ -3006,6 +3080,10 @@ function areOptionsAvailableAndInitInternal() {
3006
3080
 
3007
3081
  if (updated) {
3008
3082
  this.setOption("options", options);
3083
+ if (options === currentOptions) {
3084
+ const version = bumpOptionsVersion.call(this);
3085
+ scheduleSelectionSync.call(this, version);
3086
+ }
3009
3087
  }
3010
3088
 
3011
3089
  setStatusOrRemoveBadges.call(this);
@@ -3036,11 +3114,19 @@ function checkOptionState() {
3036
3114
  return a.value;
3037
3115
  });
3038
3116
 
3117
+ const CLASSNAME = "selected";
3039
3118
  for (const e of elements) {
3119
+ const parent = e.closest(`[${ATTRIBUTE_ROLE}=option]`);
3040
3120
  if (checkedValues.indexOf(e.value) !== -1) {
3041
3121
  if (e.checked !== true) e.checked = true;
3122
+ if (parent) {
3123
+ parent.classList.add(CLASSNAME);
3124
+ }
3042
3125
  } else {
3043
3126
  if (e.checked !== false) e.checked = false;
3127
+ if (parent) {
3128
+ parent.classList.remove(CLASSNAME);
3129
+ }
3044
3130
  }
3045
3131
  }
3046
3132
  }
@@ -3191,6 +3277,11 @@ function setSelection(selection) {
3191
3277
 
3192
3278
  selection = resultSelection;
3193
3279
 
3280
+ if (!isInteger(this[selectionVersionSymbol])) {
3281
+ this[selectionVersionSymbol] = 0;
3282
+ }
3283
+ this[selectionVersionSymbol] += 1;
3284
+
3194
3285
  this.setOption("selection", selection);
3195
3286
 
3196
3287
  checkOptionState.call(this);
@@ -916,14 +916,14 @@ function getTemplate() {
916
916
  // language=HTML
917
917
  return `
918
918
  <div data-monster-role="control" part="control">
919
- <div class="prev" data-monster-role="prev" part="prev">
919
+ <div class="prev hidden" data-monster-role="prev" part="prev">
920
920
  <slot name="prev"></slot>
921
921
  </div>
922
922
  <div data-monster-role="slider" part="slides">
923
923
  <slot></slot>
924
924
  </div>
925
- <div data-monster-role="thumbnails" part="thumbnails"></div>
926
- <div class="next" data-monster-role="next" part="next">
925
+ <div class="hidden" data-monster-role="thumbnails" part="thumbnails"></div>
926
+ <div class="next hidden" data-monster-role="next" part="next">
927
927
  <slot name="next"></slot>
928
928
  </div>
929
929
  </div>`;
@@ -98,6 +98,12 @@ const attributeObserverSymbol = Symbol.for(
98
98
  const attributeMutationObserverSymbol = Symbol(
99
99
  "@schukai/monster/dom/@@mutationObserver",
100
100
  );
101
+ const childMutationObserverSymbol = Symbol(
102
+ "@schukai/monster/dom/@@childMutationObserver",
103
+ );
104
+ const childMutationTimerSymbol = Symbol(
105
+ "@schukai/monster/dom/@@childMutationTimer",
106
+ );
101
107
 
102
108
  /**
103
109
  * @private
@@ -221,6 +227,9 @@ class CustomElement extends HTMLElement {
221
227
  this[initMethodSymbol]();
222
228
  initOptionObserver.call(this);
223
229
  this[scriptHostElementSymbol] = [];
230
+ // Catch attribute changes made before connectedCallback runs.
231
+ attachAttributeChangeMutationObserver.call(this);
232
+ attachChildMutationObserver.call(this);
224
233
  }
225
234
 
226
235
  /**
@@ -679,6 +688,14 @@ class CustomElement extends HTMLElement {
679
688
  connectedCallback() {
680
689
  // Check if the object has already been initialized
681
690
  if (!hasObjectLink(this, customElementUpdaterLinkSymbol)) {
691
+ try {
692
+ initOptionsFromAttributes(
693
+ this,
694
+ this[internalSymbol].getSubject()?.["options"],
695
+ );
696
+ } catch (e) {
697
+ addErrorAttribute(this, e);
698
+ }
682
699
  // If not, call the assembleMethod to initialize the object
683
700
  this[assembleMethodSymbol]();
684
701
  }
@@ -874,6 +891,65 @@ function attachAttributeChangeMutationObserver() {
874
891
  }
875
892
  }
876
893
 
894
+ /**
895
+ * @private
896
+ * @this CustomElement
897
+ */
898
+ function attachChildMutationObserver() {
899
+ const self = this;
900
+
901
+ if (self[childMutationObserverSymbol]) {
902
+ return;
903
+ }
904
+
905
+ if (self.getOption("features.mutationObserver") !== true) {
906
+ return;
907
+ }
908
+
909
+ self[childMutationObserverSymbol] = new MutationObserver((mutations) => {
910
+ let hasAdditions = false;
911
+ for (const mutation of mutations) {
912
+ if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
913
+ hasAdditions = true;
914
+ break;
915
+ }
916
+ }
917
+
918
+ if (!hasAdditions) {
919
+ return;
920
+ }
921
+
922
+ if (self[childMutationTimerSymbol]) {
923
+ return;
924
+ }
925
+
926
+ self[childMutationTimerSymbol] = setTimeout(() => {
927
+ self[childMutationTimerSymbol] = null;
928
+ if (!hasObjectLink(self, customElementUpdaterLinkSymbol)) {
929
+ return;
930
+ }
931
+
932
+ const updaters = getLinkedObjects(self, customElementUpdaterLinkSymbol);
933
+ for (const list of updaters) {
934
+ for (const updater of list) {
935
+ updater.run().catch((e) => {
936
+ addErrorAttribute(self, e);
937
+ });
938
+ }
939
+ }
940
+ }, 50);
941
+ });
942
+
943
+ try {
944
+ self[childMutationObserverSymbol].observe(self, {
945
+ childList: true,
946
+ subtree: true,
947
+ });
948
+ } catch (e) {
949
+ addErrorAttribute(self, e);
950
+ }
951
+ }
952
+
877
953
  /**
878
954
  * @this CustomElement
879
955
  * @private
@@ -13,7 +13,15 @@
13
13
  */
14
14
 
15
15
  import { Pathfinder } from "../../data/pathfinder.mjs";
16
- import { isFunction } from "../../types/is.mjs";
16
+ import {
17
+ isFunction,
18
+ isBoolean,
19
+ isString,
20
+ isObject,
21
+ isNumber,
22
+ isArray,
23
+ isInteger,
24
+ } from "../../types/is.mjs";
17
25
  import { attributeObserverSymbol } from "../customelement.mjs";
18
26
  import { extractKeys } from "./extract-keys.mjs";
19
27
 
@@ -77,15 +85,27 @@ function setOptionFromAttribute(
77
85
  value = mapping[optionName](value);
78
86
  }
79
87
 
80
- const typeOfOptionValue = typeof finder.getVia(optionName);
81
- if (typeOfOptionValue === "boolean") {
88
+ let optionValue = finder.getVia(optionName);
89
+ if (optionValue === null || optionValue === undefined) {
90
+ optionValue = value;
91
+ }
92
+
93
+ if (optionValue === null || optionValue === undefined) {
94
+ value = null;
95
+ } else if (isBoolean(optionValue)) {
82
96
  value = value === "true";
83
- } else if (typeOfOptionValue === "number") {
97
+ } else if (isInteger(optionValue)) {
98
+ value = Number(value);
99
+ } else if (isNumber(optionValue)) {
84
100
  value = Number(value);
85
- } else if (typeOfOptionValue === "string") {
101
+ } else if (isString(optionValue)) {
86
102
  value = String(value);
87
- } else if (typeOfOptionValue === "object") {
103
+ } else if (isObject(optionValue)) {
88
104
  value = JSON.parse(value);
105
+ } else if (isArray(optionValue)) {
106
+ value = value.split("::");
107
+ } else {
108
+ value = optionValue;
89
109
  }
90
110
 
91
111
  finder.setVia(optionName, value);