diff --git a/docs/src/content/guides/theming.mdx b/docs/src/content/guides/theming.mdx new file mode 100644 index 0000000..76c6cd6 --- /dev/null +++ b/docs/src/content/guides/theming.mdx @@ -0,0 +1,19 @@ +--- +title: "Theming" +groupTitle: "Introduction" +sort: 3 +--- + +You can customize the look and feel of the driver by applying CSS to following classes. + +```css +/* Applied to the `body` when the driver: */ +.driver-active {} /* is active */ +.driver-fade {} /* is animated */ +.driver-simple {} /* is not animated */ +``` + +Following classes are applied to the overlay element (i.e. called stage. + +```css +.driver-stage {} \ No newline at end of file diff --git a/src/driver.ts b/src/driver.ts index 1a37c72..43d1376 100644 --- a/src/driver.ts +++ b/src/driver.ts @@ -1,5 +1,5 @@ import { AllowedButtons, destroyPopover, Popover } from "./popover"; -import { destroyStage } from "./stage"; +import { destroyOverlay } from "./overlay"; import { destroyEvents, initEvents, requireRefresh } from "./events"; import { Config, configure, DriverHook, getConfig } from "./config"; import { destroyHighlight, highlight } from "./highlight"; @@ -57,19 +57,40 @@ export function driver(options: Config = {}) { } function handleArrowLeft() { - const steps = getConfig("steps") || []; + const isTransitioning = getState("__transitionCallback"); + if (isTransitioning) { + return; + } + + const activeIndex = getState("activeIndex"); + const activeStep = getState("activeStep"); + const activeElement = getState("activeElement"); + if (typeof activeIndex === "undefined" || typeof activeStep === "undefined") { + return; + } + const currentStepIndex = getState("activeIndex"); if (typeof currentStepIndex === "undefined") { return; } - const previousStepIndex = currentStepIndex - 1; - if (steps[previousStepIndex]) { - drive(previousStepIndex); + const onPrevClick = activeStep.popover?.onPrevClick || getConfig("onPrevClick"); + if (onPrevClick) { + return onPrevClick(activeElement, activeStep, { + config: getConfig(), + state: getState(), + }); } + + movePrevious(); } function handleArrowRight() { + const isTransitioning = getState("__transitionCallback"); + if (isTransitioning) { + return; + } + const activeIndex = getState("activeIndex"); const activeStep = getState("activeStep"); const activeElement = getState("activeElement"); @@ -113,8 +134,9 @@ export function driver(options: Config = {}) { } if (!steps[stepIndex]) { - console.warn(`Step not found at index: ${stepIndex}`); destroy(); + + return; } setState("activeIndex", stepIndex); @@ -185,7 +207,7 @@ export function driver(options: Config = {}) { destroyEvents(); destroyPopover(); destroyHighlight(); - destroyStage(); + destroyOverlay(); destroyEmitter(); resetState(); diff --git a/src/highlight.ts b/src/highlight.ts index 208e534..b2aa203 100644 --- a/src/highlight.ts +++ b/src/highlight.ts @@ -1,5 +1,5 @@ import { DriveStep } from "./driver"; -import { refreshStage, trackActiveElement, transitionStage } from "./stage"; +import { refreshOverlay, trackActiveElement, transitionStage } from "./overlay"; import { getConfig } from "./config"; import { repositionPopover, renderPopover, hidePopover } from "./popover"; import { bringInView } from "./utils"; @@ -51,7 +51,7 @@ export function refreshActiveHighlight() { } trackActiveElement(activeHighlight); - refreshStage(); + refreshOverlay(); repositionPopover(activeHighlight, activeStep); } diff --git a/src/stage.ts b/src/overlay.ts similarity index 84% rename from src/stage.ts rename to src/overlay.ts index 2088bc4..ea95ca1 100644 --- a/src/stage.ts +++ b/src/overlay.ts @@ -31,7 +31,7 @@ export function transitionStage(elapsed: number, duration: number, from: Element height, }; - renderStage(activeStagePosition); + renderOverlay(activeStagePosition); setState("__activeStagePosition", activeStagePosition); } @@ -51,18 +51,18 @@ export function trackActiveElement(element: Element) { setState("__activeStagePosition", activeStagePosition); - renderStage(activeStagePosition); + renderOverlay(activeStagePosition); } -export function refreshStage() { +export function refreshOverlay() { const activeStagePosition = getState("__activeStagePosition"); - const stageSvg = getState("__stageSvg"); + const overlaySvg = getState("__overlaySvg"); if (!activeStagePosition) { return; } - if (!stageSvg) { + if (!overlaySvg) { console.warn("No stage svg found."); return; } @@ -70,14 +70,14 @@ export function refreshStage() { const windowX = window.innerWidth; const windowY = window.innerHeight; - stageSvg.setAttribute("viewBox", `0 0 ${windowX} ${windowY}`); + overlaySvg.setAttribute("viewBox", `0 0 ${windowX} ${windowY}`); } -function mountStage(stagePosition: StageDefinition) { - const stageSvg = createStageSvg(stagePosition); - document.body.appendChild(stageSvg); +function mountOverlay(stagePosition: StageDefinition) { + const overlaySvg = createOverlaySvg(stagePosition); + document.body.appendChild(overlaySvg); - onDriverClick(stageSvg, e => { + onDriverClick(overlaySvg, e => { const target = e.target as SVGElement; if (target.tagName !== "path") { return; @@ -86,20 +86,20 @@ function mountStage(stagePosition: StageDefinition) { emit("overlayClick"); }); - setState("__stageSvg", stageSvg); + setState("__overlaySvg", overlaySvg); } -function renderStage(stagePosition: StageDefinition) { - const stageSvg = getState("__stageSvg"); +function renderOverlay(stagePosition: StageDefinition) { + const overlaySvg = getState("__overlaySvg"); // TODO: cancel rendering if element is not visible - if (!stageSvg) { - mountStage(stagePosition); + if (!overlaySvg) { + mountOverlay(stagePosition); return; } - const pathElement = stageSvg.firstElementChild as SVGPathElement | null; + const pathElement = overlaySvg.firstElementChild as SVGPathElement | null; if (pathElement?.tagName !== "path") { throw new Error("no path element found in stage svg"); } @@ -107,7 +107,7 @@ function renderStage(stagePosition: StageDefinition) { pathElement.setAttribute("d", generateStageSvgPathString(stagePosition)); } -function createStageSvg(stage: StageDefinition): SVGSVGElement { +function createOverlaySvg(stage: StageDefinition): SVGSVGElement { const windowX = window.innerWidth; const windowY = window.innerHeight; @@ -170,9 +170,9 @@ function generateStageSvgPathString(stage: StageDefinition) { M${highlightBoxX},${highlightBoxY} h${highlightBoxWidth} a${normalizedRadius},${normalizedRadius} 0 0 1 ${normalizedRadius},${normalizedRadius} v${highlightBoxHeight} a${normalizedRadius},${normalizedRadius} 0 0 1 -${normalizedRadius},${normalizedRadius} h-${highlightBoxWidth} a${normalizedRadius},${normalizedRadius} 0 0 1 -${normalizedRadius},-${normalizedRadius} v-${highlightBoxHeight} a${normalizedRadius},${normalizedRadius} 0 0 1 ${normalizedRadius},-${normalizedRadius} z`; } -export function destroyStage() { - const stageSvg = getState("__stageSvg"); - if (stageSvg) { - stageSvg.remove(); +export function destroyOverlay() { + const overlaySvg = getState("__overlaySvg"); + if (overlaySvg) { + overlaySvg.remove(); } } diff --git a/src/state.ts b/src/state.ts index 97f85eb..872bc66 100644 --- a/src/state.ts +++ b/src/state.ts @@ -1,4 +1,4 @@ -import { StageDefinition } from "./stage"; +import { StageDefinition } from "./overlay"; import { PopoverDOM } from "./popover"; import { DriveStep } from "./driver"; @@ -17,7 +17,7 @@ export type State = { __resizeTimeout?: number; __transitionCallback?: () => void; __activeStagePosition?: StageDefinition; - __stageSvg?: SVGSVGElement; + __overlaySvg?: SVGSVGElement; }; let currentState: State = {};