@rogieking/figui3 6.6.4 → 6.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components.css +12 -0
- package/dist/components.css +1 -1
- package/dist/fig.css +1 -1
- package/dist/fig.js +19 -19
- package/fig.js +144 -64
- package/package.json +1 -1
package/fig.js
CHANGED
|
@@ -164,6 +164,19 @@ function figUniqueId() {
|
|
|
164
164
|
return Date.now().toString(36) + Math.random().toString(36).substring(2);
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
/** Zero-size portal for fixed overlays so they never affect body layout metrics. */
|
|
168
|
+
function figGetOverlayRoot() {
|
|
169
|
+
if (!document.body) return null;
|
|
170
|
+
const attr = "data-figui-overlay-root";
|
|
171
|
+
let root = document.body.querySelector(`:scope > [${attr}]`);
|
|
172
|
+
if (!root) {
|
|
173
|
+
root = document.createElement("div");
|
|
174
|
+
root.setAttribute(attr, "");
|
|
175
|
+
document.body.append(root);
|
|
176
|
+
}
|
|
177
|
+
return root;
|
|
178
|
+
}
|
|
179
|
+
|
|
167
180
|
let _figZCounter = 10000;
|
|
168
181
|
function figGetHighestZIndex() {
|
|
169
182
|
return _figZCounter++;
|
|
@@ -734,7 +747,9 @@ customElements.define("fig-dropdown", FigDropdown);
|
|
|
734
747
|
*/
|
|
735
748
|
class FigTooltip extends HTMLElement {
|
|
736
749
|
static #lastShownAt = 0;
|
|
750
|
+
static #lastShownInstance = null;
|
|
737
751
|
static #warmupWindow = 500;
|
|
752
|
+
static #hoverOpen = null;
|
|
738
753
|
|
|
739
754
|
#boundHideOnChromeOpen;
|
|
740
755
|
#boundHidePopupOutsideClick;
|
|
@@ -747,6 +762,8 @@ class FigTooltip extends HTMLElement {
|
|
|
747
762
|
#boundHandleDialogClose;
|
|
748
763
|
#boundHandleEscape;
|
|
749
764
|
#parentDialog = null;
|
|
765
|
+
#triggerEl = null;
|
|
766
|
+
#childObserver = null;
|
|
750
767
|
#touchTimeout;
|
|
751
768
|
#isTouching = false;
|
|
752
769
|
constructor() {
|
|
@@ -772,6 +789,7 @@ class FigTooltip extends HTMLElement {
|
|
|
772
789
|
}
|
|
773
790
|
connectedCallback() {
|
|
774
791
|
this.setup();
|
|
792
|
+
this.#bindTriggerListeners();
|
|
775
793
|
this.setupEventListeners();
|
|
776
794
|
this.#parentDialog = this.closest("dialog");
|
|
777
795
|
if (this.#parentDialog) {
|
|
@@ -782,6 +800,8 @@ class FigTooltip extends HTMLElement {
|
|
|
782
800
|
disconnectedCallback() {
|
|
783
801
|
clearTimeout(this.timeout);
|
|
784
802
|
this.destroy();
|
|
803
|
+
this.#unbindTriggerListeners();
|
|
804
|
+
this.#teardownChildObserver();
|
|
785
805
|
document.removeEventListener(
|
|
786
806
|
"mousedown",
|
|
787
807
|
this.#boundHideOnChromeOpen,
|
|
@@ -801,19 +821,79 @@ class FigTooltip extends HTMLElement {
|
|
|
801
821
|
}
|
|
802
822
|
|
|
803
823
|
clearTimeout(this.#touchTimeout);
|
|
824
|
+
if (FigTooltip.#hoverOpen === this) FigTooltip.#hoverOpen = null;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
#getTrigger() {
|
|
828
|
+
return this.firstElementChild;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
#teardownChildObserver() {
|
|
832
|
+
this.#childObserver?.disconnect();
|
|
833
|
+
this.#childObserver = null;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
#bindTriggerListeners() {
|
|
837
|
+
this.#unbindTriggerListeners();
|
|
838
|
+
if (this.action === "manual") return;
|
|
839
|
+
|
|
840
|
+
const trigger = this.#getTrigger();
|
|
841
|
+
if (!trigger) {
|
|
842
|
+
if (!this.#childObserver && typeof MutationObserver !== "undefined") {
|
|
843
|
+
this.#childObserver = new MutationObserver(() => {
|
|
844
|
+
if (this.#getTrigger()) {
|
|
845
|
+
this.#teardownChildObserver();
|
|
846
|
+
this.#bindTriggerListeners();
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
this.#childObserver.observe(this, { childList: true });
|
|
850
|
+
}
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
this.#triggerEl = trigger;
|
|
804
855
|
if (this.action === "hover") {
|
|
805
|
-
this.
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
856
|
+
if (!this.isTouchDevice()) {
|
|
857
|
+
trigger.addEventListener("pointerenter", this.#boundShowDelayedPopup);
|
|
858
|
+
trigger.addEventListener("pointerleave", this.#boundHandlePointerLeave);
|
|
859
|
+
}
|
|
860
|
+
trigger.addEventListener("touchstart", this.#boundHandleTouchStart, {
|
|
861
|
+
passive: true,
|
|
862
|
+
});
|
|
863
|
+
trigger.addEventListener("touchmove", this.#boundHandleTouchMove, {
|
|
864
|
+
passive: true,
|
|
865
|
+
});
|
|
866
|
+
trigger.addEventListener("touchend", this.#boundHandleTouchEnd, {
|
|
867
|
+
passive: true,
|
|
868
|
+
});
|
|
869
|
+
trigger.addEventListener("touchcancel", this.#boundHandleTouchCancel, {
|
|
870
|
+
passive: true,
|
|
871
|
+
});
|
|
811
872
|
} else if (this.action === "click") {
|
|
812
|
-
|
|
813
|
-
|
|
873
|
+
trigger.addEventListener("click", this.#boundShowDelayedPopup);
|
|
874
|
+
trigger.addEventListener("touchstart", this.#boundShowDelayedPopup, {
|
|
875
|
+
passive: true,
|
|
876
|
+
});
|
|
814
877
|
}
|
|
815
878
|
}
|
|
816
879
|
|
|
880
|
+
#unbindTriggerListeners() {
|
|
881
|
+
const trigger = this.#triggerEl;
|
|
882
|
+
if (!trigger) return;
|
|
883
|
+
if (this.action === "hover") {
|
|
884
|
+
trigger.removeEventListener("pointerenter", this.#boundShowDelayedPopup);
|
|
885
|
+
trigger.removeEventListener("pointerleave", this.#boundHandlePointerLeave);
|
|
886
|
+
trigger.removeEventListener("touchstart", this.#boundHandleTouchStart);
|
|
887
|
+
trigger.removeEventListener("touchmove", this.#boundHandleTouchMove);
|
|
888
|
+
trigger.removeEventListener("touchend", this.#boundHandleTouchEnd);
|
|
889
|
+
trigger.removeEventListener("touchcancel", this.#boundHandleTouchCancel);
|
|
890
|
+
} else if (this.action === "click") {
|
|
891
|
+
trigger.removeEventListener("click", this.#boundShowDelayedPopup);
|
|
892
|
+
trigger.removeEventListener("touchstart", this.#boundShowDelayedPopup);
|
|
893
|
+
}
|
|
894
|
+
this.#triggerEl = null;
|
|
895
|
+
}
|
|
896
|
+
|
|
817
897
|
setup() {
|
|
818
898
|
this.style.display = "contents";
|
|
819
899
|
}
|
|
@@ -859,13 +939,13 @@ class FigTooltip extends HTMLElement {
|
|
|
859
939
|
// - Without popover support, fall back to today's behavior: nearest open
|
|
860
940
|
// <dialog> ancestor if present, else document.body.
|
|
861
941
|
if (supportsPopover) {
|
|
862
|
-
document.body.append(this.popup);
|
|
942
|
+
(figGetOverlayRoot() ?? document.body).append(this.popup);
|
|
863
943
|
} else {
|
|
864
944
|
const parentDialog = this.closest("dialog");
|
|
865
945
|
if (parentDialog && parentDialog.open) {
|
|
866
946
|
parentDialog.append(this.popup);
|
|
867
947
|
} else {
|
|
868
|
-
document.body.append(this.popup);
|
|
948
|
+
(figGetOverlayRoot() ?? document.body).append(this.popup);
|
|
869
949
|
}
|
|
870
950
|
}
|
|
871
951
|
|
|
@@ -876,6 +956,7 @@ class FigTooltip extends HTMLElement {
|
|
|
876
956
|
|
|
877
957
|
destroy() {
|
|
878
958
|
if (this.popup) {
|
|
959
|
+
this.popup.hidePopup?.();
|
|
879
960
|
this.popup.remove();
|
|
880
961
|
this.popup = null;
|
|
881
962
|
}
|
|
@@ -896,30 +977,8 @@ class FigTooltip extends HTMLElement {
|
|
|
896
977
|
}
|
|
897
978
|
|
|
898
979
|
setupEventListeners() {
|
|
899
|
-
if (this.action === "
|
|
900
|
-
if (this.action === "hover") {
|
|
901
|
-
if (!this.isTouchDevice()) {
|
|
902
|
-
this.addEventListener("pointerenter", this.#boundShowDelayedPopup);
|
|
903
|
-
this.addEventListener("pointerleave", this.#boundHandlePointerLeave);
|
|
904
|
-
}
|
|
905
|
-
this.addEventListener("touchstart", this.#boundHandleTouchStart, {
|
|
906
|
-
passive: true,
|
|
907
|
-
});
|
|
908
|
-
this.addEventListener("touchmove", this.#boundHandleTouchMove, {
|
|
909
|
-
passive: true,
|
|
910
|
-
});
|
|
911
|
-
this.addEventListener("touchend", this.#boundHandleTouchEnd, {
|
|
912
|
-
passive: true,
|
|
913
|
-
});
|
|
914
|
-
this.addEventListener("touchcancel", this.#boundHandleTouchCancel, {
|
|
915
|
-
passive: true,
|
|
916
|
-
});
|
|
917
|
-
} else if (this.action === "click") {
|
|
918
|
-
this.addEventListener("click", this.#boundShowDelayedPopup);
|
|
980
|
+
if (this.action === "click") {
|
|
919
981
|
document.body.addEventListener("click", this.#boundHidePopupOutsideClick);
|
|
920
|
-
this.addEventListener("touchstart", this.#boundShowDelayedPopup, {
|
|
921
|
-
passive: true,
|
|
922
|
-
});
|
|
923
982
|
}
|
|
924
983
|
|
|
925
984
|
document.addEventListener("mousedown", this.#boundHideOnChromeOpen, true);
|
|
@@ -932,17 +991,27 @@ class FigTooltip extends HTMLElement {
|
|
|
932
991
|
|
|
933
992
|
showDelayedPopup() {
|
|
934
993
|
if (this.#showPersisted) return;
|
|
935
|
-
this.render();
|
|
936
994
|
clearTimeout(this.timeout);
|
|
937
995
|
const warm =
|
|
996
|
+
FigTooltip.#lastShownInstance === this &&
|
|
938
997
|
Date.now() - FigTooltip.#lastShownAt < FigTooltip.#warmupWindow;
|
|
939
998
|
const effectiveDelay = warm ? 0 : this.delay;
|
|
940
|
-
this.timeout = setTimeout(
|
|
999
|
+
this.timeout = setTimeout(() => {
|
|
1000
|
+
this.render();
|
|
1001
|
+
this.showPopup();
|
|
1002
|
+
}, effectiveDelay);
|
|
941
1003
|
}
|
|
942
1004
|
|
|
943
1005
|
showPopup() {
|
|
944
1006
|
if (this.#parentDialog && !this.#parentDialog.open) return;
|
|
945
1007
|
if (!this.firstElementChild) return;
|
|
1008
|
+
if (
|
|
1009
|
+
this.action === "hover" &&
|
|
1010
|
+
FigTooltip.#hoverOpen &&
|
|
1011
|
+
FigTooltip.#hoverOpen !== this
|
|
1012
|
+
) {
|
|
1013
|
+
FigTooltip.#hoverOpen.hidePopup();
|
|
1014
|
+
}
|
|
946
1015
|
if (!this.popup) this.render();
|
|
947
1016
|
// Keep anchor in sync in case the trigger child was swapped between
|
|
948
1017
|
// creation and show.
|
|
@@ -950,7 +1019,9 @@ class FigTooltip extends HTMLElement {
|
|
|
950
1019
|
this.popup.open = true;
|
|
951
1020
|
|
|
952
1021
|
this.isOpen = true;
|
|
1022
|
+
if (this.action === "hover") FigTooltip.#hoverOpen = this;
|
|
953
1023
|
FigTooltip.#lastShownAt = Date.now();
|
|
1024
|
+
FigTooltip.#lastShownInstance = this;
|
|
954
1025
|
}
|
|
955
1026
|
|
|
956
1027
|
hidePopup() {
|
|
@@ -962,7 +1033,7 @@ class FigTooltip extends HTMLElement {
|
|
|
962
1033
|
}
|
|
963
1034
|
|
|
964
1035
|
this.isOpen = false;
|
|
965
|
-
FigTooltip.#
|
|
1036
|
+
if (FigTooltip.#hoverOpen === this) FigTooltip.#hoverOpen = null;
|
|
966
1037
|
}
|
|
967
1038
|
|
|
968
1039
|
hidePopupOutsideClick(event) {
|
|
@@ -1142,13 +1213,13 @@ class FigTooltip extends HTMLElement {
|
|
|
1142
1213
|
popup.append(content);
|
|
1143
1214
|
|
|
1144
1215
|
if (supportsPopover) {
|
|
1145
|
-
document.body.append(popup);
|
|
1216
|
+
(figGetOverlayRoot() ?? document.body).append(popup);
|
|
1146
1217
|
} else {
|
|
1147
1218
|
const parentDialog = anchor.closest?.("dialog");
|
|
1148
1219
|
if (parentDialog && parentDialog.open) {
|
|
1149
1220
|
parentDialog.append(popup);
|
|
1150
1221
|
} else {
|
|
1151
|
-
document.body.append(popup);
|
|
1222
|
+
(figGetOverlayRoot() ?? document.body).append(popup);
|
|
1152
1223
|
}
|
|
1153
1224
|
}
|
|
1154
1225
|
|
|
@@ -3026,23 +3097,6 @@ class FigPopup extends HTMLDialogElement {
|
|
|
3026
3097
|
);
|
|
3027
3098
|
}
|
|
3028
3099
|
|
|
3029
|
-
updatePointerVisibility(anchorRect, popupRect, left, top, placementSide) {
|
|
3030
|
-
if (!this.tracksAnchorBeak()) return;
|
|
3031
|
-
if (this.getAttribute("pointer") === "false") return;
|
|
3032
|
-
|
|
3033
|
-
if (
|
|
3034
|
-
anchorRect &&
|
|
3035
|
-
!this.canPointAtAnchor(anchorRect, popupRect, left, top, placementSide)
|
|
3036
|
-
) {
|
|
3037
|
-
this.setAttribute("pointer", "false");
|
|
3038
|
-
return;
|
|
3039
|
-
}
|
|
3040
|
-
|
|
3041
|
-
if (this.hasAttribute("pointer")) {
|
|
3042
|
-
this.removeAttribute("pointer");
|
|
3043
|
-
}
|
|
3044
|
-
}
|
|
3045
|
-
|
|
3046
3100
|
updatePopoverBeak(anchorRect, popupRect, left, top, placementSide) {
|
|
3047
3101
|
if (!this.tracksAnchorBeak() || !anchorRect) {
|
|
3048
3102
|
this.style.removeProperty("--fig-popup-beak-offset");
|
|
@@ -3110,6 +3164,23 @@ class FigPopup extends HTMLDialogElement {
|
|
|
3110
3164
|
return this.clampToViewport(coords, popupRect, m);
|
|
3111
3165
|
}
|
|
3112
3166
|
|
|
3167
|
+
primaryAxisOverflowPenalty(coords, popupRect, m, placementSide) {
|
|
3168
|
+
const bounds = this.getViewportBounds(m);
|
|
3169
|
+
let overflow = 0;
|
|
3170
|
+
|
|
3171
|
+
if (placementSide === "top") {
|
|
3172
|
+
overflow = Math.max(0, bounds.minTop - coords.top);
|
|
3173
|
+
} else if (placementSide === "bottom") {
|
|
3174
|
+
overflow = Math.max(0, coords.top + popupRect.height - bounds.maxBottom);
|
|
3175
|
+
} else if (placementSide === "left") {
|
|
3176
|
+
overflow = Math.max(0, bounds.minLeft - coords.left);
|
|
3177
|
+
} else if (placementSide === "right") {
|
|
3178
|
+
overflow = Math.max(0, coords.left + popupRect.width - bounds.maxRight);
|
|
3179
|
+
}
|
|
3180
|
+
|
|
3181
|
+
return overflow > 0 ? 1000 + overflow : 0;
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3113
3184
|
placementScore(anchorRect, popupRect, coords, placementSide, m) {
|
|
3114
3185
|
const resolved = this.resolveCoordsAtViewport(
|
|
3115
3186
|
anchorRect,
|
|
@@ -3119,6 +3190,12 @@ class FigPopup extends HTMLDialogElement {
|
|
|
3119
3190
|
m,
|
|
3120
3191
|
);
|
|
3121
3192
|
let score = this.overflowScore(resolved, popupRect, m);
|
|
3193
|
+
score += this.primaryAxisOverflowPenalty(
|
|
3194
|
+
coords,
|
|
3195
|
+
popupRect,
|
|
3196
|
+
m,
|
|
3197
|
+
placementSide,
|
|
3198
|
+
);
|
|
3122
3199
|
if (
|
|
3123
3200
|
anchorRect &&
|
|
3124
3201
|
!this.canPointAtAnchor(
|
|
@@ -3150,13 +3227,6 @@ class FigPopup extends HTMLDialogElement {
|
|
|
3150
3227
|
);
|
|
3151
3228
|
this.style.left = `${resolved.left}px`;
|
|
3152
3229
|
this.style.top = `${resolved.top}px`;
|
|
3153
|
-
this.updatePointerVisibility(
|
|
3154
|
-
anchorRect,
|
|
3155
|
-
popupRect,
|
|
3156
|
-
resolved.left,
|
|
3157
|
-
resolved.top,
|
|
3158
|
-
placementSide,
|
|
3159
|
-
);
|
|
3160
3230
|
this.updatePopoverBeak(
|
|
3161
3231
|
anchorRect,
|
|
3162
3232
|
popupRect,
|
|
@@ -3167,7 +3237,7 @@ class FigPopup extends HTMLDialogElement {
|
|
|
3167
3237
|
}
|
|
3168
3238
|
|
|
3169
3239
|
positionPopup() {
|
|
3170
|
-
if (!this.open
|
|
3240
|
+
if (!this.open) return;
|
|
3171
3241
|
|
|
3172
3242
|
const popupRect = this.getBoundingClientRect();
|
|
3173
3243
|
const offset = this.parseOffset();
|
|
@@ -3259,7 +3329,9 @@ class FigPopup extends HTMLDialogElement {
|
|
|
3259
3329
|
}
|
|
3260
3330
|
|
|
3261
3331
|
queueReposition() {
|
|
3262
|
-
if (!this.open || !this.shouldAutoReposition())
|
|
3332
|
+
if (!this.open || !this.isPopupDisplayed() || !this.shouldAutoReposition()) {
|
|
3333
|
+
return;
|
|
3334
|
+
}
|
|
3263
3335
|
if (this._rafId !== null) return;
|
|
3264
3336
|
|
|
3265
3337
|
this._rafId = requestAnimationFrame(() => {
|
|
@@ -3272,6 +3344,14 @@ class FigPopup extends HTMLDialogElement {
|
|
|
3272
3344
|
if (!(this.drag && this._wasDragged)) return true;
|
|
3273
3345
|
return !this.resolveAnchor();
|
|
3274
3346
|
}
|
|
3347
|
+
|
|
3348
|
+
isPopupDisplayed() {
|
|
3349
|
+
return Boolean(
|
|
3350
|
+
this._isPopupActive ||
|
|
3351
|
+
this.matches?.(":open") ||
|
|
3352
|
+
this.matches?.(":popover-open"),
|
|
3353
|
+
);
|
|
3354
|
+
}
|
|
3275
3355
|
}
|
|
3276
3356
|
figDefineCustomizedBuiltIn("fig-popup", FigPopup, { extends: "dialog" });
|
|
3277
3357
|
|
package/package.json
CHANGED