@schukai/monster 4.145.2 → 4.145.3
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.145.
|
|
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.145.3"}
|
|
@@ -532,6 +532,12 @@ function initEventHandler() {
|
|
|
532
532
|
|
|
533
533
|
const mutationCallback = (mutationList) => {
|
|
534
534
|
if (self[layoutStateSymbol]?.suppressMutation) {
|
|
535
|
+
mutationList = mutationList.filter(
|
|
536
|
+
(mutation) => mutation.attributeName !== "style",
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (mutationList.length === 0) {
|
|
535
541
|
return;
|
|
536
542
|
}
|
|
537
543
|
|
|
@@ -86,6 +86,12 @@ const controlElementSymbol = Symbol("controlElement");
|
|
|
86
86
|
* @type {symbol}
|
|
87
87
|
*/
|
|
88
88
|
const navElementSymbol = Symbol("navElement");
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @private
|
|
92
|
+
* @type {symbol}
|
|
93
|
+
*/
|
|
94
|
+
const measurementNavElementSymbol = Symbol("measurementNavElement");
|
|
89
95
|
/**
|
|
90
96
|
* @private
|
|
91
97
|
* @type {symbol}
|
|
@@ -154,6 +160,28 @@ const resizeObserverSymbol = Symbol("resizeObserver");
|
|
|
154
160
|
*/
|
|
155
161
|
const activeReferenceSymbol = Symbol("activeReference");
|
|
156
162
|
|
|
163
|
+
/**
|
|
164
|
+
* @private
|
|
165
|
+
* @type {symbol}
|
|
166
|
+
*/
|
|
167
|
+
const rearrangeFrameSymbol = Symbol("rearrangeFrame");
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* @private
|
|
171
|
+
* @type {symbol}
|
|
172
|
+
*/
|
|
173
|
+
const buttonCollectionsSignatureSymbol = Symbol("buttonCollectionsSignature");
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* @private
|
|
177
|
+
* @type {symbol}
|
|
178
|
+
*/
|
|
179
|
+
const layoutSignatureHistorySymbol = Symbol("layoutSignatureHistory");
|
|
180
|
+
|
|
181
|
+
const LAYOUT_SWITCH_HYSTERESIS = 16;
|
|
182
|
+
const LAYOUT_OSCILLATION_HISTORY_LIMIT = 4;
|
|
183
|
+
const LAYOUT_OSCILLATION_TIME_WINDOW_MS = 1000;
|
|
184
|
+
|
|
157
185
|
/**
|
|
158
186
|
* A Tabs Control
|
|
159
187
|
*
|
|
@@ -553,6 +581,13 @@ class Tabs extends CustomElement {
|
|
|
553
581
|
|
|
554
582
|
this[popperInstanceSymbol]?.destroy();
|
|
555
583
|
this[tabListMutationObserverSymbol]?.disconnect();
|
|
584
|
+
this[resizeObserverSymbol]?.disconnect();
|
|
585
|
+
if (this[rearrangeFrameSymbol] !== undefined) {
|
|
586
|
+
getWindow().cancelAnimationFrame(this[rearrangeFrameSymbol]);
|
|
587
|
+
delete this[rearrangeFrameSymbol];
|
|
588
|
+
}
|
|
589
|
+
this[measurementNavElementSymbol]?.remove();
|
|
590
|
+
delete this[measurementNavElementSymbol];
|
|
556
591
|
}
|
|
557
592
|
}
|
|
558
593
|
|
|
@@ -1327,6 +1362,8 @@ function initTabButtons() {
|
|
|
1327
1362
|
throw new Error("no shadow-root is defined");
|
|
1328
1363
|
}
|
|
1329
1364
|
|
|
1365
|
+
resetLayoutSignatureHistory.call(this);
|
|
1366
|
+
|
|
1330
1367
|
let activeReference;
|
|
1331
1368
|
let invalidActive = false;
|
|
1332
1369
|
const previousActiveReference = this[activeReferenceSymbol] || null;
|
|
@@ -1408,8 +1445,11 @@ function initTabButtons() {
|
|
|
1408
1445
|
});
|
|
1409
1446
|
}
|
|
1410
1447
|
|
|
1411
|
-
|
|
1448
|
+
const { standardButtons, popperButtons } =
|
|
1449
|
+
splitButtonsByCurrentPopperReferences.call(this, buttons);
|
|
1450
|
+
setButtonCollections.call(this, standardButtons, popperButtons);
|
|
1412
1451
|
this.setOption("marker", random());
|
|
1452
|
+
this[dimensionsSymbol].setVia("data.calculated", false);
|
|
1413
1453
|
|
|
1414
1454
|
return adjustButtonVisibility.call(this).then(() => {
|
|
1415
1455
|
if (
|
|
@@ -1491,7 +1531,9 @@ function adjustButtonVisibility() {
|
|
|
1491
1531
|
const runIfRendered = () => {
|
|
1492
1532
|
if (resolved === true) return;
|
|
1493
1533
|
|
|
1494
|
-
const defCount =
|
|
1534
|
+
const defCount =
|
|
1535
|
+
self.getOption("buttons.standard").length +
|
|
1536
|
+
self.getOption("buttons.popper").length;
|
|
1495
1537
|
const domCount = self[navElementSymbol].querySelectorAll(
|
|
1496
1538
|
'button[data-monster-role="button"][data-monster-tab-reference]',
|
|
1497
1539
|
).length;
|
|
@@ -1529,7 +1571,7 @@ function getDimValue(value) {
|
|
|
1529
1571
|
return 0;
|
|
1530
1572
|
}
|
|
1531
1573
|
|
|
1532
|
-
const valueAsInt =
|
|
1574
|
+
const valueAsInt = parseFloat(value);
|
|
1533
1575
|
|
|
1534
1576
|
if (isNaN(valueAsInt)) {
|
|
1535
1577
|
return 0;
|
|
@@ -1554,7 +1596,7 @@ function calcBoxWidth(node) {
|
|
|
1554
1596
|
getDimValue(bounding["width"]) +
|
|
1555
1597
|
getDimValue(dim["border-right-width"]) +
|
|
1556
1598
|
getDimValue(dim["margin-right"]) +
|
|
1557
|
-
getDimValue(dim["padding-
|
|
1599
|
+
getDimValue(dim["padding-right"])
|
|
1558
1600
|
);
|
|
1559
1601
|
}
|
|
1560
1602
|
|
|
@@ -1563,10 +1605,13 @@ function calcBoxWidth(node) {
|
|
|
1563
1605
|
* @return {Object}
|
|
1564
1606
|
*/
|
|
1565
1607
|
function rearrangeButtons() {
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1608
|
+
if (this[rearrangeFrameSymbol] !== undefined) {
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
this[rearrangeFrameSymbol] = getWindow().requestAnimationFrame(() => {
|
|
1613
|
+
delete this[rearrangeFrameSymbol];
|
|
1614
|
+
|
|
1570
1615
|
const space = this[dimensionsSymbol].getVia("data.space");
|
|
1571
1616
|
|
|
1572
1617
|
if (space <= 0) {
|
|
@@ -1576,16 +1621,30 @@ function rearrangeButtons() {
|
|
|
1576
1621
|
const buttons = this.getOption("buttons.standard").concat(
|
|
1577
1622
|
this.getOption("buttons.popper"),
|
|
1578
1623
|
);
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1624
|
+
const widths = buttons.map((button) =>
|
|
1625
|
+
resolveButtonWidth.call(this, button?.reference),
|
|
1626
|
+
);
|
|
1627
|
+
const switchWidth = resolveSwitchWidth.call(this);
|
|
1628
|
+
const switchCurrentlyVisible =
|
|
1629
|
+
this[switchElementSymbol] instanceof HTMLElement &&
|
|
1630
|
+
!this[switchElementSymbol].classList.contains("hidden");
|
|
1631
|
+
const { standardButtons, popperButtons } = resolveOverflowButtonLayout({
|
|
1632
|
+
buttons,
|
|
1633
|
+
widths,
|
|
1634
|
+
space,
|
|
1635
|
+
switchWidth,
|
|
1636
|
+
switchCurrentlyVisible,
|
|
1637
|
+
});
|
|
1583
1638
|
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1639
|
+
if (
|
|
1640
|
+
shouldSuppressOscillatingTabLayout.call(
|
|
1641
|
+
this,
|
|
1642
|
+
space,
|
|
1643
|
+
standardButtons,
|
|
1644
|
+
popperButtons,
|
|
1645
|
+
)
|
|
1646
|
+
) {
|
|
1647
|
+
return;
|
|
1589
1648
|
}
|
|
1590
1649
|
|
|
1591
1650
|
setButtonCollections.call(this, standardButtons, popperButtons);
|
|
@@ -1594,6 +1653,74 @@ function rearrangeButtons() {
|
|
|
1594
1653
|
});
|
|
1595
1654
|
}
|
|
1596
1655
|
|
|
1656
|
+
/**
|
|
1657
|
+
* @private
|
|
1658
|
+
* @param {Object} options
|
|
1659
|
+
* @param {Object[]} options.buttons
|
|
1660
|
+
* @param {number[]} options.widths
|
|
1661
|
+
* @param {number} options.space
|
|
1662
|
+
* @param {number} options.switchWidth
|
|
1663
|
+
* @param {boolean} options.switchCurrentlyVisible
|
|
1664
|
+
* @return {{standardButtons: Object[], popperButtons: Object[]}}
|
|
1665
|
+
*/
|
|
1666
|
+
function resolveOverflowButtonLayout({
|
|
1667
|
+
buttons,
|
|
1668
|
+
widths,
|
|
1669
|
+
space,
|
|
1670
|
+
switchWidth,
|
|
1671
|
+
switchCurrentlyVisible,
|
|
1672
|
+
}) {
|
|
1673
|
+
let layout;
|
|
1674
|
+
if (switchCurrentlyVisible) {
|
|
1675
|
+
layout = fitButtonsIntoSpace(
|
|
1676
|
+
buttons,
|
|
1677
|
+
widths,
|
|
1678
|
+
space - switchWidth - LAYOUT_SWITCH_HYSTERESIS,
|
|
1679
|
+
);
|
|
1680
|
+
if (layout.popperButtons.length === 0) {
|
|
1681
|
+
layout = fitButtonsIntoSpace(buttons, widths, space);
|
|
1682
|
+
}
|
|
1683
|
+
return layout;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
layout = fitButtonsIntoSpace(
|
|
1687
|
+
buttons,
|
|
1688
|
+
widths,
|
|
1689
|
+
space - LAYOUT_SWITCH_HYSTERESIS,
|
|
1690
|
+
);
|
|
1691
|
+
if (layout.popperButtons.length > 0) {
|
|
1692
|
+
return fitButtonsIntoSpace(buttons, widths, space - switchWidth);
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
return fitButtonsIntoSpace(buttons, widths, space);
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
/**
|
|
1699
|
+
* @private
|
|
1700
|
+
* @param {Object[]} buttons
|
|
1701
|
+
* @param {number[]} widths
|
|
1702
|
+
* @param {number} availableSpace
|
|
1703
|
+
* @return {{standardButtons: Object[], popperButtons: Object[]}}
|
|
1704
|
+
*/
|
|
1705
|
+
function fitButtonsIntoSpace(buttons, widths, availableSpace) {
|
|
1706
|
+
let sum = 0;
|
|
1707
|
+
const standardButtons = [];
|
|
1708
|
+
const popperButtons = [];
|
|
1709
|
+
const boundedSpace = Math.max(0, availableSpace);
|
|
1710
|
+
|
|
1711
|
+
for (const [index, button] of buttons.entries()) {
|
|
1712
|
+
const width = widths[index];
|
|
1713
|
+
if (popperButtons.length > 0 || sum + width > boundedSpace) {
|
|
1714
|
+
popperButtons.push(clone(button));
|
|
1715
|
+
} else {
|
|
1716
|
+
sum += width;
|
|
1717
|
+
standardButtons.push(clone(button));
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
return { standardButtons, popperButtons };
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1597
1724
|
/**
|
|
1598
1725
|
* @private
|
|
1599
1726
|
*/
|
|
@@ -1625,11 +1752,131 @@ function resolveButtonWidth(ref) {
|
|
|
1625
1752
|
return 0;
|
|
1626
1753
|
}
|
|
1627
1754
|
|
|
1628
|
-
width =
|
|
1629
|
-
|
|
1755
|
+
width = measureButtonWidth.call(this, element);
|
|
1756
|
+
if (width > 0) {
|
|
1757
|
+
this[dimensionsSymbol].setVia(`data.button.${ref}`, width);
|
|
1758
|
+
}
|
|
1759
|
+
return width;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
/**
|
|
1763
|
+
* @private
|
|
1764
|
+
* @param {boolean} force
|
|
1765
|
+
* @return {number}
|
|
1766
|
+
*/
|
|
1767
|
+
function resolveSwitchWidth(force = false) {
|
|
1768
|
+
let width = this[dimensionsSymbol].getVia("data.switchWidth", 0);
|
|
1769
|
+
if (force !== true && width > 0) {
|
|
1770
|
+
return width;
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
if (!(this[switchElementSymbol] instanceof HTMLElement)) {
|
|
1774
|
+
return 0;
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
width = measureElementWidthInNavigation.call(this, this[switchElementSymbol], {
|
|
1778
|
+
removeHiddenClass: true,
|
|
1779
|
+
});
|
|
1780
|
+
this[dimensionsSymbol].setVia("data.switchWidth", width);
|
|
1781
|
+
return width;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
/**
|
|
1785
|
+
* @private
|
|
1786
|
+
* @param {HTMLButtonElement} element
|
|
1787
|
+
* @return {number}
|
|
1788
|
+
*/
|
|
1789
|
+
function measureButtonWidth(element) {
|
|
1790
|
+
if (
|
|
1791
|
+
element.closest(`[${ATTRIBUTE_ROLE}=popper-nav]`) instanceof HTMLElement
|
|
1792
|
+
) {
|
|
1793
|
+
return measureButtonWidthInNavigation.call(this, element);
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
const width = calcBoxWidth.call(this, element);
|
|
1797
|
+
if (width > 0) {
|
|
1798
|
+
return width;
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
return measureButtonWidthInNavigation.call(this, element);
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
/**
|
|
1805
|
+
* @private
|
|
1806
|
+
* @param {HTMLButtonElement} element
|
|
1807
|
+
* @return {number}
|
|
1808
|
+
*/
|
|
1809
|
+
function measureButtonWidthInNavigation(element) {
|
|
1810
|
+
return measureElementWidthInNavigation.call(this, element);
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
/**
|
|
1814
|
+
* @private
|
|
1815
|
+
* @param {HTMLElement} element
|
|
1816
|
+
* @param {{removeHiddenClass?: boolean}} options
|
|
1817
|
+
* @return {number}
|
|
1818
|
+
*/
|
|
1819
|
+
function measureElementWidthInNavigation(element, options = {}) {
|
|
1820
|
+
const measurementNav = ensureMeasurementNav.call(this);
|
|
1821
|
+
const cloneElement = element.cloneNode(true);
|
|
1822
|
+
prepareMeasurementElement(cloneElement, options);
|
|
1823
|
+
|
|
1824
|
+
measurementNav.appendChild(cloneElement);
|
|
1825
|
+
const width = calcBoxWidth.call(this, cloneElement);
|
|
1826
|
+
cloneElement.remove();
|
|
1827
|
+
|
|
1630
1828
|
return width;
|
|
1631
1829
|
}
|
|
1632
1830
|
|
|
1831
|
+
/**
|
|
1832
|
+
* @private
|
|
1833
|
+
* @param {HTMLElement} element
|
|
1834
|
+
* @param {{removeHiddenClass?: boolean}} options
|
|
1835
|
+
* @return {void}
|
|
1836
|
+
*/
|
|
1837
|
+
function prepareMeasurementElement(element, options = {}) {
|
|
1838
|
+
element.setAttribute("aria-hidden", "true");
|
|
1839
|
+
element.setAttribute("tabindex", "-1");
|
|
1840
|
+
element.removeAttribute("data-monster-tab-reference");
|
|
1841
|
+
element.removeAttribute("id");
|
|
1842
|
+
element.dataset.monsterMeasurement = "true";
|
|
1843
|
+
|
|
1844
|
+
if (options.removeHiddenClass === true) {
|
|
1845
|
+
element.classList.remove("hidden");
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
element.style.position = "";
|
|
1849
|
+
element.style.visibility = "";
|
|
1850
|
+
element.style.pointerEvents = "";
|
|
1851
|
+
element.style.inset = "";
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
/**
|
|
1855
|
+
* @private
|
|
1856
|
+
* @return {HTMLElement}
|
|
1857
|
+
*/
|
|
1858
|
+
function ensureMeasurementNav() {
|
|
1859
|
+
if (this[measurementNavElementSymbol] instanceof HTMLElement) {
|
|
1860
|
+
return this[measurementNavElementSymbol];
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
const nav = getDocument().createElement("nav");
|
|
1864
|
+
nav.setAttribute(ATTRIBUTE_ROLE, "nav");
|
|
1865
|
+
nav.setAttribute("aria-hidden", "true");
|
|
1866
|
+
nav.dataset.monsterMeasurement = "true";
|
|
1867
|
+
nav.style.position = "absolute";
|
|
1868
|
+
nav.style.visibility = "hidden";
|
|
1869
|
+
nav.style.pointerEvents = "none";
|
|
1870
|
+
nav.style.inset = "0 auto auto 0";
|
|
1871
|
+
nav.style.width = "max-content";
|
|
1872
|
+
nav.style.height = "auto";
|
|
1873
|
+
nav.style.overflow = "visible";
|
|
1874
|
+
|
|
1875
|
+
this[controlElementSymbol].appendChild(nav);
|
|
1876
|
+
this[measurementNavElementSymbol] = nav;
|
|
1877
|
+
return nav;
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1633
1880
|
/**
|
|
1634
1881
|
* Keep the main navigation and popper navigation models in sync with one update.
|
|
1635
1882
|
*
|
|
@@ -1641,12 +1888,86 @@ function resolveButtonWidth(ref) {
|
|
|
1641
1888
|
* @param {Object[]} popperButtons
|
|
1642
1889
|
*/
|
|
1643
1890
|
function setButtonCollections(standardButtons, popperButtons) {
|
|
1891
|
+
const signature = getButtonCollectionsSignature(standardButtons, popperButtons);
|
|
1892
|
+
if (this[buttonCollectionsSignatureSymbol] === signature) {
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
const currentSignature = getButtonCollectionsSignature(
|
|
1897
|
+
this.getOption("buttons.standard", []),
|
|
1898
|
+
this.getOption("buttons.popper", []),
|
|
1899
|
+
);
|
|
1900
|
+
if (currentSignature === signature) {
|
|
1901
|
+
this[buttonCollectionsSignatureSymbol] = signature;
|
|
1902
|
+
return;
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
this[buttonCollectionsSignatureSymbol] = signature;
|
|
1644
1906
|
this.setOption("buttons", {
|
|
1645
1907
|
standard: clone(standardButtons),
|
|
1646
1908
|
popper: clone(popperButtons),
|
|
1647
1909
|
});
|
|
1648
1910
|
}
|
|
1649
1911
|
|
|
1912
|
+
/**
|
|
1913
|
+
* @private
|
|
1914
|
+
* @param {Object[]} standardButtons
|
|
1915
|
+
* @param {Object[]} popperButtons
|
|
1916
|
+
* @return {string}
|
|
1917
|
+
*/
|
|
1918
|
+
function getButtonCollectionsSignature(standardButtons, popperButtons) {
|
|
1919
|
+
return JSON.stringify({
|
|
1920
|
+
standard: standardButtons.map(getButtonSignature),
|
|
1921
|
+
popper: popperButtons.map(getButtonSignature),
|
|
1922
|
+
});
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
/**
|
|
1926
|
+
* @private
|
|
1927
|
+
* @param {Object} button
|
|
1928
|
+
* @return {Object}
|
|
1929
|
+
*/
|
|
1930
|
+
function getButtonSignature(button) {
|
|
1931
|
+
return {
|
|
1932
|
+
reference: button?.reference,
|
|
1933
|
+
name: button?.name,
|
|
1934
|
+
tab: button?.tab,
|
|
1935
|
+
label: button?.label,
|
|
1936
|
+
error: button?.error,
|
|
1937
|
+
state: button?.state,
|
|
1938
|
+
class: button?.class,
|
|
1939
|
+
disabled: button?.disabled,
|
|
1940
|
+
title: button?.title,
|
|
1941
|
+
"aria-label": button?.["aria-label"],
|
|
1942
|
+
remove: button?.remove,
|
|
1943
|
+
kind: button?.kind,
|
|
1944
|
+
priority: button?.priority,
|
|
1945
|
+
group: button?.group,
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
/**
|
|
1950
|
+
* @private
|
|
1951
|
+
* @param {Object[]} buttons
|
|
1952
|
+
* @return {{standardButtons: Object[], popperButtons: Object[]}}
|
|
1953
|
+
*/
|
|
1954
|
+
function splitButtonsByCurrentPopperReferences(buttons) {
|
|
1955
|
+
const popperReferences = new Set(
|
|
1956
|
+
this.getOption("buttons.popper", []).map((button) => button.reference),
|
|
1957
|
+
);
|
|
1958
|
+
const standardButtons = [];
|
|
1959
|
+
const popperButtons = [];
|
|
1960
|
+
for (const button of buttons) {
|
|
1961
|
+
if (popperReferences.has(button.reference)) {
|
|
1962
|
+
popperButtons.push(button);
|
|
1963
|
+
} else {
|
|
1964
|
+
standardButtons.push(button);
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
return { standardButtons, popperButtons };
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1650
1971
|
/**
|
|
1651
1972
|
* @private
|
|
1652
1973
|
* @return {Object}
|
|
@@ -1676,10 +1997,10 @@ function calculateNavigationButtonsDimensions() {
|
|
|
1676
1997
|
const element = findButtonElement.call(this, ref);
|
|
1677
1998
|
if (!(element instanceof HTMLButtonElement)) continue;
|
|
1678
1999
|
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
2000
|
+
const width = measureButtonWidth.call(this, element);
|
|
2001
|
+
if (width > 0) {
|
|
2002
|
+
this[dimensionsSymbol].setVia(`data.button.${ref}`, width);
|
|
2003
|
+
}
|
|
1683
2004
|
button["class"] = new TokenList(button["class"])
|
|
1684
2005
|
.remove("invisible")
|
|
1685
2006
|
.toString();
|
|
@@ -1692,13 +2013,81 @@ function calculateNavigationButtonsDimensions() {
|
|
|
1692
2013
|
slot.classList.remove("invisible");
|
|
1693
2014
|
}
|
|
1694
2015
|
|
|
1695
|
-
|
|
2016
|
+
const { standardButtons, popperButtons } =
|
|
2017
|
+
splitButtonsByCurrentPopperReferences.call(this, buttons);
|
|
2018
|
+
|
|
2019
|
+
resolveSwitchWidth.call(this, true);
|
|
2020
|
+
setButtonCollections.call(this, standardButtons, popperButtons);
|
|
1696
2021
|
|
|
1697
2022
|
getWindow().requestAnimationFrame(() => {
|
|
1698
2023
|
this[dimensionsSymbol].setVia("data.calculated", true);
|
|
1699
2024
|
});
|
|
1700
2025
|
}
|
|
1701
2026
|
|
|
2027
|
+
/**
|
|
2028
|
+
* @private
|
|
2029
|
+
* @param {number} space
|
|
2030
|
+
* @param {Object[]} standardButtons
|
|
2031
|
+
* @param {Object[]} popperButtons
|
|
2032
|
+
* @return {boolean}
|
|
2033
|
+
*/
|
|
2034
|
+
function shouldSuppressOscillatingTabLayout(
|
|
2035
|
+
space,
|
|
2036
|
+
standardButtons,
|
|
2037
|
+
popperButtons,
|
|
2038
|
+
) {
|
|
2039
|
+
const signature = [
|
|
2040
|
+
Math.round(space),
|
|
2041
|
+
standardButtons.map((button) => button.reference).join(","),
|
|
2042
|
+
popperButtons.map((button) => button.reference).join(","),
|
|
2043
|
+
].join("|");
|
|
2044
|
+
|
|
2045
|
+
const history = this[layoutSignatureHistorySymbol] || [];
|
|
2046
|
+
history.push({
|
|
2047
|
+
signature,
|
|
2048
|
+
time: performanceNow(),
|
|
2049
|
+
});
|
|
2050
|
+
while (history.length > LAYOUT_OSCILLATION_HISTORY_LIMIT) {
|
|
2051
|
+
history.shift();
|
|
2052
|
+
}
|
|
2053
|
+
this[layoutSignatureHistorySymbol] = history;
|
|
2054
|
+
|
|
2055
|
+
if (history.length < LAYOUT_OSCILLATION_HISTORY_LIMIT) {
|
|
2056
|
+
return false;
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
const [first, second, third, fourth] = history;
|
|
2060
|
+
if (fourth.time - first.time > LAYOUT_OSCILLATION_TIME_WINDOW_MS) {
|
|
2061
|
+
return false;
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
return (
|
|
2065
|
+
first.signature !== second.signature &&
|
|
2066
|
+
first.signature === third.signature &&
|
|
2067
|
+
second.signature === fourth.signature
|
|
2068
|
+
);
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
/**
|
|
2072
|
+
* @private
|
|
2073
|
+
* @return {void}
|
|
2074
|
+
*/
|
|
2075
|
+
function resetLayoutSignatureHistory() {
|
|
2076
|
+
delete this[layoutSignatureHistorySymbol];
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
/**
|
|
2080
|
+
* @private
|
|
2081
|
+
* @return {number}
|
|
2082
|
+
*/
|
|
2083
|
+
function performanceNow() {
|
|
2084
|
+
const globalObject = getGlobal();
|
|
2085
|
+
if (globalObject?.performance?.now instanceof Function) {
|
|
2086
|
+
return globalObject.performance.now();
|
|
2087
|
+
}
|
|
2088
|
+
return Date.now();
|
|
2089
|
+
}
|
|
2090
|
+
|
|
1702
2091
|
/**
|
|
1703
2092
|
* @private
|
|
1704
2093
|
* @param {string} ref
|
|
@@ -292,6 +292,52 @@ describe('Select', function () {
|
|
|
292
292
|
expect(mutateCount).to.equal(2);
|
|
293
293
|
});
|
|
294
294
|
|
|
295
|
+
it('should suppress repeated safe floating layout oscillation', async function () {
|
|
296
|
+
const popper = document.createElement('div');
|
|
297
|
+
const rects = [
|
|
298
|
+
{x: 0, y: 0, width: 100, height: 40},
|
|
299
|
+
{x: 0, y: 0, width: 120, height: 40},
|
|
300
|
+
{x: 0, y: 0, width: 100, height: 40},
|
|
301
|
+
{x: 0, y: 0, width: 120, height: 40},
|
|
302
|
+
{x: 0, y: 0, width: 100, height: 40},
|
|
303
|
+
];
|
|
304
|
+
let mutateCount = 0;
|
|
305
|
+
|
|
306
|
+
popper.getBoundingClientRect = () => {
|
|
307
|
+
const rect = rects[Math.min(mutateCount - 1, rects.length - 1)];
|
|
308
|
+
return {
|
|
309
|
+
...rect,
|
|
310
|
+
top: rect.y,
|
|
311
|
+
left: rect.x,
|
|
312
|
+
right: rect.x + rect.width,
|
|
313
|
+
bottom: rect.y + rect.height,
|
|
314
|
+
};
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const mutate = () => {
|
|
318
|
+
mutateCount += 1;
|
|
319
|
+
enqueueFloatingLayout({
|
|
320
|
+
popperElement: popper,
|
|
321
|
+
reason: FLOATING_LAYOUT_REASON.SETTLE,
|
|
322
|
+
mutate,
|
|
323
|
+
});
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
enqueueFloatingLayout({
|
|
327
|
+
popperElement: popper,
|
|
328
|
+
reason: FLOATING_LAYOUT_REASON.RESIZE,
|
|
329
|
+
mutate,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
await flushFloatingLayoutQueueForTests();
|
|
333
|
+
await flushFloatingLayoutQueueForTests();
|
|
334
|
+
await flushFloatingLayoutQueueForTests();
|
|
335
|
+
await flushFloatingLayoutQueueForTests();
|
|
336
|
+
await flushFloatingLayoutQueueForTests();
|
|
337
|
+
|
|
338
|
+
expect(mutateCount).to.equal(4);
|
|
339
|
+
});
|
|
340
|
+
|
|
295
341
|
it('should flush floating layout queue jobs when requestAnimationFrame stalls', async function () {
|
|
296
342
|
const originalRequestAnimationFrame = global.requestAnimationFrame;
|
|
297
343
|
const originalCancelAnimationFrame = global.cancelAnimationFrame;
|
|
@@ -207,6 +207,48 @@ function waitForCondition(check, {timeout = 4000, interval = 10} = {}) {
|
|
|
207
207
|
});
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
+
function createRect({width, height = 40, x = 0, y = 0}) {
|
|
211
|
+
return {
|
|
212
|
+
width,
|
|
213
|
+
height,
|
|
214
|
+
top: y,
|
|
215
|
+
left: x,
|
|
216
|
+
right: x + width,
|
|
217
|
+
bottom: y + height,
|
|
218
|
+
x,
|
|
219
|
+
y,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function installOverflowTabGeometryMock({navWidth = 480, switchWidth = 44} = {}) {
|
|
224
|
+
const originalGetBoundingClientRect =
|
|
225
|
+
global.HTMLElement.prototype.getBoundingClientRect;
|
|
226
|
+
|
|
227
|
+
global.HTMLElement.prototype.getBoundingClientRect = function () {
|
|
228
|
+
const role = this.getAttribute?.('data-monster-role');
|
|
229
|
+
const label = this.textContent?.trim() || '';
|
|
230
|
+
|
|
231
|
+
if (role === 'nav') {
|
|
232
|
+
return createRect({width: navWidth});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (role === 'button') {
|
|
236
|
+
return createRect({width: Math.max(70, label.length * 11)});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (role === 'switch') {
|
|
240
|
+
return createRect({width: switchWidth});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return originalGetBoundingClientRect.call(this);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
return () => {
|
|
247
|
+
global.HTMLElement.prototype.getBoundingClientRect =
|
|
248
|
+
originalGetBoundingClientRect;
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
210
252
|
describe('Tabs', function () {
|
|
211
253
|
|
|
212
254
|
before(function (done) {
|
|
@@ -398,44 +440,7 @@ describe('Tabs', function () {
|
|
|
398
440
|
window.ResizeObserver = OriginalResizeObserver;
|
|
399
441
|
global.ResizeObserver = originalGlobalResizeObserver;
|
|
400
442
|
};
|
|
401
|
-
|
|
402
|
-
global.HTMLElement.prototype.getBoundingClientRect;
|
|
403
|
-
restoreBoundingClientRect = () => {
|
|
404
|
-
global.HTMLElement.prototype.getBoundingClientRect =
|
|
405
|
-
originalGetBoundingClientRect;
|
|
406
|
-
};
|
|
407
|
-
global.HTMLElement.prototype.getBoundingClientRect = function () {
|
|
408
|
-
const role = this.getAttribute?.('data-monster-role');
|
|
409
|
-
const label = this.textContent?.trim() || '';
|
|
410
|
-
|
|
411
|
-
if (role === 'nav') {
|
|
412
|
-
return {
|
|
413
|
-
width: 480,
|
|
414
|
-
height: 40,
|
|
415
|
-
top: 0,
|
|
416
|
-
left: 0,
|
|
417
|
-
right: 480,
|
|
418
|
-
bottom: 40,
|
|
419
|
-
x: 0,
|
|
420
|
-
y: 0,
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
if (role === 'button') {
|
|
425
|
-
return {
|
|
426
|
-
width: Math.max(70, label.length * 11),
|
|
427
|
-
height: 40,
|
|
428
|
-
top: 0,
|
|
429
|
-
left: 0,
|
|
430
|
-
right: 0,
|
|
431
|
-
bottom: 40,
|
|
432
|
-
x: 0,
|
|
433
|
-
y: 0,
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
return originalGetBoundingClientRect.call(this);
|
|
438
|
-
};
|
|
443
|
+
restoreBoundingClientRect = installOverflowTabGeometryMock();
|
|
439
444
|
|
|
440
445
|
mocks.innerHTML = htmlOverflow;
|
|
441
446
|
|
|
@@ -456,8 +461,10 @@ describe('Tabs', function () {
|
|
|
456
461
|
switchButton.classList.contains('hidden') === false
|
|
457
462
|
);
|
|
458
463
|
}).then(() => {
|
|
464
|
+
let tabs;
|
|
465
|
+
let originalSetOption;
|
|
459
466
|
try {
|
|
460
|
-
|
|
467
|
+
tabs = document.getElementById('overflow-tabs');
|
|
461
468
|
expect(tabs).is.instanceof(Tabs);
|
|
462
469
|
const switchButton = tabs.shadowRoot.querySelector(
|
|
463
470
|
'[data-monster-role="switch"]',
|
|
@@ -475,29 +482,43 @@ describe('Tabs', function () {
|
|
|
475
482
|
(observer) => observer.elements.includes(tabs.shadowRoot.querySelector('[data-monster-role="nav"]')),
|
|
476
483
|
);
|
|
477
484
|
expect(resizeObserver).to.not.equal(undefined);
|
|
485
|
+
|
|
486
|
+
originalSetOption = tabs.setOption.bind(tabs);
|
|
487
|
+
let resetAllTabsDuringResize = false;
|
|
488
|
+
tabs.setOption = function (path, value) {
|
|
489
|
+
if (
|
|
490
|
+
path === 'buttons' &&
|
|
491
|
+
value?.standard?.length === 11 &&
|
|
492
|
+
value?.popper?.length === 0
|
|
493
|
+
) {
|
|
494
|
+
resetAllTabsDuringResize = true;
|
|
495
|
+
}
|
|
496
|
+
return originalSetOption(path, value);
|
|
497
|
+
};
|
|
498
|
+
|
|
478
499
|
resizeObserver.triggerResize([]);
|
|
479
500
|
|
|
480
501
|
return waitForCondition(() => {
|
|
481
|
-
return
|
|
482
|
-
TrackingResizeObserver.callbackCount > 0 &&
|
|
483
|
-
tabs.getOption('buttons.standard').length === 11 &&
|
|
484
|
-
tabs.getOption('buttons.popper').length === 0
|
|
485
|
-
);
|
|
502
|
+
return TrackingResizeObserver.callbackCount > 0;
|
|
486
503
|
}).then(() => {
|
|
487
|
-
return
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
504
|
+
return new Promise((resolve) => setTimeout(resolve, 260));
|
|
505
|
+
}).then(() => {
|
|
506
|
+
return new Promise((resolve) => requestAnimationFrame(resolve));
|
|
507
|
+
}).then(() => {
|
|
508
|
+
tabs.setOption = originalSetOption;
|
|
509
|
+
expect(resetAllTabsDuringResize).to.equal(false);
|
|
510
|
+
standardButtons = tabs.getOption('buttons.standard');
|
|
511
|
+
popperButtons = tabs.getOption('buttons.popper');
|
|
512
|
+
expect(standardButtons.length).to.be.greaterThan(0);
|
|
513
|
+
expect(popperButtons.length).to.be.greaterThan(0);
|
|
514
|
+
expect(standardButtons.length + popperButtons.length).to.equal(11);
|
|
515
|
+
expect(
|
|
516
|
+
new Set(
|
|
517
|
+
standardButtons
|
|
518
|
+
.concat(popperButtons)
|
|
519
|
+
.map((button) => button.reference),
|
|
520
|
+
).size,
|
|
521
|
+
).to.equal(11);
|
|
501
522
|
}).then(() => {
|
|
502
523
|
restoreBoundingClientRect();
|
|
503
524
|
restoreBoundingClientRect = null;
|
|
@@ -506,6 +527,9 @@ describe('Tabs', function () {
|
|
|
506
527
|
done();
|
|
507
528
|
});
|
|
508
529
|
} catch (e) {
|
|
530
|
+
if (tabs && originalSetOption) {
|
|
531
|
+
tabs.setOption = originalSetOption;
|
|
532
|
+
}
|
|
509
533
|
restoreBoundingClientRect();
|
|
510
534
|
restoreBoundingClientRect = null;
|
|
511
535
|
restoreResizeObserver();
|
|
@@ -513,6 +537,13 @@ describe('Tabs', function () {
|
|
|
513
537
|
return done(e);
|
|
514
538
|
}
|
|
515
539
|
}).catch((e) => {
|
|
540
|
+
const tabs = document.getElementById('overflow-tabs');
|
|
541
|
+
if (
|
|
542
|
+
tabs &&
|
|
543
|
+
Object.prototype.hasOwnProperty.call(tabs, 'setOption')
|
|
544
|
+
) {
|
|
545
|
+
delete tabs.setOption;
|
|
546
|
+
}
|
|
516
547
|
if (restoreBoundingClientRect instanceof Function) {
|
|
517
548
|
restoreBoundingClientRect();
|
|
518
549
|
restoreBoundingClientRect = null;
|
|
@@ -525,6 +556,86 @@ describe('Tabs', function () {
|
|
|
525
556
|
});
|
|
526
557
|
});
|
|
527
558
|
|
|
559
|
+
it('should preserve the overflow split and recalculate widths while rebuilding tab buttons', async function () {
|
|
560
|
+
this.timeout(5000);
|
|
561
|
+
|
|
562
|
+
const mocks = document.getElementById('mocks');
|
|
563
|
+
restoreBoundingClientRect = installOverflowTabGeometryMock();
|
|
564
|
+
|
|
565
|
+
let tabs;
|
|
566
|
+
let originalSetOption;
|
|
567
|
+
|
|
568
|
+
try {
|
|
569
|
+
mocks.innerHTML = htmlOverflow;
|
|
570
|
+
|
|
571
|
+
await waitForCondition(() => {
|
|
572
|
+
tabs = document.getElementById('overflow-tabs');
|
|
573
|
+
const standardButtons = tabs?.getOption?.('buttons.standard') || [];
|
|
574
|
+
const popperButtons = tabs?.getOption?.('buttons.popper') || [];
|
|
575
|
+
|
|
576
|
+
return (
|
|
577
|
+
tabs instanceof Tabs &&
|
|
578
|
+
standardButtons.length > 0 &&
|
|
579
|
+
popperButtons.length > 0 &&
|
|
580
|
+
standardButtons.length + popperButtons.length === 11
|
|
581
|
+
);
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
const initialPopperCount = tabs.getOption('buttons.popper').length;
|
|
585
|
+
const standardReference = tabs.getOption('buttons.standard.0.reference');
|
|
586
|
+
const panel = document.getElementById(standardReference);
|
|
587
|
+
expect(panel).to.not.equal(null);
|
|
588
|
+
|
|
589
|
+
originalSetOption = tabs.setOption.bind(tabs);
|
|
590
|
+
let resetAllTabsDuringRebuild = false;
|
|
591
|
+
tabs.setOption = function (path, value) {
|
|
592
|
+
if (
|
|
593
|
+
path === 'buttons' &&
|
|
594
|
+
value?.standard?.length === 11 &&
|
|
595
|
+
value?.popper?.length === 0
|
|
596
|
+
) {
|
|
597
|
+
resetAllTabsDuringRebuild = true;
|
|
598
|
+
}
|
|
599
|
+
return originalSetOption(path, value);
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
panel.setAttribute(
|
|
603
|
+
'data-monster-button-label',
|
|
604
|
+
'Aktualisierte sehr lange Tab Beschriftung mit viel mehr Platzbedarf',
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
await waitForCondition(() => {
|
|
608
|
+
return tabs
|
|
609
|
+
.getOption('buttons.standard', [])
|
|
610
|
+
.concat(tabs.getOption('buttons.popper', []))
|
|
611
|
+
.some((button) => {
|
|
612
|
+
return (
|
|
613
|
+
button.reference === standardReference &&
|
|
614
|
+
button.label.includes('Aktualisierte')
|
|
615
|
+
);
|
|
616
|
+
});
|
|
617
|
+
});
|
|
618
|
+
await waitForCondition(() => {
|
|
619
|
+
return tabs.getOption('buttons.popper').length > initialPopperCount;
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
tabs.setOption = originalSetOption;
|
|
623
|
+
expect(resetAllTabsDuringRebuild).to.equal(false);
|
|
624
|
+
expect(tabs.getOption('buttons.popper').length).to.be.greaterThan(0);
|
|
625
|
+
expect(
|
|
626
|
+
tabs
|
|
627
|
+
.getOption('buttons.standard')
|
|
628
|
+
.concat(tabs.getOption('buttons.popper')).length,
|
|
629
|
+
).to.equal(11);
|
|
630
|
+
} finally {
|
|
631
|
+
if (tabs && originalSetOption) {
|
|
632
|
+
tabs.setOption = originalSetOption;
|
|
633
|
+
}
|
|
634
|
+
restoreBoundingClientRect();
|
|
635
|
+
restoreBoundingClientRect = null;
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
|
|
528
639
|
it('should ignore unavailable panels when creating tabs and buttons', function (done) {
|
|
529
640
|
let mocks = document.getElementById('mocks');
|
|
530
641
|
mocks.innerHTML = htmlAvailability;
|