@schukai/monster 4.128.2 → 4.129.0

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.
Files changed (43) hide show
  1. package/package.json +1 -1
  2. package/source/components/content/stylesheet/camera-capture.mjs +1 -1
  3. package/source/components/content/stylesheet/copy.mjs +1 -1
  4. package/source/components/content/viewer/stylesheet/message.mjs +1 -1
  5. package/source/components/datatable/columnbar.mjs +30 -3
  6. package/source/components/datatable/datatable.mjs +29 -1
  7. package/source/components/datatable/pagination.mjs +39 -1
  8. package/source/components/datatable/stylesheet/filter-controls-defaults.mjs +1 -1
  9. package/source/components/form/login.mjs +197 -0
  10. package/source/components/form/select.mjs +4 -4
  11. package/source/components/form/stylesheet/button-bar.mjs +1 -1
  12. package/source/components/form/stylesheet/confirm-button.mjs +1 -1
  13. package/source/components/form/stylesheet/context-error.mjs +1 -1
  14. package/source/components/form/stylesheet/context-help.mjs +1 -1
  15. package/source/components/form/stylesheet/digits.mjs +1 -1
  16. package/source/components/form/stylesheet/field-set.mjs +1 -1
  17. package/source/components/form/stylesheet/login.mjs +1 -1
  18. package/source/components/form/stylesheet/popper-button.mjs +1 -1
  19. package/source/components/form/stylesheet/select.mjs +1 -1
  20. package/source/components/form/tree-select.mjs +2 -2
  21. package/source/components/layout/stylesheet/popper.mjs +1 -1
  22. package/source/components/style/floating-ui.css +7 -0
  23. package/source/dom/customcontrol.mjs +1 -1
  24. package/source/dom/customelement.mjs +218 -16
  25. package/source/dom/updater.mjs +32 -12
  26. package/source/types/is.mjs +3 -0
  27. package/source/types/proxyobserver.mjs +10 -13
  28. package/source/types/version.mjs +1 -1
  29. package/test/cases/components/content/image-editor.mjs +98 -0
  30. package/test/cases/components/datatable/drag-scroll.mjs +218 -14
  31. package/test/cases/components/form/login.mjs +168 -0
  32. package/test/cases/components/form/select.mjs +94 -32
  33. package/test/cases/components/form/tree-select.mjs +22 -1
  34. package/test/cases/dom/customcontrol.mjs +44 -10
  35. package/test/cases/dom/customelement-initfromscripthost.mjs +22 -1
  36. package/test/cases/dom/customelement.mjs +83 -0
  37. package/test/cases/dom/updater.mjs +98 -0
  38. package/test/cases/monster.mjs +1 -1
  39. package/test/cases/types/proxyobserver.mjs +31 -0
  40. package/test/web/import.js +4 -0
  41. package/test/web/puppeteer.mjs +94 -71
  42. package/test/web/test.html +29 -2
  43. package/test/web/tests.js +28297 -15864
@@ -117,6 +117,9 @@ const updateCloneDataSymbol = Symbol("@schukai/monster/dom/@@updateCloneData");
117
117
  * @type {symbol}
118
118
  */
119
119
  const scriptHostElementSymbol = Symbol("scriptHostElement");
120
+ const managedShadowRootSymbol = Symbol("managedShadowRoot");
121
+ const visibilityStateSymbol = Symbol("visibilityState");
122
+ let hostVisibilityStyleSheet = null;
120
123
 
