@schukai/monster 4.89.0 → 4.91.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 +17 -0
- package/package.json +1 -1
- package/source/components/datatable/save-button.mjs +215 -40
- package/source/components/form/repeat-field-set.mjs +1090 -0
- package/source/components/form/select.mjs +8 -1
- package/source/components/form/style/field-set.pcss +2 -296
- package/source/components/form/style/mixin/field-set-layout.pcss +545 -0
- package/source/components/form/style/repeat-field-set-items.pcss +11 -0
- package/source/components/form/style/repeat-field-set.pcss +45 -0
- package/source/components/form/stylesheet/field-set.mjs +1 -1
- package/source/components/form/stylesheet/mixin/field-set-layout.mjs +38 -0
- package/source/components/form/stylesheet/repeat-field-set-items.mjs +38 -0
- package/source/components/form/stylesheet/repeat-field-set.mjs +38 -0
- package/source/data/diff.mjs +0 -4
- package/source/monster.mjs +1 -0
- package/test/cases/data/diff.mjs +24 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
+
## [4.91.0] - 2026-01-12
|
|
6
|
+
|
|
7
|
+
### Add Features
|
|
8
|
+
|
|
9
|
+
- Update records and improve diff functionality [#372](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/372)
|
|
10
|
+
- Add new dynamic form element with repeatable fields [#372](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/372)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [4.90.0] - 2026-01-11
|
|
15
|
+
|
|
16
|
+
### Add Features
|
|
17
|
+
|
|
18
|
+
- Enhance origin values management in SaveButton component
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
5
22
|
## [4.89.0] - 2026-01-11
|
|
6
23
|
|
|
7
24
|
### 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.91.0"}
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
findElementWithSelectorUpwards,
|
|
33
33
|
getDocument,
|
|
34
34
|
} from "../../dom/util.mjs";
|
|
35
|
-
import { isString, isArray } from "../../types/is.mjs";
|
|
35
|
+
import { isString, isArray, isObject } from "../../types/is.mjs";
|
|
36
36
|
import { Observer } from "../../types/observer.mjs";
|
|
37
37
|
import { TokenList } from "../../types/tokenlist.mjs";
|
|
38
38
|
import { clone } from "../../util/clone.mjs";
|
|
@@ -62,7 +62,9 @@ const stateButtonElementSymbol = Symbol("stateButtonElement");
|
|
|
62
62
|
* @private
|
|
63
63
|
* @type {symbol}
|
|
64
64
|
*/
|
|
65
|
-
const originValuesSymbol = Symbol(
|
|
65
|
+
const originValuesSymbol = Symbol.for(
|
|
66
|
+
"@schukai/monster/components/datatable/save-button@@originValues",
|
|
67
|
+
);
|
|
66
68
|
|
|
67
69
|
/**
|
|
68
70
|
* @private
|
|
@@ -72,6 +74,7 @@ const badgeElementSymbol = Symbol("badgeElement");
|
|
|
72
74
|
const saveInFlightSymbol = Symbol("saveInFlight");
|
|
73
75
|
const pendingResetSymbol = Symbol("pendingReset");
|
|
74
76
|
const fetchInFlightSymbol = Symbol("fetchInFlight");
|
|
77
|
+
const originInitializedSymbol = Symbol("originInitialized");
|
|
75
78
|
|
|
76
79
|
/**
|
|
77
80
|
* A save button component
|
|
@@ -133,7 +136,7 @@ class SaveButton extends CustomElement {
|
|
|
133
136
|
selector: null,
|
|
134
137
|
},
|
|
135
138
|
|
|
136
|
-
changes: "
|
|
139
|
+
changes: "",
|
|
137
140
|
|
|
138
141
|
ignoreChanges: [],
|
|
139
142
|
|
|
@@ -192,22 +195,21 @@ class SaveButton extends CustomElement {
|
|
|
192
195
|
}
|
|
193
196
|
|
|
194
197
|
if (element instanceof RestDatasource) {
|
|
195
|
-
element.addEventListener("monster-datasource-fetch", (
|
|
198
|
+
element.addEventListener("monster-datasource-fetch", () => {
|
|
196
199
|
if (self[saveInFlightSymbol]) {
|
|
197
200
|
self[pendingResetSymbol] = true;
|
|
198
201
|
return;
|
|
199
202
|
}
|
|
200
203
|
self[fetchInFlightSymbol] = true;
|
|
201
|
-
self
|
|
204
|
+
clearOriginValues.call(self);
|
|
202
205
|
});
|
|
203
206
|
element.addEventListener("monster-datasource-fetched", () => {
|
|
204
207
|
self[fetchInFlightSymbol] = false;
|
|
205
|
-
|
|
206
|
-
self
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
208
|
+
setOriginValues.call(
|
|
209
|
+
self,
|
|
210
|
+
clone(self[datasourceLinkedElementSymbol].data),
|
|
211
|
+
);
|
|
212
|
+
updateChangesState.call(self);
|
|
211
213
|
});
|
|
212
214
|
element.addEventListener("monster-datasource-error", () => {
|
|
213
215
|
self[fetchInFlightSymbol] = false;
|
|
@@ -219,22 +221,25 @@ class SaveButton extends CustomElement {
|
|
|
219
221
|
new Observer(handleDataSourceChanges.bind(this)),
|
|
220
222
|
);
|
|
221
223
|
|
|
222
|
-
self
|
|
224
|
+
clearOriginValues.call(self);
|
|
223
225
|
|
|
224
226
|
element.datasource.attachObserver(
|
|
225
227
|
new Observer(function () {
|
|
226
228
|
if (self[fetchInFlightSymbol] === true) {
|
|
227
229
|
return;
|
|
228
230
|
}
|
|
229
|
-
if (!self
|
|
230
|
-
|
|
231
|
-
self
|
|
231
|
+
if (!getOriginValues.call(self)) {
|
|
232
|
+
setOriginValues.call(
|
|
233
|
+
self,
|
|
234
|
+
clone(self[datasourceLinkedElementSymbol].data),
|
|
232
235
|
);
|
|
233
236
|
}
|
|
234
237
|
|
|
235
238
|
updateChangesState.call(self);
|
|
236
239
|
}),
|
|
237
240
|
);
|
|
241
|
+
|
|
242
|
+
syncOriginValues.call(self);
|
|
238
243
|
}
|
|
239
244
|
|
|
240
245
|
this.attachObserver(
|
|
@@ -253,6 +258,55 @@ class SaveButton extends CustomElement {
|
|
|
253
258
|
}
|
|
254
259
|
}
|
|
255
260
|
|
|
261
|
+
/**
|
|
262
|
+
* @private
|
|
263
|
+
*/
|
|
264
|
+
function syncOriginValues() {
|
|
265
|
+
if (getOriginValues.call(this)) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const data = this[datasourceLinkedElementSymbol]?.data;
|
|
269
|
+
if (!data) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
setOriginValues.call(this, clone(data));
|
|
273
|
+
updateChangesState.call(this);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* @private
|
|
278
|
+
* @return {*}
|
|
279
|
+
*/
|
|
280
|
+
function getOriginValues() {
|
|
281
|
+
const datasource = this[datasourceLinkedElementSymbol];
|
|
282
|
+
return datasource ? datasource[originValuesSymbol] : null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* @private
|
|
287
|
+
* @param {*} value
|
|
288
|
+
* @return {void}
|
|
289
|
+
*/
|
|
290
|
+
function setOriginValues(value) {
|
|
291
|
+
const datasource = this[datasourceLinkedElementSymbol];
|
|
292
|
+
if (datasource) {
|
|
293
|
+
datasource[originValuesSymbol] = value;
|
|
294
|
+
}
|
|
295
|
+
this[originInitializedSymbol] = true;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* @private
|
|
300
|
+
* @return {void}
|
|
301
|
+
*/
|
|
302
|
+
function clearOriginValues() {
|
|
303
|
+
const datasource = this[datasourceLinkedElementSymbol];
|
|
304
|
+
if (datasource) {
|
|
305
|
+
datasource[originValuesSymbol] = null;
|
|
306
|
+
}
|
|
307
|
+
this[originInitializedSymbol] = false;
|
|
308
|
+
}
|
|
309
|
+
|
|
256
310
|
function getTranslations() {
|
|
257
311
|
const locale = getLocaleOfDocument();
|
|
258
312
|
switch (locale.language) {
|
|
@@ -321,19 +375,9 @@ function initControlReferences() {
|
|
|
321
375
|
|
|
322
376
|
if (this[stateButtonElementSymbol]) {
|
|
323
377
|
queueMicrotask(() => {
|
|
324
|
-
|
|
325
|
-
changed: new State(
|
|
326
|
-
"changed",
|
|
327
|
-
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-cloud-arrow-up" viewBox="0 0 16 16">' +
|
|
328
|
-
'<path fill-rule="evenodd" d="M7.646 5.146a.5.5 0 0 1 .708 0l2 2a.5.5 0 0 1-.708.708L8.5 6.707V10.5a.5.5 0 0 1-1 0V6.707L6.354 7.854a.5.5 0 1 1-.708-.708z"/>' +
|
|
329
|
-
'<path d="M4.406 3.342A5.53 5.53 0 0 1 8 2c2.69 0 4.923 2 5.166 4.579C14.758 6.804 16 8.137 16 9.773 16 11.569 14.502 13 12.687 13H3.781C1.708 13 0 11.366 0 9.318c0-1.763 1.266-3.223 2.942-3.593.143-.863.698-1.723 1.464-2.383m.653.757c-.757.653-1.153 1.44-1.153 2.056v.448l-.445.049C2.064 6.805 1 7.952 1 9.318 1 10.785 2.23 12 3.781 12h8.906C13.98 12 15 10.988 15 9.773c0-1.216-1.02-2.228-2.313-2.228h-.5v-.5C12.188 4.825 10.328 3 8 3a4.53 4.53 0 0 0-2.941 1.1z"/>' +
|
|
330
|
-
"</svg>",
|
|
331
|
-
),
|
|
332
|
-
};
|
|
333
|
-
|
|
378
|
+
ensureChangedState.call(this);
|
|
334
379
|
this[stateButtonElementSymbol].removeState();
|
|
335
380
|
this[stateButtonElementSymbol].setOption("disabled", "disabled");
|
|
336
|
-
this[stateButtonElementSymbol].setOption("states", states);
|
|
337
381
|
this[stateButtonElementSymbol].setOption(
|
|
338
382
|
"labels.button",
|
|
339
383
|
this.getOption("labels.button"),
|
|
@@ -356,16 +400,18 @@ function initEventHandler() {
|
|
|
356
400
|
this[saveInFlightSymbol] = true;
|
|
357
401
|
this[stateButtonElementSymbol].setOption("disabled", true);
|
|
358
402
|
|
|
359
|
-
flushLinkedForms
|
|
403
|
+
flushLinkedForms
|
|
404
|
+
.call(this)
|
|
360
405
|
.then(() => this[datasourceLinkedElementSymbol].write())
|
|
361
406
|
.then(() => {
|
|
362
|
-
this
|
|
363
|
-
|
|
364
|
-
this
|
|
407
|
+
clearOriginValues.call(this);
|
|
408
|
+
setOriginValues.call(
|
|
409
|
+
this,
|
|
410
|
+
clone(this[datasourceLinkedElementSymbol].data),
|
|
365
411
|
);
|
|
366
412
|
this[stateButtonElementSymbol].removeState();
|
|
367
413
|
this[stateButtonElementSymbol].setOption("disabled", true);
|
|
368
|
-
this.setOption("changes",
|
|
414
|
+
this.setOption("changes", "");
|
|
369
415
|
this.setOption(
|
|
370
416
|
"classes.badge",
|
|
371
417
|
new TokenList(this.getOption("classes.badge"))
|
|
@@ -381,7 +427,7 @@ function initEventHandler() {
|
|
|
381
427
|
this[saveInFlightSymbol] = false;
|
|
382
428
|
if (this[pendingResetSymbol]) {
|
|
383
429
|
this[pendingResetSymbol] = false;
|
|
384
|
-
this
|
|
430
|
+
clearOriginValues.call(this);
|
|
385
431
|
}
|
|
386
432
|
});
|
|
387
433
|
});
|
|
@@ -463,17 +509,33 @@ function flushLinkedForms() {
|
|
|
463
509
|
function updateChangesState() {
|
|
464
510
|
const currentValues = this[datasourceLinkedElementSymbol]?.datasource?.get();
|
|
465
511
|
const ignoreChanges = this.getOption("ignoreChanges");
|
|
466
|
-
|
|
512
|
+
const originValues = getOriginValues.call(this);
|
|
513
|
+
|
|
514
|
+
if (
|
|
515
|
+
this[originInitializedSymbol] !== true ||
|
|
516
|
+
this[fetchInFlightSymbol] === true ||
|
|
517
|
+
originValues === null ||
|
|
518
|
+
originValues === undefined ||
|
|
519
|
+
(isObject(originValues) &&
|
|
520
|
+
Object.keys(originValues).length === 0 &&
|
|
521
|
+
isObject(currentValues) &&
|
|
522
|
+
Object.keys(currentValues).length === 0)
|
|
523
|
+
) {
|
|
524
|
+
this.setOption("changes", "");
|
|
525
|
+
this.setOption(
|
|
526
|
+
"classes.badge",
|
|
527
|
+
new TokenList(this.getOption("classes.badge")).add("hidden").toString(),
|
|
528
|
+
);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
let result = diff(originValues, currentValues);
|
|
467
532
|
|
|
468
533
|
if (
|
|
469
534
|
this.getOption("logLevel") === "debug" ||
|
|
470
535
|
location.search.includes("logLevel=debug")
|
|
471
536
|
) {
|
|
472
537
|
console.groupCollapsed("SaveButton");
|
|
473
|
-
console.log(
|
|
474
|
-
"originValues",
|
|
475
|
-
JSON.parse(JSON.stringify(this[originValuesSymbol])),
|
|
476
|
-
);
|
|
538
|
+
console.log("originValues", JSON.parse(JSON.stringify(originValues)));
|
|
477
539
|
console.log("currentValues", JSON.parse(JSON.stringify(currentValues)));
|
|
478
540
|
console.log("result of diff", result);
|
|
479
541
|
console.log("ignoreChanges", ignoreChanges);
|
|
@@ -521,10 +583,12 @@ function updateChangesState() {
|
|
|
521
583
|
}
|
|
522
584
|
}
|
|
523
585
|
|
|
524
|
-
|
|
586
|
+
const changeCount = countChanges(result);
|
|
587
|
+
if (changeCount > 0) {
|
|
588
|
+
ensureChangedState.call(this);
|
|
525
589
|
this[stateButtonElementSymbol].setState("changed");
|
|
526
590
|
this[stateButtonElementSymbol].setOption("disabled", false);
|
|
527
|
-
this.setOption("changes",
|
|
591
|
+
this.setOption("changes", changeCount);
|
|
528
592
|
this.setOption(
|
|
529
593
|
"classes.badge",
|
|
530
594
|
new TokenList(this.getOption("classes.badge"))
|
|
@@ -534,7 +598,7 @@ function updateChangesState() {
|
|
|
534
598
|
} else {
|
|
535
599
|
this[stateButtonElementSymbol].removeState();
|
|
536
600
|
this[stateButtonElementSymbol].setOption("disabled", true);
|
|
537
|
-
this.setOption("changes",
|
|
601
|
+
this.setOption("changes", "");
|
|
538
602
|
this.setOption(
|
|
539
603
|
"classes.badge",
|
|
540
604
|
new TokenList(this.getOption("classes.badge")).add("hidden").toString(),
|
|
@@ -542,6 +606,117 @@ function updateChangesState() {
|
|
|
542
606
|
}
|
|
543
607
|
}
|
|
544
608
|
|
|
609
|
+
/**
|
|
610
|
+
* @private
|
|
611
|
+
* @param {Array} changes
|
|
612
|
+
* @return {number}
|
|
613
|
+
*/
|
|
614
|
+
function countChanges(changes) {
|
|
615
|
+
if (!isArray(changes) || changes.length === 0) {
|
|
616
|
+
return 0;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
let total = 0;
|
|
620
|
+
for (const change of changes) {
|
|
621
|
+
if (change?.operator === "add") {
|
|
622
|
+
const nonEmpty = countNonEmptyLeafValues(change?.second?.value);
|
|
623
|
+
total += Math.max(1, nonEmpty);
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (change?.operator === "remove") {
|
|
628
|
+
const nonEmpty = countNonEmptyLeafValues(change?.first?.value);
|
|
629
|
+
total += Math.max(1, nonEmpty);
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
total += 1;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
return total;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* @private
|
|
641
|
+
* @param {*} value
|
|
642
|
+
* @return {number}
|
|
643
|
+
*/
|
|
644
|
+
function countNonEmptyLeafValues(value) {
|
|
645
|
+
if (value === null || value === undefined) {
|
|
646
|
+
return 0;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
if (value instanceof Date) {
|
|
650
|
+
return 1;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
if (typeof value === "string") {
|
|
654
|
+
return value.trim() === "" ? 0 : 1;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (typeof value === "number") {
|
|
658
|
+
return Number.isFinite(value) ? 1 : 0;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (typeof value === "boolean") {
|
|
662
|
+
return 1;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (isArray(value)) {
|
|
666
|
+
if (value.length === 0) {
|
|
667
|
+
return 0;
|
|
668
|
+
}
|
|
669
|
+
return value.reduce((sum, item) => sum + countNonEmptyLeafValues(item), 0);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
if (isObject(value)) {
|
|
673
|
+
const keys = Object.keys(value);
|
|
674
|
+
if (keys.length === 0) {
|
|
675
|
+
return 0;
|
|
676
|
+
}
|
|
677
|
+
return keys.reduce(
|
|
678
|
+
(sum, key) => sum + countNonEmptyLeafValues(value[key]),
|
|
679
|
+
0,
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
return 1;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* @private
|
|
688
|
+
* @return {void}
|
|
689
|
+
*/
|
|
690
|
+
function ensureChangedState() {
|
|
691
|
+
const stateButton = this[stateButtonElementSymbol];
|
|
692
|
+
if (!stateButton || typeof stateButton.getOption !== "function") {
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
if (stateButton.getOption("states.changed")) {
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const states = Object.assign({}, stateButton.getOption("states") || {}, {
|
|
701
|
+
changed: getChangedState(),
|
|
702
|
+
});
|
|
703
|
+
stateButton.setOption("states", states);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* @private
|
|
708
|
+
* @return {State}
|
|
709
|
+
*/
|
|
710
|
+
function getChangedState() {
|
|
711
|
+
return new State(
|
|
712
|
+
"changed",
|
|
713
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-cloud-arrow-up" viewBox="0 0 16 16">' +
|
|
714
|
+
'<path fill-rule="evenodd" d="M7.646 5.146a.5.5 0 0 1 .708 0l2 2a.5.5 0 0 1-.708.708L8.5 6.707V10.5a.5.5 0 0 1-1 0V6.707L6.354 7.854a.5.5 0 1 1-.708-.708z"/>' +
|
|
715
|
+
'<path d="M4.406 3.342A5.53 5.53 0 0 1 8 2c2.69 0 4.923 2 5.166 4.579C14.758 6.804 16 8.137 16 9.773 16 11.569 14.502 13 12.687 13H3.781C1.708 13 0 11.366 0 9.318c0-1.763 1.266-3.223 2.942-3.593.143-.863.698-1.723 1.464-2.383m.653.757c-.757.653-1.153 1.44-1.153 2.056v.448l-.445.049C2.064 6.805 1 7.952 1 9.318 1 10.785 2.23 12 3.781 12h8.906C13.98 12 15 10.988 15 9.773c0-1.216-1.02-2.228-2.313-2.228h-.5v-.5C12.188 4.825 10.328 3 8 3a4.53 4.53 0 0 0-2.941 1.1z"/>' +
|
|
716
|
+
"</svg>",
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
|
|
545
720
|
/**
|
|
546
721
|
* @param {Object} options
|
|
547
722
|
* @deprecated 2024-12-31
|