@schukai/monster 4.83.0 → 4.85.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 +19 -0
- package/package.json +1 -1
- package/source/components/datatable/datatable.mjs +12 -1
- package/source/components/datatable/status.mjs +46 -22
- package/source/components/form/message-state-button.mjs +24 -1
- package/source/dom/customelement.mjs +34 -1
- package/test/cases/components/form/message-state-button.mjs +134 -0
- package/test/web/import.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
+
## [4.85.0] - 2026-01-08
|
|
6
|
+
|
|
7
|
+
### Add Features
|
|
8
|
+
|
|
9
|
+
- Improve spinner visibility control in DatasourceStatus component
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## [4.84.0] - 2026-01-08
|
|
14
|
+
|
|
15
|
+
### Add Features
|
|
16
|
+
|
|
17
|
+
- 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
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
- Enhance message-state-button synchronization for disabled state
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
5
24
|
## [4.83.0] - 2026-01-07
|
|
6
25
|
|
|
7
26
|
### 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.
|
|
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.85.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
|
-
|
|
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
|
|
@@ -105,6 +106,7 @@ class DatasourceStatus extends CustomElement {
|
|
|
105
106
|
|
|
106
107
|
timeouts: {
|
|
107
108
|
message: 4000,
|
|
109
|
+
spinnerMin: 200,
|
|
108
110
|
},
|
|
109
111
|
|
|
110
112
|
state: {
|
|
@@ -164,6 +166,7 @@ function initControlReferences() {
|
|
|
164
166
|
this[errorElementSymbol] = this.shadowRoot.querySelector(
|
|
165
167
|
"monster-context-error",
|
|
166
168
|
);
|
|
169
|
+
this[spinnerElementSymbol] = this.shadowRoot.querySelector(".monster-spinner");
|
|
167
170
|
}
|
|
168
171
|
|
|
169
172
|
/**
|
|
@@ -183,9 +186,44 @@ function initEventHandler() {
|
|
|
183
186
|
throw new TypeError("the element must be a datasource");
|
|
184
187
|
}
|
|
185
188
|
|
|
186
|
-
let
|
|
189
|
+
let hideTimer = null;
|
|
190
|
+
let lastShowAt = 0;
|
|
191
|
+
let requestVersion = 0;
|
|
192
|
+
|
|
193
|
+
const setSpinnerState = (state) => {
|
|
194
|
+
self.setOption("state.spinner", state);
|
|
195
|
+
const spinner = self[spinnerElementSymbol];
|
|
196
|
+
if (spinner) {
|
|
197
|
+
spinner.setAttribute("data-monster-state-loader", state);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
const clearHideTimer = () => {
|
|
201
|
+
if (hideTimer) {
|
|
202
|
+
clearTimeout(hideTimer);
|
|
203
|
+
hideTimer = null;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
const getSpinnerMinTimeout = () => {
|
|
207
|
+
const value = Number(self.getOption("timeouts.spinnerMin", 0));
|
|
208
|
+
return Number.isFinite(value) ? Math.max(0, value) : 0;
|
|
209
|
+
};
|
|
210
|
+
const scheduleHide = (version) => {
|
|
211
|
+
clearHideTimer();
|
|
212
|
+
const elapsed = Date.now() - lastShowAt;
|
|
213
|
+
const delay = Math.max(0, getSpinnerMinTimeout() - elapsed);
|
|
214
|
+
hideTimer = setTimeout(() => {
|
|
215
|
+
hideTimer = null;
|
|
216
|
+
if (version !== requestVersion) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
setSpinnerState("hide");
|
|
220
|
+
}, delay);
|
|
221
|
+
};
|
|
187
222
|
const hideSpinner = () => {
|
|
188
|
-
|
|
223
|
+
setSpinnerState("hide");
|
|
224
|
+
};
|
|
225
|
+
const showSpinner = () => {
|
|
226
|
+
setSpinnerState("show");
|
|
189
227
|
};
|
|
190
228
|
|
|
191
229
|
this[datasourceLinkedElementSymbol] = element;
|
|
@@ -194,36 +232,22 @@ function initEventHandler() {
|
|
|
194
232
|
if (typeof self[errorElementSymbol]?.resetErrorMessage === "function") {
|
|
195
233
|
self[errorElementSymbol].resetErrorMessage();
|
|
196
234
|
}
|
|
197
|
-
|
|
198
|
-
clearTimeout(fadeOutTimer);
|
|
199
|
-
fadeOutTimer = null;
|
|
200
|
-
}
|
|
201
|
-
fadeOutTimer = setTimeout(() => {
|
|
202
|
-
fadeOutTimer = null;
|
|
203
|
-
hideSpinner();
|
|
204
|
-
}, 800);
|
|
235
|
+
scheduleHide(requestVersion);
|
|
205
236
|
});
|
|
206
237
|
|
|
207
238
|
element.addEventListener("monster-datasource-fetch", function () {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
|
|
239
|
+
requestVersion += 1;
|
|
240
|
+
lastShowAt = Date.now();
|
|
241
|
+
clearHideTimer();
|
|
213
242
|
if (typeof self[errorElementSymbol]?.resetErrorMessage === "function") {
|
|
214
243
|
self[errorElementSymbol].resetErrorMessage();
|
|
215
244
|
}
|
|
216
245
|
|
|
217
|
-
|
|
246
|
+
showSpinner();
|
|
218
247
|
});
|
|
219
248
|
|
|
220
249
|
element.addEventListener("monster-datasource-error", function (event) {
|
|
221
|
-
|
|
222
|
-
clearTimeout(fadeOutTimer);
|
|
223
|
-
fadeOutTimer = null;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
hideSpinner();
|
|
250
|
+
scheduleHide(requestVersion);
|
|
227
251
|
|
|
228
252
|
const timeout = self.getOption("timeouts.message", 4000);
|
|
229
253
|
let msg = "Cannot load data";
|
|
@@ -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
|
-
|
|
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
|
+
});
|
package/test/web/import.js
CHANGED
|
@@ -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";
|