UNPKG

@esri/calcite-components

Version:

Web Components for Esri's Calcite Design System.

216 lines (215 loc) • 7.9 kB
/*! * All material copyright ESRI, All Rights Reserved, unless otherwise specified. * See https://github.com/Esri/calcite-components/blob/master/LICENSE.md for details. * v1.5.0-next.4 */ import { getShadowRootNode, isPrimaryPointerButton } from "../../utils/dom"; import { TOOLTIP_OPEN_DELAY_MS, TOOLTIP_CLOSE_DELAY_MS } from "./resources"; import { getEffectiveReferenceElement } from "./utils"; export default class TooltipManager { constructor() { // -------------------------------------------------------------------------- // // Private Properties // // -------------------------------------------------------------------------- this.registeredElements = new WeakMap(); this.registeredShadowRootCounts = new WeakMap(); this.hoverOpenTimeout = null; this.hoverCloseTimeout = null; this.hoveredTooltip = null; this.clickedTooltip = null; this.activeTooltip = null; this.registeredElementCount = 0; // -------------------------------------------------------------------------- // // Private Methods // // -------------------------------------------------------------------------- this.queryTooltip = (composedPath) => { const { registeredElements } = this; const registeredElement = composedPath.find((pathEl) => registeredElements.has(pathEl)); return registeredElements.get(registeredElement); }; this.keyDownHandler = (event) => { if (event.key === "Escape" && !event.defaultPrevented) { const { activeTooltip } = this; if (activeTooltip?.open) { this.clearHoverTimeout(); this.closeActiveTooltip(); const referenceElement = getEffectiveReferenceElement(activeTooltip); if (referenceElement instanceof Element && referenceElement.contains(event.target)) { event.preventDefault(); } } } }; this.pointerMoveHandler = (event) => { const composedPath = event.composedPath(); const { activeTooltip } = this; const hoveringActiveTooltip = activeTooltip?.open && composedPath.includes(activeTooltip); if (hoveringActiveTooltip) { this.clearHoverTimeout(); return; } const tooltip = this.queryTooltip(composedPath); this.hoveredTooltip = tooltip; if (this.isClosableClickedTooltip(tooltip)) { return; } this.clickedTooltip = null; if (tooltip) { this.openHoveredTooltip(tooltip); } else if (activeTooltip) { this.closeHoveredTooltip(); } }; this.pointerDownHandler = (event) => { if (!isPrimaryPointerButton(event)) { return; } const clickedTooltip = this.queryTooltip(event.composedPath()); this.clickedTooltip = clickedTooltip; if (clickedTooltip?.closeOnClick) { this.toggleTooltip(clickedTooltip, false); this.clearHoverTimeout(); } }; this.focusInHandler = (event) => { this.queryFocusedTooltip(event, true); }; this.focusOutHandler = (event) => { this.queryFocusedTooltip(event, false); }; this.openHoveredTooltip = (tooltip) => { this.hoverOpenTimeout = window.setTimeout(() => { if (this.hoverOpenTimeout === null) { return; } this.clearHoverCloseTimeout(); this.closeActiveTooltip(); if (tooltip !== this.hoveredTooltip) { return; } this.toggleTooltip(tooltip, true); }, this.activeTooltip ? 0 : TOOLTIP_OPEN_DELAY_MS); }; this.closeHoveredTooltip = () => { this.hoverCloseTimeout = window.setTimeout(() => { if (this.hoverCloseTimeout === null) { return; } this.closeActiveTooltip(); }, TOOLTIP_CLOSE_DELAY_MS); }; } // -------------------------------------------------------------------------- // // Public Methods // // -------------------------------------------------------------------------- registerElement(referenceEl, tooltip) { this.registeredElementCount++; this.registeredElements.set(referenceEl, tooltip); const shadowRoot = this.getReferenceElShadowRootNode(referenceEl); if (shadowRoot) { this.registerShadowRoot(shadowRoot); } if (this.registeredElementCount === 1) { this.addListeners(); } } unregisterElement(referenceEl) { const shadowRoot = this.getReferenceElShadowRootNode(referenceEl); if (shadowRoot) { this.unregisterShadowRoot(shadowRoot); } if (this.registeredElements.delete(referenceEl)) { this.registeredElementCount--; } if (this.registeredElementCount === 0) { this.removeListeners(); } } addShadowListeners(shadowRoot) { shadowRoot.addEventListener("focusin", this.focusInHandler, { capture: true }); shadowRoot.addEventListener("focusout", this.focusOutHandler, { capture: true }); } removeShadowListeners(shadowRoot) { shadowRoot.removeEventListener("focusin", this.focusInHandler, { capture: true }); shadowRoot.removeEventListener("focusout", this.focusOutHandler, { capture: true }); } addListeners() { document.addEventListener("keydown", this.keyDownHandler, { capture: true }); document.addEventListener("pointermove", this.pointerMoveHandler, { capture: true }); document.addEventListener("pointerdown", this.pointerDownHandler, { capture: true }); document.addEventListener("focusin", this.focusInHandler, { capture: true }); document.addEventListener("focusout", this.focusOutHandler, { capture: true }); } removeListeners() { document.removeEventListener("keydown", this.keyDownHandler, { capture: true }); document.removeEventListener("pointermove", this.pointerMoveHandler, { capture: true }); document.removeEventListener("pointerdown", this.pointerDownHandler, { capture: true }); document.removeEventListener("focusin", this.focusInHandler, { capture: true }); document.removeEventListener("focusout", this.focusOutHandler, { capture: true }); } clearHoverOpenTimeout() { window.clearTimeout(this.hoverOpenTimeout); this.hoverOpenTimeout = null; } clearHoverCloseTimeout() { window.clearTimeout(this.hoverCloseTimeout); this.hoverCloseTimeout = null; } clearHoverTimeout() { this.clearHoverOpenTimeout(); this.clearHoverCloseTimeout(); } closeActiveTooltip() { const { activeTooltip } = this; if (activeTooltip?.open) { this.toggleTooltip(activeTooltip, false); } } toggleFocusedTooltip(tooltip, open) { this.closeActiveTooltip(); if (open) { this.clearHoverTimeout(); } this.toggleTooltip(tooltip, open); } toggleTooltip(tooltip, open) { tooltip.open = open; this.activeTooltip = open ? tooltip : null; } queryFocusedTooltip(event, open) { const tooltip = this.queryTooltip(event.composedPath()); if (!tooltip || this.isClosableClickedTooltip(tooltip)) { return; } this.toggleFocusedTooltip(tooltip, open); } isClosableClickedTooltip(tooltip) { return tooltip?.closeOnClick && tooltip === this.clickedTooltip; } registerShadowRoot(shadowRoot) { const { registeredShadowRootCounts } = this; const newCount = (registeredShadowRootCounts.get(shadowRoot) ?? 0) + 1; if (newCount === 1) { this.addShadowListeners(shadowRoot); } registeredShadowRootCounts.set(shadowRoot, newCount); } unregisterShadowRoot(shadowRoot) { const { registeredShadowRootCounts } = this; const newCount = registeredShadowRootCounts.get(shadowRoot) - 1; if (newCount === 0) { this.removeShadowListeners(shadowRoot); } registeredShadowRootCounts.set(shadowRoot, newCount); } getReferenceElShadowRootNode(referenceEl) { return referenceEl instanceof Element ? getShadowRootNode(referenceEl) : null; } }