Showing preview only (241K chars total). Download the full file or copy to clipboard to get everything.
Repository: kamranahmedse/driver.js
Branch: master
Commit: 4e4d07d481ae
Files: 67
Total size: 224.4 KB
Directory structure:
gitextract_tn0sokzd/
├── .github/
│ └── FUNDING.yml
├── .gitignore
├── .prettierignore
├── .prettierrc
├── docs/
│ ├── .gitignore
│ ├── astro.config.mjs
│ ├── package.json
│ ├── src/
│ │ ├── components/
│ │ │ ├── Analytics/
│ │ │ │ ├── Analytics.astro
│ │ │ │ └── analytics.ts
│ │ │ ├── CodeSample.tsx
│ │ │ ├── Container.astro
│ │ │ ├── DocsHeader.tsx
│ │ │ ├── ExampleButton.tsx
│ │ │ ├── Examples.astro
│ │ │ ├── FeatureMarquee.tsx
│ │ │ ├── Features.astro
│ │ │ ├── FormHelp.tsx
│ │ │ ├── HeroSection.astro
│ │ │ ├── OpenSourceLove.astro
│ │ │ ├── Sidebar.astro
│ │ │ ├── UsecaseItem.astro
│ │ │ └── UsecaseList.astro
│ │ ├── content/
│ │ │ ├── config.ts
│ │ │ └── guides/
│ │ │ ├── animated-tour.mdx
│ │ │ ├── api.mdx
│ │ │ ├── async-tour.mdx
│ │ │ ├── basic-usage.mdx
│ │ │ ├── buttons.mdx
│ │ │ ├── configuration.mdx
│ │ │ ├── confirm-on-exit.mdx
│ │ │ ├── installation.mdx
│ │ │ ├── migrating-from-0x.mdx
│ │ │ ├── popover-position.mdx
│ │ │ ├── prevent-destroy.mdx
│ │ │ ├── simple-highlight.mdx
│ │ │ ├── static-tour.mdx
│ │ │ ├── styling-overlay.mdx
│ │ │ ├── styling-popover.mdx
│ │ │ ├── theming.mdx
│ │ │ └── tour-progress.mdx
│ │ ├── env.d.ts
│ │ ├── layouts/
│ │ │ ├── BaseLayout.astro
│ │ │ └── DocsLayout.astro
│ │ ├── lib/
│ │ │ ├── github.ts
│ │ │ └── guide.ts
│ │ └── pages/
│ │ ├── docs/
│ │ │ └── [guideId].astro
│ │ └── index.astro
│ ├── tailwind.config.cjs
│ └── tsconfig.json
├── dts-bundle-generator.config.ts
├── index.html
├── license
├── package.json
├── readme.md
├── src/
│ ├── config.ts
│ ├── driver.css
│ ├── driver.ts
│ ├── emitter.ts
│ ├── events.ts
│ ├── highlight.ts
│ ├── overlay.ts
│ ├── popover.ts
│ ├── state.ts
│ └── utils.ts
├── tests/
│ └── sum.test.ts
├── tsconfig.json
└── vite.config.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: [kamranahmedse]
================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
coverage
# Editor directories and files
.vscode/*
.history/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
================================================
FILE: .prettierignore
================================================
.history
.vscode
coverage
dist
node_modules
================================================
FILE: .prettierrc
================================================
{
"printWidth": 120,
"tabWidth": 2,
"singleQuote": false,
"trailingComma": "es5",
"arrowParens": "avoid",
"bracketSpacing": true,
"useTabs": false,
"endOfLine": "auto",
"singleAttributePerLine": false,
"bracketSameLine": false,
"jsxSingleQuote": false,
"quoteProps": "as-needed",
"semi": true
}
================================================
FILE: docs/.gitignore
================================================
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store
================================================
FILE: docs/astro.config.mjs
================================================
import { defineConfig } from "astro/config";
import tailwind from "@astrojs/tailwind";
import react from "@astrojs/react";
import mdx from "@astrojs/mdx";
import compress from "astro-compress";
// https://astro.build/config
export default defineConfig({
build: {
format: "file",
},
markdown: {
shikiConfig: {
// theme: "material-theme"
theme: "monokai",
// theme: 'poimandres'
},
},
integrations: [
tailwind(),
react(),
mdx(),
compress({
CSS: false,
JS: false,
}),
],
});
================================================
FILE: docs/package.json
================================================
{
"name": "driver-docs",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/mdx": "^1.0.0",
"@astrojs/react": "^3.0.0",
"@astrojs/tailwind": "^5.0.0",
"@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7",
"astro": "^3.0.8",
"astro-compress": "^2.0.15",
"driver.js": "1.3.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-fast-marquee": "^1.6.0",
"tailwindcss": "^3.3.3"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.9"
}
}
================================================
FILE: docs/src/components/Analytics/Analytics.astro
================================================
---
---
<script src='./analytics.ts'></script>
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LGE7XP5BHC"></script>
<script>
//@ts-nocheck
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-LGE7XP5BHC');
</script>
================================================
FILE: docs/src/components/Analytics/analytics.ts
================================================
declare global {
interface Window {
gtag: any;
fireEvent: (props: {
action: string;
category: string;
label?: string;
value?: string;
}) => void;
}
}
/**
* Tracks the event on google analytics
* @see https://developers.google.com/analytics/devguides/collection/gtagjs/events
* @param props Event properties
* @returns void
*/
window.fireEvent = (props) => {
const { action, category, label, value } = props;
if (!window.gtag) {
console.warn('Missing GTAG - Analytics disabled');
return;
}
if (import.meta.env.DEV) {
console.log('Analytics event fired', props);
}
window.gtag('event', action, {
event_category: category,
event_label: label,
value: value,
});
};
================================================
FILE: docs/src/components/CodeSample.tsx
================================================
import type { Config, DriveStep, PopoverDOM } from "driver.js";
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
type CodeSampleProps = {
heading?: string;
config?: Config;
highlight?: DriveStep;
tour?: DriveStep[];
id?: string;
className?: string;
children?: any;
buttonText?: string;
};
export function removeDummyElement() {
const el = document.querySelector(".dynamic-el");
if (el) {
el.remove();
}
}
export function mountDummyElement() {
const newDiv = (document.querySelector(".dynamic-el") || document.createElement("div")) as HTMLElement;
newDiv.innerHTML = "This is a new Element";
newDiv.style.display = "block";
newDiv.style.padding = "20px";
newDiv.style.backgroundColor = "black";
newDiv.style.color = "white";
newDiv.style.fontSize = "14px";
newDiv.style.position = "fixed";
newDiv.style.top = `${Math.random() * (500 - 30) + 30}px`;
newDiv.style.left = `${Math.random() * (500 - 30) + 30}px`;
newDiv.className = "dynamic-el";
document.body.appendChild(newDiv);
}
function attachFirstButton(popover: PopoverDOM) {
const firstButton = document.createElement("button");
firstButton.innerText = "Go to First";
popover.footerButtons.appendChild(firstButton);
firstButton.addEventListener("click", () => {
window.driverObj.drive(0);
});
}
export function CodeSample(props: CodeSampleProps) {
const { heading, id, children, buttonText = "Show me an Example", className, config, highlight, tour } = props;
if (id === "demo-hook-theme") {
config!.onPopoverRender = attachFirstButton;
}
function onClick() {
if (highlight) {
const driverObj = driver({
...config,
});
window.driverObj = driverObj;
driverObj.highlight(highlight);
} else if (tour) {
if (id === "confirm-destroy") {
config!.onDestroyStarted = () => {
if (!driverObj.hasNextStep() || confirm("Are you sure?")) {
driverObj.destroy();
}
};
}
if (id === "logger-events") {
config!.onNextClick = () => {
console.log("next clicked");
};
config!.onNextClick = () => {
console.log("Next Button Clicked");
// Implement your own functionality here
driverObj.moveNext();
};
config!.onPrevClick = () => {
console.log("Previous Button Clicked");
// Implement your own functionality here
driverObj.movePrevious();
};
config!.onCloseClick = () => {
console.log("Close Button Clicked");
// Implement your own functionality here
driverObj.destroy();
};
}
if (tour?.[2]?.popover?.title === "Next Step is Async") {
tour[2].popover.onNextClick = () => {
mountDummyElement();
driverObj.moveNext();
};
if (tour?.[3]?.element === ".dynamic-el") {
tour[3].onDeselected = () => {
removeDummyElement();
};
// @ts-ignore
tour[4].popover.onPrevClick = () => {
mountDummyElement();
driverObj.movePrevious();
};
// @ts-ignore
tour[3].popover.onPrevClick = () => {
removeDummyElement();
driverObj.movePrevious();
};
}
}
const driverObj = driver({
...config,
steps: tour,
});
window.driverObj = driverObj;
driverObj.drive();
}
}
return (
<div id={id} className={className}>
{heading && <p className="text-lg -mt-0 font-medium text-black -mb-3 rounded-md">{heading}</p>}
{children && <div className="-mb-4">{children}</div>}
<button onClick={onClick} className="w-full rounded-md bg-black p-2 text-white">
{buttonText}
</button>
</div>
);
}
================================================
FILE: docs/src/components/Container.astro
================================================
<div class="max-w-[1050px] mx-auto px-7 sm:px-10">
<slot />
</div>
================================================
FILE: docs/src/components/DocsHeader.tsx
================================================
import { useState } from "react";
import type { CollectionEntry } from "astro:content";
type DocsHeaderProps = {
groupedGuides: Record<string, CollectionEntry<"guides">[]>;
activeGuideTitle: string;
};
export function DocsHeader(props: DocsHeaderProps) {
const { groupedGuides, activeGuideTitle } = props;
const [isActive, setIsActive] = useState(false);
return (
<>
<div className="border-b flex items-center justify-between">
<div className="text-right flex justify-end py-3 px-6">
<a href="/" className="flex items-center justify-end text-xl font-bold">
<img src="/driver-head.svg" alt="Astro" className="w-10 h-10 mr-2" />
driver.js
</a>
</div>
<div className="flex items-center pr-12">
<button onClick={() => setIsActive(!isActive)} className="p-[12px] -mr-[12px] hover:bg-gray-100 rounded-md">
<img src={isActive ? "/cross.svg" : "/burger.svg"} alt="menu" className="w-[14px] h-[14px]" />
</button>
</div>
</div>
<div className={`bg-gray-50 flex flex-col gap-5 px-6 py-6 border-b ${isActive ? "block" : "hidden"}`}>
{Object.entries(groupedGuides).map(([category, guides]) => (
<div key={category} className="flex flex-col gap-2">
<div className="font-bold text-gray-900 text-sm uppercase">{category}</div>
<div className="flex flex-col">
{guides.map(guide => (
<a key={guide.slug} href={`/docs/${guide.slug}`} className={`${activeGuideTitle === guide.data.title ? 'text-black': 'text-gray-400'} hover:text-gray-900 py-1`}>
{guide.data.title}
</a>
))}
</div>
</div>
))}
</div>
</>
);
}
================================================
FILE: docs/src/components/ExampleButton.tsx
================================================
type ExampleButtonProps = {
id: string;
onClick: () => void;
text: string;
};
export function ExampleButton(props: ExampleButtonProps) {
const { id, onClick, text } = props;
return (
<button
id={id}
onClick={onClick}
className="bg-transparent rounded-lg lg:rounded-xl py-2 px-4 font-medium text-black border-2 border-black md:text-base lg:text-lg hover:bg-yellow-300 hover:text-black transition-colors duration-100">
{ text }
</button>
);
}
================================================
FILE: docs/src/components/Examples.astro
================================================
---
import { ExampleButton } from "./ExampleButton";
---
<h2 class="text-4xl md:text-5xl lg:text-6xl font-bold mb-3 md:mb-4 lg:mb-6" data-examples-heading>Examples</h2>
<p class="text-base md:text-xl lg:text-2xl text-black leading-6" data-examples-tagline>Here are just a few examples; find more <a
class="text-black underline underline-offset-4" href="/docs/installation">in the documentation</a>.</p>
<div class="flex flex-wrap gap-2 md:gap-2 lg:gap-3 my-6 md:my-8 lg:my-12" data-example-btns>
<ExampleButton id="animated-tour" text="Animated Tour" />
<ExampleButton id="static-tour" text="Non-Animated Tour" />
<ExampleButton id="async-tour" text="Async Tour" />
<ExampleButton id="no-element" text="No Element" />
<ExampleButton id="confirm-on-exit" text="Confirm on Exit" />
<ExampleButton id="show-progress" text="Show Progress" />
<ExampleButton id="simple-element-highlight" text="Simple Highlight" />
<ExampleButton id="simple-element-highlight-popover" text="Highlight with Popover" />
<ExampleButton id="prevent-close" text="Prevent Closing" />
<ExampleButton id="overlay-color" text="Overlay Color" />
<ExampleButton id="popover-position" text="Popover Positioning" />
<ExampleButton id="customizing-popover" text="Customizing Popover" />
<ExampleButton id="hooks-everything" text="Hooks for Everything" />
<a href="/docs/installation"
class="items-center flex text-base md:text-base lg:text-lg text-gray-900 bg-yellow-300 rounded-lg lg:rounded-xl px-5 py-2 hover:bg-yellow-500 hover:text-black">
and much more ...
</a>
</div>
<script>
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
import { mountDummyElement, removeDummyElement } from "../components/CodeSample";
function markDone(btn) {
btn.classList.add("bg-gray-300", "hover:bg-gray-200", "line-through");
btn.classList.remove("bg-transparent");
}
const demoTourButton = document.querySelector("[data-demo-tour]");
demoTourButton.addEventListener("click", () => {
const driverObj = driver({
popoverClass: "driverjs-theme",
showButtons: ["next", "previous"],
steps: [
{
element: "[data-hero-text]",
popover: {
title: "Before we start",
description: "This is just one use-case, make sure to look at the docs for more use-cases and examples.",
nextBtnText: "Okay, start!",
side: "bottom",
align: "start"
}
},
{
element: "[data-driver-tagline]",
popover: {
title: "Focus Anything",
description: "You can use it to highlight literally anything, images, text, svg, div, span, li etc.",
side: "bottom",
align: "start"
}
},
{
element: "[data-driver-name]",
popover: {
title: "Why Driver.js",
description: "It's lightweight, has no dependencies, is MIT licensed, is highly customizable, and is super easy to use.",
side: "bottom",
align: "start"
}
},
{
element: "[data-docs-link]",
popover: {
title: "More Powerful than Ever",
description: "Driver.js has been completely rewritten from scratch and is now more powerful than ever.",
side: "bottom",
align: "start"
}
},
{
element: "[data-example-btns]",
popover: {
title: "Examples",
description: "Here are some examples to give you a rough idea.",
side: "bottom",
align: "start"
}
},
{
element: "[data-example-btns] a:last-child",
popover: {
title: "Visit Docs",
description: "Make sure to visit the docs for more examples and use-cases.",
side: "top",
align: "start"
}
},
{
element: "[data-github-link]",
popover: {
title: "MIT Licensed",
description: "Driver.js is MIT licensed and is used by thousands of companies around the world.",
side: "top",
align: "start"
}
},
{
popover: {
description: "<img style='width: 270px; height: 206.65px; margin-bottom: 10px; border-radius: 5px;' src='https://i.imgur.com/3WpTnyA.gif' />That's it for now, make sure to <a class='underline font-medium' href='/docs/installation'>visit the docs</a> for more examples and use-cases.",
side: "bottom",
align: "start",
showButtons: [],
popoverClass: "default-theme"
}
}
]
});
driverObj.drive();
});
const animatedTourBtn = document.getElementById('animated-tour');
animatedTourBtn.addEventListener("click", () => {
const driverObj = driver({
popoverClass: "driverjs-theme",
showButtons: ["next", "previous"],
steps: [
{
element: "[data-examples-heading]",
popover: {
title: "Animated Tour",
description: "Animated tour can simply be achieved by setting `animate` option true.",
side: "bottom",
align: "start"
}
},
{
element: "[data-examples-tagline]",
popover: {
title: "Just an Example",
description: "We don't have many steps in this example, but you can have as many as you want.",
side: "bottom",
align: "start"
}
},
{
element: "[data-examples-tagline] a",
popover: {
title: "That's it for now",
description: "Make sure to visit the docs for more examples and use-cases.",
side: "bottom",
align: "start"
}
},
],
onDestroyed: () => {
markDone(animatedTourBtn);
}
});
driverObj.drive();
});
const staticTourBtn = document.getElementById('static-tour');
staticTourBtn.addEventListener("click", () => {
const driverObj = driver({
animate: false,
popoverClass: "driverjs-theme",
showButtons: ["next", "previous"],
steps: [
{
element: "[data-examples-heading]",
popover: {
title: "Static Tour",
description: "This is a static tour, which means that it won't be animated.",
side: "bottom",
align: "start"
}
},
{
element: "[data-examples-tagline]",
popover: {
title: "Just an Example",
description: "We don't have many steps in this example, but you can have as many as you want.",
side: "bottom",
align: "start"
}
},
{
element: "[data-examples-tagline] a",
popover: {
title: "That's it for now",
description: "Make sure to visit the docs for more examples and use-cases.",
side: "bottom",
align: "start"
}
},
],
onDestroyed: () => {
markDone(staticTourBtn);
}
});
driverObj.drive();
});
const asyncTourBtn = document.getElementById('async-tour');
asyncTourBtn.addEventListener("click", () => {
const driverObj = driver({
animate: true,
popoverClass: "driverjs-theme",
showButtons: ["next", "previous"],
steps: [
{
popover: {
title: "Async Tour",
description: "You can also use Driver.js to create async tours. Element for the next step doesn't exist -- we will create before moving next.",
side: "bottom",
align: "start",
onNextClick: () => {
mountDummyElement();
driverObj.moveNext();
}
}
},
{
element: '.dynamic-el',
popover: {
title: "New Element",
description: "This element was created after the first step and will be destroyed after this step.",
side: "bottom",
align: "start",
onPrevClick: () => {
removeDummyElement();
driverObj.movePrevious();
},
onNextClick: () => {
removeDummyElement();
driverObj.moveNext();
}
}
},
{
popover: {
title: "More in Docs",
description: "There is a detailed guide on <a href='/docs/async-tour/' target='_blank' class='underline font-bold'>how to create async</a> tours in the documentation.",
side: "bottom",
align: "start",
onPrevClick: () => {
mountDummyElement();
driverObj.movePrevious();
}
}
},
],
onDestroyed: () => {
markDone(asyncTourBtn);
}
});
driverObj.drive();
});
const exitConfirm = document.getElementById('confirm-on-exit');
exitConfirm.addEventListener("click", () => {
const driverObj = driver({
animate: true,
popoverClass: "driverjs-theme",
showButtons: ["next", "previous"],
steps: [
{
element: "[data-examples-heading]",
popover: {
title: "Confirm on Exit",
description: "You can also ask for confirmation before exiting the tour.",
side: "bottom",
align: "start"
}
},
{
element: "[data-examples-tagline]",
popover: {
title: "Just an Example",
description: "We don't have many steps in this example, but you can have as many as you want.",
side: "bottom",
align: "start"
}
},
{
element: "[data-examples-tagline] a",
popover: {
title: "That's it for now",
description: "Make sure to visit the docs for more examples and use-cases.",
side: "bottom",
align: "start"
}
},
],
onDestroyStarted: () => {
if (!driverObj.hasNextStep() || confirm("Are you sure?")) {
driverObj.destroy();
}
},
onDestroyed: () => {
markDone(exitConfirm);
}
});
driverObj.drive();
});
const showProgressBtn = document.getElementById('show-progress');
showProgressBtn.addEventListener('click', () => {
const driverObj = driver({
animate: true,
showProgress: true,
showButtons: ["next", "previous"],
steps: [
{
element: "[data-examples-heading]",
popover: {
title: "Show Progress",
description: "You can set `showProgress` to `true` and progress will be shown in the footer.",
side: "bottom",
align: "start"
}
},
{
element: "[data-examples-tagline]",
popover: {
title: "Just an Example",
description: "We don't have many steps in this example, but you can have as many as you want.",
side: "bottom",
align: "start"
}
},
{
element: "[data-examples-tagline] a",
popover: {
title: "That's it for now",
description: "Make sure to visit the docs for more examples and use-cases.",
side: "bottom",
align: "start"
}
},
],
onDestroyed: () => {
markDone(showProgressBtn);
}
});
driverObj.drive();
});
const simpleHighlightBtn = document.getElementById('simple-element-highlight');
simpleHighlightBtn.addEventListener('click', () => {
const driverObj = driver({
popoverClass: 'driverjs-theme',
onDestroyed: () => {
markDone(simpleHighlightBtn);
}
});
driverObj.highlight({
element: '[data-example-btns]'
})
});
const simpleHighlightPopoverBtn = document.getElementById('simple-element-highlight-popover');
simpleHighlightPopoverBtn.addEventListener('click', () => {
const driverObj = driver({
popoverClass: 'driverjs-theme',
onDestroyed: () => {
markDone(simpleHighlightPopoverBtn);
}
});
driverObj.highlight({
element: '[data-example-btns]',
popover: {
title: "Popover Highlight",
description: "You can also highlight an element with a popover.",
side: 'top'
}
});
});
const noElementbtn = document.getElementById('no-element');
noElementbtn.addEventListener('click', () => {
const driverObj = driver({
popoverClass: 'driverjs-theme',
onDestroyed: () => {
markDone(noElementbtn);
}
});
driverObj.highlight({
popover: {
title: "Without Element",
description: "You can also show a popover without highlighting an element. For example, this popover is shown without highlighting anything.",
}
});
});
const preventCloseBtn = document.getElementById('prevent-close');
preventCloseBtn.addEventListener('click', () => {
const driverObj = driver({
animate: true,
allowClose: false,
showProgress: true,
showButtons: ["next", "previous"],
steps: [
{
element: "[data-examples-heading]",
popover: {
title: "Show Progress",
description: "You can set `showProgress` to `true` and progress will be shown in the footer.",
side: "bottom",
align: "start"
}
},
{
element: "[data-examples-tagline]",
popover: {
title: "Just an Example",
description: "We don't have many steps in this example, but you can have as many as you want.",
side: "bottom",
align: "start"
}
},
{
element: "[data-examples-tagline] a",
popover: {
title: "That's it for now",
description: "Make sure to visit the docs for more examples and use-cases.",
side: "bottom",
align: "start"
}
},
],
onDestroyed: () => {
markDone(preventCloseBtn);
}
});
driverObj.drive();
});
const overlayColorBtn = document.getElementById('overlay-color');
overlayColorBtn.addEventListener('click', () => {
const driverObj = driver({
overlayColor: 'red',
overlayOpacity: 0.5,
onDestroyed: () => {
markDone(overlayColorBtn);
}
});
driverObj.highlight({
element: '[data-example-btns]',
popover: {
title: "Popover Highlight",
description: "You can also highlight an element with a popover.",
side: 'top'
}
});
});
const popoverPositionBtn = document.getElementById('popover-position');
popoverPositionBtn.addEventListener('click', () => {
const driverObj = driver({
onDestroyed: () => {
markDone(popoverPositionBtn);
}
});
driverObj.highlight({
element: '#popover-position',
popover: {
title: "Popover Position",
description: "You can also change the position of the popover using `side` and `align` options.<br /> Allowed sides are `top`, `bottom`, `left` and `right`. Allowed aligns are `start`, `center` and `end`.",
side: 'top',
align: 'start'
}
});
});
const customizingBtn = document.getElementById('customizing-popover');
customizingBtn.addEventListener('click', () => {
const driverObj = driver({
popoverClass: 'driverjs-theme',
onDestroyed: () => {
markDone(customizingBtn);
}
});
driverObj.highlight({
element: '#customizing-popover',
popover: {
title: "Customizing Popover",
description: "Add your own class using `popoverClass` or use `onPopoverRender` to get full control over the popover. <br /><br /> Visit these pages which cover <a class='font-medium underline' href='/docs/styling-popover'>styling</a> and <a class='font-medium underline' href='/docs/buttons'>customizing popovers</a> in detail.",
side: 'top',
align: 'start'
}
});
});
const hooksEverythingBtn = document.getElementById('hooks-everything');
hooksEverythingBtn.addEventListener('click', () => {
const driverObj = driver({
popoverClass: 'driverjs-theme',
onDestroyed: () => {
markDone(hooksEverythingBtn);
}
});
driverObj.highlight({
element: '#hooks-everything',
popover: {
title: "Hooks for Everything",
description: "Have a look at the <a class='font-medium underline' href='/docs/configuration'>configuration</a> page to see how you can use hooks to control the driver.",
side: 'top',
align: 'start'
}
});
});
</script>
================================================
FILE: docs/src/components/FeatureMarquee.tsx
================================================
import React from "react";
import Marquee from "react-fast-marquee";
const featureList = [
"Supports all Major Browsers",
"Works on Mobile Devices",
"Highly Customizable",
"Light-weight",
"No dependencies",
"Feature Rich",
"MIT Licensed",
"Written in TypeScript",
"Vanilla JavaScript",
"Easy to use",
"Accessible",
"Frameworks Ready",
];
export function FeatureMarquee() {
return (
<Marquee autoFill>
<p className="py-2.5 md:py-3.5 lg:py-4 text-lg md:text-xl lg:text-2xl whitespace-nowrap">
{ featureList.map((featureItem, index) => (
<React.Fragment key={index}>
{ featureItem }<span className="mx-2 md:mx-3">·</span>
</React.Fragment>
))}
</p>
</Marquee>
);
}
================================================
FILE: docs/src/components/Features.astro
================================================
---
import { Earth, Smartphone, Settings, Feather, Code2, Layers, Keyboard } from "lucide-react";
import Container from "./Container.astro";
---
<div class="py-0 mb-16 bg-white">
<Container>
<div class="max-w-screen-lg">
<h2 class="text-4xl md:text-5xl lg:text-6xl font-bold text-gray-900 mb-4 md:mb-6">Nothing else like it</h2>
<p class="text-base md:text-xl lg:text-2xl text-black mb-10 md:mb-14 lg:mb-16">
Lightweight with no external dependencies, supports all major browsers and is highly customizable.
</p>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8 md:gap-10 lg:gap-4">
<div class="group bg-yellow-50 p-6 rounded-xl transition-colors">
<div class="flex justify-center items-center mb-4 w-12 h-12 rounded-xl bg-yellow-300 lg:h-14 lg:w-14">
<Earth className="w-6 h-6 text-black lg:w-7 lg:h-7" />
</div>
<h3 class="mb-3 text-xl md:text-2xl font-bold text-black">Browser Support</h3>
<p class="text-gray-600 md:text-lg">
Works in all modern browsers including Chrome, IE9+, Safari, Firefox and Opera
</p>
</div>
<div class="group bg-yellow-50 p-6 rounded-xl transition-colors">
<div class="flex justify-center items-center mb-4 w-12 h-12 rounded-xl bg-yellow-300 lg:h-14 lg:w-14">
<Smartphone className="w-6 h-6 text-black lg:w-7 lg:h-7" />
</div>
<h3 class="mb-3 text-xl md:text-2xl font-bold text-black">Mobile Ready</h3>
<p class="text-gray-600 md:text-lg">Works on desktop, tablets and mobile devices</p>
</div>
<div class="group bg-yellow-50 p-6 rounded-xl transition-colors">
<div class="flex justify-center items-center mb-4 w-12 h-12 rounded-xl bg-yellow-300 lg:h-14 lg:w-14">
<Settings className="w-6 h-6 text-black lg:w-7 lg:h-7" />
</div>
<h3 class="mb-3 text-xl md:text-2xl font-bold text-black">Highly Customizable</h3>
<p class="text-gray-600 md:text-lg">Powerful API that allows you to customize it to your needs</p>
</div>
<div class="group bg-yellow-50 p-6 rounded-xl transition-colors">
<div class="flex justify-center items-center mb-4 w-12 h-12 rounded-xl bg-yellow-300 lg:h-14 lg:w-14">
<Feather className="w-6 h-6 text-black lg:w-7 lg:h-7" />
</div>
<h3 class="mb-3 text-xl md:text-2xl font-bold text-black">Lightweight</h3>
<p class="text-gray-600 md:text-lg">
Only 5KB minified, compared to other libraries which are typically >12KB minified
</p>
</div>
<div class="group bg-yellow-50 p-6 rounded-xl transition-colors">
<div class="flex justify-center items-center mb-4 w-12 h-12 rounded-xl bg-yellow-300 lg:h-14 lg:w-14">
<Code2 className="w-6 h-6 text-black lg:w-7 lg:h-7" />
</div>
<h3 class="mb-3 text-xl md:text-2xl font-bold text-black">No Dependencies</h3>
<p class="text-gray-600 md:text-lg">Simple to use with absolutely no external dependencies</p>
</div>
<div class="group bg-yellow-50 p-6 rounded-xl transition-colors">
<div class="flex justify-center items-center mb-4 w-12 h-12 rounded-xl bg-yellow-300 lg:h-14 lg:w-14">
<Layers className="w-6 h-6 text-black lg:w-7 lg:h-7" />
</div>
<h3 class="mb-3 text-xl md:text-2xl font-bold text-black">Feature Rich</h3>
<p class="text-gray-600 md:text-lg">Create powerful feature introductions for your web applications</p>
</div>
<div class="group bg-yellow-50 p-6 rounded-xl transition-colors">
<div class="flex justify-center items-center mb-4 w-12 h-12 rounded-xl bg-yellow-300 lg:h-14 lg:w-14">
<span class="w-6 h-6 text-black lg:w-7 lg:h-7 font-black">MIT</span>
</div>
<h3 class="mb-3 text-xl md:text-2xl font-bold text-black">MIT License</h3>
<p class="text-gray-600 md:text-lg">Free for both personal and commercial use</p>
</div>
<div class="group bg-yellow-50 p-6 rounded-xl transition-colors">
<div class="flex justify-center items-center mb-4 w-12 h-12 rounded-xl bg-yellow-300 lg:h-14 lg:w-14">
<Keyboard className="w-6 h-6 text-black lg:w-7 lg:h-7" />
</div>
<h3 class="mb-3 text-xl md:text-2xl font-bold text-black">Keyboard Control</h3>
<p class="text-gray-600 md:text-lg">All actions can be controlled via keyboard</p>
</div>
<div class="group bg-yellow-50 p-6 rounded-xl transition-colors">
<div class="flex justify-center items-center mb-4 w-12 h-12 rounded-xl bg-yellow-300 lg:h-14 lg:w-14">
<span class="w-6 h-6 text-black lg:w-7 lg:h-7 font-black">ALL</span>
</div>
<h3 class="mb-3 text-xl md:text-2xl font-bold text-black">Highlight Anything</h3>
<p class="text-gray-600 md:text-lg">Highlight any element on the page</p>
</div>
</div>
</Container>
</div>
================================================
FILE: docs/src/components/FormHelp.tsx
================================================
import { useEffect } from "react";
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
export function FormHelp() {
useEffect(() => {
const driverObj = driver({
popoverClass: "driverjs-theme",
stagePadding: 0,
onDestroyed: () => {
(document?.activeElement as any)?.blur();
}
});
const nameEl = document.getElementById("name");
const educationEl = document.getElementById("education");
const ageEl = document.getElementById("age");
const addressEl = document.getElementById("address");
const submitEl = document.getElementById("submit-btn");
nameEl!.addEventListener("focus", () => {
driverObj.highlight({
element: nameEl!,
popover: {
title: "Name",
description: "Enter your name here",
},
});
});
educationEl!.addEventListener("focus", () => {
driverObj.highlight({
element: educationEl!,
popover: {
title: "Education",
description: "Enter your education here",
},
});
});
ageEl!.addEventListener("focus", () => {
driverObj.highlight({
element: ageEl!,
popover: {
title: "Age",
description: "Enter your age here",
},
});
});
addressEl!.addEventListener("focus", () => {
driverObj.highlight({
element: addressEl!,
popover: {
title: "Address",
description: "Enter your address here",
},
});
});
submitEl!.addEventListener("focus", (e) => {
e.preventDefault();
driverObj.destroy();
});
});
return <></>;
}
================================================
FILE: docs/src/components/HeroSection.astro
================================================
---
import Container from "./Container.astro";
---
<div class="bg-white border-b border-gray-100 select-none">
<Container>
<div class="flex items-center justify-between h-16">
<a href="/" class="flex items-center justify-end text-xl font-bold text-gray-900 font-semibold gap-2.5">
<img src="/favicon.svg" alt="driver.js logo" class="h-10" />
driver.js
</a>
<span class="flex items-center gap-7">
<a href="/docs/installation" class="hover:underline underline-offset-4 text-lg font-medium text-gray-900">
<span class="hidden sm:inline">Documentation</span>
<span class="inline sm:hidden">Docs</span>
</a>
<a href="https://github.com/kamranahmedse/driver.js" target="_blank" class="hover:underline underline-offset-4 text-lg font-medium text-gray-900">GitHub</a>
</span>
</div>
</Container>
</div>
<div class="bg-yellow-300/80 overflow-hidden via-transparent">
<Container>
<div class="py-10 md:py-14 lg:py-20 flex justify-start items-center gap-4">
<div class="flex-grow" data-hero-text>
<h1 data-driver-name class="text-7xl md:text-8xl lg:text-9xl mb-2 md:mb-3 lg:mb-4 font-bold">driver.js</h1>
<p data-driver-tagline class="text-base md:text-2xl lg:text-3xl !leading-normal">
Lightweight JavaScript library for product tours, highlights, and contextual help to guide users through your
product.
</p>
<div class="mt-4 md:mt-8 lg:mt-10 flex flex-col sm:flex-row gap-2 items-stretch">
<button
data-demo-tour
class="bg-black rounded-xl py-2 md:py-3 px-6 font-medium text-white text-lg md:text-xl focus:outline-0 hover:bg-gray-800 focus:bg-gray-800"
>
Show Demo
</button>
<a
href="/docs/installation"
data-docs-link
class="bg-white rounded-xl py-2 md:py-3 px-6 font-medium text-black text-lg md:text-xl focus:outline-0 border-2 border-black text-center hover:bg-gray-200 focus:bg-gray-200"
>
Get Started
</a>
</div>
</div>
<div class="flex-shrink-0 hidden sm:flex">
<img src="/driver.svg" alt="driver.js image" class="sm:h-48 md:h-60 lg:h-72" />
</div>
</div>
</Container>
</div>
================================================
FILE: docs/src/components/OpenSourceLove.astro
================================================
---
import Container from "./Container.astro";
import { getFormattedStars } from "../lib/github";
const starCount = getFormattedStars('kamranahmedse/driver.js');
---
<div class="py-10 md:py-12 lg:py-24 bg-white text-black border-t">
<Container>
<div class="flex items-center">
<div>
<h2 class="text-3xl md:text-4xl lg:text-6xl font-bold mb-4">Loved by Many</h2>
<p class="md:text-xl lg:text-2xl text-black mb-6 lg:mb-8">With millions of downloads, Driver.js is an <span class="font-bold">MIT licensed</span>
opensource
project and is used by
thousands of companies around the world.</p>
<div class="flex flex-col sm:flex-row gap-2 md:gap-3">
<a href="https://github.com/kamranahmedse/driver.js"
data-github-link
target="_blank"
class="flex justify-center items-center font-medium text-lg md:text-xl lg:text-2xl rounded-lg lg:rounded-xl py-2 lg:py-3 px-5 bg-yellow-300 border-black hover:bg-yellow-400">
<span class="mr-3 inline-flex items-center"><img src="/star.svg" alt="Hero Image" class="h-5 md:h-7 mr-1 md:mr-2" /> { starCount }</span>
GitHub Stars
</a>
<a href="/docs/installation"
class="bg-black justify-center text-white flex items-center font-medium text-lg md:text-xl lg:text-2xl border-4 border-black rounded-lg lg:rounded-xl py-2 lg:py-3 px-5 hover:bg-gray-800">
Start Using Driver.js
</a>
</div>
</div>
<img src="/thumbs.svg" alt="Hero Image" class="hidden lg:block h-36 ml-16" />
</div>
</Container>
</div>
================================================
FILE: docs/src/components/Sidebar.astro
================================================
---
import { getCollection, getEntry } from "astro:content";
import { getFormattedStars } from "../lib/github";
import { getAllGuides } from "../lib/guide";
export interface Props {
activePath: string;
}
const { activePath, groupedGuides, activeGuideTitle } = Astro.props;
---
<div id="docs-sidebar" class="hidden md:block w-[200px] lg:w-[350px] border-r border-gray-200 text-right min-h-screen flex-shrink-0">
<div class="relative sh:sticky top-0">
<div class="justify-end flex pt-10 pb-5 px-5">
<a href="/" class="block items-center justify-end mb-2">
<img src="/driver-head.svg" alt="Astro" class="w-16 h-16" />
</a>
</div>
{Object.keys(groupedGuides).map(groupTitle => {
const guides = groupedGuides[groupTitle];
return (
<>
<h2 class="text-xl font-bold mb-2 pr-5 relative">{groupTitle}</h2>
<ul class="text-gray-400 mb-5">
{guides.map(guide => {
const guidePath = `/docs/${guide.slug}`;
return (
<li class="mb-2">
<a href={guidePath}
class:list={["hover:text-black pr-5 py-2", { "text-black": activeGuideTitle === guide.data.title }]}>{guide.data.title}</a>
</li>
);
})}
</ul>
</>
);
})}
</div>
</div>
================================================
FILE: docs/src/components/UsecaseItem.astro
================================================
---
export interface Props {
title: string;
description: string;
}
const { title, description } = Astro.props;
---
<div class="flex flex-col gap-2 md:gap-2 lg:gap-4 pt-2">
<span class="border-b-2 border-b-black block w-[30px] md:w-[40px] lg:w-[50px]"></span>
<h3 class="text-xl md:text-2xl lg:text-3xl mt-2 md:mt-3 lg:mt-0 font-bold text-black">
{ title }
</h3>
<p class="md:text-lg lg:text-xl text-black">
{ description }
</p>
</div>
================================================
FILE: docs/src/components/UsecaseList.astro
================================================
---
import UsecaseItem from "./UsecaseItem.astro";
---
<p class="text-base md:text-xl lg:text-2xl text-black">Due to its extensive API, driver.js can be used for a wide range of use
cases.</p>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 md:gap-6 lg:gap-12 mt-10">
<UsecaseItem
title="Onboard Users"
description="Onboard your users by explaining how to use your product and answer common questions."
/>
<UsecaseItem
title="Remove Distractions"
description="With highlight feature, you can remove distractions and focus your users attention on what matters."
/>
<UsecaseItem
title="Contextual Help"
description="Provide contextual help for your users, explain how to use your product and answer common questions."
/>
<UsecaseItem
title="Feature Adoption"
description="Highlight new features, explain how to use them and make sure your users don't miss them."
/>
</div>
================================================
FILE: docs/src/content/config.ts
================================================
import { z, defineCollection } from "astro:content";
const guidesCollection = defineCollection({
type: "content",
schema: z.object({
groupTitle: z.string(),
title: z.string(),
sort: z.number(),
}),
});
export const collections = {
guides: guidesCollection,
};
================================================
FILE: docs/src/content/guides/animated-tour.mdx
================================================
---
title: "Animated Tour"
groupTitle: "Examples"
sort: 2
---
import { CodeSample } from "../../components/CodeSample.tsx";
The following example shows how to create a simple tour with a few steps. Click the button below the code sample to see the tour in action.
<CodeSample
heading={'Basic Animated Tour'}
config={{
animate: true,
showProgress: true,
showButtons: ['next', 'previous'],
}}
tour={[
{ element: '#tour-example', popover: { title: 'Animated Tour Example', description: 'Here is the code example showing animated tour. Let\'s walk you through it.', side: "left", align: 'start' }},
{ element: '.line:nth-child(1)', popover: { title: 'Import the Library', description: 'It works the same in vanilla JavaScript as well as frameworks.', side: "bottom", align: 'start' }},
{ element: '.line:nth-child(2)', popover: { title: 'Importing CSS', description: 'Import the CSS which gives you the default styling for popover and overlay.', side: "bottom", align: 'start' }},
{ element: '.line:nth-child(4) span:nth-child(7)', popover: { title: 'Create Driver', description: 'Simply call the driver function to create a driver.js instance', side: "left", align: 'start' }},
{ element: '.line:nth-child(17)', popover: { title: 'Start Tour', description: 'Call the drive method to start the tour and your tour will be started.', side: "top", align: 'start' }},
{ element: '.line:nth-child(5)', popover: { title: 'Hide Progress', description: 'Progress shown in the bottom left is hidden by default. You can make driver show/hide the progress using this option.', side: "top", align: 'start' }},
{ element: '#docs-sidebar a[href="/docs/configuration"]', popover: { title: 'More Configuration', description: 'Look at this page for all the configuration options you can pass.', side: "right", align: 'start' }},
{ popover: { title: 'Happy Coding', description: 'And that is all, go ahead and start adding tours to your applications.' } }
]}
id={"tour-example"}
client:load
>
```js
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
const driverObj = driver({
showProgress: true,
steps: [
{ element: '#tour-example', popover: { title: 'Animated Tour Example', description: 'Here is the code example showing animated tour. Let\'s walk you through it.', side: "left", align: 'start' }},
{ element: 'code .line:nth-child(1)', popover: { title: 'Import the Library', description: 'It works the same in vanilla JavaScript as well as frameworks.', side: "bottom", align: 'start' }},
{ element: 'code .line:nth-child(2)', popover: { title: 'Importing CSS', description: 'Import the CSS which gives you the default styling for popover and overlay.', side: "bottom", align: 'start' }},
{ element: 'code .line:nth-child(4) span:nth-child(7)', popover: { title: 'Create Driver', description: 'Simply call the driver function to create a driver.js instance', side: "left", align: 'start' }},
{ element: 'code .line:nth-child(18)', popover: { title: 'Start Tour', description: 'Call the drive method to start the tour and your tour will be started.', side: "top", align: 'start' }},
{ element: 'a[href="/docs/configuration"]', popover: { title: 'More Configuration', description: 'Look at this page for all the configuration options you can pass.', side: "right", align: 'start' }},
{ popover: { title: 'Happy Coding', description: 'And that is all, go ahead and start adding tours to your applications.' } }
]
});
driverObj.drive();
```
</CodeSample>
================================================
FILE: docs/src/content/guides/api.mdx
================================================
---
title: "API Reference"
groupTitle: "Introduction"
sort: 4
---
Here is the list of methods provided by `driver` when you initialize it.
> **Note:** We have omitted the configuration options for brevity. Please look at the configuration section for the options. Links are provided in the description below.
```javascript
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
// Look at the configuration section for the options
// https://driverjs.com/docs/configuration#driver-configuration
const driverObj = driver({ /* ... */ });
// --------------------------------------------------
// driverObj is an object with the following methods
// --------------------------------------------------
// Start the tour using `steps` given in the configuration
driverObj.drive(); // Starts at step 0
driverObj.drive(4); // Starts at step 4
driverObj.moveNext(); // Move to the next step
driverObj.movePrevious(); // Move to the previous step
driverObj.moveTo(4); // Move to the step 4
driverObj.hasNextStep(); // Is there a next step
driverObj.hasPreviousStep() // Is there a previous step
driverObj.isFirstStep(); // Is the current step the first step
driverObj.isLastStep(); // Is the current step the last step
driverObj.getActiveIndex(); // Gets the active step index
driverObj.getActiveStep(); // Gets the active step configuration
driverObj.getPreviousStep(); // Gets the previous step configuration
driverObj.getActiveElement(); // Gets the active HTML element
driverObj.getPreviousElement(); // Gets the previous HTML element
// Is the tour or highlight currently active
driverObj.isActive();
// Recalculate and redraw the highlight
driverObj.refresh();
// Look at the configuration section for configuration options
// https://driverjs.com/docs/configuration#driver-configuration
driverObj.getConfig();
driverObj.setConfig({ /* ... */ });
driverObj.setSteps([ /* ... */ ]); // Set the steps
// Look at the state section of configuration for format of the state
// https://driverjs.com/docs/configuration#state
driverObj.getState();
// Look at the DriveStep section of configuration for format of the step
// https://driverjs.com/docs/configuration/#drive-step-configuration
driverObj.highlight({ /* ... */ }); // Highlight an element
driverObj.destroy(); // Destroy the tour
```
================================================
FILE: docs/src/content/guides/async-tour.mdx
================================================
---
title: "Async Tour"
groupTitle: "Examples"
sort: 3
---
import { CodeSample } from "../../components/CodeSample.tsx";
You can also have async steps in your tour. This is useful when you want to load some data from the server and then show the tour.
<CodeSample
heading={'Asynchronous Tour'}
tour={[
{
element: '.line:nth-child(14)',
popover: {
title: 'First Step',
description: 'You can add a function to override the default behavior of the next button i.e. to fetch some data from the server and then call moveNext()',
}
},
{
element: '.line:nth-child(17)',
popover: {
title: 'Manually Handle Next',
description: 'Here we are moving to the next step manually since driver.js does not know when the data is loaded dynamically.',
}
},
{
popover: {
title: 'Next Step is Async',
description: 'This is the first step. Next element will be loaded dynamically.',
},
},
{ element: '.dynamic-el', popover: { title: 'Async Element', description: 'This element is loaded dynamically and will be removed as soon as we move away from this step' } },
{ popover: { title: 'Last Step', description: 'This is the last step.' } }
]}
id={"tour-example"}
client:load>
```js
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
const driverObj = driver({
showProgress: true,
steps: [
{
popover: {
title: 'First Step',
description: 'This is the first step. Next element will be loaded dynamically.'
// By passing onNextClick, you can override the default behavior of the next button.
// This will prevent the driver from moving to the next step automatically.
// You can then manually call driverObj.moveNext() to move to the next step.
onNextClick: () => {
// .. load element dynamically
// .. and then call
driverObj.moveNext();
},
},
},
{
element: '.dynamic-el',
popover: {
title: 'Async Element',
description: 'This element is loaded dynamically.'
},
// onDeselected is called when the element is deselected.
// Here we are simply removing the element from the DOM.
onDeselected: () => {
// .. remove element
document.querySelector(".dynamic-el")?.remove();
}
},
{ popover: { title: 'Last Step', description: 'This is the last step.' } }
]
});
driverObj.drive();
```
</CodeSample>
> **Note**: By overriding `onNextClick`, and `onPrevClick` hooks you control the navigation of the driver. This means that user won't be able to navigate using the buttons and you will have to either call `driverObj.moveNext()` or `driverObj.movePrevious()` to navigate to the next/previous step.
>
> You can use this to implement custom logic for navigating between steps. This is also useful when you are dealing with dynamic content and want to highlight the next/previous element based on some logic.
>
> `onNextClick` and `onPrevClick` hooks can be configured at driver level as well as step level. When configured at the driver level, you control the navigation for all the steps. When configured at the step level, you control the navigation for that particular step only.
================================================
FILE: docs/src/content/guides/basic-usage.mdx
================================================
---
title: "Basic Usage"
groupTitle: "Introduction"
sort: 2
---
import { CodeSample } from "../../components/CodeSample.tsx";
Once installed, you can import and start using the library. There are several different configuration options available to customize the library. You can find more details about the options in the [configuration section](/docs/configuration). Given below are the basic steps to get started.
Here is a simple example of how to create a tour with multiple steps.
<CodeSample
heading={'Basic Tour Example'}
config={{
showProgress: true,
}}
tour={[
{ element: '#tour-example', popover: { title: 'Highlight Anything', description: 'You can highlight anything on the page.' }},
{ element: '#tour-example pre', popover: { title: 'Control with Keyboard', description: 'You can use your keyboard to highlight elements.' }},
{ popover: { title: 'Control with Keyboard', description: 'You can use your keyboard to highlight elements.' }},
{ element: '#tour-example code .line:first-child', popover: { title: 'Control with Code', description: 'You can use the code to highlight elements.' }},
{ element: '#tour-example code .line:nth-child(2)', popover: { title: 'Control with Code', description: 'You can use the code to highlight elements.', side: "bottom", align: 'start' }},
]}
id={"tour-example"}
client:load
>
```js
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
const driverObj = driver({
showProgress: true,
steps: [
{ element: '.page-header', popover: { title: 'Title', description: 'Description' } },
{ element: '.top-nav', popover: { title: 'Title', description: 'Description' } },
{ element: '.sidebar', popover: { title: 'Title', description: 'Description' } },
{ element: '.footer', popover: { title: 'Title', description: 'Description' } },
]
});
driverObj.drive();
```
</CodeSample>
You can pass a single step configuration to the `highlight` method to highlight a single element. Given below is a simple example of how to highlight a single element.
<CodeSample heading='Highlighting a simple Element' id={"some-element"} highlight={{
element: '#some-element',
popover: {
title: 'Title for the Popover',
description: 'Description for it',
},
}} client:load>
```js
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
const driverObj = driver();
driverObj.highlight({
element: '#some-element',
popover: {
title: 'Title for the Popover',
description: 'Description for it',
},
});
```
</CodeSample>
The same configuration passed to the `highlight` method can be used to create a tour. Given below is a simple example of how to create a tour with a single step.
Examples above show the basic usage of the library. Find more details about the configuration options in the [configuration section](/docs/configuration) and the examples in the [examples section](/docs/examples).
================================================
FILE: docs/src/content/guides/buttons.mdx
================================================
---
title: "Popover Buttons"
groupTitle: "Examples"
sort: 9
---
import { CodeSample } from "../../components/CodeSample.tsx";
You can use the `showButtons` option to choose which buttons to show in the popover. The default value is `['next', 'previous', 'close']`.
<div id="driver-note" className="mb-5">
> **Note:** When using the `highlight` method to highlight a single element, the only button shown is the `close`
button. However, you can use the `showButtons` option to show other buttons as well. But the buttons won't do
anything. You will have to use the `onNextClick` and `onPrevClick` callbacks to implement the functionality.
</div>
<div className='flex flex-col gap-1'>
<CodeSample
buttonText={"Show All Buttons"}
config={{
showButtons: [
'next',
'previous',
'close'
],
}}
tour={[
{
element: '#driver-note',
popover: {
title: 'Popover Title',
description: 'Popover Description'
}
},
{
element: '#driver-note p code:nth-child(4)',
popover: {
title: 'Popover Title',
description: 'Popover Description'
}
}
]}
id={"code-sample"}
client:load>
```js
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
const driverObj = driver({
showButtons: [
'next',
'previous',
'close'
],
steps: [
{
element: '#first-element',
popover: {
title: 'Popover Title',
description: 'Popover Description'
}
},
{
element: '#second-element',
popover: {
title: 'Popover Title',
description: 'Popover Description'
}
}
]
});
driverObj.drive();
```
</CodeSample>
<CodeSample
buttonText="No Close Button"
config={{
showButtons: [
'next',
'previous',
],
}}
tour={[
{
element: '#driver-note',
popover: {
title: 'Popover Title',
description: 'Popover Description'
}
},
{
element: '#driver-note code:nth-child(2)',
popover: {
title: 'Popover Title',
description: 'Popover Description'
}
}
]}
id={"code-sample"}
client:load />
<CodeSample
buttonText="No Buttons (Use Arrows)"
config={{
showButtons: [undefined],
}}
tour={[
{
element: '#driver-note',
popover: {
title: 'Popover Title',
description: 'Popover Description'
}
},
{
element: '#driver-note code:nth-child(2)',
popover: {
title: 'Popover Title',
description: 'Popover Description',
side: 'bottom',
align: 'start'
}
}
]}
id={"code-sample"}
client:load />
</div>
## Change Button Text
You can also change the text of buttons using `nextBtnText`, `prevBtnText` and `doneBtnText` options.
<div className='flex flex-col gap-1'>
<CodeSample
heading="Change Button Text"
buttonText={"Change Button Text"}
config={{
showProgress: true,
nextBtnText: '—>',
prevBtnText: '<--',
doneBtnText: 'X',
}}
tour={[
{
element: '#code-sample-3',
popover: {
title: 'Popover Title',
description: 'Popover Description'
}
},
{
element: '#code-sample-3 code',
popover: {
title: 'Popover Title',
description: 'Popover Description'
}
}
]}
id={"code-sample-3"}
client:load>
```js
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
const driverObj = driver({
nextBtnText: '—›',
prevBtnText: '‹—',
doneBtnText: '✕',
showProgress: true,
steps: [
// ...
]
});
driverObj.drive();
```
</CodeSample>
</div>
## Event Handlers
You can use the `onNextClick`, `onPreviousClick` and `onCloseClick` callbacks to implement custom functionality when the user clicks on the next and previous buttons.
> Please note that when you configure these callbacks, the default functionality of the buttons will be disabled. You will have to implement the functionality yourself.
<CodeSample
buttonText={"Show Example"}
config={{}}
tour={[
{
element: '#logger-events',
popover: {
title: 'Events Logged',
description: 'Look at your console for the events logged'
}
},
{
element: '#code-sample-4 code',
popover: {
title: 'Popover Title',
description: 'Popover Description'
}
}
]}
id={"logger-events"}
client:load>
```js
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
const driverObj = driver({
onNextClick:() => {
console.log('Next Button Clicked');
// Implement your own functionality here
driverObj.moveNext();
},
onPrevClick:() => {
console.log('Previous Button Clicked');
// Implement your own functionality here
driverObj.movePrevious();
},
onCloseClick:() => {
console.log('Close Button Clicked');
// Implement your own functionality here
driverObj.destroy();
},
steps: [
// ...
]
});
driverObj.drive();
```
</CodeSample>
## Custom Buttons
You can add custom buttons using `onPopoverRender` callback. This callback is called before the popover is rendered. In the following example, we are adding a custom button that takes the user to the first step.
<CodeSample
buttonText={"Run Example"}
config={{
prevBtnText: '← Previous',
nextBtnText: 'Next →',
doneBtnText: 'Done',
showButtons: ['next', 'previous'],
}}
tour={[
{
element: '#demo-hook-theme',
popover: {
align: 'start',
side: 'left',
title: 'More Control with Hooks',
description: 'You can use onPopoverRender hook to modify the popover DOM. Here we are adding a custom button to the popover which takes the user to the first step.'
}
},
{
element: 'h1',
popover: {
align: 'start',
side: 'bottom',
title: 'Style However You Want',
description: 'You can use the default class names and override the styles or you can pass a custom class name to the popoverClass option either globally or per step.'
}
},
{
element: 'p a',
popover: {
align: 'start',
side: 'left',
title: 'Style However You Want',
description: 'You can use the default class names and override the styles or you can pass a custom class name to the popoverClass option either globally or per step.'
}
}
]}
id={"demo-hook-theme"}
client:load
>
```js
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
const driverObj = driver({
// Get full control over the popover rendering.
// Here we are adding a custom button that takes
// user to the first step.
onPopoverRender: (popover, { config, state }) => {
const firstButton = document.createElement("button");
firstButton.innerText = "Go to First";
popover.footerButtons.appendChild(firstButton);
firstButton.addEventListener("click", () => {
driverObj.drive(0);
});
},
steps: [
// ..
]
});
driverObj.drive();
```
</CodeSample>
================================================
FILE: docs/src/content/guides/configuration.mdx
================================================
---
title: "Configuration"
groupTitle: "Introduction"
sort: 3
---
import { CodeSample } from "../../components/CodeSample.tsx";
Driver.js is built to be highly configurable. You can configure the driver globally, or per step. You can also configure the driver on the fly, while it's running.
> Driver.js is written in TypeScript. Configuration options are mostly self-explanatory. Also, if you're using an IDE like WebStorm or VSCode, you'll get autocomplete and documentation for all the configuration options.
## Driver Configuration
You can configure the driver globally by passing the configuration object to the `driver` call or by using the `setConfig` method. Given below are some of the available configuration options.
```typescript
type Config = {
// Array of steps to highlight. You should pass
// this when you want to setup a product tour.
steps?: DriveStep[];
// Whether to animate the product tour. (default: true)
animate?: boolean;
// Overlay color. (default: black)
// This is useful when you have a dark background
// and want to highlight elements with a light
// background color.
overlayColor?: string;
// Whether to smooth scroll to the highlighted element. (default: false)
smoothScroll?: boolean;
// Whether to allow closing the popover by clicking on the backdrop. (default: true)
allowClose?: boolean;
// Opacity of the backdrop. (default: 0.5)
overlayOpacity?: number;
// What to do when the overlay backdrop is clicked.
// Possible options are 'close', 'nextStep', or a custom function. (default: 'close')
overlayClickBehavior?: "close" | "nextStep" | ((element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void),
// Distance between the highlighted element and the cutout. (default: 10)
stagePadding?: number;
// Radius of the cutout around the highlighted element. (default: 5)
stageRadius?: number;
// Whether to allow keyboard navigation. (default: true)
allowKeyboardControl?: boolean;
// Whether to disable interaction with the highlighted element. (default: false)
// Can be configured at the step level as well
disableActiveInteraction?: boolean;
// If you want to add custom class to the popover
popoverClass?: string;
// Distance between the popover and the highlighted element. (default: 10)
popoverOffset?: number;
// Array of buttons to show in the popover. Defaults to ["next", "previous", "close"]
// for product tours and [] for single element highlighting.
showButtons?: AllowedButtons[];
// Array of buttons to disable. This is useful when you want to show some of the
// buttons, but disable some of them.
disableButtons?: AllowedButtons[];
// Whether to show the progress text in popover. (default: false)
showProgress?: boolean;
// Template for the progress text. You can use the following placeholders in the template:
// - {{current}}: The current step number
// - {{total}}: Total number of steps
progressText?: string;
// Text to show in the buttons. `doneBtnText`
// is used on the last step of a tour.
nextBtnText?: string;
prevBtnText?: string;
doneBtnText?: string;
// Called after the popover is rendered.
// PopoverDOM is an object with references to
// the popover DOM elements such as buttons
// title, descriptions, body, container etc.
onPopoverRender?: (popover: PopoverDOM, options: { config: Config; state: State, driver: Driver }) => void;
// Hooks to run before and after highlighting
// each step. Each hook receives the following
// parameters:
// - element: The target DOM element of the step
// - step: The step object configured for the step
// - options.config: The current configuration options
// - options.state: The current state of the driver
// - options.driver: Current driver object
onHighlightStarted?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void;
onHighlighted?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void;
onDeselected?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void;
// Hooks to run before and after the driver
// is destroyed. Each hook receives
// the following parameters:
// - element: Currently active element
// - step: The step object configured for the currently active
// - options.config: The current configuration options
// - options.state: The current state of the driver
// - options.driver: Current driver object
onDestroyStarted?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void;
onDestroyed?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void;
// Hooks to run on button clicks. Each hook receives
// the following parameters:
// - element: The current DOM element of the step
// - step: The step object configured for the step
// - options.config: The current configuration options
// - options.state: The current state of the driver
// - options.driver: Current driver object
onNextClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void;
onPrevClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void;
onCloseClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void;
};
```
> **Note**: By overriding `onNextClick`, and `onPrevClick` hooks you control the navigation of the driver. This means that user won't be able to navigate using the buttons and you will have to either call `driverObj.moveNext()` or `driverObj.movePrevious()` to navigate to the next/previous step.
>
> You can use this to implement custom logic for navigating between steps. This is also useful when you are dealing with dynamic content and want to highlight the next/previous element based on some logic.
>
> `onNextClick` and `onPrevClick` hooks can be configured at the step level as well. When configured at the driver level, you control the navigation for all the steps. When configured at the step level, you control the navigation for that particular step only.
## Popover Configuration
The popover is the main UI element of Driver.js. It's the element that highlights the target element, and shows the step content. You can configure the popover globally, or per step. Given below are some of the available configuration options.
```typescript
type Popover = {
// Title and descriptions shown in the popover.
// You can use HTML in these. Also, you can
// omit one of these to show only the other.
title?: string;
description?: string;
// The position and alignment of the popover
// relative to the target element.
side?: "top" | "right" | "bottom" | "left";
align?: "start" | "center" | "end";
// Array of buttons to show in the popover.
// When highlighting a single element, there
// are no buttons by default. When showing
// a tour, the default buttons are "next",
// "previous" and "close".
showButtons?: ("next" | "previous" | "close")[];
// An array of buttons to disable. This is
// useful when you want to show some of the
// buttons, but disable some of them.
disableButtons?: ("next" | "previous" | "close")[];
// Text to show in the buttons. `doneBtnText`
// is used on the last step of a tour.
nextBtnText?: string;
prevBtnText?: string;
doneBtnText?: string;
// Whether to show the progress text in popover.
showProgress?: boolean;
// Template for the progress text. You can use
// the following placeholders in the template:
// - {{current}}: The current step number
// - {{total}}: Total number of steps
// Defaults to following if `showProgress` is true:
// - "{{current}} of {{total}}"
progressText?: string;
// Custom class to add to the popover element.
// This can be used to style the popover.
popoverClass?: string;
// Hook to run after the popover is rendered.
// You can modify the popover element here.
// Parameter is an object with references to
// the popover DOM elements such as buttons
// title, descriptions, body, etc.
onPopoverRender?: (popover: PopoverDOM, options: { config: Config; state: State, driver: Driver }) => void;
// Callbacks for button clicks. You can use
// these to add custom behavior to the buttons.
// Each callback receives the following parameters:
// - element: The current DOM element of the step
// - step: The step object configured for the step
// - options.config: The current configuration options
// - options.state: The current state of the driver
// - options.driver: Current driver object
onNextClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void
onPrevClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void
onCloseClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void
}
```
## Drive Step Configuration
Drive step is the configuration object passed to the `highlight` method or the `steps` array of the `drive` method. You can configure the popover and the target element for each step. Given below are some of the available configuration options.
```typescript
type DriveStep = {
// The target element to highlight.
// This can be a DOM element,
// a function that returns a DOM Element, or a CSS selector.
// If this is a selector, the first matching
// element will be highlighted.
element?: Element | string | (() => Element);
// The popover configuration for this step.
// Look at the Popover Configuration section
popover?: Popover;
// Whether to disable interaction with the highlighted element. (default: false)
disableActiveInteraction?: boolean;
// Callback when the current step is deselected,
// about to be highlighted or highlighted.
// Each callback receives the following parameters:
// - element: The current DOM element of the step
// - step: The step object configured for the step
// - options.config: The current configuration options
// - options.state: The current state of the driver
// - options.driver: Current driver object
onDeselected?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void;
onHighlightStarted?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void;
onHighlighted?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void;
}
```
## State
You can access the current state of the driver by calling the `getState` method. It's also passed to the hooks and callbacks.
```typescript
type State = {
// Whether the driver is currently active or not
isInitialized?: boolean;
// Index of the currently active step if using
// as a product tour and have configured the
// steps array.
activeIndex?: number;
// DOM element of the currently active step
activeElement?: Element;
// Step object of the currently active step
activeStep?: DriveStep;
// DOM element that was previously active
previousElement?: Element;
// Step object of the previously active step
previousStep?: DriveStep;
// DOM elements for the popover i.e. including
// container, title, description, buttons etc.
popover?: PopoverDOM;
}
```
================================================
FILE: docs/src/content/guides/confirm-on-exit.mdx
================================================
---
title: "Confirm on Exit"
groupTitle: "Examples"
sort: 3
---
import { CodeSample } from "../../components/CodeSample.tsx";
You can use the `onDestroyStarted` hook to add a confirmation dialog or some other logic when the user tries to exit the tour. In the example below, upon exit we check if there are any tour steps left and ask for confirmation before we exit.
<CodeSample
heading={'Confirm on Exit'}
config={{
animate: true,
showProgress: true,
showButtons: ['next', 'previous'],
}}
tour={[
{ element: '#confirm-destroy', popover: { title: 'Animated Tour Example', description: 'Here is the code example showing animated tour. Let\'s walk you through it.', side: "left", align: 'start' }},
{ element: '.line:nth-child(1)', popover: { title: 'Import the Library', description: 'It works the same in vanilla JavaScript as well as frameworks.', side: "bottom", align: 'start' }},
{ element: '.line:nth-child(2)', popover: { title: 'Importing CSS', description: 'Import the CSS which gives you the default styling for popover and overlay.', side: "bottom", align: 'start' }},
{ popover: { title: 'Happy Coding', description: 'And that is all, go ahead and start adding tours to your applications.' } }
]}
id={"confirm-destroy"}
client:load>
```js
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
const driverObj = driver({
showProgress: true,
steps: [
{ element: '#confirm-destroy-example', popover: { title: 'Animated Tour Example', description: 'Here is the code example showing animated tour. Let\'s walk you through it.', side: "left", align: 'start' }},
{ element: 'code .line:nth-child(1)', popover: { title: 'Import the Library', description: 'It works the same in vanilla JavaScript as well as frameworks.', side: "bottom", align: 'start' }},
{ element: 'code .line:nth-child(2)', popover: { title: 'Importing CSS', description: 'Import the CSS which gives you the default styling for popover and overlay.', side: "bottom", align: 'start' }},
{ popover: { title: 'Happy Coding', description: 'And that is all, go ahead and start adding tours to your applications.' } }
],
// onDestroyStarted is called when the user tries to exit the tour
onDestroyStarted: () => {
if (!driverObj.hasNextStep() || confirm("Are you sure?")) {
driverObj.destroy();
}
},
});
driverObj.drive();
```
</CodeSample>
> **Note:** By overriding the `onDestroyStarted` hook, you are responsible for calling `driverObj.destroy()` to exit the tour.
================================================
FILE: docs/src/content/guides/installation.mdx
================================================
---
title: "Installation"
groupTitle: "Introduction"
sort: 1
---
Run one of the following commands to install the package:
```bash
# Using npm
npm install driver.js
# Using pnpm
pnpm install driver.js
# Using yarn
yarn add driver.js
```
Alternatively, you can use CDN and include the script in your HTML file:
```html
<script src="https://cdn.jsdelivr.net/npm/driver.js@latest/dist/driver.js.iife.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/driver.js@latest/dist/driver.css"/>
```
## Start Using
Once installed, you can import the package in your project. The following example shows how to highlight an element:
```js
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
const driverObj = driver();
driverObj.highlight({
element: "#some-element",
popover: {
title: "Title",
description: "Description"
}
});
```
### Note on CDN
If you are using the CDN, you will have to use the package from the `window` object:
```js
const driver = window.driver.js.driver;
const driverObj = driver();
driverObj.highlight({
element: "#some-element",
popover: {
title: "Title",
description: "Description"
}
});
```
Continue reading the [Getting Started](/docs/basic-usage) guide to learn more about the package.
================================================
FILE: docs/src/content/guides/migrating-from-0x.mdx
================================================
---
title: "Migrate to 1.x"
groupTitle: "Introduction"
sort: 6
---
Drivers 1.x is a major release that introduces a new API and a new architecture. This page will help you migrate your code from 0.x to 1.x.
> Change in how you import the library
```diff
- import Driver from 'driver.js';
- import 'driver.js/dist/driver.min.css';
+ import { driver } from 'driver.js';
+ import "driver.js/dist/driver.css";
```
> Change in how you initialize the library
```diff
- const driverObj = new Driver(config);
- driverObj.setSteps(steps);
+ // Steps can be passed in the constructor
+ const driverObj = driver({
+ ...config,
+ steps
+ });
```
> Changes in configuration
```diff
const config = {
- overlayClickNext: false, // Option has been removed
- closeBtnText: 'Close', // Option has been removed (close button is now an icon)
- scrollIntoViewOptions: {}, // Option has been renamed
- opacity: 0.75,
+ overlayOpacity: 0.75,
- className: 'scoped-class',
+ popoverClass: 'scoped-class',
- padding: 10,
+ stagePadding: 10,
- showButtons: false,
+ showButtons: ['next', 'prev', 'close'], // pass an array of buttons to show
- keyboardControl: true,
+ allowKeyboardControl: true,
- onHighlightStarted: (Element) {},
+ onHighlightStarted?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void;
- onHighlighted: (Element) {},
+ onHighlighted?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void;
- onDeselected: (Element) {}, // Called when element has been deselected
+ onDeselected?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void;
- onReset: (Element) {}, // Called when overlay is about to be cleared
+ onDestroyStarted?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void;
+ onDestroyed?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void;
+ onCloseClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void;
- onNext: (Element) => {}, // Called when moving to next step on any step
- onPrevious: (Element) => {}, // Called when moving to next step on any step
+ // By overriding the default onNextClick and onPrevClick, you control the flow of the driver
+ // Visit for more details: https://driverjs.com/docs/configuration
+ onNextClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void;
+ onPrevClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void;
+ // New options added
+ overlayColor?: string;
+ stageRadius?: number;
+ popoverOffset?: number;
+ disableButtons?: ["next", "prev", "close"];
+ showProgress?: boolean;
+ progressText?: string;
+ onPopoverRender?: (popover: PopoverDOM, options: { config: Config; state: State }) => void;
}
```
> Changes in step and popover definition
```diff
const stepDefinition = {
popover: {
- closeBtnText: 'Close', // Removed, close button is an icon
- element: '.some-element', // Required
+ element: '.some-element', // Optional, if not provided, step will be shown as modal
- className: 'popover-class',
+ popoverClass: string;
- showButtons: false,
+ showButtons: ["next", "previous", "close"]; // Array of buttons to show
- title: ''; // Required
+ title: ''; // Optional
- description: ''; // Required
+ description: ''; // Optional
- // position can be left, left-center, left-bottom, top,
- // top-center, top-right, right, right-center, right-bottom,
- // bottom, bottom-center, bottom-right, mid-center
- position: 'left',
+ // Now you need to specify the side and align separately
+ side?: "top" | "right" | "bottom" | "left";
+ align?: "start" | "center" | "end";
+ // New options
+ showProgress?: boolean;
+ progressText?: string;
+ onPopoverRender?: (popover: PopoverDOM, options: { config: Config; state: State }) => void;
+ onNextClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void
+ onPrevClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void
+ onCloseClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void
}
+ // New hook to control the flow of the driver
+ onDeselected?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void;
+ onHighlightStarted?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void;
+ onHighlighted?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void;
};
```
> Changes in API methods.
```diff
- driverObj.preventMove(); // async support is built-in, no longer need to call this
- activeElement.getCalculatedPosition();
- activeElement.hidePopover();
- activeElement.showPopover();
- activeElement.getNode();
- const isActivated = driverObj.isActivated;
+ const isActivated = driverObj.isActive();
- driverObj.start(stepNumber = 0);
+ driverObj.drive(stepNumber = 0);
- driverObj.highlight(string|stepDefinition);
+ driverObj.highlight(stepDefinition)
- driverObj.reset();
+ driverObj.destroy();
- driverObj.hasHighlightedElement();
+ typeof driverObj.getActiveElement() !== 'undefined';
- driverObj.getHighlightedElement();
+ driverObj.getActiveElement();
- driverObj.getLastHighlightedElement();
+ driverObj.getPreviousElement();
+ // New options added
+ driverObj.moveTo(stepIndex)
+ driverObj.getActiveStep(); // returns the configured step definition
+ driverObj.getPreviousStep(); // returns the previous step definition
+ driverObj.isLastStep();
+ driverObj.isFirstStep();
+ driverObj.getState();
+ driverObj.getConfig();
+ driverObj.setConfig(config);
+ driverObj.refresh();
```
Please make sure to visit the [documentation](https://driverjs.com/docs/configuration) for more details.
================================================
FILE: docs/src/content/guides/popover-position.mdx
================================================
---
title: "Popover Position"
groupTitle: "Examples"
sort: 7
---
import { CodeSample } from "../../components/CodeSample.tsx";
You can control the popover position using the `side` and `align` options. The `side` option controls the side of the element where the popover will be shown and the `align` option controls the alignment of the popover with the element.
> **Note:** Popover is intelligent enough to adjust itself to fit in the viewport. So, if you set `side` to `left` and `align` to `start`, but the popover doesn't fit in the viewport, it will automatically adjust itself to fit in the viewport. Consider highlighting and scrolling the browser to the element below to see this in action.
```js
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
const driverObj = driver();
driverObj.highlight({
element: '#left-start',
popover: {
title: 'Animated Tour Example',
description: 'Here is the code example showing animated tour. Let\'s walk you through it.',
side: "left",
align: 'start'
}
});
```
<div id="sample-box" className='p-12 bg-gray-200 rounded-md flex items-center justify-center'>
<p>Use the buttons below to show the popover.</p>
</div>
<div className="flex flex-wrap mt-3 gap-1 justify-start">
<CodeSample
buttonText={"Left Start"}
highlight={{
element: '#sample-box',
popover: {
title: 'Left Start Example',
description: 'We have side set to <mark>left</mark> and align set to <mark>start</mark>. PS, we can use HTML in the title and descriptions of popover.',
side: "left",
align: 'start'
}
}}
id={"left-start"}
client:load
/>
<CodeSample
buttonText={"Left Center"}
highlight={{
element: '#sample-box',
popover: {
title: 'Left Center Example',
description: 'We have side set to <mark>left</mark> and align set to <mark>center</mark>. PS, we can use HTML in the title and descriptions of popover.',
side: "left",
align: 'center'
}
}}
id={"left-start"}
client:load
/>
<CodeSample
buttonText={"Left End"}
highlight={{
element: '#sample-box',
popover: {
title: 'Left End Example',
description: 'We have side set to <mark>left</mark> and align set to <mark>end</mark>. PS, we can use HTML in the title and descriptions of popover.',
side: "left",
align: 'end'
}
}}
id={"left-start"}
client:load
/>
<CodeSample
buttonText={"Top Start"}
highlight={{
element: '#sample-box',
popover: {
title: 'Top Start Example',
description: 'We have side set to <mark>top</mark> and align set to <mark>start</mark>. PS, we can use HTML in the title and descriptions of popover.',
side: "top",
align: 'start'
}
}}
id={"top-start"}
client:load
/>
<CodeSample
buttonText={"Top Center"}
highlight={{
element: '#sample-box',
popover: {
title: 'Top Center Example',
description: 'We have side set to <mark>top</mark> and align set to <mark>center</mark>. PS, we can use HTML in the title and descriptions of popover.',
side: "top",
align: 'center'
}
}}
id={"top-start"}
client:load
/>
<CodeSample
buttonText={"Top End"}
highlight={{
element: '#sample-box',
popover: {
title: 'Top End Example',
description: 'We have side set to <mark>top</mark> and align set to <mark>end</mark>. PS, we can use HTML in the title and descriptions of popover.',
side: "top",
align: 'end'
}
}}
id={"top-start"}
client:load
/>
<CodeSample
buttonText={"Right Start"}
highlight={{
element: '#sample-box',
popover: {
title: 'Right Start Example',
description: 'We have side set to <mark>right</mark> and align set to <mark>start</mark>. PS, we can use HTML in the title and descriptions of popover.',
side: "right",
align: 'start'
}
}}
id={"right-start"}
client:load
/>
<CodeSample
buttonText={"Right Center"}
highlight={{
element: '#sample-box',
popover: {
title: 'Right Center Example',
description: 'We have side set to <mark>right</mark> and align set to <mark>center</mark>. PS, we can use HTML in the title and descriptions of popover.',
side: "right",
align: 'center'
}
}}
id={"right-start"}
client:load
/>
<CodeSample
buttonText={"Right End"}
highlight={{
element: '#sample-box',
popover: {
title: 'Right End Example',
description: 'We have side set to <mark>right</mark> and align set to <mark>end</mark>. PS, we can use HTML in the title and descriptions of popover.',
side: "right",
align: 'end'
}
}}
id={"right-start"}
client:load
/>
<CodeSample
buttonText={"Bottom Start"}
highlight={{
element: '#sample-box',
popover: {
title: 'Bottom Start Example',
description: 'We have side set to <mark>bottom</mark> and align set to <mark>start</mark>. PS, we can use HTML in the title and descriptions of popover.',
side: "bottom",
align: 'start'
}
}}
id={"bottom-start"}
client:load
/>
<CodeSample
buttonText={"Bottom Center"}
highlight={{
element: '#sample-box',
popover: {
title: 'Bottom Center Example',
description: 'We have side set to <mark>bottom</mark> and align set to <mark>center</mark>. PS, we can use HTML in the title and descriptions of popover.',
side: "bottom",
align: 'center'
}
}}
id={"bottom-start"}
client:load
/>
<CodeSample
buttonText={"Bottom End"}
highlight={{
element: '#sample-box',
popover: {
title: 'Bottom End Example',
description: 'We have side set to <mark>bottom</mark> and align set to <mark>end</mark>. PS, we can use HTML in the title and descriptions of popover.',
side: "bottom",
align: 'end'
}
}}
id={"right-start"}
client:load
/>
</div>
================================================
FILE: docs/src/content/guides/prevent-destroy.mdx
================================================
---
title: "Prevent Tour Exit"
groupTitle: "Examples"
sort: 3
---
import { CodeSample } from "../../components/CodeSample.tsx";
You can also prevent the user from exiting the tour using `allowClose` option. This option is useful when you want to force the user to complete the tour before they can exit.
In the example below, you won't be able to exit the tour until you reach the last step.
<CodeSample
heading={'Prevent Exit'}
config={{
animate: true,
showProgress: true,
allowClose: false,
showButtons: ['next', 'previous'],
}}
tour={[
{ element: '#prevent-exit', popover: { title: 'Animated Tour Example', description: 'Here is the code example showing animated tour. Let\'s walk you through it.', side: "left", align: 'start' }},
{ element: '.line:nth-child(1)', popover: { title: 'Import the Library', description: 'It works the same in vanilla JavaScript as well as frameworks.', side: "bottom", align: 'start' }},
{ element: '.line:nth-child(2)', popover: { title: 'Importing CSS', description: 'Import the CSS which gives you the default styling for popover and overlay.', side: "bottom", align: 'start' }},
{ popover: { title: 'Happy Coding', description: 'And that is all, go ahead and start adding tours to your applications.' } }
]}
id={"prevent-exit"}
client:load>
```js
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
const driverObj = driver({
showProgress: true,
allowClose: false,
steps: [
{ element: '#prevent-exit', popover: { title: 'Animated Tour Example', description: 'Here is the code example showing animated tour. Let\'s walk you through it.', side: "left", align: 'start' }},
{ element: 'code .line:nth-child(1)', popover: { title: 'Import the Library', description: 'It works the same in vanilla JavaScript as well as frameworks.', side: "bottom", align: 'start' }},
{ element: 'code .line:nth-child(2)', popover: { title: 'Importing CSS', description: 'Import the CSS which gives you the default styling for popover and overlay.', side: "bottom", align: 'start' }},
{ popover: { title: 'Happy Coding', description: 'And that is all, go ahead and start adding tours to your applications.' } }
],
});
driverObj.drive();
```
</CodeSample>
================================================
FILE: docs/src/content/guides/simple-highlight.mdx
================================================
---
title: "Simple Highlight"
groupTitle: "Examples"
sort: 11
---
import { FormHelp } from "../../components/FormHelp.tsx";
import { CodeSample } from "../../components/CodeSample.tsx";
Product tours is not the only usecase for Driver.js. You can use it to highlight any element on the page and show a popover with a description. This is useful for providing contextual help to the user e.g. help the user fill a form or explain a feature.
Example below shows how to highlight an element and simply show a popover.
<CodeSample
id={"highlight-me"}
buttonText={"Highlight Me"}
config={{
popoverClass: "driverjs-theme",
stagePadding: 4,
}}
highlight={{
element: "#highlight-me",
popover: {
side: "bottom",
title: "This is a title",
description: "This is a description",
},
}}
client:load
/>
Here is the code for above example:
```js
const driverObj = driver({
popoverClass: "driverjs-theme",
stagePadding: 4,
});
driverObj.highlight({
element: "#highlight-me",
popover: {
side: "bottom",
title: "This is a title",
description: "This is a description",
}
})
```
You can also use it to show a simple modal without highlighting any element.
<CodeSample
id={"highlight-me"}
buttonText={"Show Popover"}
highlight={{
popover: {
side: "bottom",
description: "<img src='https://i.imgur.com/EAQhHu5.gif' style='height: 202.5px; width: 270px;' /><span style='font-size: 15px; display: block; margin-top: 10px; text-align: center;'>Yet another highlight example.</span>",
},
}}
client:load
/>
Here is the code for above example:
```js
const driverObj = driver();
driverObj.highlight({
popover: {
description: "<img src='https://i.imgur.com/EAQhHu5.gif' style='height: 202.5px; width: 270px;' /><span style='font-size: 15px; display: block; margin-top: 10px; text-align: center;'>Yet another highlight example.</span>",
}
})
```
Focus on the input below and see how the popover is shown.
<form action="#" className='flex flex-col gap-2'>
<input id="name" type="text" placeholder="Enter your Name" className='block w-full border rounded-md py-2 px-3 focus:outline-0' />
<input id="education" type="text" placeholder="Your Education" className='block w-full border rounded-md py-2 px-3 focus:outline-0' />
<input id="age" type="number" placeholder="Your Age" className='block w-full border rounded-md py-2 px-3 focus:outline-0' />
<textarea id="address" placeholder="Your Address" className='block w-full border rounded-md py-2 px-3 focus:outline-0' />
<button id="submit-btn" className='w-full rounded-md bg-black p-2 text-white'>Submit</button>
</form>
<FormHelp client:only />
Here is the code for the above example.
```js
const driverObj = driver({
popoverClass: "driverjs-theme",
stagePadding: 0,
onDestroyed: () => {
document?.activeElement?.blur();
}
});
const nameEl = document.getElementById("name");
const educationEl = document.getElementById("education");
const ageEl = document.getElementById("age");
const addressEl = document.getElementById("address");
const formEl = document.querySelector("form");
nameEl.addEventListener("focus", () => {
driverObj.highlight({
element: nameEl,
popover: {
title: "Name",
description: "Enter your name here",
},
});
});
educationEl.addEventListener("focus", () => {
driverObj.highlight({
element: educationEl,
popover: {
title: "Education",
description: "Enter your education here",
},
});
});
ageEl.addEventListener("focus", () => {
driverObj.highlight({
element: ageEl,
popover: {
title: "Age",
description: "Enter your age here",
},
});
});
addressEl.addEventListener("focus", () => {
driverObj.highlight({
element: addressEl,
popover: {
title: "Address",
description: "Enter your address here",
},
});
});
formEl.addEventListener("blur", () => {
driverObj.destroy();
});
```
================================================
FILE: docs/src/content/guides/static-tour.mdx
================================================
---
title: "Static Tour"
groupTitle: "Examples"
sort: 2
---
import { CodeSample } from "../../components/CodeSample.tsx";
You can simply set `animate` option to `false` to make the tour static. This will make the tour not animate between steps and will just show the popover.
<CodeSample
heading={'Basic Static Tour'}
config={{
animate: false,
showProgress: false,
showButtons: ['next', 'previous'],
}}
tour={[
{ element: '#tour-example', popover: { title: 'Animated Tour Example', description: 'Here is the code example showing animated tour. Let\'s walk you through it.', side: "left", align: 'start' }},
{ element: '.line:nth-child(1)', popover: { title: 'Import the Library', description: 'It works the same in vanilla JavaScript as well as frameworks.', side: "bottom", align: 'start' }},
{ element: '.line:nth-child(2)', popover: { title: 'Importing CSS', description: 'Import the CSS which gives you the default styling for popover and overlay.', side: "bottom", align: 'start' }},
{ element: '.line:nth-child(4) span:nth-child(7)', popover: { title: 'Create Driver', description: 'Simply call the driver function to create a driver.js instance', side: "left", align: 'start' }},
{ element: '.line:nth-child(19)', popover: { title: 'Start Tour', description: 'Call the drive method to start the tour and your tour will be started.', side: "top", align: 'start' }},
{ element: '.line:nth-child(6)', popover: { title: 'Hide Progress', description: 'Progress shown in the bottom left is hidden by default. You can make driver show/hide the progress using this option.', side: "top", align: 'start' }},
{ element: '#docs-sidebar a[href="/docs/configuration"]', popover: { title: 'More Configuration', description: 'Look at this page for all the configuration options you can pass.', side: "right", align: 'start' }},
{ popover: { title: 'Happy Coding', description: 'And that is all, go ahead and start adding tours to your applications.' } }
]}
id={"tour-example"}
client:load
>
```js
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
const driverObj = driver({
animate: false,
showProgress: false,
showButtons: ['next', 'previous', 'close'],
steps: [
{ element: '#tour-example', popover: { title: 'Animated Tour Example', description: 'Here is the code example showing animated tour. Let\'s walk you through it.', side: "left", align: 'start' }},
{ element: 'code .line:nth-child(1)', popover: { title: 'Import the Library', description: 'It works the same in vanilla JavaScript as well as frameworks.', side: "bottom", align: 'start' }},
{ element: 'code .line:nth-child(2)', popover: { title: 'Importing CSS', description: 'Import the CSS which gives you the default styling for popover and overlay.', side: "bottom", align: 'start' }},
{ element: 'code .line:nth-child(4) span:nth-child(7)', popover: { title: 'Create Driver', description: 'Simply call the driver function to create a driver.js instance', side: "left", align: 'start' }},
{ element: 'code .line:nth-child(18)', popover: { title: 'Start Tour', description: 'Call the drive method to start the tour and your tour will be started.', side: "top", align: 'start' }},
{ element: '#docs-sidebar a[href="/docs/configuration"]', popover: { title: 'More Configuration', description: 'Look at this page for all the configuration options you can pass.', side: "right", align: 'start' }},
{ popover: { title: 'Happy Coding', description: 'And that is all, go ahead and start adding tours to your applications.' } }
]
});
driverObj.drive();
```
</CodeSample>
================================================
FILE: docs/src/content/guides/styling-overlay.mdx
================================================
---
title: "Styling Overlay"
groupTitle: "Examples"
sort: 5
---
import { CodeSample } from "../../components/CodeSample.tsx";
You can customize the overlay opacity and color using `overlayOpacity` and `overlayColor` options to change the look of the overlay.
> **Note:** In the examples below we have used `highlight` method to highlight the elements. The same configuration applies to the tour steps as well.
## Overlay Color
Here are some driver.js examples with different overlay colors.
```js
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
const driverObj = driver({
overlayColor: 'red'
});
driverObj.highlight({
popover: {
title: 'Pass any RGB Color',
description: 'Here we have set the overlay color to be red. You can pass any RGB color to overlayColor option.'
}
});
```
<div className='flex flex-col gap-1 -mt-5'>
<CodeSample
buttonText={"Red Color"}
config={{
overlayColor: 'red',
overlayOpacity: 0.3
}}
highlight={{
popover: {
title: 'Pass any RGB Color',
description: 'Here we have set the overlay color to be red. You can pass any RGB color to overlayColor option.',
}
}}
id={"left-start"}
client:load
/>
<CodeSample
buttonText={"Blue Color"}
config={{
overlayColor: 'blue',
overlayOpacity: 0.3
}}
highlight={{
popover: {
title: 'Pass any RGB Color',
description: 'Here we have set the overlay color to be blue. You can pass any RGB color to overlayColor option.',
}
}}
id={"left-start"}
client:load
/>
<CodeSample
buttonText={"Yellow Color"}
config={{
overlayColor: 'yellow',
overlayOpacity: 0.3
}}
highlight={{
popover: {
title: 'Pass any RGB Color',
description: 'Here we have set the overlay color to be yellow. You can pass any RGB color to overlayColor option.',
}
}}
id={"left-start"}
client:load
/>
</div>
================================================
FILE: docs/src/content/guides/styling-popover.mdx
================================================
---
title: "Styling Popover"
groupTitle: "Examples"
sort: 2
---
import { CodeSample } from "../../components/CodeSample.tsx";
You can either use the default class names and override the styles or you can pass a custom class name to the `popoverClass` option either globally or per step.
Alternatively, if want to modify the Popover DOM, you can use the `onPopoverRender` callback to get the popover DOM element and do whatever you want with it before popover is rendered.
We have added a few examples below but have a look at the [theming section](/docs/theming#styling-popover) for detailed guide including class names to target etc.
<CodeSample
heading="Using CSS"
buttonText={"Driver.js Website Theme"}
config={{
prevBtnText: '← Previous',
nextBtnText: 'Next →',
doneBtnText: 'Done',
showButtons: ['next', 'previous'],
popoverClass: 'driverjs-theme'
}}
tour={[
{
element: '#demo-theme',
popover: {
align: 'start',
side: 'left',
title: 'Style However You Want',
description: 'You can use the default class names and override the styles or you can pass a custom class name to the popoverClass option either globally or per step.'
}
},
{
element: 'h1',
popover: {
align: 'start',
side: 'bottom',
title: 'Style However You Want',
description: 'You can use the default class names and override the styles or you can pass a custom class name to the popoverClass option either globally or per step.'
}
},
{
element: 'p a',
popover: {
align: 'start',
side: 'left',
title: 'Style However You Want',
description: 'You can use the default class names and override the styles or you can pass a custom class name to the popoverClass option either globally or per step.'
}
}
]}
id={"demo-theme"}
client:load
>
```js
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
const driverObj = driver({
popoverClass: 'driverjs-theme'
});
driverObj.highlight({
element: '#demo-theme',
popover: {
title: 'Style However You Want',
description: 'You can use the default class names and override the styles or you can pass a custom class name to the popoverClass option either globally or per step.'
}
});
```
</CodeSample>
Here is the CSS used for the above example:
```css
.driver-popover.driverjs-theme {
background-color: #fde047;
color: #000;
}
.driver-popover.driverjs-theme .driver-popover-title {
font-size: 20px;
}
.driver-popover.driverjs-theme .driver-popover-title,
.driver-popover.driverjs-theme .driver-popover-description,
.driver-popover.driverjs-theme .driver-popover-progress-text {
color: #000;
}
.driver-popover.driverjs-theme button {
flex: 1;
text-align: center;
background-color: #000;
color: #ffffff;
border: 2px solid #000;
text-shadow: none;
font-size: 14px;
padding: 5px 8px;
border-radius: 6px;
}
.driver-popover.driverjs-theme button:hover {
background-color: #000;
color: #ffffff;
}
.driver-popover.driverjs-theme .driver-popover-navigation-btns {
justify-content: space-between;
gap: 3px;
}
.driver-popover.driverjs-theme .driver-popover-close-btn {
color: #9b9b9b;
}
.driver-popover.driverjs-theme .driver-popover-close-btn:hover {
color: #000;
}
.driver-popover.driverjs-theme .driver-popover-arrow-side-left.driver-popover-arrow {
border-left-color: #fde047;
}
.driver-popover.driverjs-theme .driver-popover-arrow-side-right.driver-popover-arrow {
border-right-color: #fde047;
}
.driver-popover.driverjs-theme .driver-popover-arrow-side-top.driver-popover-arrow {
border-top-color: #fde047;
}
.driver-popover.driverjs-theme .driver-popover-arrow-side-bottom.driver-popover-arrow {
border-bottom-color: #fde047;
}
```
<br/>
<CodeSample
heading="Using Hook to Modify"
buttonText={"Manipulating Popover DOM"}
config={{
prevBtnText: '← Previous',
nextBtnText: 'Next →',
doneBtnText: 'Done',
showButtons: ['next', 'previous'],
}}
tour={[
{
element: '#demo-hook-theme',
popover: {
align: 'start',
side: 'left',
title: 'More Control with Hooks',
description: 'You can use onPopoverRender hook to modify the popover DOM. Here we are adding a custom button to the popover which takes the user to the first step.'
}
},
{
element: 'h1',
popover: {
align: 'start',
side: 'bottom',
title: 'Style However You Want',
description: 'You can use the default class names and override the styles or you can pass a custom class name to the popoverClass option either globally or per step.'
}
},
{
element: 'p a',
popover: {
align: 'start',
side: 'left',
title: 'Style However You Want',
description: 'You can use the default class names and override the styles or you can pass a custom class name to the popoverClass option either globally or per step.'
}
}
]}
id={"demo-hook-theme"}
client:load
>
```js
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
const driverObj = driver({
// Get full control over the popover rendering.
// Here we are adding a custom button that takes
// the user to the first step.
onPopoverRender: (popover, { config, state }) => {
const firstButton = document.createElement("button");
firstButton.innerText = "Go to First";
popover.footerButtons.appendChild(firstButton);
firstButton.addEventListener("click", () => {
driverObj.drive(0);
});
},
steps: [
// ..
]
});
driverObj.drive();
```
</CodeSample>
================================================
FILE: docs/src/content/guides/theming.mdx
================================================
---
title: "Theming"
groupTitle: "Introduction"
sort: 5
---
You can customize the look and feel of the driver by adding custom class to popover or applying CSS to different classes used by driver.js.
## Styling Popover
You can set the `popoverClass` option globally in the driver configuration or at the step level to apply custom class to the popover and then use CSS to apply styles.
```js
const driverObj = driver({
popoverClass: 'my-custom-popover-class'
});
// or you can also have different classes for different steps
const driverObj2 = driver({
steps: [
{
element: '#some-element',
popover: {
title: 'Title',
description: 'Description',
popoverClass: 'my-custom-popover-class'
}
}
],
})
```
Here is the list of classes applied to the popover which you can use in conjunction with `popoverClass` option to apply custom styles on the popover.
```css
/* Class assigned to popover wrapper */
.driver-popover {}
/* Arrow pointing towards the highlighted element */
.driver-popover-arrow {}
/* Title and description */
.driver-popover-title {}
.driver-popover-description {}
/* Close button displayed on the top right corner */
.driver-popover-close-btn {}
/* Footer of the popover displaying progress and navigation buttons */
.driver-popover-footer {}
.driver-popover-progress-text {}
.driver-popover-prev-btn {}
.driver-popover-next-btn {}
```
Visit the [example page](/docs/styling-popover) for an example that modifies the popover styles.
## Modifying Popover DOM
Alternatively, you can also use the `onPopoverRender` hook to modify the popover DOM before it is displayed. The hook is called with the popover DOM as the first argument.
```typescript
type PopoverDOM = {
wrapper: HTMLElement;
arrow: HTMLElement;
title: HTMLElement;
description: HTMLElement;
footer: HTMLElement;
progress: HTMLElement;
previousButton: HTMLElement;
nextButton: HTMLElement;
closeButton: HTMLElement;
footerButtons: HTMLElement;
};
onPopoverRender?: (popover: PopoverDOM, opts: { config: Config; state: State }) => void;
```
## Styling Page
Following classes are applied to the page when the driver is active.
```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 i.e. the lightbox displayed over the page.
```css
.driver-overlay {}
```
## Styling Highlighted Element
Whenever an element is highlighted, the following classes are applied to it.
```css
.driver-active-element {}
```
================================================
FILE: docs/src/content/guides/tour-progress.mdx
================================================
---
title: "Tour Progress"
groupTitle: "Examples"
sort: 2
---
import { CodeSample } from "../../components/CodeSample.tsx";
You can use `showProgress` option to show the progress of the tour. It is shown in the bottom left corner of the screen. There is also `progressText` option which can be used to customize the text shown for the progress.
Please note that `showProgress` is `false` by default. Also the default text for `progressText` is `{{current}} of {{total}}`. You can use `{{current}}` and `{{total}}` in your `progressText` template to show the current and total steps.
<CodeSample
config={{
showProgress: true,
showButtons: ['next', 'previous'],
}}
tour={[
{ element: '#tour-example', popover: { title: 'Progress Example', description: 'Notice the text at the bottom left corner showing the progress', side: "left", align: 'start' }},
{ element: '.line:nth-child(1)', popover: { title: 'Import the Library', description: 'It works the same in vanilla JavaScript as well as frameworks.', side: "bottom", align: 'start' }},
{ element: '.line:nth-child(2)', popover: { title: 'Importing CSS', description: 'Import the CSS which gives you the default styling for popover and overlay.', side: "bottom", align: 'start' }},
{ element: '.line:nth-child(4) span:nth-child(7)', popover: { title: 'Create Driver', description: 'Simply call the driver function to create a driver.js instance', side: "left", align: 'start' }},
{ element: '.line:nth-child(16)', popover: { title: 'Start Tour', description: 'Call the drive method to start the tour and your tour will be started.', side: "top", align: 'start' }},
]}
id={"tour-example"}
client:load
>
```js
import { driver } from "driver.js";
import "driver.js/dist/driver.css";
const driverObj = driver({
showProgress: true,
showButtons: ['next', 'previous'],
steps: [
{ element: '#tour-example', popover: { title: 'Animated Tour Example', description: 'Here is the code example showing animated tour. Let\'s walk you through it.', side: "left", align: 'start' }},
{ element: 'code .line:nth-child(1)', popover: { title: 'Import the Library', description: 'It works the same in vanilla JavaScript as well as frameworks.', side: "bottom", align: 'start' }},
{ element: 'code .line:nth-child(2)', popover: { title: 'Importing CSS', description: 'Import the CSS which gives you the default styling for popover and overlay.', side: "bottom", align: 'start' }},
{ element: 'code .line:nth-child(4) span:nth-child(7)', popover: { title: 'Create Driver', description: 'Simply call the driver function to create a driver.js instance', side: "left", align: 'start' }},
{ element: 'code .line:nth-child(16)', popover: { title: 'Start Tour', description: 'Call the drive method to start the tour and your tour will be started.', side: "top", align: 'start' }},
]
});
driverObj.drive();
```
</CodeSample>
<div className="mb-1.5"></div>
<CodeSample
buttonText={"Different Progress Text"}
config={{
stagePadding: 5,
progressText: "Step {{current}} of {{total}}",
showProgress: true,
showButtons: ['next', 'previous'],
}}
tour={[
{ element: 'p code:nth-child(2)', popover: { title: 'progressText', description: 'You can use progressText to modify the progress text template.', side: "bottom", align: 'start' }},
{ element: '#tour-example', popover: { title: 'Progress Example', description: 'Notice the text at the bottom left corner showing the progress', side: "left", align: 'start' }},
{ element: '.line:nth-child(1)', popover: { title: 'Import the Library', description: 'It works the same in vanilla JavaScript as well as frameworks.', side: "bottom", align: 'start' }},
{ element: '.line:nth-child(2)', popover: { title: 'Importing CSS', description: 'Import the CSS which gives you the default styling for popover and overlay.', side: "bottom", align: 'start' }},
{ element: '.line:nth-child(4) span:nth-child(7)', popover: { title: 'Create Driver', description: 'Simply call the driver function to create a driver.js instance', side: "left", align: 'start' }},
{ element: '.line:nth-child(16)', popover: { title: 'Start Tour', description: 'Call the drive method to start the tour and your tour will be started.', side: "top", align: 'start' }},
]}
id={"tour-example"}
client:load
/>
================================================
FILE: docs/src/env.d.ts
================================================
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />
interface Window {
driverObj: any;
}
================================================
FILE: docs/src/layouts/BaseLayout.astro
================================================
---
import Analytics from "../components/Analytics/Analytics.astro";
export interface Props {
permalink?: string;
title?: string;
description?: string;
}
const {
permalink = "",
title = "driver.js",
description = "A light-weight, no-dependency, vanilla JavaScript library to drive user's focus across the page.",
} = Astro.props;
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>{title}</title>
<meta name="robots" content="index,follow" />
<meta name="description" itemprop="description" content={description} />
<link href={`https://driverjs.com${permalink}`} rel="canonical" />
<meta content="Kamran Ahmed" name="author" />
<meta content="summary_large_image" name="twitter:card" />
<meta content="@kamrify" name="twitter:creator" />
<meta content="1200" property="og:image:width" />
<meta content="630" property="og:image:height" />
<meta content="https://driverjs.com/og-img.png" property="og:image" />
<meta content="driverjs.com" property="og:image:alt" />
<meta content="driverjs.com" property="og:site_name" />
<meta content="Driver.js" property="og:title" />
<meta
content="A light-weight, no-dependency, vanilla JavaScript library to drive user's focus across the page."
property="og:description"
/>
<meta content="website" property="og:type" />
<meta content="https://driverjs.com/" property="og:url" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<style is:global>
.driver-popover.driverjs-theme {
background-color: #fde047;
color: #000;
}
.driver-popover.driverjs-theme .driver-popover-title {
font-size: 20px;
}
.driver-popover.driverjs-theme .driver-popover-title,
.driver-popover.driverjs-theme .driver-popover-description,
.driver-popover.driverjs-theme .driver-popover-progress-text {
color: #000;
}
.driver-popover.driverjs-theme button {
flex: 1;
text-align: center;
background-color: #000;
color: #ffffff;
border: 1px solid #000;
text-shadow: none;
font-size: 14px;
padding: 5px 8px;
border-radius: 6px;
}
.driver-popover.driverjs-theme button:focus,
.driver-popover.driverjs-theme button:hover {
background-color: #000;
opacity: 0.8;
color: #ffffff;
}
.driver-popover.driverjs-theme .driver-popover-navigation-btns {
justify-content: space-between;
gap: 3px;
}
.driver-popover.driverjs-theme .driver-popover-close-btn {
color: #9b9b9b;
}
.driver-popover.driverjs-theme .driver-popover-close-btn:hover {
color: #000;
}
.driver-popover.driverjs-theme .driver-popover-arrow-side-left.driver-popover-arrow {
border-left-color: #fde047;
}
.driver-popover.driverjs-theme .driver-popover-arrow-side-right.driver-popover-arrow {
border-right-color: #fde047;
}
.driver-popover.driverjs-theme .driver-popover-arrow-side-top.driver-popover-arrow {
border-top-color: #fde047;
}
.driver-popover.driverjs-theme .driver-popover-arrow-side-bottom.driver-popover-arrow {
border-bottom-color: #fde047;
}
</style>
</head>
<body>
<slot />
<Analytics />
</body>
</html>
================================================
FILE: docs/src/layouts/DocsLayout.astro
================================================
---
import BaseLayout from "./BaseLayout.astro";
import { DocsHeader } from "../components/DocsHeader";
import Container from "../components/Container.astro";
import { getFormattedStars } from "../lib/github";
import Sidebar from "../components/Sidebar.astro";
import type { CollectionEntry } from "astro:content";
import { getAllGuides } from "../lib/guide";
type GuideType = CollectionEntry<"guides">;
export interface Props {
guide: GuideType;
}
const groupedGuides = await getAllGuides();
const { guide } = Astro.props;
const { groupTitle, sort, title } = guide.data;
---
<BaseLayout title={`${title} - Driver.js`} permalink={`/docs/${guide.slug}`}>
<div class="block md:hidden">
<DocsHeader activeGuideTitle={title} groupedGuides={groupedGuides} client:load />
</div>
<div class="flex">
<Sidebar activeGuideTitle={title} groupedGuides={groupedGuides} />
<div
class="min-w-0 max-w-[800px] py-6 md:py-12 prose px-6 md:px-14 prose-base md:proxe-xl mb-24 prose-blockquote:font-normal prose-blockquote:not-italic prose-blockquote:text-gray-500 prose-p:before:content-['']">
<slot />
</div>
</div>
</BaseLayout>
================================================
FILE: docs/src/lib/github.ts
================================================
const formatter = Intl.NumberFormat("en-US", {
notation: "compact",
});
const defaultStarCount = 17000;
let starCount: number | undefined = undefined;
export async function countStars(repo = "kamranahmedse/driver.js"): Promise<number> {
if (starCount) {
return starCount;
}
try {
const repoData = await fetch(`https://api.github.com/repos/${repo}`);
const json = await repoData.json();
starCount = json.stargazers_count * 1 || defaultStarCount;
} catch (e) {
console.log("Failed to fetch stars", e);
starCount = defaultStarCount;
}
return starCount;
}
export async function getFormattedStars(repo = "kamranahmedse/driver.js"): Promise<string> {
const stars = import.meta.env.DEV ? defaultStarCount : await countStars(repo);
return formatter.format(stars);
}
================================================
FILE: docs/src/lib/guide.ts
================================================
import { CollectionEntry, getCollection } from "astro:content";
export async function getAllGuides(): Promise<Record<string, CollectionEntry<"guides">[]>> {
const allGuides: CollectionEntry<"guides">[] = await getCollection("guides");
const sortedGuides = allGuides.sort((a, b) => a.data.sort - b.data.sort);
return sortedGuides.reduce((acc: Record<string, CollectionEntry<"guides">[]>, curr: CollectionEntry<"guides">) => {
const { groupTitle } = curr.data;
acc[groupTitle] = acc[groupTitle] || [];
acc[groupTitle].push(curr);
return acc;
}, {});
}
================================================
FILE: docs/src/pages/docs/[guideId].astro
================================================
---
import { CollectionEntry, getCollection } from "astro:content";
import DocsLayout from "../../layouts/DocsLayout.astro";
export interface Props {
guide: CollectionEntry<"guides">;
}
export async function getStaticPaths() {
const guides = await getCollection("guides");
return guides.map(guide => ({
params: { guideId: guide.slug },
props: { guide },
}));
}
const { guideId } = Astro.params;
const { guide } = Astro.props;
const { Content, headings } = await guide.render();
---
<DocsLayout guide={guide}>
<h1 class="text-5xl font-bold mb-4">{guide.data.title}</h1>
<Content />
</DocsLayout>
================================================
FILE: docs/src/pages/index.astro
================================================
---
import BaseLayout from "../layouts/BaseLayout.astro";
import { FeatureMarquee } from "../components/FeatureMarquee";
import Container from "../components/Container.astro";
import UsecaseItem from "../components/UsecaseItem.astro";
import { ExampleButton } from "../components/ExampleButton";
import HeroSection from "../components/HeroSection.astro";
import Examples from "../components/Examples.astro";
import UsecaseList from "../components/UsecaseList.astro";
import OpenSourceLove from "../components/OpenSourceLove.astro";
---
<BaseLayout title="driver.js">
<HeroSection />
<div
class="bg-white overflow-x-hidden overflow-y-hidden relative h-[48px] md:h-[56px] lg:h-[64px] border-b-2 border-b-black"
data-feat-marquee>
<FeatureMarquee client:only />
</div>
<div class="py-10 md:py-12 lg:py-24 bg-white text-black">
<Container>
<Examples />
<UsecaseList />
</Container>
</div>
<OpenSourceLove />
<div class="py-8 bg-black text-white">
<Container>
<p class="text-lg text-white text-center">
MIT Licensed © 2024
<span class="hidden sm:inline">
<span class="mx-3">·</span>
<a href="/docs/installation" class="">
Docs
</a>
<a href="https://github.com/kamranahmedse/driver.js" target="_blank" class="ml-5">
GitHub
<img src="/arrow.svg" class="h-3 inline-block ml-2" alt="GitHub" />
</a>
<a href="https://twitter.com/kamrify" target="_blank" class=" ml-5">
Twitter
<img src="/arrow.svg" class="h-3 inline-block ml-2" alt="GitHub" />
</a>
</span>
</p>
</Container>
</div>
</BaseLayout>
================================================
FILE: docs/tailwind.config.cjs
================================================
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
theme: {
screens: {
'sh': {
'raw': '(min-height: 750px)',
},
...require('tailwindcss/defaultConfig').theme.screens,
},
container: {
},
extend: {},
},
plugins: [
require('@tailwindcss/typography'),
],
};
================================================
FILE: docs/tsconfig.json
================================================
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "react",
"strictNullChecks": true
}
}
================================================
FILE: dts-bundle-generator.config.ts
================================================
module.exports = {
entries: [
{
filePath: "./src/driver.ts",
outFile: `./dist/driver.js.d.ts`,
noCheck: false,
},
],
};
================================================
FILE: index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- prefetch image -->
<link rel="preconnect" href="https://i.imgur.com/" />
<title>Vite App</title>
<style>
* {
margin: 0;
padding: 0;
}
*:focus {
outline: 2px solid #1a73e8;
outline-offset: 2px;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans",
"Helvetica Neue", sans-serif;
font-size: 14px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.driver-popover.custom-driver-popover {
min-width: 450px;
background: #2d2d2d;
color: white;
}
.driver-popover.custom-driver-popover .driver-popover-title {
font-size: 26px;
}
.driver-popover.custom-driver-popover .driver-popover-description {
font-size: 14px;
}
.driver-popover.custom-driver-popover button {
background: #454545;
border-color: #454545;
text-shadow: none;
color: white;
font-size: 13px;
padding: 7px 10px;
}
.driver-popover.custom-driver-popover button:hover {
background: #575757;
}
.gif-popover {
display: flex;
flex-direction: column;
text-align: center;
}
.gif-popover img {
width: 100%;
height: auto;
margin-bottom: 10px;
}
.gif-popover p {
font-weight: 500;
margin-bottom: 0;
}
p {
line-height: 1.5;
margin-bottom: 15px;
}
.page-header {
text-align: center;
margin-bottom: 10px;
}
.container {
display: flex;
flex-direction: column;
max-width: 500px;
margin: 0 auto;
text-align: left;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 30px 0 10px;
}
h1 {
font-size: 48px;
font-weight: 600;
text-align: center;
}
h1 sup {
font-size: 18px;
font-weight: 400;
}
ul {
list-style: none;
padding: 0;
margin: 20px 10px 0;
line-height: 1.5;
}
ul li:before {
content: "•";
margin-right: 10px;
}
.buttons {
display: flex;
gap: 10px;
max-width: 500px;
flex-wrap: wrap;
}
button {
all: unset;
border: 1px solid #ccc;
padding: 5px 15px;
border-radius: 5px;
display: block;
cursor: pointer;
}
pre {
margin-bottom: 20px;
border: 1px solid #ccc;
background: whitesmoke;
border-radius: 5px;
padding: 10px;
line-height: 1.75;
}
#scrollable-area {
height: 300px;
overflow: auto;
border: 1px solid #ccc;
padding: 10px;
border-radius: 5px;
margin: 50px 0;
}
</style>
</head>
<body>
<div class="container">
<div class="page-header">
<h1>driver.js <sup>next</sup></h1>
<p>Rewritten and enhanced version of driver.js</p>
</div>
<h2>Highlight Feature</h2>
<p>given below are the examples of simple `highlight`</p>
<div class="buttons">
<button id="highlight-btn">Animated Highlight</button>
<button id="buggy-highlight-btn">Buggy Highlight</button>
<button id="off-screen-highlight-btn">Off Screen Highlight</button>
<button id="simple-highlight-btn">Simple Highlight</button>
<button id="transition-highlight-btn">Transition Highlight</button>
<button id="disallow-close">Disallow Close</button>
<button id="click-overlay-to-next">Click Overlay to Next</button>
<button id="click-overlay-to-handle">Custom Overlay Click Handler</button>
<button id="dark-highlight-btn">Super Dark Highlight</button>
<button id="dim-highlight-btn">Super Dim Highlight</button>
<button id="scrollable-area-btn">Scrollable Area</button>
<button id="inner-scroll-area-btn">Inner Scroll Area</button>
<button id="without-element-btn">No Element</button>
<button id="is-active-btn">Is Active?</button>
<button id="activate-check-btn">Activate and Check</button>
<button id="backdrop-color-btn">Backdrop Color</button>
<button id="hooks">Hooks</button>
<button id="destroy-btn" style="border-color: red; background: red; color: white">Destroy</button>
</div>
<br />
<p>given below are the examples of simple `highlight`</p>
<div class="buttons">
<button id="no-buttons">No Buttons</button>
<button id="buttons-from-popover">Selected Buttons</button>
<button id="next-button">Next Button</button>
<button id="previous-button">Previous Buttons</button>
<button id="next-prev-button">Next Previous Buttons</button>
<button id="close-button">Close Buttons</button>
<button id="button-texts">Button Texts</button>
<button id="disabled-buttons">Disabled Buttons</button>
<button id="button-config-events">Button Listeners</button>
<button id="button-tour-events">Tour Button Listeners</button>
<button id="popover-hook-config">Popover Modified in Hook</button>
</div>
<br />
<p>You can modify the popover as well with custom CSS and JS.</p>
<div class="buttons">
<button id="custom-classes">Custom Classes</button>
<button id="popover-hook">Popover Hook</button>
<button id="padding-change">Padding Change</button>
</div>
<h2>Tour Feature</h2>
<p>Examples below show the tour usage of driver.js.</p>
<div class="buttons">
<button id="basic-tour">Animated Tour</button>
<button id="non-animated-tour">Non-Animated Tour</button>
<button id="async-tour">Asynchronous Tour</button>
<button id="confirm-exit-tour">Confirm on Exit</button>
<button id="progress-tour">Progress Text</button>
<button id="progress-tour-template">Progress Text Template</button>
<button id="api-test">API Test</button>
<button id="reconfigure-steps">Re Configuring Steps</button>
<button id="disable-keyboard-control">Disable Keyboard Control</button>
</div>
<ul>
<li>Written in TypeScript</li>
<li>Lightweight — only 5kb gzipped</li>
<li>No dependencies</li>
<li>MIT Licensed</li>
</ul>
<ul id="hooks-list">
<li><strong>Hooks Button Demo — </strong>List of hooks fired</li>
</ul>
<h2>Yet another Tour Library?</h2>
<p>
No, it is not. Tours are just one of the many use-cases. Driver.js can be used wherever you need some sort of
overlay for the page; some common usecases could be: e.g. dimming the background when user is interacting with
some component, using it as a focus shifter to bring user's attention to some component on page, or using it to
simulate those "Turn off the Lights" widgets that you might have seen on video players online, etc.
</p>
<p class="second-para">
Driver.js is written in Vanilla JS, has zero dependencies and is highly customizable. It has several options
allowing you to manipulate how it behaves and also provides you the hooks to manipulate the elements as they are
highlighted, about to be highlighted, or deselected.
</p>
<h2 id="installation-head">Installation</h2>
<p>You can install it using yarn or npm, whatever you prefer.</p>
<pre>
yarn add driver.js
npm install driver.js</pre
>
<p>Or include it using CDN — put the version as driver.js@0.5 in the name</p>
<pre>https://unpkg.com/driver.js/dist/driver.min.js</pre>
<h2>Usage and Demo</h2>
<p id="large-paragraph-text">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi blanditiis consectetur ea eligendi id in
inventore ipsa iure laudantium libero, minus molestias necessitatibus nesciunt non omnis, quasi recusandae
tempore voluptates! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi blanditiis consectetur ea
eligendi id in inventore ipsa iure laudantium libero, minus molestias necessitatibus nesciunt non omnis, quasi
recusandae tempore voluptates!
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi blanditiis consectetur ea eligendi id in
inventore ipsa iure laudantium libero, minus molestias necessitatibus nesciunt non omnis, quasi recusandae
tempore voluptates!
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi blanditiis consectetur ea eligendi id in
inventore ipsa iure laudantium libero, minus molestias necessitatibus nesciunt non omnis, quasi recusandae
tempore voluptates!
</p>
<div id="scrollable-area">
<p>
First -> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consequuntur dicta ipsum labore quod
tempora ullam? Alias consequatur doloremque laborum maxime necessitatibus nostrum odio, officiis quibusdam
veniam! Doloribus eos id quaerat.
</p>
<p>
Second -> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consequuntur dicta ipsum labore quod
tempora ullam? Alias consequatur doloremque laborum maxime necessitatibus nostrum odio, officiis quibusdam
veniam! Doloribus eos id quaerat.
</p>
<p id="third-scroll-paragraph">
Third -> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consequuntur dicta ipsum labore quod
tempora ullam? Alias consequatur doloremque laborum maxime necessitatibus nostrum odio, officiis quibusdam
veniam! Doloribus eos id quaerat.
</p>
<p>
Fourth -> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consequuntur dicta ipsum labore quod
tempora ullam? Alias consequatur doloremque laborum maxime necessitatibus nostrum odio, officiis quibusdam
veniam! Doloribus eos id quaerat.
</p>
<p>
Fifth -> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consequuntur dicta ipsum labore quod
tempora ullam? Alias consequatur doloremque laborum maxime necessitatibus nostrum odio, officiis quibusdam
veniam! Doloribus eos id quaerat.
</p>
<p>
Sixth -> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consequuntur dicta ipsum labore quod
tempora ullam? Alias consequatur doloremque laborum maxime necessitatibus nostrum odio, officiis quibusdam
veniam! Doloribus eos id quaerat.
</p>
<p>
Seventh -> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consequuntur dicta ipsum labore quod
tempora ullam? Alias consequatur doloremque laborum maxime necessitatibus nostrum odio, officiis quibusdam
veniam! Doloribus eos id quaerat.
</p>
<p>
Eighth -> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consequuntur dicta ipsum labore quod
tempora ullam? Alias consequatur doloremque laborum maxime necessitatibus nostrum odio, officiis quibusdam
veniam! Doloribus eos id quaerat.
</p>
<p>
Ninth -> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consequuntur dicta ipsum labore quod
tempora ullam? Alias consequatur doloremque laborum maxime necessitatibus nostrum odio, officiis quibusdam
veniam! Doloribus eos id quaerat.
</p>
</div>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi blanditiis consectetur ea eligendi id in
inventore ipsa iure laudantium libero, minus molestias necessitatibus nesciunt non omnis, quasi recusandae
tempore voluptates!
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi blanditiis consectetur ea eligendi id in
inventore ipsa iure laudantium libero, minus molestias necessitatibus nesciunt non omnis, quasi recusandae
tempore voluptates!
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi blanditiis consectetur ea eligendi id in
inventore ipsa iure laudantium libero, minus molestias necessitatibus nesciunt non omnis, quasi recusandae
tempore voluptates!
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi blanditiis consectetur ea eligendi id in
inventore ipsa iure laudantium libero, minus molestias necessitatibus nesciunt non omnis, quasi recusandae
tempore voluptates!
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi blanditiis consectetur ea eligendi id in
inventore ipsa iure laudantium libero, minus molestias necessitatibus nesciunt non omnis, quasi recusandae
tempore voluptates!
</p>
</div>
<script type="module">
import { driver } from "./src/driver.ts";
const basicTourSteps = [
{
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",
},
},
{
element: ".buttons",
popover: {
title: "Buttons",
description: "Here are some buttons",
},
},
{
element: "#scrollable-area",
popover: {
title: "Scrollable Areas",
description: "There are no issues with scrollable element tours as well.",
},
},
{
element: "#third-scroll-paragraph",
popover: {
title: "Nested Scrolls",
description: "Even the nested scrollable elements work now.",
},
},
];
document.getElementById("non-animated-tour").addEventListener("click", () => {
const driverObj = driver({
animate: false,
overlayColor: "blue",
overlayOpacity: 0.3,
showProgress: true,
steps: basicTourSteps,
});
driverObj.drive();
});
document.getElementById("confirm-exit-tour").addEventListener("click", () => {
const driverObj = driver({
animate: true,
overlayColor: "green",
overlayOpacity: 0.3,
steps: basicTourSteps,
onDestroyStarted: () => {
if (driverObj.hasNextStep() && confirm("Are you sure?")) {
driverObj.destroy();
} else {
driverObj.destroy();
}
},
});
driverObj.drive();
});
document.getElementById("progress-tour").addEventListener("click", () => {
const driverObj = driver({
animate: true,
steps: basicTourSteps,
showProgress: true,
});
driverObj.drive();
});
document.getElementById("progress-tour-template").addEventListener("click", () => {
const driverObj = driver({
animate: true,
steps: basicTourSteps,
showProgress: true,
progressText: "{{current}} of {{total}} done",
});
driverObj.drive();
});
document.getElementById("api-test").addEventListener("click", () => {
const driverObj = driver({
animate: true,
steps: basicTourSteps,
disableActiveInteraction: true,
showProgress: true,
progressText: "{{current}} of {{total}} done",
onPopoverRender: popover => {
popover.title.innerHTML = `${driverObj.getActiveIndex()} ${driverObj.hasNextStep() ? "Yes" : "No"} ${
driverObj.hasPreviousStep() ? "Yes" : "No"
}`;
popover.description.innerHTML = `${driverObj.isFirstStep() ? "Yes" : "No"} ${
driverObj.isLastStep() ? "Yes" : "No"
}`;
console.log(driverObj.getActiveIndex());
console.log(driverObj.getActiveStep());
},
});
driverObj.drive(4);
});
document.getElementById("reconfigure-steps").addEventListener("click", () => {
const driverObj = driver({
animate: true,
steps: basicTourSteps,
showProgress: true,
});
driverObj.setSteps([
{
element: "h1",
popover: {
description: "This is a new description",
},
},
{
element: "p",
popover: {
description: "This is another new description",
},
},
]);
driverObj.drive();
});
document.getElementById("disable-keyboard-control").addEventListener("click", () => {
const driverObj = driver({
animate: true,
steps: basicTourSteps,
showProgress: true,
allowKeyboardControl: false,
});
driverObj.setSteps([
{
element: "h1",
popover: {
description: "This is a new description",
},
},
{
element: "p",
popover: {
description: "This is another new description",
},
},
]);
driverObj.drive();
});
document.getElementById("async-tour").addEventListener("click", () => {
const driverObj = driver({
animate: true,
overlayOpacity: 0.3,
showProgress: true,
progressText: "{{current}} / {{total}}",
steps: [
{
element: ".page-header",
popover: {
title: "Async 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: "Async Test",
description: "By overriding `onNextClick` you get control over the tour.",
side: "left",
align: "start",
onNextClick: () => {
const newDiv = document.querySelector(".dynamic-el") || document.createElement("div");
newDiv.innerHTML = "This is a new Element";
newDiv.style.display = "block";
newDiv.style.padding = "20px";
newDiv.style.backgroundColor = "black";
newDiv.style.color = "white";
newDiv.style.fontSize = "14px";
newDiv.style.position = "fixed";
newDiv.style.top = `${Math.random() * (500 - 30) + 30}px`;
newDiv.style.left = `${Math.random() * (500 - 30) + 30}px`;
newDiv.className = "dynamic-el";
document.body.appendChild(newDiv);
driverObj.moveNext();
},
},
},
{
element: ".dynamic-el",
onDeselected: element => {
element?.parentElement?.removeChild(element);
},
popover: {
title: "Dynamic Elements",
description: "This was created before we moved here",
},
},
{
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",
},
},
{
element: "#scrollable-area",
popover: {
title: "Scrollable Areas",
description: "There are no issues with scrollable element tours as well.",
},
},
{
element: "#third-scroll-paragraph",
popover: {
title: "Nested Scrolls",
description: "Even the nested scrollable elements work now.",
},
},
],
});
driverObj.drive();
});
document.getElementById("basic-tour").addEventListener("click", () => {
const driverObj = driver({
showProgress: true,
showButtons: ["next", "previous", "close"],
steps: basicTourSteps,
});
driverObj.drive();
});
document.getElementById("no-buttons").addEventListener("click", () => {
const driverObj = driver({});
driverObj.highlight({
element: "#no-buttons",
popover: {
title: "No Buttons",
description:
"No buttons are shown by default for highlight. You can pass in the option to show the buttons you want.",
},
});
});
document.getElementById("buttons-from-popover").addEventListener("click", () => {
const driverObj = driver();
driverObj.highlight({
element: "#buttons-from-popover",
popover: {
title: "No Buttons",
showButtons: ["close"],
description:
"No buttons are shown by default for highlight. You can pass in the option to show the buttons you want.",
},
});
});
document.getElementById("next-button").addEventListener("click", () => {
const driverObj = driver();
driverObj.highlight({
element: "#next-button",
popover: {
title: "Next and Close",
showButtons: ["close", "next"],
description: "This one only has next and close buttons, nothing else.",
onNextClick: (step, element, opts) => {
console.log("Next Clicked", step, element, opts);
opts.driver.destroy();
},
},
});
});
document.getElementById("previous-button").addEventListener("click", () => {
const driverObj = driver();
driverObj.highlight({
element: "#previous-button",
popover: {
title: "Previous and Close",
showButtons: ["close", "previous"],
description: "This one only has previous and close buttons, nothing else.",
},
});
});
document.getElementById("next-prev-button").addEventListener("click", () => {
const driverObj = driver();
driverObj.highlight({
element: "#next-prev-button",
popover: {
title: "Next and Previous Only",
showButtons: ["next", "previous"],
description: "This one only has next and previous buttons.",
},
});
});
document.getElementById("close-button").addEventListener("click", () => {
const driverObj = driver();
driverObj.highlight({
element: "#close-button",
popover: {
title: "Close Only",
showButtons: ["close"],
description: "This one only has close button.",
},
});
});
document.getElementById("button-texts").addEventListener("click", () => {
const driverObj = driver({
prevBtnText: "<——",
nextBtnText: "——>",
});
driverObj.highlight({
element: "#button-texts",
popover: {
side: "left",
title: "Button from Config",
showButtons: ["close", "next", "previous"],
nextBtnText: "==>",
prevBtnText: "<==",
description: "These buttons are configured using driver config.",
},
});
});
document.getElementById("popover-hook").addEventListener("click", () => {
const driverObj = driver({
onPopoverRender: popover => {
popover.title.innerText = "Modified Parent";
},
});
driverObj.highlight({
element: ".page-header",
popover: {
title: "Page Title",
description: "Body of the popover",
side: "bottom",
align: "start",
onPopoverRender: popover => {
popover.title.innerText = "Modified";
},
},
});
});
document.getElementById("padding-change").addEventListener("click", () => {
const driverObj = driver({
stagePadding: 0,
popoverOffset: 20,
stageRadius: 10,
});
driverObj.highlight({
element: "#padding-change",
popover: {
title: "Page Title",
description: "Body of the popover",
side: "bottom",
align: "start",
onPopoverRender: popover => {
popover.title.innerText = "Modified";
},
},
});
});
document.getElementById("custom-classes").addEventListener("click", () => {
const driverObj = driver({
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"],
},
});
});
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", () => {
const driverObj = driver({
onNextClick: () => {
alert("Next Clicked");
},
onPrevClick: () => {
alert("Previous Clicked");
},
onCloseClick: () => {
driverObj.destroy();
},
});
driverObj.highlight({
popover: {
title: "Global Button Listener",
description: "You can listen to the button clicks globally.",
showButtons: ["close", "next", "previous"],
onPrevClick: () => alert("Overriding — Previous Clicked"),
},
});
});
document.getElementById("button-tour-events").addEventListener("click", () => {
const driverObj = driver({
onNextClick: () => {
alert("Next Clicked");
driverObj.moveNext();
},
onPrevClick: () => {
alert("Previous Clicked");
driverObj.movePrevious();
},
onCloseClick: () => {
driverObj.destroy();
},
steps: [
{ popover: { title: "Some title", description: "Some description" } },
{ popover: { title: "Another title", description: "Some description" } },
{ popover: { title: "Yet another title", description: "Some description" } },
],
});
driverObj.drive();
});
document.getElementById("popover-hook-config").addEventListener("click", () => {
const driverObj = driver({
onPopoverRender: popover => {
const firstButton = document.createElement("button");
firstButton.innerText = "First";
popover.footerButtons.appendChild(firstButton);
firstButton.addEventListener("click", () => {
console.log("First Button Clicked");
});
},
steps: [
{ popover: { title: "Some title", description: "Some description" } },
{ popover: { title: "Another title", description: "Some description" } },
{ popover: { title: "Yet another title", description: "Some description" } },
],
});
driverObj.drive();
});
document.getElementById("is-active-btn").addEventListener("click", () => {
alert(driver().isActive());
});
document.getElementById("backdrop-color-btn").addEventListener("click", () => {
driver({
overlayColor: "blue",
overlayOpacity: 0.3,
}).highlight({
element: "#backdrop-color-btn",
});
});
document.getElementById("activate-check-btn").addEventListener("click", () => {
const driverObj = driver({
showButtons: false,
});
driverObj.highlight({
element: "#activate-check-btn",
popover: {
title: "Check if driver is active",
description: "This will alert the status after 2 seconds",
side: "bottom",
align: "start",
},
});
setTimeout(() => {
alert(`Status: ${driverObj.isActive()}. Destroying driver...`);
driverObj.destroy();
setTimeout(() => {
alert(`Status: ${driverObj.isActive()}`);
}, 0);
}, 2000);
});
document.getElementById("buggy-highlight-btn").addEventListener("click", () => {
driver().highlight({
element: ".page-header h1 sup",
popover: {
title: "Buggy Highlight",
description: "Unlike the older version, new version doesn't work with z-indexes so no more positional issues.",
side: "bottom",
align: "start",
},
});
});
document.getElementById("off-screen-highlight-btn").addEventListener("click", () => {
driver().highlight({
element: ".container",
popover: {
title: "Buggy Highlight",
description: "Unlike the older version, new version doesn't work with z-indexes so no more positional issues.",
side: "bottom",
align: "start",
},
});
});
document.getElementById("highlight-btn").addEventListener("click", () => {
driver({
animate: true,
popoverOffset: 10,
stagePadding: 10,
onDeselected: (element, step) => {
console.log("Deselected element", element, step);
},
onHighlightStarted: (element, step) => {
console.log("Started highlighting element", element, step);
},
onHighlighted: (element, step) => {
console.log("Highlighted element", element, step);
},
}).highlight({
element: "h2",
popover: {
title: "MIT License",
description: "A lightweight, no-dependency JavaScript engine to drive user's focus.",
side: "bottom",
align: "start",
},
});
});
document.getElementById("simple-highlight-btn").addEventListener("click", () => {
driver({ animate: false }).highlight({
element: "#large-paragraph-text",
popover: {
title: "driver.js",
description:
"Highlight anything, anywhere on the page. Yes, literally anything including SVG portions, scrollable items etc.",
align: "start",
side: "top",
},
});
});
document.getElementById("dark-highlight-btn").addEventListener("click", () => {
driver({
animate: true,
overlayOpacity: 0.9,
}).highlight({ element: "ul" });
});
document.getElementById("dim-highlight-btn").addEventListener("click", () => {
driver({
animate: true,
overlayOpacity: 0.2,
}).highlight({ element: ".buttons" });
});
document.getElementById("transition-highlight-btn").addEventListener("click", () => {
const driverObj = driver({
animate: true,
onDeselected: (element, step) => {
console.log("Deselected element", element, step);
},
onHighlightStarted: (element, step) => {
console.log("Started highlighting element", element, step);
},
onHighlighted: (element, step) => {
console.log("Highlighted element", element, step);
},
});
driverObj.highlight({
popover: {
title: "driver.js",
description: "Highlight anything, anywhere on the page",
},
});
window.setTimeout(() => {
driverObj.highlight({
element: ".buttons button:first-child",
popover: {
title: "driver.js",
description: "Highlight anything, anywhere on the page",
},
});
}, 2000);
window.setTimeout(() => {
driverObj.highlight({
popover: {
title: "driver.js",
description: "Highlight anything, anywhere on the page",
},
});
}, 4000);
window.setTimeout(() => {
driverObj.highlight({
element: "h2",
popover: {
description: "driver.js",
},
});
}, 6000);
});
document.getElementById("hooks").addEventListener("click", () => {
const pageHeader = document.getElementById(".page-header");
const driverObj = driver({
animate: true,
onDeselected: (element, step) => {
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 -";
console.log(`Highlight Started: ${elementText}\n${JSON.stringify(step)}`);
},
onHighlighted: (element, step) => {
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 -";
console.log(`Destroyed: ${elementText}\n${JSON.stringify(step)}`);
},
});
driverObj.highlight({
element: "#hooks",
popover: {
title: "Hooks",
description: "Here are all the hooks",
},
});
window.setTimeout(() => {
driverObj.highlight({
popover: {
title: "Popup Hook",
description: "There is no element below this popover",
},
});
}, 1000);
window.setTimeout(() => {
driverObj.highlight({
element: "#hooks-list",
popover: {
description: "Here are all the hooks",
},
});
}, 2000);
});
document.getElementById("scrollable-area-btn").addEventListener("click", () => {
const driverObj = driver({ animate: true });
driverObj.highlight({ element: "#scrollable-area" });
});
document.getElementById("without-element-btn").addEventListener("click", () => {
const driverObj = driver({
animate: true,
onDestroyed: (element, step) => {
console.log("Close modal", element, step);
},
onDeselected: (element, step) => {
console.log("Deselected element", element, step);
},
onHighlightStarted: (element, step, { config, state }) => {
console.log("Started highlighting element", element, step);
},
onHighlighted: (element, step) => {
console.log("Highlighted element", element, step);
},
});
driverObj.highlight({
popover: {
showButtons: [],
description:
"<div class='gif-popover'><img style='max-width: 100%' src='https://i.imgur.com/EAQhHu5.gif' /><p>Go and build something cool!</p></div>",
},
});
});
document.getElementById("inner-scroll-area-btn").addEventListener("click", () => {
const driverObj = driver({ animate: true });
driverObj.highlight({ element: "#third-scroll-paragraph" });
});
document.getElementById("disallow-close").addEventListener("click", () => {
const driverObj = driver({
animate: true,
allowClose: false,
});
driverObj.highlight({
element: ".buttons",
});
});
document.getElementById("click-overlay-to-next").addEventListener("click", () => {
const driverObj = driver({
animate: true,
overlayClickBehavior: "nextStep",
steps: basicTourSteps,
});
driverObj.drive();
});
document.getElementById("click-overlay-to-handle").addEventListener("click", () => {
const driverObj = driver({
animate: true,
overlayClickBehavior: (element, step) => {
alert("Clicking me");
},
steps: basicTourSteps,
});
driverObj.drive();
});
document.getElementById("destroy-btn").addEventListener("click", () => {
driver().destroy();
});
</script>
</body>
</html>
================================================
FILE: license
================================================
The MIT License
Copyright (c) Kamran Ahmed
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: package.json
================================================
{
"name": "driver.js",
"license": "MIT",
"private": false,
"version": "1.4.0",
"main": "./dist/driver.js.cjs",
"module": "./dist/driver.js.mjs",
"types": "./dist/driver.js.d.ts",
"homepage": "https://driverjs.com",
"repository": "https://github.com/kamranahmedse/driver.js",
"author": "Kamran Ahmed <kamranahmed.se@gmail.com>",
"bugs": {
"url": "https://github.com/kamranahmedse/driver.js/issues"
},
"exports": {
".": {
"types": "./dist/driver.js.d.ts",
"require": "./dist/driver.js.cjs",
"import": "./dist/driver.js.mjs"
},
"./dist/driver.css": {
"require": "./dist/driver.css",
"import": "./dist/driver.css",
"default": "./dist/driver.css"
}
},
"scripts": {
"dev": "vite --host",
"build": "tsc && vite build && dts-bundle-generator --config ./dts-bundle-generator.config.ts",
"test": "vitest",
"format": "prettier . --write"
},
"files": [
"!tests/**/*",
"!docs/**/*",
"dist/**/*",
"!dist/**/*.js.map"
],
"devDependencies": {
"@types/jsdom": "^21.1.2",
"@types/node": "^20.5.9",
"@vitest/coverage-c8": "^0.32.0",
"dts-bundle-generator": "^8.0.1",
"postcss": "^8.4.29",
"postcss-scss": "^4.0.7",
"prettier": "^3.0.3",
"ts-node": "^10.9.1",
"typescript": "^5.2.2",
"vite": "^4.4.9",
"vitest": "^0.34.3"
},
"keywords": [
"driver.js",
"driver",
"tour",
"guide",
"overlay",
"tooltip",
"walkthrough",
"product tour",
"product walkthrough",
"product guide",
"product tutorial",
"product demo",
"modal",
"lightbox"
]
}
================================================
FILE: readme.md
================================================
<h1 align="center"><img height="150" src="https://driverjs.com/driver.svg" /><br> Driver.js</h1>
<p align="center">
<a href="https://github.com/kamranahmedse/driver.js/blob/master/license">
<img src="https://img.shields.io/badge/License-MIT-yellow.svg" />
</a>
<a href="https://www.jsdelivr.com/package/npm/driver.js">
<img src="https://data.jsdelivr.com/v1/package/npm/driver.js/badge?style=rounded" alt="jsdelivr hits" />
</a>
<a href="https://npmjs.org/package/driver.js">
<img src="https://img.shields.io/npm/dm/driver.js" alt="downloads" />
</a>
</p>
<p align="center">
<b>Powerful, highly customizable vanilla JavaScript engine to drive user's focus on the page</b></br>
<sub>No external dependencies, light-weight, supports all major browsers and highly customizable </sub><br>
</p>
<br />
- **Simple**: is simple to use and has no external dependency at all
- **Light-weight**: is just 5kb gzipped as compared to other libraries which are +12kb gzipped
- **Highly customizable**: has a powerful API and can be used however you want
- **Highlight anything**: highlight any (literally any) element on page
- **Feature introductions**: create powerful feature introductions for your web applications
- **Focus shifters**: add focus shifters for users
- **User friendly**: Everything is controllable by keyboard
- **TypeScript**: Written in TypeScript
- **Consistent behavior**: usable across all browsers
- **MIT Licensed**: free for personal and commercial use
<br />
## Documentation
For demos and documentation, visit [driverjs.com](https://driverjs.com)
<br />
## So, yet another tour library?
**No**, it's more than a tour library. **Tours are just one of the many use-cases**. Driver.js can be used wherever you need some sort of overlay for the page; some common usecases could be: [highlighting a page component](https://i.imgur.com/TS0LSK9.png) when user is interacting with some component to keep them focused, providing contextual help e.g. popover with dimmed background when user is filling a form, using it as a focus shifter to bring user's attention to some component on page, using it to simulate those "Turn off the Lights" widgets that you might have seen on video players online, usage as a simple modal, and of-course product tours etc.
Driver.js is written in Vanilla TypeScript, has zero dependencies and is highly customizable. It has several options allowing you to change how it behaves and also **provides you the hooks** to manipulate the elements as they are highlighted, about to be highlighted, or deselected.
> Also, comparing the size of Driver.js with other libraries, it's the most light-weight, it is **just ~5kb gzipped** while others are 12kb+.
<br>
## Contributions
Feel free to submit pull requests, create issues or spread the word.
## License
MIT © [Kamran Ahmed](https://twitter.com/kamrify)
================================================
FILE: src/config.ts
================================================
import { Driver, DriveStep } from "./driver";
import { AllowedButtons, PopoverDOM } from "./popover";
import { State } from "./state";
export type DriverHook = (
element: Element | undefined,
step: DriveStep,
opts: { config: Config; state: State; driver: Driver }
) => void;
export type Config = {
steps?: DriveStep[];
animate?: boolean;
overlayColor?: string;
overlayOpacity?: number;
smoothScroll?: boolean;
allowClose?: boolean;
overlayClickBehavior?: "close" | "nextStep" | DriverHook;
stagePadding?: number;
stageRadius?: number;
disableActiveInteraction?: boolean;
allowKeyboardControl?: boolean;
// Popover specific configuration
popoverClass?: string;
popoverOffset?: number;
showButtons?: AllowedButtons[];
disableButtons?: AllowedButtons[];
showProgress?: boolean;
// Button texts
progressText?: string;
nextBtnText?: string;
prevBtnText?: string;
doneBtnText?: string;
// Called after the popover is rendered
onPopoverRender?: (popover: PopoverDOM, opts: { config: Config; state: State, driver: Driver }) => void;
// State based callbacks, called upon state changes
onHighlightStarted?: DriverHook;
onHighlighted?: DriverHook;
onDeselected?: DriverHook;
onDestroyStarted?: DriverHook;
onDestroyed?: DriverHook;
// Event based callbacks, called upon events
onNextClick?: DriverHook;
onPrevClick?: DriverHook;
onCloseClick?: DriverHook;
};
let currentConfig: Config = {};
let currentDriver: Driver;
export function configure(config: Config = {}) {
currentConfig = {
animate: true,
allowClose: true,
overlayClickBehavior: "close",
overlayOpacity: 0.7,
smoothScroll: false,
disableActiveInteraction: false,
showProgress: false,
stagePadding: 10,
stageRadius: 5,
popoverOffset: 10,
showButtons: ["next", "previous", "close"],
disableButtons: [],
overlayColor: "#000",
...config,
};
}
export function getConfig(): Config;
export function getConfig<K extends keyof Config>(key: K): Config[K];
export function getConfig<K extends keyof Config>(key?: K) {
return key ? currentConfig[key] : currentConfig;
}
export function setCurrentDriver(driver: Driver) {
currentDriver = driver;
}
export function getCurrentDriver() {
return currentDriver;
}
================================================
FILE: src/driver.css
================================================
.driver-active .driver-overlay {
pointer-events: none;
}
.driver-active * {
pointer-events: none;
}
.driver-active .driver-active-element,
.driver-active .driver-active-element *,
.driver-popover,
.driver-popover * {
pointer-events: auto;
}
@keyframes animate-fade-in {
0% {
opacity: 0;
}
to {
opacity: 1;
}
}
.driver-fade .driver-overlay {
animation: animate-fade-in 200ms ease-in-out;
}
.driver-fade .driver-popover {
animation: animate-fade-in 200ms;
}
/* Popover styles */
.driver-popover {
all: unset;
box-sizing: border-box;
color: #2d2d2d;
margin: 0;
padding: 15px;
border-radius: 5px;
min-width: 250px;
max-width: 300px;
box-shadow: 0 1px 10px #0006;
z-index: 1000000000;
position: fixed;
top: 0;
right: 0;
background-color: #fff;
}
.driver-popover * {
font-family: "Helvetica Neue", Inter, ui-sans-serif, "Apple Color Emoji", Helvetica, Arial, sans-serif;
}
.driver-popover-title {
font: 19px / normal sans-serif;
font-weight: 700;
display: block;
position: relative;
line-height: 1.5;
zoom: 1;
margin: 0;
}
.driver-popover-close-btn {
all: unset;
position: absolute;
top: 0;
right: 0;
width: 32px;
height: 28px;
cursor: pointer;
font-size: 18px;
font-weight: 500;
color: #d2d2d2;
z-index: 1;
text-align: center;
transition: color;
transition-duration: 200ms;
}
.driver-popover-close-btn:hover,
.driver-popover-close-btn:focus {
color: #2d2d2d;
}
.driver-popover-title[style*="block"] + .driver-popover-description {
margin-top: 5px;
}
.driver-popover-description {
margin-bottom: 0;
font: 14px / normal sans-serif;
line-height: 1.5;
font-weight: 400;
zoom: 1;
}
.driver-popover-footer {
margin-top: 15px;
text-align: right;
zoom: 1;
display: flex;
align-items: center;
justify-content: space-between;
}
.driver-popover-progress-text {
font-size: 13px;
font-weight: 400;
color: #727272;
zoom: 1;
}
.driver-popover-footer button {
all: unset;
display: inline-block;
box-sizing: border-box;
padding: 3px 7px;
text-decoration: none;
text-shadow: 1px 1px 0 #fff;
background-color: #ffffff;
color: #2d2d2d;
font: 12px / normal sans-serif;
cursor: pointer;
outline: 0;
zoom: 1;
line-height: 1.3;
border: 1px solid #ccc;
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;
}
.driver-no-interaction, .driver-no-interaction * {
pointer-events: none !important;
}
.driver-popover-footer button:hover,
.driver-popover-footer button:focus {
background-color: #f7f7f7;
}
.driver-popover-navigation-btns {
display: flex;
flex-grow: 1;
justify-content: flex-end;
}
.driver-popover-navigation-btns button + button {
margin-left: 4px;
}
.driver-popover-arrow {
content: "";
position: absolute;
border: 5px solid #fff;
}
.driver-popover-arrow-side-over {
display: none;
}
/** Popover Arrow Sides **/
.driver-popover-arrow-side-left {
left: 100%;
border-right-color: transparent;
border-bottom-color: transparent;
border-top-color: transparent;
}
.driver-popover-arrow-side-right {
right: 100%;
border-left-color: transparent;
border-bottom-color: transparent;
border-top-color: transparent;
}
.driver-popover-arrow-side-top {
top: 100%;
border-right-color: transparent;
border-bottom-color: transparent;
border-left-color: transparent;
}
.driver-popover-arrow-side-bottom {
bottom: 100%;
border-left-color: transparent;
border-top-color: transparent;
border-right-color: transparent;
}
.driver-popover-arrow-side-center {
display: none;
}
/* Left/Start + Right/Start */
.driver-popover-arrow-side-left.driver-popover-arrow-align-start,
.driver-popover-arrow-side-right.driver-popover-arrow-align-start {
top: 15px;
}
/* Top/Start + Bottom/Start */
.driver-popover-arrow-side-top.driver-popover-arrow-align-start,
.driver-popover-arrow-side-bottom.driver-popover-arrow-align-start {
left: 15px;
}
/* End/Left + End/Right */
.driver-popover-arrow-align-end.driver-popover-arrow-side-left,
.driver-popover-arrow-align-end.driver-popover-arrow-side-right {
bottom: 15px;
}
/* Top/End + Bottom/End */
.driver-popover-arrow-side-top.driver-popover-arrow-align-end,
.driver-popover-arrow-side-bottom.driver-popover-arrow-align-end {
right: 15px;
}
/* Left/Center + Right/Center */
.driver-popover-arrow-side-left.driver-popover-arrow-align-center,
.driver-popover-arrow-side-right.driver-popover-arrow-align-center {
top: 50%;
margin-top: -5px;
}
/* Top/Center + Bottom/Center */
.driver-popover-arrow-side-top.driver-popover-arrow-align-center,
.driver-popover-arrow-side-bottom.driver-popover-arrow-align-center {
left: 50%;
margin-left: -5px;
}
/* No arrow */
.driver-popover-arrow-none {
display: none;
}
================================================
FILE: src/driver.ts
================================================
import { AllowedButtons, destroyPopover, Popover } from "./popover";
import { destroyOverlay } from "./overlay";
import { destroyEvents, initEvents, requireRefresh } from "./events";
import { Config, configure, DriverHook, getConfig, getCurrentDriver, setCurrentDriver } from "./config";
import { destroyHighlight, highlight } from "./highlight";
import { destroyEmitter, listen } from "./emitter";
import { getState, resetState, setState } from "./state";
import "./driver.css";
export type DriveStep = {
element?: string | Element | (() => Element);
onHighlightStarted?: DriverHook;
onHighlighted?: DriverHook;
onDeselected?: DriverHook;
popover?: Popover;
disableActiveInteraction?: boolean;
};
export interface Driver {
isActive: () => boolean;
refresh: () => void;
drive: (stepIndex?: number) => void;
setConfig: (config: Config) => void;
setSteps: (steps: DriveStep[]) => void;
getConfig: () => Config;
getState: (key?: string) => any;
getActiveIndex: () => number | undefined;
isFirstStep: () => boolean;
isLastStep: () => boolean;
getActiveStep: () => DriveStep | undefined;
getActiveElement: () => Element | undefined;
getPreviousElement: () => Element | undefined;
getPreviousStep: () => DriveStep | undefined;
moveNext: () => void;
movePrevious: () => void;
moveTo: (index: number) => void;
hasNextStep: () => boolean;
hasPreviousStep: () => boolean;
highlight: (step: DriveStep) => void;
destroy: () => void;
}
export function driver(options: Config = {}): Driver {
configure(options);
function handleClose() {
if (!getConfig("allowClose")) {
return;
}
destroy();
}
function handleOverlayClick() {
const overlayClickBehavior = getConfig("overlayClickBehavior");
if (getConfig("allowClose") && overlayClickBehavior === "close") {
destroy();
return;
}
if (typeof overlayClickBehavior === "function") {
const activeStep = getState("__activeStep");
const activeElement = getState("__activeElement");
overlayClickBehavior(activeElement, activeStep!, {
config: getConfig(),
state: getState(),
driver: getCurrentDriver(),
});
return;
}
if (overlayClickBehavior === "nextStep") {
moveNext();
}
}
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 moveTo(index: number) {
const steps = getConfig("steps") || [];
if (steps[index]) {
drive(index);
} else {
destroy();
}
}
function handleArrowLeft() {
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 onPrevClick = activeStep.popover?.onPrevClick || getConfig("onPrevClick");
if (onPrevClick) {
return onPrevClick(activeElement, activeStep, {
config: getConfig(),
state: getState(),
driver: getCurrentDriver(),
});
}
movePrevious();
}
function handleArrowRight() {
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 onNextClick = activeStep.popover?.onNextClick || getConfig("onNextClick");
if (onNextClick) {
return onNextClick(activeElement, activeStep, {
config: getConfig(),
state: getState(),
driver: getCurrentDriver(),
});
}
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", handleOverlayClick);
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]) {
destroy();
return;
}
setState("__activeOnDestroyed", document.activeElement as HTMLElement);
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");
const showProgress =
typeof currentStep.popover?.showProgress !== "undefined"
? currentStep.popover?.showProgress
: getConfig("showProgress");
const progressText = currentStep.popover?.progressText || getConfig("progressText") || "{{current}} of {{total}}";
const progressTextReplaced = progressText
.replace("{{current}}", `${stepIndex + 1}`)
.replace("{{total}}", `${steps.length}`);
const configuredButtons = currentStep.popover?.showButtons || getConfig("showButtons");
const calculatedButtons: AllowedButtons[] = [
"next",
"previous",
...(allowsClosing ? ["close" as AllowedButtons] : []),
].filter(b => {
return !configuredButtons?.length || configuredButtons.includes(b as AllowedButtons);
}) as AllowedButtons[];
const onNextClick = currentStep.popover?.onNextClick || getConfig("onNextClick");
const onPrevClick = currentStep.popover?.onPrevClick || getConfig("onPrevClick");
const onCloseClick = currentStep.popover?.onCloseClick || getConfig("onCloseClick");
highlight({
...currentStep,
popover: {
showButtons: calculatedButtons,
nextBtnText: !hasNextStep ? doneBtnText : undefined,
disableButtons: [...(!hasPreviousStep ? ["previous" as AllowedButtons] : [])],
showProgress: showProgress,
progressText: progressTextReplaced,
onNextClick: onNextClick
? onNextClick
: () => {
if (!hasNextStep) {
destroy();
} else {
drive(stepIndex + 1);
}
},
onPrevClick: onPrevClick
? onPrevClick
: () => {
drive(stepIndex - 1);
},
onCloseClick: onCloseClick
? onCloseClick
: () => {
destroy();
},
...(currentStep?.popover || {}),
},
});
}
function destroy(withOnDestroyStartedHook = true) {
const activeElement = getState("__activeElement");
const activeStep = getState("__activeStep");
const activeOnDestroyed = getState("__activeOnDestroyed");
const onDestroyStarted = getConfig("onDestroyStarted");
// `onDestroyStarted` is used to confirm the exit of tour. If we trigger
// the hook for when user calls `destroy`, driver will get into infinite loop
// not causing tour to be destroyed.
if (withOnDestroyStartedHook && onDestroyStarted) {
const isActiveDummyElement = !activeElement || activeElement?.id === "driver-dummy-element";
onDestroyStarted(isActiveDummyElement ? undefined : activeElement, activeStep!, {
config: getConfig(),
state: getState(),
driver: getCurrentDriver(),
});
return;
}
const onDeselected = activeStep?.onDeselected || getConfig("onDeselected");
const onDestroyed = getConfig("onDestroyed");
document.body.classList.remove("driver-active", "driver-fade", "driver-simple");
destroyEvents();
destroyPopover();
destroyHighlight();
destroyOverlay();
destroyEmitter();
resetState();
if (activeElement && activeStep) {
const isActiveDummyElement = activeElement.id === "driver-dummy-element";
if (onDeselected) {
onDeselected(isActiveDummyElement ? undefined : activeElement, activeStep, {
config: getConfig(),
state: getState(),
driver: getCurrentDriver(),
});
}
if (onDestroyed) {
onDestroyed(isActiveDummyElement ? undefined : activeElement, activeStep, {
config: getConfig(),
state: getState(),
driver: getCurrentDriver(),
});
}
}
if (activeOnDestroyed) {
(activeOnDestroyed as HTMLElement).focus();
}
}
const api: Driver = {
isActive: () => getState("isInitialized") || false,
refresh: requireRefresh,
drive: (stepIndex: number = 0) => {
init();
drive(stepIndex);
},
setConfig: configure,
setSteps: (steps: DriveStep[]) => {
resetState();
configure({
...getConfig(),
steps,
});
},
getConfig,
getState,
getActiveIndex: () => getState("activeIndex"),
isFirstStep: () => getState("activeIndex") === 0,
isLastStep: () => {
const steps = getConfig("steps") || [];
const activeIndex = getState("activeIndex");
return activeIndex !== undefined && activeIndex === steps.length - 1;
},
getActiveStep: () => getState("activeStep"),
getActiveElement: () => getState("activeElement"),
getPreviousElement: () => getState("previousElement"),
getPreviousStep: () => getState("previousStep"),
moveNext,
movePrevious,
moveTo,
hasNextStep: () => {
const steps = getConfig("steps") || [];
const activeIndex = getState("activeIndex");
return activeIndex !== undefined && !!steps[activeIndex + 1];
},
hasPreviousStep: () => {
const steps = getConfig("steps") || [];
const activeIndex = getState("activeIndex");
return activeIndex !== undefined && !!steps[activeIndex - 1];
},
highlight: (step: DriveStep) => {
init();
highlight({
...step,
popover: step.popover
? {
showButtons: [],
showProgress: false,
progressText: "",
...step.popover!,
}
: undefined,
});
},
destroy: () => {
destroy(false);
},
};
setCurrentDriver(api);
return api;
}
================================================
FILE: src/emitter.ts
================================================
type allowedEvents =
| "overlayClick"
| "escapePress"
| "nextClick"
| "prevClick"
| "closeClick"
| "arrowRightPress"
| "arrowLeftPress";
let registeredListeners: Partial<{ [key in allowedEvents]: () => void }> = {};
export function listen(hook: allowedEvents, callback: () => void) {
registeredListeners[hook] = callback;
}
export function emit(hook: allowedEvents) {
registeredListeners[hook]?.();
}
export function destroyEmitter() {
registeredListeners = {};
}
================================================
FILE: src/events.ts
================================================
import { refreshActiveHighlight } from "./highlight";
import { emit } from "./emitter";
import { getState, setState } from "./state";
import { getConfig } from "./config";
import { getFocusableElements } from "./utils";
export function requireRefresh() {
const resizeTimeout = getState("__resizeTimeout");
if (resizeTimeout) {
window.cancelAnimationFrame(resizeTimeout);
}
setState("__resizeTimeout", window.requestAnimationFrame(refreshActiveHighlight));
}
function trapFocus(e: KeyboardEvent) {
const isActivated = getState("isInitialized");
if (!isActivated) {
return;
}
const isTabKey = e.key === "Tab" || e.keyCode === 9;
if (!isTabKey) {
return;
}
const activeElement = getState("__activeElement");
const popoverEl = getState("popover")?.wrapper;
const focusableEls = getFocusableElements([
...(popoverEl ? [popoverEl] : []),
...(activeElement ? [activeElement] : []),
]);
const firstFocusableEl = focusableEls[0];
const lastFocusableEl = focusableEls[focusableEls.length - 1];
e.preventDefault();
if (e.shiftKey) {
const previousFocusableEl =
focusableEls[focusableEls.indexOf(document.activeElement as HTMLElement) - 1] || lastFocusableEl;
previousFocusableEl?.focus();
} else {
const nextFocusableEl =
focusableEls[focusableEls.indexOf(document.activeElement as HTMLElement) + 1] || firstFocusableEl;
nextFocusableEl?.focus();
}
}
function onKeyup(e: KeyboardEvent) {
const allowKeyboardControl = getConfig("allowKeyboardControl") ?? true;
if (!allowKeyboardControl) {
return;
}
if (e.key === "Escape") {
emit("escapePress");
} else if (e.key === "ArrowRight") {
emit("arrowRightPress");
} else if (e.key === "ArrowLeft") {
emit("arrowLeftPress");
}
}
/**
* 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
* prevents all other pointer-events to make sure no external-library
* ever knows the click happened.
*
* @param {Element} element Element to listen for click events
* @param {(pointer: MouseEvent | PointerEvent) => void} listener Click handler
* @param {(target: HTMLElement) => boolean} shouldPreventDefault Whether to prevent default action i.e. link clicks etc
*/
export function onDriverClick(
element: Element,
listener: (pointer: MouseEvent | PointerEvent) => void,
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();
}
listener?.(e);
};
// We want to be the absolute first one to hear about the event
const useCapture = true;
// Events to disable
document.addEventListener("pointerdown", listenerWrapper, useCapture);
document.addEventListener("mousedown", listenerWrapper, useCapture);
document.addEventListener("pointerup", listenerWrapper, useCapture);
document.addEventListener("mouseup", listenerWrapper, useCapture);
// Actual click handler
document.addEventListener(
"click",
e => {
listenerWrapper(e, listener);
},
useCapture
);
}
export function initEvents() {
window.addEventListener("keyup", onKeyup, false);
window.addEventListener("keydown", trapFocus, false);
window.addEventListener("resize", requireRefresh);
window.addEventListener("scroll", requireRefresh);
}
export function destroyEvents() {
window.removeEventListener("keyup", onKeyup);
window.removeEventListener("resize", requireRefresh);
window.removeEventListener("scroll", requireRefresh);
}
================================================
FILE: src/highlight.ts
================================================
import { DriveStep } from "./driver";
import { refreshOverlay, trackActiveElement, transitionStage } from "./overlay";
import { getConfig, getCurrentDriver } from "./config";
import { hidePopover, renderPopover, repositionPopover } 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 === "function" ? element() : typeof element === "string" ? document.querySelector(element) : element;
// If the element is not found, we mount a 1px div
// at the center of the screen to highlight and show
// the popover on top of that. This is to show a
// modal-like highlight.
if (!elemObj) {
elemObj = mountDummyElement();
}
transferHighlight(elemObj, step);
}
export function refreshActiveHighlight() {
const activeHighlight = getState("__activeElement");
const activeStep = getState("__activeStep")!;
if (!activeHighlight) {
return;
}
trackActiveElement(activeHighlight);
refreshOverlay();
re
gitextract_tn0sokzd/ ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── docs/ │ ├── .gitignore │ ├── astro.config.mjs │ ├── package.json │ ├── src/ │ │ ├── components/ │ │ │ ├── Analytics/ │ │ │ │ ├── Analytics.astro │ │ │ │ └── analytics.ts │ │ │ ├── CodeSample.tsx │ │ │ ├── Container.astro │ │ │ ├── DocsHeader.tsx │ │ │ ├── ExampleButton.tsx │ │ │ ├── Examples.astro │ │ │ ├── FeatureMarquee.tsx │ │ │ ├── Features.astro │ │ │ ├── FormHelp.tsx │ │ │ ├── HeroSection.astro │ │ │ ├── OpenSourceLove.astro │ │ │ ├── Sidebar.astro │ │ │ ├── UsecaseItem.astro │ │ │ └── UsecaseList.astro │ │ ├── content/ │ │ │ ├── config.ts │ │ │ └── guides/ │ │ │ ├── animated-tour.mdx │ │ │ ├── api.mdx │ │ │ ├── async-tour.mdx │ │ │ ├── basic-usage.mdx │ │ │ ├── buttons.mdx │ │ │ ├── configuration.mdx │ │ │ ├── confirm-on-exit.mdx │ │ │ ├── installation.mdx │ │ │ ├── migrating-from-0x.mdx │ │ │ ├── popover-position.mdx │ │ │ ├── prevent-destroy.mdx │ │ │ ├── simple-highlight.mdx │ │ │ ├── static-tour.mdx │ │ │ ├── styling-overlay.mdx │ │ │ ├── styling-popover.mdx │ │ │ ├── theming.mdx │ │ │ └── tour-progress.mdx │ │ ├── env.d.ts │ │ ├── layouts/ │ │ │ ├── BaseLayout.astro │ │ │ └── DocsLayout.astro │ │ ├── lib/ │ │ │ ├── github.ts │ │ │ └── guide.ts │ │ └── pages/ │ │ ├── docs/ │ │ │ └── [guideId].astro │ │ └── index.astro │ ├── tailwind.config.cjs │ └── tsconfig.json ├── dts-bundle-generator.config.ts ├── index.html ├── license ├── package.json ├── readme.md ├── src/ │ ├── config.ts │ ├── driver.css │ ├── driver.ts │ ├── emitter.ts │ ├── events.ts │ ├── highlight.ts │ ├── overlay.ts │ ├── popover.ts │ ├── state.ts │ └── utils.ts ├── tests/ │ └── sum.test.ts ├── tsconfig.json └── vite.config.ts
SYMBOL INDEX (74 symbols across 18 files)
FILE: docs/src/components/Analytics/analytics.ts
type Window (line 2) | interface Window {
FILE: docs/src/components/CodeSample.tsx
type CodeSampleProps (line 5) | type CodeSampleProps = {
function removeDummyElement (line 18) | function removeDummyElement() {
function mountDummyElement (line 25) | function mountDummyElement() {
function attachFirstButton (line 42) | function attachFirstButton(popover: PopoverDOM) {
function CodeSample (line 52) | function CodeSample(props: CodeSampleProps) {
FILE: docs/src/components/DocsHeader.tsx
type DocsHeaderProps (line 4) | type DocsHeaderProps = {
function DocsHeader (line 9) | function DocsHeader(props: DocsHeaderProps) {
FILE: docs/src/components/ExampleButton.tsx
type ExampleButtonProps (line 1) | type ExampleButtonProps = {
function ExampleButton (line 7) | function ExampleButton(props: ExampleButtonProps) {
FILE: docs/src/components/FeatureMarquee.tsx
function FeatureMarquee (line 19) | function FeatureMarquee() {
FILE: docs/src/components/FormHelp.tsx
function FormHelp (line 5) | function FormHelp() {
FILE: docs/src/env.d.ts
type Window (line 4) | interface Window {
FILE: docs/src/lib/github.ts
function countStars (line 8) | async function countStars(repo = "kamranahmedse/driver.js"): Promise<num...
function getFormattedStars (line 26) | async function getFormattedStars(repo = "kamranahmedse/driver.js"): Prom...
FILE: docs/src/lib/guide.ts
function getAllGuides (line 3) | async function getAllGuides(): Promise<Record<string, CollectionEntry<"g...
FILE: src/config.ts
type DriverHook (line 5) | type DriverHook = (
type Config (line 11) | type Config = {
function configure (line 59) | function configure(config: Config = {}) {
function getConfig (line 80) | function getConfig<K extends keyof Config>(key?: K) {
function setCurrentDriver (line 84) | function setCurrentDriver(driver: Driver) {
function getCurrentDriver (line 88) | function getCurrentDriver() {
FILE: src/driver.ts
type DriveStep (line 10) | type DriveStep = {
type Driver (line 19) | interface Driver {
function driver (line 43) | function driver(options: Config = {}): Driver {
FILE: src/emitter.ts
type allowedEvents (line 1) | type allowedEvents =
function listen (line 12) | function listen(hook: allowedEvents, callback: () => void) {
function emit (line 16) | function emit(hook: allowedEvents) {
function destroyEmitter (line 20) | function destroyEmitter() {
FILE: src/events.ts
function requireRefresh (line 7) | function requireRefresh() {
function trapFocus (line 16) | function trapFocus(e: KeyboardEvent) {
function onKeyup (line 51) | function onKeyup(e: KeyboardEvent) {
function onDriverClick (line 77) | function onDriverClick(
function initEvents (line 116) | function initEvents() {
function destroyEvents (line 123) | function destroyEvents() {
FILE: src/highlight.ts
function mountDummyElement (line 8) | function mountDummyElement(): Element {
function highlight (line 30) | function highlight(step: DriveStep) {
function refreshActiveHighlight (line 46) | function refreshActiveHighlight() {
function transferHighlight (line 59) | function transferHighlight(toElement: Element, toStep: DriveStep) {
function destroyHighlight (line 174) | function destroyHighlight() {
FILE: src/overlay.ts
type StageDefinition (line 7) | type StageDefinition = {
function transitionStage (line 16) | function transitionStage(elapsed: number, duration: number, from: Elemen...
function trackActiveElement (line 38) | function trackActiveElement(element: Element) {
function refreshOverlay (line 57) | function refreshOverlay() {
function mountOverlay (line 76) | function mountOverlay(stagePosition: StageDefinition) {
function renderOverlay (line 92) | function renderOverlay(stagePosition: StageDefinition) {
function createOverlaySvg (line 110) | function createOverlaySvg(stage: StageDefinition): SVGSVGElement {
function generateStageSvgPathString (line 148) | function generateStageSvgPathString(stage: StageDefinition) {
function destroyOverlay (line 173) | function destroyOverlay() {
FILE: src/popover.ts
type Side (line 8) | type Side = "top" | "right" | "bottom" | "left" | "over";
type Alignment (line 9) | type Alignment = "start" | "center" | "end";
type AllowedButtons (line 10) | type AllowedButtons = "next" | "previous" | "close";
type Popover (line 12) | type Popover = {
type PopoverDOM (line 39) | type PopoverDOM = {
function hidePopover (line 52) | function hidePopover() {
function renderPopover (line 61) | function renderPopover(element: Element, step: DriveStep) {
type PopoverDimensions (line 238) | type PopoverDimensions = {
function getPopoverDimensions (line 245) | function getPopoverDimensions(): PopoverDimensions | undefined {
function calculateTopForLeftRight (line 265) | function calculateTopForLeftRight(
function calculateLeftForTopBottom (line 310) | function calculateLeftForTopBottom(
function repositionPopover (line 354) | function repositionPopover(element: Element, step: DriveStep) {
function renderPopoverArrow (line 500) | function renderPopoverArrow(alignment: Alignment, side: Side, element: E...
function createPopover (line 626) | function createPopover(): PopoverDOM {
function destroyPopover (line 696) | function destroyPopover() {
FILE: src/state.ts
type State (line 5) | type State = {
function setState (line 33) | function setState<K extends keyof State>(key: K, value: State[K]) {
function getState (line 39) | function getState<K extends keyof State>(key?: K) {
function resetState (line 43) | function resetState() {
FILE: src/utils.ts
function easeInOutQuad (line 3) | function easeInOutQuad(elapsed: number, initialValue: number, amountOfCh...
function getFocusableElements (line 10) | function getFocusableElements(parentEls: Element[] | HTMLElement[]) {
function bringInView (line 26) | function bringInView(element: Element) {
function hasScrollableParent (line 44) | function hasScrollableParent(e: Element) {
function isElementInView (line 54) | function isElementInView(element: Element) {
function isElementVisible (line 65) | function isElementVisible(el: HTMLElement) {
Condensed preview — 67 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (244K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 71,
"preview": "# These are supported funding model platforms\n\ngithub: [kamranahmedse]\n"
},
{
"path": ".gitignore",
"chars": 273,
"preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndis"
},
{
"path": ".prettierignore",
"chars": 44,
"preview": ".history\n.vscode\ncoverage\ndist\nnode_modules\n"
},
{
"path": ".prettierrc",
"chars": 321,
"preview": "{\n \"printWidth\": 120,\n \"tabWidth\": 2,\n \"singleQuote\": false,\n \"trailingComma\": \"es5\",\n \"arrowParens\": \"avoid\",\n \"b"
},
{
"path": "docs/.gitignore",
"chars": 229,
"preview": "# build output\ndist/\n\n# generated types\n.astro/\n\n# dependencies\nnode_modules/\n\n# logs\nnpm-debug.log*\nyarn-debug.log*\nyar"
},
{
"path": "docs/astro.config.mjs",
"chars": 549,
"preview": "import { defineConfig } from \"astro/config\";\nimport tailwind from \"@astrojs/tailwind\";\nimport react from \"@astrojs/react"
},
{
"path": "docs/package.json",
"chars": 676,
"preview": "{\n \"name\": \"driver-docs\",\n \"type\": \"module\",\n \"version\": \"0.0.1\",\n \"scripts\": {\n \"dev\": \"astro dev\",\n \"start\":"
},
{
"path": "docs/src/components/Analytics/Analytics.astro",
"chars": 331,
"preview": "---\n---\n\n<script src='./analytics.ts'></script>\n<script async src=\"https://www.googletagmanager.com/gtag/js?id=G-LGE7XP5"
},
{
"path": "docs/src/components/Analytics/analytics.ts",
"chars": 747,
"preview": "declare global {\n interface Window {\n gtag: any;\n fireEvent: (props: {\n action: string;\n category: stri"
},
{
"path": "docs/src/components/CodeSample.tsx",
"chars": 3868,
"preview": "import type { Config, DriveStep, PopoverDOM } from \"driver.js\";\nimport { driver } from \"driver.js\";\nimport \"driver.js/di"
},
{
"path": "docs/src/components/Container.astro",
"chars": 68,
"preview": "<div class=\"max-w-[1050px] mx-auto px-7 sm:px-10\">\n <slot />\n</div>"
},
{
"path": "docs/src/components/DocsHeader.tsx",
"chars": 1796,
"preview": "import { useState } from \"react\";\nimport type { CollectionEntry } from \"astro:content\";\n\ntype DocsHeaderProps = {\n grou"
},
{
"path": "docs/src/components/ExampleButton.tsx",
"chars": 486,
"preview": "type ExampleButtonProps = {\n id: string;\n onClick: () => void;\n text: string;\n};\n\nexport function ExampleButton(props"
},
{
"path": "docs/src/components/Examples.astro",
"chars": 16996,
"preview": "---\nimport { ExampleButton } from \"./ExampleButton\";\n---\n<h2 class=\"text-4xl md:text-5xl lg:text-6xl font-bold mb-3 md:m"
},
{
"path": "docs/src/components/FeatureMarquee.tsx",
"chars": 767,
"preview": "import React from \"react\";\nimport Marquee from \"react-fast-marquee\";\n\nconst featureList = [\n \"Supports all Major Browse"
},
{
"path": "docs/src/components/Features.astro",
"chars": 4958,
"preview": "---\nimport { Earth, Smartphone, Settings, Feather, Code2, Layers, Keyboard } from \"lucide-react\";\nimport Container from "
},
{
"path": "docs/src/components/FormHelp.tsx",
"chars": 1673,
"preview": "import { useEffect } from \"react\";\nimport { driver } from \"driver.js\";\nimport \"driver.js/dist/driver.css\";\n\nexport funct"
},
{
"path": "docs/src/components/HeroSection.astro",
"chars": 2319,
"preview": "---\nimport Container from \"./Container.astro\";\n---\n\n<div class=\"bg-white border-b border-gray-100 select-none\">\n <Conta"
},
{
"path": "docs/src/components/OpenSourceLove.astro",
"chars": 1643,
"preview": "---\nimport Container from \"./Container.astro\";\nimport { getFormattedStars } from \"../lib/github\";\n\nconst starCount = get"
},
{
"path": "docs/src/components/Sidebar.astro",
"chars": 1353,
"preview": "---\nimport { getCollection, getEntry } from \"astro:content\";\nimport { getFormattedStars } from \"../lib/github\";\nimport {"
},
{
"path": "docs/src/components/UsecaseItem.astro",
"chars": 458,
"preview": "---\nexport interface Props {\n title: string;\n description: string;\n}\n\nconst { title, description } = Astro.props;\n---\n"
},
{
"path": "docs/src/components/UsecaseList.astro",
"chars": 923,
"preview": "---\nimport UsecaseItem from \"./UsecaseItem.astro\";\n---\n<p class=\"text-base md:text-xl lg:text-2xl text-black\">Due to its"
},
{
"path": "docs/src/content/config.ts",
"chars": 282,
"preview": "import { z, defineCollection } from \"astro:content\";\n\nconst guidesCollection = defineCollection({\n type: \"content\",\n s"
},
{
"path": "docs/src/content/guides/animated-tour.mdx",
"chars": 3586,
"preview": "---\ntitle: \"Animated Tour\"\ngroupTitle: \"Examples\"\nsort: 2\n---\n\nimport { CodeSample } from \"../../components/CodeSample.t"
},
{
"path": "docs/src/content/guides/api.mdx",
"chars": 2317,
"preview": "---\ntitle: \"API Reference\"\ngroupTitle: \"Introduction\"\nsort: 4\n---\n\nHere is the list of methods provided by `driver` when"
},
{
"path": "docs/src/content/guides/async-tour.mdx",
"chars": 3381,
"preview": "---\ntitle: \"Async Tour\"\ngroupTitle: \"Examples\"\nsort: 3\n---\n\nimport { CodeSample } from \"../../components/CodeSample.tsx\""
},
{
"path": "docs/src/content/guides/basic-usage.mdx",
"chars": 2945,
"preview": "---\ntitle: \"Basic Usage\"\ngroupTitle: \"Introduction\"\nsort: 2\n---\n\nimport { CodeSample } from \"../../components/CodeSample"
},
{
"path": "docs/src/content/guides/buttons.mdx",
"chars": 7572,
"preview": "---\ntitle: \"Popover Buttons\"\ngroupTitle: \"Examples\"\nsort: 9\n---\n\nimport { CodeSample } from \"../../components/CodeSample"
},
{
"path": "docs/src/content/guides/configuration.mdx",
"chars": 11673,
"preview": "---\ntitle: \"Configuration\"\ngroupTitle: \"Introduction\"\nsort: 3\n---\n\nimport { CodeSample } from \"../../components/CodeSamp"
},
{
"path": "docs/src/content/guides/confirm-on-exit.mdx",
"chars": 2583,
"preview": "---\ntitle: \"Confirm on Exit\"\ngroupTitle: \"Examples\"\nsort: 3\n---\n\nimport { CodeSample } from \"../../components/CodeSample"
},
{
"path": "docs/src/content/guides/installation.mdx",
"chars": 1285,
"preview": "---\ntitle: \"Installation\"\ngroupTitle: \"Introduction\"\nsort: 1\n---\n\nRun one of the following commands to install the packa"
},
{
"path": "docs/src/content/guides/migrating-from-0x.mdx",
"chars": 5943,
"preview": "---\ntitle: \"Migrate to 1.x\"\ngroupTitle: \"Introduction\"\nsort: 6\n---\n\nDrivers 1.x is a major release that introduces a new"
},
{
"path": "docs/src/content/guides/popover-position.mdx",
"chars": 6205,
"preview": "---\ntitle: \"Popover Position\"\ngroupTitle: \"Examples\"\nsort: 7\n---\n\nimport { CodeSample } from \"../../components/CodeSampl"
},
{
"path": "docs/src/content/guides/prevent-destroy.mdx",
"chars": 2295,
"preview": "---\ntitle: \"Prevent Tour Exit\"\ngroupTitle: \"Examples\"\nsort: 3\n---\n\nimport { CodeSample } from \"../../components/CodeSamp"
},
{
"path": "docs/src/content/guides/simple-highlight.mdx",
"chars": 3981,
"preview": "---\ntitle: \"Simple Highlight\"\ngroupTitle: \"Examples\"\nsort: 11\n---\n\nimport { FormHelp } from \"../../components/FormHelp.t"
},
{
"path": "docs/src/content/guides/static-tour.mdx",
"chars": 3681,
"preview": "---\ntitle: \"Static Tour\"\ngroupTitle: \"Examples\"\nsort: 2\n---\n\nimport { CodeSample } from \"../../components/CodeSample.tsx"
},
{
"path": "docs/src/content/guides/styling-overlay.mdx",
"chars": 1993,
"preview": "---\ntitle: \"Styling Overlay\"\ngroupTitle: \"Examples\"\nsort: 5\n---\n\nimport { CodeSample } from \"../../components/CodeSample"
},
{
"path": "docs/src/content/guides/styling-popover.mdx",
"chars": 5799,
"preview": "---\ntitle: \"Styling Popover\"\ngroupTitle: \"Examples\"\nsort: 2\n---\n\nimport { CodeSample } from \"../../components/CodeSample"
},
{
"path": "docs/src/content/guides/theming.mdx",
"chars": 2629,
"preview": "---\ntitle: \"Theming\"\ngroupTitle: \"Introduction\"\nsort: 5\n---\n\nYou can customize the look and feel of the driver by adding"
},
{
"path": "docs/src/content/guides/tour-progress.mdx",
"chars": 4390,
"preview": "---\ntitle: \"Tour Progress\"\ngroupTitle: \"Examples\"\nsort: 2\n---\n\nimport { CodeSample } from \"../../components/CodeSample.t"
},
{
"path": "docs/src/env.d.ts",
"chars": 125,
"preview": "/// <reference path=\"../.astro/types.d.ts\" />\n/// <reference types=\"astro/client\" />\n\ninterface Window {\n driverObj: an"
},
{
"path": "docs/src/layouts/BaseLayout.astro",
"chars": 3470,
"preview": "---\nimport Analytics from \"../components/Analytics/Analytics.astro\";\nexport interface Props {\n permalink?: string;\n ti"
},
{
"path": "docs/src/layouts/DocsLayout.astro",
"chars": 1154,
"preview": "---\nimport BaseLayout from \"./BaseLayout.astro\";\nimport { DocsHeader } from \"../components/DocsHeader\";\nimport Container"
},
{
"path": "docs/src/lib/github.ts",
"chars": 807,
"preview": "const formatter = Intl.NumberFormat(\"en-US\", {\n notation: \"compact\",\n});\n\nconst defaultStarCount = 17000;\nlet starCount"
},
{
"path": "docs/src/lib/guide.ts",
"chars": 577,
"preview": "import { CollectionEntry, getCollection } from \"astro:content\";\n\nexport async function getAllGuides(): Promise<Record<st"
},
{
"path": "docs/src/pages/docs/[guideId].astro",
"chars": 621,
"preview": "---\nimport { CollectionEntry, getCollection } from \"astro:content\";\nimport DocsLayout from \"../../layouts/DocsLayout.ast"
},
{
"path": "docs/src/pages/index.astro",
"chars": 1724,
"preview": "---\nimport BaseLayout from \"../layouts/BaseLayout.astro\";\nimport { FeatureMarquee } from \"../components/FeatureMarquee\";"
},
{
"path": "docs/tailwind.config.cjs",
"chars": 396,
"preview": "/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n content: [\"./src/**/*.{astro,html,js,jsx,md,mdx,svelte,"
},
{
"path": "docs/tsconfig.json",
"chars": 154,
"preview": "{\n \"extends\": \"astro/tsconfigs/strict\",\n \"compilerOptions\": {\n \"jsx\": \"react-jsx\",\n \"jsxImportSource\": \"react\",\n"
},
{
"path": "dts-bundle-generator.config.ts",
"chars": 150,
"preview": "module.exports = {\n entries: [\n {\n filePath: \"./src/driver.ts\",\n outFile: `./dist/driver.js.d.ts`,\n n"
},
{
"path": "index.html",
"chars": 39393,
"preview": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-w"
},
{
"path": "license",
"chars": 1067,
"preview": "The MIT License\n\nCopyright (c) Kamran Ahmed\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
},
{
"path": "package.json",
"chars": 1644,
"preview": "{\n \"name\": \"driver.js\",\n \"license\": \"MIT\",\n \"private\": false,\n \"version\": \"1.4.0\",\n \"main\": \"./dist/driver.js.cjs\","
},
{
"path": "readme.md",
"chars": 2890,
"preview": "<h1 align=\"center\"><img height=\"150\" src=\"https://driverjs.com/driver.svg\" /><br> Driver.js</h1>\n\n<p align=\"center\">\n <"
},
{
"path": "src/config.ts",
"chars": 2302,
"preview": "import { Driver, DriveStep } from \"./driver\";\nimport { AllowedButtons, PopoverDOM } from \"./popover\";\nimport { State } f"
},
{
"path": "src/driver.css",
"chars": 4976,
"preview": ".driver-active .driver-overlay {\n pointer-events: none;\n}\n\n.driver-active * {\n pointer-events: none;\n}\n\n.driver-active"
},
{
"path": "src/driver.ts",
"chars": 11300,
"preview": "import { AllowedButtons, destroyPopover, Popover } from \"./popover\";\nimport { destroyOverlay } from \"./overlay\";\nimport "
},
{
"path": "src/emitter.ts",
"chars": 488,
"preview": "type allowedEvents =\n | \"overlayClick\"\n | \"escapePress\"\n | \"nextClick\"\n | \"prevClick\"\n | \"closeClick\"\n | \"arrowRig"
},
{
"path": "src/events.ts",
"chars": 3884,
"preview": "import { refreshActiveHighlight } from \"./highlight\";\nimport { emit } from \"./emitter\";\nimport { getState, setState } fr"
},
{
"path": "src/highlight.ts",
"chars": 5863,
"preview": "import { DriveStep } from \"./driver\";\nimport { refreshOverlay, trackActiveElement, transitionStage } from \"./overlay\";\ni"
},
{
"path": "src/overlay.ts",
"chars": 5830,
"preview": "import { easeInOutQuad } from \"./utils\";\nimport { onDriverClick } from \"./events\";\nimport { emit } from \"./emitter\";\nimp"
},
{
"path": "src/popover.ts",
"chars": 22809,
"preview": "import { Config, DriverHook, getConfig, getCurrentDriver } from \"./config\";\nimport { Driver, DriveStep } from \"./driver\""
},
{
"path": "src/state.ts",
"chars": 1149,
"preview": "import { StageDefinition } from \"./overlay\";\nimport { PopoverDOM } from \"./popover\";\nimport { DriveStep } from \"./driver"
},
{
"path": "src/utils.ts",
"chars": 2383,
"preview": "import { getConfig } from \"./config\";\n\nexport function easeInOutQuad(elapsed: number, initialValue: number, amountOfChan"
},
{
"path": "tests/sum.test.ts",
"chars": 158,
"preview": "import { describe, expect, it } from \"vitest\";\n\ndescribe(\"add\", () => {\n it(\"should sum of 2 and 3 equals to 5\", () => "
},
{
"path": "tsconfig.json",
"chars": 601,
"preview": "{\n \"compilerOptions\": {\n \"rootDir\": \"./src\",\n \"target\": \"ES2019\",\n \"useDefineForClassFields\": true,\n \"modul"
},
{
"path": "vite.config.ts",
"chars": 767,
"preview": "/// <reference types=\"vitest\" />\nimport path from \"path\";\nimport { defineConfig } from \"vite\";\n\nconst packageName = \"dri"
}
]
About this extraction
This page contains the full source code of the kamranahmedse/driver.js GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 67 files (224.4 KB), approximately 57.3k tokens, and a symbol index with 74 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.