@rogieking/figui3 4.7.1 → 4.8.1
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/components.css +82 -130
- package/dist/components.css +1 -1
- package/dist/fig.css +1 -1
- package/dist/fig.js +32 -32
- package/fig.js +227 -183
- package/package.json +1 -1
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}
|
|
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
|
-
|
|
597
|
-
|
|
598
|
-
|
|
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
|
-
//
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
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
|
-
|
|
626
|
-
|
|
627
|
-
|
|
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
|
-
|
|
719
|
-
|
|
720
|
-
this
|
|
721
|
-
this.popup.
|
|
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
|
|
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
|
|
982
|
-
|
|
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.
|
|
985
|
-
popup.
|
|
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
|
-
|
|
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
|
-
|
|
998
|
-
|
|
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
|
-
#
|
|
1197
|
-
if (
|
|
1118
|
+
#syncAutoResize() {
|
|
1119
|
+
if (this.autoresize) {
|
|
1120
|
+
this.#setupAutoResize();
|
|
1121
|
+
this.#scheduleAutoResize();
|
|
1122
|
+
} else {
|
|
1123
|
+
this.#teardownAutoResize();
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1198
1126
|
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
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 ===
|
|
1218
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2355
|
-
|
|
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