121
124
  /**
122
125
  * The `CustomElement` class provides a way to define a new HTML element using the power of Custom Elements.
@@ -277,6 +280,24 @@ class CustomElement extends HTMLElement {
277
280
  return this;
278
281
  }
279
282
 
283
+ /**
284
+ * Returns whether the host element is currently visible.
285
+ *
286
+ * @returns {boolean}
287
+ */
288
+ get visible() {
289
+ return !this.hidden;
290
+ }
291
+
292
+ /**
293
+ * Alias for the current host visibility state.
294
+ *
295
+ * @returns {boolean}
296
+ */
297
+ get isVisible() {
298
+ return this.visible;
299
+ }
300
+
280
301
  /**
281
302
  * The `customization` property allows overwriting the defaults.
282
303
  * Unlike the defaults that expect an object, the customization is a Map.
@@ -540,6 +561,54 @@ class CustomElement extends HTMLElement {
540
561
  return this;
541
562
  }
542
563
 
564
+ /**
565
+ * Shows the host element.
566
+ *
567
+ * @returns {CustomElement}
568
+ */
569
+ show() {
570
+ return this.setVisible(true);
571
+ }
572
+
573
+ /**
574
+ * Hides the host element.
575
+ *
576
+ * @returns {CustomElement}
577
+ */
578
+ hide() {
579
+ return this.setVisible(false);
580
+ }
581
+
582
+ /**
583
+ * Sets the host visibility.
584
+ *
585
+ * @param {boolean} visible
586
+ * @returns {CustomElement}
587
+ */
588
+ setVisible(visible) {
589
+ if (visible) {
590
+ this.removeAttribute("hidden");
591
+ } else {
592
+ this.setAttribute("hidden", "");
593
+ }
594
+
595
+ return this;
596
+ }
597
+
598
+ /**
599
+ * Toggles the host visibility.
600
+ *
601
+ * @param {boolean} [force]
602
+ * @returns {CustomElement}
603
+ */
604
+ toggleVisibility(force) {
605
+ if (typeof force === "boolean") {
606
+ return this.setVisible(force);
607
+ }
608
+
609
+ return this.setVisible(!this.visible);
610
+ }
611
+
543
612
  /**
544
613
  * Is called once via the constructor
545
614
  *
@@ -623,7 +692,7 @@ class CustomElement extends HTMLElement {
623
692
  if (this.getOption("shadowMode", false) !== false) {
624
693
  try {
625
694
  initShadowRoot.call(this);
626
- elements = this.shadowRoot.childNodes;
695
+ elements = getManagedShadowRoot.call(this)?.childNodes;
627
696
  } catch (e) {
628
697
  addErrorAttribute(this, e);
629
698
  }
@@ -673,6 +742,8 @@ class CustomElement extends HTMLElement {
673
742
  this[internalSymbol].syncDisabledState();
674
743
  }
675
744
 
745
+ syncVisibilityState.call(this);
746
+
676
747
  return this;
677
748
  }
678
749
 
@@ -746,6 +817,10 @@ class CustomElement extends HTMLElement {
746
817
  );
747
818
  }
748
819
 
820
+ if (attrName === "hidden") {
821
+ syncVisibilityState.call(this);
822
+ }
823
+
749
824
  const callback = this[attributeObserverSymbol]?.[attrName];
750
825
  if (isFunction(callback)) {
751
826
  try {
@@ -770,11 +845,12 @@ class CustomElement extends HTMLElement {
770
845
  return true;
771
846
  }
772
847
 
773
- if (!(this.shadowRoot instanceof ShadowRoot)) {
848
+ const shadowRoot = getManagedShadowRoot.call(this);
849
+ if (!(shadowRoot instanceof ShadowRoot)) {
774
850
  return false;
775
851
  }
776
852
 
777
- return containChildNode.call(this.shadowRoot, node);
853
+ return containChildNode.call(shadowRoot, node);
778
854
  }
779
855
 
780
856
  /**
@@ -815,7 +891,7 @@ function callControlCallback(callBackFunctionName, ...args) {
815
891
 
816
892
  const list = targetId.split(",");
817
893
  for (const id of list) {
818
- const host = findElementWithIdUpwards(this, targetId);
894
+ const host = findElementWithIdUpwards(this, id.trim());
819
895
  if (!(host instanceof HTMLElement)) {
820
896
  continue;
821
897
  }
@@ -878,8 +954,14 @@ function attachAttributeChangeMutationObserver() {
878
954
 
879
955
  self[attributeMutationObserverSymbol] = new MutationObserver(
880
956
  function (mutations, observer) {
957
+ const observedAttributes = new Set(
958
+ self.constructor.observedAttributes || [],
959
+ );
881
960
  for (const mutation of mutations) {
882
961
  if (mutation.type === "attributes") {
962
+ if (observedAttributes.has(mutation.attributeName)) {
963
+ continue;
964
+ }
883
965
  self.attributeChangedCallback(
884
966
  mutation.attributeName,
885
967
  mutation.oldValue,
@@ -1014,7 +1096,8 @@ function initOptionObserver() {
1014
1096
  return;
1015
1097
  }
1016
1098
 
1017
- if (!(self.shadowRoot instanceof ShadowRoot) && !self.childNodes.length) {
1099
+ const shadowRoot = getManagedShadowRoot.call(self);
1100
+ if (!(shadowRoot instanceof ShadowRoot) && !self.childNodes.length) {
1018
1101
  return;
1019
1102
  }
1020
1103
 
@@ -1024,8 +1107,8 @@ function initOptionObserver() {
1024
1107
  "button, command, fieldset, keygen, optgroup, option, select, textarea, input, [data-monster-objectlink]";
1025
1108
 
1026
1109
  let elements = [];
1027
- if (self.shadowRoot instanceof ShadowRoot) {
1028
- elements = self.shadowRoot.querySelectorAll(query);
1110
+ if (shadowRoot instanceof ShadowRoot) {
1111
+ elements = shadowRoot.querySelectorAll(query);
1029
1112
  }
1030
1113
 
1031
1114
  let nodeList;
@@ -1235,6 +1318,16 @@ function parseOptionsJSON(data) {
1235
1318
  return validateObject(obj);
1236
1319
  }
1237
1320
 
1321
+ function getManagedShadowRoot() {
1322
+ if (this[managedShadowRootSymbol] instanceof ShadowRoot) {
1323
+ return this[managedShadowRootSymbol];
1324
+ }
1325
+ if (this.shadowRoot instanceof ShadowRoot) {
1326
+ return this.shadowRoot;
1327
+ }
1328
+ return null;
1329
+ }
1330
+
1238
1331
  /**
1239
1332
  * @private
1240
1333
  * @param html
@@ -1277,6 +1370,73 @@ function initHtmlContent() {
1277
1370
  return this;
1278
1371
  }
1279
1372
 
1373
+ /**
1374
+ * @private
1375
+ * @returns {void}
1376
+ */
1377
+ function syncVisibilityState() {
1378
+ const visible = !this.hidden;
1379
+ if (typeof this[visibilityStateSymbol] === "undefined") {
1380
+ this[visibilityStateSymbol] = visible;
1381
+ if (!visible) {
1382
+ blurFocusedElement.call(this);
1383
+ }
1384
+ return;
1385
+ }
1386
+
1387
+ if (this[visibilityStateSymbol] === visible) {
1388
+ return;
1389
+ }
1390
+
1391
+ if (!visible) {
1392
+ blurFocusedElement.call(this);
1393
+ }
1394
+
1395
+ this[visibilityStateSymbol] = visible;
1396
+ dispatchVisibilityChangedEvent.call(this, visible);
1397
+ }
1398
+
1399
+ /**
1400
+ * @private
1401
+ * @returns {void}
1402
+ */
1403
+ function blurFocusedElement() {
1404
+ const shadowRoot = getManagedShadowRoot.call(this);
1405
+ const activeShadowElement = shadowRoot?.activeElement;
1406
+
1407
+ if (activeShadowElement instanceof HTMLElement) {
1408
+ activeShadowElement.blur();
1409
+ }
1410
+
1411
+ if (document.activeElement === this && typeof this.blur === "function") {
1412
+ this.blur();
1413
+ }
1414
+
1415
+ if (shadowRoot?.activeElement instanceof HTMLElement) {
1416
+ const body = getDocument()?.body;
1417
+ if (body instanceof HTMLElement) {
1418
+ body.setAttribute("tabindex", "-1");
1419
+ body.focus();
1420
+ body.removeAttribute("tabindex");
1421
+ }
1422
+ }
1423
+ }
1424
+
1425
+ /**
1426
+ * @private
1427
+ * @param {boolean} visible
1428
+ * @returns {void}
1429
+ */
1430
+ function dispatchVisibilityChangedEvent(visible) {
1431
+ this.dispatchEvent(
1432
+ new CustomEvent("monster-visibility-changed", {
1433
+ bubbles: true,
1434
+ composed: true,
1435
+ detail: { visible },
1436
+ }),
1437
+ );
1438
+ }
1439
+
1280
1440
  /**
1281
1441
  * @private
1282
1442
  * @return {CustomElement}
@@ -1286,25 +1446,29 @@ function initHtmlContent() {
1286
1446
  * @throws {TypeError} value is not an instance of
1287
1447
  */
