From a0a08e9fa3e5b02e64948e6cbde9e669056508d6 Mon Sep 17 00:00:00 2001 From: Kamran Ahmed Date: Sat, 1 Jul 2023 10:45:26 +0100 Subject: [PATCH] Add highlight hooks and allow reconfiguring driver --- docs/src/content/guides/configuration.mdx | 154 +++++++++++++++++++++- src/driver.ts | 9 +- src/highlight.ts | 4 +- 3 files changed, 162 insertions(+), 5 deletions(-) diff --git a/docs/src/content/guides/configuration.mdx b/docs/src/content/guides/configuration.mdx index 313c974..f4473d6 100644 --- a/docs/src/content/guides/configuration.mdx +++ b/docs/src/content/guides/configuration.mdx @@ -68,7 +68,7 @@ type Popover = { // Callbacks for button clicks. You can use // these to add custom behavior to the buttons. // Each callback receives the following parameters: - // - element: The target DOM element of the step + // - 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 @@ -77,3 +77,155 @@ type Popover = { onCloseClick?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => 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, or a CSS selector. + // If this is a selector, the first matching + // element will be highlighted. + element: Element | string; + + // The popover configuration for this step. + // Look at the Popover Configuration section + popover?: Popover; + + // 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 + 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; +} +``` + +## Global 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: rgba(0, 0, 0, 0.5)) + // This is useful when you have a dark background + // and want to highlight elements with a light + // background color. + backdropColor?: 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) + opacity?: number; + // 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; + + // 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. + onPopoverRendered?: (popover: PopoverDOM) => 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 + onHighlightStarted?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void;; + onHighlighted?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void;; + onDeselected?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => 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 + onDestroyStarted?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => void;; + onDestroyed?: (element?: Element, step: DriveStep, options: { config: Config; state: State }) => 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 + 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;; +}; +``` + +## 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; +} +``` \ No newline at end of file diff --git a/src/driver.ts b/src/driver.ts index 3a69887..1a37c72 100644 --- a/src/driver.ts +++ b/src/driver.ts @@ -9,6 +9,8 @@ import "./driver.css"; export type DriveStep = { element?: string | Element; + onHighlightStarted?: DriverHook; + onHighlighted?: DriverHook; onDeselected?: DriverHook; popover?: Popover; }; @@ -132,7 +134,6 @@ export function driver(options: Config = {}) { .replace("{{current}}", `${stepIndex + 1}`) .replace("{{total}}", `${steps.length}`); - console.log(showProgress); highlight({ ...currentStep, popover: { @@ -168,7 +169,8 @@ export function driver(options: Config = {}) { // the hook for when user calls `destroy`, driver will get into infinite loop // not causing tour to be destroyed. if (withOnDestroyStartedHook && onDestroyStarted) { - onDestroyStarted(activeElement, activeStep!, { + const isActiveDummyElement = !activeElement || activeElement?.id === "driver-dummy-element"; + onDestroyStarted(isActiveDummyElement ? undefined : activeElement, activeStep!, { config: getConfig(), state: getState(), }); @@ -213,6 +215,9 @@ export function driver(options: Config = {}) { init(); drive(stepIndex); }, + setConfig: configure, + getConfig, + getState, moveNext, movePrevious, hasNextStep: () => { diff --git a/src/highlight.ts b/src/highlight.ts index c7731a2..208e534 100644 --- a/src/highlight.ts +++ b/src/highlight.ts @@ -70,8 +70,8 @@ function transferHighlight(toElement: Element, toStep: DriveStep) { const isFromDummyElement = fromElement.id === "driver-dummy-element"; const isAnimatedTour = getConfig("animate"); - const highlightStartedHook = getConfig("onHighlightStarted"); - const highlightedHook = getConfig("onHighlighted"); + const highlightStartedHook = toStep.onHighlightStarted || getConfig("onHighlightStarted"); + const highlightedHook = toStep?.onHighlighted || getConfig("onHighlighted"); const deselectedHook = fromStep?.onDeselected || getConfig("onDeselected"); const config = getConfig();