import { AllowedButtons, destroyPopover, Popover } from "./popover"; import { destroyStage } from "./stage"; import { destroyEvents, initEvents, requireRefresh } from "./events"; import { Config, configure, getConfig } from "./config"; import { destroyHighlight, highlight } from "./highlight"; import { destroyEmitter, listen } from "./emitter"; import "./style.css"; import { getState, resetState, setState } from "./state"; export type DriveStep = { element?: string | Element; popover?: Popover; }; export function driver(options: Config = {}) { configure(options); function handleClose() { if (!getConfig("allowClose")) { return; } destroy(); } function moveNext() { const activeIndex = getState('activeIndex'); const steps = getConfig('steps') || []; if (typeof activeIndex === 'undefined') { return; } const nextStepIndex = activeIndex + 1; if (steps[nextStepIndex]) { drive(nextStepIndex); } else { destroy(); } } function movePrevious() { const activeIndex = getState('activeIndex'); const steps = getConfig('steps') || []; if (typeof activeIndex === 'undefined') { return; } const previousStepIndex = activeIndex - 1; if (steps[previousStepIndex]) { drive(previousStepIndex); } else { destroy(); } } function handleArrowLeft() { const steps = getConfig("steps") || []; const currentStepIndex = getState("activeIndex"); if (typeof currentStepIndex === "undefined") { return; } const previousStepIndex = currentStepIndex - 1; if (steps[previousStepIndex]) { drive(previousStepIndex); } } function handleArrowRight() { const activeIndex = getState("activeIndex"); const activeStep = getState("activeStep"); const activeElement = getState("activeElement"); if (typeof activeIndex === "undefined" || typeof activeStep === "undefined") { return; } const onNextClick = activeStep.popover?.onNextClick || getConfig("onNextClick"); if (onNextClick) { return onNextClick(activeElement, activeStep); } moveNext(); } function init() { if (getState("isInitialized")) { return; } setState("isInitialized", true); document.body.classList.add("driver-active", getConfig("animate") ? "driver-fade" : "driver-simple"); initEvents(); listen("overlayClick", handleClose); listen("escapePress", handleClose); listen("arrowLeftPress", handleArrowLeft); listen("arrowRightPress", handleArrowRight); } function drive(stepIndex: number = 0) { const steps = getConfig("steps"); if (!steps) { console.error("No steps to drive through"); destroy(); return; } if (!steps[stepIndex]) { console.warn(`Step not found at index: ${stepIndex}`); destroy(); } setState("activeIndex", stepIndex); const currentStep = steps[stepIndex]; const hasNextStep = steps[stepIndex + 1]; const hasPreviousStep = steps[stepIndex - 1]; const doneBtnText = currentStep.popover?.doneBtnText || getConfig("doneBtnText") || "Done"; const allowsClosing = getConfig("allowClose"); highlight({ ...currentStep, popover: { showButtons: ["next", "previous", ...(allowsClosing ? ["close" as AllowedButtons] : [])], nextBtnText: !hasNextStep ? doneBtnText : undefined, disableButtons: [...(!hasPreviousStep ? ["previous" as AllowedButtons] : [])], onNextClick: () => { if (!hasNextStep) { destroy(); } else { drive(stepIndex + 1); } }, onPrevClick: () => { drive(stepIndex - 1); }, onCloseClick: () => { destroy(); }, ...(currentStep?.popover || {}), }, }); } function destroy() { const activeElement = getState("activeElement"); const activeStep = getState("activeStep"); const onDeselected = getConfig("onDeselected"); const onDestroyed = getConfig("onDestroyed"); document.body.classList.remove("driver-active", "driver-fade", "driver-simple"); destroyEvents(); destroyPopover(); destroyHighlight(); destroyStage(); destroyEmitter(); resetState(); if (activeElement && activeStep) { const isActiveDummyElement = activeElement.id === "driver-dummy-element"; if (onDeselected) { onDeselected(isActiveDummyElement ? undefined : activeElement, activeStep); } if (onDestroyed) { onDestroyed(isActiveDummyElement ? undefined : activeElement, activeStep); } } } return { isActive: () => getState("isInitialized") || false, refresh: () => { requireRefresh(); }, drive: (stepIndex: number = 0) => { init(); drive(stepIndex); }, moveNext, movePrevious, highlight: (step: DriveStep) => { init(); highlight({ ...step, popover: step.popover ? { showButtons: [], ...step.popover!, } : undefined, }); }, destroy, }; }