TooltipManager.js 5.7 KB

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