@schukai/monster 4.128.1 → 4.128.3

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 (39) 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 +26 -1
  7. package/source/components/datatable/stylesheet/filter-controls-defaults.mjs +1 -1
  8. package/source/components/form/stylesheet/button-bar.mjs +1 -1
  9. package/source/components/form/stylesheet/confirm-button.mjs +1 -1
  10. package/source/components/form/stylesheet/context-error.mjs +1 -1
  11. package/source/components/form/stylesheet/context-help.mjs +1 -1
  12. package/source/components/form/stylesheet/digits.mjs +1 -1
  13. package/source/components/form/stylesheet/field-set.mjs +1 -1
  14. package/source/components/form/stylesheet/login.mjs +1 -1
  15. package/source/components/form/stylesheet/popper-button.mjs +1 -1
  16. package/source/components/form/stylesheet/select.mjs +1 -1
  17. package/source/components/layout/stylesheet/popper.mjs +1 -1
  18. package/source/components/style/floating-ui.css +7 -0
  19. package/source/components/style/floating-ui.pcss +8 -0
  20. package/source/components/stylesheet/floating-ui.mjs +1 -1
  21. package/source/dom/customcontrol.mjs +1 -1
  22. package/source/dom/customelement.mjs +37 -15
  23. package/source/dom/updater.mjs +32 -12
  24. package/source/types/is.mjs +3 -0
  25. package/source/types/proxyobserver.mjs +10 -13
  26. package/source/types/version.mjs +1 -1
  27. package/test/cases/components/content/image-editor.mjs +98 -0
  28. package/test/cases/components/datatable/drag-scroll.mjs +218 -14
  29. package/test/cases/components/form/select.mjs +70 -32
  30. package/test/cases/dom/customcontrol.mjs +44 -10
  31. package/test/cases/dom/customelement-initfromscripthost.mjs +22 -1
  32. package/test/cases/dom/customelement.mjs +83 -0
  33. package/test/cases/dom/updater.mjs +98 -0
  34. package/test/cases/monster.mjs +1 -1
  35. package/test/cases/types/proxyobserver.mjs +31 -0
  36. package/test/web/import.js +3 -0
  37. package/test/web/puppeteer.mjs +94 -71
  38. package/test/web/test.html +29 -2
  39. package/test/web/tests.js +25120 -15937
@@ -117,6 +117,7 @@ const updateCloneDataSymbol = Symbol("@schukai/monster/dom/@@updateCloneData");
117
117
  * @type {symbol}
118
118
  */
119
119
  const scriptHostElementSymbol = Symbol("scriptHostElement");
120
+ const managedShadowRootSymbol = Symbol("managedShadowRoot");
120
121
 
121
122
  /**
122
123
  * The `CustomElement` class provides a way to define a new HTML element using the power of Custom Elements.
@@ -623,7 +624,7 @@ class CustomElement extends HTMLElement {
623
624
  if (this.getOption("shadowMode", false) !== false) {
624
625
  try {
625
626
  initShadowRoot.call(this);
626
- elements = this.shadowRoot.childNodes;
627
+ elements = getManagedShadowRoot.call(this)?.childNodes;
627
628
  } catch (e) {
628
629
  addErrorAttribute(this, e);
629
630
  }
@@ -770,11 +771,12 @@ class CustomElement extends HTMLElement {
770
771
  return true;
771
772
  }
772
773
 
773
- if (!(this.shadowRoot instanceof ShadowRoot)) {
774
+ const shadowRoot = getManagedShadowRoot.call(this);
775
+ if (!(shadowRoot instanceof ShadowRoot)) {
774
776
  return false;
775
777
  }
776
778
 
777
- return containChildNode.call(this.shadowRoot, node);
779
+ return containChildNode.call(shadowRoot, node);
778
780
  }
779
781
 
780
782
  /**
@@ -815,7 +817,7 @@ function callControlCallback(callBackFunctionName, ...args) {
815
817
 
816
818
  const list = targetId.split(",");
817
819
  for (const id of list) {
818
- const host = findElementWithIdUpwards(this, targetId);
820
+ const host = findElementWithIdUpwards(this, id.trim());
819
821
  if (!(host instanceof HTMLElement)) {
820
822
  continue;
821
823
  }
@@ -878,8 +880,14 @@ function attachAttributeChangeMutationObserver() {
878
880
 
879
881
  self[attributeMutationObserverSymbol] = new MutationObserver(
880
882
  function (mutations, observer) {
883
+ const observedAttributes = new Set(
884
+ self.constructor.observedAttributes || [],
885
+ );
881
886
  for (const mutation of mutations) {
882
887
  if (mutation.type === "attributes") {
888
+ if (observedAttributes.has(mutation.attributeName)) {
889
+ continue;
890
+ }
883
891
  self.attributeChangedCallback(
884
892
  mutation.attributeName,
885
893
  mutation.oldValue,
@@ -1014,7 +1022,8 @@ function initOptionObserver() {
1014
1022
  return;
1015
1023
  }
1016
1024
 
1017
- if (!(self.shadowRoot instanceof ShadowRoot) && !self.childNodes.length) {
1025
+ const shadowRoot = getManagedShadowRoot.call(self);
1026
+ if (!(shadowRoot instanceof ShadowRoot) && !self.childNodes.length) {
1018
1027
  return;
1019
1028
  }
1020
1029
 
@@ -1024,8 +1033,8 @@ function initOptionObserver() {
1024
1033
  "button, command, fieldset, keygen, optgroup, option, select, textarea, input, [data-monster-objectlink]";
1025
1034
 
1026
1035
  let elements = [];
1027
- if (self.shadowRoot instanceof ShadowRoot) {
1028
- elements = self.shadowRoot.querySelectorAll(query);
1036
+ if (shadowRoot instanceof ShadowRoot) {
1037
+ elements = shadowRoot.querySelectorAll(query);
1029
1038
  }
1030
1039
 
1031
1040
  let nodeList;
@@ -1235,6 +1244,16 @@ function parseOptionsJSON(data) {
1235
1244
  return validateObject(obj);
1236
1245
  }
1237
1246
 
1247
+ function getManagedShadowRoot() {
1248
+ if (this[managedShadowRootSymbol] instanceof ShadowRoot) {
1249
+ return this[managedShadowRootSymbol];
1250
+ }
1251
+ if (this.shadowRoot instanceof ShadowRoot) {
1252
+ return this.shadowRoot;
1253
+ }
1254
+ return null;
1255
+ }
1256
+
1238
1257
  /**
1239
1258
  * @private
1240
1259
  * @param html
@@ -1286,7 +1305,8 @@ function initHtmlContent() {
1286
1305
  * @throws {TypeError} value is not an instance of
1287
1306
  */
