@glowhop/core-tour 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,459 @@
1
+ import type { WorkflowBuilder } from "../builder";
2
+ import type { ReadonlyStepProps, WorkflowDefinition } from "../definition";
3
+ export type { ReadonlyStartOptions, ReadonlyStepProps, WorkflowDefinition, WorkflowStepDefinition, } from "../definition";
4
+ /** A primitive value that can be stored in step data or passed to callbacks. */
5
+ export type PrimitiveValue = string | number | boolean | null;
6
+ /**
7
+ * Resolves the target element for a tour step.
8
+ *
9
+ * Can be a CSS selector string, an HTMLElement directly, or a function that resolves the target asynchronously.
10
+ */
11
+ export type TargetResolver = string | HTMLElement | ((context: TargetResolverContext) => HTMLElement | null | Promise<HTMLElement | null>);
12
+ /** Context passed to a target resolver function. */
13
+ export interface TargetResolverContext {
14
+ /** Signal that aborts when the tour is cancelled or disposed. */
15
+ signal: AbortSignal;
16
+ }
17
+ /** Configures step-level interaction behavior and error handling. */
18
+ export interface StepBehavior {
19
+ /** Allow user interaction with the page outside the target element. @default false */
20
+ allowInteraction?: boolean;
21
+ /** Disable automatic focus on the target when the step is entered. @default false */
22
+ disableAutoFocus?: boolean;
23
+ /** Disable automatic scroll to the target when the step is entered. @default false */
24
+ disableAutoScroll?: boolean;
25
+ /** How to handle when the target is not found: `"wait"` waits and retries, `"skip"` advances to next step, `"error"` halts the tour. @default "error" */
26
+ missingTargetStrategy?: "wait" | "skip" | "error";
27
+ /** Scroll behavior options. */
28
+ scroll?: ScrollOptions;
29
+ /** Timeout in ms to wait for target to appear before applying missingTargetStrategy. @default 3000 */
30
+ targetTimeout?: number;
31
+ /**
32
+ * Behavior when the dimmed overlay backdrop (outside the cutout around the
33
+ * target) is clicked: `"advance"` moves to the next step, `"cancel"` ends
34
+ * the tour, `"none"` ignores the click. Has no effect when
35
+ * `allowInteraction` is `true`, since the page stays fully interactive and
36
+ * there is no modal backdrop to click.
37
+ * @default "none"
38
+ */
39
+ overlayClick?: "none" | "advance" | "cancel";
40
+ }
41
+ /** Placement directions for positioning the pointer or popover around the target. */
42
+ export type TryOrderOptions = "top" | "bottom" | "left" | "right";
43
+ /** A resolved placement direction, including `"center"` for centered positioning. */
44
+ export type ResolvedPlacement = TryOrderOptions | "center";
45
+ /** Base configuration for animated elements. */
46
+ export interface BaseOptions {
47
+ /** Enable or disable animations. */
48
+ animated?: boolean;
49
+ /** Animation duration and easing options. */
50
+ animation?: AnimationOptions;
51
+ }
52
+ /** Configures the pointer indicator that highlights the target element. */
53
+ export interface IndicatorOptions extends BaseOptions {
54
+ /** Hide the indicator. @default false */
55
+ disabled?: boolean;
56
+ /** Gap between the target and the indicator in pixels. */
57
+ gap?: number;
58
+ /** Placement preference order when positioning the indicator. @default ["bottom", "top", "right", "left"] */
59
+ placementTryOrder?: readonly TryOrderOptions[];
60
+ }
61
+ /** Configures the darkened overlay backdrop that highlights the target. */
62
+ export interface OverlayOptions extends BaseOptions {
63
+ /** Color of the overlay backdrop (CSS color). @default "rgba(0, 0, 0, 0.5)" */
64
+ color?: string;
65
+ /** Opacity of the overlay (0-1). @default 0.7 */
66
+ opacity?: number;
67
+ /** Padding around the target cutout in pixels. @default 8 */
68
+ padding?: number;
69
+ /** Border radius of the target cutout in pixels. @default 8 */
70
+ radius?: number;
71
+ }
72
+ /** Configures the arrow that points from the popover to the target. */
73
+ export interface PopoverArrowOptions {
74
+ /** Hide the arrow. @default false */
75
+ disabled?: boolean;
76
+ /** Color of the arrow (CSS color). */
77
+ color?: string;
78
+ /** Size of the arrow in pixels. @default 12 */
79
+ size?: number;
80
+ /** Border width of the arrow in pixels. @default 0 */
81
+ borderWidth?: number;
82
+ /** Border radius of the arrow in pixels. @default 0 */
83
+ borderRadius?: number;
84
+ /** Gap between arrow tip and the target edge in pixels. @default 8 */
85
+ edgePadding?: number;
86
+ /**
87
+ * CSP nonce applied to the `<style>` element GlowTour.js injects for the
88
+ * arrow's pseudo-element rules. Required when the page's Content-Security-Policy
89
+ * blocks unnonced inline styles.
90
+ */
91
+ styleNonce?: string;
92
+ /**
93
+ * Skip injecting the built-in arrow `<style>` element entirely. Provide the
94
+ * equivalent rules yourself through whatever channel your CSP allows, such
95
+ * as an external stylesheet.
96
+ */
97
+ disableAutoStyles?: boolean;
98
+ }
99
+ /** Configures the popover box that displays content for each step. */
100
+ export interface PopoverOptions extends BaseOptions {
101
+ /** Placement preference order for the popover around the target. @default ["top", "bottom", "right", "left"] */
102
+ placementTryOrder?: readonly TryOrderOptions[];
103
+ /** Arrow configuration. */
104
+ arrow?: PopoverArrowOptions;
105
+ /** Hide the footer section. @default false */
106
+ hideFooter?: boolean;
107
+ /**
108
+ * Disables only previous-button and previous-keyboard controls. Programmatic
109
+ * navigation through the tour API and step context remains available.
110
+ */
111
+ disablePreviousButton?: boolean;
112
+ /** Hide the previous button. @default false */
113
+ hidePreviousButton?: boolean;
114
+ /**
115
+ * Disables only advance-button and advance-keyboard controls. Programmatic
116
+ * navigation through the tour API and step context remains available.
117
+ */
118
+ disableAdvanceButton?: boolean;
119
+ /** Hide the advance button. @default false */
120
+ hideAdvanceButton?: boolean;
121
+ /** Gap between the target and the popover in pixels. @default 16 */
122
+ gap?: number;
123
+ /** Keyboard shortcuts for navigation. */
124
+ keyboardShortcuts?: {
125
+ /**
126
+ * Keys that trigger previous step. @default ["ArrowLeft", "Backspace"]
127
+ */
128
+ previous?: readonly string[];
129
+ /**
130
+ * Keys that trigger advance step. @default ["Enter", "ArrowRight"]
131
+ */
132
+ advance?: readonly string[];
133
+ /**
134
+ * Keys that trigger cancel. @default ["Escape"]
135
+ */
136
+ cancel?: readonly string[];
137
+ };
138
+ }
139
+ /** Scroll behavior options passed to Element.scrollIntoView(). */
140
+ export interface ScrollOptions {
141
+ /** Scroll animation. @default "auto" */
142
+ behavior?: "auto" | "smooth";
143
+ /** Vertical alignment of the target in the viewport. @default "center" */
144
+ block?: "start" | "center" | "end" | "nearest";
145
+ /** Horizontal alignment of the target in the viewport. @default "nearest" */
146
+ inline?: "start" | "center" | "end" | "nearest";
147
+ }
148
+ /** Animation timing configuration. */
149
+ export interface AnimationOptions {
150
+ /** Duration of the animation in milliseconds. */
151
+ duration: number;
152
+ /** CSS easing function (e.g., "ease-in-out", "cubic-bezier(...)"). */
153
+ easing: string;
154
+ }
155
+ /**
156
+ * Context passed to a tour-level lifecycle hook (`onStart`, `onCancel`, `onFinish`).
157
+ */
158
+ export interface LifecycleHookContext<T> {
159
+ /**
160
+ * The step associated with this lifecycle transition:
161
+ * - `onStart`: the first step about to be entered (`workflow.steps[0]`), or
162
+ * `null` if the workflow has no steps.
163
+ * - `onCancel`: the step the tour is currently on when cancellation is
164
+ * requested. Always non-null in practice, since a step is always active
165
+ * at the point a tour can be cancelled.
166
+ * - `onFinish`: the last step the tour was on before finishing. Always
167
+ * non-null in practice, except for the edge case of a workflow with zero
168
+ * steps, which finishes immediately after `onStart` without ever
169
+ * entering a step.
170
+ */
171
+ readonly step: TourCurrentStep<T> | null;
172
+ /**
173
+ * Call this synchronously, or before the hook's returned promise resolves,
174
+ * to prevent the lifecycle transition from completing:
175
+ * - in `onStart`, the tour never starts: no step is entered (and, for a
176
+ * zero-step workflow, `onFinish` never fires either).
177
+ * - in `onCancel`, the cancellation is prevented: the tour remains on its
178
+ * current step, un-cancelled.
179
+ * - in `onFinish`, completion is prevented: the tour remains on its last
180
+ * step / current state, uncompleted.
181
+ */
182
+ abort(): void;
183
+ }
184
+ /** Options for starting a tour workflow. */
185
+ export interface StartOptions<T> {
186
+ /** Allow users to cancel the tour. @default true */
187
+ cancellable?: boolean;
188
+ /**
189
+ * Locks page scroll while the tour is active, restoring it on finish,
190
+ * cancel, error, or dispose.
191
+ *
192
+ * @default false
193
+ */
194
+ allowScroll?: boolean;
195
+ /** Default overlay options for all steps. */
196
+ overlay?: OverlayOptions;
197
+ /** Default popover options for all steps. */
198
+ popover?: PopoverOptions;
199
+ /** Default indicator options for all steps. */
200
+ indicator?: IndicatorOptions;
201
+ /** Enable or disable animations globally. */
202
+ animated?: boolean;
203
+ /** Default step behavior for all steps. */
204
+ behavior?: StepBehavior;
205
+ /** Lifecycle hook called when the tour starts. */
206
+ onStart?: (context: LifecycleHookContext<T>) => void | Promise<void>;
207
+ /** Lifecycle hook called when the tour is cancelled. */
208
+ onCancel?: (context: LifecycleHookContext<T>) => void | Promise<void>;
209
+ /** Lifecycle hook called when the tour finishes. */
210
+ onFinish?: (context: LifecycleHookContext<T>) => void | Promise<void>;
211
+ /**
212
+ * Monitoring callback for this workflow. See `TourEvent`.
213
+ *
214
+ * Monitoring only: it cannot abort a transition — that is what the `onStart` /
215
+ * `onCancel` / `onFinish` hooks and their `abort()` are for.
216
+ */
217
+ onEvent?: TourEventListener;
218
+ }
219
+ /** Update to step properties, either as a full replacement or via a function. */
220
+ export type StepPropsUpdate<T> = ReadonlyStepProps<T> | ((current: ReadonlyStepProps<T>) => ReadonlyStepProps<T>);
221
+ /** Store for the current step's properties. */
222
+ export interface StepPropsStore<T> {
223
+ /** Get the current step properties. */
224
+ get(): ReadonlyStepProps<T>;
225
+ /** Update the current step properties. */
226
+ set(update: StepPropsUpdate<T>): void;
227
+ /** Subscribe to changes in step properties. Returns an unsubscribe function. */
228
+ subscribe(listener: (props: ReadonlyStepProps<T>) => void): () => void;
229
+ }
230
+ /** Context passed to step action callbacks. */
231
+ export interface StepContext<T> {
232
+ /** Navigate to the next step. */
233
+ advance(): Promise<void>;
234
+ /** Cancel the tour. */
235
+ cancel(): Promise<void>;
236
+ /** Navigate to the previous step. */
237
+ previous(): Promise<void>;
238
+ /** The DOM element being highlighted for this step. */
239
+ readonly target: HTMLElement;
240
+ /** Store for reading and updating the current step's properties. */
241
+ readonly props: StepPropsStore<T>;
242
+ /** Signal that aborts when the step is exited or the tour is cancelled. */
243
+ readonly signal: AbortSignal;
244
+ }
245
+ /** Context passed to transition hooks (beforeAdvance, beforePrevious, beforeCancel). */
246
+ export type BeforeActionStepContext<T> = Readonly<ReadonlyStepProps<T> & {
247
+ readonly target: HTMLElement;
248
+ }>;
249
+ /** Context passed to target event handlers. */
250
+ export type StepEventContext<T> = StepContext<T>;
251
+ /** Options for the waitUntil step action. */
252
+ export interface WaitUntilOptions {
253
+ /** Polling interval in milliseconds. @default 16 */
254
+ interval?: number;
255
+ /** Maximum time to wait in milliseconds before timing out. @default 3000 */
256
+ timeout?: number;
257
+ }
258
+ /** Return value from a step action: `false` stops action sequence, otherwise continues. */
259
+ export type StepActionResult = boolean | void;
260
+ /** A callback that runs when the step is entered. */
261
+ export type StepAction<T> = (context: StepContext<T>) => Promise<StepActionResult> | StepActionResult;
262
+ /** A step action or a delay in milliseconds. */
263
+ export type StepActionInstruction<T> = StepAction<T> | number;
264
+ /** A callback that runs before transitioning to the next/previous step or cancelling. */
265
+ export type StepTransitionAction<T> = (context: BeforeActionStepContext<T>) => void | Promise<void>;
266
+ /** Handler for an event fired on the target element during a step. */
267
+ export interface EventHandler<TStepProps, TEvent extends Event = Event> {
268
+ /** Event name(s) to listen for. */
269
+ event: string;
270
+ /** Callback invoked when the event fires. */
271
+ callback: (event: TEvent, context: StepEventContext<TStepProps>) => void | Promise<void>;
272
+ }
273
+ /** Tour lifecycle status. */
274
+ export type TourStatus = "idle" | "starting" | "transitioning" | "active" | "finished" | "cancelled" | "error" | "disposed";
275
+ /** Direction of tour navigation. */
276
+ export type TourDirection = "advance" | "previous";
277
+ /** Information about the currently active step in a tour. */
278
+ export interface TourCurrentStep<T> {
279
+ /** The stable identifier of this step, as declared in the workflow. */
280
+ readonly id: string;
281
+ /** The step properties as initially configured. */
282
+ readonly initialProps: ReadonlyStepProps<T>;
283
+ /** The current step properties (may have been updated via StepPropsStore). */
284
+ readonly currentProps: ReadonlyStepProps<T>;
285
+ /** The target element this step highlights, or null if not yet resolved. */
286
+ readonly target: HTMLElement | null;
287
+ }
288
+ /** Complete state of an active tour. */
289
+ export interface TourState<T> {
290
+ /** Name of the running workflow. */
291
+ readonly name: string;
292
+ /** Total number of steps in the workflow. */
293
+ readonly totalSteps: number;
294
+ /** Index of the currently active step (0-based), or -1 if no step is active. */
295
+ readonly currentStepIndex: number;
296
+ /** The currently active step, or null if the tour is not actively showing a step. */
297
+ readonly currentStep: TourCurrentStep<T> | null;
298
+ /** Direction of the last navigation ("advance" or "previous"). */
299
+ readonly direction: TourDirection;
300
+ /** Whether advancing to the next step is possible. */
301
+ readonly canAdvance: boolean;
302
+ /** Whether going to the previous step is possible. */
303
+ readonly canPrevious: boolean;
304
+ /** Whether the tour can be cancelled. */
305
+ readonly canCancel: boolean;
306
+ /** Whether the tour is on the first step. */
307
+ readonly isFirstStep: boolean;
308
+ /** Whether the tour is on the last step. */
309
+ readonly isLastStep: boolean;
310
+ /** Current status of the tour. */
311
+ readonly status: TourStatus;
312
+ /** Error encountered during the tour, if any. */
313
+ readonly error: Error | null;
314
+ }
315
+ /** Observable store of the tour state. */
316
+ export interface ReadonlyTourState<T> {
317
+ /** Get the current tour state. */
318
+ get(): TourState<T>;
319
+ /** Subscribe to tour state changes. Returns an unsubscribe function. */
320
+ subscribe(listener: (state: TourState<T>) => void): () => void;
321
+ }
322
+ /** The main tour controller that manages workflows and navigation. */
323
+ export interface GlowTour<T> {
324
+ /** Create a new workflow builder with the given name. */
325
+ create(name: string, options?: StartOptions<T>): WorkflowBuilder<T>;
326
+ /** Run a workflow, optionally starting at a specific step. */
327
+ run(workflow: WorkflowDefinition<T>, options?: RunOptions): Promise<void>;
328
+ /** Advance to the next step. */
329
+ advance(): Promise<void>;
330
+ /** Go to the previous step. */
331
+ previous(): Promise<void>;
332
+ /** Jump to a specific step by index. */
333
+ goToStep(index: number): Promise<void>;
334
+ /** Cancel the current tour. */
335
+ cancel(): Promise<void>;
336
+ /** Dispose the tour and free resources. */
337
+ dispose(): void;
338
+ /** Observable store of the current tour state. */
339
+ readonly state: ReadonlyTourState<T>;
340
+ }
341
+ /**
342
+ * Per-run options. Unlike `StartOptions`, these belong to one `run()` call and
343
+ * are never baked into the reusable workflow definition.
344
+ */
345
+ export interface RunOptions {
346
+ /**
347
+ * Id of the step to start on, instead of the first one. Use it to resume a
348
+ * tour where the user left off.
349
+ *
350
+ * The workflow itself is not truncated: `previous()` can still go back before
351
+ * this step, and `totalSteps` is unchanged.
352
+ *
353
+ * Throws if no step carries this id — a tour that silently restarts from the
354
+ * beginning is a bug the end user sees.
355
+ */
356
+ startAt?: string;
357
+ }
358
+ /**
359
+ * What triggered a transition.
360
+ *
361
+ * `"api"` covers every call your own code makes — `advance()`, `previous()`,
362
+ * `goToStep()`, `cancel()`, and the `context.advance()` available inside a step
363
+ * action. The other three are the user acting on the tour UI directly.
364
+ */
365
+ export type TourEventSource = "api" | "trigger" | "keyboard" | "overlay";
366
+ /** Name of a monitoring event. */
367
+ export type TourEventType = "tour:start" | "step:enter" | "step:leave" | "tour:complete" | "tour:cancel" | "tour:error";
368
+ /**
369
+ * A monitoring event, as handed to `onEvent`.
370
+ *
371
+ * This is a stable, public contract: the names and the fields below are meant to
372
+ * be written straight into an analytics payload.
373
+ */
374
+ export interface TourEvent {
375
+ /** Which event this is. */
376
+ readonly type: TourEventType;
377
+ /** Name of the running workflow, as passed to `create()`. */
378
+ readonly workflowName: string;
379
+ /**
380
+ * Id of the step the event is about, or `null` when no step applies — a
381
+ * workflow with no steps, or a tour that failed before entering one.
382
+ */
383
+ readonly stepId: string | null;
384
+ /** Index of that step (0-based), or `-1` when `stepId` is `null`. */
385
+ readonly stepIndex: number;
386
+ /** Total number of steps in the workflow. */
387
+ readonly stepCount: number;
388
+ /** Direction of the navigation that led here. */
389
+ readonly direction: TourDirection;
390
+ /** What triggered the transition — a button, the keyboard, the overlay, or your code. */
391
+ readonly source: TourEventSource;
392
+ /** `Date.now()` when the event was emitted. */
393
+ readonly timestamp: number;
394
+ /**
395
+ * How long the thing this event names had been running, in milliseconds.
396
+ *
397
+ * For `step:leave`, the time spent on that step. For `tour:complete`,
398
+ * `tour:cancel` and `tour:error`, the time since `run()` was called. For
399
+ * `tour:start` and `step:enter` — the beginnings — always `0`.
400
+ */
401
+ readonly durationMs: number;
402
+ /**
403
+ * The error that ended the tour. Only ever set on `tour:error`.
404
+ */
405
+ readonly error: Error | null;
406
+ }
407
+ /**
408
+ * Monitoring callback. Receives every event of a running tour.
409
+ *
410
+ * Monitoring only: unlike the lifecycle hooks, it cannot abort or delay a
411
+ * transition. It is called synchronously and its return value is ignored; if it
412
+ * throws, the error goes to `onSubscriberError` and the tour carries on.
413
+ */
414
+ export type TourEventListener = (event: TourEvent) => void;
415
+ /** Options for creating a GlowTour instance. */
416
+ export interface GlowTourOptions {
417
+ /** Error handler for exceptions thrown in state subscribers. */
418
+ onSubscriberError?: (error: Error) => void | Promise<void>;
419
+ /**
420
+ * Monitoring callback for every tour this instance runs. Use it to wire the
421
+ * tour to analytics once, rather than per workflow.
422
+ *
423
+ * A workflow can add its own listener through `StartOptions.onEvent`; both are
424
+ * called, this one first.
425
+ */
426
+ onEvent?: TourEventListener;
427
+ }
428
+ /** Parameters for defining a tour step. */
429
+ export type StepParameters<T> = {
430
+ /**
431
+ * Stable identifier for this step, unique within the workflow.
432
+ *
433
+ * Required: it is the only durable way to designate a step across reloads
434
+ * and navigations (see `RunOptions.startAt`). A positional index is not a
435
+ * substitute — it breaks as soon as steps are reordered or inserted.
436
+ */
437
+ id: string;
438
+ /** The target element or selector for this step. */
439
+ target: TargetResolver;
440
+ /**
441
+ * Reset step properties to initial values when entering this step.
442
+ * @default true
443
+ */
444
+ resetPropsOnEnter?: boolean;
445
+ /** Overlay options for this step (overrides workflow defaults). */
446
+ overlay?: OverlayOptions;
447
+ /** Popover options for this step (overrides workflow defaults). */
448
+ popover?: PopoverOptions;
449
+ /** Indicator options for this step (overrides workflow defaults). */
450
+ indicator?: IndicatorOptions;
451
+ /** Step behavior (overrides workflow defaults). */
452
+ behavior?: StepBehavior;
453
+ /** The title content for this step. */
454
+ title: T;
455
+ /** The body content for this step. */
456
+ content: T;
457
+ /** Arbitrary data associated with this step. */
458
+ data?: Record<string, PrimitiveValue>;
459
+ };
@@ -0,0 +1,7 @@
1
+ import type { AnimationOptions, IndicatorOptions, OverlayOptions, PopoverOptions, ScrollOptions, StepBehavior } from "../types";
2
+ export declare function mergeOverlayOptions(defaults?: OverlayOptions, overrides?: OverlayOptions): OverlayOptions | undefined;
3
+ export declare function mergeIndicatorOptions(defaults?: IndicatorOptions, overrides?: IndicatorOptions): IndicatorOptions | undefined;
4
+ export declare function mergePopoverOptions(defaults?: PopoverOptions, overrides?: PopoverOptions): PopoverOptions | undefined;
5
+ export declare function mergeScrollOptions(defaults?: ScrollOptions, overrides?: ScrollOptions): ScrollOptions | undefined;
6
+ export declare function mergeAnimationOptions(defaults?: AnimationOptions, overrides?: AnimationOptions): AnimationOptions | undefined;
7
+ export declare function mergeStepBehavior(defaults?: StepBehavior, overrides?: StepBehavior): StepBehavior | undefined;
@@ -0,0 +1,28 @@
1
+ import type { TargetResolver } from "../types";
2
+ export declare function ownerWindow(element?: Node | null): (Window & typeof globalThis) | null;
3
+ export declare function ownerDocument(element?: Node | null): Document | null;
4
+ export declare function isHTMLElement(value: unknown, context?: Node | null): value is HTMLElement;
5
+ export declare function isElement(value: unknown, context?: Node | null): value is Element;
6
+ export declare function isNode(value: unknown, context?: Node | null): value is Node;
7
+ export declare function viewportDimensions(context?: Node | null): {
8
+ width: number;
9
+ height: number;
10
+ };
11
+ export declare function isInViewport(rect: {
12
+ left: number;
13
+ top: number;
14
+ right: number;
15
+ bottom: number;
16
+ }, context?: Node | null): boolean;
17
+ export declare function roundByDPR(value: number, context?: Node | null): number;
18
+ export declare function roundedRectPath(rect: DOMRect, viewport: {
19
+ width: number;
20
+ height: number;
21
+ }, options: {
22
+ padding: number;
23
+ radius: number;
24
+ }, context?: Node | null): string;
25
+ export declare function resolveTargetElement(target: TargetResolver, options: {
26
+ readonly document?: Document;
27
+ readonly signal: AbortSignal;
28
+ }, path?: string): Promise<HTMLElement | null>;