@schukai/monster 4.129.4 → 4.129.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1 +1 @@
1
- {"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6","@popperjs/core":"^2.11.8"},"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.129.4"}
1
+ {"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.129.6"}
@@ -163,7 +163,7 @@ class Copy extends CustomElement {
163
163
  * @property {boolean} features.preventOpenEventSent=false Prevent open event from being sent
164
164
  * @property {Object} popper Popper configuration
165
165
  * @property {string} popper.placement="top" Popper placement
166
- * @property {string[]} popper.middleware=["autoPlacement", "shift", "offset:15", "arrow"] Popper middleware
166
+ * @property {string[]} popper.middleware=["flip", "shift", "offset:-1", "arrow"] Popper middleware
167
167
  * @property {boolean} disabled=false Disabled state
168
168
  */
169
169
  get defaults() {
@@ -178,7 +178,7 @@ class Copy extends CustomElement {
178
178
  },
179
179
  popper: {
180
180
  placement: "top",
181
- middleware: ["autoPlacement", "offset:-1", "arrow"],
181
+ middleware: ["flip", "shift", "offset:-1", "arrow"],
182
182
  },
183
183
  });
184
184
  }
@@ -21,11 +21,14 @@ import {
21
21
  import { findTargetElementFromEvent } from "../../dom/events.mjs";
22
22
  import { clone } from "../../util/clone.mjs";
23
23
  import { ColumnBarStyleSheet } from "./stylesheet/column-bar.mjs";
24
- import { createPopper } from "@popperjs/core";
25
24
  import { getLocaleOfDocument } from "../../dom/locale.mjs";
26
25
  import { hasObjectLink } from "../../dom/attributes.mjs";
27
26
  import { customElementUpdaterLinkSymbol } from "../../dom/constants.mjs";
28
27
  import { getGlobalObject } from "../../types/global.mjs";
28
+ import {
29
+ createFloatingPopper,
30
+ popperInstanceSymbol,
31
+ } from "../form/util/popper.mjs";
29
32
 
30
33
  export { ColumnBar };
31
34
 
@@ -53,12 +56,6 @@ const settingsLayerElementSymbol = Symbol("settingsLayerElement");
53
56
  */
54
57
  const dotsContainerElementSymbol = Symbol("dotsContainerElement");
55
58
 
56
- /**
57
- * @private
58
- * @type {symbol}
59
- */
60
- const popperInstanceSymbol = Symbol("popperInstance");
61
-
62
59
  /**
63
60
  * @private
64
61
  * @type {symbol}
@@ -226,7 +223,11 @@ class ColumnBar extends CustomElement {
226
223
  }
227
224
 
228
225
  if (typeof this[dotsFrameRequestSymbol] === "number") {
229
- cancelAnimationFrame(this[dotsFrameRequestSymbol]);
226
+ const cancelFrame =
227
+ typeof globalThis.cancelAnimationFrame === "function"
228
+ ? globalThis.cancelAnimationFrame.bind(globalThis)
229
+ : globalThis.clearTimeout.bind(globalThis);
230
+ cancelFrame(this[dotsFrameRequestSymbol]);
230
231
  this[dotsFrameRequestSymbol] = null;
231
232
  }
232
233
 
@@ -241,6 +242,8 @@ class ColumnBar extends CustomElement {
241
242
  );
242
243
  this[closeEventHandlerSymbol] = null;
243
244
  }
245
+
246
+ this[popperInstanceSymbol]?.destroy();
244
247
  }
245
248
 
246
249
  /**
@@ -350,7 +353,7 @@ function initControlReferences() {
350
353
  function initEventHandler() {
351
354
  const self = this;
352
355
 
353
- self[popperInstanceSymbol] = createPopper(
356
+ self[popperInstanceSymbol] = createFloatingPopper(
354
357
  self[settingsButtonElementSymbol],
355
358
  self[settingsLayerElementSymbol],
356
359
  {
@@ -183,7 +183,7 @@ class ButtonBar extends CustomElement {
183
183
  },
184
184
  popper: {
185
185
  placement: "left",
186
- middleware: ["autoPlacement", "shift", "offset:5"],
186
+ middleware: ["flip", "shift", "offset:5"],
187
187
  },
188
188
  });
189
189
 
@@ -19,15 +19,14 @@ import {
19
19
  computePosition,
20
20
  offset,
21
21
  flip,
22
+ hide,
22
23
  size,
23
24
  shift,
24
25
  } from "@floating-ui/dom";
26
+ import { isArray, isFunction, isObject, isString } from "../../../types/is.mjs";
25
27
  import { Processing } from "../../../util/processing.mjs";
26
- import { isString, isArray, isObject, isFunction } from "../../../types/is.mjs";
27
- import { hide } from "@popperjs/core";
28
28
 
29
29
  export {
30
- canUseNativePositionedPopper,
31
30
  closePositionedPopper,
32
31
  isPositionedPopperOpen,
33
32
  openPositionedPopper,
@@ -35,9 +34,6 @@ export {
35
34
  };
36
35
 
37
36
  const autoUpdateCleanupMap = new WeakMap();
38
- const nativeArrowSyncFrameMap = new WeakMap();
39
- const anchorNameMap = new WeakMap();
40
- let anchorNameCounter = 0;
41
37
 
42
38
  /**
43
39
  * @private
@@ -50,15 +46,6 @@ function positionPopper(controlElement, popperElement, options) {
50
46
  const config = normalizePopperConfig(options);
51
47
 
52
48
  return new Processing(() => {
53
- if (canUseNativePositionedPopper(controlElement, popperElement, config)) {
54
- prepareNativePopover(controlElement, popperElement, config);
55
- startAutoUpdate(controlElement, popperElement, () => {
56
- syncNativePopover(controlElement, popperElement, config);
57
- });
58
- syncNativePopover(controlElement, popperElement, config);
59
- return;
60
- }
61
-
62
49
  enableFloatingPositioning(controlElement, popperElement, config);
63
50
  }).run();
64
51
  }
@@ -66,21 +53,16 @@ function positionPopper(controlElement, popperElement, options) {
66
53
  function openPositionedPopper(controlElement, popperElement, options) {
67
54
  const config = normalizePopperConfig(options);
68
55
 
69
- if (canUseNativePositionedPopper(controlElement, popperElement, config)) {
70
- prepareNativePopover(controlElement, popperElement, config);
71
- popperElement.style.removeProperty("transform");
72
- if (!matchesPopoverOpen(popperElement)) {
73
- popperElement.showPopover();
74
- }
75
- return;
76
- }
77
-
78
- teardownNativePopover(popperElement);
56
+ stopAutoUpdate(popperElement);
79
57
  popperElement.style.display = "block";
58
+ popperElement.style.removeProperty("transform");
59
+
60
+ // Keep the call signature stable even though only Floating UI is used now.
61
+ void controlElement;
62
+ void config;
80
63
  }
81
64
 
82
65
  function enableFloatingPositioning(controlElement, popperElement, config) {
83
- teardownNativePopover(popperElement);
84
66
  popperElement.style.removeProperty("visibility");
85
67
  popperElement.style.display = "block";
86
68
 
@@ -122,63 +104,21 @@ function syncFloatingPopover(controlElement, popperElement, config) {
122
104
 
123
105
  function closePositionedPopper(popperElement) {
124
106
  stopAutoUpdate(popperElement);
125
- cancelScheduledNativeArrowSync(popperElement);
126
-
127
- if (isNativePopoverElement(popperElement)) {
128
- if (matchesPopoverOpen(popperElement)) {
129
- popperElement.hidePopover();
130
- }
131
- popperElement.style.display = "none";
132
- popperElement.style.removeProperty("visibility");
133
- popperElement.style.removeProperty("transform");
134
- return;
135
- }
136
-
137
107
  popperElement.style.display = "none";
108
+ popperElement.style.removeProperty("visibility");
109
+ popperElement.style.removeProperty("transform");
138
110
  }
139
111
 
140
112
  function isPositionedPopperOpen(popperElement) {
141
- if (isNativePopoverElement(popperElement)) {
142
- return matchesPopoverOpen(popperElement);
143
- }
144
-
145
113
  return popperElement.style.display === "block";
146
114
  }
147
115
 
148
- function canUseNativePositionedPopper(controlElement, popperElement, options) {
149
- if (!(controlElement instanceof HTMLElement)) {
150
- return false;
151
- }
152
-
153
- if (!(popperElement instanceof HTMLElement)) {
154
- return false;
155
- }
156
-
157
- if (options?.engine !== "native") {
158
- return false;
159
- }
160
-
161
- if (!("showPopover" in HTMLElement.prototype)) {
162
- return false;
163
- }
164
-
165
- if (typeof CSS === "undefined") {
166
- return false;
167
- }
168
-
169
- return (
170
- CSS.supports("anchor-name: --monster-popper-anchor") &&
171
- CSS.supports("position-anchor: --monster-popper-anchor") &&
172
- CSS.supports("top: anchor(bottom)")
173
- );
174
- }
175
-
176
116
  function normalizePopperConfig(options) {
177
117
  const config = Object.assign(
178
118
  {},
179
119
  {
180
120
  placement: "top",
181
- engine: "native",
121
+ engine: "floating",
182
122
  },
183
123
  options,
184
124
  );
@@ -194,7 +134,7 @@ function normalizePopperConfig(options) {
194
134
  }
195
135
 
196
136
  function normalizeMiddleware(config) {
197
- const middleware = config?.["middleware"];
137
+ const middleware = config?.middleware;
198
138
  if (isArray(middleware)) {
199
139
  return [...middleware];
200
140
  }
@@ -299,499 +239,6 @@ function stopAutoUpdate(popperElement) {
299
239
  autoUpdateCleanupMap.delete(popperElement);
300
240
  }
301
241
 
302
- function scheduleNativeArrowSync(controlElement, popperElement) {
303
- cancelScheduledNativeArrowSync(popperElement);
304
-
305
- const frameState = {
306
- outer: 0,
307
- inner: 0,
308
- };
309
-
310
- frameState.outer = requestAnimationFrame(() => {
311
- frameState.inner = requestAnimationFrame(() => {
312
- nativeArrowSyncFrameMap.delete(popperElement);
313
-
314
- if (
315
- !(controlElement instanceof HTMLElement) ||
316
- !(popperElement instanceof HTMLElement) ||
317
- !isNativePopoverElement(popperElement)
318
- ) {
319
- return;
320
- }
321
-
322
- if (
323
- !matchesPopoverOpen(popperElement) &&
324
- popperElement.style.display !== "block"
325
- ) {
326
- return;
327
- }
328
-
329
- syncNativeArrow(controlElement, popperElement);
330
- });
331
- });
332
-
333
- nativeArrowSyncFrameMap.set(popperElement, frameState);
334
- }
335
-
336
- function cancelScheduledNativeArrowSync(popperElement) {
337
- const frameState = nativeArrowSyncFrameMap.get(popperElement);
338
- if (!frameState) {
339
- return;
340
- }
341
-
342
- if (frameState.outer) {
343
- cancelAnimationFrame(frameState.outer);
344
- }
345
-
346
- if (frameState.inner) {
347
- cancelAnimationFrame(frameState.inner);
348
- }
349
-
350
- nativeArrowSyncFrameMap.delete(popperElement);
351
- }
352
-
353
- function prepareNativePopover(controlElement, popperElement, config) {
354
- const anchorName = getAnchorName(controlElement);
355
- popperElement.dataset.monsterPopperEngine = "native";
356
- popperElement.setAttribute("popover", "manual");
357
- controlElement.style.setProperty("anchor-name", anchorName);
358
- popperElement.style.setProperty("position-anchor", anchorName);
359
- popperElement.style.setProperty("inset", "auto");
360
- popperElement.style.setProperty("margin", "0");
361
- popperElement.style.setProperty("overflow", "visible");
362
- popperElement.style.display = "block";
363
- popperElement.style.removeProperty("transform");
364
- applyNativeTryFallbacks(popperElement, config);
365
- }
366
-
367
- function teardownNativePopover(popperElement) {
368
- if (!isNativePopoverElement(popperElement)) {
369
- return;
370
- }
371
-
372
- if (matchesPopoverOpen(popperElement)) {
373
- popperElement.hidePopover();
374
- }
375
-
376
- cancelScheduledNativeArrowSync(popperElement);
377
- delete popperElement.dataset.monsterPopperEngine;
378
- popperElement.removeAttribute("popover");
379
- popperElement.style.removeProperty("position-anchor");
380
- popperElement.style.removeProperty("position-area");
381
- popperElement.style.removeProperty("inset-area");
382
- popperElement.style.removeProperty("position-try");
383
- popperElement.style.removeProperty("position-try-fallbacks");
384
- popperElement.style.removeProperty("margin");
385
- popperElement.style.removeProperty("margin-top");
386
- popperElement.style.removeProperty("margin-right");
387
- popperElement.style.removeProperty("margin-bottom");
388
- popperElement.style.removeProperty("margin-left");
389
- popperElement.style.removeProperty("inset");
390
- popperElement.style.removeProperty("overflow");
391
- popperElement.style.removeProperty("--monster-popper-control-width");
392
- popperElement.style.removeProperty("--monster-popper-control-height");
393
- }
394
-
395
- function syncNativePopover(controlElement, popperElement, config) {
396
- const controlRect = controlElement.getBoundingClientRect();
397
- const placement = resolvePlacement(config.placement);
398
- const offsetValue = resolveOffset(config.middlewareTokens);
399
-
400
- popperElement.style.setProperty(
401
- "--monster-popper-control-width",
402
- `${Math.round(controlRect.width)}px`,
403
- );
404
- popperElement.style.setProperty(
405
- "--monster-popper-control-height",
406
- `${Math.round(controlRect.height)}px`,
407
- );
408
-
409
- applyNativePlacementStyles(popperElement, placement, offsetValue);
410
- popperElement.style.removeProperty("visibility");
411
-
412
- if (hasViewportOverflow(popperElement)) {
413
- enableFloatingPositioning(controlElement, popperElement, config);
414
- return;
415
- }
416
-
417
- syncNativeArrow(controlElement, popperElement);
418
- scheduleNativeArrowSync(controlElement, popperElement);
419
- }
420
-
421
- function resolvePlacement(value) {
422
- if (!isString(value) || value.trim().length === 0) {
423
- return { side: "top", align: "center" };
424
- }
425
-
426
- const [rawSide, rawAlign] = value.split("-");
427
- const side = rawSide === "auto" ? "top" : rawSide;
428
- const align =
429
- rawAlign === "start" || rawAlign === "end" ? rawAlign : "center";
430
-
431
- if (!["top", "bottom", "left", "right"].includes(side)) {
432
- return { side: "top", align: "center" };
433
- }
434
-
435
- return { side, align };
436
- }
437
-
438
- function resolveOffset(middleware) {
439
- if (!isArray(middleware)) {
440
- return 10;
441
- }
442
-
443
- for (const entry of middleware) {
444
- if (!isString(entry) || !entry.startsWith("offset")) {
445
- continue;
446
- }
447
-
448
- const [, rawValue] = entry.split(":");
449
- const parsed = parseInt(rawValue);
450
- if (Number.isFinite(parsed)) {
451
- return parsed;
452
- }
453
- }
454
-
455
- return 10;
456
- }
457
-
458
- function applyNativePlacementStyles(
459
- popperElement,
460
- { side, align },
461
- offsetValue,
462
- ) {
463
- if (supportsNativePositionArea()) {
464
- applyNativePositionAreaStyles(popperElement, { side, align }, offsetValue);
465
- return;
466
- }
467
-
468
- popperElement.style.removeProperty("position-area");
469
- popperElement.style.removeProperty("inset-area");
470
- popperElement.style.removeProperty("margin-top");
471
- popperElement.style.removeProperty("margin-right");
472
- popperElement.style.removeProperty("margin-bottom");
473
- popperElement.style.removeProperty("margin-left");
474
- popperElement.style.removeProperty("top");
475
- popperElement.style.removeProperty("right");
476
- popperElement.style.removeProperty("bottom");
477
- popperElement.style.removeProperty("left");
478
-
479
- switch (side) {
480
- case "bottom":
481
- popperElement.style.setProperty(
482
- "top",
483
- `calc(anchor(bottom) + ${offsetValue}px)`,
484
- );
485
- applyHorizontalAlignment(popperElement, align);
486
- break;
487
- case "left":
488
- popperElement.style.setProperty(
489
- "left",
490
- `calc(anchor(left) - 100% - ${offsetValue}px)`,
491
- );
492
- applyVerticalAlignment(popperElement, align);
493
- break;
494
- case "right":
495
- popperElement.style.setProperty(
496
- "left",
497
- `calc(anchor(right) + ${offsetValue}px)`,
498
- );
499
- applyVerticalAlignment(popperElement, align);
500
- break;
501
- case "top":
502
- default:
503
- popperElement.style.setProperty(
504
- "top",
505
- `calc(anchor(top) - 100% - ${offsetValue}px)`,
506
- );
507
- applyHorizontalAlignment(popperElement, align);
508
- break;
509
- }
510
- }
511
-
512
- function applyNativePositionAreaStyles(
513
- popperElement,
514
- { side, align },
515
- offsetValue,
516
- ) {
517
- const positionArea = resolveNativePositionArea({ side, align });
518
-
519
- popperElement.style.removeProperty("top");
520
- popperElement.style.removeProperty("right");
521
- popperElement.style.removeProperty("bottom");
522
- popperElement.style.removeProperty("left");
523
- popperElement.style.setProperty("position-area", positionArea);
524
- popperElement.style.setProperty("inset-area", positionArea);
525
- popperElement.style.setProperty("margin-top", "0px");
526
- popperElement.style.setProperty("margin-right", "0px");
527
- popperElement.style.setProperty("margin-bottom", "0px");
528
- popperElement.style.setProperty("margin-left", "0px");
529
-
530
- switch (side) {
531
- case "bottom":
532
- popperElement.style.setProperty("margin-top", `${offsetValue}px`);
533
- break;
534
- case "left":
535
- popperElement.style.setProperty("margin-right", `${offsetValue}px`);
536
- break;
537
- case "right":
538
- popperElement.style.setProperty("margin-left", `${offsetValue}px`);
539
- break;
540
- case "top":
541
- default:
542
- popperElement.style.setProperty("margin-bottom", `${offsetValue}px`);
543
- break;
544
- }
545
- }
546
-
547
- function resolveNativePositionArea({ side, align }) {
548
- switch (side) {
549
- case "bottom":
550
- switch (align) {
551
- case "end":
552
- return "bottom span-left";
553
- case "center":
554
- return "bottom center";
555
- case "start":
556
- default:
557
- return "bottom span-right";
558
- }
559
- case "left":
560
- switch (align) {
561
- case "end":
562
- return "left span-top";
563
- case "center":
564
- return "left center";
565
- case "start":
566
- default:
567
- return "left span-bottom";
568
- }
569
- case "right":
570
- switch (align) {
571
- case "end":
572
- return "right span-top";
573
- case "center":
574
- return "right center";
575
- case "start":
576
- default:
577
- return "right span-bottom";
578
- }
579
- case "top":
580
- default:
581
- switch (align) {
582
- case "end":
583
- return "top span-left";
584
- case "center":
585
- return "top center";
586
- case "start":
587
- default:
588
- return "top span-right";
589
- }
590
- }
591
- }
592
-
593
- function supportsNativePositionArea() {
594
- if (typeof CSS === "undefined") {
595
- return false;
596
- }
597
-
598
- return (
599
- CSS.supports("position-area: bottom center") ||
600
- CSS.supports("inset-area: bottom center")
601
- );
602
- }
603
-
604
- function applyHorizontalAlignment(popperElement, align) {
605
- switch (align) {
606
- case "end":
607
- popperElement.style.setProperty("left", "calc(anchor(right) - 100%)");
608
- break;
609
- case "center":
610
- popperElement.style.setProperty(
611
- "left",
612
- "calc(anchor(left) + (var(--monster-popper-control-width) - 100%) / 2)",
613
- );
614
- break;
615
- case "start":
616
- default:
617
- popperElement.style.setProperty("left", "anchor(left)");
618
- break;
619
- }
620
- }
621
-
622
- function applyVerticalAlignment(popperElement, align) {
623
- switch (align) {
624
- case "end":
625
- popperElement.style.setProperty("top", "calc(anchor(bottom) - 100%)");
626
- break;
627
- case "center":
628
- popperElement.style.setProperty(
629
- "top",
630
- "calc(anchor(top) + (var(--monster-popper-control-height) - 100%) / 2)",
631
- );
632
- break;
633
- case "start":
634
- default:
635
- popperElement.style.setProperty("top", "anchor(top)");
636
- break;
637
- }
638
- }
639
-
640
- function applyNativeTryFallbacks(popperElement, config) {
641
- const middleware = config.middlewareTokens || [];
642
- const placement = resolvePlacement(config.placement);
643
- let fallbacks = "";
644
-
645
- if (
646
- middleware.some(
647
- (entry) => isString(entry) && entry.startsWith("autoPlacement"),
648
- )
649
- ) {
650
- fallbacks =
651
- placement.side === "left" || placement.side === "right"
652
- ? "flip-inline, flip-block"
653
- : "flip-block, flip-inline";
654
- } else if (middleware.includes("flip")) {
655
- fallbacks =
656
- placement.side === "left" || placement.side === "right"
657
- ? "flip-inline"
658
- : "flip-block";
659
- }
660
-
661
- if (fallbacks.length === 0) {
662
- popperElement.style.removeProperty("position-try");
663
- popperElement.style.removeProperty("position-try-fallbacks");
664
- return;
665
- }
666
-
667
- popperElement.style.setProperty("position-try", fallbacks);
668
- popperElement.style.setProperty("position-try-fallbacks", fallbacks);
669
- }
670
-
671
- function syncNativeArrow(controlElement, popperElement) {
672
- const arrowElement = popperElement.querySelector("[data-monster-role=arrow]");
673
-
674
- if (!(arrowElement instanceof HTMLElement)) {
675
- return;
676
- }
677
-
678
- const controlRect = controlElement.getBoundingClientRect();
679
- const popperRect = popperElement.getBoundingClientRect();
680
- const arrowHalf = arrowElement.offsetWidth / 2;
681
-
682
- if (arrowHalf === 0) {
683
- return;
684
- }
685
-
686
- const side = resolveArrowSide(controlRect, popperRect);
687
- const staticSide = side;
688
- const defaultBorder =
689
- "var(--monster-border-width) var(--monster-border-style) var(--monster-bg-color-primary-4)";
690
- const borderStyle = {
691
- borderLeft: "transparent",
692
- borderRight: "transparent",
693
- borderBottom: "transparent",
694
- borderTop: "transparent",
695
- };
696
-
697
- switch (side) {
698
- case "top":
699
- borderStyle.borderTop = defaultBorder;
700
- borderStyle.borderLeft = defaultBorder;
701
- break;
702
- case "bottom":
703
- borderStyle.borderRight = defaultBorder;
704
- borderStyle.borderBottom = defaultBorder;
705
- break;
706
- case "left":
707
- borderStyle.borderLeft = defaultBorder;
708
- borderStyle.borderBottom = defaultBorder;
709
- break;
710
- case "right":
711
- borderStyle.borderTop = defaultBorder;
712
- borderStyle.borderRight = defaultBorder;
713
- break;
714
- }
715
-
716
- const arrowOffset = resolveArrowOffset(
717
- side,
718
- controlRect,
719
- popperRect,
720
- arrowHalf,
721
- );
722
- const arrowLen = arrowElement.offsetWidth + 4;
723
-
724
- Object.assign(
725
- arrowElement.style,
726
- {
727
- left: side === "top" || side === "bottom" ? `${arrowOffset}px` : "",
728
- top: side === "left" || side === "right" ? `${arrowOffset}px` : "",
729
- right: "",
730
- bottom: "",
731
- [staticSide]: `${-arrowLen / 2}px`,
732
- transform: "rotate(45deg)",
733
- },
734
- borderStyle,
735
- );
736
- }
737
-
738
- function resolveArrowSide(controlRect, popperRect) {
739
- const triggerCenterX = controlRect.left + controlRect.width / 2;
740
- const triggerCenterY = controlRect.top + controlRect.height / 2;
741
- const candidates = [];
742
-
743
- if (triggerCenterY <= popperRect.top) {
744
- candidates.push({
745
- side: "top",
746
- distance: popperRect.top - triggerCenterY,
747
- });
748
- }
749
-
750
- if (triggerCenterY >= popperRect.bottom) {
751
- candidates.push({
752
- side: "bottom",
753
- distance: triggerCenterY - popperRect.bottom,
754
- });
755
- }
756
-
757
- if (triggerCenterX <= popperRect.left) {
758
- candidates.push({
759
- side: "left",
760
- distance: popperRect.left - triggerCenterX,
761
- });
762
- }
763
-
764
- if (triggerCenterX >= popperRect.right) {
765
- candidates.push({
766
- side: "right",
767
- distance: triggerCenterX - popperRect.right,
768
- });
769
- }
770
-
771
- if (candidates.length === 0) {
772
- return "top";
773
- }
774
-
775
- candidates.sort((a, b) => a.distance - b.distance);
776
- return candidates[0].side;
777
- }
778
-
779
- function resolveArrowOffset(side, controlRect, popperRect, arrowHalf) {
780
- if (side === "top" || side === "bottom") {
781
- return clamp(
782
- controlRect.left + controlRect.width / 2 - popperRect.left - arrowHalf,
783
- 0,
784
- Math.max(0, popperRect.width - arrowHalf * 2),
785
- );
786
- }
787
-
788
- return clamp(
789
- controlRect.top + controlRect.height / 2 - popperRect.top - arrowHalf,
790
- 0,
791
- Math.max(0, popperRect.height - arrowHalf * 2),
792
- );
793
- }
794
-
795
242
  function applyFloatingArrowStyles(arrowElement, placement, arrowData) {
796
243
  if (!(arrowElement instanceof HTMLElement)) {
797
244
  return;
@@ -847,52 +294,6 @@ function applyFloatingArrowStyles(arrowElement, placement, arrowData) {
847
294
  );
848
295
  }
849
296
 
850
- function getAnchorName(controlElement) {
851
- if (!anchorNameMap.has(controlElement)) {
852
- anchorNameCounter += 1;
853
- anchorNameMap.set(
854
- controlElement,
855
- `--monster-popper-anchor-${anchorNameCounter}`,
856
- );
857
- }
858
-
859
- return anchorNameMap.get(controlElement);
860
- }
861
-
862
- function isNativePopoverElement(popperElement) {
863
- return popperElement?.dataset?.monsterPopperEngine === "native";
864
- }
865
-
866
- function matchesPopoverOpen(popperElement) {
867
- try {
868
- return popperElement.matches(":popover-open");
869
- } catch (e) {
870
- return false;
871
- }
872
- }
873
-
874
- function hasViewportOverflow(popperElement) {
875
- if (!(popperElement instanceof HTMLElement)) {
876
- return false;
877
- }
878
-
879
- const rect = popperElement.getBoundingClientRect();
880
- const tolerance = 1;
881
- const viewportWidth = window.innerWidth;
882
- const viewportHeight = window.innerHeight;
883
-
884
- return (
885
- rect.left < -tolerance ||
886
- rect.top < -tolerance ||
887
- rect.right > viewportWidth + tolerance ||
888
- rect.bottom > viewportHeight + tolerance
889
- );
890
- }
891
-
892
- function clamp(value, min, max) {
893
- return Math.min(Math.max(value, min), max);
894
- }
895
-
896
297
  function roundByDPR(value) {
897
298
  const dpr = window.devicePixelRatio || 1;
898
299
  return Math.round(value * dpr) / dpr;
@@ -12,11 +12,33 @@
12
12
  * SPDX-License-Identifier: AGPL-3.0
13
13
  */
14
14
 
15
+ import {
16
+ arrow,
17
+ autoPlacement,
18
+ autoUpdate,
19
+ computePosition,
20
+ flip,
21
+ hide,
22
+ offset,
23
+ shift,
24
+ size,
25
+ } from "@floating-ui/dom";
15
26
  import { extend } from "../../../data/extend.mjs";
16
- import { isArray } from "../../../types/is.mjs";
27
+ import {
28
+ isArray,
29
+ isElement,
30
+ isFunction,
31
+ isNumber,
32
+ isObject,
33
+ isString,
34
+ } from "../../../types/is.mjs";
17
35
  import { validateBoolean } from "../../../types/validate.mjs";
18
36
 
19
- export { setEventListenersModifiers, popperInstanceSymbol };
37
+ export {
38
+ createFloatingPopper,
39
+ setEventListenersModifiers,
40
+ popperInstanceSymbol,
41
+ };
20
42
 
21
43
  /**
22
44
  * @private
@@ -24,6 +46,65 @@ export { setEventListenersModifiers, popperInstanceSymbol };
24
46
  */
25
47
  const popperInstanceSymbol = Symbol("popperInstance");
26
48
 
49
+ /**
50
+ * @private
51
+ * @type {symbol}
52
+ */
53
+ const optionsSymbol = Symbol("options");
54
+
55
+ /**
56
+ * @private
57
+ * @type {symbol}
58
+ */
59
+ const referenceElementSymbol = Symbol("referenceElement");
60
+
61
+ /**
62
+ * @private
63
+ * @type {symbol}
64
+ */
65
+ const popperElementSymbol = Symbol("popperElement");
66
+
67
+ /**
68
+ * @private
69
+ * @type {symbol}
70
+ */
71
+ const autoUpdateCleanupSymbol = Symbol("autoUpdateCleanup");
72
+
73
+ /**
74
+ * @private
75
+ * @param {HTMLElement} referenceElement
76
+ * @param {HTMLElement} popperElement
77
+ * @param {object} options
78
+ * @return {{destroy: Function, setOptions: Function, update: Function}}
79
+ */
80
+ function createFloatingPopper(referenceElement, popperElement, options = {}) {
81
+ const instance = {
82
+ [referenceElementSymbol]: referenceElement,
83
+ [popperElementSymbol]: popperElement,
84
+ [optionsSymbol]: normalizeOptions(options),
85
+ [autoUpdateCleanupSymbol]: null,
86
+ destroy() {
87
+ stopAutoUpdate(instance);
88
+ },
89
+ setOptions(nextOptions) {
90
+ const normalizedOptions = isFunction(nextOptions)
91
+ ? nextOptions(instance[optionsSymbol])
92
+ : nextOptions;
93
+
94
+ instance[optionsSymbol] = normalizeOptions(normalizedOptions);
95
+ syncAutoUpdate(instance);
96
+ return instance.update();
97
+ },
98
+ update() {
99
+ return updatePosition(instance);
100
+ },
101
+ };
102
+
103
+ syncAutoUpdate(instance);
104
+
105
+ return instance;
106
+ }
107
+
27
108
  /**
28
109
  * @private
29
110
  * @this {CustomElement}
@@ -31,25 +112,252 @@ const popperInstanceSymbol = Symbol("popperInstance");
31
112
  */
32
113
  function setEventListenersModifiers(mode) {
33
114
  const options = extend({}, this.getOption("popper"));
34
- const modifiers = options?.["modifiers"];
115
+ const modifiers = options?.modifiers;
35
116
 
36
117
  if (!isArray(modifiers)) {
37
- options["modifiers"] = [];
118
+ options.modifiers = [];
38
119
  }
39
120
 
40
- if (
41
- options["modifiers"].filter((entry) => {
42
- if (entry?.["name"] === "eventListeners") {
43
- // performance https://popper.js.org/docs/v2/tutorial/#performance
44
- entry["enabled"] = validateBoolean(mode);
45
- }
46
- }).length === 0
47
- ) {
48
- options["modifiers"].push({
121
+ const existingModifier = options.modifiers.find((entry) => {
122
+ return entry?.name === "eventListeners";
123
+ });
124
+
125
+ if (existingModifier) {
126
+ existingModifier.enabled = validateBoolean(mode);
127
+ } else {
128
+ options.modifiers.push({
49
129
  name: "eventListeners",
50
130
  enabled: validateBoolean(mode),
51
131
  });
52
132
  }
53
133
 
54
- this[popperInstanceSymbol].setOptions(options);
134
+ return this[popperInstanceSymbol].setOptions(options);
135
+ }
136
+
137
+ /**
138
+ * @private
139
+ * @param {object} instance
140
+ * @return {Promise<void>}
141
+ */
142
+ function updatePosition(instance) {
143
+ const referenceElement = instance[referenceElementSymbol];
144
+ const popperElement = instance[popperElementSymbol];
145
+ const options = instance[optionsSymbol];
146
+
147
+ if (!isElement(referenceElement) || !isElement(popperElement)) {
148
+ return Promise.resolve();
149
+ }
150
+
151
+ return computePosition(referenceElement, popperElement, {
152
+ placement: options.placement,
153
+ strategy: options.strategy,
154
+ middleware: options.middleware,
155
+ }).then(({ x, y, strategy }) => {
156
+ Object.assign(popperElement.style, {
157
+ position: strategy,
158
+ left: `${roundByDPR(x)}px`,
159
+ top: `${roundByDPR(y)}px`,
160
+ transform: "",
161
+ });
162
+ });
163
+ }
164
+
165
+ /**
166
+ * @private
167
+ * @param {object} instance
168
+ * @return {void}
169
+ */
170
+ function syncAutoUpdate(instance) {
171
+ stopAutoUpdate(instance);
172
+
173
+ if (instance[optionsSymbol].eventListeners !== true) {
174
+ return;
175
+ }
176
+
177
+ instance[autoUpdateCleanupSymbol] = autoUpdate(
178
+ instance[referenceElementSymbol],
179
+ instance[popperElementSymbol],
180
+ () => {
181
+ void instance.update();
182
+ },
183
+ );
184
+ }
185
+
186
+ /**
187
+ * @private
188
+ * @param {object} instance
189
+ * @return {void}
190
+ */
191
+ function stopAutoUpdate(instance) {
192
+ const cleanup = instance[autoUpdateCleanupSymbol];
193
+ if (typeof cleanup === "function") {
194
+ cleanup();
195
+ }
196
+ instance[autoUpdateCleanupSymbol] = null;
197
+ }
198
+
199
+ /**
200
+ * @private
201
+ * @param {object} options
202
+ * @return {object}
203
+ */
204
+ function normalizeOptions(options) {
205
+ const config = extend(
206
+ {
207
+ placement: "bottom",
208
+ strategy: "absolute",
209
+ modifiers: [],
210
+ },
211
+ options || {},
212
+ );
213
+
214
+ config.eventListeners = normalizeEventListeners(config.modifiers);
215
+ config.middleware = normalizeMiddleware(config);
216
+
217
+ if (config.placement === "auto") {
218
+ config.placement = "bottom";
219
+ config.middleware.unshift(
220
+ autoPlacement({
221
+ crossAxis: true,
222
+ autoAlignment: true,
223
+ }),
224
+ );
225
+ }
226
+
227
+ return config;
228
+ }
229
+
230
+ /**
231
+ * @private
232
+ * @param {object[]} modifiers
233
+ * @return {boolean}
234
+ */
235
+ function normalizeEventListeners(modifiers) {
236
+ if (!isArray(modifiers)) {
237
+ return true;
238
+ }
239
+
240
+ let result = true;
241
+
242
+ for (const entry of modifiers) {
243
+ if (entry?.name === "eventListeners") {
244
+ result = validateBoolean(entry.enabled);
245
+ }
246
+ }
247
+
248
+ return result;
249
+ }
250
+
251
+ /**
252
+ * @private
253
+ * @param {object} config
254
+ * @return {Array}
255
+ */
256
+ function normalizeMiddleware(config) {
257
+ const result = [];
258
+ const middleware = [];
259
+
260
+ if (isArray(config?.middleware)) {
261
+ middleware.push(...config.middleware);
262
+ }
263
+
264
+ if (isArray(config?.modifiers)) {
265
+ middleware.push(...config.modifiers);
266
+ }
267
+
268
+ for (const entry of middleware) {
269
+ if (isFunction(entry)) {
270
+ result.push(entry);
271
+ continue;
272
+ }
273
+
274
+ if (isObject(entry) && isFunction(entry?.fn)) {
275
+ result.push(entry);
276
+ continue;
277
+ }
278
+
279
+ if (isObject(entry) && !isString(entry?.name)) {
280
+ result.push(entry);
281
+ continue;
282
+ }
283
+
284
+ if (!isObject(entry) || !isString(entry?.name)) {
285
+ continue;
286
+ }
287
+
288
+ const normalizedEntry = normalizeModifier(entry);
289
+ if (normalizedEntry) {
290
+ result.push(normalizedEntry);
291
+ }
292
+ }
293
+
294
+ return result;
295
+ }
296
+
297
+ /**
298
+ * @private
299
+ * @param {{name: string, options?: object, enabled?: boolean}} modifier
300
+ * @return {object|null}
301
+ */
302
+ function normalizeModifier(modifier) {
303
+ if (modifier.enabled === false) {
304
+ return null;
305
+ }
306
+
307
+ switch (modifier.name) {
308
+ case "arrow":
309
+ if (!isElement(modifier?.options?.element)) {
310
+ return null;
311
+ }
312
+ return arrow(modifier.options);
313
+ case "autoPlacement":
314
+ return autoPlacement(modifier.options);
315
+ case "eventListeners":
316
+ return null;
317
+ case "flip":
318
+ return flip(modifier.options);
319
+ case "hide":
320
+ return hide(modifier.options);
321
+ case "offset":
322
+ return offset(normalizeOffset(modifier.options?.offset));
323
+ case "preventOverflow":
324
+ return shift(modifier.options);
325
+ case "shift":
326
+ return shift(modifier.options);
327
+ case "size":
328
+ return size(modifier.options);
329
+ default:
330
+ return null;
331
+ }
332
+ }
333
+
334
+ /**
335
+ * @private
336
+ * @param {number|Array<number>} rawOffset
337
+ * @return {number|object}
338
+ */
339
+ function normalizeOffset(rawOffset) {
340
+ if (isArray(rawOffset)) {
341
+ const [skidding = 0, distance = 0] = rawOffset;
342
+ return {
343
+ mainAxis: distance,
344
+ crossAxis: skidding,
345
+ };
346
+ }
347
+
348
+ if (isNumber(rawOffset)) {
349
+ return rawOffset;
350
+ }
351
+
352
+ return 0;
353
+ }
354
+
355
+ /**
356
+ * @private
357
+ * @param {number} value
358
+ * @return {number}
359
+ */
360
+ function roundByDPR(value) {
361
+ const dpr = window.devicePixelRatio || 1;
362
+ return Math.round(value * dpr) / dpr;
55
363
  }
@@ -169,8 +169,8 @@ class Popper extends CustomElement {
169
169
  content: "<slot></slot>",
170
170
  popper: {
171
171
  placement: "top",
172
- engine: "native",
173
- middleware: ["autoPlacement", "shift", "offset:15", "arrow"],
172
+ engine: "floating",
173
+ middleware: ["flip", "shift", "offset:15", "arrow"],
174
174
  contentOverflow: "both",
175
175
  },
176
176
  features: {
@@ -13,7 +13,6 @@
13
13
  */
14
14
 
15
15
  import { instanceSymbol } from "../../constants.mjs";
16
- import { createPopper } from "@popperjs/core";
17
16
  import { extend } from "../../data/extend.mjs";
18
17
  import { Pathfinder } from "../../data/pathfinder.mjs";
19
18
  import {
@@ -56,6 +55,7 @@ import { TabsStyleSheet } from "./stylesheet/tabs.mjs";
56
55
  import { loadAndAssignContent } from "../form/util/fetch.mjs";
57
56
  import { ThemeStyleSheet } from "../stylesheet/theme.mjs";
58
57
  import {
58
+ createFloatingPopper,
59
59
  popperInstanceSymbol,
60
60
  setEventListenersModifiers,
61
61
  } from "../form/util/popper.mjs";
@@ -189,9 +189,9 @@ class Tabs extends CustomElement {
189
189
  * @property {String} fetch.mode=same-origin
190
190
  * @property {String} fetch.credentials=same-origin
191
191
  * @property {Object} fetch.headers={"accept":"text/html"}}
192
- * @property {Object} popper [PopperJS Options](https://popper.js.org/docs/v2/)
193
- * @property {string} popper.placement=bottom PopperJS placement
194
- * @property {Object[]} modifiers={name:offset} PopperJS placement
192
+ * @property {Object} popper Popper configuration
193
+ * @property {string} popper.placement=bottom Popper placement
194
+ * @property {Object[]} popper.modifiers={name:offset} Placement middleware configuration
195
195
  */
196
196
  get defaults() {
197
197
  return Object.assign({}, super.defaults, {
@@ -473,6 +473,8 @@ class Tabs extends CustomElement {
473
473
  for (const [, type] of Object.entries(["click", "touch"])) {
474
474
  document.removeEventListener(type, this[closeEventHandler]);
475
475
  }
476
+
477
+ this[popperInstanceSymbol]?.destroy();
476
478
  }
477
479
  }
478
480
 
@@ -668,14 +670,13 @@ function attachTabChangeObserver() {
668
670
  /**
669
671
  * @private
670
672
  * @return {Select}
671
- * @external "external:createPopper"
672
673
  */
673
674
  function initPopper() {
674
675
  const self = this;
675
676
 
676
677
  const options = extend({}, self.getOption("popper"));
677
678
 
678
- self[popperInstanceSymbol] = createPopper(
679
+ self[popperInstanceSymbol] = createFloatingPopper(
679
680
  self[switchElementSymbol],
680
681
  self[popperElementSymbol],
681
682
  options,
@@ -13,7 +13,6 @@
13
13
  */
14
14
 
15
15
  import { instanceSymbol } from "../../constants.mjs";
16
- import { createPopper } from "@popperjs/core";
17
16
  import { extend } from "../../data/extend.mjs";
18
17
  import { Pathfinder } from "../../data/pathfinder.mjs";
19
18
  import {
@@ -57,6 +56,7 @@ import { VerticalTabsStyleSheet } from "./stylesheet/vertical-tabs.mjs";
57
56
  import { loadAndAssignContent } from "../form/util/fetch.mjs";
58
57
  import { ThemeStyleSheet } from "../stylesheet/theme.mjs";
59
58
  import {
59
+ createFloatingPopper,
60
60
  popperInstanceSymbol,
61
61
  setEventListenersModifiers,
62
62
  } from "../form/util/popper.mjs";
@@ -190,9 +190,9 @@ class VerticalTabs extends CustomElement {
190
190
  * @property {String} fetch.mode=same-origin
191
191
  * @property {String} fetch.credentials=same-origin
192
192
  * @property {Object} fetch.headers={"accept":"text/html"}}
193
- * @property {Object} popper [PopperJS Options](https://popper.js.org/docs/v2/)
194
- * @property {string} popper.placement=bottom PopperJS placement
195
- * @property {Object[]} modifiers={name:offset} PopperJS placement
193
+ * @property {Object} popper Popper configuration
194
+ * @property {string} popper.placement=right-start Popper placement
195
+ * @property {Object[]} popper.modifiers={name:offset} Placement middleware configuration
196
196
  */
197
197
  get defaults() {
198
198
  return Object.assign({}, super.defaults, {
@@ -474,6 +474,8 @@ class VerticalTabs extends CustomElement {
474
474
  for (const [, type] of Object.entries(["click", "touch"])) {
475
475
  document.removeEventListener(type, this[closeEventHandler]);
476
476
  }
477
+
478
+ this[popperInstanceSymbol]?.destroy();
477
479
  }
478
480
  }
479
481
 
@@ -669,14 +671,13 @@ function attachTabChangeObserver() {
669
671
  /**
670
672
  * @private
671
673
  * @return {Select}
672
- * @external "external:createPopper"
673
674
  */
674
675
  function initPopper() {
675
676
  const self = this;
676
677
 
677
678
  const options = extend({}, self.getOption("popper"));
678
679
 
679
- self[popperInstanceSymbol] = createPopper(
680
+ self[popperInstanceSymbol] = createFloatingPopper(
680
681
  self[switchElementSymbol],
681
682
  self[popperElementSymbol],
682
683
  options,
@@ -8,6 +8,8 @@ chai.use(chaiDom);
8
8
  let Login;
9
9
 
10
10
  describe("Login", function () {
11
+ this.timeout(10000);
12
+
11
13
  before(function (done) {
12
14
  initJSDOM().then(() => {
13
15
  import("element-internals-polyfill")
@@ -32,21 +34,14 @@ describe("Login", function () {
32
34
  expect(document.createElement("monster-login")).is.instanceof(Login);
33
35
  });
34
36
 
35
- it("should respect the native hidden attribute on the host", function (done) {
37
+ it("should respect the native hidden attribute on the host", function () {
36
38
  const login = document.createElement("monster-login");
37
39
  login.setAttribute("hidden", "");
38
40
  document.getElementById("mocks").appendChild(login);
39
41
 
40
- setTimeout(() => {
41
- try {
42
- expect(login.hidden).to.be.true;
43
- expect(login.visible).to.be.false;
44
- expect(login.isVisible).to.be.false;
45
- done();
46
- } catch (error) {
47
- done(error);
48
- }
49
- }, 100);
42
+ expect(login.hidden).to.be.true;
43
+ expect(login.visible).to.be.false;
44
+ expect(login.isVisible).to.be.false;
50
45
  });
51
46
 
52
47
  it("should hide and show the host with the public visibility API", function (done) {
@@ -47,6 +47,7 @@ function initJSDOM(options) {
47
47
  'CSSStyleSheet',
48
48
  'customElements',
49
49
  'CustomEvent',
50
+ 'cancelAnimationFrame',
50
51
  'document',
51
52
  'DOMException',
52
53
  'DOMImplementation',