@schukai/monster 4.83.0 → 4.84.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
 
4
4
 
5
+ ## [4.84.0] - 2026-01-08
6
+
7
+ ### Add Features
8
+
9
+ - Add initial implementation for issue [#366](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/366) and [#367](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/367) with accompanying HTML and MJS files
10
+ ### Bug Fixes
11
+
12
+ - Enhance message-state-button synchronization for disabled state
13
+
14
+
15
+
5
16
  ## [4.83.0] - 2026-01-07
6
17
 
7
18
  ### 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.83.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.84.0"}
@@ -122,6 +122,11 @@ const copyAllElementSymbol = Symbol("copyAllElement");
122
122
  * @type {symbol}
123
123
  */
124
124
  const resizeObserverSymbol = Symbol("resizeObserver");
125
+ /**
126
+ * @private
127
+ * @type {symbol}
128
+ */
129
+ const suppressColumnConfigSaveSymbol = Symbol("suppressColumnConfigSave");
125
130
 
126
131
  /**
127
132
  * A DataTable
@@ -696,7 +701,11 @@ function updateColumnBar() {
696
701
  });
697
702
  }
698
703
 
704
+ this[suppressColumnConfigSaveSymbol] = true;
699
705
  this[columnBarElementSymbol].setOption("columns", columns);
706
+ queueMicrotask(() => {
707
+ this[suppressColumnConfigSaveSymbol] = false;
708
+ });
700
709
  }
701
710
 
702
711
  /**
@@ -870,7 +879,9 @@ function initEventHandler() {
870
879
  new Observer(() => {
871
880
  updateHeaderFromColumnBar.call(self);
872
881
  updateGrid.call(self);
873
- updateConfigColumnBar.call(self);
882
+ if (!self[suppressColumnConfigSaveSymbol]) {
883
+ updateConfigColumnBar.call(self);
884
+ }
874
885
  }),
875
886
  );
876
887
  }
@@ -45,6 +45,7 @@ const errorElementSymbol = Symbol.for("errorElement");
45
45
  * @type {symbol}
46
46
  */
47
47
  const datasourceLinkedElementSymbol = Symbol("datasourceLinkedElement");
48
+ const spinnerElementSymbol = Symbol("spinnerElement");
48
49
 
49
50
  /**
50
51
  * A simple dataset status component
@@ -164,6 +165,7 @@ function initControlReferences() {
164
165
  this[errorElementSymbol] = this.shadowRoot.querySelector(
165
166
  "monster-context-error",
166
167
  );
168
+ this[spinnerElementSymbol] = this.shadowRoot.querySelector(".monster-spinner");
167
169
  }
168
170
 
169
171
  /**
@@ -183,9 +185,18 @@ function initEventHandler() {
183
185
  throw new TypeError("the element must be a datasource");
184
186
  }
185
187
 
186
- let fadeOutTimer = null;
188
+ const setSpinnerState = (state) => {
189
+ self.setOption("state.spinner", state);
190
+ const spinner = self[spinnerElementSymbol];
191
+ if (spinner) {
192
+ spinner.setAttribute("data-monster-state-loader", state);
193
+ }
194
+ };
187
195
  const hideSpinner = () => {
188
- self.setOption("state.spinner", "hide");
196
+ setSpinnerState("hide");
197
+ };
198
+ const showSpinner = () => {
199
+ setSpinnerState("show");
189
200
  };
190
201
 
191
202
  this[datasourceLinkedElementSymbol] = element;
@@ -194,35 +205,18 @@ function initEventHandler() {
194
205
  if (typeof self[errorElementSymbol]?.resetErrorMessage === "function") {
195
206
  self[errorElementSymbol].resetErrorMessage();
196
207
  }
197
- if (fadeOutTimer) {
198
- clearTimeout(fadeOutTimer);
199
- fadeOutTimer = null;
200
- }
201
- fadeOutTimer = setTimeout(() => {
202
- fadeOutTimer = null;
203
- hideSpinner();
204
- }, 800);
208
+ hideSpinner();
205
209
  });
206
210
 
207
211
  element.addEventListener("monster-datasource-fetch", function () {
208
- if (fadeOutTimer) {
209
- clearTimeout(fadeOutTimer);
210
- fadeOutTimer = null;
211
- }
212
-
213
212
  if (typeof self[errorElementSymbol]?.resetErrorMessage === "function") {
214
213
  self[errorElementSymbol].resetErrorMessage();
215
214
  }
216
215
 
217
- self.setOption("state.spinner", "show");
216
+ showSpinner();
218
217
  });
219
218
 
220
219
  element.addEventListener("monster-datasource-error", function (event) {
221
- if (fadeOutTimer) {
222
- clearTimeout(fadeOutTimer);
223
- fadeOutTimer = null;
224
- }
225
-
226
220
  hideSpinner();
227
221
 
228
222
  const timeout = self.getOption("timeouts.message", 4000);
@@ -13,9 +13,10 @@
13
13
  */
14
14
 
15
15
  import { instanceSymbol } from "../../constants.mjs";
16
- import { ATTRIBUTE_ROLE } from "../../dom/constants.mjs";
16
+ import { ATTRIBUTE_DISABLED, ATTRIBUTE_ROLE } from "../../dom/constants.mjs";
17
17
  import {
18
18
  assembleMethodSymbol,
19
+ attributeObserverSymbol,
19
20
  registerCustomElement,
20
21
  } from "../../dom/customelement.mjs";
21
22
  import { isArray, isString } from "../../types/is.mjs";
@@ -411,9 +412,31 @@ function initDisabledSync() {
411
412
  if (self.getOption("features.disableButton", false) !== disabled) {
412
413
  self.setOption("features.disableButton", disabled);
413
414
  }
415
+
416
+ const button = self[buttonElementSymbol];
417
+ if (!button) {
418
+ return;
419
+ }
420
+
421
+ if (disabled) {
422
+ button.setAttribute(ATTRIBUTE_DISABLED, "");
423
+ } else {
424
+ button.removeAttribute(ATTRIBUTE_DISABLED);
425
+ }
426
+
427
+ if (isFunction(button.setOption)) {
428
+ button.setOption("disabled", disabled);
429
+ }
414
430
  };
415
431
 
416
432
  syncDisabled();
433
+ const existingObserver = self[attributeObserverSymbol]?.[ATTRIBUTE_DISABLED];
434
+ if (existingObserver) {
435
+ self[attributeObserverSymbol][ATTRIBUTE_DISABLED] = () => {
436
+ existingObserver.call(self);
437
+ syncDisabled();
438
+ };
439
+ }
417
440
  self.attachObserver(new Observer(syncDisabled));
418
441
  }
