diff --git a/index.html b/index.html
index 6ecace2..140775f 100644
--- a/index.html
+++ b/index.html
@@ -183,6 +183,7 @@
+
@@ -193,6 +194,12 @@
+
Tour Feature
+ Examples below show the tour usage of driver.js.
+
+
+
+
- Written in TypeScript
- Lightweight — only 5kb gzipped
@@ -324,6 +331,51 @@ npm install driver.js
import { driver } from "./src/driver.ts";
+ document.getElementById("basic-tour").addEventListener("click", () => {
+ const driverObj = driver({
+ steps: [
+ {
+ element: ".page-header",
+ popover: {
+ title: "New and Improved Driver.js",
+ description:
+ "Driver.js has been written from the ground up. The new version is more powerful, has less surprises, more customizable and tons of new features.",
+ side: "bottom",
+ align: "start",
+ },
+ },
+ {
+ element: ".page-header h1",
+ popover: {
+ title: "No Stacking Issues",
+ description:
+ "Unlike the older version, new version doesn't work with z-indexes so no more positional issues.",
+ side: "left",
+ align: "start",
+ },
+ },
+ {
+ element: ".page-header sup",
+ popover: {
+ title: "Improved Hooks",
+ description:
+ "Unlike the older version, new version doesn't work with z-indexes so no more positional issues.",
+ side: "bottom",
+ align: "start",
+ },
+ },
+ {
+ popover: {
+ title: "No Element",
+ description: "You can now have popovers without elements as well",
+ },
+ },
+ ],
+ });
+
+ driverObj.drive();
+ });
+
document.getElementById("no-buttons").addEventListener("click", () => {
const driverObj = driver({});
@@ -426,37 +478,50 @@ npm install driver.js {
const driverObj = driver({
- onPopoverRendered: (popover) => {
- popover.title.innerText = 'Modified Parent';
- }
+ onPopoverRendered: popover => {
+ popover.title.innerText = "Modified Parent";
+ },
});
driverObj.highlight({
- element: '.page-header',
+ element: ".page-header",
popover: {
- title: 'Page Title',
- description: 'Body of the popover',
- side: 'bottom',
- align: 'start',
- onPopoverRendered: (popover) => {
- popover.title.innerText = 'Modified';
- }
- }
- })
+ title: "Page Title",
+ description: "Body of the popover",
+ side: "bottom",
+ align: "start",
+ onPopoverRendered: popover => {
+ popover.title.innerText = "Modified";
+ },
+ },
+ });
});
document.getElementById("custom-classes").addEventListener("click", () => {
const driverObj = driver({
- popoverClass: "custom-driver-popover"
- })
+ popoverClass: "custom-driver-popover",
+ });
driverObj.highlight({
popover: {
popoverClass: "custom-driver-popover",
title: "Custom Classes",
description: "Popover and buttons have custom classes",
- showButtons: ["close", "next", "previous"]
- }
- })
+ showButtons: ["close", "next", "previous"],
+ },
+ });
+ });
+
+ document.getElementById("disabled-buttons").addEventListener("click", () => {
+ const driverObj = driver();
+ driverObj.highlight({
+ element: "#disabled-buttons",
+ popover: {
+ title: "Disable Buttons",
+ description: "You can selectively disable buttons as well",
+ showButtons: ["next", "previous", "close"],
+ disableButtons: ["next", "previous"],
+ },
+ });
});
document.getElementById("button-config-events").addEventListener("click", () => {
@@ -623,19 +688,19 @@ npm install driver.js {
- const elementText = element?.textContent?.slice(0, 10) || ' - N/A -';
+ const elementText = element?.textContent?.slice(0, 10) || " - N/A -";
console.log(`Deselected: ${elementText}\n${JSON.stringify(step)}`);
},
onHighlightStarted: (element, step) => {
- const elementText = element?.textContent?.slice(0, 10) || ' - N/A -';
+ const elementText = element?.textContent?.slice(0, 10) || " - N/A -";
console.log(`Highlight Started: ${elementText}\n${JSON.stringify(step)}`);
},
onHighlighted: (element, step) => {
- const elementText = element?.textContent?.slice(0, 10) || ' - N/A -';
+ const elementText = element?.textContent?.slice(0, 10) || " - N/A -";
console.log(`Highlighted: ${elementText}\n${JSON.stringify(step)}`);
},
onDestroyed: (element, step) => {
- const elementText = element?.textContent?.slice(0, 10) || ' - N/A -';
+ const elementText = element?.textContent?.slice(0, 10) || " - N/A -";
console.log(`Destroyed: ${elementText}\n${JSON.stringify(step)}`);
},
});
diff --git a/src/config.ts b/src/config.ts
index 075a2e4..ec95049 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -2,6 +2,8 @@ import { DriveStep } from "./driver";
import {AllowedButtons, PopoverDOM} from "./popover";
export type Config = {
+ steps?: DriveStep[];
+
animate?: boolean;
backdropColor?: string;
smoothScroll?: boolean;
@@ -15,11 +17,13 @@ export type Config = {
popoverClass?: string;
popoverOffset?: number;
showButtons?: AllowedButtons[];
+ disableButtons?: AllowedButtons[];
// Button texts
nextBtnText?: string;
prevBtnText?: string;
closeBtnText?: string;
+ doneBtnText?: string;
// Called after the popover is rendered
onPopoverRendered?: (popover: PopoverDOM) => void;
@@ -49,6 +53,7 @@ export function configure(config: Config = {}) {
stageRadius: 5,
popoverOffset: 10,
showButtons: ["next", "previous", "close"],
+ disableButtons: [],
backdropColor: "#000",
...config,
};
diff --git a/src/driver.ts b/src/driver.ts
index a0f18c4..f139afb 100644
--- a/src/driver.ts
+++ b/src/driver.ts
@@ -1,4 +1,4 @@
-import { destroyPopover, Popover } from "./popover";
+import { AllowedButtons, destroyPopover, Popover } from "./popover";
import { destroyStage } from "./stage";
import { destroyEvents, initEvents, requireRefresh } from "./events";
import { Config, configure, getConfig } from "./config";
@@ -38,6 +38,49 @@ export function driver(options: Config = {}) {
listen("escapePress", handleClose);
}
+ 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();
+ }
+
+ const currentStep = steps[stepIndex];
+ const hasNextStep = steps[stepIndex + 1];
+ const hasPreviousStep = steps[stepIndex - 1];
+
+ const doneBtnText = currentStep.popover?.doneBtnText || getConfig("doneBtnText") || "Done";
+
+ highlight({
+ ...currentStep,
+ popover: {
+ showButtons: ["next", "previous", "close"],
+ 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");
@@ -72,7 +115,10 @@ export function driver(options: Config = {}) {
refresh: () => {
requireRefresh();
},
- drive: (steps: DriveStep[]) => console.log(steps),
+ drive: (stepIndex: number = 0) => {
+ init();
+ drive(stepIndex);
+ },
highlight: (step: DriveStep) => {
init();
highlight({
diff --git a/src/popover.ts b/src/popover.ts
index 31edc1e..6e68b6b 100644
--- a/src/popover.ts
+++ b/src/popover.ts
@@ -11,11 +11,12 @@ export type AllowedButtons = "next" | "previous" | "close";
export type Popover = {
title?: string;
- description: string;
+ description?: string;
side?: Side;
align?: Alignment;
showButtons?: AllowedButtons[];
+ disableButtons?: AllowedButtons[];
popoverClass?: string;
@@ -57,15 +58,19 @@ export function hidePopover() {
export function renderPopover(element: Element, step: DriveStep) {
let popover = getState("popover");
- if (!popover) {
- popover = createPopover();
- document.body.appendChild(popover.wrapper);
+ if (popover) {
+ document.body.removeChild(popover.wrapper);
}
+ popover = createPopover();
+ document.body.appendChild(popover.wrapper);
+
const {
title,
description,
- showButtons: popoverShowButtons = undefined,
+ showButtons,
+ disableButtons,
+
// doneBtnText = 'Done',
closeBtnText = getConfig("closeBtnText") || "Close",
nextBtnText = getConfig("nextBtnText") || "Next →",
@@ -90,27 +95,31 @@ export function renderPopover(element: Element, step: DriveStep) {
popover.description.style.display = "none";
}
- const showButtonsConfig: AllowedButtons[] =
- popoverShowButtons !== undefined ? popoverShowButtons : getConfig("showButtons")!;
+ const showButtonsConfig: AllowedButtons[] = showButtons || getConfig("showButtons")!;
if (showButtonsConfig?.length! > 0) {
popover.footer.style.display = "flex";
- if (!showButtonsConfig.includes("next")) {
- popover.nextButton.style.display = "none";
- }
-
- if (!showButtonsConfig.includes("previous")) {
- popover.previousButton.style.display = "none";
- }
-
- if (!showButtonsConfig.includes("close")) {
- popover.closeButton.style.display = "none";
- }
+ popover.nextButton.style.display = showButtonsConfig.includes("next") ? 'block' : 'none';
+ popover.previousButton.style.display = showButtonsConfig.includes("previous") ? 'block' : 'none';
+ popover.closeButton.style.display = showButtonsConfig.includes("close") ? 'block' : 'none';
} else {
popover.footer.style.display = "none";
}
+ const disabledButtonsConfig: AllowedButtons[] = disableButtons || getConfig('disableButtons')! || [];
+ if (disabledButtonsConfig?.includes('next')) {
+ popover.nextButton.classList.add("driver-popover-btn-disabled")
+ }
+
+ if (disabledButtonsConfig?.includes('previous')) {
+ popover.previousButton.classList.add("driver-popover-btn-disabled")
+ }
+
+ if (disabledButtonsConfig?.includes('close')) {
+ popover.closeButton.classList.add("driver-popover-btn-disabled")
+ }
+
// Reset the popover position
const popoverWrapper = popover.wrapper;
popoverWrapper.style.display = "block";
diff --git a/src/style.css b/src/style.css
index e5798a4..f29b1c7 100644
--- a/src/style.css
+++ b/src/style.css
@@ -97,6 +97,11 @@
border-radius: 3px;
}
+.driver-popover-footer .driver-popover-btn-disabled {
+ opacity: 0.5;
+ pointer-events: none;
+}
+
/* Disable the scrolling of parent element if it has an active element*/
:not(body):has(> .driver-active-element) {
overflow: hidden !important;