TooltipManager.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. /*!
  2. * All material copyright ESRI, All Rights Reserved, unless otherwise specified.
  3. * See https://github.com/Esri/calcite-components/blob/master/LICENSE.md for details.
  4. * v1.0.0-beta.82
  5. */
  6. import { TOOLTIP_DELAY_MS } from "./resources";
  7. export default class TooltipManager {
  8. constructor() {
  9. // --------------------------------------------------------------------------
  10. //
  11. // Private Properties
  12. //
  13. // --------------------------------------------------------------------------
  14. this.registeredElements = new WeakMap();
  15. this.hoverTimeouts = new WeakMap();
  16. this.registeredElementCount = 0;
  17. // --------------------------------------------------------------------------
  18. //
  19. // Private Methods
  20. //
  21. // --------------------------------------------------------------------------
  22. this.queryTooltip = (composedPath) => {
  23. const { registeredElements } = this;
  24. const registeredElement = composedPath.find((pathEl) => registeredElements.has(pathEl));
  25. return registeredElements.get(registeredElement);
  26. };
  27. this.keyDownHandler = (event) => {
  28. if (event.key === "Escape") {
  29. const { activeTooltipEl } = this;
  30. if (activeTooltipEl) {
  31. this.clearHoverTimeout(activeTooltipEl);
  32. this.toggleTooltip(activeTooltipEl, false);
  33. }
  34. }
  35. };
  36. this.mouseEnterShow = (event) => {
  37. this.hoverEvent(event, true);
  38. };
  39. this.mouseLeaveHide = (event) => {
  40. this.hoverEvent(event, false);
  41. };
  42. this.clickHandler = (event) => {
  43. this.clickedTooltip = this.queryTooltip(event.composedPath());
  44. };
  45. this.focusShow = (event) => {
  46. this.focusEvent(event, true);
  47. };
  48. this.blurHide = (event) => {
  49. this.focusEvent(event, false);
  50. };
  51. this.hoverToggle = (tooltip, value) => {
  52. const { hoverTimeouts } = this;
  53. hoverTimeouts.delete(tooltip);
  54. if (value) {
  55. this.closeExistingTooltip();
  56. }
  57. this.toggleTooltip(tooltip, value);
  58. };
  59. }
  60. // --------------------------------------------------------------------------
  61. //
  62. // Public Methods
  63. //
  64. // --------------------------------------------------------------------------
  65. registerElement(referenceEl, tooltip) {
  66. this.registeredElementCount++;
  67. this.registeredElements.set(referenceEl, tooltip);
  68. if (this.registeredElementCount === 1) {
  69. this.addListeners();
  70. }
  71. }
  72. unregisterElement(referenceEl) {
  73. if (this.registeredElements.delete(referenceEl)) {
  74. this.registeredElementCount--;
  75. }
  76. if (this.registeredElementCount === 0) {
  77. this.removeListeners();
  78. }
  79. }
  80. addListeners() {
  81. document.addEventListener("keydown", this.keyDownHandler);
  82. document.addEventListener("mouseover", this.mouseEnterShow, { capture: true });
  83. document.addEventListener("mouseout", this.mouseLeaveHide, { capture: true });
  84. document.addEventListener("pointerdown", this.clickHandler, { capture: true });
  85. document.addEventListener("focusin", this.focusShow), { capture: true };
  86. document.addEventListener("focusout", this.blurHide, { capture: true });
  87. }
  88. removeListeners() {
  89. document.removeEventListener("keydown", this.keyDownHandler);
  90. document.removeEventListener("mouseover", this.mouseEnterShow, { capture: true });
  91. document.removeEventListener("mouseout", this.mouseLeaveHide, { capture: true });
  92. document.removeEventListener("pointerdown", this.clickHandler, { capture: true });
  93. document.removeEventListener("focusin", this.focusShow, { capture: true });
  94. document.removeEventListener("focusout", this.blurHide, { capture: true });
  95. }
  96. clearHoverTimeout(tooltip) {
  97. const { hoverTimeouts } = this;
  98. if (hoverTimeouts.has(tooltip)) {
  99. window.clearTimeout(hoverTimeouts.get(tooltip));
  100. hoverTimeouts.delete(tooltip);
  101. }
  102. }
  103. closeExistingTooltip() {
  104. const { activeTooltipEl } = this;
  105. if (activeTooltipEl) {
  106. this.toggleTooltip(activeTooltipEl, false);
  107. }
  108. }
  109. focusTooltip(tooltip, value) {
  110. this.closeExistingTooltip();
  111. if (value) {
  112. this.clearHoverTimeout(tooltip);
  113. }
  114. this.toggleTooltip(tooltip, value);
  115. }
  116. toggleTooltip(tooltip, value) {
  117. tooltip.open = value;
  118. if (value) {
  119. this.activeTooltipEl = tooltip;
  120. }
  121. }
  122. hoverTooltip(tooltip, value) {
  123. this.clearHoverTimeout(tooltip);
  124. const { hoverTimeouts } = this;
  125. const timeoutId = window.setTimeout(() => this.hoverToggle(tooltip, value), TOOLTIP_DELAY_MS || 0);
  126. hoverTimeouts.set(tooltip, timeoutId);
  127. }
  128. activeTooltipHover(event) {
  129. const { activeTooltipEl, hoverTimeouts } = this;
  130. const { type } = event;
  131. if (!activeTooltipEl) {
  132. return;
  133. }
  134. if (type === "mouseover" && event.composedPath().includes(activeTooltipEl)) {
  135. this.clearHoverTimeout(activeTooltipEl);
  136. }
  137. else if (type === "mouseout" && !hoverTimeouts.has(activeTooltipEl)) {
  138. this.hoverTooltip(activeTooltipEl, false);
  139. }
  140. }
  141. hoverEvent(event, value) {
  142. const tooltip = this.queryTooltip(event.composedPath());
  143. this.activeTooltipHover(event);
  144. if (!tooltip) {
  145. return;
  146. }
  147. this.hoverTooltip(tooltip, value);
  148. }
  149. focusEvent(event, value) {
  150. const tooltip = this.queryTooltip(event.composedPath());
  151. if (!tooltip || tooltip === this.clickedTooltip) {
  152. this.clickedTooltip = null;
  153. return;
  154. }
  155. this.focusTooltip(tooltip, value);
  156. }
  157. }