1288
1448
  function initCSSStylesheet() {
1289
- if (!(this.shadowRoot instanceof ShadowRoot)) {
1449
+ const shadowRoot = getManagedShadowRoot.call(this);
1450
+ if (!(shadowRoot instanceof ShadowRoot)) {
1290
1451
  return this;
1291
1452
  }
1453
+ const visibilityStyleSheet = getHostVisibilityStyleSheet();
1292
1454
 
1293
1455
  const styleSheet = this.constructor.getCSSStyleSheet();
1294
1456
 
1295
1457
  if (styleSheet instanceof CSSStyleSheet) {
1296
1458
  if (styleSheet.cssRules.length > 0) {
1297
- this.shadowRoot.adoptedStyleSheets = [styleSheet];
1459
+ shadowRoot.adoptedStyleSheets = [visibilityStyleSheet, styleSheet];
1460
+ } else {
1461
+ shadowRoot.adoptedStyleSheets = [visibilityStyleSheet];
1298
1462
  }
1299
1463
  } else if (isArray(styleSheet)) {
1300
- const assign = [];
1464
+ const assign = [visibilityStyleSheet];
1301
1465
  for (const s of styleSheet) {
1302
1466
  if (isString(s)) {
1303
1467
  const trimedStyleSheet = s.trim();
1304
1468
  if (trimedStyleSheet !== "") {
1305
1469
  const style = document.createElement("style");
1306
1470
  style.innerHTML = trimedStyleSheet;
1307
- this.shadowRoot.prepend(style);
1471
+ shadowRoot.prepend(style);
1308
1472
  }
1309
1473
  continue;
1310
1474
  }
@@ -1317,20 +1481,56 @@ function initCSSStylesheet() {
1317
1481
  }
1318
1482
 
1319
1483
  if (assign.length > 0) {
1320
- this.shadowRoot.adoptedStyleSheets = assign;
1484
+ shadowRoot.adoptedStyleSheets = assign;
1321
1485
  }
1322
1486
  } else if (isString(styleSheet)) {
1487
+ shadowRoot.adoptedStyleSheets = [visibilityStyleSheet];
1323
1488
  const trimedStyleSheet = styleSheet.trim();
1324
1489
  if (trimedStyleSheet !== "") {
1325
1490
  const style = document.createElement("style");
1326
1491
  style.innerHTML = styleSheet;
1327
- this.shadowRoot.prepend(style);
1492
+ shadowRoot.prepend(style);
1328
1493
  }
1494
+ } else {
1495
+ shadowRoot.adoptedStyleSheets = [visibilityStyleSheet];
1329
1496
  }
1330
1497
 
1331
1498
  return this;
1332
1499
  }
1333
1500
 
1501
+ /**
1502
+ * @private
1503
+ * @returns {CSSStyleSheet}
1504
+ */
1505
+ function getHostVisibilityStyleSheet() {
1506
+ if (hostVisibilityStyleSheet instanceof CSSStyleSheet) {
1507
+ return hostVisibilityStyleSheet;
1508
+ }
1509
+
1510
+ hostVisibilityStyleSheet = new CSSStyleSheet();
1511
+ hostVisibilityStyleSheet.replaceSync(
1512
+ ":host([hidden]){display:none !important}",
1513
+ );
1514
+
1515
+ return hostVisibilityStyleSheet;
1516
+ }
1517
+
1518
+ /**
1519
+ * @private
1520
+ * @param {ShadowRoot} shadowRoot
1521
+ * @returns {void}
1522
+ */
1523
+ function appendVisibilityHostStyle(shadowRoot) {
1524
+ if (!(shadowRoot instanceof ShadowRoot)) {
1525
+ return;
1526
+ }
1527
+
1528
+ const style = document.createElement("style");
1529
+ style.setAttribute("data-monster-host-visibility", "true");
1530
+ style.textContent = ":host([hidden]){display:none !important}";
1531
+ shadowRoot.prepend(style);
1532
+ }
1533
+
1334
1534
  /**
1335
1535
  * @private
1336
1536
  * @return {CustomElement}
@@ -1352,13 +1552,15 @@ function initShadowRoot() {
1352
1552
  }
1353
1553
  }
1354
1554
 
1355
- this.attachShadow({
1555
+ this[managedShadowRootSymbol] = this.attachShadow({
1356
1556
  mode: this.getOption("shadowMode", "open"),
1357
1557
  delegatesFocus: this.getOption("delegatesFocus", true),
1358
1558
  });
1359
1559
 
1360
1560
  if (template instanceof Template) {
1361
- this.shadowRoot.appendChild(template.createDocumentFragment());
1561
+ this[managedShadowRootSymbol].appendChild(
1562
+ template.createDocumentFragment(),
1563
+ );
1362
1564
  return this;
1363
1565
  }
1364
1566
 
@@ -1374,7 +1576,7 @@ function initShadowRoot() {
1374
1576
  html = formatter.format(html);
1375
1577
  }
1376
1578
 
1377
- this.shadowRoot.innerHTML = substituteI18n.call(this, html);
1579
+ this[managedShadowRootSymbol].innerHTML = substituteI18n.call(this, html);
1378
1580
  return this;
1379
1581
  }
1380
1582
 
@@ -88,6 +88,7 @@ const updaterRootSymbol = Symbol.for("@schukai/monster/dom/@@updater-root");
88
88
  const disposedSymbol = Symbol("disposed");
89
89
  const subjectObserverSymbol = Symbol("subjectObserver");
90
90
  const patchNodeKeySymbol = Symbol("patchNodeKey");
91
+ const queuedSnapshotSymbol = Symbol("queuedSnapshot");
91
92
 
92
93
  /**
93
94
  * The updater class connects an object with the DOM. In this way, structures and contents in the DOM can be
@@ -155,6 +156,7 @@ class Updater extends Base {
155
156
  this[pendingDiffsSymbol] = [];
156
157
  this[processingSymbol] = false;
157
158
  this[disposedSymbol] = false;
159
+ this[queuedSnapshotSymbol] = clone(this[internalSymbol].last);
158
160
 
159
161
  this[subjectObserverSymbol] = new Observer(() => {
160
162
  if (this[disposedSymbol] === true) {
@@ -162,12 +164,13 @@ class Updater extends Base {
162
164
  }
163
165
 
164
166
  const real = this[internalSymbol].subject.getRealSubject();
165
- const diffResult = diff(this[internalSymbol].last, real);
166
- this[internalSymbol].last = clone(real);
167
+ const diffResult = diff(this[queuedSnapshotSymbol], real);
167
168
  if (diffResult.length === 0) {
168
169
  return Promise.resolve();
169
170
  }
170
- this[pendingDiffsSymbol].push(diffResult);
171
+ const snapshot = clone(real);
172
+ this[queuedSnapshotSymbol] = snapshot;
173
+ this[pendingDiffsSymbol].push({ diffResult, snapshot });
171
174
  return this[processQueueSymbol]();
172
175
  });
173
176
  this[internalSymbol].subject.attachObserver(this[subjectObserverSymbol]);
@@ -194,7 +197,7 @@ class Updater extends Base {
194
197
  return Promise.resolve();
195
198
  }
196
199
 
197
- const diffResult = this[pendingDiffsSymbol].shift();
200
+ const { diffResult, snapshot } = this[pendingDiffsSymbol].shift();
198
201
  if (this[internalSymbol].features.batchUpdates === true) {
199
202
  const updatePaths = new Map();
200
203
  for (const change of Object.values(diffResult)) {
@@ -225,6 +228,7 @@ class Updater extends Base {
225
228
  await this[applyChangeSymbol](change);
226
229
  }
227
230
  }
231
+ this[internalSymbol].last = clone(snapshot);
228
232
  }
229
233
  } catch (err) {
230
234
  addErrorAttribute(this[internalSymbol]?.element, err);
@@ -371,6 +375,7 @@ class Updater extends Base {
371
375
  // the key __init__has no further meaning and is only
372
376
  // used to create the diff for empty objects.
373
377
  this[internalSymbol].last = { __init__: true };
378
+ this[queuedSnapshotSymbol] = clone(this[internalSymbol].last);
374
379
  return this[internalSymbol].subject.notifyObservers();
375
380
  }
376
381
 
@@ -597,12 +602,17 @@ function retrieveAndSetValue(element) {
597
602
  switch (type) {
598
603
  case "integer?":
599
604
  case "int?":
600
- case "number?":
605
+ case "number?": {
606
+ const empty =
607
+ value === undefined ||
608
+ value === null ||
609
+ (isString(value) && value.trim() === "");
601
610
  value = Number(value);
602
- if (isNaN(value) || 0 === value) {
611
+ if (empty || isNaN(value)) {
603
612
  value = undefined;
604
613
  }
605
614
  break;
615
+ }
606
616
 
607
617
  case "number":
608
618
  case "int":
@@ -1055,7 +1065,7 @@ function runUpdateContent(container, parts, subject) {
1055
1065
  * @type {HTMLElement}
1056
1066
  */
1057
1067
  for (const [element] of iterator.entries()) {
1058
- if (mem.has(element)) return;
1068
+ if (mem.has(element)) continue;
1059
1069
  mem.add(element);
1060
1070
 
1061
1071
  const attributes = element.getAttribute(ATTRIBUTE_UPDATER_REPLACE);
@@ -1113,7 +1123,7 @@ function runUpdatePatch(container, parts, subject) {
1113
1123
  }
1114
1124
 
1115
1125
  for (const [element] of iterator.entries()) {
1116
- if (mem.has(element)) return;
1126
+ if (mem.has(element)) continue;
1117
1127
  mem.add(element);
1118
1128
 
1119
1129
  const attributes = element.getAttribute(ATTRIBUTE_UPDATER_PATCH);
@@ -1501,7 +1511,7 @@ function runUpdateAttributes(container, parts, subject) {
1501
1511
  }
1502
1512
 
1503
1513
  for (const [element] of iterator.entries()) {
1504
- if (mem.has(element)) return;
1514
+ if (mem.has(element)) continue;
1505
1515
  mem.add(element);
1506
1516
 
1507
1517
  // this case occurs when the ATTRIBUTE_UPDATER_SELECT_THIS attribute is set
@@ -1576,7 +1586,7 @@ function runUpdateProperties(container, parts, subject) {
1576
1586
  }
1577
1587
 
1578
1588
  for (const [element] of iterator.entries()) {
1579
- if (mem.has(element)) return;
1589
+ if (mem.has(element)) continue;
1580
1590
  mem.add(element);
1581
1591
 
1582
1592
  // this case occurs when the ATTRIBUTE_UPDATER_SELECT_THIS attribute is set
@@ -1625,14 +1635,24 @@ function handleInputControlAttributeUpdate(element, name, value) {
1625
1635
  if (element instanceof HTMLSelectElement) {
1626
1636
  switch (element.type) {
1627
1637
  case "select-multiple":
1628
- for (const [index, opt] of Object.entries(element.options)) {
1629
- opt.selected = value.indexOf(opt.value) !== -1;
1638
+ const selectedValues = isArray(value)
1639
+ ? value
1640
+ : value === undefined || value === null
1641
+ ? []
1642
+ : [`${value}`];
1643
+ for (const [, opt] of Object.entries(element.options)) {
1644
+ opt.selected = selectedValues.indexOf(opt.value) !== -1;
1630
1645
  }
1631
1646
 
1632
1647
  break;
1633
1648
  case "select-one":
1634
1649
  // Only one value may be selected
1650
+ if (value === undefined || value === null) {
1651
+ element.selectedIndex = -1;
1652
+ break;
1653
+ }
1635
1654
 
1655
+ element.selectedIndex = -1;
1636
1656
  for (const [index, opt] of Object.entries(element.options)) {
1637
1657
  if (opt.value === value) {
1638
1658
  element.selectedIndex = index;
@@ -36,6 +36,9 @@ export {
36
36
  * @returns {boolean}
37
37
  */
38
38
  function isElement(value) {
39
+ if (typeof Element === "undefined") {
40
+ return false;
41
+ }
39
42
  return value instanceof Element;
40
43
  }
41
44
 
@@ -209,22 +209,19 @@ function getHandler() {
209
209
  return true;
210
210
  }
211
211
 
212
- let result;
213
- let descriptor = Reflect.getOwnPropertyDescriptor(target, key);
214
-
215
- if (descriptor === undefined) {
216
- descriptor = {
217
- writable: true,
218
- enumerable: true,
219
- configurable: true,
220
- };
212
+ const result = Reflect.set(target, key, value, receiver);
213
+ if (result !== true) {
214
+ return result;
221
215
  }
222
216
 
223
- descriptor["value"] = value;
224
- result = Reflect.defineProperty(target, key, descriptor);
225
-
226
217
  if (typeof key !== "symbol") {
227
- proxy.observers.notify(proxy);
218
+ let next = Reflect.get(target, key, receiver);
219
+ if (proxy.proxyMap.has(next)) {
220
+ next = proxy.proxyMap.get(next);
221
+ }
222
+ if (next !== current) {
223
+ proxy.observers.notify(proxy);
224
+ }
228
225
  }
229
226
 
230
227
  return result;
@@ -156,7 +156,7 @@ function getMonsterVersion() {
156
156
  }
157
157
 
158
158
  /** don't touch, replaced by make with package.json version */
159
- monsterVersion = new Version("4.125.0");
159
+ monsterVersion = new Version("4.128.3");
160
160
 
161
161
  return monsterVersion;
162
162
  }
@@ -0,0 +1,98 @@
1
+ 'use strict';
2
+
3
+ import * as chai from 'chai';
4
+ import {getDocument} from "../../../../source/dom/util.mjs";
5
+ import {chaiDom} from "../../../util/chai-dom.mjs";
6
+ import {initJSDOM} from "../../../util/jsdom.mjs";
7
+
8
+ let expect = chai.expect;
9
+ chai.use(chaiDom);
10
+
11
+ describe('ImageEditor', function () {
12
+
13
+ let ImageEditor, document;
14
+ let registerCustomElement;
15
+ let CustomElement;
16
+ let assembleMethodSymbol;
17
+
18
+ before(function (done) {
19
+ initJSDOM({}).then(() => {
20
+ import("../../../../source/dom/customelement.mjs").then((domModule) => {
21
+ registerCustomElement = domModule['registerCustomElement'];
22
+ CustomElement = domModule['CustomElement'];
23
+ assembleMethodSymbol = domModule['assembleMethodSymbol'];
24
+ return import("../../../../source/components/content/image-editor.mjs");
25
+ }).then((m) => {
26
+ try {
27
+ ImageEditor = m['ImageEditor'];
28
+ document = getDocument();
29
+ done();
30
+ } catch (e) {
31
+ done(e);
32
+ }
33
+ }).catch((e) => done(e));
34
+ });
35
+ });
36
+
37
+ beforeEach(() => {
38
+ let mocks = document.getElementById('mocks');
39
+ mocks.innerHTML = '<div id="target"></div>';
40
+ });
41
+
42
+ afterEach(() => {
43
+ let mocks = document.getElementById('mocks');
44
+ mocks.innerHTML = '';
45
+ });
46
+
47
+ it('should not double-call attributeChangedCallback for readonly', function (done) {
48
+ const htmlTAG = 'monster-image-editor-observed-test';
49
+
50
+ class ObservedImageEditor extends ImageEditor {
51
+ static getTag() {
52
+ return htmlTAG;
53
+ }
54
+
55
+ get defaults() {
56
+ return Object.assign({}, super.defaults, {
57
+ shadowMode: false,
58
+ templates: {
59
+ main: '<div id="image-editor-test"></div>'
60
+ }
61
+ });
62
+ }
63
+
64
+ [assembleMethodSymbol]() {
65
+ return CustomElement.prototype[assembleMethodSymbol].call(this);
66
+ }
67
+
68
+ constructor() {
69
+ super();
70
+ this.attributeChangedCalls = [];
71
+ }
72
+
73
+ attributeChangedCallback(name, oldValue, newValue) {
74
+ this.attributeChangedCalls.push([name, oldValue, newValue]);
75
+ }
76
+ }
77
+
78
+ registerCustomElement(ObservedImageEditor);
79
+
80
+ let element = document.createElement(htmlTAG);
81
+ document.getElementById('target').appendChild(element);
82
+
83
+ element.attributeChangedCalls = [];
84
+ element.setAttribute('data-monster-readonly', '');
85
+
86
+ setTimeout(function () {
87
+ try {
88
+ const readonlyCalls = element.attributeChangedCalls.filter(
89
+ ([name]) => name === 'data-monster-readonly',
90
+ );
91
+ expect(readonlyCalls).to.have.length(1);
92
+ done();
93
+ } catch (e) {
94
+ done(e);
95
+ }
96
+ }, 20);
97
+ });
98
+ });