diff --git a/src/events.ts b/src/events.ts index ae643f7..3ecd556 100644 --- a/src/events.ts +++ b/src/events.ts @@ -64,6 +64,37 @@ function onKeyup(e: KeyboardEvent) { } } +type EventCallback = (pointer: MouseEvent | PointerEvent) => void +type EventHandlers = { + [key: string]: { + clickedListener?: EventCallback, + disabledlistener?: EventCallback + } +} +const driverEventHandlers: EventHandlers = {} + +const listenerWrapper = ( + element?: Element, + listener?: EventCallback, + shouldPreventDefault?: (target: HTMLElement) => boolean +) => { + return function (e: MouseEvent | PointerEvent) { + // event and extra_data will be available here + const target = e.target as HTMLElement; + if (!element?.contains(target)) { + return; + } + + if (!shouldPreventDefault || shouldPreventDefault(target)) { + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); + } + + listener?.(e); + }; +}; + /** * Attaches click handler to the elements created by driver.js. It makes * sure to give the listener the first chance to handle the event, and @@ -76,41 +107,49 @@ function onKeyup(e: KeyboardEvent) { */ export function onDriverClick( element: Element, - listener: (pointer: MouseEvent | PointerEvent) => void, + listener: EventCallback, shouldPreventDefault?: (target: HTMLElement) => boolean ) { - const listenerWrapper = (e: MouseEvent | PointerEvent, listener?: (pointer: MouseEvent | PointerEvent) => void) => { - const target = e.target as HTMLElement; - if (!element.contains(target)) { - return; - } - - if (!shouldPreventDefault || shouldPreventDefault(target)) { - e.preventDefault(); - e.stopPropagation(); - e.stopImmediatePropagation(); - } + // event handlers + const clickedListener = listenerWrapper(element, listener, shouldPreventDefault); + const disabledlistener = listenerWrapper(); + + // save event handlers so we can remove events later + // use the element ID as the object key + // save event handlers as values + driverEventHandlers[element.id] = { clickedListener } + + if (element.classList.contains('driver-popover')) { + // save event handlers so we can remove events later + // use the element ID as the object key + // save event handlers as values + driverEventHandlers[element.id].disabledlistener = disabledlistener + + // Events to disable + document.addEventListener("pointerdown", disabledlistener, true); + document.addEventListener("mousedown", disabledlistener, true); + document.addEventListener("pointerup", disabledlistener, true); + document.addEventListener("mouseup", disabledlistener, true); + } + // Actual click handler + document.addEventListener('click', clickedListener, true) +} - listener?.(e); - }; +export function destroyDriverEvents (element: Element) { + // extract the saved event handlers to delete these events + const { clickedListener, disabledlistener } = driverEventHandlers[element.id]; - // We want to be the absolute first one to hear about the event - const useCapture = true; + clickedListener && document.removeEventListener('click', clickedListener, true); - // Events to disable - document.addEventListener("pointerdown", listenerWrapper, useCapture); - document.addEventListener("mousedown", listenerWrapper, useCapture); - document.addEventListener("pointerup", listenerWrapper, useCapture); - document.addEventListener("mouseup", listenerWrapper, useCapture); + if (disabledlistener) { + document.removeEventListener("pointerdown", disabledlistener, true); + document.removeEventListener("mousedown", disabledlistener, true); + document.removeEventListener("pointerup", disabledlistener, true); + document.removeEventListener("mouseup", disabledlistener, true); + } - // Actual click handler - document.addEventListener( - "click", - e => { - listenerWrapper(e, listener); - }, - useCapture - ); + // clear driverEventHandlers + delete driverEventHandlers[element.id] } export function initEvents() { @@ -122,6 +161,7 @@ export function initEvents() { export function destroyEvents() { window.removeEventListener("keyup", onKeyup); + window.removeEventListener("keydown", trapFocus); window.removeEventListener("resize", requireRefresh); window.removeEventListener("scroll", requireRefresh); } diff --git a/src/overlay.ts b/src/overlay.ts index cffb4e1..d16c67c 100644 --- a/src/overlay.ts +++ b/src/overlay.ts @@ -1,5 +1,5 @@ import { easeInOutQuad } from "./utils"; -import { onDriverClick } from "./events"; +import { destroyDriverEvents, onDriverClick } from "./events"; import { emit } from "./emitter"; import { getConfig } from "./config"; import { getState, setState } from "./state"; @@ -112,6 +112,7 @@ function createOverlaySvg(stage: StageDefinition): SVGSVGElement { const windowY = window.innerHeight; const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + svg.id = "driver-overlay"; svg.classList.add("driver-overlay", "driver-overlay-animated"); svg.setAttribute("viewBox", `0 0 ${windowX} ${windowY}`); @@ -172,7 +173,10 @@ function generateStageSvgPathString(stage: StageDefinition) { export function destroyOverlay() { const overlaySvg = getState("__overlaySvg"); + if (overlaySvg) { + // remove all events associated with the overlay + destroyDriverEvents(overlaySvg); overlaySvg.remove(); } } diff --git a/src/popover.ts b/src/popover.ts index a11ca97..0e61d44 100644 --- a/src/popover.ts +++ b/src/popover.ts @@ -2,7 +2,7 @@ import { bringInView, getFocusableElements } from "./utils"; import { Config, DriverHook, getConfig } from "./config"; import { getState, setState, State } from "./state"; import { DriveStep } from "./driver"; -import { onDriverClick } from "./events"; +import { destroyDriverEvents, onDriverClick } from "./events"; import { emit } from "./emitter"; export type Side = "top" | "right" | "bottom" | "left" | "over"; @@ -55,6 +55,9 @@ export function hidePopover() { return; } + // remove all events associated with the popover + destroyDriverEvents(popover.wrapper) + popover.wrapper.style.display = "none"; } @@ -668,5 +671,8 @@ export function destroyPopover() { return; } + // remove all events associated with the popover + destroyDriverEvents(popover.wrapper) + popover.wrapper.parentElement?.removeChild(popover.wrapper); }