1288
1307
  function initCSSStylesheet() {
1289
- if (!(this.shadowRoot instanceof ShadowRoot)) {
1308
+ const shadowRoot = getManagedShadowRoot.call(this);
1309
+ if (!(shadowRoot instanceof ShadowRoot)) {
1290
1310
  return this;
1291
1311
  }
1292
1312
 
@@ -1294,7 +1314,7 @@ function initCSSStylesheet() {
1294
1314
 
1295
1315
  if (styleSheet instanceof CSSStyleSheet) {
1296
1316
  if (styleSheet.cssRules.length > 0) {
1297
- this.shadowRoot.adoptedStyleSheets = [styleSheet];
1317
+ shadowRoot.adoptedStyleSheets = [styleSheet];
1298
1318
  }
1299
1319
  } else if (isArray(styleSheet)) {
1300
1320
  const assign = [];
@@ -1304,7 +1324,7 @@ function initCSSStylesheet() {
1304
1324
  if (trimedStyleSheet !== "") {
1305
1325
  const style = document.createElement("style");
1306
1326
  style.innerHTML = trimedStyleSheet;
1307
- this.shadowRoot.prepend(style);
1327
+ shadowRoot.prepend(style);
1308
1328
  }
1309
1329
  continue;
1310
1330
  }
@@ -1317,14 +1337,14 @@ function initCSSStylesheet() {
1317
1337
  }
1318
1338
 
1319
1339
  if (assign.length > 0) {
1320
- this.shadowRoot.adoptedStyleSheets = assign;
1340
+ shadowRoot.adoptedStyleSheets = assign;
1321
1341
  }
1322
1342
  } else if (isString(styleSheet)) {
1323
1343
  const trimedStyleSheet = styleSheet.trim();
1324
1344
  if (trimedStyleSheet !== "") {
1325
1345
  const style = document.createElement("style");
1326
1346
  style.innerHTML = styleSheet;
1327
- this.shadowRoot.prepend(style);
1347
+ shadowRoot.prepend(style);
1328
1348
  }
1329
1349
  }
1330
1350
 
@@ -1352,13 +1372,15 @@ function initShadowRoot() {
1352
1372
  }
1353
1373
  }
1354
1374
 
1355
- this.attachShadow({
1375
+ this[managedShadowRootSymbol] = this.attachShadow({
1356
1376
  mode: this.getOption("shadowMode", "open"),
1357
1377
  delegatesFocus: this.getOption("delegatesFocus", true),
1358
1378
  });
1359
1379
 
1360
1380
  if (template instanceof Template) {
1361
- this.shadowRoot.appendChild(template.createDocumentFragment());
1381
+ this[managedShadowRootSymbol].appendChild(
1382
+ template.createDocumentFragment(),
1383
+ );
1362
1384
  return this;
1363
1385
  }
1364
1386
 
@@ -1374,7 +1396,7 @@ function initShadowRoot() {
1374
1396
  html = formatter.format(html);
1375
1397
  }
1376
1398
 
1377
- this.shadowRoot.innerHTML = substituteI18n.call(this, html);
1399
+ this[managedShadowRootSymbol].innerHTML = substituteI18n.call(this, html);
1378
1400
  return this;
1379
1401
  }
1380
1402
 
@@ -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.2");
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
+ });