@schukai/monster 4.43.5 → 4.43.7

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,26 @@
2
2
 
3
3
 
4
4
 
5
+ ## [4.43.7] - 2025-10-11
6
+
7
+ ### Bug Fixes
8
+
9
+ - position submenu
10
+ ### Changes
11
+
12
+ - close issue [#329](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/329)
13
+ - update webtests
14
+
15
+
16
+
17
+ ## [4.43.6] - 2025-10-11
18
+
19
+ ### Bug Fixes
20
+
21
+ - update the workflow navigation [#329](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/329)
22
+
23
+
24
+
5
25
  ## [4.43.5] - 2025-10-10
6
26
 
7
27
  ### Bug Fixes
package/package.json CHANGED
@@ -1 +1 @@
1
- {"author":"schukai GmbH","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.43.5"}
1
+ {"author":"schukai GmbH","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.43.7"}
@@ -14,8 +14,8 @@ import { instanceSymbol } from "../../constants.mjs";
14
14
  import { ATTRIBUTE_ROLE } from "../../dom/constants.mjs";
15
15
  import { CustomControl } from "../../dom/customcontrol.mjs";
16
16
  import {
17
- assembleMethodSymbol,
18
- registerCustomElement,
17
+ assembleMethodSymbol,
18
+ registerCustomElement,
19
19
  } from "../../dom/customelement.mjs";
20
20
  import { PasswordStyleSheet } from "./stylesheet/password.mjs";
21
21
  import { fireCustomEvent } from "../../dom/events.mjs";
@@ -61,107 +61,107 @@ export const inputElementSymbol = Symbol("inputIconElement");
61
61
  * @fires monster-password-show
62
62
  */
63
63
  class Password extends CustomControl {
64
- /**
65
- * This method is called by the `instanceof` operator.
66
- * @returns {symbol}
67
- */
68
- static get [instanceSymbol]() {
69
- return Symbol.for("@schukai/monster/components/form/password@@instance");
70
- }
64
+ /**
65
+ * This method is called by the `instanceof` operator.
66
+ * @returns {symbol}
67
+ */
68
+ static get [instanceSymbol]() {
69
+ return Symbol.for("@schukai/monster/components/form/password@@instance");
70
+ }
71
71
 
72
- /**
73
- *
74
- * @return {Components.Form.Password
75
- */
76
- [assembleMethodSymbol]() {
77
- super[assembleMethodSymbol]();
78
- initControlReferences.call(this);
79
- initEventHandler.call(this);
80
- initPasswordState.call(this);
81
- return this;
82
- }
72
+ /**
73
+ *
74
+ * @return {Components.Form.Password
75
+ */
76
+ [assembleMethodSymbol]() {
77
+ super[assembleMethodSymbol]();
78
+ initControlReferences.call(this);
79
+ initEventHandler.call(this);
80
+ initPasswordState.call(this);
81
+ return this;
82
+ }
83
83
 
84
- /**
85
- * The current value of the Switch
86
- * @return {string}
87
- */
88
- get value() {
89
- return this.getOption("value");
90
- }
84
+ /**
85
+ * The current value of the Switch
86
+ * @return {string}
87
+ */
88
+ get value() {
89
+ return this.getOption("value");
90
+ }
91
91
 
92
- /**
93
- * Set value
94
- * @property {string} value
95
- */
96
- set value(value) {
97
- this.setOption("value", value);
98
- this[inputElementSymbol].value = value;
99
- this.setFormValue(value);
100
- }
92
+ /**
93
+ * Set value
94
+ * @property {string} value
95
+ */
96
+ set value(value) {
97
+ this.setOption("value", value);
98
+ this[inputElementSymbol].value = value;
99
+ this.setFormValue(value);
100
+ }
101
101
 
102
- /**
103
- * To set the options via the HTML Tag, the attribute `data-monster-options` must be used.
104
- * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
105
- *
106
- * The individual configuration values can be found in the table.
107
- *
108
- * @property {Object} templates Template definitions
109
- * @property {string} templates.main Main template
110
- * @property {string} templateMapping.hidden-icon The hidden icon template (without svg tag)
111
- * @property {string} templateMapping.visible-icon The visible icon template (without svg tag)
112
- * @property {string} type="password" The type of the input field
113
- * @property {string} placeholder="" The placeholder of the input field
114
- * @property {boolean} required=false The required state of the input field
115
- * @property {string} autocomplete="off" The autocomplete state of the input field
116
- * @property {string} inputmode="text" The inputmode state of the input field
117
- * @property {Object} aria Aria attributes
118
- * @property {boolean} aria.required=false The required state of the input field
119
- * @property {string} aria.placeholder="" The placeholder of the input field
120
- * @property {boolean} disabled=false Disabled state
121
- */
122
- get defaults() {
123
- return Object.assign({}, super.defaults, {
124
- templates: {
125
- main: getTemplate(),
126
- },
127
- templateMapping: {
128
- "hidden-icon": `
102
+ /**
103
+ * To set the options via the HTML Tag, the attribute `data-monster-options` must be used.
104
+ * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
105
+ *
106
+ * The individual configuration values can be found in the table.
107
+ *
108
+ * @property {Object} templates Template definitions
109
+ * @property {string} templates.main Main template
110
+ * @property {string} templateMapping.hidden-icon The hidden icon template (without svg tag)
111
+ * @property {string} templateMapping.visible-icon The visible icon template (without svg tag)
112
+ * @property {string} type="password" The type of the input field
113
+ * @property {string} placeholder="" The placeholder of the input field
114
+ * @property {boolean} required=false The required state of the input field
115
+ * @property {string} autocomplete="off" The autocomplete state of the input field
116
+ * @property {string} inputmode="text" The inputmode state of the input field
117
+ * @property {Object} aria Aria attributes
118
+ * @property {boolean} aria.required=false The required state of the input field
119
+ * @property {string} aria.placeholder="" The placeholder of the input field
120
+ * @property {boolean} disabled=false Disabled state
121
+ */
122
+ get defaults() {
123
+ return Object.assign({}, super.defaults, {
124
+ templates: {
125
+ main: getTemplate(),
126
+ },
127
+ templateMapping: {
128
+ "hidden-icon": `
129
129
  <path d="M13.359 11.238C15.06 9.72 16 8 16 8s-3-5.5-8-5.5a7 7 0 0 0-2.79.588l.77.771A6 6 0 0 1 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13 13 0 0 1 14.828 8q-.086.13-.195.288c-.335.48-.83 1.12-1.465 1.755q-.247.248-.517.486z"/>
130
130
  <path d="M11.297 9.176a3.5 3.5 0 0 0-4.474-4.474l.823.823a2.5 2.5 0 0 1 2.829 2.829zm-2.943 1.299.822.822a3.5 3.5 0 0 1-4.474-4.474l.823.823a2.5 2.5 0 0 0 2.829 2.829"/>
131
131
  <path d="M3.35 5.47q-.27.24-.518.487A13 13 0 0 0 1.172 8l.195.288c.335.48.83 1.12 1.465 1.755C4.121 11.332 5.881 12.5 8 12.5c.716 0 1.39-.133 2.02-.36l.77.772A7 7 0 0 1 8 13.5C3 13.5 0 8 0 8s.939-1.721 2.641-3.238l.708.709zm10.296 8.884-12-12 .708-.708 12 12z"/>`,
132
- "visible-icon": `
132
+ "visible-icon": `
133
133
  <path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8M1.173 8a13 13 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5s3.879 1.168 5.168 2.457A13 13 0 0 1 14.828 8q-.086.13-.195.288c-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5s-3.879-1.168-5.168-2.457A13 13 0 0 1 1.172 8z"/>
134
134
  <path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5M4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0"/>`,
135
- },
136
- type: "password",
137
- placeholder: "",
138
- required: false,
139
- autocomplete: "off",
140
- inputmode: "text",
141
- aria: {
142
- required: false,
143
- placeholder: "",
144
- },
145
- disabled: false,
146
- value: null,
135
+ },
136
+ type: "password",
137
+ placeholder: "",
138
+ required: false,
139
+ autocomplete: "off",
140
+ inputmode: "text",
141
+ aria: {
142
+ required: false,
143
+ placeholder: "",
144
+ },
145
+ disabled: false,
146
+ value: null,
147
147
 
148
- eventProcessing: true,
149
- });
150
- }
148
+ eventProcessing: true,
149
+ });
150
+ }
151
151
 
