import type { ActiveStep } from "../runtime/active-step"; import type { TourDirection, TourEventSource } from "../types"; export interface TourViewCommands { advance(source: TourEventSource): Promise; canAdvance(): boolean; canCancel(): boolean; canPrevious(): boolean; goTo(id: string): Promise; isAdvanceDisabled(): boolean; isCancelDisabled(): boolean; isPreviousDisabled(): boolean; previous(source: TourEventSource): Promise; cancel(source: TourEventSource): Promise; reportError(error: unknown): Promise; targetDisconnected(target: HTMLElement): Promise; subscribeCapabilities?(listener: (active: boolean) => void): () => void; } export interface TourViewDriver { show(step: ActiveStep, direction: TourDirection, signal: AbortSignal, onBeforePopoverAppear?: () => void | Promise): Promise | void; clear(signal: AbortSignal): Promise | void; /** * Resumes a frozen presentation on `step.target` after its previous target * reconnected or was replaced, without unmounting or replaying `appear()`. * A no-op when the driver isn't frozen for this step — callers only invoke * it in response to a `targetDisconnected` notification they are recovering * from, so a stale or superseded call should be silently ignored rather * than throw. */ retarget(step: ActiveStep, signal: AbortSignal): Promise | void; /** * Reports that the navigation under way is waiting for the next step's * target to resolve. The presented step has not left yet, so its popover is * the one that carries the wait. */ setTargetPending?(pending: boolean): void; /** * Hides or shows the popover of the running tour. A step still entering reads it as it is * presented. Every `start()` shows it again first. */ setPopoverHidden?(hidden: boolean): void; dispose(): void; releaseMount?(): void; setCommands?(commands: TourViewCommands): void; } export declare class NoopTourViewDriver implements TourViewDriver { show(_step: ActiveStep, _direction: TourDirection, _signal: AbortSignal, onBeforePopoverAppear?: () => void | Promise): void | Promise | undefined; clear(_signal: AbortSignal): void; retarget(_step: ActiveStep, _signal: AbortSignal): void; dispose(): void; releaseMount(): void; } export declare class DomTourViewDriver implements TourViewDriver { private readonly focusGuard; private readonly scrollLock; private readonly modalToken; private readonly stepCleanups; private readonly targetCleanups; private commands; private direction; private currentStep; private currentSignal; private disposed; private generation; private active; /** * True while the presentation is held in place on a lost target: the * reposition loop is stopped and interaction is force-blocked, but overlay, * popover and pointer stay mounted at their last known position instead of * disappearing. Cleared by `retarget()` (target came back) or `clear()` * (the caller gave up and is tearing the presentation down). */ private frozen; /** * True between the spotlight's entrance and the popover's, the window in * which a step's scroll is still travelling. The tracking loop drives the * spotlight alone while it is set, and a lost target stops the tracking * instead of freezing the presentation. */ private awaitingStepUi; /** * True while the popover and pointer stand aside for a user scroll, until the * page has been still for `USER_SCROLL_IDLE_DELAY` and, when the target was * left off screen, the step has scrolled it back. The spotlight keeps * tracking the target throughout. */ private userScrolling; private scrollTimer?; private activeTarget; private targetFocusedAtFreeze; private lastTargetRect; private lastViewport; private inertBranches; private modalDocument; private modalRoot; private overlay; /** A command asked for while a visible popover was replaced, run once the new step is presented. */ private pendingCommand; private pointer; private pendingFocusGeneration; /** * The last click a presented step's button handled. It bubbles on to the window after the * command it ran started the next step, and must not be queued a second time there. */ private handledTriggerClick; private popover; private presentationDirty; /** True while the controller waits for the next step's target, see `setTargetPending`. */ private targetPending; /** True while the consumer hides the popover, see `setPopoverHidden`. */ private popoverHidden; /** * The focus a clear gives back once the popover has faded out. Kept past a clear that a new show * supersedes, so that tour returns focus there instead of to the fading popover. */ private focusToRestore; /** The `allowInteraction` value the overlay, the page modality and the focus guard reflect. */ private appliedAllowInteraction; /** Set when a live `allowInteraction` change started the pointer fade; the next frame clears it. */ private pointerFading; private rafId; private rafCancel; private root; private cancelScroll; constructor(commands?: TourViewCommands); setCommands(commands: TourViewCommands): void; registerRoot(element: HTMLElement | null): void; registerOverlay(element: SVGSVGElement | null): void; registerPopover(element: HTMLElement | null): void; registerPointer(element: HTMLElement | null): void; /** * Flags the presented popover as waiting for the next step's target, and * disables its advance control for as long as the wait lasts: the step the * user asked to leave stays on screen, and an advance that is already * refused by the controller must not keep looking available. Cleared by the * controller when the target settles, and by any teardown. */ setTargetPending(pending: boolean): void; /** * Hides the popover and hands the page back while it is hidden: nothing is left to trap focus * in, so the page leaves `inert`, focus moves from the popover to the target, and the shortcuts * stop. The overlay, the pointer and the scroll lock stay. Showing it again replays the entrance * and makes the step modal again. A step still entering applies it once presented. */ setPopoverHidden(hidden: boolean): void; show(step: ActiveStep, direction: TourDirection, signal: AbortSignal, onBeforePopoverAppear?: () => void | Promise): Promise; clear(signal: AbortSignal): Promise; releaseMount(): void; dispose(): void; private refreshRegisteredElements; private activateRegisteredElements; private initializeElements; private syncModality; private claimModal; private releaseModality; /** Gives the page back without releasing the document's modal claim. */ private liftModality; private restoreInertBranches; /** * Brings the spotlight onto the target, then hands the step's popover and * pointer over, in that order but not in lockstep. * * The spotlight's entrance never gates the popover's: the two animate side * by side, as they always have. What does gate the popover is the step's * scroll. It is pinned by a transform it only rewrites on entrance, so a * rect that is still travelling would make it jump through a fade every * fifty pixels; it waits for the page to stop and enters on a rect that will * not move again. Meanwhile the spotlight tracks the target down the page. */ private appear; /** * Retires the outgoing popover, commits the incoming step's content in its * place, waits out the step's scroll, and brings the popover and pointer in. * * Deliberately one async frame. With nothing to retire, nothing to commit * and nothing to scroll, the entrance animations are created in the tick * this was called in — which is what callers that abort mid-flight rely on, * since `cancelAnimationsOnAbort` can only cancel animations that exist. * * The content commit runs even when no popover is mounted: it carries the * controller's step-index commit and must not be skipped. */ private presentStepUi; /** The popover and pointer entrance itself, started synchronously. */ private enterStepUi; /** Fades the pointer in on the target, or out when the step does not show it. */ private presentPointer; private attachStepResources; /** * Binds the step's custom event handlers to its target element. Split out * from `attachStepResources` so a lost-then-recovered target can be * rebound on its own by `retarget()`, without re-subscribing the * step-level resources (props, capabilities, controls) that never left. */ private attachTargetResources; private listen; private schedulePosition; /** * Frame scheduling for the target's own realm, falling back to the ambient * one when that realm exposes no frame callbacks. Shared by the tracking * loop and the scroll sentinel so both read the same clock. */ private frameScheduler; private updatePosition; /** Per-frame follow-up for the popover and pointer, once they are on screen. */ private trackStepUi; /** * A scroll of the page, or of a scroller around the target, while the step is * on screen. The popover and pointer stand aside at the first one, since * following a travelling rect would fade them out and in every few pixels, * and come back once the page has been still for a moment. */ private onUserScroll; private waitForScrollIdle; /** * The page has been still for a moment: brings the popover and pointer back, * after `scroll.returnDelay` has scrolled the target back into view when the * user left part of it outside. */ private settleScroll; /** * Scrolls the target back in. As on entrance, `awaitingStepUi` leaves the * spotlight to follow the page on its own and the scroll events this raises * to be ignored; set together with `userScrolling`, it marks the scroll back, * which a wheel or touch drag interrupts. */ private scrollBack; private revealAfterScroll; private stopUserScroll; /** * Moves the popover along with its target and returns its placement. A hidden popover stays * where it is: moving it would fade it back in. The pointer still keeps clear of its placement. */ private placePopover; private observeDynamicOperation; private handleKeydown; /** * The keyboard shortcut a keydown asks for, if `available` allows it. `null` leaves the key to the * browser, as Enter on a focused control is: on a tour button, the click it produces runs that * button's own command after the consumer's click handlers, like a pointer click. */ private keyboardCommand; private handleOverlayClick; private queueTransitionKeydown; /** * Queues the command of a tour button clicked while a visible popover is replaced. Listening on * the window runs after the consumer's own click handlers, so a prevented click queues nothing. */ private queueTransitionClick; private flushPendingCommand; private loopFocus; /** * Makes a presented step modal and moves focus into its popover, or keeps the popover hidden. A * popover shown again after the entrance passed over it enters now. */ private engagePopover; /** * Fades the popover out and stops guarding focus. Focus left in the popover goes to the target, * or is dropped when the target cannot take it: `inert` would otherwise drop it on the body. The * focus the tour gives back when it ends is kept for the step that shows the popover again. */ private concealPopover; /** * Replays the popover's entrance on the rect the step was last placed on (a frozen step has no * live target to measure), then makes the step modal again. */ private revealPopover; private isPopoverConcealed; private activateFocus; private syncScrollLock; private syncShortcutLabels; private attachButtonHandlers; private findClickedTrigger; private observeControls; private deferTriggerCommand; private syncControlState; private findTriggers; private ownsTrigger; private command; private commandForGeneration; private commandForStep; private canCommand; private syncControl; private isConsumerDisabled; private isLiveDisabled; /** * The props the overlay and popover render: the step's own, flagged when the step is detached so * the overlay drops its cutout and the popover centers itself. */ private elementProps; private isPointerEnabled; private cleanupStepResources; private cleanupTargetResources; private isCurrentTargetAvailable; /** * Holds the presentation exactly where it is when its target disappears, * instead of tearing it down: overlay, popover and pointer stay mounted at * their last known rect, focus guard and scroll lock stay engaged, and only * the target's own listeners (now pointing at a dead node) are removed. * The generation is deliberately left untouched — popover buttons, the * keyboard shortcuts and any pending capability/focus bookkeeping must * keep working while frozen, since the popover is the user's escape hatch * out of a tour whose target never comes back. `commands.targetDisconnected` * drives the actual recovery (grace period, then the configured strategy) * and eventually calls back into `retarget()` or `clear()`. */ private freezeForDisconnectedTarget; /** * Blocks (or restores) interaction with the underlying page independently * of `step.behavior.allowInteraction`. Used to force interaction off while * frozen — the cutout no longer corresponds to anything after a reflow, so * it must not let clicks through even on a step that normally allows them — * and to restore the step's own setting once retargeted. */ private applyInteractionLock; /** Applies the step's own interaction setting to the overlay, the page modality and the focus guard. */ private applyInteraction; /** * Applies a `behavior.allowInteraction` changed through the step props while the step is on * screen, or stepped aside for a user scroll. A step still entering reads the new value when it * presents, and a frozen one keeps interaction off until `retarget()` restores it. The indicator * fades in or out instead of snapping. */ private syncInteraction; private isFocusInsideTarget; /** * Resumes a presentation frozen by `freezeForDisconnectedTarget` on its new * target: reattaches the target-bound listeners, restores the step's own * interaction setting, and lets the existing reposition loop tween overlay * and popover to the new rect on the next frame. Deliberately skips * `appear()` (no re-entrance animation) and `activateFocus()` (focus stays * where the user left it), only reclaiming it if it was on the target that * just disappeared and the step auto focuses. */ retarget(step: ActiveStep, signal: AbortSignal): Promise; /** * Walks the presentation from where it froze to the new target's box. The * per-frame loop can't do this on its own: it only tweens the cutout when * the step's own visuals changed, and a target that reappears elsewhere is * a pure geometry jump, which would snap. `animateTo` and the popover's * reposition both fall back to an instant move when the step isn't * animated, so this respects `animated: false` and reduced motion without * asking about them. */ private moveToRetargetedRect; private beginGeneration; private cancelAnimationsOnAbort; private cancelElementAnimations; private isCurrentGeneration; /** * Starts the step's scroll and returns a promise that settles once the page * has stopped moving, or `null` when there is nothing to wait for — the step * opts out, the scroll was applied instantly, or no scroller can be measured. * * `scrollIntoView` is called synchronously so a throwing call still rejects * the `show()` that asked for it. Only the wait is deferred, which is what * lets the backdrop appear while the page is still travelling. */ private beginTargetScroll; /** Scrolls the target in as the step asks, and returns the settle wait as `beginTargetScroll` does. */ private scrollTargetIntoView; /** * Resolves once the scroller, and the target it carries, have held still for * a couple of frames. * * The target's own position is watched alongside the document's offset: a * page that scrolls inside a container rather than the document moves the * target while `scrollingElement` sits at zero, and would otherwise read as * settled a few frames in, presenting the popover mid-scroll. * * Deliberately not the `scrollend` event: Safari only fires it from 18.2, so * older versions would fall through to the safety timeout on every step, and * the presentation would stay pinned to a stale position long after the page * actually stopped. Watching the offset costs a frame loop the browser is * already running during a smooth scroll, works everywhere, and settles just * as quickly when the browser decides there was nothing to scroll at all. * * Returns `null` when the offset cannot be read or frames cannot be * requested — there is then no way to observe the scroll, so callers treat it * as already finished rather than blocking on something unobservable. */ private waitForScrollToSettle; private throwIfAborted; private throwIfStale; private getWindow; }