@schukai/monster 4.140.2 → 4.141.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.
|
|
1
|
+
{"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.141.0"}
|
|
@@ -36,11 +36,13 @@ import { getDocument } from "../../dom/util.mjs";
|
|
|
36
36
|
import { getGlobal } from "../../types/global.mjs";
|
|
37
37
|
import { ID } from "../../types/id.mjs";
|
|
38
38
|
import { Observer } from "../../types/observer.mjs";
|
|
39
|
+
import { Queue } from "../../types/queue.mjs";
|
|
39
40
|
import { STYLE_DISPLAY_MODE_BLOCK } from "./constants.mjs";
|
|
40
41
|
import { ControlBarStyleSheet } from "./stylesheet/control-bar.mjs";
|
|
41
42
|
import { positionPopper } from "./util/floating-ui.mjs";
|
|
42
43
|
import { convertToPixels } from "../../dom/dimension.mjs";
|
|
43
44
|
import { addErrorAttribute } from "../../dom/error.mjs";
|
|
45
|
+
import { DeadMansSwitch } from "../../util/deadmansswitch.mjs";
|
|
44
46
|
import { Processing } from "../../util/processing.mjs";
|
|
45
47
|
export { ControlBar };
|
|
46
48
|
|
|
@@ -132,6 +134,11 @@ const layoutTokenSymbol = Symbol("layoutToken");
|
|
|
132
134
|
const observedLayoutNodesSignatureSymbol = Symbol(
|
|
133
135
|
"observedLayoutNodesSignature",
|
|
134
136
|
);
|
|
137
|
+
const layoutChangedEventQueueSymbol = Symbol("layoutChangedEventQueue");
|
|
138
|
+
const layoutChangedEventSwitchSymbol = Symbol("layoutChangedEventSwitch");
|
|
139
|
+
const layoutChangedEventLastSignatureSymbol = Symbol(
|
|
140
|
+
"layoutChangedEventLastSignature",
|
|
141
|
+
);
|
|
135
142
|
|
|
136
143
|
/**
|
|
137
144
|
* @private
|
|
@@ -152,6 +159,45 @@ const ATTRIBUTE_POPPER_POSITION = "data-monster-popper-position";
|
|
|
152
159
|
*/
|
|
153
160
|
const ATTRIBUTE_LAYOUT_ALIGNMENT = "data-monster-layout-alignment";
|
|
154
161
|
|
|
162
|
+
/**
|
|
163
|
+
* @private
|
|
164
|
+
* @type {string}
|
|
165
|
+
*/
|
|
166
|
+
const ATTRIBUTE_LAYOUT_STACKED = "data-monster-layout-stacked";
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @private
|
|
170
|
+
* @type {string}
|
|
171
|
+
*/
|
|
172
|
+
const ATTRIBUTE_OPTION_LAYOUT_ALIGNMENT =
|
|
173
|
+
"data-monster-option-layout-alignment";
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* @private
|
|
177
|
+
* @type {string}
|
|
178
|
+
*/
|
|
179
|
+
const ATTRIBUTE_OPTION_LAYOUT_STACKED_ALIGNMENT =
|
|
180
|
+
"data-monster-option-layout-stacked-alignment";
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* @private
|
|
184
|
+
* @type {string}
|
|
185
|
+
*/
|
|
186
|
+
const ATTRIBUTE_OPTION_LAYOUT_STACKED_BREAKPOINT =
|
|
187
|
+
"data-monster-option-layout-stacked-breakpoint";
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* @private
|
|
191
|
+
* @type {string}
|
|
192
|
+
*/
|
|
193
|
+
const EVENT_LAYOUT_CHANGED = "monster-control-bar-layout-changed";
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* @private
|
|
197
|
+
* @type {number}
|
|
198
|
+
*/
|
|
199
|
+
const LAYOUT_CHANGED_EVENT_DELAY = 20;
|
|
200
|
+
|
|
155
201
|
/**
|
|
156
202
|
* A control bar control.
|
|
157
203
|
*
|
|
@@ -193,6 +239,8 @@ class ControlBar extends CustomElement {
|
|
|
193
239
|
labels: {},
|
|
194
240
|
layout: {
|
|
195
241
|
alignment: "left",
|
|
242
|
+
stackedAlignment: undefined,
|
|
243
|
+
stackedBreakpoint: undefined,
|
|
196
244
|
},
|
|
197
245
|
popper: {
|
|
198
246
|
placement: "left",
|
|
@@ -228,7 +276,9 @@ class ControlBar extends CustomElement {
|
|
|
228
276
|
// setup structure
|
|
229
277
|
initControlBar.call(this);
|
|
230
278
|
initPopperSwitch.call(this);
|
|
279
|
+
setLayoutStackedState.call(this, false);
|
|
231
280
|
applyLayoutAlignment.call(this);
|
|
281
|
+
this[layoutChangedEventQueueSymbol] = new Queue();
|
|
232
282
|
this.attachObserver(
|
|
233
283
|
new Observer(() => {
|
|
234
284
|
applyLayoutAlignment.call(this);
|
|
@@ -236,6 +286,25 @@ class ControlBar extends CustomElement {
|
|
|
236
286
|
);
|
|
237
287
|
}
|
|
238
288
|
|
|
289
|
+
/**
|
|
290
|
+
* Set option and sync layout state for reactive layout options.
|
|
291
|
+
*
|
|
292
|
+
* @param {string} path
|
|
293
|
+
* @param {*} value
|
|
294
|
+
* @return {ControlBar}
|
|
295
|
+
*/
|
|
296
|
+
setOption(path, value) {
|
|
297
|
+
super.setOption(path, value);
|
|
298
|
+
if (
|
|
299
|
+
path === "layout.alignment" ||
|
|
300
|
+
path === "layout.stackedAlignment" ||
|
|
301
|
+
path === "layout.stackedBreakpoint"
|
|
302
|
+
) {
|
|
303
|
+
syncLayoutState.call(this);
|
|
304
|
+
}
|
|
305
|
+
return this;
|
|
306
|
+
}
|
|
307
|
+
|
|
239
308
|
/**
|
|
240
309
|
* This method is called internal and should not be called directly.
|
|
241
310
|
*
|
|
@@ -280,6 +349,9 @@ class ControlBar extends CustomElement {
|
|
|
280
349
|
static get observedAttributes() {
|
|
281
350
|
const attributes = super.observedAttributes;
|
|
282
351
|
attributes.push(ATTRIBUTE_POPPER_POSITION);
|
|
352
|
+
attributes.push(ATTRIBUTE_OPTION_LAYOUT_ALIGNMENT);
|
|
353
|
+
attributes.push(ATTRIBUTE_OPTION_LAYOUT_STACKED_ALIGNMENT);
|
|
354
|
+
attributes.push(ATTRIBUTE_OPTION_LAYOUT_STACKED_BREAKPOINT);
|
|
283
355
|
return attributes;
|
|
284
356
|
}
|
|
285
357
|
|
|
@@ -305,6 +377,7 @@ class ControlBar extends CustomElement {
|
|
|
305
377
|
this[layoutTokenSymbol] = (this[layoutTokenSymbol] || 0) + 1;
|
|
306
378
|
|
|
307
379
|
disconnectResizeObserver.call(this);
|
|
380
|
+
defuseLayoutChangedEvent.call(this);
|
|
308
381
|
if (this[mutationObserverSymbol]) {
|
|
309
382
|
this[mutationObserverSymbol].disconnect();
|
|
310
383
|
}
|
|
@@ -457,11 +530,25 @@ function initEventHandler() {
|
|
|
457
530
|
});
|
|
458
531
|
}
|
|
459
532
|
|
|
460
|
-
self[attributeObserverSymbol][ATTRIBUTE_POPPER_POSITION] =
|
|
533
|
+
self[attributeObserverSymbol][ATTRIBUTE_POPPER_POSITION] = (value) => {
|
|
461
534
|
self.setOption("popper.placement", value);
|
|
462
535
|
updatePopper.call(self);
|
|
463
536
|
};
|
|
464
537
|
|
|
538
|
+
self[attributeObserverSymbol][ATTRIBUTE_OPTION_LAYOUT_ALIGNMENT] = () => {
|
|
539
|
+
syncLayoutState.call(self);
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
self[attributeObserverSymbol][ATTRIBUTE_OPTION_LAYOUT_STACKED_ALIGNMENT] =
|
|
543
|
+
() => {
|
|
544
|
+
syncLayoutState.call(self);
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
self[attributeObserverSymbol][ATTRIBUTE_OPTION_LAYOUT_STACKED_BREAKPOINT] =
|
|
548
|
+
() => {
|
|
549
|
+
syncLayoutState.call(self);
|
|
550
|
+
};
|
|
551
|
+
|
|
465
552
|
self[resizeObserverSymbol] = new ResizeObserver(() => {
|
|
466
553
|
scheduleLayout.call(self, { measure: true, layout: true });
|
|
467
554
|
});
|
|
@@ -628,7 +715,7 @@ function rearrangeItems() {
|
|
|
628
715
|
let itemWidth = 0;
|
|
629
716
|
try {
|
|
630
717
|
itemWidth = this[dimensionsSymbol].getVia(`data.item.${ref}`);
|
|
631
|
-
} catch
|
|
718
|
+
} catch {
|
|
632
719
|
// If the path does not exist, pathfinder throws an error.
|
|
633
720
|
// In this case, we assume the width is 0.
|
|
634
721
|
// This can happen for items that have never been visible.
|
|
@@ -674,8 +761,11 @@ function rearrangeItems() {
|
|
|
674
761
|
|
|
675
762
|
const shouldShowSwitch =
|
|
676
763
|
layout.itemsToMoveToPopper.length > 0 && hasItems;
|
|
764
|
+
const shouldUseStackedLayout =
|
|
765
|
+
shouldShowSwitch || isStackedBreakpointMatched.call(this);
|
|
677
766
|
|
|
678
767
|
suppressLayoutFeedback.call(this);
|
|
768
|
+
setLayoutStackedState.call(this, shouldUseStackedLayout);
|
|
679
769
|
|
|
680
770
|
for (const item of layout.itemsToMoveToPopper) {
|
|
681
771
|
if (item.getAttribute("slot") !== "popper") {
|
|
@@ -692,6 +782,7 @@ function rearrangeItems() {
|
|
|
692
782
|
setSwitchVisible.call(this, shouldShowSwitch);
|
|
693
783
|
updateControlSizing.call(this, layout, shouldShowSwitch);
|
|
694
784
|
updateJoinedBorders.call(this, layout, shouldShowSwitch);
|
|
785
|
+
applyLayoutAlignment.call(this);
|
|
695
786
|
if (!shouldShowSwitch) {
|
|
696
787
|
hide.call(this);
|
|
697
788
|
}
|
|
@@ -1136,6 +1227,7 @@ function calculateControlBarDimensions() {
|
|
|
1136
1227
|
}
|
|
1137
1228
|
|
|
1138
1229
|
this[dimensionsSymbol].setVia("data.visible", !(width === 0));
|
|
1230
|
+
this[dimensionsSymbol].setVia("data.containerWidth", width);
|
|
1139
1231
|
|
|
1140
1232
|
const itemReferences = [];
|
|
1141
1233
|
|
|
@@ -1398,17 +1490,204 @@ function applyLayoutAlignment() {
|
|
|
1398
1490
|
return;
|
|
1399
1491
|
}
|
|
1400
1492
|
|
|
1401
|
-
const
|
|
1493
|
+
const layoutState = this[layoutStateSymbol] || {};
|
|
1494
|
+
const stackedAlignment = this.getOption("layout.stackedAlignment");
|
|
1495
|
+
let alignment = this.getOption("layout.alignment", "left");
|
|
1496
|
+
if (
|
|
1497
|
+
layoutState.stacked === true &&
|
|
1498
|
+
typeof stackedAlignment === "string" &&
|
|
1499
|
+
stackedAlignment !== ""
|
|
1500
|
+
) {
|
|
1501
|
+
alignment = stackedAlignment;
|
|
1502
|
+
}
|
|
1402
1503
|
|
|
1403
1504
|
if (alignment === "right") {
|
|
1404
|
-
|
|
1505
|
+
if (setAttributeIfChanged(
|
|
1506
|
+
this[controlBarElementSymbol],
|
|
1405
1507
|
ATTRIBUTE_LAYOUT_ALIGNMENT,
|
|
1406
1508
|
"right",
|
|
1407
|
-
)
|
|
1509
|
+
)) {
|
|
1510
|
+
queueLayoutChangedEvent.call(this);
|
|
1511
|
+
}
|
|
1512
|
+
return;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
if (setAttributeIfChanged(
|
|
1516
|
+
this[controlBarElementSymbol],
|
|
1517
|
+
ATTRIBUTE_LAYOUT_ALIGNMENT,
|
|
1518
|
+
"left",
|
|
1519
|
+
)) {
|
|
1520
|
+
queueLayoutChangedEvent.call(this);
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
/**
|
|
1525
|
+
* @private
|
|
1526
|
+
* @return {void}
|
|
1527
|
+
*/
|
|
1528
|
+
function syncLayoutState() {
|
|
1529
|
+
applyLayoutAlignment.call(this);
|
|
1530
|
+
scheduleLayout.call(this, { measure: true, layout: true });
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
/**
|
|
1534
|
+
* @private
|
|
1535
|
+
* @return {boolean}
|
|
1536
|
+
*/
|
|
1537
|
+
function isStackedBreakpointMatched() {
|
|
1538
|
+
const breakpoint = this.getOption("layout.stackedBreakpoint");
|
|
1539
|
+
if (typeof breakpoint !== "string" || breakpoint.trim() === "") {
|
|
1540
|
+
return false;
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
let width = 0;
|
|
1544
|
+
try {
|
|
1545
|
+
width = this[dimensionsSymbol].getVia("data.containerWidth");
|
|
1546
|
+
} catch {
|
|
1547
|
+
try {
|
|
1548
|
+
width = this[dimensionsSymbol].getVia("data.space");
|
|
1549
|
+
} catch {}
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
if (!(width > 0)) {
|
|
1553
|
+
return false;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
const breakpointWidth = getComputedCssPixels(breakpoint);
|
|
1557
|
+
if (!(breakpointWidth > 0)) {
|
|
1558
|
+
return false;
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
return width <= breakpointWidth;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
/**
|
|
1565
|
+
* @private
|
|
1566
|
+
* @param {boolean} stacked
|
|
1567
|
+
* @return {void}
|
|
1568
|
+
*/
|
|
1569
|
+
function setLayoutStackedState(stacked) {
|
|
1570
|
+
if (!this[layoutStateSymbol]) {
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1573
|
+
this[layoutStateSymbol].stacked = stacked === true;
|
|
1574
|
+
|
|
1575
|
+
if (!(this[controlBarElementSymbol] instanceof HTMLElement)) {
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
if (setAttributeIfChanged(
|
|
1580
|
+
this[controlBarElementSymbol],
|
|
1581
|
+
ATTRIBUTE_LAYOUT_STACKED,
|
|
1582
|
+
this[layoutStateSymbol].stacked ? "true" : "false",
|
|
1583
|
+
)) {
|
|
1584
|
+
queueLayoutChangedEvent.call(this);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
/**
|
|
1589
|
+
* @private
|
|
1590
|
+
* @return {void}
|
|
1591
|
+
*/
|
|
1592
|
+
function queueLayoutChangedEvent() {
|
|
1593
|
+
if (!(this[layoutChangedEventQueueSymbol] instanceof Queue)) {
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
this[layoutChangedEventQueueSymbol].add(getLayoutChangedEventDetail.call(this));
|
|
1598
|
+
|
|
1599
|
+
if (this[layoutChangedEventSwitchSymbol] instanceof DeadMansSwitch) {
|
|
1600
|
+
this[layoutChangedEventSwitchSymbol].touch();
|
|
1601
|
+
return;
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
this[layoutChangedEventSwitchSymbol] = new DeadMansSwitch(
|
|
1605
|
+
LAYOUT_CHANGED_EVENT_DELAY,
|
|
1606
|
+
() => {
|
|
1607
|
+
this[layoutChangedEventSwitchSymbol] = undefined;
|
|
1608
|
+
dispatchQueuedLayoutChangedEvent.call(this);
|
|
1609
|
+
},
|
|
1610
|
+
);
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
/**
|
|
1614
|
+
* @private
|
|
1615
|
+
* @return {void}
|
|
1616
|
+
*/
|
|
1617
|
+
function dispatchQueuedLayoutChangedEvent() {
|
|
1618
|
+
if (!(this[layoutChangedEventQueueSymbol] instanceof Queue)) {
|
|
1619
|
+
return;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
let detail;
|
|
1623
|
+
while (!this[layoutChangedEventQueueSymbol].isEmpty()) {
|
|
1624
|
+
detail = this[layoutChangedEventQueueSymbol].poll();
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
if (!detail) {
|
|
1628
|
+
return;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
const signature = JSON.stringify(detail);
|
|
1632
|
+
if (this[layoutChangedEventLastSignatureSymbol] === signature) {
|
|
1408
1633
|
return;
|
|
1409
1634
|
}
|
|
1635
|
+
this[layoutChangedEventLastSignatureSymbol] = signature;
|
|
1636
|
+
|
|
1637
|
+
this.dispatchEvent(
|
|
1638
|
+
new CustomEvent(EVENT_LAYOUT_CHANGED, {
|
|
1639
|
+
bubbles: true,
|
|
1640
|
+
composed: true,
|
|
1641
|
+
detail,
|
|
1642
|
+
}),
|
|
1643
|
+
);
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
/**
|
|
1647
|
+
* @private
|
|
1648
|
+
* @return {void}
|
|
1649
|
+
*/
|
|
1650
|
+
function defuseLayoutChangedEvent() {
|
|
1651
|
+
if (this[layoutChangedEventSwitchSymbol] instanceof DeadMansSwitch) {
|
|
1652
|
+
this[layoutChangedEventSwitchSymbol].defuse();
|
|
1653
|
+
this[layoutChangedEventSwitchSymbol] = undefined;
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
/**
|
|
1658
|
+
* @private
|
|
1659
|
+
* @return {Object}
|
|
1660
|
+
*/
|
|
1661
|
+
function getLayoutChangedEventDetail() {
|
|
1662
|
+
const layoutState = this[layoutStateSymbol] || {};
|
|
1663
|
+
const stacked = layoutState.stacked === true;
|
|
1664
|
+
const alignment = this[controlBarElementSymbol]?.getAttribute(
|
|
1665
|
+
ATTRIBUTE_LAYOUT_ALIGNMENT,
|
|
1666
|
+
);
|
|
1667
|
+
|
|
1668
|
+
return {
|
|
1669
|
+
alignment: alignment || this.getOption("layout.alignment", "left"),
|
|
1670
|
+
configuredAlignment: this.getOption("layout.alignment", "left"),
|
|
1671
|
+
stacked,
|
|
1672
|
+
stackedAlignment: this.getOption("layout.stackedAlignment"),
|
|
1673
|
+
stackedBreakpoint: this.getOption("layout.stackedBreakpoint"),
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
/**
|
|
1678
|
+
* @private
|
|
1679
|
+
* @param {HTMLElement} element
|
|
1680
|
+
* @param {string} attribute
|
|
1681
|
+
* @param {string} value
|
|
1682
|
+
* @return {boolean}
|
|
1683
|
+
*/
|
|
1684
|
+
function setAttributeIfChanged(element, attribute, value) {
|
|
1685
|
+
if (element.getAttribute(attribute) !== value) {
|
|
1686
|
+
element.setAttribute(attribute, value);
|
|
1687
|
+
return true;
|
|
1688
|
+
}
|
|
1410
1689
|
|
|
1411
|
-
|
|
1690
|
+
return false;
|
|
1412
1691
|
}
|
|
1413
1692
|
|
|
1414
1693
|
/**
|
|
@@ -3,7 +3,7 @@ import { chaiDom } from "../../../util/chai-dom.mjs";
|
|
|
3
3
|
import { initJSDOM } from "../../../util/jsdom.mjs";
|
|
4
4
|
import { ResizeObserverMock } from "../../../util/resize-observer.mjs";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
const expect = chai.expect;
|
|
7
7
|
chai.use(chaiDom);
|
|
8
8
|
|
|
9
9
|
const html = `
|
|
@@ -103,6 +103,68 @@ describe("ControlBar", function () {
|
|
|
103
103
|
}, 50);
|
|
104
104
|
});
|
|
105
105
|
|
|
106
|
+
it("should update the rendered layout alignment when the option changes", async function () {
|
|
107
|
+
const bar = document.getElementById("bar-right");
|
|
108
|
+
|
|
109
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
110
|
+
|
|
111
|
+
const controlBar = bar.shadowRoot.querySelector(
|
|
112
|
+
'[data-monster-role="control-bar"]',
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
expect(controlBar.getAttribute("data-monster-layout-alignment")).to.equal(
|
|
116
|
+
"right",
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
bar.setOption("layout.alignment", "left");
|
|
120
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
121
|
+
|
|
122
|
+
expect(controlBar.getAttribute("data-monster-layout-alignment")).to.equal(
|
|
123
|
+
"left",
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("should update the rendered layout alignment when the host option attribute changes", async function () {
|
|
128
|
+
const bar = document.getElementById("bar");
|
|
129
|
+
const controlBar = bar.shadowRoot.querySelector(
|
|
130
|
+
'[data-monster-role="control-bar"]',
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
bar.setAttribute("data-monster-option-layout-alignment", "right");
|
|
134
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
135
|
+
|
|
136
|
+
expect(bar.getOption("layout.alignment")).to.equal("right");
|
|
137
|
+
expect(controlBar.getAttribute("data-monster-layout-alignment")).to.equal(
|
|
138
|
+
"right",
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("should dispatch one final layout changed event for rapid alignment changes", async function () {
|
|
143
|
+
const bar = document.getElementById("bar-right");
|
|
144
|
+
const events = [];
|
|
145
|
+
|
|
146
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
147
|
+
|
|
148
|
+
bar.addEventListener("monster-control-bar-layout-changed", (event) => {
|
|
149
|
+
events.push(event.detail);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
bar.setOption("layout.alignment", "left");
|
|
153
|
+
bar.setOption("layout.alignment", "right");
|
|
154
|
+
bar.setOption("layout.alignment", "left");
|
|
155
|
+
|
|
156
|
+
await new Promise((resolve) => setTimeout(resolve, 60));
|
|
157
|
+
|
|
158
|
+
expect(events).to.have.length(1);
|
|
159
|
+
expect(events[0]).to.deep.equal({
|
|
160
|
+
alignment: "left",
|
|
161
|
+
configuredAlignment: "left",
|
|
162
|
+
stacked: false,
|
|
163
|
+
stackedAlignment: undefined,
|
|
164
|
+
stackedBreakpoint: undefined,
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
106
168
|
it("should map popper position attributes to the popper placement option", function () {
|
|
107
169
|
const bar = document.getElementById("bar");
|
|
108
170
|
|
|
@@ -515,7 +577,11 @@ describe("ControlBar", function () {
|
|
|
515
577
|
const mocks = document.getElementById("mocks");
|
|
516
578
|
mocks.innerHTML = `
|
|
517
579
|
<div id="control-bar-wrapper">
|
|
518
|
-
<monster-control-bar
|
|
580
|
+
<monster-control-bar
|
|
581
|
+
id="overflow-bar"
|
|
582
|
+
data-monster-option-layout-alignment="right"
|
|
583
|
+
data-monster-option-layout-stacked-alignment="left"
|
|
584
|
+
>
|
|
519
585
|
<input id="overflow-input" type="search">
|
|
520
586
|
<select id="overflow-select"><option>One</option></select>
|
|
521
587
|
<button id="overflow-button">Run</button>
|
|
@@ -567,12 +633,21 @@ describe("ControlBar", function () {
|
|
|
567
633
|
configurable: true,
|
|
568
634
|
value: 20,
|
|
569
635
|
});
|
|
636
|
+
const controlBar = bar.shadowRoot.querySelector(
|
|
637
|
+
'[data-monster-role="control-bar"]',
|
|
638
|
+
);
|
|
570
639
|
|
|
571
640
|
await flushFrames();
|
|
572
641
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
573
642
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
574
643
|
|
|
575
644
|
expect(switchButton.hasAttribute("hidden")).to.be.false;
|
|
645
|
+
expect(controlBar.getAttribute("data-monster-layout-stacked")).to.equal(
|
|
646
|
+
"true",
|
|
647
|
+
);
|
|
648
|
+
expect(controlBar.getAttribute("data-monster-layout-alignment")).to.equal(
|
|
649
|
+
"left",
|
|
650
|
+
);
|
|
576
651
|
expect(document.getElementById("overflow-input").hasAttribute("slot")).to
|
|
577
652
|
.be.false;
|
|
578
653
|
expect(document.getElementById("overflow-select").getAttribute("slot")).to
|
|
@@ -586,4 +661,94 @@ describe("ControlBar", function () {
|
|
|
586
661
|
globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
|
|
587
662
|
}
|
|
588
663
|
});
|
|
664
|
+
|
|
665
|
+
it("should apply stacked alignment below the configured container breakpoint without overflow", async function () {
|
|
666
|
+
const originalRequestAnimationFrame = window.requestAnimationFrame;
|
|
667
|
+
const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
|
|
668
|
+
|
|
669
|
+
const scheduledCallbacks = [];
|
|
670
|
+
const flushFrames = async () => {
|
|
671
|
+
while (scheduledCallbacks.length > 0) {
|
|
672
|
+
scheduledCallbacks.shift()();
|
|
673
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
try {
|
|
678
|
+
window.requestAnimationFrame = (callback) => {
|
|
679
|
+
scheduledCallbacks.push(callback);
|
|
680
|
+
return scheduledCallbacks.length;
|
|
681
|
+
};
|
|
682
|
+
globalThis.requestAnimationFrame = window.requestAnimationFrame;
|
|
683
|
+
|
|
684
|
+
const mocks = document.getElementById("mocks");
|
|
685
|
+
mocks.innerHTML = `
|
|
686
|
+
<div id="breakpoint-bar-wrapper">
|
|
687
|
+
<monster-control-bar
|
|
688
|
+
id="breakpoint-bar"
|
|
689
|
+
data-monster-option-layout-alignment="right"
|
|
690
|
+
data-monster-option-layout-stacked-alignment="left"
|
|
691
|
+
data-monster-option-layout-stacked-breakpoint="100px"
|
|
692
|
+
>
|
|
693
|
+
<button id="breakpoint-button">Run</button>
|
|
694
|
+
</monster-control-bar>
|
|
695
|
+
</div>
|
|
696
|
+
`;
|
|
697
|
+
|
|
698
|
+
const wrapper = document.getElementById("breakpoint-bar-wrapper");
|
|
699
|
+
const button = document.getElementById("breakpoint-button");
|
|
700
|
+
const bar = document.getElementById("breakpoint-bar");
|
|
701
|
+
const controlBar = bar.shadowRoot.querySelector(
|
|
702
|
+
'[data-monster-role="control-bar"]',
|
|
703
|
+
);
|
|
704
|
+
const switchButton = bar.shadowRoot.querySelector(
|
|
705
|
+
'[data-monster-role="switch"]',
|
|
706
|
+
);
|
|
707
|
+
|
|
708
|
+
wrapper.style.boxSizing = "border-box";
|
|
709
|
+
Object.defineProperty(wrapper, "clientWidth", {
|
|
710
|
+
configurable: true,
|
|
711
|
+
value: 90,
|
|
712
|
+
});
|
|
713
|
+
Object.defineProperty(button, "offsetWidth", {
|
|
714
|
+
configurable: true,
|
|
715
|
+
value: 40,
|
|
716
|
+
});
|
|
717
|
+
Object.defineProperty(button, "offsetHeight", {
|
|
718
|
+
configurable: true,
|
|
719
|
+
value: 30,
|
|
720
|
+
});
|
|
721
|
+
button.getBoundingClientRect = () => ({
|
|
722
|
+
width: 40,
|
|
723
|
+
height: 30,
|
|
724
|
+
top: 0,
|
|
725
|
+
right: 40,
|
|
726
|
+
bottom: 30,
|
|
727
|
+
left: 0,
|
|
728
|
+
x: 0,
|
|
729
|
+
y: 0,
|
|
730
|
+
toJSON: () => {},
|
|
731
|
+
});
|
|
732
|
+
Object.defineProperty(switchButton, "offsetWidth", {
|
|
733
|
+
configurable: true,
|
|
734
|
+
value: 20,
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
await flushFrames();
|
|
738
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
739
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
740
|
+
|
|
741
|
+
expect(switchButton.hasAttribute("hidden")).to.be.true;
|
|
742
|
+
expect(button.hasAttribute("slot")).to.be.false;
|
|
743
|
+
expect(controlBar.getAttribute("data-monster-layout-stacked")).to.equal(
|
|
744
|
+
"true",
|
|
745
|
+
);
|
|
746
|
+
expect(controlBar.getAttribute("data-monster-layout-alignment")).to.equal(
|
|
747
|
+
"left",
|
|
748
|
+
);
|
|
749
|
+
} finally {
|
|
750
|
+
window.requestAnimationFrame = originalRequestAnimationFrame;
|
|
751
|
+
globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
|
|
752
|
+
}
|
|
753
|
+
});
|
|
589
754
|
});
|