@schukai/monster 4.90.0 → 4.91.1

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,23 @@
2
2
 
3
3
 
4
4
 
5
+ ## [4.91.1] - 2026-01-12
6
+
7
+ ### Bug Fixes
8
+
9
+ - update colums
10
+
11
+
12
+
13
+ ## [4.91.0] - 2026-01-12
14
+
15
+ ### Add Features
16
+
17
+ - Update records and improve diff functionality [#372](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/372)
18
+ - Add new dynamic form element with repeatable fields [#372](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/372)
19
+
20
+
21
+
5
22
  ## [4.90.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.90.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.91.1"}
@@ -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";
@@ -74,6 +74,7 @@ const badgeElementSymbol = Symbol("badgeElement");
74
74
  const saveInFlightSymbol = Symbol("saveInFlight");
75
75
  const pendingResetSymbol = Symbol("pendingReset");
76
76
  const fetchInFlightSymbol = Symbol("fetchInFlight");
77
+ const originInitializedSymbol = Symbol("originInitialized");
77
78
 
78
79
  /**
79
80
  * A save button component
@@ -135,7 +136,7 @@ class SaveButton extends CustomElement {
135
136
  selector: null,
136
137
  },
137
138
 
138
- changes: "0",
139
+ changes: "",
139
140
 
140
141
  ignoreChanges: [],
141
142
 
@@ -204,13 +205,11 @@ class SaveButton extends CustomElement {
204
205
  });
205
206
  element.addEventListener("monster-datasource-fetched", () => {
206
207
  self[fetchInFlightSymbol] = false;
207
- if (!getOriginValues.call(self)) {
208
- setOriginValues.call(
209
- self,
210
- clone(self[datasourceLinkedElementSymbol].data),
211
- );
212
- updateChangesState.call(self);
213
- }
208
+ setOriginValues.call(
209
+ self,
210
+ clone(self[datasourceLinkedElementSymbol].data),
211
+ );
212
+ updateChangesState.call(self);
214
213
  });
215
214
  element.addEventListener("monster-datasource-error", () => {
216
215
  self[fetchInFlightSymbol] = false;
@@ -293,6 +292,7 @@ function setOriginValues(value) {
293
292
  if (datasource) {
294
293
  datasource[originValuesSymbol] = value;
295
294
  }
295
+ this[originInitializedSymbol] = true;
296
296
  }
297
297
 
298
298
  /**
@@ -304,6 +304,7 @@ function clearOriginValues() {
304
304
  if (datasource) {
305
305
  datasource[originValuesSymbol] = null;
306
306
  }
307
+ this[originInitializedSymbol] = false;
307
308
  }
308
309
 
309
310
  function getTranslations() {
@@ -374,19 +375,9 @@ function initControlReferences() {
374
375
 
375
376
  if (this[stateButtonElementSymbol]) {
376
377
  queueMicrotask(() => {
377
- const states = {
378
- changed: new State(
379
- "changed",
380
- '<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">' +
381
- '<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"/>' +
382
- '<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"/>' +
383
- "</svg>",
384
- ),
385
- };
386
-
378
+ ensureChangedState.call(this);
387
379
  this[stateButtonElementSymbol].removeState();
388
380
  this[stateButtonElementSymbol].setOption("disabled", "disabled");
389
- this[stateButtonElementSymbol].setOption("states", states);
390
381
  this[stateButtonElementSymbol].setOption(
391
382
  "labels.button",
392
383
  this.getOption("labels.button"),
@@ -409,7 +400,8 @@ function initEventHandler() {
409
400
  this[saveInFlightSymbol] = true;
410
401
  this[stateButtonElementSymbol].setOption("disabled", true);
411
402
 
412
- flushLinkedForms.call(this)
403
+ flushLinkedForms
404
+ .call(this)
413
405
  .then(() => this[datasourceLinkedElementSymbol].write())
414
406
  .then(() => {
415
407
  clearOriginValues.call(this);
@@ -419,7 +411,7 @@ function initEventHandler() {
419
411
  );
420
412
  this[stateButtonElementSymbol].removeState();
421
413
  this[stateButtonElementSymbol].setOption("disabled", true);
422
- this.setOption("changes", 0);
414
+ this.setOption("changes", "");
423
415
  this.setOption(
424
416
  "classes.badge",
425
417
  new TokenList(this.getOption("classes.badge"))
@@ -518,6 +510,24 @@ function updateChangesState() {
518
510
  const currentValues = this[datasourceLinkedElementSymbol]?.datasource?.get();
519
511
  const ignoreChanges = this.getOption("ignoreChanges");
520
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
+ }
521
531
  let result = diff(originValues, currentValues);
522
532
 
523
533
  if (
@@ -525,10 +535,7 @@ function updateChangesState() {
525
535
  location.search.includes("logLevel=debug")
526
536
  ) {
527
537
  console.groupCollapsed("SaveButton");
528
- console.log(
529
- "originValues",
530
- JSON.parse(JSON.stringify(originValues)),
531
- );
538
+ console.log("originValues", JSON.parse(JSON.stringify(originValues)));
532
539
  console.log("currentValues", JSON.parse(JSON.stringify(currentValues)));
533
540
  console.log("result of diff", result);
534
541
  console.log("ignoreChanges", ignoreChanges);
@@ -576,10 +583,12 @@ function updateChangesState() {
576
583
  }
577
584
  }
578
585
 
579
- if (isArray(result) && result.length > 0) {
586
+ const changeCount = countChanges(result);
587
+ if (changeCount > 0) {
588
+ ensureChangedState.call(this);
580
589
  this[stateButtonElementSymbol].setState("changed");
581
590
  this[stateButtonElementSymbol].setOption("disabled", false);
582
- this.setOption("changes", result.length);
591
+ this.setOption("changes", changeCount);
583
592
  this.setOption(
584
593
  "classes.badge",
585
594
  new TokenList(this.getOption("classes.badge"))
@@ -589,7 +598,7 @@ function updateChangesState() {
589
598
  } else {
590
599
  this[stateButtonElementSymbol].removeState();
591
600
  this[stateButtonElementSymbol].setOption("disabled", true);
592
- this.setOption("changes", 0);
601
+ this.setOption("changes", "");
593
602
  this.setOption(
594
603
  "classes.badge",
595
604
  new TokenList(this.getOption("classes.badge")).add("hidden").toString(),
@@ -597,6 +606,117 @@ function updateChangesState() {
597
606
  }
598
607
  }
599
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
+
600
720
  /**
601
721
  * @param {Object} options
602
722
  * @deprecated 2024-12-31