@schukai/monster 4.137.4 → 4.137.5
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 +1 -1
- package/source/components/datatable/pagination.mjs +206 -41
- package/source/components/form/button-bar.mjs +122 -26
- package/source/components/navigation/site-navigation.mjs +223 -63
- package/test/cases/components/datatable/pagination.mjs +11 -0
- package/test/cases/components/form/button-bar.mjs +89 -0
- package/test/cases/components/navigation/site-navigation.mjs +104 -0
- package/test/web/import.js +1 -0
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.137.
|
|
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.137.5"}
|
|
@@ -89,6 +89,7 @@ const layoutApplySymbol = Symbol("layoutApply");
|
|
|
89
89
|
*/
|
|
90
90
|
const labelStateSymbol = Symbol("labelState");
|
|
91
91
|
const layoutModeSymbol = Symbol("layoutMode");
|
|
92
|
+
const layoutMeasurementCacheSymbol = Symbol("layoutMeasurementCache");
|
|
92
93
|
const lastNavClickTimeSymbol = Symbol("lastNavClickTime");
|
|
93
94
|
const lastNavClickTargetSymbol = Symbol("lastNavClickTarget");
|
|
94
95
|
|
|
@@ -919,43 +920,38 @@ function applyPaginationLayout() {
|
|
|
919
920
|
return;
|
|
920
921
|
}
|
|
921
922
|
|
|
922
|
-
list.classList.add("pagination-no-wrap");
|
|
923
|
-
list.setAttribute("data-monster-adaptive-ready", "false");
|
|
924
|
-
|
|
925
923
|
const prevLink = this.shadowRoot.querySelector(
|
|
926
924
|
"a[data-monster-role=pagination-prev]",
|
|
927
925
|
);
|
|
928
926
|
const nextLink = this.shadowRoot.querySelector(
|
|
929
927
|
"a[data-monster-role=pagination-next]",
|
|
930
928
|
);
|
|
929
|
+
const signature = getPaginationLayoutSignature.call(this, list);
|
|
930
|
+
const cache = getPaginationLayoutCache.call(this);
|
|
931
931
|
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
nextLink,
|
|
951
|
-
"compact-summary",
|
|
952
|
-
);
|
|
953
|
-
const widthCompact = applyPaginationMode.call(
|
|
932
|
+
if (
|
|
933
|
+
cache.lastAppliedSignature === signature &&
|
|
934
|
+
cache.lastAvailableWidth === availableWidth &&
|
|
935
|
+
cache.lastAppliedMode === this[layoutModeSymbol] &&
|
|
936
|
+
list.getAttribute("data-monster-adaptive-ready") === "true"
|
|
937
|
+
) {
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
setClassEnabled(list, "pagination-no-wrap", true);
|
|
942
|
+
setAttributeValue(list, "data-monster-adaptive-ready", "false");
|
|
943
|
+
|
|
944
|
+
const {
|
|
945
|
+
widthFull,
|
|
946
|
+
widthNoNumbers,
|
|
947
|
+
widthCompactSummary,
|
|
948
|
+
widthCompact,
|
|
949
|
+
} = measurePaginationModeWidths.call(
|
|
954
950
|
this,
|
|
955
951
|
list,
|
|
956
952
|
prevLink,
|
|
957
953
|
nextLink,
|
|
958
|
-
|
|
954
|
+
signature,
|
|
959
955
|
);
|
|
960
956
|
|
|
961
957
|
const nextMode = choosePaginationMode.call(this, {
|
|
@@ -967,7 +963,10 @@ function applyPaginationLayout() {
|
|
|
967
963
|
});
|
|
968
964
|
|
|
969
965
|
applyPaginationMode.call(this, list, prevLink, nextLink, nextMode);
|
|
970
|
-
list
|
|
966
|
+
setAttributeValue(list, "data-monster-adaptive-ready", "true");
|
|
967
|
+
cache.lastAppliedSignature = signature;
|
|
968
|
+
cache.lastAvailableWidth = availableWidth;
|
|
969
|
+
cache.lastAppliedMode = nextMode;
|
|
971
970
|
} finally {
|
|
972
971
|
this[layoutApplySymbol] = false;
|
|
973
972
|
}
|
|
@@ -998,7 +997,7 @@ function setNumberItemsVisible(list, visible) {
|
|
|
998
997
|
for (const item of numberItems) {
|
|
999
998
|
const container = item.parentElement;
|
|
1000
999
|
if (!container) continue;
|
|
1001
|
-
container
|
|
1000
|
+
setStyleValue(container, "display", visible ? "" : "none");
|
|
1002
1001
|
}
|
|
1003
1002
|
}
|
|
1004
1003
|
|
|
@@ -1014,7 +1013,7 @@ function setSummaryVisible(list, visible) {
|
|
|
1014
1013
|
if (!summaryItem) {
|
|
1015
1014
|
return;
|
|
1016
1015
|
}
|
|
1017
|
-
summaryItem
|
|
1016
|
+
setStyleValue(summaryItem, "display", visible ? "flex" : "none");
|
|
1018
1017
|
}
|
|
1019
1018
|
|
|
1020
1019
|
/**
|
|
@@ -1047,11 +1046,11 @@ function setCompactLabels(compact, prevLink, nextLink) {
|
|
|
1047
1046
|
if (!state) return;
|
|
1048
1047
|
|
|
1049
1048
|
if (compact) {
|
|
1050
|
-
|
|
1051
|
-
|
|
1049
|
+
setInnerHTML(prevLink, compactPrevIcon);
|
|
1050
|
+
setInnerHTML(nextLink, compactNextIcon);
|
|
1052
1051
|
} else {
|
|
1053
|
-
|
|
1054
|
-
|
|
1052
|
+
setInnerHTML(prevLink, state.previous);
|
|
1053
|
+
setInnerHTML(nextLink, state.next);
|
|
1055
1054
|
}
|
|
1056
1055
|
}
|
|
1057
1056
|
|
|
@@ -1161,29 +1160,29 @@ function getEmbeddedAvailableWidth(parentNode) {
|
|
|
1161
1160
|
function applyPaginationMode(list, prevLink, nextLink, mode) {
|
|
1162
1161
|
switch (mode) {
|
|
1163
1162
|
case "compact":
|
|
1164
|
-
list
|
|
1165
|
-
list
|
|
1163
|
+
setClassEnabled(list, "pagination-numbers-hidden", true);
|
|
1164
|
+
setClassEnabled(list, "pagination-compact", true);
|
|
1166
1165
|
setNumberItemsVisible(list, false);
|
|
1167
1166
|
setSummaryVisible(list, true);
|
|
1168
1167
|
setCompactLabels.call(this, true, prevLink, nextLink);
|
|
1169
1168
|
break;
|
|
1170
1169
|
case "compact-summary":
|
|
1171
|
-
list
|
|
1172
|
-
list
|
|
1170
|
+
setClassEnabled(list, "pagination-numbers-hidden", true);
|
|
1171
|
+
setClassEnabled(list, "pagination-compact", true);
|
|
1173
1172
|
setNumberItemsVisible(list, false);
|
|
1174
1173
|
setSummaryVisible(list, true);
|
|
1175
1174
|
setCompactLabels.call(this, true, prevLink, nextLink);
|
|
1176
1175
|
break;
|
|
1177
1176
|
case "no-numbers":
|
|
1178
|
-
list
|
|
1179
|
-
list
|
|
1177
|
+
setClassEnabled(list, "pagination-numbers-hidden", true);
|
|
1178
|
+
setClassEnabled(list, "pagination-compact", false);
|
|
1180
1179
|
setNumberItemsVisible(list, false);
|
|
1181
1180
|
setSummaryVisible(list, true);
|
|
1182
1181
|
setCompactLabels.call(this, false, prevLink, nextLink);
|
|
1183
1182
|
break;
|
|
1184
1183
|
default:
|
|
1185
|
-
list
|
|
1186
|
-
list
|
|
1184
|
+
setClassEnabled(list, "pagination-numbers-hidden", false);
|
|
1185
|
+
setClassEnabled(list, "pagination-compact", false);
|
|
1187
1186
|
setNumberItemsVisible(list, true);
|
|
1188
1187
|
setSummaryVisible(list, false);
|
|
1189
1188
|
setCompactLabels.call(this, false, prevLink, nextLink);
|
|
@@ -1193,6 +1192,172 @@ function applyPaginationMode(list, prevLink, nextLink, mode) {
|
|
|
1193
1192
|
return list.scrollWidth;
|
|
1194
1193
|
}
|
|
1195
1194
|
|
|
1195
|
+
/**
|
|
1196
|
+
* @private
|
|
1197
|
+
* @param {HTMLElement} list
|
|
1198
|
+
* @param {HTMLElement|null} prevLink
|
|
1199
|
+
* @param {HTMLElement|null} nextLink
|
|
1200
|
+
* @param {string} signature
|
|
1201
|
+
* @return {object}
|
|
1202
|
+
*/
|
|
1203
|
+
function measurePaginationModeWidths(list, prevLink, nextLink, signature) {
|
|
1204
|
+
const cache = getPaginationLayoutCache.call(this);
|
|
1205
|
+
if (cache.widthSignature === signature && cache.widths) {
|
|
1206
|
+
return cache.widths;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
const widths = {
|
|
1210
|
+
widthFull: applyPaginationMode.call(
|
|
1211
|
+
this,
|
|
1212
|
+
list,
|
|
1213
|
+
prevLink,
|
|
1214
|
+
nextLink,
|
|
1215
|
+
"full",
|
|
1216
|
+
),
|
|
1217
|
+
widthNoNumbers: applyPaginationMode.call(
|
|
1218
|
+
this,
|
|
1219
|
+
list,
|
|
1220
|
+
prevLink,
|
|
1221
|
+
nextLink,
|
|
1222
|
+
"no-numbers",
|
|
1223
|
+
),
|
|
1224
|
+
widthCompactSummary: applyPaginationMode.call(
|
|
1225
|
+
this,
|
|
1226
|
+
list,
|
|
1227
|
+
prevLink,
|
|
1228
|
+
nextLink,
|
|
1229
|
+
"compact-summary",
|
|
1230
|
+
),
|
|
1231
|
+
widthCompact: applyPaginationMode.call(
|
|
1232
|
+
this,
|
|
1233
|
+
list,
|
|
1234
|
+
prevLink,
|
|
1235
|
+
nextLink,
|
|
1236
|
+
"compact",
|
|
1237
|
+
),
|
|
1238
|
+
};
|
|
1239
|
+
|
|
1240
|
+
cache.widthSignature = signature;
|
|
1241
|
+
cache.widths = widths;
|
|
1242
|
+
return widths;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/**
|
|
1246
|
+
* @private
|
|
1247
|
+
* @return {object}
|
|
1248
|
+
*/
|
|
1249
|
+
function getPaginationLayoutCache() {
|
|
1250
|
+
if (!this[layoutMeasurementCacheSymbol]) {
|
|
1251
|
+
this[layoutMeasurementCacheSymbol] = {
|
|
1252
|
+
lastAppliedMode: null,
|
|
1253
|
+
lastAppliedSignature: null,
|
|
1254
|
+
lastAvailableWidth: null,
|
|
1255
|
+
widthSignature: null,
|
|
1256
|
+
widths: null,
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
return this[layoutMeasurementCacheSymbol];
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
/**
|
|
1264
|
+
* @private
|
|
1265
|
+
* @param {HTMLElement} list
|
|
1266
|
+
* @return {string}
|
|
1267
|
+
*/
|
|
1268
|
+
function getPaginationLayoutSignature(list) {
|
|
1269
|
+
const pagination = this.getOption("pagination", {});
|
|
1270
|
+
const itemSignature = Array.isArray(pagination.items)
|
|
1271
|
+
? pagination.items
|
|
1272
|
+
.map((item) => {
|
|
1273
|
+
return [
|
|
1274
|
+
item?.class || "",
|
|
1275
|
+
item?.href || "",
|
|
1276
|
+
item?.label || "",
|
|
1277
|
+
item?.no ?? "",
|
|
1278
|
+
].join(":");
|
|
1279
|
+
})
|
|
1280
|
+
.join("|")
|
|
1281
|
+
: "";
|
|
1282
|
+
|
|
1283
|
+
return [
|
|
1284
|
+
pagination.current ?? "",
|
|
1285
|
+
pagination.pages ?? "",
|
|
1286
|
+
pagination.summary ?? "",
|
|
1287
|
+
pagination.prevClass ?? "",
|
|
1288
|
+
pagination.prevHref ?? "",
|
|
1289
|
+
pagination.prevNo ?? "",
|
|
1290
|
+
pagination.nextClass ?? "",
|
|
1291
|
+
pagination.nextHref ?? "",
|
|
1292
|
+
pagination.nextNo ?? "",
|
|
1293
|
+
itemSignature,
|
|
1294
|
+
list.children.length,
|
|
1295
|
+
].join("::");
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
/**
|
|
1299
|
+
* @private
|
|
1300
|
+
* @param {HTMLElement|null} element
|
|
1301
|
+
* @param {string} className
|
|
1302
|
+
* @param {boolean} enabled
|
|
1303
|
+
* @return {void}
|
|
1304
|
+
*/
|
|
1305
|
+
function setClassEnabled(element, className, enabled) {
|
|
1306
|
+
if (!(element instanceof HTMLElement)) {
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
if (element.classList.contains(className) !== enabled) {
|
|
1310
|
+
element.classList.toggle(className, enabled);
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
/**
|
|
1315
|
+
* @private
|
|
1316
|
+
* @param {HTMLElement|null} element
|
|
1317
|
+
* @param {string} name
|
|
1318
|
+
* @param {string} value
|
|
1319
|
+
* @return {void}
|
|
1320
|
+
*/
|
|
1321
|
+
function setAttributeValue(element, name, value) {
|
|
1322
|
+
if (!(element instanceof HTMLElement)) {
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
if (element.getAttribute(name) !== value) {
|
|
1326
|
+
element.setAttribute(name, value);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
/**
|
|
1331
|
+
* @private
|
|
1332
|
+
* @param {HTMLElement|null} element
|
|
1333
|
+
* @param {string} property
|
|
1334
|
+
* @param {string} value
|
|
1335
|
+
* @return {void}
|
|
1336
|
+
*/
|
|
1337
|
+
function setStyleValue(element, property, value) {
|
|
1338
|
+
if (!(element instanceof HTMLElement)) {
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1341
|
+
if (element.style[property] !== value) {
|
|
1342
|
+
element.style[property] = value;
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
/**
|
|
1347
|
+
* @private
|
|
1348
|
+
* @param {HTMLElement|null} element
|
|
1349
|
+
* @param {string} value
|
|
1350
|
+
* @return {void}
|
|
1351
|
+
*/
|
|
1352
|
+
function setInnerHTML(element, value) {
|
|
1353
|
+
if (!(element instanceof HTMLElement)) {
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
if (element.innerHTML !== value) {
|
|
1357
|
+
element.innerHTML = value;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1196
1361
|
/**
|
|
1197
1362
|
* @private
|
|
1198
1363
|
* @param {object} params
|
|
@@ -129,6 +129,14 @@ const switchElementSymbol = Symbol("switchElement");
|
|
|
129
129
|
const layoutStateSymbol = Symbol("layoutState");
|
|
130
130
|
const layoutFrameSymbol = Symbol("layoutFrame");
|
|
131
131
|
const layoutTokenSymbol = Symbol("layoutToken");
|
|
132
|
+
const observedLayoutNodesSignatureSymbol = Symbol("observedLayoutNodesSignature");
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @private
|
|
136
|
+
* @type {WeakMap<HTMLElement, number>}
|
|
137
|
+
*/
|
|
138
|
+
const layoutNodeIds = new WeakMap();
|
|
139
|
+
let layoutNodeId = 0;
|
|
132
140
|
|
|
133
141
|
/**
|
|
134
142
|
* @private
|
|
@@ -209,6 +217,7 @@ class ButtonBar extends CustomElement {
|
|
|
209
217
|
needsLayout: true,
|
|
210
218
|
needsObserve: true,
|
|
211
219
|
suppressSlotChange: false,
|
|
220
|
+
suppressMutation: false,
|
|
212
221
|
};
|
|
213
222
|
|
|
214
223
|
initControlReferences.call(this);
|
|
@@ -385,6 +394,10 @@ function initEventHandler() {
|
|
|
385
394
|
const self = this;
|
|
386
395
|
|
|
387
396
|
const mutationCallback = (mutationList) => {
|
|
397
|
+
if (self[layoutStateSymbol]?.suppressMutation) {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
388
401
|
let needsRecalc = false;
|
|
389
402
|
for (const mutation of mutationList) {
|
|
390
403
|
if (mutation.type === "attributes") {
|
|
@@ -572,7 +585,6 @@ function runLayout() {
|
|
|
572
585
|
* @return {Object}
|
|
573
586
|
*/
|
|
574
587
|
function rearrangeButtons() {
|
|
575
|
-
const state = this[layoutStateSymbol];
|
|
576
588
|
let space = 0;
|
|
577
589
|
try {
|
|
578
590
|
space = this[dimensionsSymbol].getVia("data.space");
|
|
@@ -660,28 +672,22 @@ function rearrangeButtons() {
|
|
|
660
672
|
const shouldShowSwitch =
|
|
661
673
|
layout.buttonsToMoveToPopper.length > 0 && hasButtons;
|
|
662
674
|
|
|
663
|
-
|
|
664
|
-
state.suppressSlotChange = true;
|
|
665
|
-
}
|
|
675
|
+
suppressLayoutFeedback.call(this);
|
|
666
676
|
|
|
667
677
|
for (const button of layout.buttonsToMoveToPopper) {
|
|
668
|
-
button.
|
|
678
|
+
if (button.getAttribute("slot") !== "popper") {
|
|
679
|
+
button.setAttribute("slot", "popper");
|
|
680
|
+
}
|
|
669
681
|
}
|
|
670
682
|
|
|
671
683
|
for (const button of layout.visibleButtonsInMainSlot) {
|
|
672
|
-
button.
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
if (state) {
|
|
676
|
-
state.suppressSlotChange = false;
|
|
684
|
+
if (button.hasAttribute("slot")) {
|
|
685
|
+
button.removeAttribute("slot");
|
|
686
|
+
}
|
|
677
687
|
}
|
|
678
688
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
this[switchElementSymbol].classList.remove("hidden");
|
|
682
|
-
} else {
|
|
683
|
-
this[switchElementSymbol].setAttribute("hidden", "");
|
|
684
|
-
this[switchElementSymbol].classList.add("hidden");
|
|
689
|
+
setSwitchVisible.call(this, shouldShowSwitch);
|
|
690
|
+
if (!shouldShowSwitch) {
|
|
685
691
|
hide.call(this);
|
|
686
692
|
}
|
|
687
693
|
}
|
|
@@ -831,34 +837,123 @@ function calculateButtonBarDimensions() {
|
|
|
831
837
|
* @private
|
|
832
838
|
*/
|
|
833
839
|
function updateResizeObserverObservation() {
|
|
840
|
+
const observedNodes = getLayoutObservedNodes.call(this);
|
|
841
|
+
const signature = getLayoutObservedNodesSignature(observedNodes);
|
|
842
|
+
if (this[observedLayoutNodesSignatureSymbol] === signature) {
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
this[observedLayoutNodesSignatureSymbol] = signature;
|
|
846
|
+
|
|
834
847
|
this[resizeObserverSymbol].disconnect();
|
|
835
848
|
if (this[mutationObserverSymbol]) {
|
|
836
849
|
this[mutationObserverSymbol].disconnect();
|
|
837
850
|
}
|
|
838
851
|
|
|
839
|
-
|
|
840
|
-
slottedNodes.forEach((node) => {
|
|
852
|
+
observedNodes.forEach((node) => {
|
|
841
853
|
this[resizeObserverSymbol].observe(node);
|
|
842
|
-
if (this[mutationObserverSymbol]) {
|
|
854
|
+
if (node !== this.parentElement && this[mutationObserverSymbol]) {
|
|
843
855
|
this[mutationObserverSymbol].observe(node, {
|
|
844
856
|
attributes: true,
|
|
845
857
|
attributeFilter: ["style", "class", "hidden"],
|
|
846
858
|
});
|
|
847
859
|
}
|
|
848
860
|
});
|
|
861
|
+
}
|
|
849
862
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
863
|
+
/**
|
|
864
|
+
* @private
|
|
865
|
+
* @return {HTMLElement[]}
|
|
866
|
+
*/
|
|
867
|
+
function getLayoutObservedNodes() {
|
|
868
|
+
const observedNodes = [];
|
|
869
|
+
const slottedNodes = getSlottedElements.call(this);
|
|
870
|
+
slottedNodes.forEach((node) => {
|
|
871
|
+
if (node instanceof HTMLElement) {
|
|
872
|
+
observedNodes.push(node);
|
|
854
873
|
}
|
|
874
|
+
});
|
|
855
875
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
876
|
+
let parent = this.parentNode;
|
|
877
|
+
while (!(parent instanceof HTMLElement) && parent !== null) {
|
|
878
|
+
parent = parent.parentNode;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
if (parent instanceof HTMLElement) {
|
|
882
|
+
observedNodes.push(parent);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
return observedNodes;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* @private
|
|
890
|
+
* @param {HTMLElement[]} nodes
|
|
891
|
+
* @return {string}
|
|
892
|
+
*/
|
|
893
|
+
function getLayoutObservedNodesSignature(nodes) {
|
|
894
|
+
return nodes.map(getLayoutNodeId).join("|");
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
/**
|
|
898
|
+
* @private
|
|
899
|
+
* @param {HTMLElement} node
|
|
900
|
+
* @return {number}
|
|
901
|
+
*/
|
|
902
|
+
function getLayoutNodeId(node) {
|
|
903
|
+
let id = layoutNodeIds.get(node);
|
|
904
|
+
if (id === undefined) {
|
|
905
|
+
id = ++layoutNodeId;
|
|
906
|
+
layoutNodeIds.set(node, id);
|
|
907
|
+
}
|
|
908
|
+
return id;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* @private
|
|
913
|
+
* @return {void}
|
|
914
|
+
*/
|
|
915
|
+
function suppressLayoutFeedback() {
|
|
916
|
+
const state = this[layoutStateSymbol];
|
|
917
|
+
if (!state) {
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
state.suppressSlotChange = true;
|
|
922
|
+
state.suppressMutation = true;
|
|
923
|
+
queueMicrotask(() => {
|
|
924
|
+
state.suppressSlotChange = false;
|
|
925
|
+
state.suppressMutation = false;
|
|
859
926
|
});
|
|
860
927
|
}
|
|
861
928
|
|
|
929
|
+
/**
|
|
930
|
+
* @private
|
|
931
|
+
* @param {boolean} visible
|
|
932
|
+
* @return {void}
|
|
933
|
+
*/
|
|
934
|
+
function setSwitchVisible(visible) {
|
|
935
|
+
if (!(this[switchElementSymbol] instanceof HTMLElement)) {
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
if (visible) {
|
|
940
|
+
if (this[switchElementSymbol].hasAttribute("hidden")) {
|
|
941
|
+
this[switchElementSymbol].removeAttribute("hidden");
|
|
942
|
+
}
|
|
943
|
+
if (this[switchElementSymbol].classList.contains("hidden")) {
|
|
944
|
+
this[switchElementSymbol].classList.remove("hidden");
|
|
945
|
+
}
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
if (!this[switchElementSymbol].hasAttribute("hidden")) {
|
|
950
|
+
this[switchElementSymbol].setAttribute("hidden", "");
|
|
951
|
+
}
|
|
952
|
+
if (!this[switchElementSymbol].classList.contains("hidden")) {
|
|
953
|
+
this[switchElementSymbol].classList.add("hidden");
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
862
957
|
/**
|
|
863
958
|
* @private
|
|
864
959
|
*/
|
|
@@ -866,6 +961,7 @@ function disconnectResizeObserver() {
|
|
|
866
961
|
if (this[resizeObserverSymbol] instanceof ResizeObserver) {
|
|
867
962
|
this[resizeObserverSymbol].disconnect();
|
|
868
963
|
}
|
|
964
|
+
this[observedLayoutNodesSignatureSymbol] = null;
|
|
869
965
|
}
|
|
870
966
|
|
|
871
967
|
/**
|
|
@@ -43,6 +43,12 @@ const instanceSymbol = Symbol("instanceSymbol");
|
|
|
43
43
|
const activeSubmenuHiderSymbol = Symbol("activeSubmenuHider");
|
|
44
44
|
const hideHamburgerMenuSymbol = Symbol("hideHamburgerMenu");
|
|
45
45
|
const hamburgerCloseButtonSymbol = Symbol("hamburgerCloseButton");
|
|
46
|
+
const layoutFrameSymbol = Symbol("layoutFrame");
|
|
47
|
+
const layoutSignatureSymbol = Symbol("layoutSignature");
|
|
48
|
+
const measurementCacheSymbol = Symbol("measurementCache");
|
|
49
|
+
|
|
50
|
+
const navigationItemIds = new WeakMap();
|
|
51
|
+
let navigationItemId = 0;
|
|
46
52
|
|
|
47
53
|
function getAutoUpdateOptions() {
|
|
48
54
|
return {
|
|
@@ -115,14 +121,16 @@ class SiteNavigation extends CustomElement {
|
|
|
115
121
|
connectedCallback() {
|
|
116
122
|
super.connectedCallback();
|
|
117
123
|
attachResizeObserver.call(this);
|
|
118
|
-
|
|
119
|
-
populateTabs.call(this);
|
|
120
|
-
});
|
|
124
|
+
schedulePopulateTabs.call(this);
|
|
121
125
|
}
|
|
122
126
|
|
|
123
127
|
disconnectedCallback() {
|
|
124
128
|
super.disconnectedCallback();
|
|
125
129
|
detachResizeObserver.call(this);
|
|
130
|
+
if (typeof this[layoutFrameSymbol] === "number") {
|
|
131
|
+
cancelAnimationFrame(this[layoutFrameSymbol]);
|
|
132
|
+
}
|
|
133
|
+
this[layoutFrameSymbol] = null;
|
|
126
134
|
}
|
|
127
135
|
}
|
|
128
136
|
|
|
@@ -300,14 +308,28 @@ function attachResizeObserver() {
|
|
|
300
308
|
}
|
|
301
309
|
}
|
|
302
310
|
this[timerCallbackSymbol] = new DeadMansSwitch(200, () => {
|
|
303
|
-
|
|
304
|
-
populateTabs.call(this);
|
|
305
|
-
});
|
|
311
|
+
schedulePopulateTabs.call(this);
|
|
306
312
|
});
|
|
307
313
|
});
|
|
308
314
|
this[resizeObserverSymbol].observe(this);
|
|
309
315
|
}
|
|
310
316
|
|
|
317
|
+
/**
|
|
318
|
+
* @private
|
|
319
|
+
* @this {SiteNavigation}
|
|
320
|
+
* @return {void}
|
|
321
|
+
*/
|
|
322
|
+
function schedulePopulateTabs() {
|
|
323
|
+
if (typeof this[layoutFrameSymbol] === "number") {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
this[layoutFrameSymbol] = requestAnimationFrame(() => {
|
|
328
|
+
this[layoutFrameSymbol] = null;
|
|
329
|
+
populateTabs.call(this);
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
311
333
|
/**
|
|
312
334
|
* Disconnects and cleans up the ResizeObserver instance.
|
|
313
335
|
* @private
|
|
@@ -595,6 +617,156 @@ function cloneNavItem(item) {
|
|
|
595
617
|
return liClone;
|
|
596
618
|
}
|
|
597
619
|
|
|
620
|
+
/**
|
|
621
|
+
* @private
|
|
622
|
+
* @param {HTMLElement} hamburgerButton
|
|
623
|
+
* @return {number}
|
|
624
|
+
*/
|
|
625
|
+
function measureHamburgerWidth(hamburgerButton) {
|
|
626
|
+
const originalDisplay = hamburgerButton.style.display;
|
|
627
|
+
const originalVisibility = hamburgerButton.style.visibility;
|
|
628
|
+
setStyleValue(hamburgerButton, "visibility", "hidden");
|
|
629
|
+
setStyleValue(hamburgerButton, "display", "flex");
|
|
630
|
+
const width = Math.ceil(hamburgerButton.getBoundingClientRect().width) || 0;
|
|
631
|
+
setStyleValue(hamburgerButton, "display", originalDisplay);
|
|
632
|
+
setStyleValue(hamburgerButton, "visibility", originalVisibility);
|
|
633
|
+
return width;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* @private
|
|
638
|
+
* @param {HTMLElement[]} sourceItems
|
|
639
|
+
* @param {string} sourceSignature
|
|
640
|
+
* @param {HTMLElement} visibleList
|
|
641
|
+
* @param {HTMLElement} navEl
|
|
642
|
+
* @return {object}
|
|
643
|
+
*/
|
|
644
|
+
function getNavigationMeasurements(
|
|
645
|
+
sourceItems,
|
|
646
|
+
sourceSignature,
|
|
647
|
+
visibleList,
|
|
648
|
+
navEl,
|
|
649
|
+
) {
|
|
650
|
+
const cache = this[measurementCacheSymbol];
|
|
651
|
+
if (cache?.sourceSignature === sourceSignature && cache.items.length > 0) {
|
|
652
|
+
return cache;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const originalOverflow = navEl.style.overflow;
|
|
656
|
+
const originalFlexWrap = visibleList.style.flexWrap;
|
|
657
|
+
const originalVisibility = visibleList.style.visibility;
|
|
658
|
+
|
|
659
|
+
setStyleValue(navEl, "overflow", "hidden");
|
|
660
|
+
setStyleValue(visibleList, "flexWrap", "nowrap");
|
|
661
|
+
setStyleValue(visibleList, "visibility", "hidden");
|
|
662
|
+
|
|
663
|
+
const clones = sourceItems.map(cloneNavItem);
|
|
664
|
+
visibleList.replaceChildren(...clones);
|
|
665
|
+
|
|
666
|
+
const items = clones.map((clone) => {
|
|
667
|
+
const submenu = clone.querySelector("ul, div[part='mega-menu']");
|
|
668
|
+
if (submenu instanceof HTMLElement) {
|
|
669
|
+
setStyleValue(submenu, "display", "none");
|
|
670
|
+
}
|
|
671
|
+
return {
|
|
672
|
+
requiredWidth: clone.offsetLeft + clone.offsetWidth,
|
|
673
|
+
width: clone.getBoundingClientRect().width || clone.offsetWidth || 0,
|
|
674
|
+
};
|
|
675
|
+
});
|
|
676
|
+
const gap = parseFloat(getComputedStyle(visibleList).gap || "0") || 0;
|
|
677
|
+
|
|
678
|
+
visibleList.replaceChildren();
|
|
679
|
+
setStyleValue(navEl, "overflow", originalOverflow);
|
|
680
|
+
setStyleValue(visibleList, "flexWrap", originalFlexWrap);
|
|
681
|
+
setStyleValue(visibleList, "visibility", originalVisibility);
|
|
682
|
+
|
|
683
|
+
this[measurementCacheSymbol] = {
|
|
684
|
+
gap,
|
|
685
|
+
items,
|
|
686
|
+
sourceSignature,
|
|
687
|
+
};
|
|
688
|
+
return this[measurementCacheSymbol];
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* @private
|
|
693
|
+
* @param {HTMLElement[]} sourceItems
|
|
694
|
+
* @return {string}
|
|
695
|
+
*/
|
|
696
|
+
function getSourceItemsSignature(sourceItems) {
|
|
697
|
+
return sourceItems
|
|
698
|
+
.map((item) => {
|
|
699
|
+
return [
|
|
700
|
+
getNavigationItemId(item),
|
|
701
|
+
item.className || "",
|
|
702
|
+
item.textContent || "",
|
|
703
|
+
item.querySelectorAll("li").length,
|
|
704
|
+
].join(":");
|
|
705
|
+
})
|
|
706
|
+
.join("|");
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* @private
|
|
711
|
+
* @param {HTMLElement} item
|
|
712
|
+
* @return {number}
|
|
713
|
+
*/
|
|
714
|
+
function getNavigationItemId(item) {
|
|
715
|
+
let id = navigationItemIds.get(item);
|
|
716
|
+
if (id === undefined) {
|
|
717
|
+
id = ++navigationItemId;
|
|
718
|
+
navigationItemIds.set(item, id);
|
|
719
|
+
}
|
|
720
|
+
return id;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* @private
|
|
725
|
+
* @param {object} params
|
|
726
|
+
* @return {string}
|
|
727
|
+
*/
|
|
728
|
+
function getLayoutSignature({ fit, navWidth, rest }) {
|
|
729
|
+
return [
|
|
730
|
+
navWidth,
|
|
731
|
+
fit.map(getNavigationItemId).join(","),
|
|
732
|
+
rest.map(getNavigationItemId).join(","),
|
|
733
|
+
].join("::");
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* @private
|
|
738
|
+
* @param {HTMLElement} element
|
|
739
|
+
* @param {string} property
|
|
740
|
+
* @param {string} value
|
|
741
|
+
* @return {void}
|
|
742
|
+
*/
|
|
743
|
+
function setStyleValue(element, property, value) {
|
|
744
|
+
if (!(element instanceof HTMLElement)) {
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
if (element.style[property] !== value) {
|
|
748
|
+
element.style[property] = value;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* @private
|
|
754
|
+
* @param {HTMLElement} element
|
|
755
|
+
* @param {HTMLElement[]} children
|
|
756
|
+
* @return {void}
|
|
757
|
+
*/
|
|
758
|
+
function replaceChildrenIfChanged(element, children) {
|
|
759
|
+
const currentSignature = Array.from(element.children)
|
|
760
|
+
.map((child) => [child.className || "", child.textContent || ""].join(":"))
|
|
761
|
+
.join("|");
|
|
762
|
+
const nextSignature = children
|
|
763
|
+
.map((child) => [child.className || "", child.textContent || ""].join(":"))
|
|
764
|
+
.join("|");
|
|
765
|
+
if (currentSignature !== nextSignature) {
|
|
766
|
+
element.replaceChildren(...children);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
598
770
|
/**
|
|
599
771
|
* Measures available space and distributes slotted navigation items between
|
|
600
772
|
* the visible list and the hidden hamburger menu list.
|
|
@@ -618,12 +790,12 @@ function populateTabs() {
|
|
|
618
790
|
(ul) => ul.parentElement === this,
|
|
619
791
|
);
|
|
620
792
|
|
|
621
|
-
visibleList.innerHTML = "";
|
|
622
|
-
hiddenList.innerHTML = "";
|
|
623
|
-
hamburgerButton.style.display = "none";
|
|
624
793
|
this.style.visibility = "hidden";
|
|
625
794
|
|
|
626
795
|
if (!topLevelUl) {
|
|
796
|
+
replaceChildrenIfChanged.call(this, visibleList, []);
|
|
797
|
+
replaceChildrenIfChanged.call(this, hiddenList, []);
|
|
798
|
+
setStyleValue(hamburgerButton, "display", "none");
|
|
627
799
|
this.style.visibility = "visible";
|
|
628
800
|
return; // Nichts zu tun
|
|
629
801
|
}
|
|
@@ -631,27 +803,29 @@ function populateTabs() {
|
|
|
631
803
|
(n) => n.tagName === "LI",
|
|
632
804
|
);
|
|
633
805
|
if (sourceItems.length === 0) {
|
|
806
|
+
replaceChildrenIfChanged.call(this, visibleList, []);
|
|
807
|
+
replaceChildrenIfChanged.call(this, hiddenList, []);
|
|
808
|
+
setStyleValue(hamburgerButton, "display", "none");
|
|
634
809
|
this.style.visibility = "visible";
|
|
635
810
|
return;
|
|
636
811
|
}
|
|
637
812
|
|
|
638
813
|
const navWidth = navEl.clientWidth;
|
|
639
|
-
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
navEl.style.overflow = "hidden";
|
|
649
|
-
visibleList.style.flexWrap = "nowrap";
|
|
650
|
-
visibleList.style.visibility = "hidden"; // Inhalt der Liste während Manipulation ausblenden
|
|
814
|
+
const sourceSignature = getSourceItemsSignature(sourceItems);
|
|
815
|
+
const hamburgerWidth = measureHamburgerWidth(hamburgerButton);
|
|
816
|
+
const measurements = getNavigationMeasurements.call(
|
|
817
|
+
this,
|
|
818
|
+
sourceItems,
|
|
819
|
+
sourceSignature,
|
|
820
|
+
visibleList,
|
|
821
|
+
navEl,
|
|
822
|
+
);
|
|
651
823
|
|
|
652
824
|
const fit = [];
|
|
653
825
|
const rest = [];
|
|
654
826
|
let hasOverflow = false;
|
|
827
|
+
const availableWidth = navWidth - hamburgerWidth;
|
|
828
|
+
const SAFETY_MARGIN = 1;
|
|
655
829
|
|
|
656
830
|
for (let i = 0; i < sourceItems.length; i++) {
|
|
657
831
|
const item = sourceItems[i];
|
|
@@ -661,12 +835,7 @@ function populateTabs() {
|
|
|
661
835
|
continue;
|
|
662
836
|
}
|
|
663
837
|
|
|
664
|
-
const
|
|
665
|
-
visibleList.appendChild(liClone);
|
|
666
|
-
|
|
667
|
-
const requiredWidth = liClone.offsetLeft + liClone.offsetWidth;
|
|
668
|
-
const availableWidth = navWidth - hamburgerWidth;
|
|
669
|
-
const SAFETY_MARGIN = 1; // 1px Sicherheitsmarge für Subpixel-Rendering
|
|
838
|
+
const requiredWidth = measurements.items[i]?.requiredWidth || 0;
|
|
670
839
|
|
|
671
840
|
if (requiredWidth > availableWidth + SAFETY_MARGIN) {
|
|
672
841
|
hasOverflow = true;
|
|
@@ -677,49 +846,40 @@ function populateTabs() {
|
|
|
677
846
|
}
|
|
678
847
|
|
|
679
848
|
if (fit.length > 0 && rest.length > 0) {
|
|
680
|
-
const
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
);
|
|
688
|
-
if (submenu) submenu.style.display = "none";
|
|
689
|
-
|
|
690
|
-
visibleList.appendChild(firstHiddenItemClone);
|
|
691
|
-
const firstHiddenItemWidth =
|
|
692
|
-
firstHiddenItemClone.getBoundingClientRect().width;
|
|
693
|
-
visibleList.removeChild(firstHiddenItemClone);
|
|
694
|
-
|
|
695
|
-
const gap = parseFloat(getComputedStyle(visibleList).gap || "0") || 0;
|
|
696
|
-
if (visibleItemsWidth + gap + firstHiddenItemWidth <= navWidth) {
|
|
849
|
+
const visibleItemsWidth = measurements.items[fit.length - 1]?.requiredWidth || 0;
|
|
850
|
+
const firstHiddenIndex = sourceItems.indexOf(rest[0]);
|
|
851
|
+
const firstHiddenItemWidth = measurements.items[firstHiddenIndex]?.width || 0;
|
|
852
|
+
if (
|
|
853
|
+
visibleItemsWidth + measurements.gap + firstHiddenItemWidth <=
|
|
854
|
+
navWidth
|
|
855
|
+
) {
|
|
697
856
|
fit.push(rest.shift());
|
|
698
857
|
}
|
|
699
858
|
}
|
|
700
859
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
const clonedVisible = fit.map(cloneNavItem);
|
|
707
|
-
visibleList.append(...clonedVisible);
|
|
708
|
-
visibleList
|
|
709
|
-
.querySelectorAll(":scope > li")
|
|
710
|
-
.forEach((li) => setupSubmenu.call(this, li, "visible", 1));
|
|
860
|
+
const layoutSignature = getLayoutSignature({ fit, navWidth, rest });
|
|
861
|
+
if (this[layoutSignatureSymbol] === layoutSignature) {
|
|
862
|
+
setStyleValue(visibleList, "visibility", "visible");
|
|
863
|
+
this.style.visibility = "visible";
|
|
864
|
+
return;
|
|
711
865
|
}
|
|
866
|
+
this[layoutSignatureSymbol] = layoutSignature;
|
|
712
867
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
868
|
+
const clonedVisible = fit.map(cloneNavItem);
|
|
869
|
+
const clonedHidden = rest.map(cloneNavItem);
|
|
870
|
+
replaceChildrenIfChanged.call(this, visibleList, clonedVisible);
|
|
871
|
+
replaceChildrenIfChanged.call(this, hiddenList, clonedHidden);
|
|
872
|
+
|
|
873
|
+
visibleList
|
|
874
|
+
.querySelectorAll(":scope > li")
|
|
875
|
+
.forEach((li) => setupSubmenu.call(this, li, "visible", 1));
|
|
876
|
+
|
|
877
|
+
setStyleValue(hamburgerButton, "display", rest.length ? "flex" : "none");
|
|
878
|
+
hiddenList
|
|
879
|
+
.querySelectorAll(":scope > li")
|
|
880
|
+
.forEach((li) => setupSubmenu.call(this, li, "hidden", 1));
|
|
721
881
|
|
|
722
|
-
visibleList
|
|
882
|
+
setStyleValue(visibleList, "visibility", "visible");
|
|
723
883
|
this.style.visibility = "visible";
|
|
724
884
|
fireCustomEvent(this, "monster-layout-change", {
|
|
725
885
|
visibleItems: fit,
|
|
@@ -123,6 +123,16 @@ describe('Pagination', function () {
|
|
|
123
123
|
expect(list).to.exist;
|
|
124
124
|
|
|
125
125
|
list.setAttribute('data-monster-adaptive-ready', 'true');
|
|
126
|
+
control.refreshLayout();
|
|
127
|
+
list.setAttribute('data-monster-adaptive-ready', 'true');
|
|
128
|
+
let adaptiveReadyResetCount = 0;
|
|
129
|
+
const originalSetAttribute = list.setAttribute.bind(list);
|
|
130
|
+
list.setAttribute = (name, value) => {
|
|
131
|
+
if (name === 'data-monster-adaptive-ready' && value === 'false') {
|
|
132
|
+
adaptiveReadyResetCount++;
|
|
133
|
+
}
|
|
134
|
+
return originalSetAttribute(name, value);
|
|
135
|
+
};
|
|
126
136
|
control.setPaginationState({currentPage: 2, totalPages: 5});
|
|
127
137
|
|
|
128
138
|
expect(list.getAttribute('data-monster-adaptive-ready')).to.equal('true');
|
|
@@ -130,6 +140,7 @@ describe('Pagination', function () {
|
|
|
130
140
|
setTimeout(() => {
|
|
131
141
|
try {
|
|
132
142
|
expect(list.getAttribute('data-monster-adaptive-ready')).to.equal('true');
|
|
143
|
+
expect(adaptiveReadyResetCount).to.equal(0);
|
|
133
144
|
done();
|
|
134
145
|
} catch (e) {
|
|
135
146
|
done(e);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as chai from "chai";
|
|
2
2
|
import { chaiDom } from "../../../util/chai-dom.mjs";
|
|
3
3
|
import { initJSDOM } from "../../../util/jsdom.mjs";
|
|
4
|
+
import { ResizeObserverMock } from "../../../util/resize-observer.mjs";
|
|
4
5
|
|
|
5
6
|
let expect = chai.expect;
|
|
6
7
|
chai.use(chaiDom);
|
|
@@ -94,4 +95,92 @@ describe("ButtonBar", function () {
|
|
|
94
95
|
}
|
|
95
96
|
}, 50);
|
|
96
97
|
});
|
|
98
|
+
|
|
99
|
+
it("should coalesce resize layouts and avoid unchanged layout writes", async function () {
|
|
100
|
+
const OriginalResizeObserver = window.ResizeObserver;
|
|
101
|
+
const originalGlobalResizeObserver = globalThis.ResizeObserver;
|
|
102
|
+
const originalRequestAnimationFrame = window.requestAnimationFrame;
|
|
103
|
+
const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
|
|
104
|
+
|
|
105
|
+
class TrackingResizeObserver extends ResizeObserverMock {
|
|
106
|
+
static instances = [];
|
|
107
|
+
|
|
108
|
+
constructor(callback) {
|
|
109
|
+
super(callback);
|
|
110
|
+
TrackingResizeObserver.instances.push(this);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const scheduledCallbacks = [];
|
|
115
|
+
const flushFrames = async () => {
|
|
116
|
+
while (scheduledCallbacks.length > 0) {
|
|
117
|
+
scheduledCallbacks.shift()();
|
|
118
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
window.ResizeObserver = TrackingResizeObserver;
|
|
124
|
+
globalThis.ResizeObserver = TrackingResizeObserver;
|
|
125
|
+
window.requestAnimationFrame = (callback) => {
|
|
126
|
+
scheduledCallbacks.push(callback);
|
|
127
|
+
return scheduledCallbacks.length;
|
|
128
|
+
};
|
|
129
|
+
globalThis.requestAnimationFrame = window.requestAnimationFrame;
|
|
130
|
+
|
|
131
|
+
const mocks = document.getElementById("mocks");
|
|
132
|
+
mocks.innerHTML = `
|
|
133
|
+
<div id="button-bar-wrapper">
|
|
134
|
+
<monster-button-bar id="overflow-bar"></monster-button-bar>
|
|
135
|
+
</div>
|
|
136
|
+
`;
|
|
137
|
+
|
|
138
|
+
const wrapper = document.getElementById("button-bar-wrapper");
|
|
139
|
+
const bar = document.getElementById("overflow-bar");
|
|
140
|
+
|
|
141
|
+
wrapper.style.boxSizing = "border-box";
|
|
142
|
+
wrapper.style.width = "50px";
|
|
143
|
+
Object.defineProperty(wrapper, "clientWidth", {
|
|
144
|
+
configurable: true,
|
|
145
|
+
value: 50,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const switchButton = bar.shadowRoot.querySelector(
|
|
149
|
+
'[data-monster-role="switch"]',
|
|
150
|
+
);
|
|
151
|
+
Object.defineProperty(switchButton, "offsetWidth", {
|
|
152
|
+
configurable: true,
|
|
153
|
+
value: 20,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
await flushFrames();
|
|
157
|
+
|
|
158
|
+
const resizeObserver = TrackingResizeObserver.instances.find((observer) =>
|
|
159
|
+
observer.elements.includes(wrapper),
|
|
160
|
+
);
|
|
161
|
+
expect(resizeObserver).to.exist;
|
|
162
|
+
|
|
163
|
+
let hiddenWriteCount = 0;
|
|
164
|
+
const originalSetAttribute = switchButton.setAttribute.bind(switchButton);
|
|
165
|
+
switchButton.setAttribute = (name, value) => {
|
|
166
|
+
if (name === "hidden") hiddenWriteCount++;
|
|
167
|
+
return originalSetAttribute(name, value);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
resizeObserver.triggerResize([]);
|
|
171
|
+
resizeObserver.triggerResize([]);
|
|
172
|
+
|
|
173
|
+
expect(scheduledCallbacks.length).to.equal(1);
|
|
174
|
+
await flushFrames();
|
|
175
|
+
|
|
176
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
177
|
+
expect(scheduledCallbacks.length).to.equal(0);
|
|
178
|
+
expect(hiddenWriteCount).to.equal(0);
|
|
179
|
+
} finally {
|
|
180
|
+
window.ResizeObserver = OriginalResizeObserver;
|
|
181
|
+
globalThis.ResizeObserver = originalGlobalResizeObserver;
|
|
182
|
+
window.requestAnimationFrame = originalRequestAnimationFrame;
|
|
183
|
+
globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
|
|
184
|
+
}
|
|
185
|
+
});
|
|
97
186
|
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import * as chai from "chai";
|
|
2
|
+
import { initJSDOM } from "../../../util/jsdom.mjs";
|
|
3
|
+
import { ResizeObserverMock } from "../../../util/resize-observer.mjs";
|
|
4
|
+
|
|
5
|
+
const expect = chai.expect;
|
|
6
|
+
|
|
7
|
+
describe("SiteNavigation", function () {
|
|
8
|
+
let SiteNavigation;
|
|
9
|
+
|
|
10
|
+
before(function (done) {
|
|
11
|
+
initJSDOM()
|
|
12
|
+
.then(() => {
|
|
13
|
+
import("../../../../source/components/navigation/site-navigation.mjs")
|
|
14
|
+
.then((m) => {
|
|
15
|
+
SiteNavigation = m.SiteNavigation;
|
|
16
|
+
done();
|
|
17
|
+
})
|
|
18
|
+
.catch((e) => done(e));
|
|
19
|
+
})
|
|
20
|
+
.catch((e) => done(e));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
document.getElementById("mocks").innerHTML = "";
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should register monster-site-navigation", function () {
|
|
28
|
+
expect(document.createElement("monster-site-navigation")).to.be.instanceof(
|
|
29
|
+
SiteNavigation,
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should coalesce resize layout updates", function (done) {
|
|
34
|
+
const OriginalResizeObserver = window.ResizeObserver;
|
|
35
|
+
const originalGlobalResizeObserver = globalThis.ResizeObserver;
|
|
36
|
+
const originalRequestAnimationFrame = window.requestAnimationFrame;
|
|
37
|
+
const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
|
|
38
|
+
|
|
39
|
+
class TrackingResizeObserver extends ResizeObserverMock {
|
|
40
|
+
static instances = [];
|
|
41
|
+
|
|
42
|
+
constructor(callback) {
|
|
43
|
+
super(callback);
|
|
44
|
+
TrackingResizeObserver.instances.push(this);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const scheduledCallbacks = [];
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
window.ResizeObserver = TrackingResizeObserver;
|
|
52
|
+
globalThis.ResizeObserver = TrackingResizeObserver;
|
|
53
|
+
window.requestAnimationFrame = (callback) => {
|
|
54
|
+
scheduledCallbacks.push(callback);
|
|
55
|
+
return scheduledCallbacks.length;
|
|
56
|
+
};
|
|
57
|
+
globalThis.requestAnimationFrame = window.requestAnimationFrame;
|
|
58
|
+
|
|
59
|
+
const mocks = document.getElementById("mocks");
|
|
60
|
+
mocks.innerHTML = `
|
|
61
|
+
<monster-site-navigation id="nav">
|
|
62
|
+
<ul>
|
|
63
|
+
<li><a href="#one">One</a></li>
|
|
64
|
+
<li><a href="#two">Two</a></li>
|
|
65
|
+
</ul>
|
|
66
|
+
</monster-site-navigation>
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
const nav = document.getElementById("nav");
|
|
70
|
+
const resizeObserver = TrackingResizeObserver.instances.find((observer) =>
|
|
71
|
+
observer.elements.includes(nav),
|
|
72
|
+
);
|
|
73
|
+
expect(resizeObserver).to.exist;
|
|
74
|
+
|
|
75
|
+
while (scheduledCallbacks.length > 0) {
|
|
76
|
+
scheduledCallbacks.shift()();
|
|
77
|
+
}
|
|
78
|
+
resizeObserver.triggerResize([]);
|
|
79
|
+
resizeObserver.triggerResize([]);
|
|
80
|
+
|
|
81
|
+
setTimeout(() => {
|
|
82
|
+
try {
|
|
83
|
+
expect(scheduledCallbacks.length).to.equal(1);
|
|
84
|
+
scheduledCallbacks.shift()();
|
|
85
|
+
done();
|
|
86
|
+
} catch (e) {
|
|
87
|
+
done(e);
|
|
88
|
+
} finally {
|
|
89
|
+
window.ResizeObserver = OriginalResizeObserver;
|
|
90
|
+
globalThis.ResizeObserver = originalGlobalResizeObserver;
|
|
91
|
+
window.requestAnimationFrame = originalRequestAnimationFrame;
|
|
92
|
+
globalThis.requestAnimationFrame =
|
|
93
|
+
originalGlobalRequestAnimationFrame;
|
|
94
|
+
}
|
|
95
|
+
}, 260);
|
|
96
|
+
} catch (e) {
|
|
97
|
+
window.ResizeObserver = OriginalResizeObserver;
|
|
98
|
+
globalThis.ResizeObserver = originalGlobalResizeObserver;
|
|
99
|
+
window.requestAnimationFrame = originalRequestAnimationFrame;
|
|
100
|
+
globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
|
|
101
|
+
done(e);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
});
|
package/test/web/import.js
CHANGED
|
@@ -3,6 +3,7 @@ import "./prepare.js";
|
|
|
3
3
|
import "../cases/components/layout/tabs.mjs";
|
|
4
4
|
import "../cases/components/layout/slit-panel.mjs";
|
|
5
5
|
import "../cases/components/layout/panel.mjs";
|
|
6
|
+
import "../cases/components/navigation/site-navigation.mjs";
|
|
6
7
|
import "../cases/components/content/viewer.mjs";
|
|
7
8
|
import "../cases/components/content/image-editor.mjs";
|
|
8
9
|
import "../cases/components/form/buy-box.mjs";
|