@schukai/monster 4.145.0 → 4.145.2
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/control-bar.mjs +71 -34
- package/source/components/form/util/floating-ui.mjs +210 -0
- package/test/cases/components/form/button-bar.mjs +38 -8
- package/test/cases/components/form/control-bar.mjs +121 -3
- package/test/cases/components/form/floating-ui.mjs +78 -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.145.
|
|
1
|
+
{"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.145.2"}
|
|
@@ -219,6 +219,35 @@ const EVENT_LAYOUT_CHANGED = "monster-control-bar-layout-changed";
|
|
|
219
219
|
const LAYOUT_CHANGED_EVENT_DELAY = 20;
|
|
220
220
|
const LAYOUT_SWITCH_HYSTERESIS = 16;
|
|
221
221
|
|
|
222
|
+
/**
|
|
223
|
+
* @private
|
|
224
|
+
* @type {string}
|
|
225
|
+
*/
|
|
226
|
+
const CONTAINED_CONTROL_SELECTOR = [
|
|
227
|
+
"monster-input-group",
|
|
228
|
+
"monster-select",
|
|
229
|
+
"monster-button",
|
|
230
|
+
"monster-state-button",
|
|
231
|
+
"monster-message-state-button",
|
|
232
|
+
"monster-action-button",
|
|
233
|
+
"monster-api-button",
|
|
234
|
+
"monster-confirm-button",
|
|
235
|
+
"monster-popper-button",
|
|
236
|
+
].join(",");
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* @private
|
|
240
|
+
* @type {string}
|
|
241
|
+
*/
|
|
242
|
+
const VISUAL_BORDER_CONTROL_SELECTOR = [
|
|
243
|
+
"button",
|
|
244
|
+
"input",
|
|
245
|
+
"select",
|
|
246
|
+
"textarea",
|
|
247
|
+
CONTAINED_CONTROL_SELECTOR,
|
|
248
|
+
"[data-monster-role=control]",
|
|
249
|
+
].join(",");
|
|
250
|
+
|
|
222
251
|
/**
|
|
223
252
|
* A control bar control.
|
|
224
253
|
*
|
|
@@ -1213,24 +1242,36 @@ function isWrapperElement(element) {
|
|
|
1213
1242
|
* @return {HTMLElement[]}
|
|
1214
1243
|
*/
|
|
1215
1244
|
function getContainedControlElements(element) {
|
|
1216
|
-
if (
|
|
1245
|
+
if (element.matches(CONTAINED_CONTROL_SELECTOR)) {
|
|
1217
1246
|
return [];
|
|
1218
1247
|
}
|
|
1219
1248
|
|
|
1220
1249
|
return Array.from(
|
|
1221
|
-
element.querySelectorAll(
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1250
|
+
element.querySelectorAll(CONTAINED_CONTROL_SELECTOR),
|
|
1251
|
+
).filter((control) => {
|
|
1252
|
+
return (
|
|
1253
|
+
control instanceof HTMLElement &&
|
|
1254
|
+
isTopLevelContainedControl(element, control)
|
|
1255
|
+
);
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
/**
|
|
1260
|
+
* @private
|
|
1261
|
+
* @param {HTMLElement} container
|
|
1262
|
+
* @param {HTMLElement} control
|
|
1263
|
+
* @return {boolean}
|
|
1264
|
+
*/
|
|
1265
|
+
function isTopLevelContainedControl(container, control) {
|
|
1266
|
+
let parent = control.parentElement;
|
|
1267
|
+
while (parent instanceof HTMLElement && parent !== container) {
|
|
1268
|
+
if (parent.matches(CONTAINED_CONTROL_SELECTOR)) {
|
|
1269
|
+
return false;
|
|
1270
|
+
}
|
|
1271
|
+
parent = parent.parentElement;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
return true;
|
|
1234
1275
|
}
|
|
1235
1276
|
|
|
1236
1277
|
/**
|
|
@@ -1253,6 +1294,13 @@ function updateJoinedBorders(layout, shouldShowSwitch) {
|
|
|
1253
1294
|
const marginTopByElement = new Map();
|
|
1254
1295
|
|
|
1255
1296
|
collectInlineJoinedBorders.call(this, mainItems, marginLeftByElement);
|
|
1297
|
+
for (const item of [...mainItems, ...layout.itemsToMoveToPopper]) {
|
|
1298
|
+
collectInlineJoinedBorders.call(
|
|
1299
|
+
this,
|
|
1300
|
+
getContainedControlElements(item),
|
|
1301
|
+
marginLeftByElement,
|
|
1302
|
+
);
|
|
1303
|
+
}
|
|
1256
1304
|
collectBlockJoinedBorders.call(
|
|
1257
1305
|
this,
|
|
1258
1306
|
layout.itemsToMoveToPopper,
|
|
@@ -1392,6 +1440,9 @@ function getJoinedBorderOffsetElements() {
|
|
|
1392
1440
|
const elements = Array.from(this.children).filter(
|
|
1393
1441
|
(element) => element instanceof HTMLElement,
|
|
1394
1442
|
);
|
|
1443
|
+
for (const element of [...elements]) {
|
|
1444
|
+
elements.push(...getContainedControlElements(element));
|
|
1445
|
+
}
|
|
1395
1446
|
if (this[popperNavElementSymbol] instanceof HTMLElement) {
|
|
1396
1447
|
elements.push(this[popperNavElementSymbol]);
|
|
1397
1448
|
}
|
|
@@ -1458,22 +1509,6 @@ function getVisualBorderElement(element, side) {
|
|
|
1458
1509
|
return this[switchElementSymbol] || element;
|
|
1459
1510
|
}
|
|
1460
1511
|
|
|
1461
|
-
const selector = [
|
|
1462
|
-
"button",
|
|
1463
|
-
"input",
|
|
1464
|
-
"select",
|
|
1465
|
-
"textarea",
|
|
1466
|
-
"monster-input-group",
|
|
1467
|
-
"monster-select",
|
|
1468
|
-
"monster-button",
|
|
1469
|
-
"monster-state-button",
|
|
1470
|
-
"monster-message-state-button",
|
|
1471
|
-
"monster-action-button",
|
|
1472
|
-
"monster-api-button",
|
|
1473
|
-
"monster-confirm-button",
|
|
1474
|
-
"monster-popper-button",
|
|
1475
|
-
"[data-monster-role=control]",
|
|
1476
|
-
].join(",");
|
|
1477
1512
|
if (element.shadowRoot instanceof ShadowRoot) {
|
|
1478
1513
|
const primaryControl = element.shadowRoot.querySelector(
|
|
1479
1514
|
"[data-monster-role=button],button,input,select,textarea",
|
|
@@ -1482,7 +1517,9 @@ function getVisualBorderElement(element, side) {
|
|
|
1482
1517
|
return getVisualBorderElement.call(this, primaryControl, side);
|
|
1483
1518
|
}
|
|
1484
1519
|
|
|
1485
|
-
const control = element.shadowRoot.querySelector(
|
|
1520
|
+
const control = element.shadowRoot.querySelector(
|
|
1521
|
+
VISUAL_BORDER_CONTROL_SELECTOR,
|
|
1522
|
+
);
|
|
1486
1523
|
if (control instanceof HTMLElement && control !== element) {
|
|
1487
1524
|
return getVisualBorderElement.call(this, control, side);
|
|
1488
1525
|
}
|
|
@@ -1490,9 +1527,9 @@ function getVisualBorderElement(element, side) {
|
|
|
1490
1527
|
return element;
|
|
1491
1528
|
}
|
|
1492
1529
|
|
|
1493
|
-
const candidates = Array.from(
|
|
1494
|
-
(
|
|
1495
|
-
);
|
|
1530
|
+
const candidates = Array.from(
|
|
1531
|
+
element.querySelectorAll(VISUAL_BORDER_CONTROL_SELECTOR),
|
|
1532
|
+
).filter((candidate) => candidate instanceof HTMLElement);
|
|
1496
1533
|
if (candidates.length === 0) {
|
|
1497
1534
|
return element;
|
|
1498
1535
|
}
|
|
@@ -34,6 +34,8 @@ import {
|
|
|
34
34
|
export {
|
|
35
35
|
applyAdaptiveFloatingElementSize,
|
|
36
36
|
closePositionedPopper,
|
|
37
|
+
createVisibilityRecoveryConfig,
|
|
38
|
+
getFloatingVisibleRatio,
|
|
37
39
|
resolveClippingBoundaryElement,
|
|
38
40
|
resolveParentPopperContentBoundary,
|
|
39
41
|
isPositionedPopperOpen,
|
|
@@ -43,11 +45,13 @@ export {
|
|
|
43
45
|
|
|
44
46
|
const autoUpdateCleanupMap = new WeakMap();
|
|
45
47
|
const settlingFrameMap = new WeakMap();
|
|
48
|
+
const visibilityRecoveryMap = new WeakMap();
|
|
46
49
|
const floatingResizeObserverMap = new WeakMap();
|
|
47
50
|
const floatingSyncCycleMap = new WeakMap();
|
|
48
51
|
const floatingAppearanceFrameMap = new WeakMap();
|
|
49
52
|
const floatingAppearanceTimeoutMap = new WeakMap();
|
|
50
53
|
const adaptiveScrollHeightDatasetKey = "monsterAdaptiveScrollHeight";
|
|
54
|
+
const MINIMUM_VISIBLE_FLOATING_RATIO = 0.8;
|
|
51
55
|
|
|
52
56
|
/**
|
|
53
57
|
* @private
|
|
@@ -76,6 +80,7 @@ function openPositionedPopper(controlElement, popperElement, options) {
|
|
|
76
80
|
|
|
77
81
|
stopAutoUpdate(popperElement);
|
|
78
82
|
cancelFloatingAppearanceFrame(popperElement);
|
|
83
|
+
visibilityRecoveryMap.delete(popperElement);
|
|
79
84
|
popperElement.dataset.monsterAppearance = "opening";
|
|
80
85
|
popperElement.style.display = "block";
|
|
81
86
|
popperElement.style.position = config.strategy;
|
|
@@ -187,6 +192,13 @@ function syncFloatingPopover(
|
|
|
187
192
|
if (allowSettlingPass) {
|
|
188
193
|
scheduleSettlingPass(controlElement, popperElement, config);
|
|
189
194
|
}
|
|
195
|
+
|
|
196
|
+
scheduleVisibilityRecoveryPass(
|
|
197
|
+
controlElement,
|
|
198
|
+
popperElement,
|
|
199
|
+
config,
|
|
200
|
+
syncCycleId,
|
|
201
|
+
);
|
|
190
202
|
});
|
|
191
203
|
}
|
|
192
204
|
|
|
@@ -194,6 +206,7 @@ function closePositionedPopper(popperElement) {
|
|
|
194
206
|
cancelFloatingLayout(popperElement);
|
|
195
207
|
stopAutoUpdate(popperElement);
|
|
196
208
|
cancelFloatingAppearanceFrame(popperElement);
|
|
209
|
+
visibilityRecoveryMap.delete(popperElement);
|
|
197
210
|
delete popperElement.dataset.monsterAppearance;
|
|
198
211
|
popperElement.style.display = "none";
|
|
199
212
|
popperElement.style.removeProperty("visibility");
|
|
@@ -358,6 +371,203 @@ function normalizeShiftOptions(tokens, detectOverflowOptions) {
|
|
|
358
371
|
return options;
|
|
359
372
|
}
|
|
360
373
|
|
|
374
|
+
function scheduleVisibilityRecoveryPass(
|
|
375
|
+
controlElement,
|
|
376
|
+
popperElement,
|
|
377
|
+
config,
|
|
378
|
+
syncCycleId,
|
|
379
|
+
) {
|
|
380
|
+
if (config.visibilityRecovery === false || config.visibilityRecoveryPass) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (!isActiveFloatingSyncCycle(popperElement, syncCycleId)) {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const visibleRatio = getFloatingVisibleRatio(popperElement);
|
|
389
|
+
if (visibleRatio >= MINIMUM_VISIBLE_FLOATING_RATIO) {
|
|
390
|
+
visibilityRecoveryMap.delete(popperElement);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (visibilityRecoveryMap.has(popperElement)) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
visibilityRecoveryMap.set(popperElement, true);
|
|
398
|
+
|
|
399
|
+
enqueueFloatingLayout({
|
|
400
|
+
popperElement,
|
|
401
|
+
reason: FLOATING_LAYOUT_REASON.SETTLE | FLOATING_LAYOUT_REASON.VIEWPORT,
|
|
402
|
+
isActive: () => isPositionedPopperOpen(popperElement),
|
|
403
|
+
position: () => {
|
|
404
|
+
runFloatingUpdateHook(popperElement);
|
|
405
|
+
syncFloatingPopover(
|
|
406
|
+
controlElement,
|
|
407
|
+
popperElement,
|
|
408
|
+
createVisibilityRecoveryConfig(config),
|
|
409
|
+
false,
|
|
410
|
+
);
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function getFloatingVisibleRatio(floatingElement) {
|
|
416
|
+
if (!(floatingElement instanceof HTMLElement)) {
|
|
417
|
+
return 1;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const rect = floatingElement.getBoundingClientRect();
|
|
421
|
+
const area = rect.width * rect.height;
|
|
422
|
+
if (!Number.isFinite(area) || area <= 0) {
|
|
423
|
+
return 1;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const visibleRect = getVisibleFloatingRect(floatingElement, rect);
|
|
427
|
+
if (!visibleRect) {
|
|
428
|
+
return 0;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const visibleArea = visibleRect.width * visibleRect.height;
|
|
432
|
+
return Math.max(0, Math.min(1, visibleArea / area));
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function getVisibleFloatingRect(floatingElement, rect) {
|
|
436
|
+
let visibleRect = normalizeRect(rect);
|
|
437
|
+
const viewportRect = {
|
|
438
|
+
top: 0,
|
|
439
|
+
left: 0,
|
|
440
|
+
right: window.innerWidth || document.documentElement.clientWidth || 0,
|
|
441
|
+
bottom: window.innerHeight || document.documentElement.clientHeight || 0,
|
|
442
|
+
};
|
|
443
|
+
viewportRect.width = Math.max(0, viewportRect.right - viewportRect.left);
|
|
444
|
+
viewportRect.height = Math.max(0, viewportRect.bottom - viewportRect.top);
|
|
445
|
+
|
|
446
|
+
visibleRect = intersectRects(visibleRect, viewportRect);
|
|
447
|
+
if (!visibleRect) {
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
for (const clippingContainer of getFloatingClippingContainers(floatingElement)) {
|
|
452
|
+
visibleRect = intersectRects(
|
|
453
|
+
visibleRect,
|
|
454
|
+
normalizeRect(clippingContainer.getBoundingClientRect()),
|
|
455
|
+
);
|
|
456
|
+
if (!visibleRect) {
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return visibleRect;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function getFloatingClippingContainers(element) {
|
|
465
|
+
const result = [];
|
|
466
|
+
let current = getComposedParent(element);
|
|
467
|
+
|
|
468
|
+
while (current) {
|
|
469
|
+
if (
|
|
470
|
+
current instanceof HTMLElement &&
|
|
471
|
+
isClippingContainer(getComputedStyle(current))
|
|
472
|
+
) {
|
|
473
|
+
result.push(current);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
current = getComposedParent(current);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return result;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function normalizeRect(rect) {
|
|
483
|
+
const left = Number.isFinite(rect.left) ? rect.left : rect.x || 0;
|
|
484
|
+
const top = Number.isFinite(rect.top) ? rect.top : rect.y || 0;
|
|
485
|
+
const right = Number.isFinite(rect.right) ? rect.right : left + rect.width;
|
|
486
|
+
const bottom = Number.isFinite(rect.bottom)
|
|
487
|
+
? rect.bottom
|
|
488
|
+
: top + rect.height;
|
|
489
|
+
|
|
490
|
+
return {
|
|
491
|
+
top,
|
|
492
|
+
left,
|
|
493
|
+
right,
|
|
494
|
+
bottom,
|
|
495
|
+
width: Math.max(0, right - left),
|
|
496
|
+
height: Math.max(0, bottom - top),
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function intersectRects(firstRect, secondRect) {
|
|
501
|
+
const left = Math.max(firstRect.left, secondRect.left);
|
|
502
|
+
const top = Math.max(firstRect.top, secondRect.top);
|
|
503
|
+
const right = Math.min(firstRect.right, secondRect.right);
|
|
504
|
+
const bottom = Math.min(firstRect.bottom, secondRect.bottom);
|
|
505
|
+
|
|
506
|
+
if (right <= left || bottom <= top) {
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return {
|
|
511
|
+
top,
|
|
512
|
+
left,
|
|
513
|
+
right,
|
|
514
|
+
bottom,
|
|
515
|
+
width: right - left,
|
|
516
|
+
height: bottom - top,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function createVisibilityRecoveryConfig(config) {
|
|
521
|
+
return Object.assign({}, config, {
|
|
522
|
+
visibilityRecoveryPass: true,
|
|
523
|
+
middleware: createVisibilityRecoveryMiddleware(config.middleware),
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function createVisibilityRecoveryMiddleware(middleware) {
|
|
528
|
+
const source = isArray(middleware) ? middleware : [];
|
|
529
|
+
const offsetMiddleware = [];
|
|
530
|
+
const remainingMiddleware = [];
|
|
531
|
+
let hasShift = false;
|
|
532
|
+
|
|
533
|
+
for (const entry of source) {
|
|
534
|
+
const tokenName = getMiddlewareTokenName(entry);
|
|
535
|
+
if (tokenName === "flip" || tokenName === "autoPlacement") {
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
if (tokenName === "offset") {
|
|
539
|
+
offsetMiddleware.push(entry);
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
if (tokenName === "shift") {
|
|
543
|
+
hasShift = true;
|
|
544
|
+
}
|
|
545
|
+
remainingMiddleware.push(entry);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (!hasShift) {
|
|
549
|
+
remainingMiddleware.unshift("shift:crossAxis");
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return [
|
|
553
|
+
...offsetMiddleware,
|
|
554
|
+
"autoPlacement:top,bottom,right,left",
|
|
555
|
+
...remainingMiddleware,
|
|
556
|
+
];
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function getMiddlewareTokenName(entry) {
|
|
560
|
+
if (isString(entry)) {
|
|
561
|
+
return entry.split(":").shift();
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (isObject(entry) && isString(entry.name)) {
|
|
565
|
+
return entry.name;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
|
|
361
571
|
function createAdaptiveSizeMiddleware(
|
|
362
572
|
detectOverflowOptions,
|
|
363
573
|
popperElement,
|
|
@@ -6,6 +6,29 @@ import { ResizeObserverMock } from "../../../util/resize-observer.mjs";
|
|
|
6
6
|
let expect = chai.expect;
|
|
7
7
|
chai.use(chaiDom);
|
|
8
8
|
|
|
9
|
+
function waitForCondition(check, { timeout = 4000, interval = 25 } = {}) {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
const start = Date.now();
|
|
12
|
+
const poll = () => {
|
|
13
|
+
try {
|
|
14
|
+
if (check()) {
|
|
15
|
+
resolve();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
} catch {}
|
|
19
|
+
|
|
20
|
+
if (Date.now() - start >= timeout) {
|
|
21
|
+
reject(new Error("Timed out waiting for condition"));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setTimeout(poll, interval);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
poll();
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
9
32
|
const waitForLayout = () => new Promise((resolve) => setTimeout(resolve, 120));
|
|
10
33
|
|
|
11
34
|
const html = `
|
|
@@ -92,7 +115,12 @@ describe("ButtonBar", function () {
|
|
|
92
115
|
`;
|
|
93
116
|
const bar = document.getElementById("empty-auto-hidden-button-bar");
|
|
94
117
|
|
|
95
|
-
await
|
|
118
|
+
await waitForCondition(() => {
|
|
119
|
+
return (
|
|
120
|
+
bar.hasAttribute("hidden") &&
|
|
121
|
+
bar.hasAttribute("data-monster-empty-hidden")
|
|
122
|
+
);
|
|
123
|
+
});
|
|
96
124
|
|
|
97
125
|
expect(bar.getOption("layout.hideWhenEmpty")).to.equal(true);
|
|
98
126
|
expect(bar.hasAttribute("hidden")).to.be.true;
|
|
@@ -112,15 +140,17 @@ describe("ButtonBar", function () {
|
|
|
112
140
|
const bar = document.getElementById("dynamic-auto-hidden-button-bar");
|
|
113
141
|
const button = document.getElementById("dynamic-auto-hidden-button");
|
|
114
142
|
|
|
115
|
-
await
|
|
116
|
-
|
|
117
|
-
|
|
143
|
+
await waitForCondition(() => {
|
|
144
|
+
return bar.hasAttribute("hidden");
|
|
145
|
+
});
|
|
118
146
|
|
|
119
147
|
button.removeAttribute("hidden");
|
|
120
|
-
await
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
148
|
+
await waitForCondition(() => {
|
|
149
|
+
return (
|
|
150
|
+
!bar.hasAttribute("hidden") &&
|
|
151
|
+
!bar.hasAttribute("data-monster-empty-hidden")
|
|
152
|
+
);
|
|
153
|
+
});
|
|
124
154
|
});
|
|
125
155
|
|
|
126
156
|
it("should keep an auto-hidden button bar visible when buttons overflow into the popper", async function () {
|
|
@@ -6,7 +6,8 @@ import { ResizeObserverMock } from "../../../util/resize-observer.mjs";
|
|
|
6
6
|
const expect = chai.expect;
|
|
7
7
|
chai.use(chaiDom);
|
|
8
8
|
|
|
9
|
-
const waitForLayout = (
|
|
9
|
+
const waitForLayout = (timeout = 120) =>
|
|
10
|
+
new Promise((resolve) => setTimeout(resolve, timeout));
|
|
10
11
|
|
|
11
12
|
const html = `
|
|
12
13
|
<div id="test1">
|
|
@@ -156,13 +157,13 @@ describe("ControlBar", function () {
|
|
|
156
157
|
expect(bar.hasAttribute("data-monster-empty-hidden")).to.be.true;
|
|
157
158
|
|
|
158
159
|
button.removeAttribute("hidden");
|
|
159
|
-
await waitForLayout();
|
|
160
|
+
await waitForLayout(360);
|
|
160
161
|
|
|
161
162
|
expect(bar.hasAttribute("hidden")).to.be.false;
|
|
162
163
|
expect(bar.hasAttribute("data-monster-empty-hidden")).to.be.false;
|
|
163
164
|
|
|
164
165
|
button.style.display = "none";
|
|
165
|
-
await waitForLayout();
|
|
166
|
+
await waitForLayout(360);
|
|
166
167
|
|
|
167
168
|
expect(bar.hasAttribute("hidden")).to.be.true;
|
|
168
169
|
expect(bar.hasAttribute("data-monster-empty-hidden")).to.be.true;
|
|
@@ -632,6 +633,123 @@ describe("ControlBar", function () {
|
|
|
632
633
|
}
|
|
633
634
|
});
|
|
634
635
|
|
|
636
|
+
it("should size and join popper buttons inside custom control wrappers", async function () {
|
|
637
|
+
const originalRequestAnimationFrame = window.requestAnimationFrame;
|
|
638
|
+
const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
|
|
639
|
+
|
|
640
|
+
const scheduledCallbacks = [];
|
|
641
|
+
const flushFrames = async () => {
|
|
642
|
+
while (scheduledCallbacks.length > 0) {
|
|
643
|
+
scheduledCallbacks.shift()();
|
|
644
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
try {
|
|
649
|
+
window.requestAnimationFrame = (callback) => {
|
|
650
|
+
scheduledCallbacks.push(callback);
|
|
651
|
+
return scheduledCallbacks.length;
|
|
652
|
+
};
|
|
653
|
+
globalThis.requestAnimationFrame = window.requestAnimationFrame;
|
|
654
|
+
|
|
655
|
+
const mocks = document.getElementById("mocks");
|
|
656
|
+
mocks.innerHTML = `
|
|
657
|
+
<div id="custom-wrapper-border-bar-wrapper">
|
|
658
|
+
<monster-control-bar id="custom-wrapper-border-bar">
|
|
659
|
+
<nucleus-workflow-transition-control id="custom-wrapper-workflow">
|
|
660
|
+
<monster-popper-button id="custom-wrapper-release">
|
|
661
|
+
<span slot="button">Release order</span>
|
|
662
|
+
</monster-popper-button>
|
|
663
|
+
<monster-popper-button id="custom-wrapper-cancel">
|
|
664
|
+
<span slot="button">Cancel order</span>
|
|
665
|
+
</monster-popper-button>
|
|
666
|
+
</nucleus-workflow-transition-control>
|
|
667
|
+
<nucleus-follow-ups-action id="custom-wrapper-follow-up">
|
|
668
|
+
<monster-popper-button id="custom-wrapper-follow-up-button">
|
|
669
|
+
<span slot="button">Create follow-up</span>
|
|
670
|
+
<monster-message-state-button id="custom-wrapper-follow-up-submit">
|
|
671
|
+
Create follow-up
|
|
672
|
+
</monster-message-state-button>
|
|
673
|
+
</monster-popper-button>
|
|
674
|
+
</nucleus-follow-ups-action>
|
|
675
|
+
</monster-control-bar>
|
|
676
|
+
</div>
|
|
677
|
+
`;
|
|
678
|
+
|
|
679
|
+
const wrapper = document.getElementById(
|
|
680
|
+
"custom-wrapper-border-bar-wrapper",
|
|
681
|
+
);
|
|
682
|
+
const workflow = document.getElementById("custom-wrapper-workflow");
|
|
683
|
+
const followUp = document.getElementById("custom-wrapper-follow-up");
|
|
684
|
+
const release = document.getElementById("custom-wrapper-release");
|
|
685
|
+
const cancel = document.getElementById("custom-wrapper-cancel");
|
|
686
|
+
const followUpButton = document.getElementById(
|
|
687
|
+
"custom-wrapper-follow-up-button",
|
|
688
|
+
);
|
|
689
|
+
const followUpSubmit = document.getElementById(
|
|
690
|
+
"custom-wrapper-follow-up-submit",
|
|
691
|
+
);
|
|
692
|
+
|
|
693
|
+
wrapper.style.boxSizing = "border-box";
|
|
694
|
+
wrapper.style.width = "500px";
|
|
695
|
+
Object.defineProperty(wrapper, "clientWidth", {
|
|
696
|
+
configurable: true,
|
|
697
|
+
value: 500,
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
for (const control of [workflow, followUp]) {
|
|
701
|
+
Object.defineProperty(control, "offsetWidth", {
|
|
702
|
+
configurable: true,
|
|
703
|
+
value: 160,
|
|
704
|
+
});
|
|
705
|
+
Object.defineProperty(control, "offsetHeight", {
|
|
706
|
+
configurable: true,
|
|
707
|
+
value: 30,
|
|
708
|
+
});
|
|
709
|
+
control.getBoundingClientRect = () => ({
|
|
710
|
+
width: 160,
|
|
711
|
+
height: 30,
|
|
712
|
+
top: 0,
|
|
713
|
+
right: 160,
|
|
714
|
+
bottom: 30,
|
|
715
|
+
left: 0,
|
|
716
|
+
x: 0,
|
|
717
|
+
y: 0,
|
|
718
|
+
toJSON: () => {},
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const releaseInnerButton = release.shadowRoot?.querySelector(
|
|
723
|
+
'[data-monster-role="button"]',
|
|
724
|
+
);
|
|
725
|
+
const cancelInnerButton = cancel.shadowRoot?.querySelector(
|
|
726
|
+
'[data-monster-role="button"]',
|
|
727
|
+
);
|
|
728
|
+
const followUpInnerButton = followUpButton.shadowRoot?.querySelector(
|
|
729
|
+
'[data-monster-role="button"]',
|
|
730
|
+
);
|
|
731
|
+
|
|
732
|
+
releaseInnerButton.style.borderRightWidth = "3px";
|
|
733
|
+
cancelInnerButton.style.borderLeftWidth = "3px";
|
|
734
|
+
cancelInnerButton.style.borderRightWidth = "2px";
|
|
735
|
+
followUpInnerButton.style.borderLeftWidth = "2px";
|
|
736
|
+
|
|
737
|
+
await flushFrames();
|
|
738
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
739
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
740
|
+
|
|
741
|
+
expect(release.style.height).to.equal("100%");
|
|
742
|
+
expect(cancel.style.height).to.equal("100%");
|
|
743
|
+
expect(followUpButton.style.height).to.equal("100%");
|
|
744
|
+
expect(cancel.style.marginLeft).to.equal("-3px");
|
|
745
|
+
expect(followUp.style.marginLeft).to.equal("-2px");
|
|
746
|
+
expect(followUpSubmit.style.height).to.equal("");
|
|
747
|
+
} finally {
|
|
748
|
+
window.requestAnimationFrame = originalRequestAnimationFrame;
|
|
749
|
+
globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
|
|
750
|
+
}
|
|
751
|
+
});
|
|
752
|
+
|
|
635
753
|
it("should join borders between message state and confirm buttons", async function () {
|
|
636
754
|
const originalRequestAnimationFrame = window.requestAnimationFrame;
|
|
637
755
|
const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
|
|
@@ -5,6 +5,8 @@ let expect = chai.expect;
|
|
|
5
5
|
|
|
6
6
|
let resolveClippingBoundaryElement;
|
|
7
7
|
let applyAdaptiveFloatingElementSize;
|
|
8
|
+
let createVisibilityRecoveryConfig;
|
|
9
|
+
let getFloatingVisibleRatio;
|
|
8
10
|
|
|
9
11
|
describe("form floating-ui boundary resolution", function () {
|
|
10
12
|
before(function (done) {
|
|
@@ -17,6 +19,8 @@ describe("form floating-ui boundary resolution", function () {
|
|
|
17
19
|
.then((m) => {
|
|
18
20
|
resolveClippingBoundaryElement = m.resolveClippingBoundaryElement;
|
|
19
21
|
applyAdaptiveFloatingElementSize = m.applyAdaptiveFloatingElementSize;
|
|
22
|
+
createVisibilityRecoveryConfig = m.createVisibilityRecoveryConfig;
|
|
23
|
+
getFloatingVisibleRatio = m.getFloatingVisibleRatio;
|
|
20
24
|
done();
|
|
21
25
|
})
|
|
22
26
|
.catch((e) => done(e));
|
|
@@ -398,4 +402,78 @@ describe("form floating-ui boundary resolution", function () {
|
|
|
398
402
|
expect(content.style.height).to.equal("200px");
|
|
399
403
|
expect(content.style.maxHeight).to.equal("200px");
|
|
400
404
|
});
|
|
405
|
+
|
|
406
|
+
it("should calculate the visible popper ratio against clipping containers", function () {
|
|
407
|
+
const mocks = document.getElementById("mocks");
|
|
408
|
+
const wrapper = document.createElement("div");
|
|
409
|
+
const popper = document.createElement("div");
|
|
410
|
+
const originalInnerWidth = window.innerWidth;
|
|
411
|
+
const originalInnerHeight = window.innerHeight;
|
|
412
|
+
|
|
413
|
+
Object.defineProperty(window, "innerWidth", {
|
|
414
|
+
configurable: true,
|
|
415
|
+
value: 200,
|
|
416
|
+
});
|
|
417
|
+
Object.defineProperty(window, "innerHeight", {
|
|
418
|
+
configurable: true,
|
|
419
|
+
value: 200,
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
wrapper.style.overflow = "hidden";
|
|
423
|
+
wrapper.getBoundingClientRect = () => {
|
|
424
|
+
return {
|
|
425
|
+
width: 100,
|
|
426
|
+
height: 100,
|
|
427
|
+
top: 0,
|
|
428
|
+
left: 0,
|
|
429
|
+
right: 100,
|
|
430
|
+
bottom: 100,
|
|
431
|
+
x: 0,
|
|
432
|
+
y: 0,
|
|
433
|
+
};
|
|
434
|
+
};
|
|
435
|
+
popper.getBoundingClientRect = () => {
|
|
436
|
+
return {
|
|
437
|
+
width: 100,
|
|
438
|
+
height: 100,
|
|
439
|
+
top: -50,
|
|
440
|
+
left: 50,
|
|
441
|
+
right: 150,
|
|
442
|
+
bottom: 50,
|
|
443
|
+
x: 50,
|
|
444
|
+
y: -50,
|
|
445
|
+
};
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
wrapper.appendChild(popper);
|
|
449
|
+
mocks.appendChild(wrapper);
|
|
450
|
+
|
|
451
|
+
try {
|
|
452
|
+
expect(getFloatingVisibleRatio(popper)).to.equal(0.25);
|
|
453
|
+
} finally {
|
|
454
|
+
Object.defineProperty(window, "innerWidth", {
|
|
455
|
+
configurable: true,
|
|
456
|
+
value: originalInnerWidth,
|
|
457
|
+
});
|
|
458
|
+
Object.defineProperty(window, "innerHeight", {
|
|
459
|
+
configurable: true,
|
|
460
|
+
value: originalInnerHeight,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it("should build a bounded visibility recovery pass with auto placement", function () {
|
|
466
|
+
const recoveryConfig = createVisibilityRecoveryConfig({
|
|
467
|
+
placement: "top",
|
|
468
|
+
middleware: ["flip", "shift", "offset:15", "arrow"],
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
expect(recoveryConfig.visibilityRecoveryPass).to.equal(true);
|
|
472
|
+
expect(recoveryConfig.middleware).to.deep.equal([
|
|
473
|
+
"offset:15",
|
|
474
|
+
"autoPlacement:top,bottom,right,left",
|
|
475
|
+
"shift",
|
|
476
|
+
"arrow",
|
|
477
|
+
]);
|
|
478
|
+
});
|
|
401
479
|
});
|