@schukai/monster 4.122.2 → 4.124.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,23 @@
2
2
 
3
3
 
4
4
 
5
+ ## [4.124.0] - 2026-02-20
6
+
7
+ ### Add Features
8
+
9
+ - Enhance login component with behavior options for password reset handling
10
+
11
+
12
+
13
+ ## [4.123.0] - 2026-02-17
14
+
15
+ ### Add Features
16
+
17
+ - Optimize tree menu entry visibility handling
18
+ - Enhance tree menu entry state management for improved performance and consistency
19
+
20
+
21
+
5
22
  ## [4.122.2] - 2026-02-16
6
23
 
7
24
  ### Bug Fixes
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.122.2"}
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.124.0"}
@@ -218,6 +218,8 @@ class Login extends CustomElement {
218
218
  * @property {Object} callbacks Optional callback hooks for modifying internal behavior
219
219
  * @property {Function} callbacks.username A function that receives and can transform the entered username before submission
220
220
  * @property {Function} callbacks.forgotPassword A function that receives and can transform the entered email before submission
221
+ * @property {Object} behavior Behavior options for response handling
222
+ * @property {number[]} behavior.passwordResetDisabledStatusCodes HTTP status codes that indicate reset is disabled
221
223
  * @property {number} digits Number of digits required for second factor or password reset code input
222
224
  * @property {Object[]} successUrls List of URLs shown after successful login (e.g., home or logout)
223
225
  * @property {string} successUrls.label Label for the success URL (displayed)
@@ -297,6 +299,10 @@ class Login extends CustomElement {
297
299
  forgotPassword: null,
298
300
  },
299
301
 
302
+ behavior: {
303
+ passwordResetDisabledStatusCodes: [401],
304
+ },
305
+
300
306
  digits: 6,
301
307
 
302
308
  successUrls: [
@@ -1484,35 +1490,37 @@ function initEventHandler() {
1484
1490
  getWindow()
1485
1491
  .fetch(url, options)
1486
1492
  .then((response) => {
1493
+ const disabledCodes =
1494
+ this.getOption("behavior.passwordResetDisabledStatusCodes") || [401];
1495
+ const isResetDisabled =
1496
+ disabledCodes.includes(response.status) &&
1497
+ response.headers.has("x-password-reset") &&
1498
+ response.headers.get("x-password-reset").includes("disabled");
1499
+
1487
1500
  if (response.ok) {
1488
1501
  const timeoutSuccess = this.getOption("timeoutForSuccess");
1489
1502
  this[requestLinkButtonSymbol].setState("successful", timeoutSuccess);
1490
1503
  setTimeout(() => {
1491
1504
  this.openDigits();
1492
1505
  }, timeoutSuccess);
1506
+ } else if (isResetDisabled) {
1507
+ this[requestLinkButtonSymbol].setMessage(
1508
+ this.getOption("labels.messagePasswordResetDisabled"),
1509
+ );
1510
+ } else if (response.status === 403) {
1511
+ this[requestLinkButtonSymbol].setMessage(
1512
+ this.getOption("labels.messageForbidden"),
1513
+ );
1514
+ } else if (response.status === 401) {
1515
+ this[requestLinkButtonSymbol].setMessage(
1516
+ this.getOption("labels.messageLoginFailed"),
1517
+ );
1493
1518
  } else {
1494
- if (response.status === 403) {
1495
- this[requestLinkButtonSymbol].setMessage(
1496
- this.getOption("labels.messageForbidden"),
1497
- );
1498
- } else if (response.status === 401) {
1499
- if (
1500
- response.headers.has("x-password-reset") &&
1501
- response.headers.get("x-password-reset").includes("disabled")
1502
- ) {
1503
- this[requestLinkButtonSymbol].setMessage(
1504
- this.getOption("labels.messagePasswordResetDisabled"),
1505
- );
1506
- } else {
1507
- this[requestLinkButtonSymbol].setMessage(
1508
- this.getOption("labels.messageLoginFailed"),
1509
- );
1510
- }
1511
- } else {
1512
- this[requestLinkButtonSymbol].setMessage(
1513
- this.getOption("labels.messageSomethingWentWrong"),
1514
- );
1515
- }
1519
+ this[requestLinkButtonSymbol].setMessage(
1520
+ this.getOption("labels.messageSomethingWentWrong"),
1521
+ );
1522
+ }
1523
+ if (!response.ok) {
1516
1524
  this[requestLinkButtonSymbol].showMessage(timeout);
1517
1525
  this[requestLinkButtonSymbol].setState("failed", timeout);
1518
1526
  }
@@ -124,6 +124,9 @@ class HtmlTreeMenu extends CustomElement {
124
124
  onlazyloaded: null,
125
125
  onlazyerror: null,
126
126
  },
127
+ updater: {
128
+ batchUpdates: true,
129
+ },
127
130
  entries: [],
128
131
  });
129
132
  }
@@ -687,50 +690,46 @@ function applyEntryState(index, state, event) {
687
690
  event,
688
691
  });
689
692
 
690
- this.setOption("entries." + index + ".state", state);
691
- const newVisibility = state === "open" ? "visible" : "hidden";
693
+ const entries = this.getOption("entries", []);
694
+ const nextEntries = [...entries];
695
+ let changed = false;
692
696
 
693
- const entryNode = this.shadowRoot.querySelector(
694
- "[data-monster-insert-reference=entries-" + index + "]",
695
- );
697
+ nextEntries[index] = Object.assign({}, entry, {
698
+ state,
699
+ });
700
+ changed = true;
701
+ const newVisibility = state === "open" ? "visible" : "hidden";
702
+ const targetIntend = entry.intend;
703
+ const end = getSubtreeEndIndex(entries, index);
704
+ const childIntend = targetIntend + 1;
696
705
 
697
- if (entryNode?.hasAttribute(ATTRIBUTE_INTEND)) {
698
- const intend = entryNode.getAttribute(ATTRIBUTE_INTEND);
699
- let ref = entryNode.nextElementSibling;
700
- const childIntend = parseInt(intend) + 1;
706
+ for (let i = index + 1; i < end; i += 1) {
707
+ const childEntry = entries[i];
701
708
 
702
- const cmp = (a, b) => {
703
- if (state === "open") {
704
- return a === b;
709
+ if (state === "open") {
710
+ if (childEntry.intend !== childIntend) {
711
+ continue;
705
712
  }
706
-
707
- return a >= b;
708
- };
709
-
710
- while (ref?.hasAttribute(ATTRIBUTE_INTEND)) {
711
- const refIntend = ref.getAttribute(ATTRIBUTE_INTEND);
712
-
713
- if (!cmp(Number.parseInt(refIntend), childIntend)) {
714
- if (refIntend === intend) {
715
- break;
716
- }
717
- ref = ref.nextElementSibling;
713
+ if (childEntry.visibility === newVisibility) {
718
714
  continue;
719
715
  }
720
-
721
- const refIndex = ref
722
- .getAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE)
723
- .split("-")
724
- .pop();
725
-
726
- this.setOption("entries." + refIndex + ".visibility", newVisibility);
727
-
728
- if (state === "close") {
729
- this.setOption("entries." + refIndex + ".state", "close");
716
+ } else {
717
+ if (childEntry.visibility === newVisibility) {
718
+ continue;
730
719
  }
720
+ }
731
721
 
732
- ref = ref.nextElementSibling;
722
+ const updates = { visibility: newVisibility };
723
+ if (state === "close" && childEntry["has-children"]) {
724
+ updates.state = "close";
733
725
  }
726
+
727
+ nextEntries[i] = Object.assign({}, childEntry, updates);
728
+ changed = true;
729
+ }
730
+
731
+ if (changed === true) {
732
+ this.setOption("entries", nextEntries);
734
733
  }
735
734
  }
736
735
 
@@ -751,16 +750,29 @@ function setEntryVisibility(value, visibility) {
751
750
  return;
752
751
  }
753
752
 
754
- this.setOption("entries." + index + ".visibility", visibility);
755
- const intend = target.intend;
756
- for (let i = index + 1; i < entries.length; i += 1) {
757
- if (entries[i].intend <= intend) {
758
- break;
759
- }
753
+ const nextEntries = [...entries];
754
+ let changed = false;
755
+ if (target.visibility !== visibility) {
756
+ nextEntries[index] = Object.assign({}, target, {
757
+ visibility,
758
+ });
759
+ changed = true;
760
+ }
761
+ const end = getSubtreeEndIndex(entries, index);
762
+ for (let i = index + 1; i < end; i += 1) {
760
763
  if (visibility === "hidden") {
761
- this.setOption("entries." + i + ".visibility", "hidden");
764
+ if (entries[i].visibility !== "hidden") {
765
+ nextEntries[i] = Object.assign({}, entries[i], {
766
+ visibility: "hidden",
767
+ });
768
+ changed = true;
769
+ }
762
770
  }
763
771
  }
772
+
773
+ if (changed === true) {
774
+ this.setOption("entries", nextEntries);
775
+ }
764
776
  }
765
777
 
766
778
  /**
@@ -804,6 +816,9 @@ function collapseEntry(value) {
804
816
  if (!entry || entry["has-children"] === false) {
805
817
  return;
806
818
  }
819
+ if (entry.state === "close") {
820
+ return;
821
+ }
807
822
 
808
823
  const doAction = getAction.call(this, ["oncollapse", "close"]);
809
824
  if (isFunction(doAction)) {
@@ -814,15 +829,38 @@ function collapseEntry(value) {
814
829
  index,
815
830
  });
816
831
 
817
- this.setOption("entries." + index + ".state", "close");
818
-
819
832
  const entries = this.getOption("entries", []);
833
+ const nextEntries = [...entries];
834
+ let changed = false;
835
+ nextEntries[index] = Object.assign({}, entries[index], {
836
+ state: "close",
837
+ });
838
+ changed = true;
820
839
  const end = getSubtreeEndIndex(entries, index);
821
840
  for (let i = index + 1; i < end; i += 1) {
822
- this.setOption("entries." + i + ".visibility", "hidden");
823
- if (entries[i]["has-children"]) {
824
- this.setOption("entries." + i + ".state", "close");
841
+ const childEntry = entries[i];
842
+ const hasChildren = childEntry["has-children"] === true;
843
+ if (
844
+ childEntry.visibility === "hidden" &&
845
+ (!hasChildren || childEntry.state === "close")
846
+ ) {
847
+ continue;
848
+ }
849
+
850
+ const updates = {
851
+ visibility: "hidden",
852
+ };
853
+ if (hasChildren && childEntry.state !== "close") {
854
+ updates.state = "close";
825
855
  }
856
+
857
+ const updatedChild = Object.assign({}, childEntry, updates);
858
+ nextEntries[i] = updatedChild;
859
+ changed = true;
860
+ }
861
+
862
+ if (changed === true) {
863
+ this.setOption("entries", nextEntries);
826
864
  }
827
865
  }
828
866
 
@@ -990,16 +1028,37 @@ function expandEntryByIndex(index) {
990
1028
  index,
991
1029
  });
992
1030
 
993
- this.setOption("entries." + index + ".state", "open");
994
- this.setOption("entries." + index + ".visibility", "visible");
995
-
996
1031
  const entries = this.getOption("entries", []);
1032
+ const nextEntries = [...entries];
1033
+ let changed = false;
1034
+ nextEntries[index] = Object.assign({}, entry, {
1035
+ state: "open",
1036
+ visibility: "visible",
1037
+ });
1038
+ changed = true;
997
1039
  const end = getSubtreeEndIndex(entries, index);
998
1040
  for (let i = index + 1; i < end; i += 1) {
999
- this.setOption("entries." + i + ".visibility", "visible");
1000
- if (entries[i]["has-children"]) {
1001
- this.setOption("entries." + i + ".state", "open");
1041
+ const childEntry = entries[i];
1042
+ const changedVisibility = childEntry.visibility !== "visible";
1043
+ const changedState =
1044
+ childEntry["has-children"] && childEntry.state !== "open";
1045
+
1046
+ if (!changedVisibility && !changedState) {
1047
+ continue;
1002
1048
  }
1049
+
1050
+ const updatedChild = Object.assign({}, childEntry, {
1051
+ visibility: "visible",
1052
+ });
1053
+ if (childEntry["has-children"]) {
1054
+ updatedChild.state = "open";
1055
+ }
1056
+ nextEntries[i] = updatedChild;
1057
+ changed = true;
1058
+ }
1059
+
1060
+ if (changed === true) {
1061
+ this.setOption("entries", nextEntries);
1003
1062
  }
1004
1063
  }
1005
1064
 
@@ -173,6 +173,10 @@ class TreeMenu extends CustomElement {
173
173
  },
174
174
  },
175
175
 
176
+ updater: {
177
+ batchUpdates: true,
178
+ },
179
+
176
180
  data: [],
177
181
  entries: [],
178
182
  });
@@ -387,7 +391,14 @@ function initEventHandler() {
387
391
  doAction.call(this, this.getOption("entries." + index), index);
388
392
  }
389
393
 
390
- this.setOption("entries." + index + ".state", newState);
394
+ const entries = this.getOption("entries", []);
395
+ const nextEntries = [...entries];
396
+ let changed = false;
397
+
398
+ nextEntries[index] = Object.assign({}, currentEntry, {
399
+ state: newState,
400
+ });
401
+ changed = true;
391
402
  const newVisibility = newState === "open" ? "visible" : "hidden";
392
403
 
393
404
  if (container.hasAttribute(ATTRIBUTE_INTEND)) {
@@ -418,16 +429,25 @@ function initEventHandler() {
418
429
  .getAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE)
419
430
  .split("-")
420
431
  .pop();
421
-
422
- this.setOption("entries." + refIndex + ".visibility", newVisibility);
423
-
424
- if (newState === "close") {
425
- this.setOption("entries." + refIndex + ".state", "close");
432
+ const childIndex = Number.parseInt(refIndex);
433
+ const childEntry = entries[childIndex];
434
+ if (!childEntry) {
435
+ ref = ref.nextElementSibling;
436
+ continue;
426
437
  }
427
438
 
439
+ nextEntries[childIndex] = Object.assign({}, childEntry, {
440
+ visibility: newVisibility,
441
+ ...(newState === "close" ? { state: "close" } : {}),
442
+ });
443
+ changed = true;
428
444
  ref = ref.nextElementSibling;
429
445
  }
430
446
  }
447
+
448
+ if (changed === true) {
449
+ this.setOption("entries", nextEntries);
450
+ }
431
451
  };
432
452
 
433
453
  const types = this.getOption("toggleEventType", ["click"]);