fokus-styles 2.3.0 → 2.5.0

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.
Files changed (101) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/css/components.css +583 -83
  3. package/dist/css/components.css.map +1 -1
  4. package/dist/css/components.min.css +1 -1
  5. package/dist/css/components.min.css.map +1 -1
  6. package/dist/css/fokus-components.css +583 -83
  7. package/dist/css/fokus-components.css.map +1 -1
  8. package/dist/css/fokus-components.min.css +1 -1
  9. package/dist/css/fokus-components.min.css.map +1 -1
  10. package/dist/css/fokus-core.css +22 -0
  11. package/dist/css/fokus-core.css.map +1 -1
  12. package/dist/css/fokus-core.min.css +1 -1
  13. package/dist/css/fokus-core.min.css.map +1 -1
  14. package/dist/css/fokus-dark.css +10 -0
  15. package/dist/css/fokus-dark.css.map +1 -1
  16. package/dist/css/fokus-dark.min.css +1 -1
  17. package/dist/css/fokus-dark.min.css.map +1 -1
  18. package/dist/css/fokus-rtl.css +9166 -0
  19. package/dist/css/fokus-rtl.css.map +1 -0
  20. package/dist/css/fokus-rtl.min.css +2 -0
  21. package/dist/css/fokus-rtl.min.css.map +1 -0
  22. package/dist/css/fokus-utilities.css +158 -0
  23. package/dist/css/fokus-utilities.css.map +1 -1
  24. package/dist/css/fokus-utilities.min.css +1 -1
  25. package/dist/css/fokus-utilities.min.css.map +1 -1
  26. package/dist/css/fokus.css +1018 -258
  27. package/dist/css/fokus.css.map +1 -1
  28. package/dist/css/fokus.min.css +1 -1
  29. package/dist/css/fokus.min.css.map +1 -1
  30. package/dist/css/forms.css +322 -176
  31. package/dist/css/forms.css.map +1 -1
  32. package/dist/css/forms.min.css +1 -1
  33. package/dist/css/forms.min.css.map +1 -1
  34. package/dist/css/helpers.css +158 -0
  35. package/dist/css/helpers.css.map +1 -1
  36. package/dist/css/helpers.min.css +1 -1
  37. package/dist/css/helpers.min.css.map +1 -1
  38. package/dist/css/layout.css +22 -0
  39. package/dist/css/layout.css.map +1 -1
  40. package/dist/css/layout.min.css +1 -1
  41. package/dist/css/layout.min.css.map +1 -1
  42. package/dist/js/fokus.js +270 -25
  43. package/dist/js/fokus.js.map +2 -2
  44. package/dist/js/fokus.min.js +18 -18
  45. package/dist/js/fokus.min.js.map +3 -3
  46. package/docs/components/alert.md +69 -24
  47. package/docs/components/badge.md +23 -4
  48. package/docs/components/checkbox.md +16 -3
  49. package/docs/components/popover.md +20 -7
  50. package/docs/components/radio.md +11 -1
  51. package/docs/components/responsive.md +19 -0
  52. package/docs/components/switch.md +30 -8
  53. package/docs/components/tag.md +19 -4
  54. package/docs/components/toast.md +72 -59
  55. package/docs/components/tooltip.md +38 -2
  56. package/docs/guides/accessibility.md +7 -0
  57. package/docs/guides/rtl-and-system-preferences.md +28 -0
  58. package/docs/prompts/prompt-plan.md +4 -4
  59. package/docs/reference/accessibility-matrix.md +7 -7
  60. package/docs/reference/contrast-report.md +4 -0
  61. package/docs/reference/definitions.md +5 -1
  62. package/docs/reference/design-tokens.md +14 -0
  63. package/docs/reference/size-baseline.json +13 -13
  64. package/mockup/assets/example-theme.css +11 -0
  65. package/mockup/assets/showcase-contracts.js +6 -6
  66. package/mockup/examples/alerts.html +105 -31
  67. package/mockup/examples/badges-alerts.html +91 -59
  68. package/mockup/examples/check-radio-switch.html +575 -99
  69. package/mockup/examples/popover.html +81 -0
  70. package/mockup/examples/tag.html +97 -87
  71. package/mockup/examples/toast.html +113 -0
  72. package/mockup/examples/tooltip.html +68 -0
  73. package/mockup/forms.html +1 -1
  74. package/mockup/overlays-commands.html +2 -2
  75. package/package.json +2 -1
  76. package/packages/fokus-components/scss/components/_alerts.scss +128 -6
  77. package/packages/fokus-components/scss/components/_badges.scss +26 -10
  78. package/packages/fokus-components/scss/components/_index.scss +1 -0
  79. package/packages/fokus-components/scss/components/_popover.scss +131 -21
  80. package/packages/fokus-components/scss/components/_responsive.scss +23 -0
  81. package/packages/fokus-components/scss/components/_tag.scss +37 -14
  82. package/packages/fokus-components/scss/components/_toasts.scss +156 -11
  83. package/packages/fokus-components/scss/components/_tooltips.scss +70 -18
  84. package/packages/fokus-components/scss/forms/_check-radio-switch.scss +313 -215
  85. package/packages/fokus-components/scss/forms/_forms.scss +16 -0
  86. package/packages/fokus-core/scss/base/_accessibility.scss +14 -0
  87. package/packages/fokus-core/scss/tokens/_root.scss +11 -0
  88. package/packages/fokus-js/js/carousel.d.ts +4 -0
  89. package/packages/fokus-js/js/carousel.js +8 -0
  90. package/packages/fokus-js/js/core/positioning.js +18 -9
  91. package/packages/fokus-js/js/dropdown.d.ts +4 -0
  92. package/packages/fokus-js/js/dropdown.js +7 -0
  93. package/packages/fokus-js/js/popover.js +26 -1
  94. package/packages/fokus-js/js/tabs.d.ts +3 -0
  95. package/packages/fokus-js/js/tabs.js +5 -0
  96. package/packages/fokus-js/js/tag.js +17 -0
  97. package/packages/fokus-js/js/toast.js +77 -3
  98. package/packages/fokus-js/js/tooltip.d.ts +8 -1
  99. package/packages/fokus-js/js/tooltip.js +106 -13
  100. package/packages/fokus-utilities/scss/utilities/_api.scss +48 -0
  101. package/scss/entries/rtl-entry.scss +11 -0
