mirror of
https://github.com/kamranahmedse/driver.js.git
synced 2026-10-30 19:27:40 +08:00
Make keyboard control follow the buttons
This commit is contained in:
@@ -18,6 +18,10 @@ ships, these entries move under a versioned heading.
|
||||
|
||||
### Changed
|
||||
|
||||
- Keyboard control follows the popover buttons: arrow keys do nothing when
|
||||
their button is hidden or disabled, and Escape does nothing when the close
|
||||
button is disabled.
|
||||
([#589](https://github.com/nilbuild/driver.js/issues/589))
|
||||
- Only one instance is on screen at a time. Starting a tour or highlight closes
|
||||
any other active instance; it gets `onDeselected` and `onDestroyed` but not
|
||||
`onDestroyStarted`.
|
||||
|
||||
@@ -59,6 +59,8 @@ type Config = {
|
||||
stageRadius?: number;
|
||||
|
||||
// Whether to allow keyboard navigation. (default: true)
|
||||
// Arrow keys are ignored when their button is hidden or disabled,
|
||||
// and Escape is ignored when the close button is disabled.
|
||||
allowKeyboardControl?: boolean;
|
||||
|
||||
// Whether to disable interaction with the highlighted element. (default: false)
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import { destroyPopover, Popover } from "./popover";
|
||||
import { AllowedButtons, destroyPopover, Popover } from "./popover";
|
||||
import { destroyOverlay } from "./overlay";
|
||||
import { destroyEvents, initEvents, requireRefresh } from "./events";
|
||||
import { Config, createContext, DriverHook } from "./context";
|
||||
import { destroyHighlight, highlight } from "./highlight";
|
||||
import { findReachableIndex, resolveNextHook, resolvePrevHook, resolveTourStep, shouldSkipStep } from "./step";
|
||||
import {
|
||||
findReachableIndex,
|
||||
resolveNextHook,
|
||||
resolvePrevHook,
|
||||
resolveStepButtons,
|
||||
resolveTourStep,
|
||||
shouldSkipStep,
|
||||
} from "./step";
|
||||
import { resolveElement } from "./utils";
|
||||
import "./driver.css";
|
||||
|
||||
@@ -64,6 +71,21 @@ export function driver(options: Config = {}): Driver {
|
||||
destroy();
|
||||
}
|
||||
|
||||
function handleEscape() {
|
||||
const { disableButtons } = resolveStepButtons(ctx, ctx.getState("__activeStep"));
|
||||
if (disableButtons.includes("close")) {
|
||||
return;
|
||||
}
|
||||
|
||||
handleClose();
|
||||
}
|
||||
|
||||
function canUseButton(button: AllowedButtons) {
|
||||
const { showButtons, disableButtons } = resolveStepButtons(ctx, ctx.getState("__activeStep"));
|
||||
|
||||
return showButtons.includes(button) && !disableButtons.includes(button);
|
||||
}
|
||||
|
||||
function handleOverlayClick() {
|
||||
const overlayClickBehavior = ctx.getConfig("overlayClickBehavior");
|
||||
|
||||
@@ -179,6 +201,10 @@ export function driver(options: Config = {}): Driver {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!canUseButton("previous")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const onPrevClick = resolvePrevHook(ctx, activeStep);
|
||||
if (onPrevClick) {
|
||||
return onPrevClick(activeElement, activeStep, ctx.getHookOpts());
|
||||
@@ -200,6 +226,10 @@ export function driver(options: Config = {}): Driver {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!canUseButton("next")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const onNextClick = resolveNextHook(ctx, activeStep);
|
||||
if (onNextClick) {
|
||||
return onNextClick(activeElement, activeStep, ctx.getHookOpts());
|
||||
@@ -229,7 +259,7 @@ export function driver(options: Config = {}): Driver {
|
||||
|
||||
ctx.listen("overlayClick", handleOverlayClick);
|
||||
ctx.listen("activeElementClick", handleActiveElementClick);
|
||||
ctx.listen("escapePress", handleClose);
|
||||
ctx.listen("escapePress", handleEscape);
|
||||
ctx.listen("closeClick", handleClose);
|
||||
ctx.listen("arrowLeftPress", handleArrowLeft);
|
||||
ctx.listen("arrowRightPress", handleArrowRight);
|
||||
|
||||
@@ -114,6 +114,13 @@ export function resolveTourStep(ctx: Context, stepIndex: number, defaults: TourS
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveStepButtons(ctx: Context, step?: DriveStep) {
|
||||
return {
|
||||
showButtons: step?.popover?.showButtons || ctx.getConfig("showButtons") || [],
|
||||
disableButtons: step?.popover?.disableButtons || ctx.getConfig("disableButtons") || [],
|
||||
};
|
||||
}
|
||||
|
||||
function resolveStepPosition(ctx: Context, element: Element, step: DriveStep): PositionOptions {
|
||||
const stagePadding = ctx.getConfig("stagePadding") || 0;
|
||||
|
||||
@@ -140,8 +147,7 @@ function resolveStepPopover(ctx: Context, element: Element, step: DriveStep): Po
|
||||
title: popover.title,
|
||||
description: popover.description,
|
||||
|
||||
showButtons: popover.showButtons || ctx.getConfig("showButtons")!,
|
||||
disableButtons: popover.disableButtons || ctx.getConfig("disableButtons")! || [],
|
||||
...resolveStepButtons(ctx, step),
|
||||
showProgress: popover.showProgress || ctx.getConfig("showProgress") || false,
|
||||
|
||||
progressText: popover.progressText ?? (ctx.getConfig("progressText") || DEFAULT_PROGRESS_TEXT),
|
||||
|
||||
@@ -106,3 +106,65 @@ describe("keyboard control", () => {
|
||||
expect(d.getActiveIndex()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("keyboard control follows the buttons", () => {
|
||||
it("ignores ArrowRight when the next button is hidden", async () => {
|
||||
const d = createDriver({ animate: false, showButtons: ["previous", "close"], steps: SAMPLE_STEPS });
|
||||
d.drive();
|
||||
await nextFrame();
|
||||
|
||||
pressKey("ArrowRight");
|
||||
await nextFrame();
|
||||
expect(d.getActiveIndex()).toBe(0);
|
||||
});
|
||||
|
||||
it("ignores ArrowLeft when the previous button is hidden", async () => {
|
||||
const d = createDriver({ animate: false, showButtons: ["next", "close"], steps: SAMPLE_STEPS });
|
||||
d.drive(1);
|
||||
await nextFrame();
|
||||
|
||||
pressKey("ArrowLeft");
|
||||
await nextFrame();
|
||||
expect(d.getActiveIndex()).toBe(1);
|
||||
});
|
||||
|
||||
it("ignores ArrowRight when the next button is disabled", async () => {
|
||||
const d = createDriver({
|
||||
animate: false,
|
||||
steps: [
|
||||
{ element: "#intro", popover: { title: "Step 1", disableButtons: ["next"] } },
|
||||
{ element: "#card-1", popover: { title: "Step 2" } },
|
||||
],
|
||||
});
|
||||
d.drive();
|
||||
await nextFrame();
|
||||
|
||||
pressKey("ArrowRight");
|
||||
await nextFrame();
|
||||
expect(d.getActiveIndex()).toBe(0);
|
||||
});
|
||||
|
||||
it("ignores Escape when the close button is disabled", () => {
|
||||
const d = createDriver({ animate: false, disableButtons: ["close"], steps: SAMPLE_STEPS });
|
||||
d.drive();
|
||||
pressKey("Escape");
|
||||
|
||||
expect(d.isActive()).toBe(true);
|
||||
});
|
||||
|
||||
it("still closes with Escape when the close button is only hidden", () => {
|
||||
const d = createDriver({ animate: false, showButtons: ["next", "previous"], steps: SAMPLE_STEPS });
|
||||
d.drive();
|
||||
pressKey("Escape");
|
||||
|
||||
expect(d.isActive()).toBe(false);
|
||||
});
|
||||
|
||||
it("closes a highlight with Escape", () => {
|
||||
const d = createDriver({ animate: false });
|
||||
d.highlight({ element: "#intro", popover: { title: "Intro" } });
|
||||
pressKey("Escape");
|
||||
|
||||
expect(d.isActive()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user