152
- /**
153
- * @return {string}
154
- */
155
- static getTag() {
156
- return "monster-password";
157
- }
152
+ /**
153
+ * @return {string}
154
+ */
155
+ static getTag() {
156
+ return "monster-password";
157
+ }
158
158
 
159
- /**
160
- * @return {CSSStyleSheet[]}
161
- */
162
- static getCSSStyleSheet() {
163
- return [PasswordStyleSheet];
164
- }
159
+ /**
160
+ * @return {CSSStyleSheet[]}
161
+ */
162
+ static getCSSStyleSheet() {
163
+ return [PasswordStyleSheet];
164
+ }
165
165
  }
166
166
 
167
167
  /**
@@ -171,54 +171,54 @@ class Password extends CustomControl {
171
171
  * @fires monster-password-show
172
172
  */
173
173
  function initEventHandler() {
174
- const self = this;
175
- const element = this[passwordElementSymbol];
174
+ const self = this;
175
+ const element = this[passwordElementSymbol];
176
176
 
177
- const type = "click";
177
+ const type = "click";
178
178
 
179
- this[hiddenIconElementSymbol].addEventListener(type, function (event) {
180
- fireCustomEvent(self, "monster-password-show", {
181
- element: self,
182
- });
179
+ this[hiddenIconElementSymbol].addEventListener(type, function (event) {
180
+ fireCustomEvent(self, "monster-password-show", {
181
+ element: self,
182
+ });
183
183
 
184
- requestAnimationFrame(() => {
185
- self[visibleIconElementSymbol].classList.remove("hidden");
186
- self[hiddenIconElementSymbol].classList.add("hidden");
187
- self.setOption("type", "text");
188
- });
189
- });
184
+ requestAnimationFrame(() => {
185
+ self[visibleIconElementSymbol].classList.remove("hidden");
186
+ self[hiddenIconElementSymbol].classList.add("hidden");
187
+ self.setOption("type", "text");
188
+ });
189
+ });
190
190
 
191
- this[visibleIconElementSymbol].addEventListener(type, function (event) {
192
- fireCustomEvent(self, "monster-password-hide", {
193
- element: self,
194
- });
191
+ this[visibleIconElementSymbol].addEventListener(type, function (event) {
192
+ fireCustomEvent(self, "monster-password-hide", {
193
+ element: self,
194
+ });
195
195
 
196
- requestAnimationFrame(() => {
197
- self[visibleIconElementSymbol].classList.add("hidden");
198
- self[hiddenIconElementSymbol].classList.remove("hidden");
199
- self.setOption("type", "password");
200
- });
201
- });
196
+ requestAnimationFrame(() => {
197
+ self[visibleIconElementSymbol].classList.add("hidden");
198
+ self[hiddenIconElementSymbol].classList.remove("hidden");
199
+ self.setOption("type", "password");
200
+ });
201
+ });
202
202
 
203
- this[inputElementSymbol].addEventListener("input", function (event) {
204
- self.setFormValue(self.value);
205
- self.setOption("value", this.value);
206
- });
203
+ this[inputElementSymbol].addEventListener("input", function (event) {
204
+ self.setFormValue(self.value);
205
+ self.setOption("value", this.value);
206
+ });
207
207
 
208
- return this;
208
+ return this;
209
209
  }
