Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new overlay and popup configuration options, allow to pass function which returns Element in element step config #508

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
116 changes: 116 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ <h2>Highlight Feature</h2>
<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="backdrop-disabled-btn">Disabled backdrop</button>
<button id="disabled-stick-to-viewport">Disabled stick to viewport</button>
<button id="disabled-scroll">Disabled body scrolling</button>
<button id="disabled-same-element-animation">Disabled same element step animation</button>
<button id="hooks">Hooks</button>
<button id="destroy-btn" style="border-color: red; background: red; color: white">Destroy</button>
</div>
Expand Down Expand Up @@ -409,6 +413,77 @@ <h2>Usage and Demo</h2>
},
];


const sameElementSteps = [
{
element: ".page-header h1",
popover: {
title: "1 popup on the same element",
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 h1",
popover: {
title: "2 popup on the same element",
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 h1",
popover: {
title: "3 popup on the same element",
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,
Expand Down Expand Up @@ -877,6 +952,47 @@ <h2>Usage and Demo</h2>
});
});

document.getElementById("backdrop-disabled-btn").addEventListener("click", () => {
driver({
overlayEnable: false,
}).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("disabled-stick-to-viewport").addEventListener("click", () => {
const driverObj = driver({
popoverStickToViewport: false,
steps: basicTourSteps,
});

driverObj.drive();
});

document.getElementById("disabled-scroll").addEventListener("click", () => {
const driverObj = driver({
allowScroll: false,
steps: basicTourSteps,
});

driverObj.drive();
});

document.getElementById("disabled-same-element-animation").addEventListener("click", () => {
const driverObj = driver({
animateBetweenSameElements: false,
steps: sameElementSteps,
});

driverObj.drive();
});

document.getElementById("activate-check-btn").addEventListener("click", () => {
const driverObj = driver({
showButtons: false,
Expand Down
8 changes: 8 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ export type Config = {
steps?: DriveStep[];

animate?: boolean;
animateBetweenSameElements?: boolean
overlayEnable?: boolean;
overlayColor?: string;
overlayOpacity?: number;
smoothScroll?: boolean;
allowClose?: boolean;
allowScroll?: boolean;
stagePadding?: number;
stageRadius?: number;

Expand All @@ -22,6 +25,7 @@ export type Config = {
// Popover specific configuration
popoverClass?: string;
popoverOffset?: number;
popoverStickToViewport?: boolean;
showButtons?: AllowedButtons[];
disableButtons?: AllowedButtons[];
showProgress?: boolean;
Expand Down Expand Up @@ -53,14 +57,18 @@ let currentConfig: Config = {};
export function configure(config: Config = {}) {
currentConfig = {
animate: true,
animateBetweenSameElements: true,
allowClose: true,
allowScroll: true,
overlayOpacity: 0.7,
overlayEnable: true,
smoothScroll: false,
disableActiveInteraction: false,
showProgress: false,
stagePadding: 10,
stageRadius: 5,
popoverOffset: 10,
popoverStickToViewport: true,
showButtons: ["next", "previous", "close"],
disableButtons: [],
overlayColor: "#000",
Expand Down
12 changes: 8 additions & 4 deletions src/driver.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
pointer-events: none;
}

.driver-active * {
.driver-active.driver-no-scroll {
overflow: hidden;
}

.driver-active:not(.driver-no-overlay) * {
pointer-events: none;
}

.driver-active .driver-active-element,
.driver-active .driver-active-element *,
.driver-popover,
.driver-popover * {
.driver-active .driver-popover,
.driver-active .driver-popover * {
pointer-events: auto;
}

Expand All @@ -27,7 +31,7 @@
animation: animate-fade-in 200ms ease-in-out;
}

.driver-fade .driver-popover {
.driver-fade .driver-popover:not(.driver-no-animation) {
animation: animate-fade-in 200ms;
}

Expand Down
5 changes: 3 additions & 2 deletions src/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { getState, resetState, setState } from "./state";
import "./driver.css";

export type DriveStep = {
element?: string | Element;
element?: string | Element | (() => Element | null);
onHighlightStarted?: DriverHook;
onHighlighted?: DriverHook;
onDeselected?: DriverHook;
Expand Down Expand Up @@ -131,6 +131,7 @@ export function driver(options: Config = {}) {

listen("overlayClick", handleClose);
listen("escapePress", handleClose);
listen("windowClick", handleClose);
listen("arrowLeftPress", handleArrowLeft);
listen("arrowRightPress", handleArrowRight);
}
Expand Down Expand Up @@ -234,7 +235,7 @@ export function driver(options: Config = {}) {
const onDeselected = activeStep?.onDeselected || getConfig("onDeselected");
const onDestroyed = getConfig("onDestroyed");

document.body.classList.remove("driver-active", "driver-fade", "driver-simple");
document.body.classList.remove("driver-active", "driver-fade", "driver-simple", "driver-no-overlay", "driver-no-scroll");

destroyEvents();
destroyPopover();
Expand Down
3 changes: 2 additions & 1 deletion src/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ type allowedEvents =
| "prevClick"
| "closeClick"
| "arrowRightPress"
| "arrowLeftPress";
| "arrowLeftPress"
| "windowClick";

let registeredListeners: Partial<{ [key in allowedEvents]: () => void }> = {};

Expand Down
17 changes: 17 additions & 0 deletions src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ import { getState, setState } from "./state";
import { getConfig } from "./config";
import { getFocusableElements } from "./utils";

function onWindowClick(e: MouseEvent) {
const isOverlayEnabled = getConfig('overlayEnable')
if (isOverlayEnabled) return;

const isPopoverContent = (e.target as HTMLElement)?.closest('#driver-popover-content')
const isActive = getState('activeStep')
const isTransitioning = getState("__transitionCallback")

if (isTransitioning || !isActive || isPopoverContent) return;

emit('windowClick')
}

export function requireRefresh() {
const resizeTimeout = getState("__resizeTimeout");
if (resizeTimeout) {
Expand Down Expand Up @@ -118,10 +131,14 @@ export function initEvents() {
window.addEventListener("keydown", trapFocus, false);
window.addEventListener("resize", requireRefresh);
window.addEventListener("scroll", requireRefresh);
if (!getConfig('overlayEnable')) {
window.addEventListener('click', onWindowClick)
}
}

export function destroyEvents() {
window.removeEventListener("keyup", onKeyup);
window.removeEventListener("resize", requireRefresh);
window.removeEventListener("scroll", requireRefresh);
window.removeEventListener("click", onWindowClick);
}
38 changes: 32 additions & 6 deletions src/highlight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,30 @@ function mountDummyElement(): Element {
return element;
}

export function highlight(step: DriveStep) {
function getStepElement(step: DriveStep): Element | null {
const { element } = step;
let elemObj = typeof element === "string" ? document.querySelector(element) : element;

if (typeof element === 'function') {
return element();
}

if (typeof element === 'string') {
return document.querySelector(element);
}

return element ?? null;
}

export function highlight(step: DriveStep) {
let elemObj = getStepElement(step);

if (!getConfig("overlayEnable")) {
document.body.classList.add("driver-no-overlay");
}

if (!getConfig("allowScroll")) {
document.body.classList.add("driver-no-scroll");
}

// If the element is not found, we mount a 1px div
// at the center of the screen to highlight and show
Expand Down Expand Up @@ -62,11 +83,12 @@ function transferHighlight(toElement: Element, toStep: DriveStep) {

const fromStep = getState("__activeStep");
const fromElement = getState("__activeElement") || toElement;
const isSameElement = fromElement === toElement;

// If it's the first time we're highlighting an element, we show
// the popover immediately. Otherwise, we wait for the animation
// to finish before showing the popover.
const isFirstHighlight = !fromElement || fromElement === toElement;
const isFirstHighlight = !fromElement || isSameElement;
const isToDummyElement = toElement.id === "driver-dummy-element";
const isFromDummyElement = fromElement.id === "driver-dummy-element";

Expand Down Expand Up @@ -102,6 +124,8 @@ function transferHighlight(toElement: Element, toStep: DriveStep) {
setState("activeStep", toStep);
setState("activeElement", toElement);

const isSameElementAnimationEnabled = !isSameElement || !!getConfig("animateBetweenSameElements");

const animate = () => {
const transitionCallback = getState("__transitionCallback");

Expand All @@ -112,12 +136,15 @@ function transferHighlight(toElement: Element, toStep: DriveStep) {
return;
}

setState("__activeElement", toElement);

const elapsed = Date.now() - start;
const timeRemaining = duration - elapsed;
const isHalfwayThrough = timeRemaining <= duration / 2;


if (toStep.popover && isHalfwayThrough && !isPopoverRendered && hasDelayedPopover) {
renderPopover(toElement, toStep);
renderPopover(toElement, toStep, isSameElementAnimationEnabled);
isPopoverRendered = true;
}

Expand All @@ -137,7 +164,6 @@ function transferHighlight(toElement: Element, toStep: DriveStep) {
setState("__previousStep", fromStep);
setState("__previousElement", fromElement);
setState("__activeStep", toStep);
setState("__activeElement", toElement);
}

window.requestAnimationFrame(animate);
Expand All @@ -149,7 +175,7 @@ function transferHighlight(toElement: Element, toStep: DriveStep) {

bringInView(toElement);
if (!hasDelayedPopover && toStep.popover) {
renderPopover(toElement, toStep);
renderPopover(toElement, toStep, isSameElementAnimationEnabled);
}

fromElement.classList.remove("driver-active-element", "driver-no-interaction");
Expand Down
6 changes: 6 additions & 0 deletions src/overlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ export function trackActiveElement(element: Element) {
}

export function refreshOverlay() {
const isOverlayEnabled = getConfig('overlayEnable')
if (!isOverlayEnabled) return;

const activeStagePosition = getState("__activeStagePosition");
const overlaySvg = getState("__overlaySvg");

Expand Down Expand Up @@ -90,6 +93,9 @@ function mountOverlay(stagePosition: StageDefinition) {
}

function renderOverlay(stagePosition: StageDefinition) {
const isOverlayEnabled = getConfig('overlayEnable')
if (!isOverlayEnabled) return;

const overlaySvg = getState("__overlaySvg");

// TODO: cancel rendering if element is not visible
Expand Down
Loading