npm package: @sammy-labs/walkthroughs
Description: Sammy Labs Walkthroughs
Author: Sammy Labs Team
Latest version: 0.2.24

# @sammy-labs/walkthroughs

This package provides a simple, **Provider + Hook** architecture to integrate Sammy's interactive walkthroughs into your React (or Next.js) application. It manages global state, highlights elements on the page, and guides the user step-by-step through a sequence of interactions.

Key features include:

- **Global** `WalkthroughProvider` to manage authentication tokens, driver instances, and global config.
- **React Hook** `useWalkthrough` to start, stop, or control walkthroughs anywhere in your app.
- **Utility** methods for fetching and transforming walkthrough data (from pre-fetched).
- **Highly configurable** overlay, popover, animations, and interaction settings.

## Table of Contents

- [Installation](#installation)
- [Core Concepts](#core-concepts)
- [Quick Start](#quick-start)
- [Usage Examples](#usage-examples)
  - [Next.js Integration](#nextjs-integration)
- [API Reference](#api-reference)
  - [WalkthroughProvider](#walkthroughprovider)
  - [useWalkthrough(options?)](#usewalkthroughoptions)
  - [Global Configuration (WalkthroughConfig)](#global-configuration-walkthroughconfig)
  - [Driver Configuration (DriverConfig)](#driver-configuration-driverconfig)
  - [Helper Functions (flow-guide utilities)](#helper-functions-flow-guide-utilities)
- [Advanced Usage and Tips](#advanced-usage-and-tips)
  - [Auth Token Management](#auth-token-management)
  - [Walkthrough Data Fetching](#walkthrough-data-fetching)
  - [Handling Multiple Walkthroughs](#handling-multiple-walkthroughs)
  - [Custom Logging](#custom-logging)
- [Advanced Features](#advanced-features)
  - [Informational Steps](#informational-steps)
  - [Observer Elements](#observer-elements)
  - [Location Change & SPA Support](#location-change--spa-support)
  - [Video Embeds](#video-embeds)
  - [Fallback / Draggable Popovers](#fallback--draggable-popovers)
  - [Quality-of-Life Improvements](#quality-of-life-improvements)
  - [Feature-Rich Example](#feature-rich-example)
- [FAQ](#faq)
- [License](#license)

## Installation

Install the package via npm or yarn:

```bash
npm install @sammy-labs/walkthroughs
# or
yarn add @sammy-labs/walkthroughs
```

This will give you access to:

- `WalkthroughProvider` (the React provider)
- `useWalkthrough` (the React hook)
- Various type definitions (e.g., `FlowError`, `WalkthroughResponse`, etc.)

Ensure you also import the CSS file when needed:

```css
@import "@sammy-labs/walkthroughs/dist/index.css";
```

Or in a Next.js / Plasmo environment:

```js
import "@sammy-labs/walkthroughs/dist/index.css";
```

## Core Concepts

1. **`<WalkthroughProvider>`**

   - A React provider that stores all global state for the walkthroughs:
     - The **auth token** (JWT) for the Sammy API (optional, but needed if you want to fetch walkthrough data from the Sammy hosted API).
     - The **driver instance** for highlighting elements and showing popovers.
     - **Global configuration** for controlling debug mode, fallback behaviors, screenshots, etc.
   - Must wrap any part of your application that uses the library.

2. **`useWalkthrough` Hook**

   - Gives you intuitive methods to **start** or **stop** a walkthrough:
     - `startWithId(flowId)` to fetch from the Sammy API by flow ID.
     - `startWithData(yourFlowObject)` to start from pre-fetched or user-provided data.
     - `stop()` to terminate any running walkthrough.
     - Also provides status checks (e.g., `isActive()`).

3. **Driver & Global Configuration**

   - **Driver Config**: e.g. overlay color, popover offset, show next/prev buttons, animations, etc.
   - **Global Config**: e.g. wait times, fallback settings, custom API base URLs, debug mode.

4. **Walkthrough Data**
   - The library expects data in a certain shape (`WalkthroughResponse`) which it will parse into a series of steps. Each step references an element (or set of elements) to highlight and the text to display to the user.

## Quick Start

Below is a minimal usage example:

1. **Wrap your application**:

   ```tsx
   import React from "react";
   import { WalkthroughProvider } from "@sammy-labs/walkthroughs";
   import "@sammy-labs/walkthroughs/dist/index.css";

   function MyApp({ Component, pageProps }) {
     const sammyToken = "YOUR_JWT_TOKEN"; // typically fetched from your server
     return (
       <WalkthroughProvider
         token={sammyToken}
         baseUrl="https://api.sammylabs.com"
         driverConfig={{ overlayOpacity: 0.7 }}
         config={{ debug: true }}
       >
         <Component {...pageProps} />
       </WalkthroughProvider>
     );
   }

   export default MyApp;
   ```

2. **Use the Hook** in any component:

   ```tsx
   import React from "react";
   import { useWalkthrough } from "@sammy-labs/walkthroughs";

   export function StartButton() {
     const { startWithId, isActive, stop } = useWalkthrough();

     const handleStart = async () => {
       // Start a walkthrough by ID from the Sammy hosted API
       const success = await startWithId("12345");
       if (!success) {
         alert("Failed to start Sammy walkthrough.");
       }
     };

     return (
       <div>
         <button onClick={handleStart}>Start Walkthrough #12345</button>
         {isActive() && <button onClick={stop}>Stop Walkthrough</button>}
       </div>
     );
   }
   ```

That's enough to get you started! The library will automatically fetch data for that flow ID, highlight elements, show popovers, and proceed step by step.

## Usage Examples

### Next.js Integration

Below is a typical Next.js integration, referencing your environment variables to generate the Sammy token.

#### 1) **Create a provider** (e.g. `app/providers/SammyWalkthroughProvider.tsx`):

```tsx
"use client";

import React, { useState, useEffect } from "react";
import { WalkthroughProvider } from "@sammy-labs/walkthroughs";

export default function SammyWalkthroughProvider({ children }) {
  const [token, setToken] = useState<string | null>(null);

  useEffect(() => {
    // Example: fetch or generate a token from your server
    async function fetchToken() {
      const resp = await fetch("/api/sammy-auth");
      const data = await resp.json();
      setToken(data.token);
    }
    fetchToken();
  }, []);

  if (!token) {
    return <div>Loading walkthrough token...</div>;
  }

  return (
    <WalkthroughProvider
      token={token}
      baseUrl={process.env.NEXT_PUBLIC_SAMMY_BASE_URL}
      onTokenExpired={() => {
        console.log("Sammy token expired, re-fetching");
      }}
      onError={(err) => console.warn("Walkthrough error:", err)}
      driverConfig={{ overlayOpacity: 0.6 }}
      config={{ debug: false, askInput: true }}
    >
      {children}
    </WalkthroughProvider>
  );
}
```

#### 2) **Wrap** your app or layout:

```tsx
// app/layout.tsx or pages/_app.tsx
import SammyWalkthroughProvider from "./providers/SammyWalkthroughProvider";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <SammyWalkthroughProvider>{children}</SammyWalkthroughProvider>
      </body>
    </html>
  );
}
```

#### 3) **Use** the Hook in a client component:

```tsx
"use client";

import React from "react";
import { useWalkthrough } from "@sammy-labs/walkthroughs";

export default function Dashboard() {
  const { startWithId, stop, isActive } = useWalkthrough();

  return (
    <div>
      <button onClick={() => startWithId("8750")}>Show Onboarding Tips</button>
      {isActive() && <button onClick={stop}>Stop</button>}
    </div>
  );
}
```

## API Reference

### `WalkthroughProvider`

A React provider that must wrap any area that uses the library.

**Props**

| Prop                 | Type                         | Default                   | Description                                                                                                                |
| -------------------- | ---------------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| **`token`**          | `string \| null`             | `null`                    | A short-lived JWT token used to fetch data from the Sammy API. If omitted, only local flows (`startWithData()`) will work. |
| **`baseUrl`**        | `string`                     | `"http://localhost:8000"` | The base URL for Sammy's endpoints.                                                                                        |
| **`onTokenExpired`** | `() => void`                 | none                      | Called when a `401` occurs, letting you re-fetch a new token.                                                              |
| **`onError`**        | `(error: FlowError) => void` | none                      | Called when any error occurs (element not found, API failure, etc.).                                                       |
| **`driverConfig`**   | `Partial<DriverConfig>`      | `{ ... }`                 | Fine-grained driver settings (overlay color, popover offsets, etc.)                                                        |
| **`config`**         | `Partial<WalkthroughConfig>` | `{ ... }`                 | Global config options (debug, domain override, fallback settings, etc.).                                                   |
| **`logoUrl`**        | `string`                     | `""`                      | An optional logo to display in the popovers.                                                                               |
| **`children`**       | `ReactNode`                  | required                  | Your app or subtree.                                                                                                       |

**Example:**

```tsx
<WalkthroughProvider
  token="..."
  baseUrl="https://api.sammylabs.com"
  onTokenExpired={() => console.log("Token expired, re-auth!")}
  onError={(e) => console.error("Walkthrough error:", e)}
  driverConfig={{ overlayOpacity: 0.7 }}
  config={{ debug: true, askInput: false }}
>
  <App />
</WalkthroughProvider>
```

### `useWalkthrough(options?)`

Hook that returns convenience methods and the current walkthrough state.

**Signature**:

```ts
function useWalkthrough(options?: {
  checkQueryOnMount?: boolean;
  onError?: (error: FlowError) => void;
  waitTime?: number;
  driverConfig?: Partial<DriverConfig>;
  config?: Partial<WalkthroughConfig>;
  disableRedirects?: boolean;
  autoStartPendingWalkthrough?: boolean;
  fallbackTimeout?: number;
}): {
  startWithId(flowId: string | number): Promise<boolean>;
  startWithData(
    data: WalkthroughResponse | WalkthroughSearchResult | any
  ): Promise<boolean>;
  stop(): boolean;
  isActive(): boolean;
  configure(driverConfig: Partial<DriverConfig>): void;
  configureGlobal(globalConfig: Partial<WalkthroughConfig>): void;

  state: {
    isTokenValid: boolean;
    isLoading: boolean;
    error: FlowError | null;
    token: string | null;
    baseUrl: string;
    isActive: boolean;
    config: WalkthroughConfig;
  };
};
```

#### **Options**

- **`checkQueryOnMount?: boolean`**  
  If `true`, automatically checks the URL query parameters (default key `sammy_flow_id`) on mount to see if a walkthrough should be started.

- **`onError?: (error: FlowError) => void`**  
  Local error handler specifically for this hook usage. Merged with or overrides the provider-level `onError`.

- **`waitTime?: number`**  
  Milliseconds to wait before automatically starting a walkthrough from query parameters.

- **`driverConfig?: Partial<DriverConfig>`**  
  Additional driver config merges on top of the provider config.

- **`config?: Partial<WalkthroughConfig>`**  
  Additional global config merges on top of the provider config.

- **`disableRedirects?: boolean`**  
  If `true`, will not auto-redirect the user if the recorded URL in the walkthrough data doesn't match the current page.

- **`autoStartPendingWalkthrough?: boolean`**  
  If `true`, automatically attempts to start any pending walkthrough data that was stored from a previous page (useful in multi-page flows).

- **`fallbackTimeout?: number`**  
  Maximum time in milliseconds to wait for DOM elements to be found before using fallback elements. Defaults to 10000 (10s). This is deliberately separate from the main config object to avoid React re-render issues when used within effects.

#### **Returned Methods**

1. **`startWithId(flowId)`**  
   Fetches a walkthrough from the Sammy API using `flowId` and starts it.  
   Returns `true` if successful, `false` if not.

2. **`startWithData(data)`**  
   Starts a walkthrough using pre-fetched or user-provided data.

   - Accepts a `WalkthroughResponse`, or older shapes like search results.
   - Returns `true` if success, `false` otherwise.

3. **`stop()`**  
   Stops any active walkthrough, removing highlights/overlays.

4. **`isActive()`**  
   Returns `true` if a walkthrough is currently in progress.

5. **`configure(driverConfig)`**  
   Dynamically update the driver config.

6. **`configureGlobal(globalConfig)`**  
   Dynamically update the global config.

#### **Returned State**

- **`isTokenValid: boolean`**  
  Whether token is valid from last check (internal usage).
- **`isLoading: boolean`**  
  Whether the library is currently fetching data.
- **`error: FlowError \| null`**  
  Error from the last attempt.
- **`token: string \| null`**  
  The token being used from the provider.
- **`baseUrl: string`**  
  The base API URL in use.
- **`isActive: boolean`**  
  Shortcut for `isActive()`.
- **`config: WalkthroughConfig`**  
  The **merged** global config object.

### Global Configuration (`WalkthroughConfig`)

```ts
export type WalkthroughConfig = {
  // Query parameter key for auto-starting flow, default "sammy_flow_id"
  flowIdQueryParam: string;

  // Wait time before automatically starting a flow from query param
  waitTimeAfterLoadMs: number;

  // Maximum number of attempts to find a DOM element
  maxElementFindAttempts: number;

  // Timeout for each attempt to find an element, in ms
  elementFindTimeoutMs: number;

  // How long the DOM must remain stable before searching for elements
  domStabilityMs: number;

  // Max total time to wait for DOM to be stable
  maxDomStabilityWaitMs: number;

  // Enables debug logging
  debug: boolean;

  // Default base URL for the Sammy API
  apiBaseUrl: string;

  // Optional logo URL for popovers
  logoUrl: string;

  // Base URL for screenshot images
  imageBaseUrl: string;

  // Domain override used to rewrite step URLs if different from environment
  overrideDomainUrl: string;

  // Whether to show an "Ask a question" input in the popover
  askInput: boolean;

  // Whether to log events to Sammy
  enableLogging: boolean;
};
```

**Common fields**:

- `debug`: set to `true` to see console logs about fallback elements, element searches, etc.
- `overrideDomainUrl`: if the recorded steps have different domain references than your current domain, you can override them.

**Example**:

```tsx
<WalkthroughProvider
  config={{
    debug: true,
    maxElementFindAttempts: 3,
    elementFindTimeoutMs: 10000,
    overrideDomainUrl: "https://demo.deel.com",
  }}
>
  <App />
</WalkthroughProvider>
```

**Example with fallbackTimeout**:

```tsx
// Using the hook with fallbackTimeout
function WalkthroughButton() {
  const { startWithId } = useWalkthrough({
    // Separate from config to avoid re-render issues in effects
    fallbackTimeout: 15000, // Wait up to 15 seconds for elements to be found
    config: {
      debug: true,
      overrideDomainUrl: "https://demo.deel.com",
    },
  });

  return (
    <button onClick={() => startWithId("12345")}>Start Walkthrough</button>
  );
}
```

### Driver Configuration (`DriverConfig`)

These are more about the highlight overlay and popover.

```ts
export interface DriverConfig {
  steps?: DriveStep[];
  enableLogging?: boolean;
  animate?: boolean;
  overlayColor?: string; // default #000
  overlayOpacity?: number; // default 0.7
  smoothScroll?: boolean; // default false
  allowClose?: boolean; // default true
  overlayClickBehavior?: "close" | "nextStep";
  stagePadding?: number; // default 8
  stageRadius?: number; // default 12
  disableActiveInteraction?: boolean; // default false
  allowKeyboardControl?: boolean; // default true
  popoverClass?: string;
  popoverOffset?: number;
  showButtons?: ("next" | "previous" | "close")[];
  disableButtons?: ("next" | "previous" | "close")[];
  showProgress?: boolean;
  progressText?: string; // e.g. "{{current}} of {{total}}"
  nextBtnText?: string;
  prevBtnText?: string;
  doneBtnText?: string;
  logoUrl?: string;
  // Lifecycle callbacks
  onHighlightStarted?: DriverHook;
  onHighlighted?: DriverHook;
  onDeselected?: DriverHook;
  onDestroyStarted?: DriverHook;
  onDestroyed?: DriverHook;
  onNextClick?: DriverHook;
  onPrevClick?: DriverHook;
  onCloseClick?: DriverHook;
}
```

**Example**:

```tsx
// Overriding certain fields:
<WalkthroughProvider
  driverConfig={{
    overlayOpacity: 0.5,
    animate: true,
    showButtons: ["next", "previous"],
    doneBtnText: "Done!",
  }}
>
  <App />
</WalkthroughProvider>
```

### Helper Functions (flow-guide utilities)

While normally you only need the Hook, there are some lower-level helpers if you want direct control:

- **`executeApiFlow(flowId, orgId, token, baseUrl, onError)`**  
  Fetches the data from the Sammy hosted API and starts the flow.
- **`executeFlowWithData(data, onError, options)`**  
  Takes a `WalkthroughResponse` (or older format) and starts it.

These are used internally by `startWithId` and `startWithData`.

## Advanced Usage and Tips

### Auth Token Management

Your token typically expires after a certain time. If the user is still going through a walkthrough and the token expires, the library calls `onTokenExpired()`, giving you a chance to refresh it. Then you can pass the new token back to the provider (e.g., via React state).

```tsx
<WalkthroughProvider
  token={myToken}
  onTokenExpired={() => {
    // e.g., re-fetch new token and re-set the parent state
  }}
>
  ...
</WalkthroughProvider>
```

### Walkthrough Data Fetching

If you already have the data for a flow (e.g., from server side rendering or some custom server endpoint), you can pass it directly to `startWithData()`:

```ts
const { startWithData } = useWalkthrough();

// Suppose you have a big object "myWalkthrough" that matches WalkthroughResponse
await startWithData(myWalkthrough);
```

No additional fetch calls are needed in that scenario.

### Handling Multiple Walkthroughs

Currently, the library supports **one** active walkthrough at a time. If you try to start a new one while one is running, it will close the previous driver. If you want to queue multiple flows, you can do so sequentially:

```ts
const { startWithId, stop } = useWalkthrough();

// Example queue approach
await startWithId("flowA");
// After finishing or stopping, then
await startWithId("flowB");
```

### Custom Logging

The walkthrough package includes a built‐in logging mechanism that automatically sends events such as **start**, **step**, **finish**, **redirect**, and **abandon** to your Sammy API endpoint. These events are defined in the `LogEventType` enum and encapsulate key information about the walkthrough's progress.

The default logging mechanism (to the Sammy hosted API) is handled by the `logWalkthroughEvent()` function, located in **`packages/walkthroughs/src/lib/log.ts`**. It performs two main tasks:

1. **Invokes any user‐provided callback** (`onWalkthroughEvent`) — letting you intercept logs locally.
2. **Sends a request to the Sammy hosted API** (the configured `baseUrl`) to record that event.

You can **customize** logging in two major ways:

1. **Providing an `onWalkthroughEvent`** callback in your `<WalkthroughProvider>`.
2. **Overriding or modifying** `logWalkthroughEvent()` to change how the package logs to your server.

#### 1. Using `onWalkthroughEvent` in `<WalkthroughProvider>`

When you set up the Walkthrough Provider, you can attach a callback named `onWalkthroughEvent` that fires **immediately** whenever the walkthrough emits an event. Example:

```tsx
import React from "react";
import {
  WalkthroughProvider,
  type LogEvent,
  LogEventType,
} from "@sammy-labs/walkthroughs";

function App() {
  return (
    <WalkthroughProvider
      token="YOUR_JWT_TOKEN"
      baseUrl="https://api.sammylabs.com"
      onWalkthroughEvent={(event: LogEvent) => {
        // Log it:
        console.log("WalkthroughEvent Received:", event.event_type);

        // Example switch on event type:
        switch (event.event_type) {
          case LogEventType.START:
            // The user started the walkthrough
            break;
          case LogEventType.STEP:
            // The user advanced to a new step
            break;
          case LogEventType.FINISH:
            // The user completed the entire walkthrough
            break;
          // etc.
          default:
            break;
        }

        // Forward to your own analytics if you like
        myAnalyticsService.track("sammy_walkthrough_event", event);
      }}
    >
      {/* ...your app... */}
    </WalkthroughProvider>
  );
}
```

**Common Use Cases**:

- **Forwarding** events into your analytics (Segment, Mixpanel, GA, etc.).
- **Storing** local logs in your own database.
- **Displaying** in-app notifications when specific events occur.

#### 2. Customizing or Replacing `logWalkthroughEvent()`

Under the hood, the library calls a helper function `logWalkthroughEvent(event, token, baseUrl)` to record each event server‐side. It is located in **`packages/walkthroughs/src/lib/log.ts`**.

```ts
export async function logWalkthroughEvent(
  event: LogEvent,
  token: string,
  baseUrl: string
): Promise<LogResponse | void> {
  // 1) If there's an `onWalkthroughEvent` callback in WalkthroughProvider, call it:
  const globalContext = (window as any)?.__WALKTHROUGH_CONTEXT__;
  if (globalContext && typeof globalContext.onWalkthroughEvent === "function") {
    globalContext.onWalkthroughEvent(event);
  }

  // 2) Then send the event to the Sammy hosted API:
  try {
    const response = await fetch(`${baseUrl}/public/walkthrough/log`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${token}`,
      },
      body: JSON.stringify({ event }),
    });

    if (!response.ok) {
      throw new Error(`Failed to log event: ${response.statusText}`);
    }

    // The response typically returns { event_type, timestamp }
    return await response.json();
  } catch (err) {
    console.error("logWalkthroughEvent error:", err);
  }
}
```

#### 3. The Event Types and Data

Each event object includes fields to identify the status of the walkthrough. For reference, `LogEvent` is a union type that may have:

- **`event_type`** — One of: `start | step | finish | abandon | redirect | fallback`.
- **`user_replay_id`** — A client-generated ID to group all events for one walkthrough session.
- **`user_replay_step_id`** — A unique ID for each step event within a session.
- **`step_number`** — (Optional) 1‐based step index (if relevant).
- **`flow_id`, `flow_version_id`** — Identifiers used in `START` events.
- **`status`** — A short code describing the step's status, e.g. `"clicked"`, `"finished"`, `"abandoned"`.

#### 4. Example: Logging to Your Own Server

Using the `onWalkthroughEvent` callback is generally simplest. For example, to POST each event to your own server:

```tsx
<WalkthroughProvider
  token={token}
  baseUrl="https://api.sammylabs.com"
  onWalkthroughEvent={(event) => {
    // 1) Immediately forward to your analytics
    fetch("https://myserver.com/internal-analytics", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ event }),
    });
  }}
>
  {/* app code */}
</WalkthroughProvider>
```

This ensures:

- Sammy logs remain intact (unless you override them).
- You also get immediate, client‐side event data for your own logs.

## Advanced Features

### Informational Steps

**What are they?**

- Steps that show a "corner popover" (no overlay) with informational content (optionally including a video), **without** blocking or restricting page interaction.
- Marked by `step_type?: "informational"` in both `HistoryStep` and `DriveStep`.
- They display the popover in the bottom‐left by default, allow normal clicking outside (no overlay), and can be "dragged" around.

**Key Points**

- Use `step_type: "informational"` in your API response or in a manual step definition to trigger corner popovers.
- The code sets a body class (`.sammy-labs-informational-step`) to apply special styling.
- You can still attach a `video_info` object to show a YouTube embed.

**Example**:

```json
{
  "step_num": 3,
  "step_type": "informational",
  "video_info": {
    "youtube_url": "https://www.youtube.com/watch?v=abc123&t=60"
  },
  "state": {
    "url": "https://example.com/dashboard",
    "interacted_element": []
  },
  "result": [],
  "model_output": {
    /* ... */
  }
}
```

### Observer Elements

**What are they?**

- A secondary "watcher" element used in cases where the real interactive element may appear or change **later** in the DOM (e.g., hidden behind a dynamic menu).
- The walkthrough can attach a `MutationObserver` on the `observer_element` to wait until the real `interactiveElements` become visible or inserted.

**Usage**

- Add `observer_element?: InteractiveElement` in your step.
- The code will try to highlight the `observer_element` first, and then watch for the real interactive element to appear.
- If found, it replaces the highlight with the real element automatically.

**Example**:

```json
{
  "step_num": 2,
  "observer_element": {
    "xpath": "//div[@class='dynamic-menu']",
    "attributes": { "class": "dynamic-menu" }
  },
  "interactiveElements": [
    {
      "xpath": "//button[text()='Add to Cart']",
      "attributes": { "type": "button" }
    }
  ]
}
```

### Location Change & SPA Support

**Why?**

- Many modern apps are Single-Page Applications (SPA) where route changes don't always trigger a full page refresh.
- The library can now detect location changes (via History API or polling) and re‐run logic, letting you maintain or remove highlights when a user navigates in an SPA.

**Configuration**

- In `WalkthroughProvider`, set:

  ```tsx
  <WalkthroughProvider
    ...
    locationChangeEvents={true}
    locationChangePollInterval={500} // How often to poll for location changes
    locationChangeDebug={false}      // Whether to log debug info
    ...
  >
  ```

- Or set these in the global `config` object:

  ```ts
  config={{
    enableLocationChangeEvents: true,
    locationChangePollInterval: 500,
    locationChangeDebug: false
  }}
  ```

**Behavior**

- Automatically dispatches `locationchange` events.
- The driver listens and either re‐highlights or removes highlights if the new URL is incompatible with the step (especially for informational steps that specify a `url` field).

### Video Embeds

**What & How**

- A step can show a YouTube video in its popover by including `video_info` with `youtube_url` (and optionally `start_time`).
- Only used if the step is **informational** (or if the fallback popover is forced).
- The code auto‐embeds an `<iframe>` into the popover.

**Example**:

```json
{
  "step_num": 4,
  "step_type": "informational",
  "video_info": {
    "youtube_url": "https://youtu.be/VIDEOID?t=120"
  }
  ...
}
```

### Fallback / Draggable Popovers

**Fallback**

- If the driver can't locate the real DOM element, it falls back to a small, invisible dummy element for highlight.
- Shows a popover in the corner of the screen so the user knows a step is "missing."

**Draggable**

- Both fallback and informational popovers can be dragged around by the user.
- Implemented via a `mousedown` → `mousemove` approach to let you reposition the popover if it obstructs something.

### Quality-of-Life Improvements

- **Improved Debug Logging**: Additional console logs if an element fails to match.
- **Better Re‐Drive**: If the user leaves the page or the DOM changes drastically, the code tries to re‐drive the same step to keep it consistent.
- **Smoother Overlay**: The overlay and highlight transitions are more robust.

### Feature-Rich Example

Here is a complete example combining some of these features:

```tsx
import {
  WalkthroughProvider,
  useWalkthrough,
  type WalkthroughResponse,
} from "@sammy-labs/walkthroughs";

function App() {
  return (
    <WalkthroughProvider
      token="..."
      baseUrl="https://api.sammylabs.com"
      locationChangeEvents={true}
      locationChangePollInterval={500}
      driverConfig={{
        overlayOpacity: 0.5,
      }}
      config={{
        debug: true,
        overrideDomainUrl: "https://demo.yourapp.com",
        askInput: false,
      }}
    >
      <MyComponent />
    </WalkthroughProvider>
  );
}

function MyComponent() {
  const { startWithData, stop, isActive } = useWalkthrough();

  const runInformationalStep = async () => {
    const sample: WalkthroughResponse = {
      flow_id: "demo_flow",
      history: [
        {
          step_num: 1,
          step_type: "informational",
          video_info: { youtube_url: "https://youtu.be/abc123" },
          state: {
            url: "https://demo.yourapp.com/page1",
            interacted_element: [],
          },
          result: [],
          model_output: {
            action: [],
            current_state: {
              memory: "",
              next_goal: "",
              evaluation_previous_goal: "",
            },
          },
        },
      ],
    };

    await startWithData(sample);
  };

  return (
    <div>
      <button onClick={runInformationalStep}>Start Informational Demo</button>
      <button onClick={() => isActive() && stop()}>Stop Walkthrough</button>
    </div>
  );
}
```

## FAQ

1. **"Why can't it find my element?"**

   - Ensure your selectors (like `xpath` or attributes) are stable. Turn on `debug: true` to see logs. If the element is in a lazy-loaded modal, you may need extra time or increase `elementFindTimeoutMs`.

2. **"Can I override the domain in the steps?"**

   - Yes, use `overrideDomainUrl: "https://demo.deel.com"` in your config. The library will reconstruct the final URL for each step using that domain.

3. **"How do I customize how long the system waits for elements before creating fallbacks?"**

   - Use the `fallbackTimeout` parameter directly in the `useWalkthrough` hook options. For example: `useWalkthrough({ fallbackTimeout: 15000 })` will wait 15 seconds before creating fallback elements. This parameter is separated from the main config object to avoid React re-render issues when used in effects.

4. **"How do I pass a custom popup style or embed a custom button?"**

   - For custom styling, pass a `popoverClass` or an `onPopoverRender` callback in `driverConfig`. For full control, you can modify the driver callbacks.

5. **"Does it work outside of React?"**
   - The library is React-based. If you need pure JS usage, you might adapt the underlying logic or run the code in a minimal React container.

## License

`@sammy-labs/walkthroughs` is licensed under the **MIT License**. See the [LICENSE](LICENSE) file for more details.