Add tour functionality
Browse files- index.html +87 -22
- src/config.ts +5 -0
- src/driver.ts +48 -2
- src/popover.ts +27 -18
- src/style.css +5 -0
index.html
CHANGED
@@ -183,6 +183,7 @@
|
|
183 |
<button id="next-prev-button">Next Previous Buttons</button>
|
184 |
<button id="close-button">Close Buttons</button>
|
185 |
<button id="button-texts">Button Texts</button>
|
|
|
186 |
<button id="button-config-events">Button Listeners</button>
|
187 |
</div>
|
188 |
|
@@ -193,6 +194,12 @@
|
|
193 |
<button id="popover-hook">Popover Hook</button>
|
194 |
</div>
|
195 |
|
|
|
|
|
|
|
|
|
|
|
|
|
196 |
<ul>
|
197 |
<li>Written in TypeScript</li>
|
198 |
<li>Lightweight — only 5kb gzipped</li>
|
@@ -324,6 +331,51 @@ npm install driver.js</pre
|
|
324 |
<script type="module">
|
325 |
import { driver } from "./src/driver.ts";
|
326 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
327 |
document.getElementById("no-buttons").addEventListener("click", () => {
|
328 |
const driverObj = driver({});
|
329 |
|
@@ -426,37 +478,50 @@ npm install driver.js</pre
|
|
426 |
|
427 |
document.getElementById("popover-hook").addEventListener("click", () => {
|
428 |
const driverObj = driver({
|
429 |
-
onPopoverRendered:
|
430 |
-
popover.title.innerText =
|
431 |
-
}
|
432 |
});
|
433 |
driverObj.highlight({
|
434 |
-
element:
|
435 |
popover: {
|
436 |
-
title:
|
437 |
-
description:
|
438 |
-
side:
|
439 |
-
align:
|
440 |
-
onPopoverRendered:
|
441 |
-
popover.title.innerText =
|
442 |
-
}
|
443 |
-
}
|
444 |
-
})
|
445 |
});
|
446 |
|
447 |
document.getElementById("custom-classes").addEventListener("click", () => {
|
448 |
const driverObj = driver({
|
449 |
-
popoverClass: "custom-driver-popover"
|
450 |
-
})
|
451 |
|
452 |
driverObj.highlight({
|
453 |
popover: {
|
454 |
popoverClass: "custom-driver-popover",
|
455 |
title: "Custom Classes",
|
456 |
description: "Popover and buttons have custom classes",
|
457 |
-
showButtons: ["close", "next", "previous"]
|
458 |
-
}
|
459 |
-
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
460 |
});
|
461 |
|
462 |
document.getElementById("button-config-events").addEventListener("click", () => {
|
@@ -623,19 +688,19 @@ npm install driver.js</pre
|
|
623 |
const driverObj = driver({
|
624 |
animate: true,
|
625 |
onDeselected: (element, step) => {
|
626 |
-
const elementText = element?.textContent?.slice(0, 10) ||
|
627 |
console.log(`Deselected: ${elementText}\n${JSON.stringify(step)}`);
|
628 |
},
|
629 |
onHighlightStarted: (element, step) => {
|
630 |
-
const elementText = element?.textContent?.slice(0, 10) ||
|
631 |
console.log(`Highlight Started: ${elementText}\n${JSON.stringify(step)}`);
|
632 |
},
|
633 |
onHighlighted: (element, step) => {
|
634 |
-
const elementText = element?.textContent?.slice(0, 10) ||
|
635 |
console.log(`Highlighted: ${elementText}\n${JSON.stringify(step)}`);
|
636 |
},
|
637 |
onDestroyed: (element, step) => {
|
638 |
-
const elementText = element?.textContent?.slice(0, 10) ||
|
639 |
console.log(`Destroyed: ${elementText}\n${JSON.stringify(step)}`);
|
640 |
},
|
641 |
});
|
|
|
183 |
<button id="next-prev-button">Next Previous Buttons</button>
|
184 |
<button id="close-button">Close Buttons</button>
|
185 |
<button id="button-texts">Button Texts</button>
|
186 |
+
<button id="disabled-buttons">Disabled Buttons</button>
|
187 |
<button id="button-config-events">Button Listeners</button>
|
188 |
</div>
|
189 |
|
|
|
194 |
<button id="popover-hook">Popover Hook</button>
|
195 |
</div>
|
196 |
|
197 |
+
<h2>Tour Feature</h2>
|
198 |
+
<p>Examples below show the tour usage of driver.js.</p>
|
199 |
+
<div class="buttons">
|
200 |
+
<button id="basic-tour">Basic Tour</button>
|
201 |
+
</div>
|
202 |
+
|
203 |
<ul>
|
204 |
<li>Written in TypeScript</li>
|
205 |
<li>Lightweight — only 5kb gzipped</li>
|
|
|
331 |
<script type="module">
|
332 |
import { driver } from "./src/driver.ts";
|
333 |
|
334 |
+
document.getElementById("basic-tour").addEventListener("click", () => {
|
335 |
+
const driverObj = driver({
|
336 |
+
steps: [
|
337 |
+
{
|
338 |
+
element: ".page-header",
|
339 |
+
popover: {
|
340 |
+
title: "New and Improved Driver.js",
|
341 |
+
description:
|
342 |
+
"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.",
|
343 |
+
side: "bottom",
|
344 |
+
align: "start",
|
345 |
+
},
|
346 |
+
},
|
347 |
+
{
|
348 |
+
element: ".page-header h1",
|
349 |
+
popover: {
|
350 |
+
title: "No Stacking Issues",
|
351 |
+
description:
|
352 |
+
"Unlike the older version, new version doesn't work with z-indexes so no more positional issues.",
|
353 |
+
side: "left",
|
354 |
+
align: "start",
|
355 |
+
},
|
356 |
+
},
|
357 |
+
{
|
358 |
+
element: ".page-header sup",
|
359 |
+
popover: {
|
360 |
+
title: "Improved Hooks",
|
361 |
+
description:
|
362 |
+
"Unlike the older version, new version doesn't work with z-indexes so no more positional issues.",
|
363 |
+
side: "bottom",
|
364 |
+
align: "start",
|
365 |
+
},
|
366 |
+
},
|
367 |
+
{
|
368 |
+
popover: {
|
369 |
+
title: "No Element",
|
370 |
+
description: "You can now have popovers without elements as well",
|
371 |
+
},
|
372 |
+
},
|
373 |
+
],
|
374 |
+
});
|
375 |
+
|
376 |
+
driverObj.drive();
|
377 |
+
});
|
378 |
+
|
379 |
document.getElementById("no-buttons").addEventListener("click", () => {
|
380 |
const driverObj = driver({});
|
381 |
|
|
|
478 |
|
479 |
document.getElementById("popover-hook").addEventListener("click", () => {
|
480 |
const driverObj = driver({
|
481 |
+
onPopoverRendered: popover => {
|
482 |
+
popover.title.innerText = "Modified Parent";
|
483 |
+
},
|
484 |
});
|
485 |
driverObj.highlight({
|
486 |
+
element: ".page-header",
|
487 |
popover: {
|
488 |
+
title: "Page Title",
|
489 |
+
description: "Body of the popover",
|
490 |
+
side: "bottom",
|
491 |
+
align: "start",
|
492 |
+
onPopoverRendered: popover => {
|
493 |
+
popover.title.innerText = "Modified";
|
494 |
+
},
|
495 |
+
},
|
496 |
+
});
|
497 |
});
|
498 |
|
499 |
document.getElementById("custom-classes").addEventListener("click", () => {
|
500 |
const driverObj = driver({
|
501 |
+
popoverClass: "custom-driver-popover",
|
502 |
+
});
|
503 |
|
504 |
driverObj.highlight({
|
505 |
popover: {
|
506 |
popoverClass: "custom-driver-popover",
|
507 |
title: "Custom Classes",
|
508 |
description: "Popover and buttons have custom classes",
|
509 |
+
showButtons: ["close", "next", "previous"],
|
510 |
+
},
|
511 |
+
});
|
512 |
+
});
|
513 |
+
|
514 |
+
document.getElementById("disabled-buttons").addEventListener("click", () => {
|
515 |
+
const driverObj = driver();
|
516 |
+
driverObj.highlight({
|
517 |
+
element: "#disabled-buttons",
|
518 |
+
popover: {
|
519 |
+
title: "Disable Buttons",
|
520 |
+
description: "You can selectively disable buttons as well",
|
521 |
+
showButtons: ["next", "previous", "close"],
|
522 |
+
disableButtons: ["next", "previous"],
|
523 |
+
},
|
524 |
+
});
|
525 |
});
|
526 |
|
527 |
document.getElementById("button-config-events").addEventListener("click", () => {
|
|
|
688 |
const driverObj = driver({
|
689 |
animate: true,
|
690 |
onDeselected: (element, step) => {
|
691 |
+
const elementText = element?.textContent?.slice(0, 10) || " - N/A -";
|
692 |
console.log(`Deselected: ${elementText}\n${JSON.stringify(step)}`);
|
693 |
},
|
694 |
onHighlightStarted: (element, step) => {
|
695 |
+
const elementText = element?.textContent?.slice(0, 10) || " - N/A -";
|
696 |
console.log(`Highlight Started: ${elementText}\n${JSON.stringify(step)}`);
|
697 |
},
|
698 |
onHighlighted: (element, step) => {
|
699 |
+
const elementText = element?.textContent?.slice(0, 10) || " - N/A -";
|
700 |
console.log(`Highlighted: ${elementText}\n${JSON.stringify(step)}`);
|
701 |
},
|
702 |
onDestroyed: (element, step) => {
|
703 |
+
const elementText = element?.textContent?.slice(0, 10) || " - N/A -";
|
704 |
console.log(`Destroyed: ${elementText}\n${JSON.stringify(step)}`);
|
705 |
},
|
706 |
});
|
src/config.ts
CHANGED
@@ -2,6 +2,8 @@ import { DriveStep } from "./driver";
|
|
2 |
import {AllowedButtons, PopoverDOM} from "./popover";
|
3 |
|
4 |
export type Config = {
|
|
|
|
|
5 |
animate?: boolean;
|
6 |
backdropColor?: string;
|
7 |
smoothScroll?: boolean;
|
@@ -15,11 +17,13 @@ export type Config = {
|
|
15 |
popoverClass?: string;
|
16 |
popoverOffset?: number;
|
17 |
showButtons?: AllowedButtons[];
|
|
|
18 |
|
19 |
// Button texts
|
20 |
nextBtnText?: string;
|
21 |
prevBtnText?: string;
|
22 |
closeBtnText?: string;
|
|
|
23 |
|
24 |
// Called after the popover is rendered
|
25 |
onPopoverRendered?: (popover: PopoverDOM) => void;
|
@@ -49,6 +53,7 @@ export function configure(config: Config = {}) {
|
|
49 |
stageRadius: 5,
|
50 |
popoverOffset: 10,
|
51 |
showButtons: ["next", "previous", "close"],
|
|
|
52 |
backdropColor: "#000",
|
53 |
...config,
|
54 |
};
|
|
|
2 |
import {AllowedButtons, PopoverDOM} from "./popover";
|
3 |
|
4 |
export type Config = {
|
5 |
+
steps?: DriveStep[];
|
6 |
+
|
7 |
animate?: boolean;
|
8 |
backdropColor?: string;
|
9 |
smoothScroll?: boolean;
|
|
|
17 |
popoverClass?: string;
|
18 |
popoverOffset?: number;
|
19 |
showButtons?: AllowedButtons[];
|
20 |
+
disableButtons?: AllowedButtons[];
|
21 |
|
22 |
// Button texts
|
23 |
nextBtnText?: string;
|
24 |
prevBtnText?: string;
|
25 |
closeBtnText?: string;
|
26 |
+
doneBtnText?: string;
|
27 |
|
28 |
// Called after the popover is rendered
|
29 |
onPopoverRendered?: (popover: PopoverDOM) => void;
|
|
|
53 |
stageRadius: 5,
|
54 |
popoverOffset: 10,
|
55 |
showButtons: ["next", "previous", "close"],
|
56 |
+
disableButtons: [],
|
57 |
backdropColor: "#000",
|
58 |
...config,
|
59 |
};
|
src/driver.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
import { destroyPopover, Popover } from "./popover";
|
2 |
import { destroyStage } from "./stage";
|
3 |
import { destroyEvents, initEvents, requireRefresh } from "./events";
|
4 |
import { Config, configure, getConfig } from "./config";
|
@@ -38,6 +38,49 @@ export function driver(options: Config = {}) {
|
|
38 |
listen("escapePress", handleClose);
|
39 |
}
|
40 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
41 |
function destroy() {
|
42 |
const activeElement = getState("activeElement");
|
43 |
const activeStep = getState("activeStep");
|
@@ -72,7 +115,10 @@ export function driver(options: Config = {}) {
|
|
72 |
refresh: () => {
|
73 |
requireRefresh();
|
74 |
},
|
75 |
-
drive: (
|
|
|
|
|
|
|
76 |
highlight: (step: DriveStep) => {
|
77 |
init();
|
78 |
highlight({
|
|
|
1 |
+
import { AllowedButtons, destroyPopover, Popover } from "./popover";
|
2 |
import { destroyStage } from "./stage";
|
3 |
import { destroyEvents, initEvents, requireRefresh } from "./events";
|
4 |
import { Config, configure, getConfig } from "./config";
|
|
|
38 |
listen("escapePress", handleClose);
|
39 |
}
|
40 |
|
41 |
+
function drive(stepIndex: number = 0) {
|
42 |
+
const steps = getConfig("steps");
|
43 |
+
if (!steps) {
|
44 |
+
console.error("No steps to drive through");
|
45 |
+
destroy();
|
46 |
+
return;
|
47 |
+
}
|
48 |
+
|
49 |
+
if (!steps[stepIndex]) {
|
50 |
+
console.warn(`Step not found at index: ${stepIndex}`);
|
51 |
+
destroy();
|
52 |
+
}
|
53 |
+
|
54 |
+
const currentStep = steps[stepIndex];
|
55 |
+
const hasNextStep = steps[stepIndex + 1];
|
56 |
+
const hasPreviousStep = steps[stepIndex - 1];
|
57 |
+
|
58 |
+
const doneBtnText = currentStep.popover?.doneBtnText || getConfig("doneBtnText") || "Done";
|
59 |
+
|
60 |
+
highlight({
|
61 |
+
...currentStep,
|
62 |
+
popover: {
|
63 |
+
showButtons: ["next", "previous", "close"],
|
64 |
+
nextBtnText: !hasNextStep ? doneBtnText : undefined,
|
65 |
+
disableButtons: [...(!hasPreviousStep ? ["previous" as AllowedButtons] : [])],
|
66 |
+
onNextClick: () => {
|
67 |
+
if (!hasNextStep) {
|
68 |
+
destroy();
|
69 |
+
} else {
|
70 |
+
drive(stepIndex + 1);
|
71 |
+
}
|
72 |
+
},
|
73 |
+
onPrevClick: () => {
|
74 |
+
drive(stepIndex - 1);
|
75 |
+
},
|
76 |
+
onCloseClick: () => {
|
77 |
+
destroy();
|
78 |
+
},
|
79 |
+
...(currentStep?.popover || {}),
|
80 |
+
},
|
81 |
+
});
|
82 |
+
}
|
83 |
+
|
84 |
function destroy() {
|
85 |
const activeElement = getState("activeElement");
|
86 |
const activeStep = getState("activeStep");
|
|
|
115 |
refresh: () => {
|
116 |
requireRefresh();
|
117 |
},
|
118 |
+
drive: (stepIndex: number = 0) => {
|
119 |
+
init();
|
120 |
+
drive(stepIndex);
|
121 |
+
},
|
122 |
highlight: (step: DriveStep) => {
|
123 |
init();
|
124 |
highlight({
|
src/popover.ts
CHANGED
@@ -11,11 +11,12 @@ export type AllowedButtons = "next" | "previous" | "close";
|
|
11 |
|
12 |
export type Popover = {
|
13 |
title?: string;
|
14 |
-
description
|
15 |
side?: Side;
|
16 |
align?: Alignment;
|
17 |
|
18 |
showButtons?: AllowedButtons[];
|
|
|
19 |
|
20 |
popoverClass?: string;
|
21 |
|
@@ -57,15 +58,19 @@ export function hidePopover() {
|
|
57 |
|
58 |
export function renderPopover(element: Element, step: DriveStep) {
|
59 |
let popover = getState("popover");
|
60 |
-
if (
|
61 |
-
popover
|
62 |
-
document.body.appendChild(popover.wrapper);
|
63 |
}
|
64 |
|
|
|
|
|
|
|
65 |
const {
|
66 |
title,
|
67 |
description,
|
68 |
-
showButtons
|
|
|
|
|
69 |
// doneBtnText = 'Done',
|
70 |
closeBtnText = getConfig("closeBtnText") || "Close",
|
71 |
nextBtnText = getConfig("nextBtnText") || "Next →",
|
@@ -90,27 +95,31 @@ export function renderPopover(element: Element, step: DriveStep) {
|
|
90 |
popover.description.style.display = "none";
|
91 |
}
|
92 |
|
93 |
-
const showButtonsConfig: AllowedButtons[] =
|
94 |
-
popoverShowButtons !== undefined ? popoverShowButtons : getConfig("showButtons")!;
|
95 |
|
96 |
if (showButtonsConfig?.length! > 0) {
|
97 |
popover.footer.style.display = "flex";
|
98 |
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
if (!showButtonsConfig.includes("previous")) {
|
104 |
-
popover.previousButton.style.display = "none";
|
105 |
-
}
|
106 |
-
|
107 |
-
if (!showButtonsConfig.includes("close")) {
|
108 |
-
popover.closeButton.style.display = "none";
|
109 |
-
}
|
110 |
} else {
|
111 |
popover.footer.style.display = "none";
|
112 |
}
|
113 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
114 |
// Reset the popover position
|
115 |
const popoverWrapper = popover.wrapper;
|
116 |
popoverWrapper.style.display = "block";
|
|
|
11 |
|
12 |
export type Popover = {
|
13 |
title?: string;
|
14 |
+
description?: string;
|
15 |
side?: Side;
|
16 |
align?: Alignment;
|
17 |
|
18 |
showButtons?: AllowedButtons[];
|
19 |
+
disableButtons?: AllowedButtons[];
|
20 |
|
21 |
popoverClass?: string;
|
22 |
|
|
|
58 |
|
59 |
export function renderPopover(element: Element, step: DriveStep) {
|
60 |
let popover = getState("popover");
|
61 |
+
if (popover) {
|
62 |
+
document.body.removeChild(popover.wrapper);
|
|
|
63 |
}
|
64 |
|
65 |
+
popover = createPopover();
|
66 |
+
document.body.appendChild(popover.wrapper);
|
67 |
+
|
68 |
const {
|
69 |
title,
|
70 |
description,
|
71 |
+
showButtons,
|
72 |
+
disableButtons,
|
73 |
+
|
74 |
// doneBtnText = 'Done',
|
75 |
closeBtnText = getConfig("closeBtnText") || "Close",
|
76 |
nextBtnText = getConfig("nextBtnText") || "Next →",
|
|
|
95 |
popover.description.style.display = "none";
|
96 |
}
|
97 |
|
98 |
+
const showButtonsConfig: AllowedButtons[] = showButtons || getConfig("showButtons")!;
|
|
|
99 |
|
100 |
if (showButtonsConfig?.length! > 0) {
|
101 |
popover.footer.style.display = "flex";
|
102 |
|
103 |
+
popover.nextButton.style.display = showButtonsConfig.includes("next") ? 'block' : 'none';
|
104 |
+
popover.previousButton.style.display = showButtonsConfig.includes("previous") ? 'block' : 'none';
|
105 |
+
popover.closeButton.style.display = showButtonsConfig.includes("close") ? 'block' : 'none';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
106 |
} else {
|
107 |
popover.footer.style.display = "none";
|
108 |
}
|
109 |
|
110 |
+
const disabledButtonsConfig: AllowedButtons[] = disableButtons || getConfig('disableButtons')! || [];
|
111 |
+
if (disabledButtonsConfig?.includes('next')) {
|
112 |
+
popover.nextButton.classList.add("driver-popover-btn-disabled")
|
113 |
+
}
|
114 |
+
|
115 |
+
if (disabledButtonsConfig?.includes('previous')) {
|
116 |
+
popover.previousButton.classList.add("driver-popover-btn-disabled")
|
117 |
+
}
|
118 |
+
|
119 |
+
if (disabledButtonsConfig?.includes('close')) {
|
120 |
+
popover.closeButton.classList.add("driver-popover-btn-disabled")
|
121 |
+
}
|
122 |
+
|
123 |
// Reset the popover position
|
124 |
const popoverWrapper = popover.wrapper;
|
125 |
popoverWrapper.style.display = "block";
|
src/style.css
CHANGED
@@ -97,6 +97,11 @@
|
|
97 |
border-radius: 3px;
|
98 |
}
|
99 |
|
|
|
|
|
|
|
|
|
|
|
100 |
/* Disable the scrolling of parent element if it has an active element*/
|
101 |
:not(body):has(> .driver-active-element) {
|
102 |
overflow: hidden !important;
|
|
|
97 |
border-radius: 3px;
|
98 |
}
|
99 |
|
100 |
+
.driver-popover-footer .driver-popover-btn-disabled {
|
101 |
+
opacity: 0.5;
|
102 |
+
pointer-events: none;
|
103 |
+
}
|
104 |
+
|
105 |
/* Disable the scrolling of parent element if it has an active element*/
|
106 |
:not(body):has(> .driver-active-element) {
|
107 |
overflow: hidden !important;
|