@schukai/monster 4.142.0 → 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
|
+
{"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.
|
|
@@ -610,6 +611,9 @@ function initEventHandler() {
|
|
|
610
611
|
};
|
|
611
612
|
|
|
612
613
|
self[resizeObserverSymbol] = new ResizeObserver((entries) => {
|
|
614
|
+
if (self[layoutStateSymbol]?.suppressResize) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
613
617
|
if (!hasContainerResizeEntry.call(self, entries)) {
|
|
614
618
|
return;
|
|
615
619
|
}
|
|
@@ -798,6 +802,7 @@ function rearrangeItems() {
|
|
|
798
802
|
availableSpace = 0;
|
|
799
803
|
}
|
|
800
804
|
let sum = 0;
|
|
805
|
+
let overflowStarted = false;
|
|
801
806
|
const visibleItemsInMainSlot = [];
|
|
802
807
|
const itemsToMoveToPopper = [];
|
|
803
808
|
|
|
@@ -807,7 +812,8 @@ function rearrangeItems() {
|
|
|
807
812
|
continue;
|
|
808
813
|
}
|
|
809
814
|
|
|
810
|
-
if (sum + entry.width > availableSpace) {
|
|
815
|
+
if (overflowStarted || sum + entry.width > availableSpace) {
|
|
816
|
+
overflowStarted = true;
|
|
811
817
|
itemsToMoveToPopper.push(entry.element);
|
|
812
818
|
} else {
|
|
813
819
|
sum += entry.width;
|
|
@@ -818,12 +824,41 @@ function rearrangeItems() {
|
|
|
818
824
|
return { visibleItemsInMainSlot, itemsToMoveToPopper };
|
|
819
825
|
};
|
|
820
826
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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
|
+
}
|
|
824
843
|
}
|
|
825
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
|
+
|
|
826
856
|
const shouldShowSwitch = layout.itemsToMoveToPopper.length > 0 && hasItems;
|
|
857
|
+
updateLastOverflowBoundary.call(
|
|
858
|
+
this,
|
|
859
|
+
layout,
|
|
860
|
+
itemEntries.map((entry) => entry.element),
|
|
861
|
+
);
|
|
827
862
|
const shouldUseStackedLayout =
|
|
828
863
|
shouldShowSwitch || isStackedBreakpointMatched.call(this);
|
|
829
864
|
|
|
@@ -851,6 +886,147 @@ function rearrangeItems() {
|
|
|
851
886
|
}
|
|
852
887
|
}
|
|
853
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
|
+
|
|
854
1030
|
/**
|
|
855
1031
|
* @private
|
|
856
1032
|
* @param {HTMLElement} node
|
|
@@ -211,4 +211,115 @@ describe("ButtonBar", function () {
|
|
|
211
211
|
globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
|
|
212
212
|
}
|
|
213
213
|
});
|
|
214
|
+
|
|
215
|
+
it("should ignore resize feedback caused by its own layout writes", async function () {
|
|
216
|
+
const OriginalResizeObserver = window.ResizeObserver;
|
|
217
|
+
const originalGlobalResizeObserver = globalThis.ResizeObserver;
|
|
218
|
+
const originalRequestAnimationFrame = window.requestAnimationFrame;
|
|
219
|
+
const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
|
|
220
|
+
|
|
221
|
+
class TrackingResizeObserver extends ResizeObserverMock {
|
|
222
|
+
static instances = [];
|
|
223
|
+
|
|
224
|
+
constructor(callback) {
|
|
225
|
+
super(callback);
|
|
226
|
+
TrackingResizeObserver.instances.push(this);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const scheduledCallbacks = [];
|
|
231
|
+
const flushOneFrame = async () => {
|
|
232
|
+
const callback = scheduledCallbacks.shift();
|
|
233
|
+
if (callback instanceof Function) {
|
|
234
|
+
callback();
|
|
235
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
const flushFrames = async () => {
|
|
239
|
+
while (scheduledCallbacks.length > 0) {
|
|
240
|
+
await flushOneFrame();
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
window.ResizeObserver = TrackingResizeObserver;
|
|
246
|
+
globalThis.ResizeObserver = TrackingResizeObserver;
|
|
247
|
+
window.requestAnimationFrame = (callback) => {
|
|
248
|
+
scheduledCallbacks.push(callback);
|
|
249
|
+
return scheduledCallbacks.length;
|
|
250
|
+
};
|
|
251
|
+
globalThis.requestAnimationFrame = window.requestAnimationFrame;
|
|
252
|
+
|
|
253
|
+
const mocks = document.getElementById("mocks");
|
|
254
|
+
mocks.innerHTML = `
|
|
255
|
+
<div id="feedback-bar-wrapper">
|
|
256
|
+
<monster-button-bar id="feedback-bar">
|
|
257
|
+
<button type="button">One</button>
|
|
258
|
+
<button type="button">Two</button>
|
|
259
|
+
<button type="button">Three</button>
|
|
260
|
+
</monster-button-bar>
|
|
261
|
+
</div>
|
|
262
|
+
`;
|
|
263
|
+
|
|
264
|
+
const wrapper = document.getElementById("feedback-bar-wrapper");
|
|
265
|
+
const bar = document.getElementById("feedback-bar");
|
|
266
|
+
|
|
267
|
+
wrapper.style.boxSizing = "border-box";
|
|
268
|
+
wrapper.style.width = "80px";
|
|
269
|
+
Object.defineProperty(wrapper, "clientWidth", {
|
|
270
|
+
configurable: true,
|
|
271
|
+
value: 80,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const buttons = Array.from(bar.querySelectorAll("button"));
|
|
275
|
+
for (const button of buttons) {
|
|
276
|
+
Object.defineProperty(button, "offsetWidth", {
|
|
277
|
+
configurable: true,
|
|
278
|
+
value: 48,
|
|
279
|
+
});
|
|
280
|
+
button.getBoundingClientRect = () => ({
|
|
281
|
+
width: 48,
|
|
282
|
+
height: 32,
|
|
283
|
+
top: 0,
|
|
284
|
+
left: 0,
|
|
285
|
+
right: 48,
|
|
286
|
+
bottom: 32,
|
|
287
|
+
x: 0,
|
|
288
|
+
y: 0,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const switchButton = bar.shadowRoot.querySelector(
|
|
293
|
+
'[data-monster-role="switch"]',
|
|
294
|
+
);
|
|
295
|
+
Object.defineProperty(switchButton, "offsetWidth", {
|
|
296
|
+
configurable: true,
|
|
297
|
+
value: 20,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
await flushOneFrame();
|
|
301
|
+
|
|
302
|
+
const resizeObserver = TrackingResizeObserver.instances.find((observer) =>
|
|
303
|
+
observer.elements.includes(wrapper),
|
|
304
|
+
);
|
|
305
|
+
expect(resizeObserver).to.exist;
|
|
306
|
+
|
|
307
|
+
const suppressedFrameCount = scheduledCallbacks.length;
|
|
308
|
+
resizeObserver.triggerResize([{ target: wrapper }]);
|
|
309
|
+
expect(scheduledCallbacks.length).to.equal(suppressedFrameCount);
|
|
310
|
+
|
|
311
|
+
await flushFrames();
|
|
312
|
+
|
|
313
|
+
resizeObserver.triggerResize([{ target: wrapper }]);
|
|
314
|
+
expect(scheduledCallbacks.length).to.equal(1);
|
|
315
|
+
|
|
316
|
+
await flushFrames();
|
|
317
|
+
expect(scheduledCallbacks.length).to.equal(0);
|
|
318
|
+
} finally {
|
|
319
|
+
window.ResizeObserver = OriginalResizeObserver;
|
|
320
|
+
globalThis.ResizeObserver = originalGlobalResizeObserver;
|
|
321
|
+
window.requestAnimationFrame = originalRequestAnimationFrame;
|
|
322
|
+
globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
|
|
323
|
+
}
|
|
324
|
+
});
|
|
214
325
|
});
|
|
@@ -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;
|