@schukai/monster 4.140.3 → 4.141.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/package.json CHANGED
@@ -1 +1 @@
1
- {"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"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.140.3"}
1
+ {"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"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.141.0"}
@@ -179,6 +179,13 @@ const ATTRIBUTE_OPTION_LAYOUT_ALIGNMENT =
179
179
  const ATTRIBUTE_OPTION_LAYOUT_STACKED_ALIGNMENT =
180
180
  "data-monster-option-layout-stacked-alignment";
181
181
 
182
+ /**
183
+ * @private
184
+ * @type {string}
185
+ */
186
+ const ATTRIBUTE_OPTION_LAYOUT_STACKED_BREAKPOINT =
187
+ "data-monster-option-layout-stacked-breakpoint";
188
+
182
189
  /**
183
190
  * @private
184
191
  * @type {string}
@@ -233,6 +240,7 @@ class ControlBar extends CustomElement {
233
240
  layout: {
234
241
  alignment: "left",
235
242
  stackedAlignment: undefined,
243
+ stackedBreakpoint: undefined,
236
244
  },
237
245
  popper: {
238
246
  placement: "left",
@@ -287,7 +295,11 @@ class ControlBar extends CustomElement {
287
295
  */
288
296
  setOption(path, value) {
289
297
  super.setOption(path, value);
290
- if (path === "layout.alignment" || path === "layout.stackedAlignment") {
298
+ if (
299
+ path === "layout.alignment" ||
300
+ path === "layout.stackedAlignment" ||
301
+ path === "layout.stackedBreakpoint"
302
+ ) {
291
303
  syncLayoutState.call(this);
292
304
  }
293
305
  return this;
@@ -339,6 +351,7 @@ class ControlBar extends CustomElement {
339
351
  attributes.push(ATTRIBUTE_POPPER_POSITION);
340
352
  attributes.push(ATTRIBUTE_OPTION_LAYOUT_ALIGNMENT);
341
353
  attributes.push(ATTRIBUTE_OPTION_LAYOUT_STACKED_ALIGNMENT);
354
+ attributes.push(ATTRIBUTE_OPTION_LAYOUT_STACKED_BREAKPOINT);
342
355
  return attributes;
343
356
  }
344
357
 
@@ -531,6 +544,11 @@ function initEventHandler() {
531
544
  syncLayoutState.call(self);
532
545
  };
533
546
 
547
+ self[attributeObserverSymbol][ATTRIBUTE_OPTION_LAYOUT_STACKED_BREAKPOINT] =
548
+ () => {
549
+ syncLayoutState.call(self);
550
+ };
551
+
534
552
  self[resizeObserverSymbol] = new ResizeObserver(() => {
535
553
  scheduleLayout.call(self, { measure: true, layout: true });
536
554
  });
@@ -743,9 +761,11 @@ function rearrangeItems() {
743
761
 
744
762
  const shouldShowSwitch =
745
763
  layout.itemsToMoveToPopper.length > 0 && hasItems;
764
+ const shouldUseStackedLayout =
765
+ shouldShowSwitch || isStackedBreakpointMatched.call(this);
746
766
 
747
767
  suppressLayoutFeedback.call(this);
748
- setLayoutStackedState.call(this, shouldShowSwitch);
768
+ setLayoutStackedState.call(this, shouldUseStackedLayout);
749
769
 
750
770
  for (const item of layout.itemsToMoveToPopper) {
751
771
  if (item.getAttribute("slot") !== "popper") {
@@ -1207,6 +1227,7 @@ function calculateControlBarDimensions() {
1207
1227
  }
1208
1228
 
1209
1229
  this[dimensionsSymbol].setVia("data.visible", !(width === 0));
1230
+ this[dimensionsSymbol].setVia("data.containerWidth", width);
1210
1231
 
1211
1232
  const itemReferences = [];
1212
1233
 
@@ -1509,6 +1530,37 @@ function syncLayoutState() {
1509
1530
  scheduleLayout.call(this, { measure: true, layout: true });
1510
1531
  }
1511
1532
 
1533
+ /**
1534
+ * @private
1535
+ * @return {boolean}
1536
+ */
1537
+ function isStackedBreakpointMatched() {
1538
+ const breakpoint = this.getOption("layout.stackedBreakpoint");
1539
+ if (typeof breakpoint !== "string" || breakpoint.trim() === "") {
1540
+ return false;
1541
+ }
1542
+
1543
+ let width = 0;
1544
+ try {
1545
+ width = this[dimensionsSymbol].getVia("data.containerWidth");
1546
+ } catch {
1547
+ try {
1548
+ width = this[dimensionsSymbol].getVia("data.space");
1549
+ } catch {}
1550
+ }
1551
+
1552
+ if (!(width > 0)) {
1553
+ return false;
1554
+ }
1555
+
1556
+ const breakpointWidth = getComputedCssPixels(breakpoint);
1557
+ if (!(breakpointWidth > 0)) {
1558
+ return false;
1559
+ }
1560
+
1561
+ return width <= breakpointWidth;
1562
+ }
1563
+
1512
1564
  /**
1513
1565
  * @private
1514
1566
  * @param {boolean} stacked
@@ -1618,6 +1670,7 @@ function getLayoutChangedEventDetail() {
1618
1670
  configuredAlignment: this.getOption("layout.alignment", "left"),
1619
1671
  stacked,
1620
1672
  stackedAlignment: this.getOption("layout.stackedAlignment"),
1673
+ stackedBreakpoint: this.getOption("layout.stackedBreakpoint"),
1621
1674
  };
1622
1675
  }
1623
1676
 
@@ -161,6 +161,7 @@ describe("ControlBar", function () {
161
161
  configuredAlignment: "left",
162
162
  stacked: false,
163
163
  stackedAlignment: undefined,
164
+ stackedBreakpoint: undefined,
164
165
  });
165
166
  });
166
167
 
@@ -660,4 +661,94 @@ describe("ControlBar", function () {
660
661
  globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
661
662
  }
662
663
  });
664
+
665
+ it("should apply stacked alignment below the configured container breakpoint without overflow", async function () {
666
+ const originalRequestAnimationFrame = window.requestAnimationFrame;
667
+ const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
668
+
669
+ const scheduledCallbacks = [];
670
+ const flushFrames = async () => {
671
+ while (scheduledCallbacks.length > 0) {
672
+ scheduledCallbacks.shift()();
673
+ await new Promise((resolve) => setTimeout(resolve, 0));
674
+ }
675
+ };
676
+
677
+ try {
678
+ window.requestAnimationFrame = (callback) => {
679
+ scheduledCallbacks.push(callback);
680
+ return scheduledCallbacks.length;
681
+ };
682
+ globalThis.requestAnimationFrame = window.requestAnimationFrame;
683
+
684
+ const mocks = document.getElementById("mocks");
685
+ mocks.innerHTML = `
686
+ <div id="breakpoint-bar-wrapper">
687
+ <monster-control-bar
688
+ id="breakpoint-bar"
689
+ data-monster-option-layout-alignment="right"
690
+ data-monster-option-layout-stacked-alignment="left"
691
+ data-monster-option-layout-stacked-breakpoint="100px"
692
+ >
693
+ <button id="breakpoint-button">Run</button>
694
+ </monster-control-bar>
695
+ </div>
696
+ `;
697
+
698
+ const wrapper = document.getElementById("breakpoint-bar-wrapper");
699
+ const button = document.getElementById("breakpoint-button");
700
+ const bar = document.getElementById("breakpoint-bar");
701
+ const controlBar = bar.shadowRoot.querySelector(
702
+ '[data-monster-role="control-bar"]',
703
+ );
704
+ const switchButton = bar.shadowRoot.querySelector(
705
+ '[data-monster-role="switch"]',
706
+ );
707
+
708
+ wrapper.style.boxSizing = "border-box";
709
+ Object.defineProperty(wrapper, "clientWidth", {
710
+ configurable: true,
711
+ value: 90,
712
+ });
713
+ Object.defineProperty(button, "offsetWidth", {
714
+ configurable: true,
715
+ value: 40,
716
+ });
717
+ Object.defineProperty(button, "offsetHeight", {
718
+ configurable: true,
719
+ value: 30,
720
+ });
721
+ button.getBoundingClientRect = () => ({
722
+ width: 40,
723
+ height: 30,
724
+ top: 0,
725
+ right: 40,
726
+ bottom: 30,
727
+ left: 0,
728
+ x: 0,
729
+ y: 0,
730
+ toJSON: () => {},
731
+ });
732
+ Object.defineProperty(switchButton, "offsetWidth", {
733
+ configurable: true,
734
+ value: 20,
735
+ });
736
+
737
+ await flushFrames();
738
+ await new Promise((resolve) => setTimeout(resolve, 0));
739
+ await new Promise((resolve) => setTimeout(resolve, 0));
740
+
741
+ expect(switchButton.hasAttribute("hidden")).to.be.true;
742
+ expect(button.hasAttribute("slot")).to.be.false;
743
+ expect(controlBar.getAttribute("data-monster-layout-stacked")).to.equal(
744
+ "true",
745
+ );
746
+ expect(controlBar.getAttribute("data-monster-layout-alignment")).to.equal(
747
+ "left",
748
+ );
749
+ } finally {
750
+ window.requestAnimationFrame = originalRequestAnimationFrame;
751
+ globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
752
+ }
753
+ });
663
754
  });