@schukai/monster 4.53.0 → 4.54.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,28 @@
2
2
 
3
3
 
4
4
 
5
+ ## [4.54.1] - 2025-12-28
6
+
7
+ ### Bug Fixes
8
+
9
+ - Reorganize imports and improve code structure in CustomElement
10
+
11
+
12
+
13
+ ## [4.54.0] - 2025-12-28
14
+
15
+ ### Add Features
16
+
17
+ - Update project references and add issue handling for button bar
18
+ ### Bug Fixes
19
+
20
+ - revert to jsdom 26.1
21
+ ### Changes
22
+
23
+ - close issue [#347](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/347)
24
+
25
+
26
+
5
27
  ## [4.53.0] - 2025-12-28
6
28
 
7
29
  ### Add Features
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.53.0"}
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.54.1"}
@@ -112,6 +112,12 @@ const popperSwitchEventHandler = Symbol("popperSwitchEventHandler");
112
112
  */
113
113
  const popperNavElementSymbol = Symbol("popperNavElement");
114
114
 
115
+ /**
116
+ * @private
117
+ * @type {symbol}
118
+ */
119
+ const mutationObserverSymbol = Symbol("mutationObserver");
120
+
115
121
  /**
116
122
  * @private
117
123
  * @type {symbol}
@@ -257,6 +263,9 @@ class ButtonBar extends CustomElement {
257
263
  }
258
264
 
259
265
  disconnectResizeObserver.call(this);
266
+ if (this[mutationObserverSymbol]) {
267
+ this[mutationObserverSymbol].disconnect();
268
+ }
260
269
  }
261
270
 
262
271
  /**
@@ -318,67 +327,82 @@ function isElementTrulyVisible(element) {
318
327
  */
319
328
  function initEventHandler() {
320
329
  const self = this;
330
+
331
+ const triggerRecalculation = () => {
332
+ if (self[timerCallbackSymbol] instanceof DeadMansSwitch) {
333
+ try {
334
+ self[timerCallbackSymbol].touch();
335
+ return;
336
+ } catch (e) {
337
+ if (e.message !== "has already run") throw e;
338
+ delete self[timerCallbackSymbol];
339
+ }
340
+ }
341
+
342
+ self[timerCallbackSymbol] = new DeadMansSwitch(50, () => {
343
+ requestAnimationFrame(() => {
344
+ updatePopper.call(self);
345
+ self[dimensionsSymbol].setVia("data.calculated", false);
346
+ try {
347
+ checkAndRearrangeButtons.call(self);
348
+ } catch (error) {
349
+ addErrorAttribute(
350
+ self,
351
+ error?.message || "An error occurred while rearranging the buttons",
352
+ );
353
+ }
354
+ });
355
+ });
356
+ };
357
+
358
+ const mutationCallback = (mutationList) => {
359
+ let needsRecalc = false;
360
+ for (const mutation of mutationList) {
361
+ if (mutation.type === "attributes") {
362
+ const target = mutation.target;
363
+ if (target instanceof HTMLElement) {
364
+ const ref = target.getAttribute("data-monster-reference");
365
+ if (ref && !isElementTrulyVisible(target)) {
366
+ self[dimensionsSymbol].setVia(`data.button.${ref}`, 0);
367
+ needsRecalc = true;
368
+ }
369
+ }
370
+ }
371
+ }
372
+ if (needsRecalc) {
373
+ triggerRecalculation();
374
+ }
375
+ };
376
+
321
377
  /**
322
378
  * @param {Event} event
323
379
  */
324
380
  self[closeEventHandler] = (event) => {
325
381
  const path = event.composedPath();
326
-
327
- for (const [, element] of Object.entries(path)) {
328
- if (element === self) {
329
- return;
330
- }
382
+ for (const element of path) {
383
+ if (element === self) return;
331
384
  }
332
-
333
385
  hide.call(self);
334
386
  };
335
387
 
336
388
  if (self[buttonBarSlotElementSymbol]) {
337
- self[buttonBarSlotElementSymbol].addEventListener("slotchange", (event) => {
389
+ self[buttonBarSlotElementSymbol].addEventListener("slotchange", () => {
338
390
  checkAndRearrangeButtons.call(self);
339
391
  });
340
392
  }
341
393
 
342
394
  if (self[popperElementSymbol]) {
343
- self[popperElementSymbol].addEventListener("slotchange", (event) => {
395
+ self[popperElementSymbol].addEventListener("slotchange", () => {
344
396
  checkAndRearrangeButtons.call(self);
345
397
  });
346
398
  }
347
399
 
348
- // data-monster-options
349
400
  self[attributeObserverSymbol][ATTRIBUTE_POPPER_POSITION] = function (value) {
350
401
  self.setOption("classes.button", value);
351
402
  };
352
403
 
353
- self[resizeObserverSymbol] = new ResizeObserver((entries) => {
354
- if (self[timerCallbackSymbol] instanceof DeadMansSwitch) {
355
- try {
356
- self[timerCallbackSymbol].touch();
357
- return;
358
- } catch (e) {
359
- // catch Error("has already run");
360
- if (e.message !== "has already run") {
361
- throw e;
362
- }
363
- delete self[timerCallbackSymbol];
364
- }
365
- }
366
-
367
- self[timerCallbackSymbol] = new DeadMansSwitch(200, () => {
368
- requestAnimationFrame(() => {
369
- updatePopper.call(self);
370
- self[dimensionsSymbol].setVia("data.calculated", false);
371
- try {
372
- checkAndRearrangeButtons.call(self);
373
- } catch (error) {
374
- addErrorAttribute(
375
- this,
376
- error?.message || "An error occurred while rearranging the buttons",
377
- );
378
- }
379
- });
380
- });
381
- });
404
+ self[resizeObserverSymbol] = new ResizeObserver(triggerRecalculation);
405
+ self[mutationObserverSymbol] = new MutationObserver(mutationCallback);
382
406
 
383
407
  initSlotChangedHandler.call(self);
384
408
  }
@@ -440,17 +464,39 @@ function rearrangeButtons() {
440
464
  continue;
441
465
  }
442
466
 
443
- if (isElementTrulyVisible(element)) {
444
- const buttonWidth = this[dimensionsSymbol].getVia(`data.button.${ref}`);
445
- if (sum + buttonWidth > space) {
467
+ let buttonWidth = 0;
468
+ try {
469
+ buttonWidth = this[dimensionsSymbol].getVia(`data.button.${ref}`);
470
+ } catch (e) {
471
+ // If the path does not exist, pathfinder throws an error.
472
+ // In this case, we assume the width is 0.
473
+ // This can happen for buttons that have never been visible.
474
+ }
475
+ const style = getComputedStyle(element);
476
+
477
+ // A user-hidden button should not participate in layout calculations.
478
+ // We will assign it to the popper to keep it out of the flow.
479
+ if (style.display === "none") {
480
+ // This button is not visible. It could be user-hidden, or it could be
481
+ // in the popper which is currently hidden.
482
+ // We should only count it towards the popper if it has a non-zero width,
483
+ // which implies it's a genuinely overflowing button, not one that is
484
+ // user-hidden from the start.
485
+ if (buttonWidth > 0) {
446
486
  buttonsToMoveToPopper.push(element);
447
487
  } else {
448
- sum += buttonWidth;
488
+ // It has 0 width. Treat it as a truly hidden element.
489
+ // Put it in the main slot, where it will be invisible and take no space.
449
490
  visibleButtonsInMainSlot.push(element);
450
491
  }
492
+ continue;
493
+ }
494
+
495
+ const switchWidth = this[dimensionsSymbol].getVia("data.switchWidth") || 2;
496
+ if (sum + buttonWidth > space - switchWidth) { buttonsToMoveToPopper.push(element);
451
497
  } else {
452
- // If a button is not truly visible, force it into the popper slot
453
- buttonsToMoveToPopper.push(element);
498
+ sum += buttonWidth;
499
+ visibleButtonsInMainSlot.push(element);
454
500
  }
455
501
  }
456
502
 
@@ -462,11 +508,9 @@ function rearrangeButtons() {
462
508
  button.removeAttribute("slot");
463
509
  }
464
510
 
465
- const trulyVisibleButtonsInPopper = Array.from(
466
- getSlottedElements.call(this, ":scope", "popper"),
467
- ).filter(isElementTrulyVisible);
511
+ const buttonsInPopper = getSlottedElements.call(this, ":scope", "popper");
468
512
 
469
- if (trulyVisibleButtonsInPopper.length > 0) {
513
+ if (buttonsInPopper.size > 0) {
470
514
  this[switchElementSymbol].classList.remove("hidden");
471
515
  } else {
472
516
  this[switchElementSymbol].classList.add("hidden");
@@ -576,9 +620,11 @@ function calculateButtonBarDimensions() {
576
620
  const ref = button.getAttribute("data-monster-reference");
577
621
  if (ref === null) continue;
578
622
 
579
- // Only consider truly visible buttons for width calculation
623
+ buttonReferences.push(ref);
624
+
625
+ // Only calculate width for visible buttons. Assume invisible ones
626
+ // (e.g. in popper) have their width calculated previously and stored.
580
627
  if (isElementTrulyVisible(button)) {
581
- buttonReferences.push(ref);
582
628
  this[dimensionsSymbol].setVia(
583
629
  `data.button.${ref}`,
584
630
  calcBoxWidth.call(this, button),
@@ -586,6 +632,13 @@ function calculateButtonBarDimensions() {
586
632
  }
587
633
  }
588
634
 
635
+ if (this[switchElementSymbol]) {
636
+ this[dimensionsSymbol].setVia(
637
+ "data.switchWidth",
638
+ this[switchElementSymbol].offsetWidth,
639
+ );
640
+ }
641
+
589
642
  this[dimensionsSymbol].setVia("data.calculated", true);
590
643
  this[dimensionsSymbol].setVia("data.buttonReferences", buttonReferences);
591
644
  }
@@ -595,10 +648,19 @@ function calculateButtonBarDimensions() {
595
648
  */
596
649
  function updateResizeObserverObservation() {
597
650
  this[resizeObserverSymbol].disconnect();
651
+ if (this[mutationObserverSymbol]) {
652
+ this[mutationObserverSymbol].disconnect();
653
+ }
598
654
 
599
655
  const slottedNodes = getSlottedElements.call(this);
600
656
  slottedNodes.forEach((node) => {
601
657
  this[resizeObserverSymbol].observe(node);
658
+ if (this[mutationObserverSymbol]) {
659
+ this[mutationObserverSymbol].observe(node, {
660
+ attributes: true,
661
+ attributeFilter: ["style", "class"],
662
+ });
663
+ }
602
664
  });
603
665
 
604
666
  requestAnimationFrame(() => {