210
210
 
211
211
  /**
212
212
  * @private
213
213
  */
214
214
  function initPasswordState() {
215
- if (this.getOption("type") === "password") {
216
- this[visibleIconElementSymbol].classList.add("hidden");
217
- this[hiddenIconElementSymbol].classList.remove("hidden");
218
- } else {
219
- this[visibleIconElementSymbol].classList.remove("hidden");
220
- this[hiddenIconElementSymbol].classList.add("hidden");
221
- }
215
+ if (this.getOption("type") === "password") {
216
+ this[visibleIconElementSymbol].classList.add("hidden");
217
+ this[hiddenIconElementSymbol].classList.remove("hidden");
218
+ } else {
219
+ this[visibleIconElementSymbol].classList.remove("hidden");
220
+ this[hiddenIconElementSymbol].classList.add("hidden");
221
+ }
222
222
  }
223
223
 
224
224
  /**
@@ -226,21 +226,21 @@ function initPasswordState() {
226
226
  * @return {void}
227
227
  */
228
228
  function initControlReferences() {
229
- this[passwordElementSymbol] = this.shadowRoot.querySelector(
230
- `[${ATTRIBUTE_ROLE}="control"]`,
231
- );
229
+ this[passwordElementSymbol] = this.shadowRoot.querySelector(
230
+ `[${ATTRIBUTE_ROLE}="control"]`,
231
+ );
232
232
 
233
- this[visibleIconElementSymbol] = this.shadowRoot.querySelector(
234
- `[${ATTRIBUTE_ROLE}="visible-icon"]`,
235
- );
233
+ this[visibleIconElementSymbol] = this.shadowRoot.querySelector(
234
+ `[${ATTRIBUTE_ROLE}="visible-icon"]`,
235
+ );
236
236
 
237
- this[hiddenIconElementSymbol] = this.shadowRoot.querySelector(
238
- `[${ATTRIBUTE_ROLE}="hidden-icon"]`,
239
- );
237
+ this[hiddenIconElementSymbol] = this.shadowRoot.querySelector(
238
+ `[${ATTRIBUTE_ROLE}="hidden-icon"]`,
239
+ );
240
240
 
241
- this[inputElementSymbol] = this.shadowRoot.querySelector(
242
- `[${ATTRIBUTE_ROLE}="input"]`,
243
- );
241
+ this[inputElementSymbol] = this.shadowRoot.querySelector(
242
+ `[${ATTRIBUTE_ROLE}="input"]`,
243
+ );
244
244
  }
