@rogieking/figui3 4.7.0 → 4.8.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.
package/fig.js CHANGED
@@ -503,7 +503,9 @@ customElements.define("fig-dropdown", FigDropdown);
503
503
  * @attr {string} action - The trigger action: "hover" (default) or "click"
504
504
  * @attr {number} delay - Delay in milliseconds before showing tooltip (default: 500)
505
505
  * @attr {string} text - The tooltip text content
506
- * @attr {string} offset - Comma-separated offset values: left,top,right,bottom
506
+ * @attr {string} theme - Optional theme passed to the underlying popup (e.g. "brand").
507
+ * @attr {string} pointer - "false" to hide the beak.
508
+ * @attr {boolean} show - When set, force-show the tooltip (ignores hide).
507
509
  */
508
510
  class FigTooltip extends HTMLElement {
509
511
  static #lastShownAt = 0;
@@ -521,8 +523,6 @@ class FigTooltip extends HTMLElement {
521
523
  #parentDialog = null;
522
524
  #touchTimeout;
523
525
  #isTouching = false;
524
- #observer = null;
525
- #repositionRAF = null;
526
526
  constructor() {
527
527
  super();
528
528
  this.action = this.getAttribute("action") || "hover";
@@ -560,7 +560,6 @@ class FigTooltip extends HTMLElement {
560
560
  this.#boundHideOnChromeOpen,
561
561
  true,
562
562
  );
563
- this.#stopObserving();
564
563
  if (this.#parentDialog) {
565
564
  this.#parentDialog.removeEventListener("close", this.#boundHandleDialogClose);
566
565
  this.#parentDialog = null;
@@ -593,47 +592,61 @@ class FigTooltip extends HTMLElement {
593
592
 
594
593
  render() {
595
594
  this.destroy();
596
- let content = document.createElement("span");
597
- this.popup = document.createElement("span");
598
- this.popup.setAttribute("class", "fig-tooltip");
595
+ const supportsPopover =
596
+ typeof HTMLElement !== "undefined" &&
597
+ "popover" in HTMLElement.prototype;
598
+
599
+ const content = document.createElement("span");
600
+ // Customized built-in: `is` MUST be passed via createElement options;
601
+ // setAttribute("is", ...) after the fact is a no-op per the HTML spec.
602
+ this.popup = document.createElement("dialog", { is: "fig-popup" });
603
+ // Also set the `is` attribute explicitly so CSS selectors like
604
+ // `dialog[is="fig-popup"]` match. createElement's `is` option upgrades
605
+ // the element but doesn't reflect to the attribute in all engines.
606
+ this.popup.setAttribute("is", "fig-popup");
607
+ this.popup.setAttribute("variant", "tooltip");
608
+ this.popup.setAttribute("data-tooltip-managed", "");
599
609
  this.popup.setAttribute("role", "tooltip");
610
+ this.popup.setAttribute("closedby", "none");
611
+ if (supportsPopover) this.popup.setAttribute("popover", "manual");
612
+
600
613
  const tooltipId = figUniqueId();
601
614
  this.popup.setAttribute("id", tooltipId);
602
- this.popup.style.position = "fixed";
603
- this.popup.style.visibility = "hidden";
604
- this.popup.style.display = "inline-flex";
605
- this.popup.style.pointerEvents = "none";
606
615
  const theme = this.getAttribute("theme");
607
616
  if (theme) this.popup.setAttribute("theme", theme);
608
617
  const pointer = this.getAttribute("pointer");
609
618
  if (pointer !== null) this.popup.setAttribute("pointer", pointer);
619
+
610
620
  this.popup.append(content);
611
- content.innerText = this.getAttribute("text");
621
+ content.innerText = this.getAttribute("text") ?? "";
622
+
612
623
  // Set aria-describedby on the trigger element
613
624
  if (this.firstElementChild) {
614
625
  this.firstElementChild.setAttribute("aria-describedby", tooltipId);
615
626
  }
616
627
 
617
- // If tooltip is inside a dialog, append to dialog to stay in top layer
618
- const parentDialog = this.closest("dialog");
619
- if (parentDialog && parentDialog.open) {
620
- parentDialog.append(this.popup);
621
- } else {
628
+ // Attach to DOM.
629
+ // - With popover support, body is fine because top-layer promotion handles
630
+ // stacking above any open <dialog> (including modal).
631
+ // - Without popover support, fall back to today's behavior: nearest open
632
+ // <dialog> ancestor if present, else document.body.
633
+ if (supportsPopover) {
622
634
  document.body.append(this.popup);
635
+ } else {
636
+ const parentDialog = this.closest("dialog");
637
+ if (parentDialog && parentDialog.open) {
638
+ parentDialog.append(this.popup);
639
+ } else {
640
+ document.body.append(this.popup);
641
+ }
623
642
  }
624
643
 
625
- const text = content.childNodes[0];
626
- if (text) {
627
- const range = document.createRange();
628
- range.setStartBefore(text);
629
- range.setEndAfter(text);
630
- const clientRect = range.getBoundingClientRect();
631
- content.style.width = `${clientRect.width}px`;
632
- }
644
+ // Bind the popup's anchor to this tooltip's trigger child so fig-popup
645
+ // can position itself and update its beak via data-beak-side.
646
+ this.popup.anchor = this.firstElementChild;
633
647
  }
634
648
 
635
649
  destroy() {
636
- this.#stopObserving();
637
650
  if (this.popup) {
638
651
  this.popup.remove();
639
652
  this.popup = null;
@@ -684,20 +697,6 @@ class FigTooltip extends HTMLElement {
684
697
  document.addEventListener("mousedown", this.#boundHideOnChromeOpen, true);
685
698
  }
686
699
 
687
- getOffset() {
688
- const defaultOffset = { left: 8, top: 4, right: 8, bottom: 4 };
689
- const offsetAttr = this.getAttribute("offset");
690
- if (!offsetAttr) return defaultOffset;
691
-
692
- const [left, top, right, bottom] = offsetAttr.split(",").map(Number);
693
- return {
694
- left: isNaN(left) ? defaultOffset.left : left,
695
- top: isNaN(top) ? defaultOffset.top : top,
696
- right: isNaN(right) ? defaultOffset.right : right,
697
- bottom: isNaN(bottom) ? defaultOffset.bottom : bottom,
698
- };
699
- }
700
-
701
700
  get #showPersisted() {
702
701
  return this.hasAttribute("show") && this.getAttribute("show") !== "false";
703
702
  }
@@ -714,72 +713,22 @@ class FigTooltip extends HTMLElement {
714
713
 
715
714
  showPopup() {
716
715
  if (this.#parentDialog && !this.#parentDialog.open) return;
716
+ if (!this.firstElementChild) return;
717
717
  if (!this.popup) this.render();
718
- this.popup.style.display = "block";
719
- this.popup.style.visibility = "hidden";
720
- this.#repositionPopup();
721
- this.popup.style.opacity = "1";
722
- this.popup.style.visibility = "visible";
723
- this.popup.style.pointerEvents = "all";
724
- this.popup.style.zIndex = figGetHighestZIndex() + 1;
718
+ // Keep anchor in sync in case the trigger child was swapped between
719
+ // creation and show.
720
+ this.popup.anchor = this.firstElementChild;
721
+ this.popup.open = true;
725
722
 
726
723
  this.isOpen = true;
727
724
  FigTooltip.#lastShownAt = Date.now();
728
- this.#startObserving();
729
- }
730
-
731
- #repositionPopup() {
732
- if (!this.popup || !this.firstElementChild) return;
733
-
734
- const rect = this.firstElementChild.getBoundingClientRect();
735
- const popupRect = this.popup.getBoundingClientRect();
736
- const offset = this.getOffset();
737
-
738
- const container = this.popup.parentElement;
739
- const containerRect =
740
- container && container !== document.body
741
- ? container.getBoundingClientRect()
742
- : { left: 0, top: 0 };
743
-
744
- // Position the tooltip above the element
745
- let top = rect.top - popupRect.height - offset.top - containerRect.top;
746
- let left =
747
- rect.left + (rect.width - popupRect.width) / 2 - containerRect.left;
748
- this.popup.setAttribute("position", "top");
749
-
750
- // Adjust if tooltip would go off-screen
751
- if (top + containerRect.top < 0) {
752
- this.popup.setAttribute("position", "bottom");
753
- top = rect.bottom + offset.bottom - containerRect.top;
754
- }
755
- const absLeft = left + containerRect.left;
756
- if (absLeft < offset.left) {
757
- left = offset.left - containerRect.left;
758
- } else if (absLeft + popupRect.width > window.innerWidth - offset.right) {
759
- left =
760
- window.innerWidth - popupRect.width - offset.right - containerRect.left;
761
- }
762
-
763
- // Calculate the center of the target element relative to the tooltip
764
- const targetCenter = rect.left - containerRect.left + rect.width / 2;
765
- const beakOffset = targetCenter - left;
766
-
767
- // Set the beak offset as a CSS custom property
768
- this.popup.style.setProperty("--beak-offset", `${beakOffset}px`);
769
-
770
- this.popup.style.top = `${top}px`;
771
- this.popup.style.left = `${left}px`;
772
725
  }
773
726
 
774
727
  hidePopup() {
775
728
  if (this.#showPersisted) return;
776
729
  clearTimeout(this.timeout);
777
730
  clearTimeout(this.#touchTimeout);
778
- this.#stopObserving();
779
731
  if (this.popup) {
780
- this.popup.style.opacity = "0";
781
- this.popup.style.display = "block";
782
- this.popup.style.pointerEvents = "none";
783
732
  this.destroy();
784
733
  }
785
734
 
@@ -787,35 +736,6 @@ class FigTooltip extends HTMLElement {
787
736
  FigTooltip.#lastShownAt = Date.now();
788
737
  }
789
738
 
790
- #startObserving() {
791
- this.#stopObserving();
792
- const target = this.firstElementChild;
793
- if (!target) return;
794
-
795
- this.#observer = new MutationObserver(() => {
796
- if (this.#repositionRAF) cancelAnimationFrame(this.#repositionRAF);
797
- this.#repositionRAF = requestAnimationFrame(() => {
798
- this.#repositionPopup();
799
- });
800
- });
801
-
802
- this.#observer.observe(target, {
803
- attributes: true,
804
- attributeFilter: ["style", "class", "transform"],
805
- });
806
- }
807
-
808
- #stopObserving() {
809
- if (this.#repositionRAF) {
810
- cancelAnimationFrame(this.#repositionRAF);
811
- this.#repositionRAF = null;
812
- }
813
- if (this.#observer) {
814
- this.#observer.disconnect();
815
- this.#observer = null;
816
- }
817
- }
818
-
819
739
  hidePopupOutsideClick(event) {
820
740
  if (this.isOpen && !this.popup.contains(event.target)) {
821
741
  this.hidePopup();
@@ -885,15 +805,7 @@ class FigTooltip extends HTMLElement {
885
805
  const content = this.popup.firstElementChild ?? this.popup.firstChild;
886
806
  if (!content) return;
887
807
  content.innerText = value;
888
- content.style.width = "";
889
- const textNode = content.childNodes[0];
890
- if (textNode) {
891
- const range = document.createRange();
892
- range.setStartBefore(textNode);
893
- range.setEndAfter(textNode);
894
- content.style.width = `${range.getBoundingClientRect().width}px`;
895
- }
896
- if (this.isOpen) this.#repositionPopup();
808
+ // fig-popup observes content size changes and will reposition itself.
897
809
  }
898
810
  get open() {
899
811
  return this.hasAttribute("open") && this.getAttribute("open") === "true";
@@ -978,51 +890,34 @@ class FigTooltip extends HTMLElement {
978
890
  FigTooltip.#programmatic.set(anchor, state);
979
891
 
980
892
  state.timeout = setTimeout(() => {
981
- const popup = document.createElement("span");
982
- popup.setAttribute("class", "fig-tooltip");
893
+ const supportsPopover =
894
+ typeof HTMLElement !== "undefined" &&
895
+ "popover" in HTMLElement.prototype;
896
+
897
+ const popup = document.createElement("dialog", { is: "fig-popup" });
898
+ popup.setAttribute("is", "fig-popup");
899
+ popup.setAttribute("variant", "tooltip");
900
+ popup.setAttribute("data-tooltip-managed", "");
983
901
  popup.setAttribute("role", "tooltip");
984
- popup.style.position = "fixed";
985
- popup.style.pointerEvents = "none";
902
+ popup.setAttribute("closedby", "none");
903
+ if (supportsPopover) popup.setAttribute("popover", "manual");
986
904
  const content = document.createElement("span");
987
905
  content.innerText = text;
988
906
  popup.append(content);
989
907
 
990
- const parentDialog = anchor.closest("dialog");
991
- if (parentDialog && parentDialog.open) {
992
- parentDialog.append(popup);
993
- } else {
908
+ if (supportsPopover) {
994
909
  document.body.append(popup);
910
+ } else {
911
+ const parentDialog = anchor.closest?.("dialog");
912
+ if (parentDialog && parentDialog.open) {
913
+ parentDialog.append(popup);
914
+ } else {
915
+ document.body.append(popup);
916
+ }
995
917
  }
996
918
 
997
- const rect = anchor.getBoundingClientRect();
998
- const popupRect = popup.getBoundingClientRect();
999
- const container = popup.parentElement;
1000
- const containerRect =
1001
- container && container !== document.body
1002
- ? container.getBoundingClientRect()
1003
- : { left: 0, top: 0 };
1004
-
1005
- let top = rect.top - popupRect.height - 4 - containerRect.top;
1006
- let left =
1007
- rect.left + (rect.width - popupRect.width) / 2 - containerRect.left;
1008
- popup.setAttribute("position", "top");
1009
-
1010
- if (top + containerRect.top < 0) {
1011
- popup.setAttribute("position", "bottom");
1012
- top = rect.bottom + 4 - containerRect.top;
1013
- }
1014
- if (left + containerRect.left < 8) {
1015
- left = 8 - containerRect.left;
1016
- }
1017
- if (left + popupRect.width + containerRect.left > window.innerWidth - 8) {
1018
- left = window.innerWidth - popupRect.width - 8 - containerRect.left;
1019
- }
1020
-
1021
- const targetCenter = rect.left - containerRect.left + rect.width / 2;
1022
- popup.style.setProperty("--beak-offset", `${targetCenter - left}px`);
1023
- popup.style.top = `${top}px`;
1024
- popup.style.left = `${left}px`;
1025
- popup.style.zIndex = figGetHighestZIndex() + 1;
919
+ popup.anchor = anchor;
920
+ popup.open = true;
1026
921
 
1027
922
  state.popup = popup;
1028
923
  FigTooltip.#lastShownAt = Date.now();
@@ -1141,10 +1036,19 @@ class FigDialog extends HTMLDialogElement {
1141
1036
  #boundPointerUp;
1142
1037
  #boundClose;
1143
1038
  #boundIframeMessage;
1039
+ #boundContentMutation;
1040
+ #boundContentResize;
1041
+ #resizeObserver = null;
1042
+ #mutationObserver = null;
1043
+ #autoResizeRafId = 0;
1144
1044
  #offset = 16; // 1rem in pixels
1145
1045
  #positionInitialized = false;
1146
1046
  #dragThreshold = 3; // pixels before drag starts
1147
1047
 
1048
+ static get observedAttributes() {
1049
+ return ["autoresize"];
1050
+ }
1051
+
1148
1052
  constructor() {
1149
1053
  super();
1150
1054
  this.#boundPointerDown = this.#handlePointerDown.bind(this);
@@ -1152,6 +1056,15 @@ class FigDialog extends HTMLDialogElement {
1152
1056
  this.#boundPointerUp = this.#handlePointerUp.bind(this);
1153
1057
  this.#boundClose = this.close.bind(this);
1154
1058
  this.#boundIframeMessage = this.#handleIframeMessage.bind(this);
1059
+ this.#boundContentMutation = this.#scheduleAutoResize.bind(this);
1060
+ this.#boundContentResize = this.#scheduleAutoResize.bind(this);
1061
+ }
1062
+
1063
+ get autoresize() {
1064
+ return (
1065
+ this.hasAttribute("autoresize") &&
1066
+ this.getAttribute("autoresize") !== "false"
1067
+ );
1155
1068
  }
1156
1069
 
1157
1070
  connectedCallback() {
@@ -1168,6 +1081,7 @@ class FigDialog extends HTMLDialogElement {
1168
1081
  this.#addCloseListeners();
1169
1082
  this.#setupDragListeners();
1170
1083
  this.#applyPosition();
1084
+ this.#syncAutoResize();
1171
1085
  });
1172
1086
 
1173
1087
  window.addEventListener("message", this.#boundIframeMessage);
@@ -1179,9 +1093,17 @@ class FigDialog extends HTMLDialogElement {
1179
1093
  button.removeEventListener("click", this.#boundClose);
1180
1094
  });
1181
1095
  window.removeEventListener("message", this.#boundIframeMessage);
1096
+ this.#teardownAutoResize();
1097
+ }
1098
+
1099
+ attributeChangedCallback(name) {
1100
+ if (name === "autoresize" && this.isConnected) {
1101
+ this.#syncAutoResize();
1102
+ }
1182
1103
  }
1183
1104
 
1184
1105
  #handleIframeMessage(event) {
1106
+ if (!this.autoresize) return;
1185
1107
  const data = event?.data;
1186
1108
  if (!data || data.type !== "figui:iframe-resize") return;
1187
1109
  const source = event.source;
@@ -1193,13 +1115,79 @@ class FigDialog extends HTMLDialogElement {
1193
1115
  this.#resizeForIframe(iframe, data);
1194
1116
  }
1195
1117
 
1196
- #resizeForIframe(iframe, data) {
1197
- if (typeof data.height !== "number" || !(data.height > 0)) return;
1118
+ #syncAutoResize() {
1119
+ if (this.autoresize) {
1120
+ this.#setupAutoResize();
1121
+ this.#scheduleAutoResize();
1122
+ } else {
1123
+ this.#teardownAutoResize();
1124
+ }
1125
+ }
1198
1126
 
1199
- // Compute the dialog's non-iframe vertical chrome: dialog padding/border
1200
- // plus the laid-out height of every direct child that isn't the iframe
1201
- // (header, footer, gaps between flex children, etc.). We avoid using the
1202
- // iframe's own rect because it may be 0 or stale before/after the resize.
1127
+ #setupAutoResize() {
1128
+ if (!this.#resizeObserver) {
1129
+ this.#resizeObserver = new ResizeObserver(this.#boundContentResize);
1130
+ for (const child of this.children) {
1131
+ try {
1132
+ this.#resizeObserver.observe(child);
1133
+ } catch {}
1134
+ }
1135
+ }
1136
+ if (!this.#mutationObserver) {
1137
+ this.#mutationObserver = new MutationObserver((mutations) => {
1138
+ for (const m of mutations) {
1139
+ m.addedNodes?.forEach((node) => {
1140
+ if (node instanceof Element && node.parentElement === this) {
1141
+ try {
1142
+ this.#resizeObserver?.observe(node);
1143
+ } catch {}
1144
+ }
1145
+ });
1146
+ }
1147
+ this.#scheduleAutoResize();
1148
+ });
1149
+ this.#mutationObserver.observe(this, {
1150
+ childList: true,
1151
+ subtree: true,
1152
+ attributes: true,
1153
+ characterData: true,
1154
+ });
1155
+ }
1156
+ }
1157
+
1158
+ #teardownAutoResize() {
1159
+ if (this.#resizeObserver) {
1160
+ this.#resizeObserver.disconnect();
1161
+ this.#resizeObserver = null;
1162
+ }
1163
+ if (this.#mutationObserver) {
1164
+ this.#mutationObserver.disconnect();
1165
+ this.#mutationObserver = null;
1166
+ }
1167
+ if (this.#autoResizeRafId) {
1168
+ cancelAnimationFrame(this.#autoResizeRafId);
1169
+ this.#autoResizeRafId = 0;
1170
+ }
1171
+ }
1172
+
1173
+ #scheduleAutoResize() {
1174
+ if (!this.autoresize) return;
1175
+ if (this.#autoResizeRafId) return;
1176
+ this.#autoResizeRafId = requestAnimationFrame(() => {
1177
+ this.#autoResizeRafId = 0;
1178
+ this.#applyAutoResize();
1179
+ });
1180
+ }
1181
+
1182
+ #applyAutoResize() {
1183
+ if (!this.autoresize) return;
1184
+ // When an iframe child is present, defer to the iframe's postMessage
1185
+ // broadcast (the only reliable source of its content height).
1186
+ if (this.querySelector(":scope > iframe")) return;
1187
+ this.#resizeToContent(null);
1188
+ }
1189
+
1190
+ #computeChrome(skipChild) {
1203
1191
  const cs = window.getComputedStyle(this);
1204
1192
  const verticalBoxExtras =
1205
1193
  parseFloat(cs.paddingTop || "0") +
@@ -1214,17 +1202,32 @@ class FigDialog extends HTMLDialogElement {
1214
1202
  const rect = child.getBoundingClientRect();
1215
1203
  if (rect.height === 0) continue;
1216
1204
  visibleChildren += 1;
1217
- if (child === iframe) continue;
1218
- siblingsHeight += rect.height;
1205
+ if (child === skipChild) continue;
1206
+ const childCS = window.getComputedStyle(child);
1207
+ const marginY =
1208
+ parseFloat(childCS.marginTop || "0") +
1209
+ parseFloat(childCS.marginBottom || "0");
1210
+ siblingsHeight += rect.height + marginY;
1219
1211
  }
1220
1212
  if (gap && visibleChildren > 1) {
1221
1213
  siblingsHeight += gap * (visibleChildren - 1);
1222
1214
  }
1215
+ return verticalBoxExtras + siblingsHeight;
1216
+ }
1223
1217
 
1224
- const chrome = verticalBoxExtras + siblingsHeight;
1218
+ #resizeForIframe(iframe, data) {
1219
+ if (typeof data.height !== "number" || !(data.height > 0)) return;
1220
+ const chrome = this.#computeChrome(iframe);
1225
1221
  this.style.height = `${Math.ceil(data.height + chrome)}px`;
1226
1222
  }
1227
1223
 
1224
+ #resizeToContent() {
1225
+ // Let CSS handle the sizing via `height: max-content` (applied by the
1226
+ // [autoresize] rule). Just clear any previously applied inline height
1227
+ // (e.g. from drag/resize) so the CSS rule wins.
1228
+ if (this.style.height) this.style.height = "";
1229
+ }
1230
+
1228
1231
  #ensureHeader() {
1229
1232
  if (this.querySelector("fig-header[dialog-header]")) return;
1230
1233
  const header = document.createElement("fig-header");
@@ -1659,6 +1662,20 @@ class FigPopup extends HTMLDialogElement {
1659
1662
 
1660
1663
  connectedCallback() {
1661
1664
  this.ensureInitialized();
1665
+ if (this.getAttribute("variant") === "tooltip") {
1666
+ if (!this.hasAttribute("position")) {
1667
+ this.setAttribute("position", "top center");
1668
+ }
1669
+ if (!this.hasAttribute("offset")) {
1670
+ this.setAttribute("offset", "8 8");
1671
+ }
1672
+ if (!this.hasAttribute("viewport-margin")) {
1673
+ this.setAttribute("viewport-margin", "8");
1674
+ }
1675
+ if (!this.hasAttribute("theme")) {
1676
+ this.setAttribute("theme", "menu");
1677
+ }
1678
+ }
1662
1679
  if (!this.hasAttribute("position")) {
1663
1680
  this.setAttribute("position", "top center");
1664
1681
  }
@@ -1745,7 +1762,21 @@ class FigPopup extends HTMLDialogElement {
1745
1762
  this.style.margin = "0";
1746
1763
  this.style.zIndex = String(figGetHighestZIndex() + 1);
1747
1764
 
1748
- if (!super.open) {
1765
+ // When the popup opts into the native popover API, prefer showPopover()
1766
+ // so the element is promoted into the browser's top layer (above any
1767
+ // modal dialogs) without needing showModal().
1768
+ const usePopover =
1769
+ this.hasAttribute("popover") &&
1770
+ typeof this.showPopover === "function" &&
1771
+ !this.matches?.(":popover-open");
1772
+ if (usePopover) {
1773
+ try {
1774
+ this.showPopover();
1775
+ } catch (e) {
1776
+ // Fall back to non-modal dialog show below.
1777
+ }
1778
+ }
1779
+ if (!usePopover && !super.open) {
1749
1780
  try {
1750
1781
  this.show();
1751
1782
  } catch (e) {
@@ -1780,6 +1811,17 @@ class FigPopup extends HTMLDialogElement {
1780
1811
  true,
1781
1812
  );
1782
1813
 
1814
+ if (
1815
+ this.hasAttribute("popover") &&
1816
+ typeof this.hidePopover === "function" &&
1817
+ this.matches?.(":popover-open")
1818
+ ) {
1819
+ try {
1820
+ this.hidePopover();
1821
+ } catch (e) {
1822
+ // Ignore.
1823
+ }
1824
+ }
1783
1825
  if (super.open) {
1784
1826
  try {
1785
1827
  this.close();
@@ -2351,8 +2393,10 @@ class FigPopup extends HTMLDialogElement {
2351
2393
  }
2352
2394
 
2353
2395
  updatePopoverBeak(anchorRect, popupRect, left, top, placementSide) {
2354
- if (this.getAttribute("variant") !== "popover" || !anchorRect) {
2355
- this.style.removeProperty("--beak-offset");
2396
+ const variant = this.getAttribute("variant");
2397
+ const beakVariants = variant === "popover" || variant === "tooltip";
2398
+ if (!beakVariants || !anchorRect) {
2399
+ this.style.removeProperty("--fig-popup-beak-offset");
2356
2400
  this.removeAttribute("data-beak-side");
2357
2401
  return;
2358
2402
  }
@@ -2385,7 +2429,7 @@ class FigPopup extends HTMLDialogElement {
2385
2429
  beakOffset = Math.min(max, Math.max(min, beakOffset));
2386
2430
  }
2387
2431
 
2388
- this.style.setProperty("--beak-offset", `${beakOffset}px`);
2432
+ this.style.setProperty("--fig-popup-beak-offset", `${beakOffset}px`);
2389
2433
  }
2390
2434
 
2391
2435
  overflowScore(coords, popupRect, m) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "4.7.0",
3
+ "version": "4.8.0",
4
4
  "description": "A lightweight web components library for building Figma plugin and widget UIs with native look and feel",
5
5
  "author": "Rogie King",
6
6
  "license": "MIT",