import * as React$1 from 'react'; import React__default, { ReactElement, ReactNode } from 'react'; import { P as Placement, K as KeyboardConfig, S as SpotlightConfig, a as PersistenceConfig, A as A11yConfig, b as ScrollConfig, F as FlowSessionConfig, C as CrossTabConfig, T as TourKitConfig, R as Rect, c as Side, d as Alignment, e as Storage } from './config-DfZMwC7R.js'; export { D as Direction, f as Position, g as defaultA11yConfig, h as defaultKeyboardConfig, i as defaultPersistenceConfig, j as defaultScrollConfig, k as defaultSpotlightConfig } from './config-DfZMwC7R.js'; import * as react_jsx_runtime from 'react/jsx-runtime'; import { ClassValue } from 'clsx'; /** * Diagnostic engine types — Phase 3 (full surface). * * The `EligibilityReport` is what `explainTour` produces and * `useTourDiagnostic` exposes. The `DiagnosticGate` interface is the * extension contract — upper packages (`@tour-kit/license`, * `@tour-kit/scheduling`, etc.) implement it WITHOUT this package importing * any of them. Hard rule: `@tour-kit/core` sits at the bottom of the * dependency graph; never `import { ... } from '@tour-kit/'` here. */ /** * Canonical failure codes. The `(string & {})` escape hatch keeps * literal-union autocomplete for built-ins while allowing extension gates to * surface their own codes (`'LICENSE_INVALID'`, `'OUT_OF_WINDOW'`, ...). */ type GateCode = 'STRUCTURE_INVALID' | 'AUDIENCE_MISMATCH' | 'ALREADY_COMPLETED' | 'ALREADY_SKIPPED' | 'OUT_OF_WINDOW' | 'LICENSE_INVALID' | 'LICENSE_EXPIRED' | 'TARGET_NOT_FOUND' | 'WHEN_RETURNED_FALSE' | 'ROUTE_MISMATCH' | 'AUTOSTART_DISABLED' | (string & {}); type GateName = 'structure' | 'audience' | 'persistence' | 'scheduling' | 'license' | 'target' | 'when' | 'route' | 'autostart' | (string & {}); type GateReason = { ok: true; gate: GateName; detail?: Record; } | { ok: false; gate: GateName; code: GateCode; message: string; detail?: Record; }; interface EligibilityReport { tourId: string; willFire: boolean; reasons: GateReason[]; firstFailingGate: Extract | null; evaluatedAt: number; } interface DiagnosticContext { userContext?: Record; completedTours: readonly string[]; skippedTours: readonly string[]; schedule?: { from?: Date; to?: Date; }; route?: { current: string; matcher: string; mode: 'exact' | 'startsWith' | 'contains'; }; targetResolver?: (selector: string) => HTMLElement | null; } interface DiagnosticGate { /** Stable identifier, e.g. 'license', 'scheduling', 'audience'. */ id: string; /** Run synchronously OR async. May throw — orchestrator captures the error. */ evaluate: (ctx: DiagnosticContext) => GateReason | Promise; } /** * Dev-mode test bridge — published on `window.__tourKit__` ONLY when a * `` is mounted. Mirrors the existing * imperative ref so Playwright (and other out-of-process drivers) can move * a tour forward without re-deriving controller boilerplate. * * The bridge is intentionally read-mostly: every control verb already exists * on the provider. Production builds tree-shake it away because * `enableTestBridge` defaults to `false` and the effect short-circuits at * the top. */ interface TestBridge { /** Programmatically start a tour by id. */ start: (tourId: string) => void; /** Advance to the next step in the active tour. */ next: () => void; /** Go back one step in the active tour. */ previous: () => void; /** Jump to a specific step by id in the active tour. */ goToStep: (stepId: string) => void; /** Mark the active tour completed. */ complete: () => void; /** Skip the active tour. */ skip: () => void; /** * Read the diagnostic for a registered tour. Returns `null` when * `diagnose` is `false` (the default) or when no tour with the given id * is registered. */ getDiagnostic: (tourId: string) => EligibilityReport | null; } /** * Ambient augmentation publishing the dev-mode test bridge on `window`. * * The `?` is non-negotiable: production builds (and any provider that omits * `enableTestBridge`) MUST leave the global as `undefined`. Consumers should * always optional-chain through `window.__tourKit__?.` so a missing bridge * never crashes their tooling. * * @internal Wired by `` — never set directly. */ declare global { interface Window { __tourKit__?: TestBridge; } } /** * `LocalizedText` is the shared shape for any human-readable string that * may either be a literal (interpolated via `interpolate`) or a dictionary * key (resolved via `useT()`). The discriminated union narrows on * `typeof === 'string'`, so existing string-based consumers stay assignable * — adding the object branch is a pure widening. */ type LocalizedText = string | { key: string; }; /** * Type guard: true when `value` is the `{ key: string }` branch of * `LocalizedText`. Returns false for plain strings, `undefined`, `null`, * arrays, primitives, and objects without a `key` property. */ declare function isI18nKey(value: unknown): value is { key: string; }; /** * Audience targeting condition. * * Promoted from `@tour-kit/announcements` in Phase 1 of the UserGuiding parity * initiative. The shape is byte-identical to the previous announcements-local * declaration so the announcements re-export keeps the same TypeScript identity. */ interface AudienceCondition { /** Condition type */ type: 'user_property' | 'segment' | 'feature_flag' | 'custom'; /** Property or segment name (supports dot notation for nested keys) */ key: string; /** Comparison operator */ operator: 'equals' | 'not_equals' | 'contains' | 'not_contains' | 'in' | 'not_in' | 'exists' | 'not_exists'; /** Expected value(s) — required for every operator except `exists`/`not_exists` */ value?: unknown; } type TourTargetRef = React$1.RefObject; type TourTargetGetter = () => HTMLElement | null; /** * Three accepted shapes for `target` on a `TourStep` / `HintConfig`: * * - selector string (legacy; runs `document.querySelector` at resolve time) * - `RefObject` (recommended; survives portals, CSS modules, dynamic ids) * - getter function (escape hatch for lazily-mounted DOM) * * Backwards-compat: string form is documented as fallback only and emits NO * dev warning. Existing string selectors continue to resolve through * `document.querySelector` unchanged. */ type TourTarget = string | TourTargetRef | TourTargetGetter; /** * Resolve a `TourTarget` to a live `HTMLElement` (or `null` when missing). * * Branch order (closed, non-overlapping): * * 1. `typeof t === 'string'` → `document.querySelector(t)` * 2. `t && typeof t === 'object' && 'current' in t` → `t.current` * 3. `typeof t === 'function'` → `t()` * * Strings can't carry `.current`, refs are objects with `.current`, thunks are * callables — no branch overlaps with another. Returns `HTMLElement | null`, * never `undefined`. * * SSR-safe: when `document` is not defined (Next.js RSC, Remix server render), * the string branch returns `null` without throwing. */ declare function resolveTarget(t: TourTarget): HTMLElement | null; /** * Structural alias of `MediaSlotProps` from `@tour-kit/media`. Re-declared * inline here so `@tour-kit/core` does not take a (type-only or otherwise) * dependency on `@tour-kit/media` — core sits at the bottom of the dep graph. * * The shape MUST stay assignment-compatible with `MediaSlotProps`. Update both * sites if the public surface changes. */ interface TourStepMedia { src: string; type?: 'auto' | 'youtube' | 'vimeo' | 'loom' | 'wistia' | 'video' | 'gif' | 'lottie' | 'image'; poster?: string; aspectRatio?: '16/9' | '4/3' | '1/1' | '9/16' | '21/9' | 'auto'; className?: string; alt?: string; title?: string; autoplay?: boolean; loop?: boolean; muted?: boolean; } /** * Audience prop shape — discriminated by `Array.isArray()`. * * - Array branch: legacy inline conditions evaluated via `matchesAudience`. * - Object branch: named segment lookup via `useSegment` / `useSegments`. * * Adding the object branch is a pure widening — pre-Phase-3 consumers using * `audience: AudienceCondition[]` keep compiling unchanged. */ type AudienceProp = AudienceCondition[] | { segment: string; }; /** * Fields shared by both visible and hidden steps. Hidden steps run lifecycle * callbacks and branching without mounting any DOM — everything UI-rendering * lives on `VisibleTourStep` instead. */ interface BaseTourStep { id: TId; /** * Filter this step out for users who don't match. Accepts the legacy * `AudienceCondition[]` array (evaluated via `matchesAudience`) or the * `{ segment: 'name' }` object (resolved via `useSegments`). */ audience?: AudienceProp; route?: string; routeDelay?: number; /** Route matching mode (default: 'exact') */ routeMatch?: 'exact' | 'startsWith' | 'contains'; /** * How to handle navigation when the step's `route` differs from the current * route. * * - `'auto'` (default): provider calls `router.navigate(step.route)` and * awaits the target via `waitForStepTarget` before dispatching `GO_TO_STEP`. * On timeout, throws `TourRouteError({ code: 'TARGET_NOT_FOUND' })`. * - `'prompt'`: provider raises `onNavigationRequired` for a * `` UI; consumer drives the navigation. * - `'manual'`: provider does nothing; consumer must call * `useTourRoute().goToStepRoute()` explicitly. * * @default 'auto' */ routeChangeStrategy?: 'auto' | 'prompt' | 'manual'; when?: (context: TourCallbackContext) => boolean | Promise; waitForTarget?: boolean; waitTimeout?: number; onBeforeShow?: (context: TourCallbackContext) => boolean | undefined | Promise; /** Runs before the step mounts (visible) or auto-advances (hidden). */ onEnter?: (context: TourCallbackContext) => void | Promise; onShow?: (context: TourCallbackContext) => void; onBeforeHide?: (context: TourCallbackContext) => boolean | undefined | Promise; onHide?: (context: TourCallbackContext) => void; /** * Override the next navigation behavior * Determines where to go when the user clicks "Next" or the tour advances */ onNext?: Branch; /** * Override the previous navigation behavior * Determines where to go when the user clicks "Back" * Set to null to disable going back from this step */ onPrev?: Branch; /** * Named actions that can be triggered from step content * Use with useBranch().triggerAction() from your step components * * @example * ```tsx * onAction: { * 'select-developer': 'developer-path-step', * 'select-designer': 'designer-path-step', * 'skip-onboarding': 'complete' * } * ``` */ onAction?: Record; } /** * A step that mounts a tooltip / spotlight against a target element. * * `target` and `content` are required so authors get a compile-time error if * they forget either — `validateTour` enforces the same at runtime. */ interface VisibleTourStep extends BaseTourStep { /** @default 'visible' */ kind?: 'visible'; target: TourTarget; /** * Step title. Accepts a plain string (interpolated via `interpolate`), * a `{ key: string }` dictionary lookup (resolved via `useT()`), or any * `ReactNode` for arbitrary JSX. Strings without `{{var}}` tokens render * unchanged — the widening is back-compat-safe. */ title?: React__default.ReactNode | LocalizedText; /** * Optional short description rendered above `content`. i18n-friendly: * accepts string (interpolated) or `{ key }` (translated). */ description?: LocalizedText; content: React__default.ReactNode; /** * Optional media (video / GIF / Lottie / image) rendered above the step * description by ``. Auto-detects the embed provider via URL * pattern matching unless `type` is explicit. */ media?: TourStepMedia; placement?: Placement; offset?: [number, number]; showNavigation?: boolean; showClose?: boolean; showProgress?: boolean; className?: string; spotlightPadding?: number; spotlightRadius?: number; interactive?: boolean; advanceOn?: { event: 'click' | 'input' | 'custom'; selector?: string; handler?: () => boolean; }; } /** * A step that runs lifecycle callbacks and branching without rendering UI. * * Useful for trait-based forks (`onEnter` reads context, `onNext` returns a * branch) and completion gates. Authoring a hidden step with any UI field * (`target`, `content`, `title`, `placement`, `advanceOn`) fails at compile * time via `?: never`, mirroring the runtime check in `validateTour`. */ interface HiddenTourStep extends BaseTourStep { kind: 'hidden'; target?: never; content?: never; title?: never; placement?: never; advanceOn?: never; } /** * Single step in a tour — `VisibleTourStep | HiddenTourStep` discriminated by * the `kind` field. Hidden steps run lifecycle callbacks (`onEnter`, `onShow`) * and branching logic (`onNext`) without mounting a DOM element. * * `TId` defaults to `string` so existing call sites (`TourStep`, `TourStep[]`) * keep working unchanged. Authors who want compile-time step-id narrowing pass * a literal-string union: `TourStep<'welcome' | 'pricing'>`. The canonical * inference pattern is `[...] as const satisfies ReadonlyArray` * combined with `StepIdOf` (see below). */ type TourStep = VisibleTourStep | HiddenTourStep; type StepOptions = Omit, 'id'>; /** * Type guard narrowing a `TourStep` to the visible branch of the union. * Useful for `Array.find` / `filter` callbacks where TypeScript otherwise * keeps the result as the full union. */ declare function isVisibleStep(step: TourStep): step is VisibleTourStep; /** * Extract the literal-id union from a const-tuple of steps. * * @example * ```ts * const steps = [ * { id: 'welcome', target: '#a', content: 'a' }, * { id: 'pricing', target: '#b', content: 'b' }, * ] as const satisfies ReadonlyArray * * type Ids = StepIdOf // 'welcome' | 'pricing' * ``` */ type StepIdOf> = T[number]['id']; /** * Tour definition. * * `TStep` defaults to `TourStep` (which itself defaults `TId` to `string`), so * `Tour` with no generic args keeps accepting dynamic / server-fetched steps. * Pass `TourStep<'a' | 'b' | ...>` to narrow `steps[].id` and the `onStepChange` * step argument at compile time. `Tour>` is the explicit * widening escape hatch. */ interface Tour { id: string; steps: TStep[]; /** * Filter the entire tour for users who don't match. Same shape as * `TourStep.audience`. When the filter rejects, the tour is not registered * and `useTour().isActive` stays false. */ audience?: AudienceProp; autoStart?: boolean; startAt?: number; keyboard?: KeyboardConfig | boolean; spotlight?: SpotlightConfig | boolean; persistence?: PersistenceConfig | boolean; a11y?: A11yConfig; scroll?: ScrollConfig; /** * Tour-level eligibility predicate evaluated by the diagnostic engine * (`explainTour`'s `when` gate). Sync callbacks returning `false` flip the * tour to `WHEN_RETURNED_FALSE`; throwing callbacks are captured with the * error in `detail`. Async callbacks are NOT awaited in diagnostic mode — * the gate returns `ok: true` with a `detail.note` documenting that the * async path is evaluated at runtime instead. Step-level `when` (on * `TourStep`) is unaffected — it is owned by the runtime step pipeline. */ when?: () => boolean | Promise; onStart?: (context: TourCallbackContext) => void; onComplete?: (context: TourCallbackContext) => void; onSkip?: (context: TourCallbackContext) => void; onStepChange?: (step: TStep, index: number, context: TourCallbackContext) => void; /** * Called when a branch action is triggered from a step * @param stepId - The step where the action was triggered * @param actionId - The action ID that was triggered * @param target - The resolved branch target */ onBranchAction?: (stepId: string, actionId: string, target: BranchTarget) => void; /** * Called when branching to a different tour * @param toTourId - The tour being navigated to * @param fromStepId - The step where the branch occurred */ onTourBranch?: (toTourId: string, fromStepId: string) => void; } type TourOptions = Omit, 'id' | 'steps'>; /** * Current tour state */ interface TourState { tourId: string | null; isActive: boolean; currentStepIndex: number; currentStep: TStep | null; totalSteps: number; isLoading: boolean; isTransitioning: boolean; completedTours: string[]; skippedTours: string[]; /** List of step IDs visited in the current tour session */ visitedSteps: string[]; /** Map of step ID to number of times it has been visited */ stepVisitCount: Map; /** ID of the previous step (for branch context) */ previousStepId: string | null; } /** * Extended tour context data (passed to callbacks) */ interface TourCallbackContext extends TourState { tour: Tour | null; data: Record; } /** * Tour action methods. * * `goToStep` and `startTour`'s `stepId` are narrowed to `TStep['id']` when a * concrete step type is supplied, giving const-authored tours compile-time * misspelling errors. With the default `TStep = TourStep`, `id`/`stepId` widen * back to `string` — preserving every existing call site. */ interface TourActions { start: (tourId?: string, stepIndex?: number) => void; next: () => void; prev: () => void; goTo: (stepIndex: number) => void; skip: () => void; complete: () => void; stop: () => void; setDontShowAgain: (tourId: string, value: boolean) => void; reset: (tourId?: string) => void; setData: (key: string, value: unknown) => void; /** * Navigate directly to a step by its ID. * * The `id` parameter is narrowed to `TStep['id']` — pass a literal-union * step type (e.g., `TourStep<'welcome' | 'pricing'>`) to make misspellings * fail at compile time. */ goToStep: (stepId: TId) => Promise; /** * Start a different tour (for cross-tour branching). * * @param tourId - The tour to start * @param stepId - Optional step ID (narrowed to `TStep['id']`) or numeric index */ startTour: (tourId: string, stepId?: TId | number) => Promise; /** * Trigger a branch action defined in the current step's onAction * @param actionId - The action ID to trigger * @param payload - Optional data to pass to the branch resolver */ triggerBranchAction: (actionId: string, payload?: unknown) => Promise; } /** * Combined context value */ interface TourContextValue extends TourState, TourActions { tour: Tour | null; data: Record; /** * Per-tour diagnostic reports — populated only when the provider is mounted * with `diagnose={true}`. Each key is a registered tour id; the value is the * latest `EligibilityReport` produced by `explainTour`. `undefined` when * diagnostics are off (the default in production builds). */ diagnostics?: Record; } /** * Initial state */ declare const initialTourState: TourState; /** * Target for a branch action * * Can be: * - string: Step ID to navigate to * - number: Step index to navigate to * - 'next': Continue to next step (default behavior) * - 'prev': Go to previous step * - 'complete': Complete the tour * - 'skip': Skip/abort the tour * - 'restart': Restart the tour from beginning * - BranchToTour: Navigate to a different tour * - BranchSkip: Skip a number of steps * - BranchWait: Wait before proceeding * - null: Stay on current step (no navigation) */ type BranchTarget = string | number | 'next' | 'prev' | 'complete' | 'skip' | 'restart' | BranchToTour | BranchSkip | BranchWait | null; /** * Branch to a different tour */ interface BranchToTour { tour: string; step?: string | number; } /** * Skip a number of steps forward */ interface BranchSkip { skip: number; } /** * Wait before proceeding to next target */ interface BranchWait { wait: number; then?: BranchTarget; } /** * Extended context available to branch resolvers * * Extends TourCallbackContext with additional branch-specific properties: * - action/actionPayload for onAction branches * - setData function for storing data during branch resolution * * Note: visitedSteps, stepVisitCount, and previousStepId are inherited from TourState */ interface BranchContext extends TourCallbackContext { /** The action ID that triggered this branch (for onAction branches) */ action?: string; /** Payload passed with the action */ actionPayload?: unknown; /** Function to store data in the tour context */ setData: (key: string, value: unknown) => void; } /** * Function that resolves a branch target based on context * * Can return a BranchTarget synchronously or asynchronously. * Useful for: * - Conditional navigation based on user data * - API calls to determine next step * - Complex branching logic */ type BranchResolver = (context: BranchContext) => BranchTarget | Promise; /** * A branch definition: either a static target or a resolver function */ type Branch = BranchTarget | BranchResolver; /** * Return type for the useBranch hook */ interface UseBranchReturn { /** * Trigger a named action defined in the current step's onAction * @param actionId - The action ID to trigger * @param payload - Optional data to pass to the branch resolver */ triggerAction: (actionId: string, payload?: unknown) => Promise; /** * List of available action IDs for the current step */ availableActions: string[]; /** * Check if an action is available for the current step * @param actionId - The action ID to check */ hasAction: (actionId: string) => boolean; /** * Preview where an action would navigate without actually navigating * @param actionId - The action ID to preview * @param payload - Optional data to pass to the branch resolver * @returns The resolved target without navigating */ previewAction: (actionId: string, payload?: unknown) => Promise; } /** * Frequency rules — promoted from `@tour-kit/announcements` in Phase 3a of the * UserGuiding parity initiative. Re-exported from `@tour-kit/announcements` * with `@deprecated` JSDoc for backward compat. */ /** * How often a UI surface (announcement, hint, tour) can be shown. * * - `'once'` — show only when never viewed * - `'session'` — show until dismissed in the current session * - `'always'` — show every time conditions are met * - `{ type: 'times', count }` — show up to `count` times total * - `{ type: 'interval', days }` — re-show after `days` days have passed */ type FrequencyRule = 'once' | 'session' | 'always' | { type: 'times'; count: number; } | { type: 'interval'; days: number; }; /** * Minimal state shape needed by the frequency helpers. Both * `AnnouncementState` (announcements) and the per-hint frequency slice * satisfy this — keep this interface narrow so new consumers (tours, etc.) * do not have to invent new state fields. */ interface FrequencyState { viewCount: number; isDismissed: boolean; lastViewedAt: Date | null; } /** * Decide whether a surface can be shown right now given its persisted state * and the configured rule. `undefined` rule defaults to "always show". */ declare function canShowByFrequency(state: FrequencyState, rule: FrequencyRule | undefined): boolean; /** * Whether dismissal is permanent for the rule. `'once'` and `'session'` * suppress further shows after dismissal; `'always'` and the object rules * permit re-showing. */ declare function canShowAfterDismissal(rule: FrequencyRule | undefined): boolean; /** * Hard cap on lifetime views for the rule. `'once'` → 1; everything else is * effectively unbounded except `{ type: 'times', count }` which returns `count`. */ declare function getViewLimit(rule: FrequencyRule | undefined): number; type HotspotPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'center'; interface HintConfig { id: string; target: TourTarget; /** Optional title rendered above the content (Phase 3a). */ title?: LocalizedText; content: React__default.ReactNode | LocalizedText; position?: HotspotPosition; tooltipPlacement?: Placement; pulse?: boolean; autoShow?: boolean; persist?: boolean; /** * Filter this hint for users who don't match. Same shape as `Tour.audience`. * Phase 3a addition. */ audience?: AudienceProp; /** * How often this hint can be re-shown. Phase 3a addition. Persisted state * lives at `tourkit:hint:freq:` via the provider's storage adapter. */ frequency?: FrequencyRule; onClick?: () => void; onShow?: () => void; onDismiss?: () => void; } interface HintState { id: string; isOpen: boolean; isDismissed: boolean; } interface HintsState { hints: Map; activeHint: string | null; } interface HintsActions { registerHint: (id: string) => void; unregisterHint: (id: string) => void; showHint: (id: string) => void; hideHint: (id: string) => void; dismissHint: (id: string) => void; resetHint: (id: string) => void; resetAllHints: () => void; } interface HintsContextValue extends HintsState, HintsActions { } /** * Router adapter interface for multi-page tours. * Implement this for your routing library. * * Supported routers: * - Next.js App Router (next/navigation) * - Next.js Pages Router (next/router) * - React Router v6/v7 (react-router / react-router-dom) */ interface RouterAdapter { /** * Get current route/pathname * @returns Current pathname (e.g., '/dashboard') */ getCurrentRoute(): string; /** * Navigate to a route. * * Return types vary by implementation: * - Next.js App Router: void * - Next.js Pages Router: Promise * - React Router: void */ navigate(route: string): undefined | Promise; /** * Check if current route matches a pattern * @param pattern - Route pattern to match * @param mode - Matching mode (default: 'exact') */ matchRoute(pattern: string, mode?: 'exact' | 'startsWith' | 'contains'): boolean; /** * Subscribe to route changes. * Callback is invoked when the route changes. * @returns Cleanup function to unsubscribe */ onRouteChange(callback: (route: string) => void): () => void; } /** * Multi-page persistence configuration. * Extends base PersistenceConfig with route-specific options. */ interface MultiPagePersistenceConfig { /** Enable persistence */ enabled: boolean; /** Storage type */ storage?: 'localStorage' | 'sessionStorage' | 'memory'; /** Storage key prefix */ key?: string; /** Sync across browser tabs (localStorage only) */ syncTabs?: boolean; /** Expiry time in milliseconds (default: 24 hours) */ expiryMs?: number; /** * Active flow session — single tour at a time, scoped to one tab. * When set, a hard reload resumes the active tour at its persisted step. */ flowSession?: FlowSessionConfig; /** * Cross-tab pause/resume gate (BroadcastChannel-based). * Pauses the tour in this tab when another tab posts `tour:active`. */ crossTab?: CrossTabConfig; } interface TourKitContextValue { config: TourKitConfig; /** * Resolved text direction ('ltr' or 'rtl') * Automatically resolved from config.dir or document.dir */ direction: 'ltr' | 'rtl'; /** * Whether the current direction is RTL */ isRTL: boolean; onTourStart?: (tourId: string) => void; onTourComplete?: (tourId: string) => void; onTourSkip?: (tourId: string, stepIndex: number) => void; onStepView?: (tourId: string, stepId: string, stepIndex: number) => void; /** * Called when a branch action is triggered from a step * @param tourId - The current tour ID * @param stepId - The step where the action was triggered * @param actionId - The action ID that was triggered * @param target - The resolved branch target */ onBranchAction?: (tourId: string, stepId: string, actionId: string, target: BranchTarget) => void; /** * Called when branching to a different tour * @param fromTourId - The tour being navigated from * @param toTourId - The tour being navigated to * @param fromStepId - The step where the branch occurred */ onTourBranch?: (fromTourId: string, toTourId: string, fromStepId: string) => void; } declare const TourKitContext: React$1.Context; /** * Hook to access TourKit context * Provides access to global config, direction (RTL/LTR), and callbacks * * @throws Error if used outside of TourKitProvider */ declare function useTourKitContext(): TourKitContextValue; /** * Hook to access RTL direction information * Returns direction ('ltr' | 'rtl') and isRTL boolean */ declare function useDirection(): { direction: 'ltr' | 'rtl'; isRTL: boolean; }; interface TourKitProviderProps { children: React$1.ReactNode; config?: TourKitConfig; onTourStart?: (tourId: string) => void; onTourComplete?: (tourId: string) => void; onTourSkip?: (tourId: string, stepIndex: number) => void; onStepView?: (tourId: string, stepId: string, stepIndex: number) => void; } declare function TourKitProvider({ children, config, onTourStart, onTourComplete, onTourSkip, onStepView, }: TourKitProviderProps): react_jsx_runtime.JSX.Element; declare const TourContext: React$1.Context | null>; declare function useTourContext(): TourContextValue; declare function useTourContextOptional(): TourContextValue | null; /** * Cross-page navigation: post-navigate "wait for the target to mount on the * new route, or surface a typed failure". Thin wrapper over the existing * MutationObserver-based `waitForElement` — does NOT re-implement it. */ /** * Typed failure for cross-page tour navigation. * * Discriminated by `code`: * - `TARGET_NOT_FOUND`: target did not appear within the timeout on the new * route. Most common on auto-strategy steps when the target component is * gated by data fetching. * - `NAVIGATION_REJECTED`: router-level navigation refused (e.g. Pages Router * `router.push()` resolved with `false`). * - `TIMEOUT`: reserved for non-target-related timeouts. */ declare class TourRouteError extends Error { readonly code: 'TARGET_NOT_FOUND' | 'NAVIGATION_REJECTED' | 'TIMEOUT'; readonly route: string; readonly selector?: string; constructor(args: { code: TourRouteError['code']; route: string; selector?: string; message: string; }); } interface WaitForStepTargetOptions { /** Route the navigation just landed on — included in the error for diagnostics. */ route: string; /** Reject after this many ms (default: 3000). */ timeoutMs?: number; /** * Optional cancellation. When aborted, the wait rejects with * `Error('aborted')` — NOT a `TourRouteError`. Distinct semantics so * `onStepError` can stay scoped to "target missing" failures. */ signal?: AbortSignal; } /** * Resolve `step.target` after a route change. * * Behavior matrix: * - `target` is a string selector → defers to `waitForElement(selector, timeoutMs, signal)`. * On timeout, the underlying `Error` is rethrown as * `TourRouteError({ code: 'TARGET_NOT_FOUND' })`. * On abort, the underlying `Error('aborted')` is rethrown unchanged so * callers can distinguish user-initiated cancellation from missing targets. * - `target` is a `RefObject` already populated → resolves synchronously. * - `target` is a `RefObject` with `current === null` → throws * `TourRouteError({ code: 'TARGET_NOT_FOUND' })` immediately. There's no * selector to observe for. */ declare function waitForStepTarget(step: VisibleTourStep, opts: WaitForStepTargetOptions): Promise; interface TourProviderProps { children: React$1.ReactNode; tours?: Tour[]; /** Router adapter for multi-page tours */ router?: RouterAdapter; /** Persistence config for multi-page tours */ routePersistence?: MultiPagePersistenceConfig; /** Auto-navigate when step requires different route (default: true) */ autoNavigate?: boolean; /** Callback when navigation is needed but autoNavigate is false */ onNavigationRequired?: (route: string, stepId: string) => void; /** * Called when the active tour is paused by an external signal. * Currently the only `reason` is `'cross-tab'` (another tab posted * `tour:active` on the cross-tab `BroadcastChannel`). */ onTourPaused?: (tourId: string, reason: 'cross-tab') => void; /** * Called when a cross-page step fails — typically when the step's target * does not appear on the new route within `routeChangeStrategy: 'auto'`'s * 3000ms wait. The provider stops the tour after this fires. * * Aborts triggered by `STOP_TOUR` or unmount do NOT call this — those are * cooperative cancellation, not failures. */ onStepError?: (err: TourRouteError) => void; /** * Diagnostic mode — when `true`, the provider runs `explainTour` for every * registered tour and exposes the result via `useTourDiagnostic(tourId)`. * Defaults to `false`; the orchestrator tree-shakes out when unused. * * In `NODE_ENV !== 'production'` builds, leaving this off triggers a * one-time `console.warn` per provider mount nudging dev consumers toward * the better debug surface. */ diagnose?: boolean; /** * Extension gates (license, scheduling, custom) appended to the diagnostic * pipeline AFTER built-ins. Only consulted when `diagnose` is `true`. */ diagnosticGates?: DiagnosticGate[]; /** * Optional user context plumbed to diagnostic gates (audience filter, etc). * Only read when `diagnose` is `true`. */ userContext?: Record; /** * Dev-only test bridge — when `true`, sets `window.__tourKit__` to a * `TestBridge` exposing the imperative controls (mirrors the existing * ref) so Playwright/E2E drivers can advance a tour from outside React. * * Defaults to `false`. Production builds MUST never expose this — wrap * in a `process.env.NODE_ENV !== 'production'` guard at the call site. * Tree-shakes when the prop is a literal `false`. */ enableTestBridge?: boolean; } declare function TourProvider({ children, tours, router, routePersistence, autoNavigate, onNavigationRequired, onTourPaused, onStepError, diagnose, diagnosticGates, userContext, enableTestBridge, }: TourProviderProps): react_jsx_runtime.JSX.Element; /** * Return shape of `useTour()`. * * `goToStep` and `startTour` are surfaced at the top level (no `.actions.` * prefix) and accept step ids narrowed to `TStep['id']`. When `TStep` defaults * to `TourStep`, the id parameter widens to `string` — preserving every * existing call site that writes `useTour().start(...)` / `useTour().next(...)`. */ interface UseTourReturn { isActive: boolean; isLoading: boolean; isTransitioning: boolean; currentStep: TStep | null; currentStepIndex: number; totalSteps: number; isFirstStep: boolean; isLastStep: boolean; progress: number; start: (tourIdOrStepIndex?: string | number, stepIndex?: number) => void; next: () => void; prev: () => void; goTo: (stepIndex: number) => void; skip: () => void; complete: () => void; stop: () => void; /** * Navigate directly to a step by its ID. Narrowed to `TStep['id']` when a * concrete step type is supplied — misspellings fail at compile time. */ goToStep: (stepId: TId) => Promise; /** * Start a different tour by ID. `stepId` is narrowed to that tour's step * ids when `TStep` is specified, or widens to `string | number` by default. */ startTour: (tourId: string, stepId?: TId | number) => Promise; isStepActive: (stepId: TStep['id']) => boolean; getStep: (stepId: TStep['id']) => TStep | undefined; } declare function useTour(tourId?: string): UseTourReturn; interface UseStepReturn { isActive: boolean; isVisible: boolean; hasCompleted: boolean; targetElement: HTMLElement | null; targetRect: DOMRect | null; show: () => void; hide: () => void; complete: () => void; } declare function useStep(stepId: string): UseStepReturn; interface UseSpotlightReturn { isVisible: boolean; targetRect: DOMRect | null; overlayStyle: React.CSSProperties; cutoutStyle: React.CSSProperties; show: (target: HTMLElement, config?: SpotlightConfig) => void; hide: () => void; update: () => void; } declare function useSpotlight(): UseSpotlightReturn; interface ElementPositionResult { element: HTMLElement | null; rect: DOMRect | null; scrollParent: HTMLElement | Window | null; update: () => void; } /** * Subscribe to a target's position. Accepts every `TourTarget` shape (string * selector, `RefObject`, getter) plus a direct `HTMLElement` for callers that * already hold the resolved node. Resolution flows through `getElement`, which * delegates the union branches to `resolveTarget`. */ declare function useElementPosition(target: TourTarget | HTMLElement | null): ElementPositionResult; declare function useKeyboardNavigation(config?: KeyboardConfig): void; interface UseFocusTrapOptions { /** * When `true`, sibling content outside the trapped container is marked * `inert` + `aria-hidden="true"` while the trap is active, giving true modal * semantics (`aria-modal="true"`). The subtree containing the trapped * container (e.g. its portal root) is left interactive. Attributes are * restored to their previous values on deactivate/unmount. * * Default: `false` (focus is trapped, but the background stays perceivable — * appropriate for non-modal dialogs). */ inertBackground?: boolean; } interface UseFocusTrapReturn { containerRef: React.RefObject; activate: () => void; deactivate: () => void; } declare function useFocusTrap(enabled?: boolean, options?: UseFocusTrapOptions): UseFocusTrapReturn; interface UsePersistenceReturn { getCompletedTours: () => string[]; getSkippedTours: () => string[]; getDontShowAgain: (tourId: string) => boolean; getLastStep: (tourId: string) => number | null; markCompleted: (tourId: string) => void; markSkipped: (tourId: string) => void; setDontShowAgain: (tourId: string, value: boolean) => void; saveStep: (tourId: string, stepIndex: number) => void; reset: (tourId?: string) => void; } declare function usePersistence(config?: PersistenceConfig): UsePersistenceReturn; /** * SSR-safe media query hook. * * Uses `useSyncExternalStore` so the server snapshot (`false`) is also used for * the first client (hydration) render, then React re-renders with the real * `matchMedia` value after hydration. This guarantees the first client render * matches the server output — no hydration mismatch — without reading * `matchMedia` in a render-time initializer. */ declare function useMediaQuery(query: string): boolean; declare function usePrefersReducedMotion(): boolean; /** * SSR-safe wrapper around `usePrefersReducedMotion` that defaults to `true` * server-side and on first client render (Comeau pattern), then flips to the * actual `matchMedia` value after the first effect. * * Why: animation classes that depend on this hook must default to "no * animation" during SSR/first paint to avoid a one-frame motion flash for * users who have requested reduced motion. */ declare function useReducedMotion(): boolean; interface PersistedRouteState { tourId: string | null; stepIndex: number; completedTours: string[]; skippedTours: string[]; timestamp: number; } interface UseRoutePersistenceReturn { /** Save current tour state */ save: (state: Partial) => void; /** Load persisted state */ load: () => PersistedRouteState | null; /** Clear persisted state */ clear: () => void; /** Check if state is stale */ isStale: () => boolean; /** * Increments whenever another tab writes to the same storage key while * `syncTabs` is enabled. Subscribe to this value from a `useEffect` to * re-hydrate state in response to cross-tab writes. */ externalVersion: number; } /** * Hook for persisting tour state across page navigations. * Extends base persistence with route-specific handling. * * @remarks * Uses existing storage utilities from @tour-kit/core. * Handles SSR gracefully by checking for window availability. */ declare function useRoutePersistence(config: MultiPagePersistenceConfig): UseRoutePersistenceReturn; interface FlowSessionV2 { schemaVersion: 2; tourId: string; stepIndex: number; /** * Route the active tour was on at the last save. Used by the provider to * resume on the correct URL after a hard refresh on cross-page tours. * `undefined` for V1 → V2 migrated blobs and for tours without a router. */ currentRoute?: string; /** epoch ms */ startedAt: number; /** epoch ms */ lastUpdatedAt: number; } interface UseFlowSessionReturn { session: FlowSessionV2 | null; /** * Persist the active step. `currentRoute` is included so a hard-refresh * during a multi-page tour resumes on the right URL — pass * `router?.getCurrentRoute()` from the provider, or `undefined` for * single-route tours. */ save: (stepIndex: number, currentRoute?: string) => void; clear: () => void; isStale: boolean; /** * `true` once the post-mount storage read has run (immediately when the * hook is disabled). `session` is `null` until then — consumers that give * the flow session precedence over other restore paths must wait for * `ready` instead of treating the initial `null` as "no session". */ ready: boolean; } interface UseFlowSessionConfig extends FlowSessionConfig { /** Storage key prefix (default: `tourkit`). Full key is `${keyPrefix}:flow:active`. */ keyPrefix?: string; } /** * Persist the active tour's session so a hard reload resumes it in place. * * Uses a single fixed storage key (`${keyPrefix}:flow:active` by default) — the * blob itself carries the `tourId`, so on a fresh mount the hook can discover * which tour was active without the caller needing to know it up front. * * The `tourId` argument identifies the tour for which `save()` writes new * snapshots; pass `''` to disable writes (loads still work). * * Throttle: `save()` is trailing-edge throttled at 200ms via the existing * `throttleTime` util — a burst of step changes coalesces into 1 storage write * per window. * * SSR-safe: returns no-op shape when `window` is undefined. * Quota-safe: `setItem` failures (`QuotaExceededError`) are logged and swallowed. */ declare function useFlowSession(tourId: string, config?: UseFlowSessionConfig): UseFlowSessionReturn; interface UseBroadcastReturn { /** Post a message to all other tabs subscribing to the same channel name. */ post: (msg: TMsg) => void; /** * Subscribe to messages on the channel. * @returns Cleanup function — call on unmount to remove the listener. */ subscribe: (handler: (msg: TMsg) => void) => () => void; } /** * Typed wrapper around `BroadcastChannel` for cross-tab pub/sub. * * Lazy-initializes the channel and closes it on unmount. When the runtime * does not provide `BroadcastChannel` (e.g. older Safari) or when * `options.enabled === false`, both `post` and `subscribe` are no-ops so * consumers do not need to branch. * * Self-message filtering is the consumer's responsibility — attach a * `tabId` to your messages and ignore matching ones. * * @typeParam TMsg - Discriminated union of message shapes for this channel. */ declare function useBroadcast(channelName: string, options?: { enabled?: boolean; }): UseBroadcastReturn; interface UseAdvanceOnOptions { /** Enable/disable the advanceOn behavior (default: true) */ enabled?: boolean; } /** * Hook to handle advanceOn behavior - automatically advances tour when * user performs specified action on target element */ declare function useAdvanceOn(options?: UseAdvanceOnOptions): void; /** * Dispatch a custom advance event (for 'custom' event type) * This allows programmatic advancement when the step has advanceOn: { event: 'custom' } * * @param selector - Optional CSS selector for the target element (defaults to document) */ declare function dispatchAdvanceEvent(selector?: string): void; /** * Hook for triggering branch actions from step content * * @example * ```tsx * function RoleSelectStep() { * const { triggerAction, hasAction } = useBranch() * * return ( *
*

What's your role?

* * *
* ) * } * ``` * * @throws Error if used outside of TourProvider */ declare function useBranch(): UseBranchReturn; /** * Safely get an element from various target shapes. * * Accepts the full `TourTarget` union (string selector, `RefObject`, or * getter function), plus the legacy direct `HTMLElement` passthrough used by * internal callers that already hold the resolved node. Selector / ref / getter * branches delegate to `resolveTarget` so the resolver stays the single source * of truth. */ declare function getElement(target: TourTarget | HTMLElement | null): HTMLElement | null; /** * Check if element is fully visible in viewport */ declare function isElementVisible(element: HTMLElement): boolean; /** * Check if element is at least partially visible */ declare function isElementPartiallyVisible(element: HTMLElement): boolean; /** * Wait for element to appear in DOM. * * @param selector - CSS selector to query against `document` * @param timeout - Reject after this many ms (default 5000) * @param signal - Optional `AbortSignal`. When aborted, the observer is * disconnected and the promise rejects with `Error('aborted')`. If the * signal is already aborted when called, rejects synchronously without * ever attaching the observer. */ declare function waitForElement(selector: string, timeout?: number, signal?: AbortSignal): Promise; /** * Get all focusable elements within a container * * Uses getComputedStyle rather than offsetParent so `position: fixed` * descendants (which have a null offsetParent) are still included. */ declare function getFocusableElements(container: HTMLElement): HTMLElement[]; /** * Find the scrollable parent of an element */ declare function getScrollParent(element: HTMLElement): HTMLElement | Window; /** * Detect document direction */ declare function getDocumentDirection(): 'ltr' | 'rtl'; /** * Mirror a side for RTL */ declare function mirrorSide(side: Side, isRTL: boolean): Side; /** * Mirror an alignment for RTL */ declare function mirrorAlignment(alignment: Alignment, isRTL: boolean): Alignment; /** * Mirror a placement for RTL layout * - left ↔ right * - start ↔ end */ declare function mirrorPlacementForRTL(placement: Placement, isRTL: boolean): Placement; /** * Get element's position including scroll offset */ declare function getElementRect(element: HTMLElement): Rect; /** * Get current viewport dimensions */ declare function getViewportDimensions(): { width: number; height: number; }; /** * Parse placement string into side and alignment */ declare function parsePlacement(placement: Placement): { side: Side; alignment: Alignment; }; /** * Get the opposite side */ declare function getOppositeSide(side: Side): Side; /** * Scroll element into view with configuration */ declare function scrollIntoView(element: HTMLElement, config?: ScrollConfig): Promise; /** * Scroll to specific position */ declare function scrollTo(container: HTMLElement | Window, position: { top?: number; left?: number; }, behavior?: ScrollBehavior): void; /** * Get current scroll position */ declare function getScrollPosition(container?: HTMLElement | Window): { x: number; y: number; }; /** * Lock body scroll and return unlock function. * * Ref-counted: nested calls share a single lock. The saved scroll position * is captured once (on the outermost call) and restored once (when the * outermost unlock runs). */ declare function lockScroll(): () => void; /** * Create storage adapter from config */ declare function createStorageAdapter(storageType: PersistenceConfig['storage']): Storage; /** * No-op storage for SSR */ declare function createNoopStorage(): Storage; /** * Cookie-based storage adapter */ declare function createCookieStorage(options?: { expires?: number; path?: string; }): Storage; /** * Safe JSON parse with fallback */ declare function safeJSONParse(value: string | null, fallback: T): T; /** * Create storage with key prefix */ declare function createPrefixedStorage(storage: Storage, prefix: string): Storage; /** * Closure-backed in-memory implementation of the DOM `Storage` shape. Used * by `useRoutePersistence` and `useChecklistPersistence` as the SSR / private- * browsing fallback when `window.localStorage` is unavailable. * * Returns the full DOM shape (including `length` and `key(index)`) because * existing call sites read those properties — promoted from the * `_data` cast hack in `useChecklistPersistence` and the closure version in * `useRoutePersistence`, deduplicated in Phase 1 of the refactor train. * * Each call returns an isolated store — instances do NOT share state. */ declare function createMemoryStorage(): globalThis.Storage; /** * Announce message to screen readers */ declare function announce(message: string, politeness?: 'polite' | 'assertive'): void; /** * Generate unique ID for accessibility */ declare function generateId(prefix?: string): string; /** * Check if user prefers reduced motion */ declare function prefersReducedMotion(): boolean; /** * Generate step announcement for screen readers */ declare function getStepAnnouncement(stepTitle: string | undefined, currentStep: number, totalSteps: number): string; /** * Create a tour with auto-generated ID */ declare function createTour(steps: TourStep[], options?: TourOptions): Tour; /** * Create a tour with explicit ID */ declare function createNamedTour(id: string, steps: TourStep[], options?: TourOptions): Tour; /** * Create a visible step with auto-generated ID. `target` and `content` are * required — hidden steps should be authored as object literals because they * don't share this surface. */ declare function createStep(target: VisibleTourStep['target'], content: VisibleTourStep['content'], options?: Partial): VisibleTourStep; /** * Create a visible step with an explicit ID. */ declare function createNamedStep(id: string, target: VisibleTourStep['target'], content: VisibleTourStep['content'], options?: Partial): VisibleTourStep; /** * Configurable logger utility for tour-kit * * Provides environment-aware logging with configurable levels. * In production, only errors are logged by default. * In development, warnings and errors are logged. * * @example * ```ts * import { logger } from '@tour-kit/core' * * // Configure logging level * logger.configure({ level: 'debug' }) * * // Use logger * logger.debug('Debug info:', data) * logger.warn('Warning:', message) * logger.error('Error:', error) * ``` */ type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent'; interface LoggerConfig { level: LogLevel; prefix?: string; } declare const logger: { /** * Configure the logger */ configure: (newConfig: Partial) => void; /** * Get current logger configuration */ getConfig: () => Readonly; /** * Log debug messages (only in development with level='debug') */ debug: (...args: unknown[]) => void; /** * Log info messages */ info: (...args: unknown[]) => void; /** * Log warning messages */ warn: (...args: unknown[]) => void; /** * Log error messages */ error: (...args: unknown[]) => void; }; /** * Maximum depth for recursive branch resolution * Prevents infinite loops from circular branch references */ declare const MAX_BRANCH_DEPTH = 50; /** * Type guard: Check if target is a BranchToTour object */ declare function isBranchToTour(target: BranchTarget): target is BranchToTour; /** * Type guard: Check if target is a BranchSkip object */ declare function isBranchSkip(target: BranchTarget): target is BranchSkip; /** * Type guard: Check if target is a BranchWait object */ declare function isBranchWait(target: BranchTarget): target is BranchWait; /** * Check if target is a special navigation keyword */ declare function isSpecialTarget(target: BranchTarget): target is 'next' | 'prev' | 'complete' | 'skip' | 'restart'; /** * Check if a branch is a resolver function */ declare function isBranchResolver(branch: Branch): branch is (context: BranchContext) => BranchTarget | Promise; /** * Resolve a branch to its target * * Handles both static targets and resolver functions. * Includes depth tracking to prevent infinite loops. * * @param branch - The branch to resolve (target or resolver) * @param context - The branch context for resolver functions * @param depth - Current resolution depth (for loop detection) * @returns The resolved branch target */ declare function resolveBranch(branch: Branch, context: BranchContext, depth?: number): Promise; /** * Convert a branch target to a step index * * @param target - The branch target to convert * @param currentIndex - Current step index * @param stepIdMap - Map of step ID to index * @param totalSteps - Total number of steps in the tour * @returns Step index, or null for special targets that don't map to an index */ declare function resolveTargetToIndex(target: BranchTarget, currentIndex: number, stepIdMap: Map, totalSteps: number): number | null; /** * Check if a step has been visited too many times (potential loop) * * @param stepId - The step ID to check * @param visitCount - Map of step ID to visit count * @param maxVisits - Maximum allowed visits (default: 10) * @returns true if the step has been visited too many times */ declare function isLoopDetected(stepId: string, visitCount: Map, maxVisits?: number): boolean; /** * Throttle utilities for performance optimization * @module throttle */ type AnyFunction = (...args: unknown[]) => void; interface ThrottledFunction { (...args: Parameters): void; /** Cancel any pending execution */ cancel: () => void; } interface ThrottledFunctionWithFlush extends ThrottledFunction { /** Execute pending call immediately */ flush: () => void; } /** * RAF-based throttling for smooth 60fps updates during scroll/resize. * Coalesces rapid calls to a single execution per animation frame. * * @example * ```ts * const throttledUpdate = throttleRAF(() => { * element.getBoundingClientRect() * }) * * window.addEventListener('scroll', throttledUpdate, { passive: true }) * * // Cleanup * throttledUpdate.cancel() * ``` */ declare function throttleRAF(callback: T): ThrottledFunction; /** * Time-based throttling with trailing edge execution. * Queues the most recent call and executes after the interval. * Supports flush for immediate execution of pending call. * * @example * ```ts * const throttled = throttleTime(sendAnalytics, 1000) * * // Call multiple times - only fires once per second * throttled(event1) * throttled(event2) * * // Force immediate execution of pending call * throttled.flush() * ``` */ declare function throttleTime(callback: T, ms: number): ThrottledFunctionWithFlush; /** * Leading-edge throttle - fires immediately, then ignores calls for interval. * Useful for immediate user feedback while preventing rapid-fire events. * * @example * ```ts * const throttled = throttleLeading(onClick, 500) * * // First call fires immediately * // Subsequent calls within 500ms are ignored * throttled() // Executes * throttled() // Ignored * throttled() // Ignored * ``` */ declare function throttleLeading(callback: T, ms: number): ThrottledFunction; /** * Merge class names with Tailwind CSS classes * Compatible with shadcn/ui cn() utility */ declare function cn(...inputs: ClassValue[]): string; type TourValidationCode = 'INVALID_HIDDEN_STEP' | 'HIDDEN_STEP_LOOP'; /** * Thrown by `validateTour` (config-time) and by the provider's hidden-step * loop guard (runtime). Catch-site filtering uses the readonly `code` field. */ declare class TourValidationError extends Error { readonly code: TourValidationCode; readonly stepId: string; constructor(args: { code: TourValidationCode; stepId: string; message: string; }); } /** * Validate a Tour at provider mount. * * Currently enforces: hidden steps must not declare UI fields. Throws a * `TourValidationError` whose message names the offending step id and field. * * Phase 3 (refactor train) — TS now rejects authored hidden steps with * `target`/`content`/`title`/`placement`/`advanceOn` via `?: never` on * `HiddenTourStep`, so this runtime pass exists to protect untyped JSON / * `as any` boundaries. The fields are typed as `never` on the narrowed * branch, so reading them returns `undefined` without needing a cast. */ declare function validateTour(tour: Tour): void; type RenderProp = (props: Record) => ReactElement; interface UnifiedSlotProps { children: ReactNode | RenderProp; [key: string]: unknown; } /** * Unified Slot component that works with both Radix UI and Base UI. * * For Radix UI: clones element with merged props (className concat, * style merge, on* handler compose, ref merge). * For Base UI: calls `children` as a render prop with the merged props. * * Wrapped in `React.forwardRef` so the `ref` prop reaches this component * on both React 18 (where `ref` is stripped from `props` by createElement) * and React 19 (where it's a regular prop). */ declare const UnifiedSlot: React$1.ForwardRefExoticComponent & React$1.RefAttributes>; type UILibrary = 'radix-ui' | 'base-ui'; interface UILibraryProviderProps { library?: UILibrary; children: React.ReactNode; } /** * Provider component that allows users to choose their preferred UI library. * Defaults to 'radix-ui' for backward compatibility. * * @example * ```tsx * * * * ``` */ declare function UILibraryProvider({ library, children }: UILibraryProviderProps): react_jsx_runtime.JSX.Element; /** * Hook to get the current UI library setting * * @returns The current UI library ('radix-ui' or 'base-ui') */ declare function useUILibrary(): UILibrary; interface InterpolateOptions { /** Returned when a key is missing AND no inline fallback was given. Default: '' */ defaultFallback?: string; /** Logs unresolved keys to console.warn in dev. Default: true */ warnOnMissing?: boolean; } declare function interpolate(template: string, vars: Record | undefined, opts?: InterpolateOptions): string; type Messages = Record; type TranslateFn = (key: string, vars?: Record) => string; /** * Hook returning a translator function `t(key, vars?)` that: * * 1. Delegates to `LocaleContextValue.t` when a consumer adapter is supplied * (host i18n libraries like next-intl take precedence over the built-in path). * 2. Looks up `messages[key]`. Falls back to the key itself in dev (visible * breadcrumb) and to an empty string in production. * 3. Resolves any `{count, plural, …}` block via `Intl.PluralRules`. * 4. Substitutes `{{var}}` tokens via `interpolate`. */ declare function useT(): TranslateFn; interface LocaleContextValue { locale: string; messages: Messages; /** Optional consumer-side override — delegates t() to host app's i18n */ t?: TranslateFn; /** Auto-derived from locale; consumers can override */ direction?: 'ltr' | 'rtl'; } interface LocaleProviderProps extends Partial { children: ReactNode; } declare function LocaleProvider({ locale, messages, t, direction, children, }: LocaleProviderProps): react_jsx_runtime.JSX.Element; declare function useLocale(): LocaleContextValue; /** * Promoted from `@tour-kit/announcements` in Phase 1 of the UserGuiding parity * initiative. Re-exported there for backward compat — see `packages/announcements/src/core/audience.ts`. */ /** * Type guard narrowing `AudienceProp` to its segment-named branch. Previously * duplicated in `@tour-kit/react`, `@tour-kit/hints`, and `@tour-kit/announcements`. */ declare function isSegmentAudience(audience: AudienceProp): audience is { segment: string; }; /** * Pure boolean test: does the current user satisfy this audience? Single * source of truth shared by `useStepFilter` (react), `useHintFilter` (hints), * and the segment branch of `evaluateAnnouncementAudience` (announcements). * * - `undefined` audience → `true` (no filter) * - segment-shape `{ segment: 'x' }` → reads `segments[x] === true`; warns * once per unknown segment in dev, naming the calling hook. * - array-shape `AudienceCondition[]` → delegates to `matchesAudience` with * `userContext` (the legacy contract). */ declare function evaluateAudience(audience: AudienceProp | undefined, segments: Record, userContext: Record | undefined, caller: string): boolean; /** * Check if user context matches audience conditions */ declare function matchesAudience(conditions: AudienceCondition[] | undefined, userContext: Record | undefined): boolean; /** * Structured sibling of `matchesAudience` consumed by the diagnostic engine. * Returns a `GateReason` describing the audience-gate outcome — including the * first failing condition (or the failing segment) in `detail` so operators * see WHY the audience filter rejected the user. * * Segment-form audience (`{ segment: 'admins' }`) looks up * `userContext.segments` (a `string[]` listing the user's resolved segments). * If the segment is missing or absent, the gate fails with `AUDIENCE_MISMATCH`. */ declare function explainAudience(audience: AudienceProp | undefined, userContext: Record | undefined): GateReason; /** * Validate audience conditions */ declare function validateConditions(conditions: AudienceCondition[]): string[]; /** * Diagnostic engine — Phase 3. * * Wraps every reason a tour might not fire into a single structured * `EligibilityReport`. Runs seven built-in gates in a fixed order, then any * extension gates registered via ``. NEVER * throws: internal errors are captured as `ok: false` reasons. */ /** * Canonical built-in evaluation order. Tests pin this tuple — the order is * part of the contract that consumers (and ``) rely on. */ declare const BUILTIN_GATE_ORDER: readonly ["structure", "audience", "persistence", "route", "target", "when", "autostart"]; /** * Evaluate every built-in gate (in `BUILTIN_GATE_ORDER`) and any extension * gates (in registration order), then return the aggregated report. * * Built-in gates are sync; extension gates may be async. NEVER throws — a * throwing extension yields a synthetic `{ ok: false, code: '${ID}_THREW' }`. * A failing `structure` gate short-circuits the rest of the pipeline: nothing * else matters when the tour shape is invalid. */ declare function explainTour(tour: Tour, ctx: DiagnosticContext, extensions?: DiagnosticGate[]): Promise; /** * Read the diagnostic report for a given tour id. Returns `null` when the * provider is mounted without `diagnose={true}`, when the tour is unknown, * or before the first diagnostic effect tick has resolved. * * Throws if called outside a `` — the diagnostic surface is * useless without context plumbing, so make the misuse loud. */ declare function useTourDiagnostic(tourId: string): EligibilityReport | null; /** * Returns a memoized resolver `(value) => string` that takes a `LocalizedText` * (or `undefined`) and returns the rendered string. Intended for any component * that surfaces consumer-authored copy where the output must be a `string` * (e.g. `aria-label`, `title` attributes, `Dialog.Title` props). * * const resolveText = useResolveLocalizedText() * const title = resolveText(task.config.title) * * For `ReactNode` pass-through use `useResolvedText` (per-package hook in * `@tour-kit/react` / `@tour-kit/hints`) — it returns `ReactNode` so consumers * can pass JSX through arbitrary `LocalizedText | ReactNode` fields. * * **Vars source:** `useSegmentationContext().userContext`. Consumers who pass * `userContext` to `` for audience targeting get * automatic interpolation against the same data with no extra plumbing. * * **Without any provider:** the default segmentation context has * `userContext: undefined`, so plain templates fall back to `{{x | default}}` * rules and keyed values resolve to the key itself in dev / empty in prod — * the same contract as `useT()`. */ declare function useResolveLocalizedText(): (value: LocalizedText | undefined) => string; /** * Resolve a `LocalizedText | ReactNode` value into a `ReactNode`. Drives the * Phase 3a unified text pipeline (promoted to core in Phase 1 of the refactor * train — previously duplicated in `@tour-kit/react`, `@tour-kit/hints`, and * `@tour-kit/announcements`). * * - `string` → `interpolate(value, vars)` (templated literal) * - `{ key }` → `useT()(value.key, vars)` (i18n dictionary) * - any other `ReactNode` → returned as-is * * `vars` defaults to `useSegmentationContext().userContext` so consumers * authoring `'Hi {{user.name}}'` get interpolation against the same context * driving audience targeting. * * **Hook, not function** — `useT()` requires React render context. Call from * a component body, never from an event handler or `.map()` callback. * * For string-only outputs (aria-label, title, Dialog.Title) use * `useResolveLocalizedText` instead — it returns `string` and skips the * ReactNode pass-through branch. */ declare function useResolvedText(value: React$1.ReactNode | LocalizedText | undefined, vars?: Record): React$1.ReactNode; /** * Return shape of `useTourActions(tourId)`. * * `useTourActions` reads from a module-level registry that every `` * and standalone `` populates on mount. The hook lets a sibling * subtree control a tour without prop drilling or window events. * * When the tour id is unknown (e.g., during a route transition before the tour * has mounted), `useTourActions` returns a module-level **frozen no-op** object * — every method is a silent no-op and the state slice reports `isActive: false`. * The frozen return shape lets call sites write `useTourActions(id).start()` * without optional chaining; the call quietly drops on the floor instead of * throwing. */ interface UseTourActionsReturn { /** True when the tour is registered AND currently the active tour. */ isActive: boolean; /** Current step id when the tour is active; `null` otherwise. */ currentStepId: string | null; /** `(currentStepIndex + 1) / totalSteps`, clamped to 0..1. */ progress: number; /** Start the tour from its `startAt` step (no-op if id is unknown). */ start: () => void; /** Stop the tour (no-op if id is unknown or the tour is not active). */ stop: () => void; /** Restart the tour from step 0, regardless of `startAt`. */ restart: () => void; /** Advance to the next step (only when this tour is active). */ next: () => void; /** Go to the previous step (only when this tour is active). */ prev: () => void; /** Jump to a specific step by id (only when this tour is active). */ goToStep: (stepId: string) => void; } /** * Read tour state and call imperative actions from anywhere in the React tree, * including siblings of the owning `` or `` component. * * Standalone `` self-registers at mount, and any sibling * can call `useTourActions('welcome').start()` to drive it. When the tour id * is unknown (typo, route transition, future mount), the hook returns a * frozen no-op object — calls silently drop rather than throw, so callers * don't need to wrap every action in optional chaining. * * @param tourId - Tour id matching a `` / `` entry */ declare function useTourActions(tourId: string): UseTourActionsReturn; /** * A condition-based segment. Multiple AudienceConditions in the array are * AND-joined by `matchesAudience()` (Phase 1). * * To express OR composition, register a second segment with the alternate * conditions and reference both names at the call site, OR use an audience * operator that already supports OR semantics (e.g. `operator: 'in'` against * an array of values). * * @example * const segments = { * admins: [ * { type: 'user_property', key: 'role', operator: 'equals', value: 'admin' }, * ], * 'eu-pro': [ * { type: 'user_property', key: 'country', operator: 'in', value: ['DE','FR','ES','IT'] }, * { type: 'user_property', key: 'plan', operator: 'equals', value: 'pro' }, * ], // AND-joined: EU country AND plan=pro * } */ type SegmentDefinition = AudienceCondition[]; /** * A static, CSV-loadable cohort of user IDs. Resolves to `true` when the * provider's `currentUserId` appears in the list. Returns `false` (without * throwing) when `currentUserId` is undefined, so anonymous-user code paths * stay stable. */ interface StaticSegment { type: 'static'; userIds: ReadonlyArray; } /** * Discriminated union for segment registration. Narrow with `Array.isArray(seg)`: * arrays are `SegmentDefinition`, objects are `StaticSegment`. */ type SegmentSource = SegmentDefinition | StaticSegment; interface SegmentationContextValue { segments: Record; userContext?: Record; currentUserId?: string; } interface SegmentationProviderProps extends SegmentationContextValue { children: ReactNode; } declare function SegmentationProvider({ children, segments, userContext, currentUserId, }: SegmentationProviderProps): react_jsx_runtime.JSX.Element; declare function useSegmentationContext(): SegmentationContextValue; /** * Resolve a named segment to a boolean for the current user. * * Composition rules: * - Inside one segment, conditions are AND-joined by `matchesAudience`. * - To express OR, register two named segments and reference both at the * call site, OR use an audience operator that supports OR semantics * (e.g. `operator: 'in'` against an array of values). * * Returns `false` (with a dev-only `console.warn`) for unknown names — never * throws — so a typo cannot crash the consumer tree. Static segments without * a `currentUserId` resolve to `false` (anonymous users by definition cannot * be in a closed cohort). An empty conditions array (`[]`) resolves to `true` * because `matchesAudience` treats "no conditions" as "match everyone". * * @example * * * * * useSegment('admins') // → true * useSegment('beta') // → true * useSegment('ghost') // → false + dev warn */ declare function useSegment(name: string): boolean; /** * Batch lookup: returns a `Record` keyed by every registered * segment name. Useful for debug overlays and analytics dispatch. * * Does NOT warn on unknown names — enumeration intentionally walks the * registered set, so by definition every key is known. */ declare function useSegments(): Record; /** * Parse a CSV string into a deduped array of trimmed user IDs. * * Spec (RFC 4180-lite — no nested CSV, no multi-line quoted fields): * - Strips a leading BOM (``). * - Splits on `\r\n` or `\n`. Empty lines are skipped (including a trailing one). * - Header detection: case-insensitive match on `id` / `user_id` / `userId` * in the first column → drops that row. Otherwise treats the first row as data. * - Quote-aware first-column parser: `"u,1"` preserves the comma; doubled * quotes `""` decode to a literal `"`. * - Multi-column rows: only the first column is taken. * - Result is trimmed and deduplicated, preserving first-seen order. * * Hand-rolled to keep `@tour-kit/core` under its bundle budget — adding * `papaparse` or `csv-parse` would bust the brotli-with-deps cap (24 KB * after Phase 2; measured 23.13 KB). * * @example * parseUserIdsFromCsv('id\nu_1\nu_2') // → ['u_1', 'u_2'] * parseUserIdsFromCsv('id\n"u,1"\nu_2') // → ['u,1', 'u_2'] * parseUserIdsFromCsv('id,email\nu_1,a@b.com') // → ['u_1'] */ declare function parseUserIdsFromCsv(csv: string): string[]; export { A11yConfig, Alignment, type AudienceCondition, type AudienceProp, BUILTIN_GATE_ORDER, type Branch, type BranchContext, type BranchResolver, type BranchSkip, type BranchTarget, type BranchToTour, type BranchWait, CrossTabConfig, type DiagnosticContext, type DiagnosticGate, type ElementPositionResult, type EligibilityReport, FlowSessionConfig, type FrequencyRule, type FrequencyState, type GateCode, type GateName, type GateReason, type HiddenTourStep, type HintConfig, type HintState, type HintsActions, type HintsContextValue, type HintsState, type HotspotPosition, type InterpolateOptions, KeyboardConfig, type LocaleContextValue, LocaleProvider, type LocaleProviderProps, type LocalizedText, type LogLevel, type LoggerConfig, MAX_BRANCH_DEPTH, type Messages, type MultiPagePersistenceConfig, PersistenceConfig, Placement, Rect, type RenderProp, type RouterAdapter, ScrollConfig, type SegmentDefinition, type SegmentSource, type SegmentationContextValue, SegmentationProvider, type SegmentationProviderProps, Side, SpotlightConfig, type StaticSegment, type StepIdOf, type StepOptions, Storage, type TestBridge, type ThrottledFunction, type ThrottledFunctionWithFlush, type Tour, type TourActions, type TourCallbackContext, TourContext, type TourContextValue, TourKitConfig, TourKitContext, type TourKitContextValue, TourKitProvider, type TourKitProviderProps, type TourOptions, TourProvider, type TourProviderProps, TourRouteError, type TourState, type TourStep, type TourStepMedia, type TourTarget, type TourTargetGetter, type TourTargetRef, TourValidationError, type TranslateFn, type UILibrary, UILibraryProvider, type UILibraryProviderProps, UnifiedSlot, type UnifiedSlotProps, type UseAdvanceOnOptions, type UseBranchReturn, type UseBroadcastReturn, type UseFlowSessionConfig, type UseFlowSessionReturn, type UseFocusTrapReturn, type UsePersistenceReturn, type UseRoutePersistenceReturn, type UseSpotlightReturn, type UseStepReturn, type UseTourActionsReturn, type UseTourReturn, type VisibleTourStep, type WaitForStepTargetOptions, announce, canShowAfterDismissal, canShowByFrequency, cn, createCookieStorage, createMemoryStorage, createNamedStep, createNamedTour, createNoopStorage, createPrefixedStorage, createStep, createStorageAdapter, createTour, dispatchAdvanceEvent, evaluateAudience, explainAudience, explainTour, generateId, getDocumentDirection, getElement, getElementRect, getFocusableElements, getOppositeSide, getScrollParent, getScrollPosition, getStepAnnouncement, getViewLimit, getViewportDimensions, initialTourState, interpolate, isBranchResolver, isBranchSkip, isBranchToTour, isBranchWait, isElementPartiallyVisible, isElementVisible, isI18nKey, isLoopDetected, isSegmentAudience, isSpecialTarget, isVisibleStep, lockScroll, logger, matchesAudience, mirrorAlignment, mirrorPlacementForRTL, mirrorSide, parsePlacement, parseUserIdsFromCsv, prefersReducedMotion, resolveBranch, resolveTarget, resolveTargetToIndex, safeJSONParse, scrollIntoView, scrollTo, throttleLeading, throttleRAF, throttleTime, useAdvanceOn, useBranch, useBroadcast, useDirection, useElementPosition, useFlowSession, useFocusTrap, useKeyboardNavigation, useLocale, useMediaQuery, usePersistence, usePrefersReducedMotion, useReducedMotion, useResolveLocalizedText, useResolvedText, useRoutePersistence, useSegment, useSegmentationContext, useSegments, useSpotlight, useStep, useT, useTour, useTourActions, useTourContext, useTourContextOptional, useTourDiagnostic, useTourKitContext, useUILibrary, validateConditions, validateTour, waitForElement, waitForStepTarget };