|
import { DriveStep } from "./driver"; |
|
import { refreshStage, trackActiveElement, transitionStage } from "./stage"; |
|
import { getConfig } from "./config"; |
|
import { repositionPopover, renderPopover, hidePopover } from "./popover"; |
|
import { bringInView } from "./utils"; |
|
import { getState, setState } from "./state"; |
|
|
|
function mountDummyElement(): Element { |
|
const existingDummy = document.getElementById("driver-dummy-element"); |
|
if (existingDummy) { |
|
return existingDummy; |
|
} |
|
|
|
let element = document.createElement("div"); |
|
|
|
element.id = "driver-dummy-element"; |
|
element.style.width = "0"; |
|
element.style.height = "0"; |
|
element.style.pointerEvents = "none"; |
|
element.style.opacity = "0"; |
|
element.style.position = "fixed"; |
|
element.style.top = "50%"; |
|
element.style.left = "50%"; |
|
|
|
document.body.appendChild(element); |
|
|
|
return element; |
|
} |
|
|
|
export function highlight(step: DriveStep) { |
|
const { element } = step; |
|
let elemObj = typeof element === "string" ? document.querySelector(element) : element; |
|
|
|
|
|
|
|
|
|
|
|
if (!elemObj) { |
|
elemObj = mountDummyElement(); |
|
} |
|
|
|
transferHighlight(elemObj, step); |
|
} |
|
|
|
export function refreshActiveHighlight() { |
|
const activeHighlight = getState("activeElement"); |
|
const activeStep = getState("activeStep")!; |
|
|
|
if (!activeHighlight) { |
|
return; |
|
} |
|
|
|
trackActiveElement(activeHighlight); |
|
refreshStage(); |
|
repositionPopover(activeHighlight, activeStep); |
|
} |
|
|
|
function transferHighlight(toElement: Element, toStep: DriveStep) { |
|
const duration = 400; |
|
const start = Date.now(); |
|
|
|
const fromStep = getState("activeStep"); |
|
const fromElement = getState("activeElement") || toElement; |
|
|
|
|
|
|
|
|
|
const isFirstHighlight = !fromElement || fromElement === toElement; |
|
const isToDummyElement = toElement.id === "driver-dummy-element"; |
|
const isFromDummyElement = fromElement.id === "driver-dummy-element"; |
|
|
|
const isAnimatedTour = getConfig("animate"); |
|
const highlightStartedHook = getConfig("onHighlightStarted"); |
|
const highlightedHook = getConfig("onHighlighted"); |
|
const deselectedHook = getConfig("onDeselected"); |
|
|
|
if (!isFirstHighlight && deselectedHook) { |
|
deselectedHook(isFromDummyElement ? undefined : fromElement, fromStep!); |
|
} |
|
|
|
if (highlightStartedHook) { |
|
highlightStartedHook(isToDummyElement ? undefined : toElement, toStep); |
|
} |
|
|
|
const hasDelayedPopover = !isFirstHighlight && isAnimatedTour; |
|
let isPopoverRendered = false; |
|
|
|
hidePopover(); |
|
|
|
const animate = () => { |
|
const transitionCallback = getState("transitionCallback"); |
|
|
|
|
|
|
|
|
|
if (transitionCallback !== animate) { |
|
return; |
|
} |
|
|
|
const elapsed = Date.now() - start; |
|
const timeRemaining = duration - elapsed; |
|
const isHalfwayThrough = timeRemaining <= duration / 2; |
|
|
|
if (toStep.popover && isHalfwayThrough && !isPopoverRendered && hasDelayedPopover) { |
|
renderPopover(toElement, toStep); |
|
isPopoverRendered = true; |
|
} |
|
|
|
if (getConfig("animate") && elapsed < duration) { |
|
transitionStage(elapsed, duration, fromElement, toElement); |
|
} else { |
|
trackActiveElement(toElement); |
|
|
|
if (highlightedHook) { |
|
highlightedHook(isToDummyElement ? undefined : toElement, toStep); |
|
} |
|
|
|
setState("transitionCallback", undefined); |
|
setState("previousStep", fromStep); |
|
setState("previousElement", fromElement); |
|
setState("activeStep", toStep); |
|
setState("activeElement", toElement); |
|
} |
|
|
|
window.requestAnimationFrame(animate); |
|
}; |
|
|
|
setState("transitionCallback", animate); |
|
window.requestAnimationFrame(animate); |
|
|
|
bringInView(toElement); |
|
if (!hasDelayedPopover && toStep.popover) { |
|
renderPopover(toElement, toStep); |
|
} |
|
|
|
fromElement.classList.remove("driver-active-element"); |
|
toElement.classList.add("driver-active-element"); |
|
} |
|
|
|
export function destroyHighlight() { |
|
document.getElementById("driver-dummy-element")?.remove(); |
|
document.querySelectorAll(".driver-active-element").forEach(element => { |
|
element.classList.remove("driver-active-element"); |
|
}); |
|
} |
|
|