@schukai/monster 4.140.3 → 4.141.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/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.1"}
@@ -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") {
@@ -1183,10 +1203,12 @@ function calculateControlBarDimensions() {
1183
1203
  }
1184
1204
 
1185
1205
  let width = this.parentElement.clientWidth;
1206
+ let containerWidth = width;
1186
1207
  if (computedStyle.getPropertyValue("box-sizing") !== "border-box") {
1187
1208
  width = computedStyle.getPropertyValue("width");
1188
1209
 
1189
1210
  const pixel = getComputedCssPixels(width);
1211
+ containerWidth = pixel;
1190
1212
 
1191
1213
  this[dimensionsSymbol].setVia("data.space", pixel);
1192
1214
  } else {
@@ -1206,7 +1228,8 @@ function calculateControlBarDimensions() {
1206
1228
  );
1207
1229
  }
1208
1230
 
1209
- this[dimensionsSymbol].setVia("data.visible", !(width === 0));
1231
+ this[dimensionsSymbol].setVia("data.visible", !(containerWidth === 0));
1232
+ this[dimensionsSymbol].setVia("data.containerWidth", containerWidth);
1210
1233
 
1211
1234
  const itemReferences = [];
1212
1235
 
@@ -1509,6 +1532,41 @@ function syncLayoutState() {
1509
1532
  scheduleLayout.call(this, { measure: true, layout: true });
1510
1533
  }
1511
1534
 
1535
+ /**
1536
+ * @private
1537
+ * @return {boolean}
1538
+ */
1539
+ function isStackedBreakpointMatched() {
1540
+ const breakpoint = this.getOption("layout.stackedBreakpoint");
1541
+ if (typeof breakpoint !== "string" || breakpoint.trim() === "") {
1542
+ return false;
1543
+ }
1544
+
1545
+ let width = 0;
1546
+ try {
1547
+ width = this[dimensionsSymbol].getVia("data.containerWidth");
1548
+ } catch {
1549
+ try {
1550
+ width = this[dimensionsSymbol].getVia("data.space");
1551
+ } catch {}
1552
+ }
1553
+
1554
+ if (!(width > 0)) {
1555
+ width = getComputedCssPixels(width);
1556
+ }
1557
+
1558
+ if (!(width > 0)) {
1559
+ return false;
1560
+ }
1561
+
1562
+ const breakpointWidth = getComputedCssPixels(breakpoint);
1563
+ if (!(breakpointWidth > 0)) {
1564
+ return false;
1565
+ }
1566
+
1567
+ return width <= breakpointWidth;
1568
+ }
1569
+
1512
1570
  /**
1513
1571
  * @private
1514
1572
  * @param {boolean} stacked
@@ -1531,6 +1589,7 @@ function setLayoutStackedState(stacked) {
1531
1589
  )) {
1532
1590
  queueLayoutChangedEvent.call(this);
1533
1591
  }
1592
+ applyLayoutAlignment.call(this);
1534
1593
  }
1535
1594
 
1536
1595
  /**
@@ -1618,6 +1677,7 @@ function getLayoutChangedEventDetail() {
1618
1677
  configuredAlignment: this.getOption("layout.alignment", "left"),
1619
1678
  stacked,
1620
1679
  stackedAlignment: this.getOption("layout.stackedAlignment"),
1680
+ stackedBreakpoint: this.getOption("layout.stackedBreakpoint"),
1621
1681
  };
1622
1682
  }
1623
1683
 
@@ -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,183 @@ 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
+ });
754
+
755
+ it("should match the stacked breakpoint when the content-box container width is a css string", async function () {
756
+ const originalRequestAnimationFrame = window.requestAnimationFrame;
757
+ const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
758
+
759
+ const scheduledCallbacks = [];
760
+ const flushFrames = async () => {
761
+ while (scheduledCallbacks.length > 0) {
762
+ scheduledCallbacks.shift()();
763
+ await new Promise((resolve) => setTimeout(resolve, 0));
764
+ }
765
+ };
766
+
767
+ try {
768
+ window.requestAnimationFrame = (callback) => {
769
+ scheduledCallbacks.push(callback);
770
+ return scheduledCallbacks.length;
771
+ };
772
+ globalThis.requestAnimationFrame = window.requestAnimationFrame;
773
+
774
+ const mocks = document.getElementById("mocks");
775
+ mocks.innerHTML = `
776
+ <div id="content-box-breakpoint-wrapper">
777
+ <monster-control-bar
778
+ id="content-box-breakpoint-bar"
779
+ data-monster-option-layout-alignment="right"
780
+ data-monster-option-layout-stacked-alignment="left"
781
+ data-monster-option-layout-stacked-breakpoint="480px"
782
+ >
783
+ <button id="content-box-breakpoint-button">Run</button>
784
+ </monster-control-bar>
785
+ </div>
786
+ `;
787
+
788
+ const wrapper = document.getElementById(
789
+ "content-box-breakpoint-wrapper",
790
+ );
791
+ const button = document.getElementById("content-box-breakpoint-button");
792
+ const bar = document.getElementById("content-box-breakpoint-bar");
793
+ const controlBar = bar.shadowRoot.querySelector(
794
+ '[data-monster-role="control-bar"]',
795
+ );
796
+ const switchButton = bar.shadowRoot.querySelector(
797
+ '[data-monster-role="switch"]',
798
+ );
799
+
800
+ wrapper.style.boxSizing = "content-box";
801
+ wrapper.style.width = "350.156px";
802
+ Object.defineProperty(button, "offsetWidth", {
803
+ configurable: true,
804
+ value: 40,
805
+ });
806
+ Object.defineProperty(button, "offsetHeight", {
807
+ configurable: true,
808
+ value: 30,
809
+ });
810
+ button.getBoundingClientRect = () => ({
811
+ width: 40,
812
+ height: 30,
813
+ top: 0,
814
+ right: 40,
815
+ bottom: 30,
816
+ left: 0,
817
+ x: 0,
818
+ y: 0,
819
+ toJSON: () => {},
820
+ });
821
+ Object.defineProperty(switchButton, "offsetWidth", {
822
+ configurable: true,
823
+ value: 20,
824
+ });
825
+
826
+ await flushFrames();
827
+ await new Promise((resolve) => setTimeout(resolve, 0));
828
+ await new Promise((resolve) => setTimeout(resolve, 0));
829
+
830
+ expect(switchButton.hasAttribute("hidden")).to.be.true;
831
+ expect(button.hasAttribute("slot")).to.be.false;
832
+ expect(controlBar.getAttribute("data-monster-layout-stacked")).to.equal(
833
+ "true",
834
+ );
835
+ expect(controlBar.getAttribute("data-monster-layout-alignment")).to.equal(
836
+ "left",
837
+ );
838
+ } finally {
839
+ window.requestAnimationFrame = originalRequestAnimationFrame;
840
+ globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
841
+ }
842
+ });
663
843
  });