@glowhop/core-tour 1.3.1 → 1.5.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.
@@ -1,10 +1,18 @@
1
- import type { ReadonlyStepProps, StepProps } from "./types";
1
+ import type { TourClassNames, TourControls } from "../types";
2
+ import type { DeepReadonly, ReadonlyStepProps, StepProps } from "./types";
2
3
  /**
3
4
  * Creates a shallow clone of step properties with deep clones of nested objects.
4
5
  * @param props The properties to clone.
5
6
  * @returns A mutable copy of the properties.
6
7
  */
7
8
  export declare function cloneStepProps<T>(props: ReadonlyStepProps<T>): StepProps<T>;
9
+ /** Copies `controls` into frozen records, with frozen copies of their `keys`. */
10
+ export declare function cloneControls(controls: DeepReadonly<TourControls> | undefined): TourControls | undefined;
11
+ /**
12
+ * Copies `classNames` into a frozen record. Its class arrays are shared rather than copied: they are
13
+ * typed readonly and nothing in the tour writes to them, and copying them costs bundle size.
14
+ */
15
+ export declare function cloneClassNames(classNames: TourClassNames | undefined): TourClassNames | undefined;
8
16
  /**
9
17
  * Creates a deep-frozen copy of step properties.
10
18
  * @param props The properties to freeze.
@@ -1,18 +1,21 @@
1
- import type { EventHandler, IndicatorOptions, OverlayOptions, PopoverOptions, PrimitiveValue, StartOptions, StepActionInstruction, StepBehavior, StepParameters, StepTransitionAction, TargetResolver } from "../types";
1
+ import type { IndicatorOptions, OverlayOptions, PopoverOptions, PrimitiveValue, StartOptions, StepActionInstruction, StepBehavior, StepHookAction, StepParameters, TargetEventHandler, TargetResolver, TourClassNames, TourControls } from "../types";
2
2
  /** Recursively makes all properties readonly at any depth. */
3
3
  export type DeepReadonly<T> = T extends (...arguments_: infer _Arguments) => infer _Return ? T : T extends readonly (infer TEntry)[] ? readonly DeepReadonly<TEntry>[] : T extends object ? {
4
4
  readonly [TKey in keyof T]: DeepReadonly<T[TKey]>;
5
5
  } : T;
6
- /** Step properties (title, content, and optional display options) excluding target and behavior. */
7
- export type StepProps<T> = Omit<StepParameters<T>, "id" | "target" | "resetPropsOnEnter" | "behavior">;
6
+ /** Step properties (title, content, behavior, and optional display options) excluding id and target. */
7
+ export type StepProps<T> = Omit<StepParameters<T>, "id" | "target">;
8
8
  /** Immutable step properties. */
9
9
  export type ReadonlyStepProps<T> = {
10
- readonly title: T;
10
+ readonly title?: T;
11
11
  readonly content: T;
12
12
  readonly data?: Readonly<Record<string, PrimitiveValue>>;
13
13
  readonly overlay?: DeepReadonly<OverlayOptions>;
14
14
  readonly popover?: DeepReadonly<PopoverOptions>;
15
15
  readonly indicator?: DeepReadonly<IndicatorOptions>;
16
+ readonly behavior?: DeepReadonly<StepBehavior>;
17
+ readonly controls?: DeepReadonly<TourControls>;
18
+ readonly classNames?: DeepReadonly<TourClassNames>;
16
19
  };
17
20
  /** Immutable tour start options. */
18
21
  export type ReadonlyStartOptions<T> = DeepReadonly<StartOptions<T>>;
@@ -21,14 +24,11 @@ export interface WorkflowStepDefinition<T> {
21
24
  /** Stable identifier, unique within the workflow. */
22
25
  readonly id: string;
23
26
  readonly target: TargetResolver;
24
- readonly resetPropsOnEnter?: boolean;
25
- readonly behavior?: DeepReadonly<StepBehavior>;
26
27
  readonly props: ReadonlyStepProps<T>;
27
28
  readonly actions: readonly StepActionInstruction<T>[];
28
- readonly eventHandlers: readonly EventHandler<T>[];
29
- readonly advanceAction: StepTransitionAction<T> | null;
30
- readonly previousAction: StepTransitionAction<T> | null;
31
- readonly cancelAction: StepTransitionAction<T> | null;
29
+ readonly targetEvents: readonly TargetEventHandler<T>[];
30
+ readonly beforeEnter: StepHookAction<T> | null;
31
+ readonly beforeLeave: StepHookAction<T> | null;
32
32
  }