245
245
 
246
246
  /**
@@ -248,8 +248,8 @@ function initControlReferences() {
248
248
  * @return {string}
249
249
  */
250
250
  function getTemplate() {
251
- // language=HTML
252
- return `
251
+ // language=HTML
252
+ return `
253
253
  <div data-monster-role="control" part="control">
254
254
  <monster-input-group part="input-group">
255
255
  <input data-monster-role="input"
@@ -204,7 +204,6 @@ function initEventHandler() {
204
204
  if (window.innerWidth > 768) {
205
205
  computePosition(hamburgerButton, hamburgerNav, {
206
206
  placement: "bottom-end",
207
- strategy: "fixed",
208
207
  middleware: [
209
208
  offset(8),
210
209
  flip(),
@@ -313,9 +312,14 @@ function setupSubmenu(parentLi, context = "visible", level = 1) {
313
312
  const component = this;
314
313
  let cleanup;
315
314
  let hideTimeout;
315
+ let isHovering = false;
316
316
 
317
317
  const immediateHide = () => {
318
318
  submenu.style.display = "none";
319
+ Object.assign(submenu.style, {
320
+ maxHeight: "",
321
+ overflowY: "",
322
+ });
319
323
  submenu
320
324
  .querySelectorAll(
321
325
  "ul[style*='display: block'], div[part='mega-menu'][style*='display: block']",
@@ -323,19 +327,16 @@ function setupSubmenu(parentLi, context = "visible", level = 1) {
323
327
  .forEach((sub) => {
324
328
  sub.style.display = "none";
325
329
  });
326
-
327
330
  fireCustomEvent(this, "monster-submenu-hide", {
328
331
  context,
329
332
  trigger: parentLi,
330
333
  submenu,
331
334
  level,
332
335
  });
333
-
334
336
  if (cleanup) {
335
337
  cleanup();
336
338
  cleanup = null;
337
339
  }
338
-
339
340
  if (
340
341
  level === 1 &&
341
342
  component[activeSubmenuHiderSymbol] === immediateHide
@@ -346,8 +347,6 @@ function setupSubmenu(parentLi, context = "visible", level = 1) {
346
347
 
347
348
  const show = () => {
348
349
  component[hideHamburgerMenuSymbol]?.();
349
- clearTimeout(hideTimeout);
350
-
351
350
  if (level === 1) {
352
351
  if (
353
352
  component[activeSubmenuHiderSymbol] &&
@@ -368,7 +367,6 @@ function setupSubmenu(parentLi, context = "visible", level = 1) {
368
367
  }
369
368
  });
370
369
  }
371
-
372
370
  submenu.style.display = "block";
373
371
  fireCustomEvent(this, "monster-submenu-show", {
374
372
  context,
@@ -376,16 +374,14 @@ function setupSubmenu(parentLi, context = "visible", level = 1) {
376
374
  submenu,
377
375
  level,
378
376
  });
379
-
380
- if (level === 1 && !cleanup) {
377
+ if (!cleanup) {
381
378
  cleanup = autoUpdate(parentLi, submenu, () => {
382
- computePosition(parentLi, submenu, {
383
- placement: "bottom-start",
384
- strategy: "fixed",
385
- middleware: [
386
- offset(8),
387
- flip({ fallbackPlacements: ["top-start"] }),
388
- shift({ padding: 8 }),
379
+ const middleware = [offset(8), flip(), shift({ padding: 8 })];
380
+ const containsSubmenus = submenu.querySelector(
381
+ "ul, div[part='mega-menu']",
382
+ );
383
+ if (!containsSubmenus) {
384
+ middleware.push(
389
385
  size({
390
386
  apply: ({ availableHeight, elements }) => {
391
387
  Object.assign(elements.floating.style, {
@@ -395,7 +391,11 @@ function setupSubmenu(parentLi, context = "visible", level = 1) {
395
391
  },
396
392
  padding: 8,
397
393
  }),
398
- ],
394
+ );
395
+ }
396
+ computePosition(parentLi, submenu, {
397
+ placement: level === 1 ? "bottom-start" : "right-start",
398
+ middleware: middleware,
399
399
  }).then(({ x, y, strategy }) => {
400
400
  Object.assign(submenu.style, {
401
401
  position: strategy,
@@ -407,22 +407,33 @@ function setupSubmenu(parentLi, context = "visible", level = 1) {
407
407
  }
408
408
  };
409
409
 
410
- const hideWithDelay = () => {
411
- hideTimeout = setTimeout(immediateHide, 200);
410
+ const handleMouseEnter = () => {
411
+ isHovering = true; // Status auf "aktiv" setzen
412
+ clearTimeout(hideTimeout);
413
+ if (submenu.style.display !== "block") {
414
+ show();
415
+ }
412
416
  };
413
417
 
414
- parentLi.addEventListener("mouseenter", show);
415
- parentLi.addEventListener("mouseleave", hideWithDelay);
416
- submenu.addEventListener("mouseenter", () => clearTimeout(hideTimeout));
417
- submenu.addEventListener("mouseleave", hideWithDelay);
418
+ const handleMouseLeave = () => {
419
+ isHovering = false; // Status auf "inaktiv" setzen
420
+ hideTimeout = setTimeout(() => {
421
+ if (!isHovering) {
422
+ immediateHide();
423
+ }
424
+ }, 250);
425
+ };
426
+
427
+ parentLi.addEventListener("mouseenter", handleMouseEnter);
428
+ parentLi.addEventListener("mouseleave", handleMouseLeave);
429
+ submenu.addEventListener("mouseenter", handleMouseEnter);
430
+ submenu.addEventListener("mouseleave", handleMouseLeave);
418
431
  } else {
419
- // Click behavior
420
432
  const anchor = parentLi.querySelector(":scope > a");
421
433
  if (anchor) {
422
434
  anchor.addEventListener("click", (event) => {
423
435
  event.preventDefault();
424
436
  event.stopPropagation();
425
-
426
437
  if (!submenu.classList.contains("is-open")) {
427
438
  [...parentLi.parentElement.children]
428
439
  .filter((li) => li !== parentLi)
@@ -435,7 +446,6 @@ function setupSubmenu(parentLi, context = "visible", level = 1) {
435
446
  }
436
447
  });
437
448
  }
438
-
439
449
  const isOpen = submenu.classList.toggle("is-open");
440
450
  const eventName = isOpen
441
451
  ? "monster-submenu-show"
@@ -450,7 +460,6 @@ function setupSubmenu(parentLi, context = "visible", level = 1) {
450
460
  }
451
461
  }
452
462
 
453
- // Recursive call for nested lists
454
463
  if (submenu.tagName === "UL") {
455
464
  submenu
456
465
  .querySelectorAll(":scope > li")
@@ -174,22 +174,6 @@
174
174
  background-color: var(--monster-color-primary-2);
175
175
  }
176
176
 
177
- [part="visible-list"] [part="submenu"] [part="submenu"] {
178
- position: relative;
179
- top: auto;
180
- left: auto;
181
- box-shadow: none;
182
- border: none;
183
- border-top: 1px solid var(--monster-color-primary-2);
184
- min-width: unset;
185
- margin-top: var(--monster-space-3);
186
- background-color: transparent;
187
- padding-top: var(--monster-space-3);
188
- padding-bottom: 0;
189
- padding-right: 0;
190
- padding-left: var(--monster-space-3);
191
- }
192
-
193
177
  [part="mega-menu"] {
194
178
  padding: var(--monster-space-4);
195
179
  min-width: 400px;