@schukai/monster 4.136.25 → 4.136.27
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/button-bar.mjs +26 -11
- package/source/components/form/util/floating-ui.mjs +4 -1
- package/source/components/layout/tabs.mjs +3 -0
- package/source/components/style/floating-ui.css +2 -1
- package/source/components/style/floating-ui.pcss +2 -1
- package/source/components/stylesheet/floating-ui.mjs +1 -1
- package/test/cases/components/form/floating-ui.mjs +25 -0
- package/test/cases/components/layout/tabs.mjs +140 -50
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.27"}
|
|
@@ -359,6 +359,25 @@ function isElementTrulyVisible(element) {
|
|
|
359
359
|
);
|
|
360
360
|
}
|
|
361
361
|
|
|
362
|
+
/**
|
|
363
|
+
* @private
|
|
364
|
+
* @param {HTMLElement} element
|
|
365
|
+
* @return {boolean}
|
|
366
|
+
*/
|
|
367
|
+
function isElementSelfHidden(element) {
|
|
368
|
+
if (!(element instanceof HTMLElement)) {
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const computedStyle = getComputedStyle(element);
|
|
373
|
+
return (
|
|
374
|
+
element.hidden === true ||
|
|
375
|
+
element.hasAttribute("hidden") ||
|
|
376
|
+
computedStyle.display === "none" ||
|
|
377
|
+
computedStyle.visibility === "hidden"
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
362
381
|
/**
|
|
363
382
|
* @private
|
|
364
383
|
*/
|
|
@@ -372,8 +391,10 @@ function initEventHandler() {
|
|
|
372
391
|
const target = mutation.target;
|
|
373
392
|
if (target instanceof HTMLElement) {
|
|
374
393
|
const ref = target.getAttribute("data-monster-reference");
|
|
375
|
-
if (ref
|
|
376
|
-
|
|
394
|
+
if (ref) {
|
|
395
|
+
if (!isElementTrulyVisible(target)) {
|
|
396
|
+
self[dimensionsSymbol].setVia(`data.button.${ref}`, 0);
|
|
397
|
+
}
|
|
377
398
|
needsRecalc = true;
|
|
378
399
|
}
|
|
379
400
|
}
|
|
@@ -596,12 +617,10 @@ function rearrangeButtons() {
|
|
|
596
617
|
// In this case, we assume the width is 0.
|
|
597
618
|
// This can happen for buttons that have never been visible.
|
|
598
619
|
}
|
|
599
|
-
const style = getComputedStyle(element);
|
|
600
|
-
|
|
601
620
|
buttonEntries.push({
|
|
602
621
|
element,
|
|
603
622
|
width: buttonWidth,
|
|
604
|
-
hidden:
|
|
623
|
+
hidden: isElementSelfHidden(element),
|
|
605
624
|
});
|
|
606
625
|
}
|
|
607
626
|
|
|
@@ -617,11 +636,7 @@ function rearrangeButtons() {
|
|
|
617
636
|
|
|
618
637
|
for (const entry of buttonEntries) {
|
|
619
638
|
if (entry.hidden) {
|
|
620
|
-
|
|
621
|
-
buttonsToMoveToPopper.push(entry.element);
|
|
622
|
-
} else {
|
|
623
|
-
visibleButtonsInMainSlot.push(entry.element);
|
|
624
|
-
}
|
|
639
|
+
visibleButtonsInMainSlot.push(entry.element);
|
|
625
640
|
continue;
|
|
626
641
|
}
|
|
627
642
|
|
|
@@ -826,7 +841,7 @@ function updateResizeObserverObservation() {
|
|
|
826
841
|
if (this[mutationObserverSymbol]) {
|
|
827
842
|
this[mutationObserverSymbol].observe(node, {
|
|
828
843
|
attributes: true,
|
|
829
|
-
attributeFilter: ["style", "class"],
|
|
844
|
+
attributeFilter: ["style", "class", "hidden"],
|
|
830
845
|
});
|
|
831
846
|
}
|
|
832
847
|
});
|
|
@@ -456,7 +456,10 @@ function applyOverlayAwareContentHeight(
|
|
|
456
456
|
preferredHeight > contentMaxHeight;
|
|
457
457
|
|
|
458
458
|
contentElement.style.removeProperty("overflow");
|
|
459
|
-
contentElement.style.overflowX =
|
|
459
|
+
contentElement.style.overflowX =
|
|
460
|
+
contentElement.dataset.monsterOverflowMode === "horizontal"
|
|
461
|
+
? "auto"
|
|
462
|
+
: "visible";
|
|
460
463
|
contentElement.style.overflowY = isConstrained ? "auto" : "visible";
|
|
461
464
|
|
|
462
465
|
if (Number.isFinite(constrainedHeight) && constrainedHeight > 0) {
|
|
@@ -43,7 +43,8 @@ div[data-monster-role="popper"] > [part="content"] {
|
|
|
43
43
|
div[data-monster-role="popper"]
|
|
44
44
|
> [part="content"][data-monster-overflow-mode="horizontal"] {
|
|
45
45
|
clip-path: none;
|
|
46
|
-
overflow:
|
|
46
|
+
overflow-x: auto;
|
|
47
|
+
overflow-y: visible;
|
|
47
48
|
}
|
|
48
49
|
div[data-monster-role="popper"]
|
|
49
50
|
> [part="content"][data-monster-overflow-mode="visible"] {
|
|
@@ -25,7 +25,7 @@ try {
|
|
|
25
25
|
FloatingUiStyleSheet.insertRule(
|
|
26
26
|
`
|
|
27
27
|
@layer floatingui {
|
|
28
|
-
div[data-monster-role=popper]{align-content:center;background:var(--monster-bg-color-primary-1);border-color:var(--monster-bg-color-primary-4);border-radius:var(--monster-border-radius);border-style:var(--monster-border-style);border-width:var(--monster-border-width);box-shadow:var(--monster-box-shadow-1);box-sizing:border-box;color:var(--monster-color-primary-1);display:none;justify-content:space-between;left:0;max-height:var(--monster-popper-max-height,calc(100vh - 2rem));max-width:var(--monster-popper-max-width,calc(100vw - 2rem));opacity:1;padding:1.1em;position:absolute;top:0;transition:opacity var(--monster-popper-fade-duration,.14s) var(--monster-popper-fade-timing,cubic-bezier(.2,0,0,1));width:-moz-max-content;width:max-content;z-index:var(--monster-z-index-modal)}div[data-monster-role=popper][data-monster-appearance=opening]{opacity:0}div[data-monster-role=popper][data-monster-appearance=open]{opacity:1}@media (prefers-reduced-motion:reduce){div[data-monster-role=popper]{transition:none}}div[data-monster-role=popper]>[part=content]{max-height:var(--monster-popper-content-max-height,calc(100vh - 4.2rem));max-width:100%;overflow:auto}div[data-monster-role=popper]>[part=content][data-monster-overflow-mode=horizontal]{clip-path:none;overflow:visible}div[data-monster-role=popper]>[part=content][data-monster-overflow-mode=visible]{clip-path:none;max-height:none;max-width:none;overflow:visible}div[data-monster-role=popper] div[data-monster-role=arrow]{background:var(--monster-bg-color-primary-1);height:calc(max(var(--monster-popper-witharrrow-distance), -1 * var(--monster-popper-witharrrow-distance))*2);pointer-events:none;position:absolute;width:calc(max(var(--monster-popper-witharrrow-distance), -1 * var(--monster-popper-witharrrow-distance))*2);z-index:-1}
|
|
28
|
+
div[data-monster-role=popper]{align-content:center;background:var(--monster-bg-color-primary-1);border-color:var(--monster-bg-color-primary-4);border-radius:var(--monster-border-radius);border-style:var(--monster-border-style);border-width:var(--monster-border-width);box-shadow:var(--monster-box-shadow-1);box-sizing:border-box;color:var(--monster-color-primary-1);display:none;justify-content:space-between;left:0;max-height:var(--monster-popper-max-height,calc(100vh - 2rem));max-width:var(--monster-popper-max-width,calc(100vw - 2rem));opacity:1;padding:1.1em;position:absolute;top:0;transition:opacity var(--monster-popper-fade-duration,.14s) var(--monster-popper-fade-timing,cubic-bezier(.2,0,0,1));width:-moz-max-content;width:max-content;z-index:var(--monster-z-index-modal)}div[data-monster-role=popper][data-monster-appearance=opening]{opacity:0}div[data-monster-role=popper][data-monster-appearance=open]{opacity:1}@media (prefers-reduced-motion:reduce){div[data-monster-role=popper]{transition:none}}div[data-monster-role=popper]>[part=content]{max-height:var(--monster-popper-content-max-height,calc(100vh - 4.2rem));max-width:100%;overflow:auto}div[data-monster-role=popper]>[part=content][data-monster-overflow-mode=horizontal]{clip-path:none;overflow-x:auto;overflow-y:visible}div[data-monster-role=popper]>[part=content][data-monster-overflow-mode=visible]{clip-path:none;max-height:none;max-width:none;overflow:visible}div[data-monster-role=popper] div[data-monster-role=arrow]{background:var(--monster-bg-color-primary-1);height:calc(max(var(--monster-popper-witharrrow-distance), -1 * var(--monster-popper-witharrrow-distance))*2);pointer-events:none;position:absolute;width:calc(max(var(--monster-popper-witharrrow-distance), -1 * var(--monster-popper-witharrrow-distance))*2);z-index:-1}
|
|
29
29
|
}`,
|
|
30
30
|
0,
|
|
31
31
|
);
|
|
@@ -310,4 +310,29 @@ describe("form floating-ui boundary resolution", function () {
|
|
|
310
310
|
expect(popper.style.width).to.equal("240px");
|
|
311
311
|
expect(popper.style.minWidth).to.equal("240px");
|
|
312
312
|
});
|
|
313
|
+
|
|
314
|
+
it("should constrain horizontal overlay-aware content on the inline axis", function () {
|
|
315
|
+
const mocks = document.getElementById("mocks");
|
|
316
|
+
const popper = document.createElement("div");
|
|
317
|
+
const content = document.createElement("div");
|
|
318
|
+
|
|
319
|
+
content.setAttribute("part", "content");
|
|
320
|
+
content.setAttribute("data-monster-overflow-mode", "horizontal");
|
|
321
|
+
|
|
322
|
+
Object.defineProperty(content, "scrollHeight", {
|
|
323
|
+
configurable: true,
|
|
324
|
+
value: 120,
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
popper.appendChild(content);
|
|
328
|
+
mocks.appendChild(popper);
|
|
329
|
+
|
|
330
|
+
applyAdaptiveFloatingElementSize(popper, {
|
|
331
|
+
availableWidth: 240,
|
|
332
|
+
availableHeight: 200,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
expect(content.style.overflowX).to.equal("auto");
|
|
336
|
+
expect(content.style.overflowY).to.equal("visible");
|
|
337
|
+
});
|
|
313
338
|
});
|
|
@@ -2,6 +2,7 @@ import {getGlobal} from "../../../../source/types/global.mjs";
|
|
|
2
2
|
import * as chai from 'chai';
|
|
3
3
|
import {chaiDom} from "../../../util/chai-dom.mjs";
|
|
4
4
|
import {initJSDOM} from "../../../util/jsdom.mjs";
|
|
5
|
+
import {ResizeObserverMock} from "../../../util/resize-observer.mjs";
|
|
5
6
|
|
|
6
7
|
let expect = chai.expect;
|
|
7
8
|
chai.use(chaiDom);
|
|
@@ -61,6 +62,35 @@ let htmlOverflow = `
|
|
|
61
62
|
`;
|
|
62
63
|
|
|
63
64
|
let Tabs;
|
|
65
|
+
let restoreBoundingClientRect = null;
|
|
66
|
+
let restoreResizeObserver = null;
|
|
67
|
+
|
|
68
|
+
function waitForCondition(check, {timeout = 4000, interval = 10} = {}) {
|
|
69
|
+
const startedAt = Date.now();
|
|
70
|
+
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const tick = () => {
|
|
73
|
+
try {
|
|
74
|
+
if (check() === true) {
|
|
75
|
+
resolve();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
} catch (e) {
|
|
79
|
+
reject(e);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (Date.now() - startedAt > timeout) {
|
|
84
|
+
reject(new Error('condition timed out'));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
setTimeout(tick, interval);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
tick();
|
|
92
|
+
});
|
|
93
|
+
}
|
|
64
94
|
|
|
65
95
|
describe('Tabs', function () {
|
|
66
96
|
|
|
@@ -92,6 +122,14 @@ describe('Tabs', function () {
|
|
|
92
122
|
describe('document.createElement()', function () {
|
|
93
123
|
|
|
94
124
|
afterEach(() => {
|
|
125
|
+
if (restoreBoundingClientRect instanceof Function) {
|
|
126
|
+
restoreBoundingClientRect();
|
|
127
|
+
restoreBoundingClientRect = null;
|
|
128
|
+
}
|
|
129
|
+
if (restoreResizeObserver instanceof Function) {
|
|
130
|
+
restoreResizeObserver();
|
|
131
|
+
restoreResizeObserver = null;
|
|
132
|
+
}
|
|
95
133
|
let mocks = document.getElementById('mocks');
|
|
96
134
|
mocks.innerHTML = "";
|
|
97
135
|
})
|
|
@@ -219,10 +257,35 @@ describe('Tabs', function () {
|
|
|
219
257
|
});
|
|
220
258
|
|
|
221
259
|
it('should move overflowing tabs into the popper without losing them', function (done) {
|
|
260
|
+
this.timeout(5000);
|
|
222
261
|
|
|
223
262
|
let mocks = document.getElementById('mocks');
|
|
263
|
+
const OriginalResizeObserver = window.ResizeObserver;
|
|
264
|
+
const originalGlobalResizeObserver = global.ResizeObserver;
|
|
265
|
+
class TrackingResizeObserver extends ResizeObserverMock {
|
|
266
|
+
static instances = [];
|
|
267
|
+
static callbackCount = 0;
|
|
268
|
+
|
|
269
|
+
constructor(callback) {
|
|
270
|
+
super((entries, observer) => {
|
|
271
|
+
TrackingResizeObserver.callbackCount++;
|
|
272
|
+
callback(entries, observer);
|
|
273
|
+
});
|
|
274
|
+
TrackingResizeObserver.instances.push(this);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
window.ResizeObserver = TrackingResizeObserver;
|
|
278
|
+
global.ResizeObserver = TrackingResizeObserver;
|
|
279
|
+
restoreResizeObserver = () => {
|
|
280
|
+
window.ResizeObserver = OriginalResizeObserver;
|
|
281
|
+
global.ResizeObserver = originalGlobalResizeObserver;
|
|
282
|
+
};
|
|
224
283
|
const originalGetBoundingClientRect =
|
|
225
284
|
global.HTMLElement.prototype.getBoundingClientRect;
|
|
285
|
+
restoreBoundingClientRect = () => {
|
|
286
|
+
global.HTMLElement.prototype.getBoundingClientRect =
|
|
287
|
+
originalGetBoundingClientRect;
|
|
288
|
+
};
|
|
226
289
|
global.HTMLElement.prototype.getBoundingClientRect = function () {
|
|
227
290
|
const role = this.getAttribute?.('data-monster-role');
|
|
228
291
|
const label = this.textContent?.trim() || '';
|
|
@@ -258,63 +321,90 @@ describe('Tabs', function () {
|
|
|
258
321
|
|
|
259
322
|
mocks.innerHTML = htmlOverflow;
|
|
260
323
|
|
|
261
|
-
|
|
324
|
+
waitForCondition(() => {
|
|
325
|
+
const tabs = document.getElementById('overflow-tabs');
|
|
326
|
+
const switchButton = tabs?.shadowRoot?.querySelector(
|
|
327
|
+
'[data-monster-role="switch"]',
|
|
328
|
+
);
|
|
329
|
+
const standardButtons = tabs?.getOption?.('buttons.standard') || [];
|
|
330
|
+
const popperButtons = tabs?.getOption?.('buttons.popper') || [];
|
|
331
|
+
|
|
332
|
+
return (
|
|
333
|
+
tabs instanceof Tabs &&
|
|
334
|
+
switchButton !== null &&
|
|
335
|
+
standardButtons.length > 0 &&
|
|
336
|
+
popperButtons.length > 0 &&
|
|
337
|
+
standardButtons.length + popperButtons.length === 11 &&
|
|
338
|
+
switchButton.classList.contains('hidden') === false
|
|
339
|
+
);
|
|
340
|
+
}).then(() => {
|
|
262
341
|
try {
|
|
263
342
|
const tabs = document.getElementById('overflow-tabs');
|
|
264
343
|
expect(tabs).is.instanceof(Tabs);
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
344
|
+
const switchButton = tabs.shadowRoot.querySelector(
|
|
345
|
+
'[data-monster-role="switch"]',
|
|
346
|
+
);
|
|
347
|
+
let standardButtons = tabs.getOption('buttons.standard');
|
|
348
|
+
let popperButtons = tabs.getOption('buttons.popper');
|
|
349
|
+
|
|
350
|
+
expect(switchButton).to.not.equal(null);
|
|
351
|
+
expect(standardButtons.length).to.be.greaterThan(0);
|
|
352
|
+
expect(popperButtons.length).to.be.greaterThan(0);
|
|
353
|
+
expect(standardButtons.length + popperButtons.length).to.equal(11);
|
|
354
|
+
expect(switchButton.classList.contains('hidden')).to.equal(false);
|
|
355
|
+
|
|
356
|
+
const resizeObserver = TrackingResizeObserver.instances.find(
|
|
357
|
+
(observer) => observer.elements.includes(tabs.shadowRoot.querySelector('[data-monster-role="nav"]')),
|
|
358
|
+
);
|
|
359
|
+
expect(resizeObserver).to.not.equal(undefined);
|
|
360
|
+
resizeObserver.triggerResize([]);
|
|
361
|
+
|
|
362
|
+
return waitForCondition(() => {
|
|
363
|
+
return (
|
|
364
|
+
TrackingResizeObserver.callbackCount > 0 &&
|
|
365
|
+
tabs.getOption('buttons.standard').length === 11 &&
|
|
366
|
+
tabs.getOption('buttons.popper').length === 0
|
|
367
|
+
);
|
|
368
|
+
}).then(() => {
|
|
369
|
+
return waitForCondition(() => {
|
|
370
|
+
standardButtons = tabs.getOption('buttons.standard');
|
|
371
|
+
popperButtons = tabs.getOption('buttons.popper');
|
|
372
|
+
return (
|
|
373
|
+
standardButtons.length > 0 &&
|
|
374
|
+
popperButtons.length > 0 &&
|
|
375
|
+
standardButtons.length + popperButtons.length === 11 &&
|
|
376
|
+
new Set(
|
|
377
|
+
standardButtons
|
|
378
|
+
.concat(popperButtons)
|
|
379
|
+
.map((button) => button.reference),
|
|
380
|
+
).size === 11
|
|
270
381
|
);
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
).to.equal(11);
|
|
280
|
-
expect(switchButton.classList.contains('hidden')).to.equal(false);
|
|
281
|
-
|
|
282
|
-
window.dispatchEvent(new Event('resize'));
|
|
283
|
-
setTimeout(() => {
|
|
284
|
-
try {
|
|
285
|
-
standardButtons = tabs.getOption('buttons.standard');
|
|
286
|
-
popperButtons = tabs.getOption('buttons.popper');
|
|
287
|
-
expect(standardButtons.length).to.be.greaterThan(0);
|
|
288
|
-
expect(popperButtons.length).to.be.greaterThan(0);
|
|
289
|
-
expect(
|
|
290
|
-
standardButtons.length + popperButtons.length,
|
|
291
|
-
).to.equal(11);
|
|
292
|
-
expect(
|
|
293
|
-
new Set(
|
|
294
|
-
standardButtons.concat(popperButtons).map((button) => button.reference),
|
|
295
|
-
).size,
|
|
296
|
-
).to.equal(11);
|
|
297
|
-
global.HTMLElement.prototype.getBoundingClientRect =
|
|
298
|
-
originalGetBoundingClientRect;
|
|
299
|
-
done();
|
|
300
|
-
} catch (e) {
|
|
301
|
-
global.HTMLElement.prototype.getBoundingClientRect =
|
|
302
|
-
originalGetBoundingClientRect;
|
|
303
|
-
done(e);
|
|
304
|
-
}
|
|
305
|
-
}, 250);
|
|
306
|
-
} catch (e) {
|
|
307
|
-
global.HTMLElement.prototype.getBoundingClientRect =
|
|
308
|
-
originalGetBoundingClientRect;
|
|
309
|
-
done(e);
|
|
310
|
-
}
|
|
311
|
-
}, 250);
|
|
382
|
+
});
|
|
383
|
+
}).then(() => {
|
|
384
|
+
restoreBoundingClientRect();
|
|
385
|
+
restoreBoundingClientRect = null;
|
|
386
|
+
restoreResizeObserver();
|
|
387
|
+
restoreResizeObserver = null;
|
|
388
|
+
done();
|
|
389
|
+
});
|
|
312
390
|
} catch (e) {
|
|
313
|
-
|
|
314
|
-
|
|
391
|
+
restoreBoundingClientRect();
|
|
392
|
+
restoreBoundingClientRect = null;
|
|
393
|
+
restoreResizeObserver();
|
|
394
|
+
restoreResizeObserver = null;
|
|
315
395
|
return done(e);
|
|
316
396
|
}
|
|
317
|
-
|
|
397
|
+
}).catch((e) => {
|
|
398
|
+
if (restoreBoundingClientRect instanceof Function) {
|
|
399
|
+
restoreBoundingClientRect();
|
|
400
|
+
restoreBoundingClientRect = null;
|
|
401
|
+
}
|
|
402
|
+
if (restoreResizeObserver instanceof Function) {
|
|
403
|
+
restoreResizeObserver();
|
|
404
|
+
restoreResizeObserver = null;
|
|
405
|
+
}
|
|
406
|
+
done(e);
|
|
407
|
+
});
|
|
318
408
|
});
|
|
319
409
|
|
|
320
410
|
});
|