33
33
  /** A complete tour workflow definition (immutable). */
34
34
  export interface WorkflowDefinition<T> {
@@ -1,16 +1,13 @@
1
- import type { EventHandler, StartOptions, StepActionInstruction, StepParameters, StepTransitionAction } from "../types";
1
+ import type { StartOptions, StepActionInstruction, StepHookAction, StepParameters, TargetEventHandler } from "../types";
2
2
  import type { StepProps, WorkflowDefinition, WorkflowStepDefinition } from "./types";
3
3
  export interface WorkflowStepDraft<T> {
4
4
  id: string;
5
5
  target: StepParameters<T>["target"];
6
- resetPropsOnEnter?: boolean;
7
6
  props: StepProps<T>;
8
- behavior?: StepParameters<T>["behavior"];
9
7
  actions: StepActionInstruction<T>[];
10
- eventHandlers: EventHandler<T>[];
11
- advanceAction: StepTransitionAction<T> | null;
12
- previousAction: StepTransitionAction<T> | null;
13
- cancelAction: StepTransitionAction<T> | null;
8
+ targetEvents: TargetEventHandler<T>[];
9
+ beforeEnter: StepHookAction<T> | null;
10
+ beforeLeave: StepHookAction<T> | null;
14
11
  }
15
12
  /**
16
13
  * Creates a mutable copy of a workflow step definition.
@@ -5,6 +5,7 @@ export interface TourViewCommands {
5
5
  canAdvance(): boolean;
6
6
  canCancel(): boolean;
7
7
  canPrevious(): boolean;
8
+ goTo(id: string): Promise<void>;
8
9
  isAdvanceDisabled(): boolean;
9
10
  isCancelDisabled(): boolean;
10
11
  isPreviousDisabled(): boolean;
@@ -26,6 +27,17 @@ export interface TourViewDriver<T> {
26
27
  * than throw.
27
28
  */
28
29
  retarget(step: ActiveStep<T>, signal: AbortSignal): Promise<void> | void;
30
+ /**
31
+ * Reports that the navigation under way is waiting for the next step's
32
+ * target to resolve. The presented step has not left yet, so its popover is
33
+ * the one that carries the wait.
34
+ */
35
+ setTargetPending?(pending: boolean): void;
36
+ /**
37
+ * Hides or shows the popover of the running tour. A step still entering reads it as it is
38
+ * presented. Every `start()` shows it again first.
39
+ */
40
+ setPopoverHidden?(hidden: boolean): void;
29
41
  dispose(): void;
30
42
  releaseMount?(): void;
31
43
  setCommands?(commands: TourViewCommands): void;
@@ -65,6 +77,14 @@ export declare class DomTourViewDriver<T> implements TourViewDriver<T> {
65
77
  * instead of freezing the presentation.
66
78
  */
67
79
  private awaitingStepUi;
80
+ /**
81
+ * True while the popover and pointer stand aside for a user scroll, until the
82
+ * page has been still for `USER_SCROLL_IDLE_DELAY` and, when the target was
83
+ * left off screen, the step has scrolled it back. The spotlight keeps
84
+ * tracking the target throughout.
85
+ */
86
+ private userScrolling;
87
+ private scrollTimer?;
68
88
  private activeTarget;
69
89
  private targetFocusedAtFreeze;
70
90
  private lastTargetRect;
@@ -73,11 +93,30 @@ export declare class DomTourViewDriver<T> implements TourViewDriver<T> {
73
93
  private modalDocument;
74
94
  private modalRoot;
75
95
  private overlay;
76
- private pendingKeyboardCommand;
96
+ /** A command asked for while a visible popover was replaced, run once the new step is presented. */
97
+ private pendingCommand;
77
98
  private pointer;
78
99
  private pendingFocusGeneration;
100
+ /**
101
+ * The last click a presented step's button handled. It bubbles on to the window after the
102
+ * command it ran started the next step, and must not be queued a second time there.
103
+ */
104
+ private handledTriggerClick;
79
105
  private popover;
80
106
  private presentationDirty;
107
+ /** True while the controller waits for the next step's target, see `setTargetPending`. */
108
+ private targetPending;
109
+ /** True while the consumer hides the popover, see `setPopoverHidden`. */
110
+ private popoverHidden;
111
+ /**
112
+ * The focus a clear gives back once the popover has faded out. Kept past a clear that a new show
113
+ * supersedes, so that tour returns focus there instead of to the fading popover.
114
+ */
115
+ private focusToRestore;
116
+ /** The `allowInteraction` value the overlay, the page modality and the focus guard reflect. */
117
+ private appliedAllowInteraction;
118
+ /** Set when a live `allowInteraction` change started the pointer fade; the next frame clears it. */
119
+ private pointerFading;
81
120
  private rafId;
82
121
  private rafCancel;
83
122
  private root;
@@ -88,6 +127,21 @@ export declare class DomTourViewDriver<T> implements TourViewDriver<T> {
88
127
  registerOverlay(element: SVGSVGElement | null): void;
89
128
  registerPopover(element: HTMLElement | null): void;
90
129
  registerPointer(element: HTMLElement | null): void;
130
+ /**
131
+ * Flags the presented popover as waiting for the next step's target, and
132
+ * disables its advance control for as long as the wait lasts: the step the
133
+ * user asked to leave stays on screen, and an advance that is already
134
+ * refused by the controller must not keep looking available. Cleared by the
135
+ * controller when the target settles, and by any teardown.
136
+ */
137
+ setTargetPending(pending: boolean): void;
138
+ /**
139
+ * Hides the popover and hands the page back while it is hidden: nothing is left to trap focus
140
+ * in, so the page leaves `inert`, focus moves from the popover to the target, and the shortcuts
141
+ * stop. The overlay, the pointer and the scroll lock stay. Showing it again replays the entrance
142
+ * and makes the step modal again. A step still entering applies it once presented.
143
+ */
144
+ setPopoverHidden(hidden: boolean): void;
91
145
  show(step: ActiveStep<T>, direction: TourDirection, signal: AbortSignal, onBeforePopoverAppear?: () => void | Promise<void>): Promise<void>;
92
146
  clear(signal: AbortSignal): Promise<void>;
93
147
  releaseMount(): void;
@@ -96,7 +150,10 @@ export declare class DomTourViewDriver<T> implements TourViewDriver<T> {
96
150
  private activateRegisteredElements;
97
151
  private initializeElements;
98
152
  private syncModality;
153
+ private claimModal;
99
154
  private releaseModality;
155
+ /** Gives the page back without releasing the document's modal claim. */
156
+ private liftModality;
100
157
  private restoreInertBranches;
101
158
  /**
102
159
  * Brings the spotlight onto the target, then hands the step's popover and
@@ -125,6 +182,8 @@ export declare class DomTourViewDriver<T> implements TourViewDriver<T> {
125
182
  private presentStepUi;
126
183
  /** The popover and pointer entrance itself, started synchronously. */
127
184
  private enterStepUi;
185
+ /** Fades the pointer in on the target, or out when the step does not show it. */
186
+ private presentPointer;
128
187
  private attachStepResources;
129
188
  /**
130
189
  * Binds the step's custom event handlers to its target element. Split out
@@ -144,12 +203,68 @@ export declare class DomTourViewDriver<T> implements TourViewDriver<T> {
144
203
  private updatePosition;
145
204
  /** Per-frame follow-up for the popover and pointer, once they are on screen. */
146
205
  private trackStepUi;
206
+ /**
207
+ * A scroll of the page, or of a scroller around the target, while the step is
208
+ * on screen. The popover and pointer stand aside at the first one, since
209
+ * following a travelling rect would fade them out and in every few pixels,
210
+ * and come back once the page has been still for a moment.
211
+ */
212
+ private onUserScroll;
213
+ private waitForScrollIdle;
214
+ /**
215
+ * The page has been still for a moment: brings the popover and pointer back,
216
+ * after `scroll.returnDelay` has scrolled the target back into view when the
217
+ * user left part of it outside.
218
+ */
219
+ private settleScroll;
220
+ /**
221
+ * Scrolls the target back in. As on entrance, `awaitingStepUi` leaves the
222
+ * spotlight to follow the page on its own and the scroll events this raises
223
+ * to be ignored; set together with `userScrolling`, it marks the scroll back,
224
+ * which a wheel or touch drag interrupts.
225
+ */
226
+ private scrollBack;
227
+ private revealAfterScroll;
228
+ private stopUserScroll;
229
+ /**
230
+ * Moves the popover along with its target and returns its placement. A hidden popover stays
231
+ * where it is: moving it would fade it back in. The pointer still keeps clear of its placement.
232
+ */
233
+ private placePopover;
147
234
  private observeDynamicOperation;
148
235
  private handleKeydown;
236
+ /**
237
+ * The keyboard shortcut a keydown asks for, if `available` allows it. `null` leaves the key to the
238
+ * browser, as Enter on a focused control is: on a tour button, the click it produces runs that
239
+ * button's own command after the consumer's click handlers, like a pointer click.
240
+ */
241
+ private keyboardCommand;
149
242
  private handleOverlayClick;
150
243
  private queueTransitionKeydown;
151
- private flushPendingKeyboardCommand;
244
+ /**
245
+ * Queues the command of a tour button clicked while a visible popover is replaced. Listening on
246
+ * the window runs after the consumer's own click handlers, so a prevented click queues nothing.
247
+ */
248
+ private queueTransitionClick;
249
+ private flushPendingCommand;
152
250
  private loopFocus;
251
+ /**
252
+ * Makes a presented step modal and moves focus into its popover, or keeps the popover hidden. A
253
+ * popover shown again after the entrance passed over it enters now.
254
+ */
255
+ private engagePopover;
256
+ /**
257
+ * Fades the popover out and stops guarding focus. Focus left in the popover goes to the target,
258
+ * or is dropped when the target cannot take it: `inert` would otherwise drop it on the body. The
259
+ * focus the tour gives back when it ends is kept for the step that shows the popover again.
260
+ */
261
+ private concealPopover;
262
+ /**
263
+ * Replays the popover's entrance on the rect the step was last placed on (a frozen step has no
264
+ * live target to measure), then makes the step modal again.
265
+ */
266
+ private revealPopover;
267
+ private isPopoverConcealed;
153
268
  private activateFocus;
154
269
  private syncScrollLock;
155
270
  private syncShortcutLabels;
@@ -167,6 +282,11 @@ export declare class DomTourViewDriver<T> implements TourViewDriver<T> {
167
282
  private syncControl;
168
283
  private isConsumerDisabled;
169
284
  private isLiveDisabled;
285
+ /**
286
+ * The props the overlay and popover render: the step's own, flagged when the step is detached so
287
+ * the overlay drops its cutout and the popover centers itself.
288
+ */
289
+ private elementProps;
170
290
  private isPointerEnabled;
171
291
  private cleanupStepResources;
172
292
  private cleanupTargetResources;
@@ -192,6 +312,15 @@ export declare class DomTourViewDriver<T> implements TourViewDriver<T> {
192
312
  * and to restore the step's own setting once retargeted.
193
313
  */
194
314
  private applyInteractionLock;
315
+ /** Applies the step's own interaction setting to the overlay, the page modality and the focus guard. */
316
+ private applyInteraction;
317
+ /**
318
+ * Applies a `behavior.allowInteraction` changed through the step props while the step is on
319
+ * screen, or stepped aside for a user scroll. A step still entering reads the new value when it
320
+ * presents, and a frozen one keeps interaction off until `retarget()` restores it. The indicator
321
+ * fades in or out instead of snapping.
322
+ */
323
+ private syncInteraction;
195
324
  private isFocusInsideTarget;
196
325
  /**
197
326
  * Resumes a presentation frozen by `freezeForDisconnectedTarget` on its new
@@ -200,7 +329,7 @@ export declare class DomTourViewDriver<T> implements TourViewDriver<T> {
200
329
  * and popover to the new rect on the next frame. Deliberately skips
201
330
  * `appear()` (no re-entrance animation) and `activateFocus()` (focus stays
202
331
  * where the user left it), only reclaiming it if it was on the target that
203
- * just disappeared.
332
+ * just disappeared and the step auto focuses.
204
333
  */
205
334
  retarget(step: ActiveStep<T>, signal: AbortSignal): Promise<void>;
206
335
  /**
@@ -227,6 +356,8 @@ export declare class DomTourViewDriver<T> implements TourViewDriver<T> {
227
356
  * lets the backdrop appear while the page is still travelling.
228
357
  */
229
358
  private beginTargetScroll;
359
+ /** Scrolls the target in as the step asks, and returns the settle wait as `beginTargetScroll` does. */
360
+ private scrollTargetIntoView;
230
361
  /**
231
362
  * Resolves once the scroller has held still for a couple of frames.
232
363
  *
@@ -1,5 +1,7 @@
1
1
  import type { IndicatorOptions, OverlayOptions, PopoverOptions } from "../types";
2
2
  export interface TourElementStep {
3
+ /** No target: the popover is centered and the backdrop has no cutout. */
4
+ readonly detached?: boolean;
3
5
  readonly indicator?: IndicatorOptions;
4
6
  readonly overlay?: OverlayOptions;
5
7
  readonly popover?: PopoverOptions;
@@ -12,6 +14,13 @@ export default abstract class GlowTourElement {
12
14
  disabled?: boolean;
13
15
  } | undefined;
14
16
  private readonly animations;
17
+ /**
18
+ * Animations that ran to completion and whose `fill: "forwards"` still applies.
19
+ *
20
+ * They keep overriding the element's inline styles, so a presentation that writes its state
21
+ * without animating would be painted with the previous animation's last frame instead.
22
+ */
23
+ private readonly filledAnimations;
15
24
  private readonly cancelledAnimations;
16
25
  private released;
17
26
  constructor(element: HTMLElement | SVGSVGElement, options?: {
@@ -49,12 +58,25 @@ export default abstract class GlowTourElement {
49
58
  */
50
59
  private _finishWhileDocumentHidden;
51
60
  protected _waitForAnimation(animation: Animation): Promise<boolean>;
52
- protected abstract _disappear(): Promise<void>;
61
+ /**
62
+ * Drops what finished animations still impose on the element.
63
+ *
64
+ * Every animation is followed by the inline styles that record the state it landed on, so
65
+ * releasing the fill leaves the element exactly as it looks. Without this, a fade-out that
66
+ * finished keeps forcing `opacity: 0` over the inline `opacity: 1` of the next presentation,
67
+ * and an unanimated one never starts an animation of its own to take the fill over.
68
+ */
69
+ protected _releaseFilledAnimations(): void;
70
+ protected abstract _disappear(hideFromAssistiveTechnology?: boolean): Promise<void>;
53
71
  protected abstract _getNextStyles(position: DOMRect, step: TourElementStep): Keyframe;
54
72
  abstract updatePosition(nextPosition: DOMRect, step: TourElementStep): void;
55
73
  abstract initializeProps(): void;
56
74
  getElement(): HTMLElement | SVGSVGElement | null;
57
- disappear(): Promise<void>;
75
+ /**
76
+ * Fades the element out. The popover stays exposed to assistive technology when passed `false`,
77
+ * for a step change: its live region then announces the new step and focus stays in it.
78
+ */
79
+ disappear(hideFromAssistiveTechnology?: boolean): Promise<void>;
58
80
  release(): void;
59
81
  cancelAnimations(): void;
60
82
  protected _cancelAnimation(animation: Animation): void;
@@ -14,18 +14,29 @@ export default class PopoverElement extends GlowTourElement {
14
14
  private readonly mutationLease;
15
15
  protected _getNextStyles(position: DOMRect, step: TourElementStep): Keyframe;
16
16
  resolvePosition(targetPosition: DOMRect, step: TourElementStep): PopoverPosition;
17
+ /**
18
+ * Marks the popover as waiting for the next step's target. Written through the mutation lease,
19
+ * like every other attribute the popover owns, so unbinding the element gives it back clean.
20
+ */
21
+ setAwaitingTarget(awaiting: boolean): void;
17
22
  private _centerPosition;
18
23
  private _applyPositionState;
19
24
  /**
20
25
  * Fades the popover in at `nextPosition` and commits that placement.
21
26
  *
22
- * The outgoing half of a step change is {@link disappear}, deliberately kept
23
- * separate: between the two, the caller swaps the step's content while the
24
- * popover is off screen, and — when the step scrolls — waits for the scroll
25
- * to settle so this entrance reads a rect that will not move again.
27
+ * The outgoing half of a step change is `disappear(false)`, which keeps the
28
+ * popover exposed to assistive technology, deliberately kept separate: between the two, the caller swaps the step's
29
+ * content while the popover is faded out, and — when the step scrolls — waits
30
+ * for the scroll to settle so this entrance reads a rect that will not move
31
+ * again.
26
32
  */
27
33
  present(nextPosition: DOMRect, step: TourElementStep): Promise<void>;
28
- initializeProps(): void;
34
+ /**
35
+ * Resets the popover to its idle presentation. A step change that replaces a
36
+ * visible popover passes `false`: the popover stays exposed so its live
37
+ * region announces the new step and focus stays in it.
38
+ */
39
+ initializeProps(hideFromAssistiveTechnology?: boolean): void;
29
40
  updatePosition(nextPosition: DOMRect, step: TourElementStep, onReposition?: (reposition: Promise<void>) => void): ResolvedPlacement;
30
41
  cancelAnimations(): void;
31
42
  private _flushReposition;
@@ -34,8 +45,14 @@ export default class PopoverElement extends GlowTourElement {
34
45
  private _applyArrowStyles;
35
46
  private _applyArrowStyle;
36
47
  _appear(position: DOMRect, step: TourElementStep): Promise<void>;
37
- _disappear(): Promise<void>;
48
+ _disappear(hideFromAssistiveTechnology?: boolean): Promise<void>;
38
49
  protected _release(): void;
39
50
  private _applyVisibleState;
51
+ /**
52
+ * Out of sight but still exposed to assistive technology. Pointer input is blocked the way
53
+ * `inert` blocks it: the controller ignores commands mid-transition, so a click must not look
54
+ * accepted.
55
+ */
56
+ private _applyFadedState;
40
57
  private _applyHiddenState;
41
58
  }
package/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export type { EventName, WorkflowBuilder, WorkflowStepBuilder } from "./builder";
2
2
  export type { ReadonlyStartOptions, ReadonlyStepProps, WorkflowDefinition, WorkflowStepDefinition, } from "./definition";
3
3
  export { createGlowTour } from "./runtime/tour-controller";
4
- export type { AnimationOptions, BaseOptions, BeforeActionStepContext, EventHandler, GlowTour, GlowTourOptions, IndicatorOptions, LifecycleHookContext, OverlayOptions, PopoverArrowOptions, PopoverOptions, PrimitiveValue, ReadonlyTourState, ResolvedPlacement, ScrollOptions, StartOptions, StepAction, StepActionInstruction, StepActionResult, StepBehavior, StepContext, StepEventContext, StepParameters, StepPropsStore, StepPropsUpdate, StepTransitionAction, TargetResolver, TargetResolverContext, TourCurrentStep, TourDirection, TourEvent, TourEventListener, TourEventSource, TourEventType, TourState, TourStatus, TryOrderOptions, WaitUntilOptions, } from "./types";
4
+ export type { AnimationOptions, BaseOptions, ClassValue, GlowTour, GlowTourOptions, IndicatorOptions, LifecycleHookContext, MissingTargetOptions, OverlayOptions, PopoverArrowOptions, PopoverOptions, PrimitiveValue, ReadonlyTourState, ResolvedPlacement, ScrollOptions, StartOptions, StepAction, StepActionInstruction, StepActionResult, StepBehavior, StepContext, StepEventContext, StepHookAction, StepHookContext, StepParameters, StepPropsPatch, StepPropsStore, StepPropsUpdate, TargetEventHandler, TargetResolver, TargetResolverContext, TourClassNames, TourControl, TourControlState, TourControls, TourCurrentStep, TourDirection, TourEvent, TourEventListener, TourEventSource, TourEventType, TourState, TourStatus, TryOrderOptions, WaitUntilOptions, } from "./types";