mirror of
https://github.com/kamranahmedse/driver.js.git
synced 2026-10-31 03:52:13 +08:00
Add support onDoneClick
This commit is contained in:
@@ -180,10 +180,12 @@ You can also change the text of buttons using `nextBtnText`, `prevBtnText` and `
|
||||
|
||||
## 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.
|
||||
You can use the `onNextClick`, `onPrevClick`, `onCloseClick` and `onDoneClick` callbacks to implement custom functionality when the user clicks on the 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.
|
||||
|
||||
> `onDoneClick` runs when the done button (the next button on the last step) is clicked. When provided, it runs instead of `onNextClick` on the last step, so you can detect when the user finishes the tour. Driver.js won't tear down the tour for you here — call `driverObj.destroy()` yourself.
|
||||
|
||||
<CodeSample
|
||||
buttonText={"Show Example"}
|
||||
config={{}}
|
||||
@@ -225,6 +227,11 @@ You can use the `onNextClick`, `onPreviousClick` and `onCloseClick` callbacks to
|
||||
// Implement your own functionality here
|
||||
driverObj.destroy();
|
||||
},
|
||||
onDoneClick:() => {
|
||||
console.log('Done Button Clicked');
|
||||
// Runs on the last step instead of onNextClick
|
||||
driverObj.destroy();
|
||||
},
|
||||
steps: [
|
||||
// ...
|
||||
]
|
||||
|
||||
@@ -112,6 +112,10 @@ type Config = {
|
||||
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;
|
||||
// Runs when the done button (the next button on the
|
||||
// last step of a tour) is clicked. When provided, it
|
||||
// runs instead of `onNextClick` on the last step.
|
||||
onDoneClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void;
|
||||
};
|
||||
```
|
||||
|
||||
@@ -120,6 +124,8 @@ type Config = {
|
||||
> 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.
|
||||
>
|
||||
> `onDoneClick` lets you detect when the user finishes the tour by clicking the done button on the last step. When provided, it runs instead of `onNextClick` on the last step, and Driver.js won't destroy the tour automatically — you control teardown by calling `driverObj.destroy()` yourself. Like the other click hooks, it can be configured at the driver or step level.
|
||||
|
||||
## Popover Configuration
|
||||
|
||||
@@ -187,6 +193,9 @@ type Popover = {
|
||||
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
|
||||
// Runs instead of `onNextClick` when the done button
|
||||
// (the next button on the last step) is clicked.
|
||||
onDoneClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State, driver: Driver }) => void
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ export type Config = {
|
||||
onNextClick?: DriverHook;
|
||||
onPrevClick?: DriverHook;
|
||||
onCloseClick?: DriverHook;
|
||||
onDoneClick?: DriverHook;
|
||||
};
|
||||
|
||||
let currentConfig: Config = {};
|
||||
|
||||
+24
-1
@@ -82,8 +82,20 @@ export function driver(options: Config = {}): Driver {
|
||||
if (overlayClickBehavior === "nextStep") {
|
||||
const activeStep = getState("activeStep");
|
||||
const activeElement = getState("activeElement");
|
||||
const onNextClick = activeStep?.popover?.onNextClick || getConfig("onNextClick");
|
||||
|
||||
const steps = getConfig("steps") || [];
|
||||
const isLastStep = getState("activeIndex") === steps.length - 1;
|
||||
const onDoneClick = activeStep?.popover?.onDoneClick || getConfig("onDoneClick");
|
||||
if (isLastStep && onDoneClick) {
|
||||
onDoneClick(activeElement, activeStep!, {
|
||||
config: getConfig(),
|
||||
state: getState(),
|
||||
driver: getCurrentDriver(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const onNextClick = activeStep?.popover?.onNextClick || getConfig("onNextClick");
|
||||
if (onNextClick) {
|
||||
onNextClick(activeElement, activeStep!, {
|
||||
config: getConfig(),
|
||||
@@ -180,6 +192,17 @@ export function driver(options: Config = {}): Driver {
|
||||
return;
|
||||
}
|
||||
|
||||
const steps = getConfig("steps") || [];
|
||||
const isLastStep = activeIndex === steps.length - 1;
|
||||
const onDoneClick = activeStep.popover?.onDoneClick || getConfig("onDoneClick");
|
||||
if (isLastStep && onDoneClick) {
|
||||
return onDoneClick(activeElement, activeStep, {
|
||||
config: getConfig(),
|
||||
state: getState(),
|
||||
driver: getCurrentDriver(),
|
||||
});
|
||||
}
|
||||
|
||||
const onNextClick = activeStep.popover?.onNextClick || getConfig("onNextClick");
|
||||
if (onNextClick) {
|
||||
return onNextClick(activeElement, activeStep, {
|
||||
|
||||
@@ -34,6 +34,7 @@ export type Popover = {
|
||||
onNextClick?: DriverHook;
|
||||
onPrevClick?: DriverHook;
|
||||
onCloseClick?: DriverHook;
|
||||
onDoneClick?: DriverHook;
|
||||
};
|
||||
|
||||
export type PopoverDOM = {
|
||||
@@ -167,8 +168,19 @@ export function renderPopover(element: Element, step: DriveStep) {
|
||||
const onNextClick = step.popover?.onNextClick || getConfig("onNextClick");
|
||||
const onPrevClick = step.popover?.onPrevClick || getConfig("onPrevClick");
|
||||
const onCloseClick = step.popover?.onCloseClick || getConfig("onCloseClick");
|
||||
const onDoneClick = step.popover?.onDoneClick || getConfig("onDoneClick");
|
||||
|
||||
if (!!target.closest(".driver-popover-next-btn")) {
|
||||
// On the final step the next button acts as the done button, so a
|
||||
// dedicated onDoneClick takes precedence over onNextClick when provided.
|
||||
if (isDoneStep && onDoneClick) {
|
||||
return onDoneClick(element, step, {
|
||||
config: getConfig(),
|
||||
state: getState(),
|
||||
driver: getCurrentDriver(),
|
||||
});
|
||||
}
|
||||
|
||||
// If the user has provided a custom callback, call it
|
||||
// otherwise, emit the event.
|
||||
if (onNextClick) {
|
||||
|
||||
@@ -86,4 +86,57 @@ describe("button interactions", () => {
|
||||
expect(onNextClick).toHaveBeenCalledTimes(1);
|
||||
expect(d.getActiveIndex()).toBe(0);
|
||||
});
|
||||
|
||||
it("runs onDoneClick instead of onNextClick on the final step", () => {
|
||||
const onDoneClick = vi.fn();
|
||||
const onNextClick = vi.fn();
|
||||
const d = createDriver({ animate: false, steps: SAMPLE_STEPS, onDoneClick, onNextClick });
|
||||
d.drive(SAMPLE_STEPS.length - 1);
|
||||
navButton("next")?.click();
|
||||
|
||||
expect(onDoneClick).toHaveBeenCalledTimes(1);
|
||||
expect(onNextClick).not.toHaveBeenCalled();
|
||||
|
||||
const [element, step, options] = onDoneClick.mock.calls[0];
|
||||
expect(element).toBe(document.querySelector(".feature-list"));
|
||||
expect(step.popover?.title).toBe("Step 3");
|
||||
expect(options.driver).toBe(d);
|
||||
});
|
||||
|
||||
it("leaves teardown to onDoneClick instead of auto-destroying", () => {
|
||||
const onDoneClick = vi.fn();
|
||||
const d = createDriver({ animate: false, steps: SAMPLE_STEPS, onDoneClick });
|
||||
d.drive(SAMPLE_STEPS.length - 1);
|
||||
navButton("next")?.click();
|
||||
|
||||
expect(onDoneClick).toHaveBeenCalledTimes(1);
|
||||
expect(d.isActive()).toBe(true);
|
||||
});
|
||||
|
||||
it("does not fire onDoneClick on non-final steps", () => {
|
||||
const onDoneClick = vi.fn();
|
||||
const onNextClick = vi.fn();
|
||||
const d = createDriver({ animate: false, steps: SAMPLE_STEPS, onDoneClick, onNextClick });
|
||||
d.drive();
|
||||
navButton("next")?.click();
|
||||
|
||||
expect(onDoneClick).not.toHaveBeenCalled();
|
||||
expect(onNextClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("supports a step-level onDoneClick override", () => {
|
||||
const onDoneClick = vi.fn();
|
||||
const d = createDriver({
|
||||
animate: false,
|
||||
steps: [
|
||||
{ element: "#intro", popover: { title: "Step 1" } },
|
||||
{ element: "#card-1", popover: { title: "Step 2", onDoneClick } },
|
||||
],
|
||||
});
|
||||
d.drive(1);
|
||||
navButton("next")?.click();
|
||||
|
||||
expect(onDoneClick).toHaveBeenCalledTimes(1);
|
||||
expect(d.isActive()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
+15
-1
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createDriver, nextFrame, popoverTitle, pressKey, SAMPLE_STEPS, useDriverHarness } from "./utils";
|
||||
|
||||
useDriverHarness();
|
||||
@@ -48,6 +48,20 @@ describe("keyboard control", () => {
|
||||
expect(d.getActiveIndex()).toBe(0);
|
||||
});
|
||||
|
||||
it("runs onDoneClick when ArrowRight is pressed on the final step", async () => {
|
||||
const onDoneClick = vi.fn();
|
||||
const onNextClick = vi.fn();
|
||||
const d = createDriver({ animate: false, steps: SAMPLE_STEPS, onDoneClick, onNextClick });
|
||||
d.drive(SAMPLE_STEPS.length - 1);
|
||||
await nextFrame();
|
||||
|
||||
pressKey("ArrowRight");
|
||||
|
||||
expect(onDoneClick).toHaveBeenCalledTimes(1);
|
||||
expect(onNextClick).not.toHaveBeenCalled();
|
||||
expect(d.isActive()).toBe(true);
|
||||
});
|
||||
|
||||
it("ignores keys when allowKeyboardControl is false", () => {
|
||||
const d = createDriver({ animate: false, allowKeyboardControl: false, steps: SAMPLE_STEPS });
|
||||
d.drive();
|
||||
|
||||
Reference in New Issue
Block a user