@schukai/monster 4.136.3 → 4.136.4
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/form/select.mjs +52 -44
- package/source/components/form/util/floating-ui.mjs +41 -2
- package/test/cases/components/form/floating-ui.mjs +30 -0
- package/test/cases/components/form/message-state-button.mjs +54 -32
- package/test/cases/components/form/select.mjs +40 -0
- package/test/util/intersection-mock.mjs +3 -1
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.136.
|
|
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.136.4"}
|
|
@@ -1765,55 +1765,20 @@ function getTranslations() {
|
|
|
1765
1765
|
*/
|
|
1766
1766
|
function lookupSelection() {
|
|
1767
1767
|
const self = this;
|
|
1768
|
+
const IntersectionObserverImplementation =
|
|
1769
|
+
getGlobal().IntersectionObserver;
|
|
1768
1770
|
|
|
1769
|
-
|
|
1771
|
+
if (!(IntersectionObserverImplementation instanceof Function)) {
|
|
1772
|
+
runSelectionLookupWhenVisible(self);
|
|
1773
|
+
return;
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
const observer = new IntersectionObserverImplementation(
|
|
1770
1777
|
(entries, obs) => {
|
|
1771
1778
|
for (const entry of entries) {
|
|
1772
1779
|
if (entry.isIntersecting) {
|
|
1773
1780
|
obs.disconnect();
|
|
1774
|
-
|
|
1775
|
-
setTimeout(() => {
|
|
1776
|
-
const selection = self.getOption("selection");
|
|
1777
|
-
if (
|
|
1778
|
-
selection.length === 0 ||
|
|
1779
|
-
self[isLoadingSymbol] ||
|
|
1780
|
-
self[lazyLoadDoneSymbol]
|
|
1781
|
-
) {
|
|
1782
|
-
return;
|
|
1783
|
-
}
|
|
1784
|
-
|
|
1785
|
-
let url = self.getOption("lookup.url") || self.getOption("url");
|
|
1786
|
-
self[cleanupOptionsListSymbol] = false;
|
|
1787
|
-
|
|
1788
|
-
if (self.getOption("lookup.grouping") === true) {
|
|
1789
|
-
const values = selection
|
|
1790
|
-
.map((s) => s?.["value"])
|
|
1791
|
-
.filter(
|
|
1792
|
-
(value) => isEmptyLookupValue.call(self, value) === false,
|
|
1793
|
-
);
|
|
1794
|
-
if (values.length === 0) {
|
|
1795
|
-
return;
|
|
1796
|
-
}
|
|
1797
|
-
filterFromRemoteByValue
|
|
1798
|
-
.call(self, url, { filter: values.join(",") })
|
|
1799
|
-
.catch((e) => {
|
|
1800
|
-
addErrorAttribute(self, e);
|
|
1801
|
-
});
|
|
1802
|
-
return;
|
|
1803
|
-
}
|
|
1804
|
-
|
|
1805
|
-
for (const s of selection) {
|
|
1806
|
-
const value = s?.["value"];
|
|
1807
|
-
if (isEmptyLookupValue.call(self, value)) {
|
|
1808
|
-
continue;
|
|
1809
|
-
}
|
|
1810
|
-
filterFromRemoteByValue
|
|
1811
|
-
.call(self, url, { filter: value })
|
|
1812
|
-
.catch((e) => {
|
|
1813
|
-
addErrorAttribute(self, e);
|
|
1814
|
-
});
|
|
1815
|
-
}
|
|
1816
|
-
}, 100);
|
|
1781
|
+
runSelectionLookupWhenVisible(self);
|
|
1817
1782
|
}
|
|
1818
1783
|
}
|
|
1819
1784
|
},
|
|
@@ -1823,6 +1788,49 @@ function lookupSelection() {
|
|
|
1823
1788
|
observer.observe(self);
|
|
1824
1789
|
}
|
|
1825
1790
|
|
|
1791
|
+
function runSelectionLookupWhenVisible(self) {
|
|
1792
|
+
setTimeout(() => {
|
|
1793
|
+
const selection = self.getOption("selection");
|
|
1794
|
+
if (
|
|
1795
|
+
selection.length === 0 ||
|
|
1796
|
+
self[isLoadingSymbol] ||
|
|
1797
|
+
self[lazyLoadDoneSymbol]
|
|
1798
|
+
) {
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
let url = self.getOption("lookup.url") || self.getOption("url");
|
|
1803
|
+
self[cleanupOptionsListSymbol] = false;
|
|
1804
|
+
|
|
1805
|
+
if (self.getOption("lookup.grouping") === true) {
|
|
1806
|
+
const values = selection
|
|
1807
|
+
.map((s) => s?.["value"])
|
|
1808
|
+
.filter((value) => isEmptyLookupValue.call(self, value) === false);
|
|
1809
|
+
if (values.length === 0) {
|
|
1810
|
+
return;
|
|
1811
|
+
}
|
|
1812
|
+
filterFromRemoteByValue
|
|
1813
|
+
.call(self, url, { filter: values.join(",") })
|
|
1814
|
+
.catch((e) => {
|
|
1815
|
+
addErrorAttribute(self, e);
|
|
1816
|
+
});
|
|
1817
|
+
return;
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
for (const s of selection) {
|
|
1821
|
+
const value = s?.["value"];
|
|
1822
|
+
if (isEmptyLookupValue.call(self, value)) {
|
|
1823
|
+
continue;
|
|
1824
|
+
}
|
|
1825
|
+
filterFromRemoteByValue
|
|
1826
|
+
.call(self, url, { filter: value })
|
|
1827
|
+
.catch((e) => {
|
|
1828
|
+
addErrorAttribute(self, e);
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
}, 100);
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1826
1834
|
/**
|
|
1827
1835
|
* @private
|
|
1828
1836
|
* @param {*} value
|
|
@@ -467,8 +467,12 @@ function syncNestedScrollContainerHeight(contentElement, contentMaxHeight) {
|
|
|
467
467
|
}
|
|
468
468
|
|
|
469
469
|
if (Number.isFinite(contentMaxHeight) && contentMaxHeight > 0) {
|
|
470
|
-
|
|
471
|
-
|
|
470
|
+
const nextNestedHeight = resolveNestedScrollContainerHeight(
|
|
471
|
+
nestedScrollableElement,
|
|
472
|
+
contentMaxHeight,
|
|
473
|
+
);
|
|
474
|
+
nestedScrollableElement.style.height = `${nextNestedHeight}px`;
|
|
475
|
+
nestedScrollableElement.style.maxHeight = `${nextNestedHeight}px`;
|
|
472
476
|
return;
|
|
473
477
|
}
|
|
474
478
|
|
|
@@ -476,6 +480,41 @@ function syncNestedScrollContainerHeight(contentElement, contentMaxHeight) {
|
|
|
476
480
|
nestedScrollableElement.style.maxHeight = "";
|
|
477
481
|
}
|
|
478
482
|
|
|
483
|
+
function resolveNestedScrollContainerHeight(
|
|
484
|
+
nestedScrollableElement,
|
|
485
|
+
contentMaxHeight,
|
|
486
|
+
) {
|
|
487
|
+
const declaredHeight = readDeclaredDimension(nestedScrollableElement, "height");
|
|
488
|
+
const declaredMaxHeight = readDeclaredDimension(
|
|
489
|
+
nestedScrollableElement,
|
|
490
|
+
"maxHeight",
|
|
491
|
+
);
|
|
492
|
+
const scrollHeight = nestedScrollableElement.scrollHeight;
|
|
493
|
+
const preferredHeightCandidates = [
|
|
494
|
+
declaredHeight,
|
|
495
|
+
declaredMaxHeight,
|
|
496
|
+
scrollHeight,
|
|
497
|
+
];
|
|
498
|
+
const preferredHeight = preferredHeightCandidates.find((value) => {
|
|
499
|
+
return Number.isFinite(value) && value > 0;
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
if (Number.isFinite(preferredHeight) && preferredHeight > 0) {
|
|
503
|
+
return Math.min(contentMaxHeight, preferredHeight);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return contentMaxHeight;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function readDeclaredDimension(element, property) {
|
|
510
|
+
if (!(element instanceof HTMLElement)) {
|
|
511
|
+
return NaN;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const value = Number.parseFloat(element.style?.[property] || "");
|
|
515
|
+
return Number.isFinite(value) ? value : NaN;
|
|
516
|
+
}
|
|
517
|
+
|
|
479
518
|
function syncPreferredFloatingWidth(floatingElement, maxWidth) {
|
|
480
519
|
const preferredWidth = Number.parseFloat(
|
|
481
520
|
floatingElement.dataset.monsterPreferredWidth || "",
|
|
@@ -207,4 +207,34 @@ describe("form floating-ui boundary resolution", function () {
|
|
|
207
207
|
|
|
208
208
|
expect(content.style.maxHeight).to.equal("26px");
|
|
209
209
|
});
|
|
210
|
+
|
|
211
|
+
it("should respect a smaller nested scroll container height", function () {
|
|
212
|
+
const mocks = document.getElementById("mocks");
|
|
213
|
+
const popper = document.createElement("div");
|
|
214
|
+
const content = document.createElement("div");
|
|
215
|
+
const options = document.createElement("div");
|
|
216
|
+
|
|
217
|
+
content.setAttribute("part", "content");
|
|
218
|
+
content.style.overflowY = "hidden";
|
|
219
|
+
options.style.overflowY = "auto";
|
|
220
|
+
options.style.height = "72px";
|
|
221
|
+
options.style.maxHeight = "72px";
|
|
222
|
+
Object.defineProperty(options, "scrollHeight", {
|
|
223
|
+
configurable: true,
|
|
224
|
+
value: 72,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
content.appendChild(options);
|
|
228
|
+
popper.appendChild(content);
|
|
229
|
+
mocks.appendChild(popper);
|
|
230
|
+
|
|
231
|
+
applyAdaptiveFloatingElementSize(popper, {
|
|
232
|
+
availableWidth: 220,
|
|
233
|
+
availableHeight: 180,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
expect(content.style.maxHeight).to.equal("180px");
|
|
237
|
+
expect(options.style.height).to.equal("72px");
|
|
238
|
+
expect(options.style.maxHeight).to.equal("72px");
|
|
239
|
+
});
|
|
210
240
|
});
|
|
@@ -261,44 +261,39 @@ describe("MessageStateButton", function () {
|
|
|
261
261
|
}, 0);
|
|
262
262
|
});
|
|
263
263
|
|
|
264
|
-
it("should resolve nested select message content to horizontal clipping only", function (
|
|
264
|
+
it("should resolve nested select message content to horizontal clipping only", async function () {
|
|
265
265
|
let mocks = document.getElementById("mocks");
|
|
266
266
|
const button = document.createElement("monster-message-state-button");
|
|
267
267
|
button.innerHTML = "Save";
|
|
268
268
|
mocks.appendChild(button);
|
|
269
269
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
270
|
+
const wrapper = document.createElement("div");
|
|
271
|
+
wrapper.appendChild(document.createElement("monster-select"));
|
|
272
|
+
button.setMessage(wrapper);
|
|
273
|
+
button.showMessage();
|
|
274
|
+
|
|
275
|
+
await waitForCondition(() => {
|
|
276
|
+
const content = button.shadowRoot?.querySelector('[part="content"]');
|
|
277
|
+
return (
|
|
278
|
+
content?.getAttribute("data-monster-overflow-mode") === "horizontal"
|
|
279
|
+
);
|
|
280
|
+
});
|
|
276
281
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
).to.equal("overlay");
|
|
293
|
-
done();
|
|
294
|
-
} catch (e) {
|
|
295
|
-
done(e);
|
|
296
|
-
}
|
|
297
|
-
}, 0);
|
|
298
|
-
} catch (e) {
|
|
299
|
-
done(e);
|
|
300
|
-
}
|
|
301
|
-
}, 0);
|
|
282
|
+
const content = button.shadowRoot.querySelector('[part="content"]');
|
|
283
|
+
const message = button.shadowRoot.querySelector(
|
|
284
|
+
'[data-monster-role="message"]',
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
expect(content).to.exist;
|
|
288
|
+
expect(content.getAttribute("data-monster-overflow-mode")).to.equal(
|
|
289
|
+
"horizontal",
|
|
290
|
+
);
|
|
291
|
+
expect(content.getAttribute("data-monster-message-layout")).to.equal(
|
|
292
|
+
"overlay",
|
|
293
|
+
);
|
|
294
|
+
expect(message.getAttribute("data-monster-message-layout")).to.equal(
|
|
295
|
+
"overlay",
|
|
296
|
+
);
|
|
302
297
|
});
|
|
303
298
|
|
|
304
299
|
it("should resolve wide plain content to the wide layout", function (done) {
|
|
@@ -489,3 +484,30 @@ describe("MessageStateButton", function () {
|
|
|
489
484
|
});
|
|
490
485
|
});
|
|
491
486
|
});
|
|
487
|
+
|
|
488
|
+
function waitForCondition(check, { timeout = 4000, interval = 25 } = {}) {
|
|
489
|
+
return new Promise((resolve, reject) => {
|
|
490
|
+
const start = Date.now();
|
|
491
|
+
|
|
492
|
+
const poll = () => {
|
|
493
|
+
try {
|
|
494
|
+
if (check()) {
|
|
495
|
+
resolve();
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
} catch (error) {
|
|
499
|
+
reject(error);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (Date.now() - start >= timeout) {
|
|
504
|
+
reject(new Error("Timed out while waiting for test condition."));
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
setTimeout(poll, interval);
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
poll();
|
|
512
|
+
});
|
|
513
|
+
}
|
|
@@ -549,6 +549,46 @@ describe('Select', function () {
|
|
|
549
549
|
}, 50);
|
|
550
550
|
});
|
|
551
551
|
|
|
552
|
+
it('should not throw when IntersectionObserver is unavailable', function (done) {
|
|
553
|
+
this.timeout(2000);
|
|
554
|
+
|
|
555
|
+
let mocks = document.getElementById('mocks');
|
|
556
|
+
const savedIntersectionObserver = global.IntersectionObserver;
|
|
557
|
+
global.IntersectionObserver = undefined;
|
|
558
|
+
window.IntersectionObserver = undefined;
|
|
559
|
+
const failures = [];
|
|
560
|
+
const onError = (event) => {
|
|
561
|
+
failures.push(event?.error || event);
|
|
562
|
+
};
|
|
563
|
+
window.addEventListener('error', onError);
|
|
564
|
+
|
|
565
|
+
const select = document.createElement('monster-select');
|
|
566
|
+
select.setOption('url', 'https://example.com/items?filter={filter}&page={page}');
|
|
567
|
+
select.setOption('filter.mode', 'remote');
|
|
568
|
+
select.setOption('mapping.selector', 'items.*');
|
|
569
|
+
select.setOption('mapping.labelTemplate', '${name}');
|
|
570
|
+
select.setOption('mapping.valueTemplate', '${id}');
|
|
571
|
+
select.setOption('mapping.total', 'pagination.total');
|
|
572
|
+
select.setOption('mapping.currentPage', 'pagination.page');
|
|
573
|
+
select.setOption('mapping.objectsPerPage', 'pagination.perPage');
|
|
574
|
+
select.setOption('selection', [{value: 'alpha'}]);
|
|
575
|
+
mocks.appendChild(select);
|
|
576
|
+
|
|
577
|
+
setTimeout(() => {
|
|
578
|
+
try {
|
|
579
|
+
expect(failures).to.have.length(0);
|
|
580
|
+
} catch (e) {
|
|
581
|
+
return done(e);
|
|
582
|
+
} finally {
|
|
583
|
+
window.removeEventListener('error', onError);
|
|
584
|
+
global.IntersectionObserver = savedIntersectionObserver;
|
|
585
|
+
window.IntersectionObserver = savedIntersectionObserver;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
done();
|
|
589
|
+
}, 250);
|
|
590
|
+
});
|
|
591
|
+
|
|
552
592
|
});
|
|
553
593
|
|
|
554
594
|
|
|
@@ -14,6 +14,7 @@ export function setupIntersectionObserverMock(
|
|
|
14
14
|
} = {}) {
|
|
15
15
|
|
|
16
16
|
const savedImplementation = window.IntersectionObserver;
|
|
17
|
+
const savedGlobalImplementation = global.IntersectionObserver;
|
|
17
18
|
|
|
18
19
|
let lastObject;
|
|
19
20
|
|
|
@@ -61,9 +62,10 @@ export function setupIntersectionObserverMock(
|
|
|
61
62
|
return {
|
|
62
63
|
restore: function () {
|
|
63
64
|
window.IntersectionObserver = savedImplementation;
|
|
65
|
+
global.IntersectionObserver = savedGlobalImplementation;
|
|
64
66
|
},
|
|
65
67
|
getInstance: function () {
|
|
66
68
|
return lastObject;
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
|
-
}
|
|
71
|
+
}
|