@rogieking/figui3 6.6.5 → 6.7.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
@@ -747,7 +747,9 @@ customElements.define("fig-dropdown", FigDropdown);
747
747
  */
748
748
  class FigTooltip extends HTMLElement {
749
749
  static #lastShownAt = 0;
750
- static #warmupWindow = 500;
750
+ static #lastHiddenAt = 0;
751
+ static #warmupWindow = 1000;
752
+ static #hoverOpen = null;
751
753
 
752
754
  #boundHideOnChromeOpen;
753
755
  #boundHidePopupOutsideClick;
@@ -760,6 +762,8 @@ class FigTooltip extends HTMLElement {
760
762
  #boundHandleDialogClose;
761
763
  #boundHandleEscape;
762
764
  #parentDialog = null;
765
+ #triggerEl = null;
766
+ #childObserver = null;
763
767
  #touchTimeout;
764
768
  #isTouching = false;
765
769
  constructor() {
@@ -785,6 +789,7 @@ class FigTooltip extends HTMLElement {
785
789
  }
786
790
  connectedCallback() {
787
791
  this.setup();
792
+ this.#bindTriggerListeners();
788
793
  this.setupEventListeners();
789
794
  this.#parentDialog = this.closest("dialog");
790
795
  if (this.#parentDialog) {
@@ -795,6 +800,8 @@ class FigTooltip extends HTMLElement {
795
800
  disconnectedCallback() {
796
801
  clearTimeout(this.timeout);
797
802
  this.destroy();
803
+ this.#unbindTriggerListeners();
804
+ this.#teardownChildObserver();
798
805
  document.removeEventListener(
799
806
  "mousedown",
800
807
  this.#boundHideOnChromeOpen,
@@ -814,17 +821,77 @@ class FigTooltip extends HTMLElement {
814
821
  }
815
822
 
816
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;
855
+ if (this.action === "hover") {
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
+ });
872
+ } else if (this.action === "click") {
873
+ trigger.addEventListener("click", this.#boundShowDelayedPopup);
874
+ trigger.addEventListener("touchstart", this.#boundShowDelayedPopup, {
875
+ passive: true,
876
+ });
877
+ }
878
+ }
879
+
880
+ #unbindTriggerListeners() {
881
+ const trigger = this.#triggerEl;
882
+ if (!trigger) return;
817
883
  if (this.action === "hover") {
818
- this.removeEventListener("pointerenter", this.#boundShowDelayedPopup);
819
- this.removeEventListener("pointerleave", this.#boundHandlePointerLeave);
820
- this.removeEventListener("touchstart", this.#boundHandleTouchStart);
821
- this.removeEventListener("touchmove", this.#boundHandleTouchMove);
822
- this.removeEventListener("touchend", this.#boundHandleTouchEnd);
823
- this.removeEventListener("touchcancel", this.#boundHandleTouchCancel);
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);
824
890
  } else if (this.action === "click") {
825
- this.removeEventListener("click", this.#boundShowDelayedPopup);
826
- this.removeEventListener("touchstart", this.#boundShowDelayedPopup);
891
+ trigger.removeEventListener("click", this.#boundShowDelayedPopup);
892
+ trigger.removeEventListener("touchstart", this.#boundShowDelayedPopup);
827
893
  }
894
+ this.#triggerEl = null;
828
895
  }
829
896
 
830
897
  setup() {
@@ -889,6 +956,7 @@ class FigTooltip extends HTMLElement {
889
956
 
890
957
  destroy() {
891
958
  if (this.popup) {
959
+ this.popup.hidePopup?.();
892
960
  this.popup.remove();
893
961
  this.popup = null;
894
962
  }
@@ -909,30 +977,8 @@ class FigTooltip extends HTMLElement {
909
977
  }
910
978
 
911
979
  setupEventListeners() {
912
- if (this.action === "manual") return;
913
- if (this.action === "hover") {
914
- if (!this.isTouchDevice()) {
915
- this.addEventListener("pointerenter", this.#boundShowDelayedPopup);
916
- this.addEventListener("pointerleave", this.#boundHandlePointerLeave);
917
- }
918
- this.addEventListener("touchstart", this.#boundHandleTouchStart, {
919
- passive: true,
920
- });
921
- this.addEventListener("touchmove", this.#boundHandleTouchMove, {
922
- passive: true,
923
- });
924
- this.addEventListener("touchend", this.#boundHandleTouchEnd, {
925
- passive: true,
926
- });
927
- this.addEventListener("touchcancel", this.#boundHandleTouchCancel, {
928
- passive: true,
929
- });
930
- } else if (this.action === "click") {
931
- this.addEventListener("click", this.#boundShowDelayedPopup);
980
+ if (this.action === "click") {
932
981
  document.body.addEventListener("click", this.#boundHidePopupOutsideClick);
933
- this.addEventListener("touchstart", this.#boundShowDelayedPopup, {
934
- passive: true,
935
- });
936
982
  }
937
983
 
938
984
  document.addEventListener("mousedown", this.#boundHideOnChromeOpen, true);
@@ -943,19 +989,41 @@ class FigTooltip extends HTMLElement {
943
989
  return this.hasAttribute("show") && this.getAttribute("show") !== "false";
944
990
  }
945
991
 
992
+ #isWarmSession() {
993
+ const now = Date.now();
994
+ const windowMs = FigTooltip.#warmupWindow;
995
+ if (this.action === "hover" && FigTooltip.#hoverOpen) return true;
996
+ if (FigTooltip.#lastShownAt && now - FigTooltip.#lastShownAt < windowMs)
997
+ return true;
998
+ if (FigTooltip.#lastHiddenAt && now - FigTooltip.#lastHiddenAt < windowMs)
999
+ return true;
1000
+ return false;
1001
+ }
1002
+
946
1003
  showDelayedPopup() {
947
1004
  if (this.#showPersisted) return;
948
- this.render();
949
1005
  clearTimeout(this.timeout);
950
- const warm =
951
- Date.now() - FigTooltip.#lastShownAt < FigTooltip.#warmupWindow;
952
- const effectiveDelay = warm ? 0 : this.delay;
953
- this.timeout = setTimeout(this.showPopup.bind(this), effectiveDelay);
1006
+ if (this.#isWarmSession()) {
1007
+ this.render();
1008
+ this.showPopup();
1009
+ return;
1010
+ }
1011
+ this.timeout = setTimeout(() => {
1012
+ this.render();
1013
+ this.showPopup();
1014
+ }, this.delay);
954
1015
  }
955
1016
 
956
1017
  showPopup() {
957
1018
  if (this.#parentDialog && !this.#parentDialog.open) return;
958
1019
  if (!this.firstElementChild) return;
1020
+ if (
1021
+ this.action === "hover" &&
1022
+ FigTooltip.#hoverOpen &&
1023
+ FigTooltip.#hoverOpen !== this
1024
+ ) {
1025
+ FigTooltip.#hoverOpen.hidePopup();
1026
+ }
959
1027
  if (!this.popup) this.render();
960
1028
  // Keep anchor in sync in case the trigger child was swapped between
961
1029
  // creation and show.
@@ -963,6 +1031,7 @@ class FigTooltip extends HTMLElement {
963
1031
  this.popup.open = true;
964
1032
 
965
1033
  this.isOpen = true;
1034
+ if (this.action === "hover") FigTooltip.#hoverOpen = this;
966
1035
  FigTooltip.#lastShownAt = Date.now();
967
1036
  }
968
1037
 
@@ -970,12 +1039,14 @@ class FigTooltip extends HTMLElement {
970
1039
  if (this.#showPersisted) return;
971
1040
  clearTimeout(this.timeout);
972
1041
  clearTimeout(this.#touchTimeout);
1042
+ const wasShowing = this.isOpen;
973
1043
  if (this.popup) {
974
1044
  this.destroy();
975
1045
  }
976
1046
 
977
1047
  this.isOpen = false;
978
- FigTooltip.#lastShownAt = Date.now();
1048
+ if (FigTooltip.#hoverOpen === this) FigTooltip.#hoverOpen = null;
1049
+ if (wasShowing) FigTooltip.#lastHiddenAt = Date.now();
979
1050
  }
980
1051
 
981
1052
  hidePopupOutsideClick(event) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "6.6.5",
3
+ "version": "6.7.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",