package/dist/js/fokus.js CHANGED
@@ -110,15 +110,15 @@ var FokusStyles = (() => {
110
110
  }
111
111
  }
112
112
  function computePosition(referenceEl, floatingEl, options = {}) {
113
- const { placement = "bottom", align = "center", offset = 8, padding = 8 } = options;
113
+ var _a;
114
+ const { placement = "bottom", align = "center", offset = 8, padding = 8, fallbackPlacements } = options;
114
115
  const referenceRect = referenceEl.getBoundingClientRect();
115
116
  const floatingRect = floatingEl.getBoundingClientRect();
116
117
  const viewportWidth = document.documentElement.clientWidth;
117
118
  const viewportHeight = document.documentElement.clientHeight;
118
- let finalPlacement = placement;
119
- if (!fitsPlacement(placement, referenceRect, floatingRect, offset, viewportWidth, viewportHeight) && fitsPlacement(OPPOSITE_PLACEMENT[placement], referenceRect, floatingRect, offset, viewportWidth, viewportHeight)) {
120
- finalPlacement = OPPOSITE_PLACEMENT[placement];
121
- }
119
+ const candidates = [...new Set(fallbackPlacements != null ? fallbackPlacements : [placement, OPPOSITE_PLACEMENT[placement], "bottom", "top", "right", "left"])].filter(Boolean);
120
+ const primaryCandidates = candidates.slice(0, 2);
121
+ const finalPlacement = (_a = primaryCandidates.find((candidate) => fitsPlacement(candidate, referenceRect, floatingRect, offset, viewportWidth, viewportHeight))) != null ? _a : placement;
122
122
  let top;
123
123
  let left;
124
124
  if (finalPlacement === "top" || finalPlacement === "bottom") {
@@ -132,10 +132,15 @@ var FokusStyles = (() => {
132
132
  const maxTop = Math.max(padding, viewportHeight - floatingRect.height - padding);
133
133
  left = Math.min(Math.max(left, padding), maxLeft);
134
134
  top = Math.min(Math.max(top, padding), maxTop);
135
+ const crossSize = finalPlacement === "top" || finalPlacement === "bottom" ? floatingRect.width : floatingRect.height;
136
+ const crossStart = finalPlacement === "top" || finalPlacement === "bottom" ? left : top;
137
+ const referenceCenter = finalPlacement === "top" || finalPlacement === "bottom" ? referenceRect.left + referenceRect.width / 2 : referenceRect.top + referenceRect.height / 2;
138
+ const crossOffset = Math.min(Math.max(referenceCenter - crossStart, 8), Math.max(8, crossSize - 8));
135
139
  return {
136
140
  top: top + window.scrollY,
137
141
  left: left + window.scrollX,
138
- placement: finalPlacement
142
+ placement: finalPlacement,
143
+ arrowOffset: `${crossOffset}px`
139
144
  };
140
145
  }
141
146
  function watchPosition(referenceEl, floatingEl, options = {}) {
@@ -164,6 +169,10 @@ var FokusStyles = (() => {
164
169
  floatingEl.style.position = "absolute";
165
170
  floatingEl.style.top = `${position.top}px`;
166
171
  floatingEl.style.left = `${position.left}px`;
172
+ if (position.arrowOffset) {
173
+ floatingEl.style.setProperty("--fs-popover-arrow-offset", position.arrowOffset);
174
+ floatingEl.style.setProperty("--fs-tooltip-arrow-offset", position.arrowOffset);
175
+ }
167
176
  }
168
177
 
169
178
  // packages/fokus-js/js/core/overlay.js
@@ -385,7 +394,7 @@ var FokusStyles = (() => {
385
394
 
386
395
  // packages/fokus-js/js/dropdown.js
387
396
  var instances = createInstanceRegistry();
388
- var Dropdown = class {
397
+ var Dropdown = class _Dropdown {
389
398
  constructor(toggleEl, options = {}) {
390
399
  var _a, _b, _c, _d;
391
400
  const targetSelector = toggleEl.getAttribute("data-fs-target");
@@ -428,6 +437,20 @@ var FokusStyles = (() => {
428
437
  static getInstance(el) {
429
438
  return instances.get(el);
430
439
  }
440
+ static getOrCreateInstance(el, options = {}) {
441
+ var _a;
442
+ return (_a = this.getInstance(el)) != null ? _a : new _Dropdown(el, options);
443
+ }
444
+ static show(el, options = {}) {
445
+ return this.getOrCreateInstance(el, options).show();
446
+ }
447
+ static hide(el) {
448
+ var _a;
449
+ return (_a = this.getInstance(el)) == null ? void 0 : _a.hide();
450
+ }
451
+ static toggle(el, options = {}) {
452
+ return this.getOrCreateInstance(el, options).toggle();
453
+ }
431
454
  _handleToggleClick(event) {
432
455
  event.preventDefault();
433
456
  this.toggle();
@@ -489,6 +512,7 @@ var FokusStyles = (() => {
489
512
  if (this.toggleEl.contains(event.target)) return;
490
513
  this.hide();
491
514
  });
515
+ this.toggleEl.dispatchEvent(new CustomEvent("fs:shown", { bubbles: true }));
492
516
  this.toggleEl.dispatchEvent(new CustomEvent("fs:dropdown:shown", { bubbles: true }));
493
517
  }
494
518
  hide() {
@@ -504,6 +528,7 @@ var FokusStyles = (() => {
504
528
  this._outsideClickCleanup = null;
505
529
  (_b = this._positionCleanup) == null ? void 0 : _b.call(this);
506
530
  this._positionCleanup = null;
531
+ this.toggleEl.dispatchEvent(new CustomEvent("fs:hidden", { bubbles: true }));
507
532
  this.toggleEl.dispatchEvent(new CustomEvent("fs:dropdown:hidden", { bubbles: true }));
508
533
  }
509
534
  toggle() {
@@ -530,9 +555,20 @@ var FokusStyles = (() => {
530
555
  // packages/fokus-js/js/tooltip.js
531
556
  var instances2 = createInstanceRegistry();
532
557
  var idCounter = 0;
558
+ var openInstance = null;
559
+ var DEFAULT_TOOLTIP_OFFSET = 8;
560
+ function toDelay(value, fallback = 0) {
561
+ if (value === null || value === void 0 || value === "") return fallback;
562
+ const parsed = Number(value);
563
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
564
+ }
565
+ function parsePlacement(value = "top") {
566
+ const [placement, align = "center"] = value.split("-");
567
+ return { placement, align: ["start", "center", "end"].includes(align) ? align : "center" };
568
+ }
533
569
  var Tooltip = class {
534
570
  constructor(referenceEl, options = {}) {
535
- var _a, _b, _c, _d;
571
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
536
572
  const title = (_b = (_a = referenceEl.getAttribute("title")) != null ? _a : referenceEl.getAttribute("data-title")) != null ? _b : options.title;
537
573
  if (!title) {
538
574
  throw new Error("FokusStyles.Tooltip: elemento sem texto (atributo title/data-title).");
@@ -541,10 +577,18 @@ var FokusStyles = (() => {
541
577
  idCounter += 1;
542
578
  this.referenceEl = referenceEl;
543
579
  this.title = title;
544
- this.placement = (_d = (_c = options.placement) != null ? _c : referenceEl.getAttribute("data-placement")) != null ? _d : "top";
580
+ const parsedPlacement = parsePlacement((_d = (_c = options.placement) != null ? _c : referenceEl.getAttribute("data-placement")) != null ? _d : "top");
581
+ this.placement = parsedPlacement.placement;
582
+ this.align = (_f = (_e = options.align) != null ? _e : referenceEl.getAttribute("data-align")) != null ? _f : parsedPlacement.align;
583
+ this.offset = toDelay((_g = options.offset) != null ? _g : referenceEl.getAttribute("data-offset"), DEFAULT_TOOLTIP_OFFSET);
584
+ this.showDelay = toDelay((_h = options.showDelay) != null ? _h : referenceEl.getAttribute("data-show-delay"), 0);
585
+ this.hideDelay = toDelay((_i = options.hideDelay) != null ? _i : referenceEl.getAttribute("data-hide-delay"));
545
586
  this.isOpen = false;
546
587
  this.id = `fokus-tooltip-${idCounter}`;
547
588
  this._positionCleanup = null;
589
+ this._showTimer = null;
590
+ this._hideTimer = null;
591
+ this._touchOpened = false;
548
592
  this._originalDescribedBy = referenceEl.getAttribute("aria-describedby");
549
593
  this.tooltipEl = document.createElement("div");
550
594
  this.tooltipEl.className = "fs-tooltip";
@@ -553,13 +597,32 @@ var FokusStyles = (() => {
553
597
  this.tooltipEl.innerHTML = '<div class="fs-tooltip-arrow"></div><div class="fs-tooltip-inner"></div>';
554
598
  this.tooltipEl.querySelector(".fs-tooltip-inner").textContent = this.title;
555
599
  document.body.appendChild(this.tooltipEl);
556
- referenceEl.setAttribute("aria-describedby", this.id);
557
- this._handleShow = this.show.bind(this);
558
- this._handleHide = this.hide.bind(this);
559
- referenceEl.addEventListener("mouseenter", this._handleShow);
560
- referenceEl.addEventListener("mouseleave", this._handleHide);
600
+ this._addDescribedBy();
601
+ this._handleShow = () => this._scheduleShow(true);
602
+ this._handleHide = () => this._scheduleHide();
603
+ this._handlePointerEnter = () => this._scheduleShow();
604
+ this._handlePointerLeave = () => this._scheduleHide();
605
+ this._handlePointerDown = (event) => {
606
+ if (event.pointerType === "touch") {
607
+ if (this._touchOpened && this.isOpen) {
608
+ this.hideImmediate();
609
+ return;
610
+ }
611
+ this._touchOpened = true;
612
+ this._scheduleShow(true);
613
+ }
614
+ };
615
+ referenceEl.addEventListener("pointerenter", this._handlePointerEnter);
616
+ referenceEl.addEventListener("pointerleave", this._handlePointerLeave);
617
+ referenceEl.addEventListener("pointerdown", this._handlePointerDown);
618
+ referenceEl.addEventListener("mouseenter", this._handlePointerEnter);
619
+ referenceEl.addEventListener("mouseleave", this._handlePointerLeave);
561
620
  referenceEl.addEventListener("focus", this._handleShow);
562
621
  referenceEl.addEventListener("blur", this._handleHide);
622
+ this._handleDocumentPointerDown = (event) => {
623
+ if (this._touchOpened && !this.referenceEl.contains(event.target) && !this.tooltipEl.contains(event.target)) this.hide();
624
+ };
625
+ document.addEventListener("pointerdown", this._handleDocumentPointerDown, true);
563
626
  this._removeEscapeListener = onEscapeKey(() => {
564
627
  if (this.isOpen) this.hide();
565
628
  });
@@ -568,9 +631,44 @@ var FokusStyles = (() => {
568
631
  static getInstance(el) {
569
632
  return instances2.get(el);
570
633
  }
634
+ _clearTimers() {
635
+ clearTimeout(this._showTimer);
636
+ clearTimeout(this._hideTimer);
637
+ this._showTimer = null;
638
+ this._hideTimer = null;
639
+ }
640
+ _addDescribedBy() {
641
+ var _a;
642
+ const ids = new Set(((_a = this.referenceEl.getAttribute("aria-describedby")) != null ? _a : "").split(/\s+/).filter(Boolean));
643
+ ids.add(this.id);
644
+ this.referenceEl.setAttribute("aria-describedby", [...ids].join(" "));
645
+ }
646
+ _scheduleShow(immediate = false) {
647
+ this._clearTimers();
648
+ if (this.isOpen) return;
649
+ const delay = immediate ? 0 : this.showDelay;
650
+ if (delay === 0) {
651
+ this.show();
652
+ return;
653
+ }
654
+ this._showTimer = setTimeout(() => this.show(), delay);
655
+ }
656
+ _scheduleHide(immediate = false) {
657
+ this._clearTimers();
658
+ if (!this.isOpen) return;
659
+ const delay = immediate ? 0 : this.hideDelay;
660
+ if (delay === 0) {
661
+ this.hide();
662
+ return;
663
+ }
664
+ this._hideTimer = setTimeout(() => this.hide(), delay);
665
+ }
571
666
  show() {
572
667
  var _a;
668
+ this._clearTimers();
573
669
  if (this.isOpen) return;
670
+ if (openInstance && openInstance !== this) openInstance.hide(true);
671
+ openInstance = this;
574
672
  this.isOpen = true;
575
673
  const theme = (_a = this.referenceEl.closest("[data-theme]")) == null ? void 0 : _a.getAttribute("data-theme");
576
674
  if (theme) {
@@ -579,23 +677,31 @@ var FokusStyles = (() => {
579
677
  this.tooltipEl.removeAttribute("data-theme");
580
678
  }
581
679
  this.tooltipEl.classList.add("is-open");
582
- const position = computePosition(this.referenceEl, this.tooltipEl, { placement: this.placement, offset: 10 });
680
+ const position = computePosition(this.referenceEl, this.tooltipEl, { placement: this.placement, align: this.align, offset: this.offset });
583
681
  applyPosition(this.tooltipEl, position);
584
682
  this._positionCleanup = watchPosition(this.referenceEl, this.tooltipEl, {
585
683
  placement: this.placement,
586
- offset: 10
684
+ align: this.align,
685
+ offset: this.offset
587
686
  });
588
687
  this.tooltipEl.setAttribute("data-placement", position.placement);
688
+ this.tooltipEl.setAttribute("data-align", this.align);
589
689
  this.referenceEl.dispatchEvent(new CustomEvent("fs:tooltip:shown", { bubbles: true }));
590
690
  }
591
691
  hide() {
692
+ this.hideImmediate();
693
+ }
694
+ hideImmediate() {
592
695
  var _a;
696
+ this._clearTimers();
593
697
  if (!this.isOpen) return;
594
698
  this.isOpen = false;
699
+ this._touchOpened = false;
595
700
  this.tooltipEl.classList.remove("is-open");
596
701
  (_a = this._positionCleanup) == null ? void 0 : _a.call(this);
597
702
  this._positionCleanup = null;
598
703
  this.referenceEl.dispatchEvent(new CustomEvent("fs:tooltip:hidden", { bubbles: true }));
704
+ if (openInstance === this) openInstance = null;
599
705
  }
600
706
  toggle() {
601
707
  if (this.isOpen) {
@@ -605,11 +711,16 @@ var FokusStyles = (() => {
605
711
  }
606
712
  }
607
713
  dispose() {
608
- this.hide();
609
- this.referenceEl.removeEventListener("mouseenter", this._handleShow);
610
- this.referenceEl.removeEventListener("mouseleave", this._handleHide);
714
+ this.hideImmediate();
715
+ this._clearTimers();
716
+ this.referenceEl.removeEventListener("pointerenter", this._handlePointerEnter);
717
+ this.referenceEl.removeEventListener("pointerleave", this._handlePointerLeave);
718
+ this.referenceEl.removeEventListener("pointerdown", this._handlePointerDown);
719
+ this.referenceEl.removeEventListener("mouseenter", this._handlePointerEnter);
720
+ this.referenceEl.removeEventListener("mouseleave", this._handlePointerLeave);
611
721
  this.referenceEl.removeEventListener("focus", this._handleShow);
612
722
  this.referenceEl.removeEventListener("blur", this._handleHide);
723
+ document.removeEventListener("pointerdown", this._handleDocumentPointerDown, true);
613
724
  this._removeEscapeListener();
614
725
  if (this._originalDescribedBy === null) this.referenceEl.removeAttribute("aria-describedby");
615
726
  else this.referenceEl.setAttribute("aria-describedby", this._originalDescribedBy);
@@ -623,6 +734,7 @@ var FokusStyles = (() => {
623
734
  var instances3 = createInstanceRegistry();
624
735
  var idCounter2 = 0;
625
736
  var HOVER_HIDE_DELAY = 100;
737
+ var openPopovers = /* @__PURE__ */ new Set();
626
738
  var Popover = class {
627
739
  constructor(triggerEl, options = {}) {
628
740
  var _a, _b, _c, _d, _e, _f;
@@ -640,6 +752,7 @@ var FokusStyles = (() => {
640
752
  this._outsideClickCleanup = null;
641
753
  this._removeEscapeListener = null;
642
754
  this._hideTimer = null;
755
+ this._suppressNextFocus = false;
643
756
  this._positionCleanup = null;
644
757
  this._originalParent = popoverEl.parentNode;
645
758
  this._originalNextSibling = popoverEl.nextSibling;
@@ -653,6 +766,11 @@ var FokusStyles = (() => {
653
766
  if (!headerEl.id) headerEl.id = `fokus-popover-header-${idCounter2}`;
654
767
  popoverEl.setAttribute("aria-labelledby", headerEl.id);
655
768
  }
769
+ const descriptionEl = popoverEl.querySelector(".fs-popover-text");
770
+ if (descriptionEl) {
771
+ if (!descriptionEl.id) descriptionEl.id = `fokus-popover-text-${idCounter2}`;
772
+ popoverEl.setAttribute("aria-describedby", descriptionEl.id);
773
+ }
656
774
  triggerEl.setAttribute("aria-controls", popoverEl.id);
657
775
  triggerEl.setAttribute("aria-expanded", "false");
658
776
  this._handleDismissClick = this._handleDismissClick.bind(this);
@@ -684,7 +802,13 @@ var FokusStyles = (() => {
684
802
  this.popoverEl.addEventListener("mouseenter", this._handleMouseEnter);
685
803
  this.popoverEl.addEventListener("mouseleave", this._handleMouseLeave);
686
804
  } else if (this.trigger === "focus") {
687
- this._handleTriggerFocus = () => this.show();
805
+ this._handleTriggerFocus = () => {
806
+ if (this._suppressNextFocus) {
807
+ this._suppressNextFocus = false;
808
+ return;
809
+ }
810
+ this.show();
811
+ };
688
812
  this._handleTriggerFocusOut = (event) => {
689
813
  if (this.popoverEl.contains(event.relatedTarget)) return;
690
814
  this.hide();
@@ -707,7 +831,12 @@ var FokusStyles = (() => {
707
831
  }
708
832
  _handleDismissClick(event) {
709
833
  if (event.target.closest('[data-fs-dismiss="popover"]')) {
834
+ const restoreFocus = this.popoverEl.contains(document.activeElement);
835
+ if (restoreFocus && this.trigger === "focus") this._suppressNextFocus = true;
710
836
  this.hide();
837
+ if (restoreFocus && typeof this.triggerEl.focus === "function") {
838
+ this.triggerEl.focus();
839
+ }
711
840
  }
712
841
  }
713
842
  show() {
@@ -721,6 +850,9 @@ var FokusStyles = (() => {
721
850
  this.popoverEl.removeAttribute("data-theme");
722
851
  }
723
852
  this.popoverEl.classList.add("is-open");
853
+ openPopovers.forEach((popover) => {
854
+ if (popover !== this) popover.hide();
855
+ });
724
856
  const position = computePosition(this.triggerEl, this.popoverEl, {
725
857
  placement: this.placement,
726
858
  align: this.align,
@@ -733,6 +865,7 @@ var FokusStyles = (() => {
733
865
  offset: 10
734
866
  });
735
867
  this.popoverEl.setAttribute("data-placement", position.placement);
868
+ openPopovers.add(this);
736
869
  this.triggerEl.setAttribute("aria-expanded", "true");
737
870
  if (this.trigger === "click") {
738
871
  this._outsideClickCleanup = onClickOutside(this.popoverEl, (event) => {
@@ -746,6 +879,7 @@ var FokusStyles = (() => {
746
879
  var _a, _b;
747
880
  if (!this.isOpen) return;
748
881
  this.isOpen = false;
882
+ openPopovers.delete(this);
749
883
  this.popoverEl.classList.remove("is-open");
750
884
  (_a = this._positionCleanup) == null ? void 0 : _a.call(this);
751
885
  this._positionCleanup = null;
@@ -975,12 +1109,25 @@ var FokusStyles = (() => {
975
1109
  // fs:stepper:beforechange): preventDefault() bloqueia a remoção.
976
1110
  dismiss() {
977
1111
  if (this._isLoading) return false;
1112
+ const dismissButton = this.tagEl.querySelector('[data-fs-dismiss="tag"]');
1113
+ const shouldRestoreFocus = dismissButton && document.activeElement === dismissButton;
978
1114
  const event = new CustomEvent("fs:tag:dismissed", { bubbles: true, cancelable: true });
979
1115
  this.tagEl.dispatchEvent(event);
980
1116
  if (event.defaultPrevented) return false;
1117
+ const focusTarget = shouldRestoreFocus ? this._getFocusTarget() : null;
981
1118
  this.tagEl.remove();
1119
+ focusTarget == null ? void 0 : focusTarget.focus();
982
1120
  return true;
983
1121
  }
1122
+ _getFocusTarget() {
1123
+ const group = this.tagEl.closest(".fs-tag-group");
1124
+ if (!group) return null;
1125
+ const buttons = [...group.querySelectorAll('[data-fs-dismiss="tag"]')].filter((button) => !button.disabled && !button.hidden && button.getAttribute("aria-hidden") !== "true");
1126
+ const currentButton = this.tagEl.querySelector('[data-fs-dismiss="tag"]');
1127
+ const currentIndex = buttons.indexOf(currentButton);
1128
+ const remaining = buttons.filter((button) => button !== currentButton);
1129
+ return remaining[currentIndex] || remaining[currentIndex - 1] || null;
1130
+ }
984
1131
  // Estado útil enquanto uma remoção ou atualização assíncrona está pendente.
985
1132
  // Além da aparência, bloqueia o dismiss e comunica o estado a AT.
986
1133
  setLoading(loading) {
@@ -1422,7 +1569,7 @@ var FokusStyles = (() => {
1422
1569
  // packages/fokus-js/js/tabs.js
1423
1570
  var instances11 = createInstanceRegistry();
1424
1571
  var idCounter6 = 0;
1425
- var Tabs = class {
1572
+ var Tabs = class _Tabs {
1426
1573
  constructor(tablistEl) {
1427
1574
  this.tablistEl = tablistEl;
1428
1575
  this.tabs = Array.from(tablistEl.querySelectorAll(".fs-nav-link"));
@@ -1453,6 +1600,17 @@ var FokusStyles = (() => {
1453
1600
  static getInstance(el) {
1454
1601
  return instances11.get(el);
1455
1602
  }
1603
+ static getOrCreateInstance(el) {
1604
+ var _a;
1605
+ return (_a = this.getInstance(el)) != null ? _a : new _Tabs(el);
1606
+ }
1607
+ static show(el, tab) {
1608
+ return this.getOrCreateInstance(el).show(tab);
1609
+ }
1610
+ static dispose(el) {
1611
+ var _a;
1612
+ return (_a = this.getInstance(el)) == null ? void 0 : _a.dispose();
1613
+ }
1456
1614
  _handleClick(event) {
1457
1615
  const tab = event.target.closest(".fs-nav-link:not(.is-disabled)");
1458
1616
  if (!tab || !this.tabs.includes(tab)) return;
@@ -1485,6 +1643,7 @@ var FokusStyles = (() => {
1485
1643
  pane == null ? void 0 : pane.classList.toggle("is-active", isActive);
1486
1644
  if (pane) pane.hidden = !isActive;
1487
1645
  });
1646
+ tab.dispatchEvent(new CustomEvent("fs:shown", { bubbles: true }));
1488
1647
  tab.dispatchEvent(
1489
1648
  new CustomEvent("fs:tab:changed", { bubbles: true, detail: { target: tab.getAttribute("data-fs-target") } })
1490
1649
  );
@@ -1506,11 +1665,37 @@ var FokusStyles = (() => {
1506
1665
  this.autohide = toastEl.getAttribute("data-autohide") !== "false";
1507
1666
  this.isOpen = false;
1508
1667
  this._hideTimer = null;
1668
+ this._paused = false;
1669
+ this._remaining = this.delay;
1670
+ this._startedAt = 0;
1671
+ this._progressTimer = null;
1509
1672
  toastEl.setAttribute("role", "status");
1510
1673
  toastEl.setAttribute("aria-live", "polite");
1511
1674
  toastEl.style.display = "none";
1512
1675
  this._handleDismissClick = this._handleDismissClick.bind(this);
1676
+ this._pause = () => {
1677
+ if (!this.isOpen || this._paused) return;
1678
+ this._paused = true;
1679
+ clearTimeout(this._hideTimer);
1680
+ if (this.autohide && this._startedAt) {
1681
+ this._remaining = Math.max(0, this._remaining - (Date.now() - this._startedAt));
1682
+ }
1683
+ this._updateProgress();
1684
+ clearInterval(this._progressTimer);
1685
+ this.toastEl.classList.add("is-paused");
1686
+ };
1687
+ this._resume = () => {
1688
+ if (!this.isOpen || !this._paused) return;
1689
+ this._paused = false;
1690
+ this.toastEl.classList.remove("is-paused");
1691
+ this._startProgress();
1692
+ if (this.autohide) this._scheduleHide();
1693
+ };
1513
1694
  toastEl.addEventListener("click", this._handleDismissClick);
1695
+ toastEl.addEventListener("mouseenter", this._pause);
1696
+ toastEl.addEventListener("mouseleave", this._resume);
1697
+ toastEl.addEventListener("focusin", this._pause);
1698
+ toastEl.addEventListener("focusout", this._resume);
1514
1699
  instances12.set(toastEl, this);
1515
1700
  }
1516
1701
  static getInstance(el) {
@@ -1525,20 +1710,59 @@ var FokusStyles = (() => {
1525
1710
  if (this.isOpen) return;
1526
1711
  this.toastEl.dispatchEvent(new CustomEvent("fs:show", { bubbles: true }));
1527
1712
  this.isOpen = true;
1528
- expand(this.toastEl).then(() => {
1713
+ this._remaining = this.delay;
1714
+ this._startedAt = Date.now();
1715
+ this.toastEl.style.setProperty("--fs-toast-duration", `${this.delay}ms`);
1716
+ const expansion = expand(this.toastEl);
1717
+ this.toastEl.classList.remove("is-progressing");
1718
+ void this.toastEl.offsetWidth;
1719
+ if (this.toastEl.getAttribute("data-toast-progress") === "true" && this.autohide) {
1720
+ this.toastEl.classList.add("is-progressing");
1721
+ this._startProgress();
1722
+ }
1723
+ expansion.then(() => {
1529
1724
  this.toastEl.dispatchEvent(new CustomEvent("fs:shown", { bubbles: true }));
1530
1725
  this.toastEl.dispatchEvent(new CustomEvent("fs:toast:shown", { bubbles: true }));
1531
1726
  });
1532
- if (this.autohide) {
1533
- this._hideTimer = setTimeout(() => this.hide(), this.delay);
1727
+ this._scheduleHide();
1728
+ }
1729
+ _scheduleHide() {
1730
+ clearTimeout(this._hideTimer);
1731
+ if (this.autohide && !this._paused) {
1732
+ this._startedAt = Date.now();
1733
+ this._hideTimer = setTimeout(() => this.hide(), this._remaining);
1534
1734
  }
1535
1735
  }
1736
+ _updateProgress() {
1737
+ if (this.toastEl.getAttribute("data-toast-progress") !== "true" || !this.autohide) return;
1738
+ const percentage = Math.max(0, Math.min(100, this._remaining / this.delay * 100));
1739
+ const progressEl = this.toastEl.querySelector(".fs-toast-progress");
1740
+ if (progressEl) progressEl.style.inlineSize = `${percentage}%`;
1741
+ }
1742
+ _startProgress() {
1743
+ if (this.toastEl.getAttribute("data-toast-progress") !== "true" || !this.autohide) return;
1744
+ clearInterval(this._progressTimer);
1745
+ this._updateProgress();
1746
+ this._progressTimer = setInterval(() => {
1747
+ if (!this.isOpen || this._paused) return;
1748
+ this._remaining = Math.max(0, this.delay - (Date.now() - this._startedAt));
1749
+ this._updateProgress();
1750
+ }, 50);
1751
+ }
1536
1752
  hide() {
1537
1753
  if (!this.isOpen) return;
1538
1754
  this.toastEl.dispatchEvent(new CustomEvent("fs:hide", { bubbles: true }));
1539
1755
  this.isOpen = false;
1540
1756
  clearTimeout(this._hideTimer);
1541
1757
  this._hideTimer = null;
1758
+ clearInterval(this._progressTimer);
1759
+ this._progressTimer = null;
1760
+ this._startedAt = 0;
1761
+ this._remaining = this.delay;
1762
+ this.toastEl.classList.remove("is-paused");
1763
+ this.toastEl.classList.remove("is-progressing");
1764
+ const progressEl = this.toastEl.querySelector(".fs-toast-progress");
1765
+ if (progressEl) progressEl.style.removeProperty("inline-size");
1542
1766
  collapse(this.toastEl).then(() => {
1543
1767
  this.toastEl.dispatchEvent(new CustomEvent("fs:hidden", { bubbles: true }));
1544
1768
  this.toastEl.dispatchEvent(new CustomEvent("fs:toast:hidden", { bubbles: true }));
@@ -1553,7 +1777,12 @@ var FokusStyles = (() => {
1553
1777
  }
1554
1778
  dispose() {
1555
1779
  clearTimeout(this._hideTimer);
1780
+ clearInterval(this._progressTimer);
1556
1781
  this.toastEl.removeEventListener("click", this._handleDismissClick);
1782
+ this.toastEl.removeEventListener("mouseenter", this._pause);
1783
+ this.toastEl.removeEventListener("mouseleave", this._resume);
1784
+ this.toastEl.removeEventListener("focusin", this._pause);
1785
+ this.toastEl.removeEventListener("focusout", this._resume);
1557
1786
  instances12.delete(this.toastEl);
1558
1787
  }
1559
1788
  };
@@ -1562,7 +1791,7 @@ var FokusStyles = (() => {
1562
1791
  // packages/fokus-js/js/carousel.js
1563
1792
  var instances13 = createInstanceRegistry();
1564
1793
  var interactiveSelector = "a, button, input, select, textarea, label, [contenteditable='true']";
1565
- var Carousel = class {
1794
+ var Carousel = class _Carousel {
1566
1795
  constructor(carouselEl) {
1567
1796
  var _a, _b, _c;
1568
1797
  this.carouselEl = carouselEl;
@@ -1643,6 +1872,19 @@ var FokusStyles = (() => {
1643
1872
  static getInstance(el) {
1644
1873
  return instances13.get(el);
1645
1874
  }
1875
+ static getOrCreateInstance(el) {
1876
+ var _a;
1877
+ return (_a = this.getInstance(el)) != null ? _a : new _Carousel(el);
1878
+ }
1879
+ static next(el) {
1880
+ return this.getOrCreateInstance(el).next();
1881
+ }
1882
+ static prev(el) {
1883
+ return this.getOrCreateInstance(el).prev();
1884
+ }
1885
+ static goTo(el, index) {
1886
+ return this.getOrCreateInstance(el).goTo(index);
1887
+ }
1646
1888
  _isDocumentVisible() {
1647
1889
  return document.visibilityState !== "hidden";
1648
1890
  }
@@ -1707,6 +1949,9 @@ var FokusStyles = (() => {
1707
1949
  if (to === from) return;
1708
1950
  this.index = to;
1709
1951
  this._render();
1952
+ this.carouselEl.dispatchEvent(
1953
+ new CustomEvent("fs:shown", { bubbles: true, detail: { from, to } })
1954
+ );
1710
1955
  this.carouselEl.dispatchEvent(
1711
1956
  new CustomEvent("fs:carousel:slid", { bubbles: true, detail: { from, to } })
1712
1957
  );