@schukai/monster 4.142.1 → 4.142.2

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.142.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.142.2"}
@@ -204,6 +204,7 @@ const EVENT_LAYOUT_CHANGED = "monster-control-bar-layout-changed";
204
204
  * @type {number}
205
205
  */
206
206
  const LAYOUT_CHANGED_EVENT_DELAY = 20;
207
+ const LAYOUT_SWITCH_HYSTERESIS = 16;
207
208
 
208
209
  /**
209
210
  * A control bar control.
@@ -801,6 +802,7 @@ function rearrangeItems() {
801
802
  availableSpace = 0;
802
803
  }
803
804
  let sum = 0;
805
+ let overflowStarted = false;
804
806
  const visibleItemsInMainSlot = [];
805
807
  const itemsToMoveToPopper = [];
806
808
 
@@ -810,7 +812,8 @@ function rearrangeItems() {
810
812
  continue;
811
813
  }
812
814
 
813
- if (sum + entry.width > availableSpace) {
815
+ if (overflowStarted || sum + entry.width > availableSpace) {
816
+ overflowStarted = true;
814
817
  itemsToMoveToPopper.push(entry.element);
815
818
  } else {
816
819
  sum += entry.width;
@@ -821,12 +824,41 @@ function rearrangeItems() {
821
824
  return { visibleItemsInMainSlot, itemsToMoveToPopper };
822
825
  };
823
826
 
824
- let layout = layoutItems(space);
825
- if (layout.itemsToMoveToPopper.length > 0) {
826
- layout = layoutItems(space - switchWidth);
827
+ const switchCurrentlyVisible =
828
+ this[switchElementSymbol] instanceof HTMLElement &&
829
+ !this[switchElementSymbol].hasAttribute("hidden");
830
+ let layout;
831
+ if (switchCurrentlyVisible) {
832
+ layout = layoutItems(space - switchWidth - LAYOUT_SWITCH_HYSTERESIS);
833
+ if (layout.itemsToMoveToPopper.length === 0) {
834
+ layout = layoutItems(space);
835
+ }
836
+ } else {
837
+ layout = layoutItems(space - LAYOUT_SWITCH_HYSTERESIS);
838
+ if (layout.itemsToMoveToPopper.length > 0) {
839
+ layout = layoutItems(space - switchWidth);
840
+ } else {
841
+ layout = layoutItems(space);
842
+ }
827
843
  }
828
844
 
845
+ layout = normalizeSpacerOverflowLayout(
846
+ layout,
847
+ itemEntries.map((entry) => entry.element),
848
+ );
849
+ layout = stabilizeOverflowBoundary.call(
850
+ this,
851
+ layout,
852
+ itemEntries,
853
+ switchCurrentlyVisible,
854
+ );
855
+
829
856
  const shouldShowSwitch = layout.itemsToMoveToPopper.length > 0 && hasItems;
857
+ updateLastOverflowBoundary.call(
858
+ this,
859
+ layout,
860
+ itemEntries.map((entry) => entry.element),
861
+ );
830
862
  const shouldUseStackedLayout =
831
863
  shouldShowSwitch || isStackedBreakpointMatched.call(this);
832
864
 
@@ -854,6 +886,147 @@ function rearrangeItems() {
854
886
  }
855
887
  }
856
888
 
889
+ /**
890
+ * @private
891
+ * @param {{visibleItemsInMainSlot: HTMLElement[], itemsToMoveToPopper: HTMLElement[]}} layout
892
+ * @param {{element: HTMLElement, hidden: boolean}[]} itemEntries
893
+ * @param {boolean} switchCurrentlyVisible
894
+ * @return {{visibleItemsInMainSlot: HTMLElement[], itemsToMoveToPopper: HTMLElement[]}}
895
+ */
896
+ function stabilizeOverflowBoundary(layout, itemEntries, switchCurrentlyVisible) {
897
+ const state = this[layoutStateSymbol];
898
+ if (!switchCurrentlyVisible || !state) {
899
+ return layout;
900
+ }
901
+
902
+ const orderedItems = itemEntries.map((entry) => entry.element);
903
+ const nextBoundary = getFirstOverflowIndex(layout, orderedItems);
904
+ const previousBoundary = state.lastOverflowStartIndex;
905
+ if (
906
+ typeof previousBoundary !== "number" ||
907
+ typeof nextBoundary !== "number" ||
908
+ nextBoundary <= previousBoundary ||
909
+ previousBoundary < 0 ||
910
+ previousBoundary >= itemEntries.length
911
+ ) {
912
+ return layout;
913
+ }
914
+
915
+ const popperItems = new Set();
916
+ const mainItems = new Set();
917
+ for (let index = 0; index < itemEntries.length; index++) {
918
+ const entry = itemEntries[index];
919
+ if (index >= previousBoundary && !entry.hidden) {
920
+ popperItems.add(entry.element);
921
+ } else {
922
+ mainItems.add(entry.element);
923
+ }
924
+ }
925
+
926
+ return normalizeSpacerOverflowLayout(
927
+ {
928
+ visibleItemsInMainSlot: orderedItems.filter((item) => mainItems.has(item)),
929
+ itemsToMoveToPopper: orderedItems.filter((item) => popperItems.has(item)),
930
+ },
931
+ orderedItems,
932
+ );
933
+ }
934
+
935
+ /**
936
+ * @private
937
+ * @param {{itemsToMoveToPopper: HTMLElement[]}} layout
938
+ * @param {HTMLElement[]} orderedItems
939
+ * @return {void}
940
+ */
941
+ function updateLastOverflowBoundary(layout, orderedItems) {
942
+ const state = this[layoutStateSymbol];
943
+ if (!state) {
944
+ return;
945
+ }
946
+
947
+ const boundary = getFirstOverflowIndex(layout, orderedItems);
948
+ if (typeof boundary === "number") {
949
+ state.lastOverflowStartIndex = boundary;
950
+ return;
951
+ }
952
+ delete state.lastOverflowStartIndex;
953
+ }
954
+
955
+ /**
956
+ * @private
957
+ * @param {{itemsToMoveToPopper: HTMLElement[]}} layout
958
+ * @param {HTMLElement[]} orderedItems
959
+ * @return {number|undefined}
960
+ */
961
+ function getFirstOverflowIndex(layout, orderedItems) {
962
+ const popperItems = new Set(layout.itemsToMoveToPopper);
963
+ for (let index = 0; index < orderedItems.length; index++) {
964
+ if (popperItems.has(orderedItems[index])) {
965
+ return index;
966
+ }
967
+ }
968
+ return undefined;
969
+ }
970
+
971
+ /**
972
+ * @private
973
+ * @param {{visibleItemsInMainSlot: HTMLElement[], itemsToMoveToPopper: HTMLElement[]}} layout
974
+ * @param {HTMLElement[]} orderedItems
975
+ * @return {{visibleItemsInMainSlot: HTMLElement[], itemsToMoveToPopper: HTMLElement[]}}
976
+ */
977
+ function normalizeSpacerOverflowLayout(layout, orderedItems) {
978
+ if (
979
+ !layout.itemsToMoveToPopper.some(
980
+ (item) => !isControlBarSpacerElement(item),
981
+ )
982
+ ) {
983
+ return layout;
984
+ }
985
+
986
+ const popperItems = new Set(layout.itemsToMoveToPopper);
987
+ const mainItems = new Set(layout.visibleItemsInMainSlot);
988
+
989
+ for (let index = 0; index < orderedItems.length; index++) {
990
+ const item = orderedItems[index];
991
+ if (!isControlBarSpacerElement(item) || !mainItems.has(item)) {
992
+ continue;
993
+ }
994
+
995
+ const previous = findAdjacentNonSpacerItem(orderedItems, index, -1);
996
+ const next = findAdjacentNonSpacerItem(orderedItems, index, 1);
997
+ if (popperItems.has(previous) || popperItems.has(next)) {
998
+ mainItems.delete(item);
999
+ popperItems.add(item);
1000
+ }
1001
+ }
1002
+
1003
+ return {
1004
+ visibleItemsInMainSlot: orderedItems.filter((item) => mainItems.has(item)),
1005
+ itemsToMoveToPopper: orderedItems.filter((item) => popperItems.has(item)),
1006
+ };
1007
+ }
1008
+
1009
+ /**
1010
+ * @private
1011
+ * @param {HTMLElement[]} orderedItems
1012
+ * @param {number} startIndex
1013
+ * @param {1|-1} direction
1014
+ * @return {HTMLElement|undefined}
1015
+ */
1016
+ function findAdjacentNonSpacerItem(orderedItems, startIndex, direction) {
1017
+ for (
1018
+ let index = startIndex + direction;
1019
+ index >= 0 && index < orderedItems.length;
1020
+ index += direction
1021
+ ) {
1022
+ const item = orderedItems[index];
1023
+ if (!isControlBarSpacerElement(item)) {
1024
+ return item;
1025
+ }
1026
+ }
1027
+ return undefined;
1028
+ }
1029
+
857
1030
  /**
858
1031
  * @private
859
1032
  * @param {HTMLElement} node
@@ -756,6 +756,479 @@ describe("ControlBar", function () {
756
756
  }
757
757
  });
758
758
 
759
+ it("should keep the overflow switch stable across the switch-width threshold", async function () {
760
+ const OriginalResizeObserver = window.ResizeObserver;
761
+ const originalGlobalResizeObserver = globalThis.ResizeObserver;
762
+ const originalRequestAnimationFrame = window.requestAnimationFrame;
763
+ const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
764
+
765
+ class TrackingResizeObserver extends ResizeObserverMock {
766
+ static instances = [];
767
+
768
+ constructor(callback) {
769
+ super(callback);
770
+ TrackingResizeObserver.instances.push(this);
771
+ }
772
+ }
773
+
774
+ const scheduledCallbacks = [];
775
+ const flushFrames = async () => {
776
+ while (scheduledCallbacks.length > 0) {
777
+ scheduledCallbacks.shift()();
778
+ await new Promise((resolve) => setTimeout(resolve, 0));
779
+ }
780
+ };
781
+
782
+ try {
783
+ window.ResizeObserver = TrackingResizeObserver;
784
+ globalThis.ResizeObserver = TrackingResizeObserver;
785
+ window.requestAnimationFrame = (callback) => {
786
+ scheduledCallbacks.push(callback);
787
+ return scheduledCallbacks.length;
788
+ };
789
+ globalThis.requestAnimationFrame = window.requestAnimationFrame;
790
+
791
+ const mocks = document.getElementById("mocks");
792
+ mocks.innerHTML = `
793
+ <div id="threshold-bar-wrapper">
794
+ <monster-control-bar id="threshold-bar">
795
+ <button id="threshold-one">One</button>
796
+ <button id="threshold-two">Two</button>
797
+ <button id="threshold-three">Three</button>
798
+ </monster-control-bar>
799
+ </div>
800
+ `;
801
+
802
+ const wrapper = document.getElementById("threshold-bar-wrapper");
803
+ const bar = document.getElementById("threshold-bar");
804
+ const buttons = [
805
+ document.getElementById("threshold-one"),
806
+ document.getElementById("threshold-two"),
807
+ document.getElementById("threshold-three"),
808
+ ];
809
+ let wrapperWidth = 90;
810
+
811
+ wrapper.style.boxSizing = "border-box";
812
+ Object.defineProperty(wrapper, "clientWidth", {
813
+ configurable: true,
814
+ get: () => wrapperWidth,
815
+ });
816
+
817
+ for (const button of buttons) {
818
+ Object.defineProperty(button, "offsetWidth", {
819
+ configurable: true,
820
+ value: 50,
821
+ });
822
+ Object.defineProperty(button, "offsetHeight", {
823
+ configurable: true,
824
+ value: 30,
825
+ });
826
+ button.getBoundingClientRect = () => ({
827
+ width: 50,
828
+ height: 30,
829
+ top: 0,
830
+ right: 50,
831
+ bottom: 30,
832
+ left: 0,
833
+ x: 0,
834
+ y: 0,
835
+ toJSON: () => {},
836
+ });
837
+ }
838
+
839
+ const switchButton = bar.shadowRoot.querySelector(
840
+ '[data-monster-role="switch"]',
841
+ );
842
+ Object.defineProperty(switchButton, "offsetWidth", {
843
+ configurable: true,
844
+ value: 20,
845
+ });
846
+
847
+ await flushFrames();
848
+ expect(switchButton.hasAttribute("hidden")).to.be.false;
849
+
850
+ const resizeObserver = TrackingResizeObserver.instances.find((observer) =>
851
+ observer.elements.includes(wrapper),
852
+ );
853
+ expect(resizeObserver).to.exist;
854
+
855
+ wrapperWidth = 155;
856
+ resizeObserver.triggerResize([{ target: wrapper }]);
857
+ await flushFrames();
858
+ expect(switchButton.hasAttribute("hidden")).to.be.false;
859
+ expect(document.getElementById("threshold-three").getAttribute("slot")).to
860
+ .equal("popper");
861
+
862
+ wrapperWidth = 175;
863
+ resizeObserver.triggerResize([{ target: wrapper }]);
864
+ await flushFrames();
865
+ expect(switchButton.hasAttribute("hidden")).to.be.false;
866
+ expect(document.getElementById("threshold-three").getAttribute("slot")).to
867
+ .equal("popper");
868
+
869
+ wrapperWidth = 190;
870
+ resizeObserver.triggerResize([{ target: wrapper }]);
871
+ await flushFrames();
872
+ expect(switchButton.hasAttribute("hidden")).to.be.true;
873
+ expect(document.getElementById("threshold-three").hasAttribute("slot")).to
874
+ .be.false;
875
+
876
+ wrapperWidth = 160;
877
+ resizeObserver.triggerResize([{ target: wrapper }]);
878
+ await flushFrames();
879
+ expect(switchButton.hasAttribute("hidden")).to.be.false;
880
+ expect(document.getElementById("threshold-three").getAttribute("slot")).to
881
+ .equal("popper");
882
+ } finally {
883
+ window.ResizeObserver = OriginalResizeObserver;
884
+ globalThis.ResizeObserver = originalGlobalResizeObserver;
885
+ window.requestAnimationFrame = originalRequestAnimationFrame;
886
+ globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
887
+ }
888
+ });
889
+
890
+ it("should move spacers with adjacent overflow controls", async function () {
891
+ const OriginalResizeObserver = window.ResizeObserver;
892
+ const originalGlobalResizeObserver = globalThis.ResizeObserver;
893
+ const originalRequestAnimationFrame = window.requestAnimationFrame;
894
+ const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
895
+
896
+ class TrackingResizeObserver extends ResizeObserverMock {
897
+ static instances = [];
898
+
899
+ constructor(callback) {
900
+ super(callback);
901
+ TrackingResizeObserver.instances.push(this);
902
+ }
903
+ }
904
+
905
+ const scheduledCallbacks = [];
906
+ const flushFrames = async () => {
907
+ while (scheduledCallbacks.length > 0) {
908
+ scheduledCallbacks.shift()();
909
+ await new Promise((resolve) => setTimeout(resolve, 0));
910
+ }
911
+ };
912
+
913
+ try {
914
+ window.ResizeObserver = TrackingResizeObserver;
915
+ globalThis.ResizeObserver = TrackingResizeObserver;
916
+ window.requestAnimationFrame = (callback) => {
917
+ scheduledCallbacks.push(callback);
918
+ return scheduledCallbacks.length;
919
+ };
920
+ globalThis.requestAnimationFrame = window.requestAnimationFrame;
921
+
922
+ const mocks = document.getElementById("mocks");
923
+ mocks.innerHTML = `
924
+ <div id="spacer-overflow-wrapper">
925
+ <monster-control-bar id="spacer-overflow-bar">
926
+ <button id="spacer-overflow-one">One</button>
927
+ <monster-control-bar-spacer id="spacer-overflow-spacer-one"></monster-control-bar-spacer>
928
+ <button id="spacer-overflow-two">Two</button>
929
+ <monster-control-bar-spacer id="spacer-overflow-spacer-two"></monster-control-bar-spacer>
930
+ <button id="spacer-overflow-three">Three</button>
931
+ </monster-control-bar>
932
+ </div>
933
+ `;
934
+
935
+ const wrapper = document.getElementById("spacer-overflow-wrapper");
936
+ const bar = document.getElementById("spacer-overflow-bar");
937
+ const sizes = new Map([
938
+ ["spacer-overflow-one", 50],
939
+ ["spacer-overflow-spacer-one", 10],
940
+ ["spacer-overflow-two", 100],
941
+ ["spacer-overflow-spacer-two", 10],
942
+ ["spacer-overflow-three", 50],
943
+ ]);
944
+
945
+ wrapper.style.boxSizing = "border-box";
946
+ Object.defineProperty(wrapper, "clientWidth", {
947
+ configurable: true,
948
+ value: 100,
949
+ });
950
+
951
+ for (const [id, width] of sizes) {
952
+ const element = document.getElementById(id);
953
+ Object.defineProperty(element, "offsetWidth", {
954
+ configurable: true,
955
+ value: width,
956
+ });
957
+ Object.defineProperty(element, "offsetHeight", {
958
+ configurable: true,
959
+ value: 30,
960
+ });
961
+ element.getBoundingClientRect = () => ({
962
+ width,
963
+ height: 30,
964
+ top: 0,
965
+ right: width,
966
+ bottom: 30,
967
+ left: 0,
968
+ x: 0,
969
+ y: 0,
970
+ toJSON: () => {},
971
+ });
972
+ }
973
+
974
+ const switchButton = bar.shadowRoot.querySelector(
975
+ '[data-monster-role="switch"]',
976
+ );
977
+ Object.defineProperty(switchButton, "offsetWidth", {
978
+ configurable: true,
979
+ value: 20,
980
+ });
981
+
982
+ await flushFrames();
983
+ expect(document.getElementById("spacer-overflow-one").hasAttribute("slot"))
984
+ .to.be.false;
985
+ expect(
986
+ document.getElementById("spacer-overflow-spacer-one").getAttribute("slot"),
987
+ ).to.equal("popper");
988
+ expect(document.getElementById("spacer-overflow-two").getAttribute("slot"))
989
+ .to.equal("popper");
990
+ expect(
991
+ document.getElementById("spacer-overflow-spacer-two").getAttribute("slot"),
992
+ ).to.equal("popper");
993
+ expect(
994
+ document.getElementById("spacer-overflow-three").getAttribute("slot"),
995
+ ).to.equal("popper");
996
+
997
+ const resizeObserver = TrackingResizeObserver.instances.find((observer) =>
998
+ observer.elements.includes(wrapper),
999
+ );
1000
+ expect(resizeObserver).to.exist;
1001
+
1002
+ resizeObserver.triggerResize([{ target: wrapper }]);
1003
+ await flushFrames();
1004
+ expect(
1005
+ document.getElementById("spacer-overflow-spacer-one").getAttribute("slot"),
1006
+ ).to.equal("popper");
1007
+ expect(
1008
+ document.getElementById("spacer-overflow-spacer-two").getAttribute("slot"),
1009
+ ).to.equal("popper");
1010
+ } finally {
1011
+ window.ResizeObserver = OriginalResizeObserver;
1012
+ globalThis.ResizeObserver = originalGlobalResizeObserver;
1013
+ window.requestAnimationFrame = originalRequestAnimationFrame;
1014
+ globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
1015
+ }
1016
+ });
1017
+
1018
+ it("should keep overflow as an ordered suffix when later controls would fit", async function () {
1019
+ const originalRequestAnimationFrame = window.requestAnimationFrame;
1020
+ const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
1021
+
1022
+ const scheduledCallbacks = [];
1023
+ const flushFrames = async () => {
1024
+ while (scheduledCallbacks.length > 0) {
1025
+ scheduledCallbacks.shift()();
1026
+ await new Promise((resolve) => setTimeout(resolve, 0));
1027
+ }
1028
+ };
1029
+
1030
+ try {
1031
+ window.requestAnimationFrame = (callback) => {
1032
+ scheduledCallbacks.push(callback);
1033
+ return scheduledCallbacks.length;
1034
+ };
1035
+ globalThis.requestAnimationFrame = window.requestAnimationFrame;
1036
+
1037
+ const mocks = document.getElementById("mocks");
1038
+ mocks.innerHTML = `
1039
+ <div id="ordered-overflow-wrapper">
1040
+ <monster-control-bar id="ordered-overflow-bar">
1041
+ <button id="ordered-overflow-one">One</button>
1042
+ <button id="ordered-overflow-wide">Wide</button>
1043
+ <button id="ordered-overflow-small">Small</button>
1044
+ </monster-control-bar>
1045
+ </div>
1046
+ `;
1047
+
1048
+ const wrapper = document.getElementById("ordered-overflow-wrapper");
1049
+ const bar = document.getElementById("ordered-overflow-bar");
1050
+ const sizes = new Map([
1051
+ ["ordered-overflow-one", 50],
1052
+ ["ordered-overflow-wide", 100],
1053
+ ["ordered-overflow-small", 20],
1054
+ ]);
1055
+
1056
+ wrapper.style.boxSizing = "border-box";
1057
+ Object.defineProperty(wrapper, "clientWidth", {
1058
+ configurable: true,
1059
+ value: 90,
1060
+ });
1061
+
1062
+ for (const [id, width] of sizes) {
1063
+ const element = document.getElementById(id);
1064
+ Object.defineProperty(element, "offsetWidth", {
1065
+ configurable: true,
1066
+ value: width,
1067
+ });
1068
+ Object.defineProperty(element, "offsetHeight", {
1069
+ configurable: true,
1070
+ value: 30,
1071
+ });
1072
+ element.getBoundingClientRect = () => ({
1073
+ width,
1074
+ height: 30,
1075
+ top: 0,
1076
+ right: width,
1077
+ bottom: 30,
1078
+ left: 0,
1079
+ x: 0,
1080
+ y: 0,
1081
+ toJSON: () => {},
1082
+ });
1083
+ }
1084
+
1085
+ const switchButton = bar.shadowRoot.querySelector(
1086
+ '[data-monster-role="switch"]',
1087
+ );
1088
+ Object.defineProperty(switchButton, "offsetWidth", {
1089
+ configurable: true,
1090
+ value: 20,
1091
+ });
1092
+
1093
+ await flushFrames();
1094
+
1095
+ expect(document.getElementById("ordered-overflow-one").hasAttribute("slot"))
1096
+ .to.be.false;
1097
+ expect(document.getElementById("ordered-overflow-wide").getAttribute("slot"))
1098
+ .to.equal("popper");
1099
+ expect(document.getElementById("ordered-overflow-small").getAttribute("slot"))
1100
+ .to.equal("popper");
1101
+ } finally {
1102
+ window.requestAnimationFrame = originalRequestAnimationFrame;
1103
+ globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
1104
+ }
1105
+ });
1106
+
1107
+ it("should keep the previous overflow boundary while the switch remains visible", async function () {
1108
+ const OriginalResizeObserver = window.ResizeObserver;
1109
+ const originalGlobalResizeObserver = globalThis.ResizeObserver;
1110
+ const originalRequestAnimationFrame = window.requestAnimationFrame;
1111
+ const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
1112
+
1113
+ class TrackingResizeObserver extends ResizeObserverMock {
1114
+ static instances = [];
1115
+
1116
+ constructor(callback) {
1117
+ super(callback);
1118
+ TrackingResizeObserver.instances.push(this);
1119
+ }
1120
+ }
1121
+
1122
+ const scheduledCallbacks = [];
1123
+ const flushFrames = async () => {
1124
+ while (scheduledCallbacks.length > 0) {
1125
+ scheduledCallbacks.shift()();
1126
+ await new Promise((resolve) => setTimeout(resolve, 0));
1127
+ }
1128
+ };
1129
+
1130
+ try {
1131
+ window.ResizeObserver = TrackingResizeObserver;
1132
+ globalThis.ResizeObserver = TrackingResizeObserver;
1133
+ window.requestAnimationFrame = (callback) => {
1134
+ scheduledCallbacks.push(callback);
1135
+ return scheduledCallbacks.length;
1136
+ };
1137
+ globalThis.requestAnimationFrame = window.requestAnimationFrame;
1138
+
1139
+ const mocks = document.getElementById("mocks");
1140
+ mocks.innerHTML = `
1141
+ <div id="sticky-overflow-wrapper">
1142
+ <monster-control-bar id="sticky-overflow-bar">
1143
+ <button id="sticky-overflow-one">One</button>
1144
+ <button id="sticky-overflow-wide">Wide</button>
1145
+ <button id="sticky-overflow-small">Small</button>
1146
+ </monster-control-bar>
1147
+ </div>
1148
+ `;
1149
+
1150
+ const wrapper = document.getElementById("sticky-overflow-wrapper");
1151
+ const bar = document.getElementById("sticky-overflow-bar");
1152
+ let wrapperWidth = 90;
1153
+ const sizes = new Map([
1154
+ ["sticky-overflow-one", 50],
1155
+ ["sticky-overflow-wide", 100],
1156
+ ["sticky-overflow-small", 20],
1157
+ ]);
1158
+
1159
+ wrapper.style.boxSizing = "border-box";
1160
+ Object.defineProperty(wrapper, "clientWidth", {
1161
+ configurable: true,
1162
+ get: () => wrapperWidth,
1163
+ });
1164
+
1165
+ for (const [id, width] of sizes) {
1166
+ const element = document.getElementById(id);
1167
+ Object.defineProperty(element, "offsetWidth", {
1168
+ configurable: true,
1169
+ value: width,
1170
+ });
1171
+ Object.defineProperty(element, "offsetHeight", {
1172
+ configurable: true,
1173
+ value: 30,
1174
+ });
1175
+ element.getBoundingClientRect = () => ({
1176
+ width,
1177
+ height: 30,
1178
+ top: 0,
1179
+ right: width,
1180
+ bottom: 30,
1181
+ left: 0,
1182
+ x: 0,
1183
+ y: 0,
1184
+ toJSON: () => {},
1185
+ });
1186
+ }
1187
+
1188
+ const switchButton = bar.shadowRoot.querySelector(
1189
+ '[data-monster-role="switch"]',
1190
+ );
1191
+ Object.defineProperty(switchButton, "offsetWidth", {
1192
+ configurable: true,
1193
+ value: 20,
1194
+ });
1195
+
1196
+ await flushFrames();
1197
+ expect(document.getElementById("sticky-overflow-wide").getAttribute("slot"))
1198
+ .to.equal("popper");
1199
+ expect(document.getElementById("sticky-overflow-small").getAttribute("slot"))
1200
+ .to.equal("popper");
1201
+
1202
+ const resizeObserver = TrackingResizeObserver.instances.find((observer) =>
1203
+ observer.elements.includes(wrapper),
1204
+ );
1205
+ expect(resizeObserver).to.exist;
1206
+
1207
+ wrapperWidth = 190;
1208
+ resizeObserver.triggerResize([{ target: wrapper }]);
1209
+ await flushFrames();
1210
+ expect(switchButton.hasAttribute("hidden")).to.be.false;
1211
+ expect(document.getElementById("sticky-overflow-wide").getAttribute("slot"))
1212
+ .to.equal("popper");
1213
+ expect(document.getElementById("sticky-overflow-small").getAttribute("slot"))
1214
+ .to.equal("popper");
1215
+
1216
+ wrapperWidth = 220;
1217
+ resizeObserver.triggerResize([{ target: wrapper }]);
1218
+ await flushFrames();
1219
+ expect(switchButton.hasAttribute("hidden")).to.be.true;
1220
+ expect(document.getElementById("sticky-overflow-wide").hasAttribute("slot"))
1221
+ .to.be.false;
1222
+ expect(document.getElementById("sticky-overflow-small").hasAttribute("slot"))
1223
+ .to.be.false;
1224
+ } finally {
1225
+ window.ResizeObserver = OriginalResizeObserver;
1226
+ globalThis.ResizeObserver = originalGlobalResizeObserver;
1227
+ window.requestAnimationFrame = originalRequestAnimationFrame;
1228
+ globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
1229
+ }
1230
+ });
1231
+
759
1232
  it("should match the stacked breakpoint when the content-box container width is a css string", async function () {
760
1233
  const originalRequestAnimationFrame = window.requestAnimationFrame;
761
1234
  const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;