@schukai/monster 4.137.3 → 4.137.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 CHANGED
@@ -1 +1 @@
1
- {"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.137.3"}
1
+ {"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.137.4"}
@@ -25,6 +25,7 @@ import { getLocaleOfDocument } from "../../dom/locale.mjs";
25
25
  import { hasObjectLink } from "../../dom/attributes.mjs";
26
26
  import { customElementUpdaterLinkSymbol } from "../../dom/constants.mjs";
27
27
  import { getGlobalObject } from "../../types/global.mjs";
28
+ import { UniqueQueue } from "../../types/uniquequeue.mjs";
28
29
  import {
29
30
  createFloatingPopper,
30
31
  popperInstanceSymbol,
@@ -83,6 +84,23 @@ const dotsUpdateInProgressSymbol = Symbol("dotsUpdateInProgress");
83
84
  * @type {symbol}
84
85
  */
85
86
  const dotsFrameRequestSymbol = Symbol("dotsFrameRequest");
87
+ /**
88
+ * @private
89
+ * @type {symbol}
90
+ */
91
+ const dotsLayoutCacheSymbol = Symbol("dotsLayoutCache");
92
+
93
+ /**
94
+ * @private
95
+ * @type {UniqueQueue}
96
+ */
97
+ const scheduledDotsUpdates = new UniqueQueue();
98
+
99
+ /**
100
+ * @private
101
+ * @type {number|null}
102
+ */
103
+ let scheduledDotsFrameRequest = null;
86
104
 
87
105
  /**
88
106
  * @private
@@ -222,14 +240,8 @@ class ColumnBar extends CustomElement {
222
240
  this[dotsMutationObserverSymbol] = null;
223
241
  }
224
242
 
225
- if (typeof this[dotsFrameRequestSymbol] === "number") {
226
- const cancelFrame =
227
- typeof globalThis.cancelAnimationFrame === "function"
228
- ? globalThis.cancelAnimationFrame.bind(globalThis)
229
- : globalThis.clearTimeout.bind(globalThis);
230
- cancelFrame(this[dotsFrameRequestSymbol]);
231
- this[dotsFrameRequestSymbol] = null;
232
- }
243
+ removeScheduledDotsUpdate(this);
244
+ this[dotsFrameRequestSymbol] = null;
233
245
 
234
246
  if (this[closeEventHandlerSymbol]) {
235
247
  getGlobalObject("document").removeEventListener(
@@ -455,7 +467,11 @@ function initDotsObservers() {
455
467
  const parentElement = this.parentElement;
456
468
 
457
469
  if (controlElement && !this[resizeObserverSymbol]) {
458
- this[resizeObserverSymbol] = new ResizeObserver(() => {
470
+ this[resizeObserverSymbol] = new ResizeObserver((entries) => {
471
+ updateDotsLayoutResizeCache.call(this, entries, {
472
+ controlElement,
473
+ parentElement,
474
+ });
459
475
  scheduleDotsVisibilityUpdate.call(this);
460
476
  });
461
477
  this[resizeObserverSymbol].observe(controlElement);
@@ -465,10 +481,17 @@ function initDotsObservers() {
465
481
  }
466
482
 
467
483
  if (this[dotsContainerElementSymbol] && !this[dotsMutationObserverSymbol]) {
468
- this[dotsMutationObserverSymbol] = new MutationObserver(() => {
469
- if (this[dotsUpdateInProgressSymbol]) {
484
+ this[dotsMutationObserverSymbol] = new MutationObserver((mutations) => {
485
+ if (
486
+ this[dotsUpdateInProgressSymbol] ||
487
+ mutations.every(isOverflowIndicatorMutation)
488
+ ) {
470
489
  return;
471
490
  }
491
+ invalidateDotsLayoutCache.call(this, {
492
+ dots: true,
493
+ visibility: true,
494
+ });
472
495
  scheduleDotsVisibilityUpdate.call(this);
473
496
  });
474
497
  this[dotsMutationObserverSymbol].observe(this[dotsContainerElementSymbol], {
@@ -481,29 +504,43 @@ function initDotsObservers() {
481
504
  * @private
482
505
  */
483
506
  function scheduleDotsVisibilityUpdate() {
484
- if (
485
- this[dotsFrameRequestSymbol] !== null &&
486
- this[dotsFrameRequestSymbol] !== undefined
487
- ) {
488
- return;
489
- }
507
+ scheduledDotsUpdates.add(this);
508
+ this[dotsFrameRequestSymbol] = true;
490
509
 
491
- this[dotsFrameRequestSymbol] = requestAnimationFrame(() => {
492
- this[dotsFrameRequestSymbol] = null;
493
- updateDotsVisibility.call(this);
510
+ if (scheduledDotsFrameRequest !== null) return;
511
+
512
+ scheduledDotsFrameRequest = requestAnimationFrame(() => {
513
+ const pending = [];
514
+ while (!scheduledDotsUpdates.isEmpty()) {
515
+ pending.push(scheduledDotsUpdates.poll());
516
+ }
517
+ scheduledDotsFrameRequest = null;
518
+
519
+ const layouts = [];
520
+ for (const element of pending) {
521
+ element[dotsFrameRequestSymbol] = null;
522
+ const layout = measureDotsLayout.call(element);
523
+ if (layout) {
524
+ layouts.push(layout);
525
+ }
526
+ }
527
+
528
+ for (const layout of layouts) {
529
+ applyDotsLayout(layout);
530
+ }
494
531
  });
495
532
  }
496
533
 
497
534
  /**
498
535
  * @private
499
536
  */
500
- function updateDotsVisibility() {
537
+ function measureDotsLayout() {
501
538
  if (this[dotsUpdateInProgressSymbol]) {
502
- return;
539
+ return null;
503
540
  }
504
541
 
505
542
  if (!this.shadowRoot || !this[dotsContainerElementSymbol]) {
506
- return;
543
+ return null;
507
544
  }
508
545
 
509
546
  const controlElement = this.shadowRoot.querySelector(
@@ -513,81 +550,318 @@ function updateDotsVisibility() {
513
550
  const parentElement = this.parentElement;
514
551
 
515
552
  if (!controlElement || !settingsButton) {
516
- return;
553
+ return null;
517
554
  }
518
555
 
519
556
  const dotsContainer = this[dotsContainerElementSymbol];
520
- dotsContainer.classList.remove("dots-hidden");
557
+ const cache = getDotsLayoutCache.call(this);
558
+ const dots = Array.from(
559
+ dotsContainer.querySelectorAll("li:not(.dots-overflow-indicator)"),
560
+ );
521
561
 
522
- this[dotsUpdateInProgressSymbol] = true;
523
- try {
524
- const dots = Array.from(dotsContainer.querySelectorAll("li"));
525
- if (dots.length === 0) {
526
- return;
527
- }
562
+ if (dots.length === 0) {
563
+ return {
564
+ hostElement: this,
565
+ dots,
566
+ dotsContainer,
567
+ hiddenCount: 0,
568
+ hideContainer: true,
569
+ visibleDots: 0,
570
+ };
571
+ }
528
572
 
529
- for (const dot of dots) {
530
- dot.classList.remove("dots-overflow-hidden");
531
- }
573
+ if (cache.dotsCount !== dots.length) {
574
+ cache.dotsCount = dots.length;
575
+ cache.dotSlotWidth = null;
576
+ cache.lastVisibleDots = null;
577
+ cache.lastHiddenCount = null;
578
+ }
532
579
 
533
- const indicator = dotsContainer.querySelector(".dots-overflow-indicator");
534
- if (indicator) {
535
- indicator.remove();
536
- }
580
+ if (cache.controlWidth === null || cache.controlWidth <= 0) {
581
+ cache.controlWidth = controlElement.getBoundingClientRect().width;
582
+ }
583
+ if (cache.settingsWidth === null || cache.settingsWidth <= 0) {
584
+ cache.settingsWidth = settingsButton.getBoundingClientRect().width;
585
+ }
537
586
 
538
- const controlWidth = controlElement.getBoundingClientRect().width;
539
- const settingsWidth = settingsButton.getBoundingClientRect().width;
540
- const availableWidth = Math.max(
541
- 0,
542
- getAvailableWidth(parentElement, settingsWidth, this) ??
543
- controlWidth - settingsWidth - 12,
544
- );
587
+ const availableWidth = Math.max(
588
+ 0,
589
+ getAvailableWidth(parentElement, cache.settingsWidth, this, cache) ??
590
+ cache.controlWidth - cache.settingsWidth - 12,
591
+ );
545
592
 
546
- const dotSlotWidth = getDotSlotWidth(dots[0]);
547
- const maxDots =
548
- dotSlotWidth > 0
549
- ? Math.floor(availableWidth / dotSlotWidth)
550
- : dots.length;
551
- const configuredMaxVisible = parseInt(
552
- this.getOption("dots.maxVisible"),
553
- 10,
593
+ if (cache.dotSlotWidth === null || cache.dotSlotWidth <= 0) {
594
+ cache.dotSlotWidth = getDotSlotWidth(dots[0]);
595
+ }
596
+
597
+ const maxDots =
598
+ cache.dotSlotWidth > 0
599
+ ? Math.floor(availableWidth / cache.dotSlotWidth)
600
+ : dots.length;
601
+ const configuredMaxVisible = parseInt(this.getOption("dots.maxVisible"), 10);
602
+ const enforceMaxVisible =
603
+ Number.isFinite(configuredMaxVisible) && configuredMaxVisible > 0;
604
+
605
+ if (maxDots <= 1) {
606
+ return {
607
+ hostElement: this,
608
+ dots,
609
+ dotsContainer,
610
+ hiddenCount: 0,
611
+ hideContainer: true,
612
+ visibleDots: 0,
613
+ };
614
+ }
615
+
616
+ const configLimit = enforceMaxVisible
617
+ ? configuredMaxVisible
618
+ : Number.POSITIVE_INFINITY;
619
+ const baseLimit = Math.min(maxDots, configLimit, dots.length);
620
+ const needsIndicator = baseLimit < dots.length;
621
+
622
+ let visibleDots = baseLimit;
623
+ if (needsIndicator) {
624
+ visibleDots = Math.min(
625
+ Math.max(1, maxDots - 1),
626
+ configLimit,
627
+ dots.length,
554
628
  );
555
- const enforceMaxVisible =
556
- Number.isFinite(configuredMaxVisible) && configuredMaxVisible > 0;
629
+ }
557
630
 
558
- if (maxDots <= 1) {
559
- dotsContainer.classList.add("dots-hidden");
560
- return;
561
- }
631
+ return {
632
+ hostElement: this,
633
+ dots,
634
+ dotsContainer,
635
+ hiddenCount: dots.length - visibleDots,
636
+ hideContainer: false,
637
+ visibleDots,
638
+ };
639
+ }
640
+
641
+ /**
642
+ * @private
643
+ * @param {object} layout
644
+ * @return {void}
645
+ */
646
+ function applyDotsLayout(layout) {
647
+ const cache = getDotsLayoutCache.call(layout.hostElement);
562
648
 
563
- const configLimit = enforceMaxVisible
564
- ? configuredMaxVisible
565
- : Number.POSITIVE_INFINITY;
566
- const baseLimit = Math.min(maxDots, configLimit, dots.length);
567
- const needsIndicator = baseLimit < dots.length;
568
-
569
- let visibleDots = baseLimit;
570
- if (needsIndicator) {
571
- visibleDots = Math.min(
572
- Math.max(1, maxDots - 1),
573
- configLimit,
574
- dots.length,
649
+ layout.hostElement[dotsUpdateInProgressSymbol] = true;
650
+ try {
651
+ if (cache.lastContainerHidden !== layout.hideContainer) {
652
+ layout.dotsContainer.classList.toggle(
653
+ "dots-hidden",
654
+ layout.hideContainer,
575
655
  );
656
+ cache.lastContainerHidden = layout.hideContainer;
576
657
  }
577
- for (let i = visibleDots; i < dots.length; i++) {
578
- dots[i].classList.add("dots-overflow-hidden");
658
+
659
+ if (
660
+ cache.lastVisibleDots !== layout.visibleDots ||
661
+ cache.lastDotsCount !== layout.dots.length
662
+ ) {
663
+ for (let i = 0; i < layout.dots.length; i++) {
664
+ const shouldHide = i >= layout.visibleDots;
665
+ if (
666
+ layout.dots[i].classList.contains("dots-overflow-hidden") !==
667
+ shouldHide
668
+ ) {
669
+ layout.dots[i].classList.toggle("dots-overflow-hidden", shouldHide);
670
+ }
671
+ }
672
+ cache.lastVisibleDots = layout.visibleDots;
673
+ cache.lastDotsCount = layout.dots.length;
579
674
  }
580
675
 
581
- const hiddenCount = dots.length - visibleDots;
582
- if (hiddenCount > 0) {
583
- const overflowIndicator = document.createElement("li");
584
- overflowIndicator.className = "dots-overflow-indicator";
585
- overflowIndicator.textContent = `+${hiddenCount}`;
586
- dotsContainer.appendChild(overflowIndicator);
676
+ if (cache.lastHiddenCount !== layout.hiddenCount) {
677
+ updateOverflowIndicator(layout.dotsContainer, layout.hiddenCount);
678
+ cache.lastHiddenCount = layout.hiddenCount;
587
679
  }
588
680
  } finally {
589
- this[dotsUpdateInProgressSymbol] = false;
681
+ layout.hostElement[dotsUpdateInProgressSymbol] = false;
682
+ }
683
+ }
684
+
685
+ /**
686
+ * @private
687
+ * @param {MutationRecord} mutation
688
+ * @return {boolean}
689
+ */
690
+ function isOverflowIndicatorMutation(mutation) {
691
+ const changedNodes = Array.from(mutation.addedNodes).concat(
692
+ Array.from(mutation.removedNodes),
693
+ );
694
+
695
+ return (
696
+ changedNodes.length > 0 &&
697
+ changedNodes.every((node) => {
698
+ return (
699
+ node instanceof HTMLElement &&
700
+ node.classList.contains("dots-overflow-indicator")
701
+ );
702
+ })
703
+ );
704
+ }
705
+
706
+ /**
707
+ * @private
708
+ * @param {HTMLElement} dotsContainer
709
+ * @param {number} hiddenCount
710
+ * @return {void}
711
+ */
712
+ function updateOverflowIndicator(dotsContainer, hiddenCount) {
713
+ let overflowIndicator = dotsContainer.querySelector(
714
+ ".dots-overflow-indicator",
715
+ );
716
+
717
+ if (hiddenCount <= 0) {
718
+ overflowIndicator?.remove();
719
+ return;
720
+ }
721
+
722
+ const label = `+${hiddenCount}`;
723
+ if (!overflowIndicator) {
724
+ overflowIndicator = document.createElement("li");
725
+ overflowIndicator.className = "dots-overflow-indicator";
726
+ dotsContainer.appendChild(overflowIndicator);
727
+ }
728
+
729
+ if (overflowIndicator.textContent !== label) {
730
+ overflowIndicator.textContent = label;
731
+ }
732
+ }
733
+
734
+ /**
735
+ * @private
736
+ * @param {ColumnBar} element
737
+ * @return {void}
738
+ */
739
+ function removeScheduledDotsUpdate(element) {
740
+ const pending = [];
741
+ while (!scheduledDotsUpdates.isEmpty()) {
742
+ const item = scheduledDotsUpdates.poll();
743
+ if (item !== element) {
744
+ pending.push(item);
745
+ }
746
+ }
747
+ for (const item of pending) {
748
+ scheduledDotsUpdates.add(item);
749
+ }
750
+ }
751
+
752
+ /**
753
+ * @private
754
+ * @return {object}
755
+ */
756
+ function getDotsLayoutCache() {
757
+ if (!this[dotsLayoutCacheSymbol]) {
758
+ this[dotsLayoutCacheSymbol] = {
759
+ controlWidth: null,
760
+ dotSlotWidth: null,
761
+ dotsCount: null,
762
+ lastContainerHidden: null,
763
+ lastDotsCount: null,
764
+ lastHiddenCount: null,
765
+ lastVisibleDots: null,
766
+ parentGap: null,
767
+ parentWidth: null,
768
+ settingsWidth: null,
769
+ siblingsSignature: null,
770
+ siblingsWidth: null,
771
+ };
772
+ }
773
+
774
+ return this[dotsLayoutCacheSymbol];
775
+ }
776
+
777
+ /**
778
+ * @private
779
+ * @param {object} options
780
+ * @return {void}
781
+ */
782
+ function invalidateDotsLayoutCache(options = {}) {
783
+ const cache = getDotsLayoutCache.call(this);
784
+
785
+ if (options.dots === true) {
786
+ cache.dotSlotWidth = null;
787
+ cache.dotsCount = null;
788
+ }
789
+
790
+ if (options.visibility === true) {
791
+ cache.lastContainerHidden = null;
792
+ cache.lastDotsCount = null;
793
+ cache.lastHiddenCount = null;
794
+ cache.lastVisibleDots = null;
795
+ }
796
+
797
+ if (options.control === true) {
798
+ cache.controlWidth = null;
799
+ }
800
+
801
+ if (options.parent === true) {
802
+ cache.parentWidth = null;
803
+ }
804
+
805
+ if (options.settings === true) {
806
+ cache.settingsWidth = null;
807
+ }
808
+
809
+ if (options.siblings === true) {
810
+ cache.parentGap = null;
811
+ cache.siblingsSignature = null;
812
+ cache.siblingsWidth = null;
813
+ }
814
+ }
815
+
816
+ /**
817
+ * @private
818
+ * @param {ResizeObserverEntry[]} entries
819
+ * @param {object} elements
820
+ * @return {void}
821
+ */
822
+ function updateDotsLayoutResizeCache(entries, { controlElement, parentElement }) {
823
+ const cache = getDotsLayoutCache.call(this);
824
+
825
+ if (!Array.isArray(entries) || entries.length === 0) {
826
+ invalidateDotsLayoutCache.call(this, {
827
+ control: true,
828
+ parent: true,
829
+ siblings: true,
830
+ });
831
+ return;
590
832
  }
833
+
834
+ for (const entry of entries) {
835
+ const width = getResizeEntryWidth(entry);
836
+ if (width === null) continue;
837
+
838
+ if (entry.target === controlElement) {
839
+ cache.controlWidth = width;
840
+ }
841
+ if (entry.target === parentElement) {
842
+ cache.parentWidth = width;
843
+ cache.siblingsSignature = null;
844
+ cache.siblingsWidth = null;
845
+ }
846
+ }
847
+ }
848
+
849
+ /**
850
+ * @private
851
+ * @param {ResizeObserverEntry} entry
852
+ * @return {number|null}
853
+ */
854
+ function getResizeEntryWidth(entry) {
855
+ const borderSize = Array.isArray(entry.borderBoxSize)
856
+ ? entry.borderBoxSize[0]
857
+ : entry.borderBoxSize;
858
+ const contentSize = Array.isArray(entry.contentBoxSize)
859
+ ? entry.contentBoxSize[0]
860
+ : entry.contentBoxSize;
861
+ const width =
862
+ borderSize?.inlineSize ?? contentSize?.inlineSize ?? entry.contentRect?.width;
863
+
864
+ return Number.isFinite(width) ? width : null;
591
865
  }
592
866
 
593
867
  /**
@@ -607,32 +881,50 @@ function getDotSlotWidth(dot) {
607
881
  * @private
608
882
  * @param {HTMLElement|null} parent
609
883
  * @param {number} settingsWidth
884
+ * @param {ColumnBar} hostElement
885
+ * @param {object} cache
610
886
  * @return {number|null}
611
887
  */
612
- function getAvailableWidth(parent, settingsWidth, hostElement) {
888
+ function getAvailableWidth(parent, settingsWidth, hostElement, cache) {
613
889
  if (!parent) return null;
614
890
 
615
- const parentWidth = parent.getBoundingClientRect().width;
891
+ const parentWidth =
892
+ cache.parentWidth ?? parent.getBoundingClientRect().width ?? null;
616
893
  if (!parentWidth) return null;
617
-
618
- const styles = getComputedStyle(parent);
619
- const gapValue = styles.columnGap || styles.gap || "0";
620
- const gap = parseFloat(gapValue) || 0;
894
+ cache.parentWidth = parentWidth;
621
895
 
622
896
  const siblings = Array.from(parent.children).filter(
623
897
  (el) => el !== hostElement,
624
898
  );
625
- let siblingsWidth = 0;
626
- for (const sibling of siblings) {
627
- const siblingStyles = getComputedStyle(sibling);
628
- if (siblingStyles.display === "none") {
629
- continue;
899
+ const siblingsSignature = siblings
900
+ .map((sibling) => sibling.getAttribute("data-monster-role") || sibling.tagName)
901
+ .join("|");
902
+
903
+ if (
904
+ cache.siblingsSignature !== siblingsSignature ||
905
+ cache.siblingsWidth === null ||
906
+ cache.parentGap === null
907
+ ) {
908
+ const styles = getComputedStyle(parent);
909
+ const gapValue = styles.columnGap || styles.gap || "0";
910
+ cache.parentGap = parseFloat(gapValue) || 0;
911
+ cache.siblingsSignature = siblingsSignature;
912
+ cache.siblingsWidth = 0;
913
+
914
+ for (const sibling of siblings) {
915
+ const siblingStyles = getComputedStyle(sibling);
916
+ if (siblingStyles.display === "none") {
917
+ continue;
918
+ }
919
+ cache.siblingsWidth += sibling.getBoundingClientRect().width;
630
920
  }
631
- siblingsWidth += sibling.getBoundingClientRect().width;
632
921
  }
633
922
 
634
923
  const gaps = siblings.length > 0 ? siblings.length : 0;
635
- return Math.max(0, parentWidth - siblingsWidth - gap * gaps - settingsWidth);
924
+ return Math.max(
925
+ 0,
926
+ parentWidth - cache.siblingsWidth - cache.parentGap * gaps - settingsWidth,
927
+ );
636
928
  }
637
929
 
638
930
  /**
@@ -557,4 +557,122 @@ describe("Datatable drag scroll", function () {
557
557
  globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
558
558
  }
559
559
  });
560
+
561
+ it("coalesces column bar dot updates and ignores overflow indicator mutations", async function () {
562
+ const OriginalResizeObserver = window.ResizeObserver;
563
+ const originalGlobalResizeObserver = globalThis.ResizeObserver;
564
+ const originalRequestAnimationFrame = window.requestAnimationFrame;
565
+ const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
566
+
567
+ class TrackingResizeObserver extends ResizeObserverMock {
568
+ static instances = [];
569
+
570
+ constructor(callback) {
571
+ super(callback);
572
+ TrackingResizeObserver.instances.push(this);
573
+ }
574
+ }
575
+
576
+ try {
577
+ window.ResizeObserver = TrackingResizeObserver;
578
+ globalThis.ResizeObserver = TrackingResizeObserver;
579
+
580
+ const mocks = document.getElementById("mocks");
581
+ const wrapper = document.createElement("div");
582
+ let parentWidth = 80;
583
+
584
+ Object.defineProperty(wrapper, "getBoundingClientRect", {
585
+ configurable: true,
586
+ value: () => ({ width: parentWidth }),
587
+ });
588
+
589
+ mocks.appendChild(wrapper);
590
+
591
+ const createColumnBar = async () => {
592
+ const columnBar = document.createElement("monster-column-bar");
593
+ columnBar.setOption("columns", [
594
+ { index: 0, name: "One", visible: true },
595
+ { index: 1, name: "Two", visible: true },
596
+ { index: 2, name: "Three", visible: true },
597
+ { index: 3, name: "Four", visible: true },
598
+ { index: 4, name: "Five", visible: true },
599
+ { index: 5, name: "Six", visible: true },
600
+ ]);
601
+ wrapper.appendChild(columnBar);
602
+
603
+ await new Promise((resolve) => setTimeout(resolve, 30));
604
+
605
+ const control = columnBar.shadowRoot.querySelector(
606
+ "[data-monster-role=control]",
607
+ );
608
+ const settingsButton = columnBar.shadowRoot.querySelector(
609
+ "[data-monster-role=settings-button]",
610
+ );
611
+ const dotsContainer = columnBar.shadowRoot.querySelector(
612
+ "[data-monster-role=dots]",
613
+ );
614
+ const dots = Array.from(dotsContainer.querySelectorAll("li"));
615
+
616
+ Object.defineProperty(control, "getBoundingClientRect", {
617
+ configurable: true,
618
+ value: () => ({ width: parentWidth }),
619
+ });
620
+ Object.defineProperty(settingsButton, "getBoundingClientRect", {
621
+ configurable: true,
622
+ value: () => ({ width: 20 }),
623
+ });
624
+ dots.forEach((dot) => {
625
+ Object.defineProperty(dot, "getBoundingClientRect", {
626
+ configurable: true,
627
+ value: () => ({ width: 20 }),
628
+ });
629
+ });
630
+
631
+ return { columnBar, control, dotsContainer };
632
+ };
633
+
634
+ const first = await createColumnBar();
635
+ const second = await createColumnBar();
636
+
637
+ const firstResizeObserver = TrackingResizeObserver.instances.find(
638
+ (observer) => observer.elements.includes(first.control),
639
+ );
640
+ const secondResizeObserver = TrackingResizeObserver.instances.find(
641
+ (observer) => observer.elements.includes(second.control),
642
+ );
643
+
644
+ expect(firstResizeObserver).to.exist;
645
+ expect(secondResizeObserver).to.exist;
646
+
647
+ const scheduledCallbacks = [];
648
+ window.requestAnimationFrame = (callback) => {
649
+ scheduledCallbacks.push(callback);
650
+ return scheduledCallbacks.length;
651
+ };
652
+ globalThis.requestAnimationFrame = window.requestAnimationFrame;
653
+
654
+ firstResizeObserver.triggerResize([]);
655
+ secondResizeObserver.triggerResize([]);
656
+
657
+ expect(scheduledCallbacks.length).to.equal(1);
658
+
659
+ scheduledCallbacks.shift()();
660
+
661
+ expect(
662
+ first.dotsContainer.querySelector(".dots-overflow-indicator"),
663
+ ).to.exist;
664
+ expect(
665
+ second.dotsContainer.querySelector(".dots-overflow-indicator"),
666
+ ).to.exist;
667
+
668
+ await new Promise((resolve) => setTimeout(resolve, 0));
669
+
670
+ expect(scheduledCallbacks.length).to.equal(0);
671
+ } finally {
672
+ window.ResizeObserver = OriginalResizeObserver;
673
+ globalThis.ResizeObserver = originalGlobalResizeObserver;
674
+ window.requestAnimationFrame = originalRequestAnimationFrame;
675
+ globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
676
+ }
677
+ });
560
678
  });