419
442
 
@@ -1039,7 +1039,7 @@ function initOptionObserver() {
1039
1039
  for (const list of updaters) {
1040
1040
  for (const updater of list) {
1041
1041
  const d = clone(self[internalSymbol].getRealSubject()["options"]);
1042
- Object.assign(updater.getSubject(), d);
1042
+ syncUpdaterSubject(updater.getSubject(), d);
1043
1043
  }
1044
1044
  }
1045
1045
  }),
@@ -1068,6 +1068,39 @@ function initOptionObserver() {
1068
1068
  };
1069
1069
  }
1070
1070
 
1071
+ /**
1072
+ * @private
1073
+ * @param {object} target
1074
+ * @param {object} source
1075
+ * @return {void}
1076
+ */
1077
+ function syncUpdaterSubject(target, source) {
1078
+ if (!isObject(source)) {
1079
+ return;
1080
+ }
1081
+
1082
+ for (const [key, value] of Object.entries(source)) {
1083
+ if (isArray(value)) {
1084
+ if (!isArray(target?.[key])) {
1085
+ target[key] = [];
1086
+ }
1087
+ target[key].length = 0;
1088
+ target[key].push(...clone(value));
1089
+ continue;
1090
+ }
1091
+
1092
+ if (isObject(value)) {
1093
+ if (!isObject(target?.[key]) || isArray(target?.[key])) {
1094
+ target[key] = {};
1095
+ }
1096
+ syncUpdaterSubject(target[key], value);
1097
+ continue;
1098
+ }
1099
+
1100
+ target[key] = value;
1101
+ }
1102
+ }
1103
+
1071
1104
  /**
1072
1105
  * @private
1073
1106
  * @return {object}
@@ -0,0 +1,134 @@
1
+ import { getGlobal } from "../../../../source/types/global.mjs";
2
+ import * as chai from "chai";
3
+ import { chaiDom } from "../../../util/chai-dom.mjs";
4
+ import { initJSDOM } from "../../../util/jsdom.mjs";
5
+ import { ResizeObserverMock } from "../../../util/resize-observer.mjs";
6
+
7
+ let expect = chai.expect;
8
+ chai.use(chaiDom);
9
+
10
+ const global = getGlobal();
11
+
12
+ let html1 = `
13
+ <div id="test1">
14
+ </div>
15
+ `;
16
+
17
+ let html2 = `
18
+ <div id="test2">
19
+ <monster-message-state-button data-monster-option-labels-button="Save">
20
+ Save
21
+ </monster-message-state-button>
22
+ </div>
23
+ `;
24
+
25
+ let MessageStateButton;
26
+
27
+ describe("MessageStateButton", function () {
28
+ before(function (done) {
29
+ initJSDOM().then(() => {
30
+ import("element-internals-polyfill").catch((e) => done(e));
31
+
32
+ if (!global.ResizeObserver) {
33
+ global.ResizeObserver = ResizeObserverMock;
34
+ }
35
+
36
+ import("../../../../source/components/form/message-state-button.mjs")
37
+ .then((m) => {
38
+ MessageStateButton = m["MessageStateButton"];
39
+ done();
40
+ })
41
+ .catch((e) => done(e));
42
+ });
43
+ });
44
+
45
+ describe("new MessageStateButton", function () {
46
+ beforeEach(() => {
47
+ let mocks = document.getElementById("mocks");
48
+ mocks.innerHTML = html1;
49
+ });
50
+
51
+ afterEach(() => {
52
+ let mocks = document.getElementById("mocks");
53
+ mocks.innerHTML = "";
54
+ });
55
+
56
+ describe("create from template", function () {
57
+ beforeEach(() => {
58
+ let mocks = document.getElementById("mocks");
59
+ mocks.innerHTML = html2;
60
+ });
61
+
62
+ afterEach(() => {
63
+ let mocks = document.getElementById("mocks");
64
+ mocks.innerHTML = "";
65
+ });
66
+
67
+ it("should contain monster-message-state-button", function () {
68
+ expect(document.getElementById("test2")).contain.html(
69
+ "<monster-message-state-button",
70
+ );
71
+ });
72
+ });
73
+
74
+ describe("document.createElement", function () {
75
+ it("should instance of message-state-button", function () {
76
+ expect(document.createElement("monster-message-state-button")).is
77
+ .instanceof(MessageStateButton);
78
+ });
79
+ });
80
+ });
81
+
82
+ describe("disabled toggle", function () {
83
+ afterEach(() => {
84
+ let mocks = document.getElementById("mocks");
85
+ mocks.innerHTML = "";
86
+ });
87
+
88
+ it("should sync disabled attribute to inner button", function (done) {
89
+ let mocks = document.getElementById("mocks");
90
+ const button = document.createElement("monster-message-state-button");
91
+ button.innerHTML = "Save";
92
+ mocks.appendChild(button);
93
+
94
+ setTimeout(() => {
95
+ try {
96
+ const inner = button.shadowRoot.querySelector(
97
+ "monster-state-button",
98
+ );
99
+ expect(inner).to.exist;
100
+
101
+ button.setAttribute("disabled", "");
102
+ setTimeout(() => {
103
+ try {
104
+ expect(inner.hasAttribute("disabled")).to.be.true;
105
+
106
+ button.removeAttribute("disabled");
107
+ setTimeout(() => {
108
+ try {
109
+ expect(inner.hasAttribute("disabled")).to.be.false;
110
+
111
+ button.setAttribute("disabled", "");
112
+ setTimeout(() => {
113
+ try {
114
+ expect(inner.hasAttribute("disabled")).to.be.true;
115
+ done();
116
+ } catch (e) {
117
+ done(e);
118
+ }
119
+ }, 0);
120
+ } catch (e) {
121
+ done(e);
122
+ }
123
+ }, 0);
124
+ } catch (e) {
125
+ done(e);
126
+ }
127
+ }, 0);
128
+ } catch (e) {
129
+ done(e);
130
+ }
131
+ }, 0);
132
+ });
133
+ });
134
+ });
@@ -7,6 +7,7 @@ import "../cases/components/form/buy-box.mjs";
7
7
  import "../cases/components/form/button-bar.mjs";
8
8
  import "../cases/components/form/reload.mjs";
9
9
  import "../cases/components/form/state-button.mjs";
10
+ import "../cases/components/form/message-state-button.mjs";
10
11
  import "../cases/components/form/select.mjs";
11
12
  import "../cases/components/form/confirm-button.mjs";
12
13
  import "../cases/components/form/form.mjs";