@steppy/player 0.0.0-stage → 0.4.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.
- package/LICENSE +202 -0
- package/NOTICE +9 -0
- package/README.md +190 -2
- package/THIRD_PARTY_LICENSES.txt +158 -0
- package/dist/steppy-cursor-legacy.js +2 -0
- package/dist/steppy-cursor-legacy.js.map +1 -0
- package/dist/steppy-player.js +5419 -0
- package/dist/steppy-player.js.map +1 -0
- package/dist/steppy-tour.js +2164 -0
- package/dist/steppy-tour.js.map +1 -0
- package/dist/steppy-video.js +472 -0
- package/dist/steppy-video.js.map +1 -0
- package/dist/types/cursor/animator.d.ts +135 -0
- package/dist/types/cursor/types.d.ts +61 -0
- package/dist/types/i18n/ui-strings.d.ts +48 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/interaction/positioning.d.ts +22 -0
- package/dist/types/manifest/content.d.ts +24 -0
- package/dist/types/manifest/index.d.ts +3 -0
- package/dist/types/manifest/parse.d.ts +23 -0
- package/dist/types/manifest/schema.d.ts +2895 -0
- package/dist/types/steppy-guide.d.ts +195 -0
- package/dist/types/templates/base-styles.d.ts +15 -0
- package/dist/types/templates/controls.d.ts +17 -0
- package/dist/types/templates/index.d.ts +23 -0
- package/dist/types/templates/inline.d.ts +10 -0
- package/dist/types/templates/overlay.d.ts +10 -0
- package/dist/types/templates/sidebar.d.ts +19 -0
- package/dist/types/templates/types.d.ts +76 -0
- package/dist/types/tour/api.d.ts +54 -0
- package/dist/types/tour/contract.d.ts +54 -0
- package/dist/types/tour/dom.d.ts +43 -0
- package/dist/types/tour/engine.d.ts +157 -0
- package/dist/types/tour/events.d.ts +42 -0
- package/dist/types/tour/host.d.ts +153 -0
- package/dist/types/tour/index.d.ts +15 -0
- package/dist/types/tour/layer.d.ts +16 -0
- package/dist/types/tour/loader.d.ts +70 -0
- package/dist/types/tour/resolver.d.ts +30 -0
- package/dist/types/tour/routes.d.ts +40 -0
- package/dist/types/tour/signature.d.ts +16 -0
- package/dist/types/tour/storage.d.ts +28 -0
- package/dist/types/tour/strings.d.ts +42 -0
- package/dist/types/tour/styles.d.ts +13 -0
- package/dist/types/tour/templates.d.ts +47 -0
- package/dist/types/tour/tracker.d.ts +45 -0
- package/dist/types/tour/triggers.d.ts +52 -0
- package/dist/types/tour/validate.d.ts +82 -0
- package/dist/types/ui/markdown.d.ts +58 -0
- package/dist/types/ui/names.d.ts +18 -0
- package/dist/types/ui/placement.d.ts +59 -0
- package/dist/types/ui/strings.d.ts +31 -0
- package/dist/types/ui/tokens.d.ts +57 -0
- package/package.json +70 -4
- package/schema/README.md +19 -0
- package/schema/guide.schema.json +427 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tour state machine, as a pure reducer: ``reduce(state, event)`` returns
|
|
3
|
+
* the next state and the effects the host must carry out (load, navigate, wait
|
|
4
|
+
* for an element, emit an event, persist the resume record, ...). No DOM, no
|
|
5
|
+
* timers — every transition is a unit test.
|
|
6
|
+
*
|
|
7
|
+
* Async results come back as events carrying the ``token`` they were started
|
|
8
|
+
* under; ``token`` changes whenever a step or phase change invalidates pending
|
|
9
|
+
* work, so a late answer from an abandoned wait is dropped instead of moving
|
|
10
|
+
* the tour.
|
|
11
|
+
*
|
|
12
|
+
* Phases: ``idle`` → ``loading`` → (``entering``) → ``resolving`` →
|
|
13
|
+
* ``showing``; a target that does not appear in time puts the step in
|
|
14
|
+
* ``fallback`` (the "can't find this element" popover, with Skip / End); a
|
|
15
|
+
* target that disappears while shown goes ``lost``, then ``paused`` (the pill)
|
|
16
|
+
* when it stays away.
|
|
17
|
+
*/
|
|
18
|
+
import type { TourErrorReason } from './contract.js';
|
|
19
|
+
import type { Issue, TourFile } from './validate.js';
|
|
20
|
+
export type Phase = 'idle' | 'loading' | 'entering' | 'resolving' | 'showing' | 'lost' | 'fallback' | 'paused';
|
|
21
|
+
export type StepReason = 'start' | 'advance' | 'jump' | 'skip' | 'resume' | 'recovered';
|
|
22
|
+
export type DismissVia = 'close' | 'escape' | 'end' | 'api' | 'replaced' | 'disabled';
|
|
23
|
+
export type TourSourceKind = 'bundled' | 'remote' | 'custom';
|
|
24
|
+
export type TriggerKind = 'click' | 'input-commit' | 'change' | 'visible';
|
|
25
|
+
export type WaitMode = 'resolve' | 'fallback' | 'lost' | 'paused' | 'visible';
|
|
26
|
+
export interface Loaded {
|
|
27
|
+
tour: TourFile;
|
|
28
|
+
source: TourSourceKind;
|
|
29
|
+
/** Per step: does it apply to the current layout (``tours.layouts``)? */
|
|
30
|
+
applicable: readonly boolean[];
|
|
31
|
+
start: number;
|
|
32
|
+
resumed: boolean;
|
|
33
|
+
/** The entry route must be navigated to before the first step. */
|
|
34
|
+
navigate: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface TourErrorDetail {
|
|
37
|
+
reason: TourErrorReason;
|
|
38
|
+
fatal: boolean;
|
|
39
|
+
message: string;
|
|
40
|
+
tourId?: string;
|
|
41
|
+
buildId?: string;
|
|
42
|
+
locale?: string;
|
|
43
|
+
stepId?: string;
|
|
44
|
+
index?: number;
|
|
45
|
+
testId?: string;
|
|
46
|
+
kind?: 'target' | 'waitFor';
|
|
47
|
+
missing?: string[];
|
|
48
|
+
issues?: Issue[];
|
|
49
|
+
}
|
|
50
|
+
export interface EngineState {
|
|
51
|
+
readonly phase: Phase;
|
|
52
|
+
readonly token: number;
|
|
53
|
+
readonly loaded: Loaded | null;
|
|
54
|
+
readonly index: number;
|
|
55
|
+
/** The ``tour-step`` reason to report when the pending step shows. */
|
|
56
|
+
readonly reason: StepReason;
|
|
57
|
+
readonly fieldNonEmpty: boolean;
|
|
58
|
+
}
|
|
59
|
+
export type EngineEvent = {
|
|
60
|
+
type: 'REQUEST';
|
|
61
|
+
} | {
|
|
62
|
+
type: 'LOADED';
|
|
63
|
+
loaded: Loaded;
|
|
64
|
+
} | {
|
|
65
|
+
type: 'LOAD_FAILED';
|
|
66
|
+
error: TourErrorDetail;
|
|
67
|
+
} | {
|
|
68
|
+
type: 'ENTER_DONE';
|
|
69
|
+
token: number;
|
|
70
|
+
} | {
|
|
71
|
+
type: 'ENTER_FAILED';
|
|
72
|
+
token: number;
|
|
73
|
+
message: string;
|
|
74
|
+
} | {
|
|
75
|
+
type: 'RESOLVED';
|
|
76
|
+
token: number;
|
|
77
|
+
fieldNonEmpty?: boolean;
|
|
78
|
+
} | {
|
|
79
|
+
type: 'RESOLVE_TIMEOUT';
|
|
80
|
+
token: number;
|
|
81
|
+
testId: string | null;
|
|
82
|
+
kind: 'target' | 'waitFor';
|
|
83
|
+
} | {
|
|
84
|
+
type: 'TRIGGER';
|
|
85
|
+
token: number;
|
|
86
|
+
kind: TriggerKind;
|
|
87
|
+
} | {
|
|
88
|
+
type: 'NEXT';
|
|
89
|
+
token?: number;
|
|
90
|
+
} | {
|
|
91
|
+
type: 'SKIP';
|
|
92
|
+
token: number;
|
|
93
|
+
} | {
|
|
94
|
+
type: 'FIELD';
|
|
95
|
+
token: number;
|
|
96
|
+
nonEmpty: boolean;
|
|
97
|
+
} | {
|
|
98
|
+
type: 'JUMP';
|
|
99
|
+
token: number;
|
|
100
|
+
to: number;
|
|
101
|
+
performed: boolean;
|
|
102
|
+
} | {
|
|
103
|
+
type: 'TARGET_LOST';
|
|
104
|
+
token: number;
|
|
105
|
+
} | {
|
|
106
|
+
type: 'TARGET_BACK';
|
|
107
|
+
token: number;
|
|
108
|
+
} | {
|
|
109
|
+
type: 'LOST_TIMEOUT';
|
|
110
|
+
token: number;
|
|
111
|
+
} | {
|
|
112
|
+
type: 'RESUME';
|
|
113
|
+
token: number;
|
|
114
|
+
} | {
|
|
115
|
+
type: 'DISMISS';
|
|
116
|
+
via: DismissVia;
|
|
117
|
+
reason?: string;
|
|
118
|
+
} | {
|
|
119
|
+
type: 'LOCALE';
|
|
120
|
+
tour: TourFile;
|
|
121
|
+
};
|
|
122
|
+
export type Effect = {
|
|
123
|
+
type: 'load';
|
|
124
|
+
} | {
|
|
125
|
+
type: 'emit';
|
|
126
|
+
name: 'tour-start' | 'tour-step' | 'tour-complete' | 'tour-dismiss' | 'tour-offpath' | 'tour-error';
|
|
127
|
+
detail: object;
|
|
128
|
+
} | {
|
|
129
|
+
type: 'persist';
|
|
130
|
+
index: number;
|
|
131
|
+
} | {
|
|
132
|
+
type: 'clearPersist';
|
|
133
|
+
} | {
|
|
134
|
+
type: 'navigate';
|
|
135
|
+
token: number;
|
|
136
|
+
} | {
|
|
137
|
+
type: 'wait';
|
|
138
|
+
mode: WaitMode;
|
|
139
|
+
index: number;
|
|
140
|
+
token: number;
|
|
141
|
+
} | {
|
|
142
|
+
type: 'focus';
|
|
143
|
+
} | {
|
|
144
|
+
type: 'teardown';
|
|
145
|
+
};
|
|
146
|
+
export interface Transition {
|
|
147
|
+
state: EngineState;
|
|
148
|
+
effects: Effect[];
|
|
149
|
+
}
|
|
150
|
+
export declare function initialState(): EngineState;
|
|
151
|
+
export declare const isActive: (s: EngineState) => boolean;
|
|
152
|
+
/** Position among applicable steps (0-based) and their count — what progress shows. */
|
|
153
|
+
export declare function position(s: EngineState, index?: number): {
|
|
154
|
+
index: number;
|
|
155
|
+
total: number;
|
|
156
|
+
};
|
|
157
|
+
export declare function reduce(s: EngineState, e: EngineEvent): Transition;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The six DOM events the overlay dispatches on its host element — bubbling
|
|
3
|
+
* and composed, JSON-only details. Colon-free names on purpose: Angular
|
|
4
|
+
* templates read ``(a:b)`` as a global event target.
|
|
5
|
+
*/
|
|
6
|
+
import type { AdvanceKind } from './contract.js';
|
|
7
|
+
import type { DismissVia, StepReason, TourErrorDetail, TourSourceKind } from './engine.js';
|
|
8
|
+
export interface TourEventMeta {
|
|
9
|
+
tourId: string;
|
|
10
|
+
buildId: string;
|
|
11
|
+
locale: string;
|
|
12
|
+
}
|
|
13
|
+
export interface TourStepPosition extends TourEventMeta {
|
|
14
|
+
stepId: string;
|
|
15
|
+
/** 0-based, counting only the steps that apply to the current layout. */
|
|
16
|
+
index: number;
|
|
17
|
+
total: number;
|
|
18
|
+
}
|
|
19
|
+
export interface TourEventDetailMap {
|
|
20
|
+
'tour-start': TourEventMeta & {
|
|
21
|
+
total: number;
|
|
22
|
+
source: TourSourceKind;
|
|
23
|
+
resumed: boolean;
|
|
24
|
+
entryNavigated: boolean;
|
|
25
|
+
};
|
|
26
|
+
'tour-step': TourStepPosition & {
|
|
27
|
+
advance: AdvanceKind;
|
|
28
|
+
reason: StepReason;
|
|
29
|
+
};
|
|
30
|
+
'tour-complete': TourEventMeta & {
|
|
31
|
+
total: number;
|
|
32
|
+
};
|
|
33
|
+
'tour-dismiss': TourStepPosition & {
|
|
34
|
+
via: DismissVia;
|
|
35
|
+
reason?: string;
|
|
36
|
+
};
|
|
37
|
+
'tour-offpath': TourStepPosition & {
|
|
38
|
+
reason: 'target-lost';
|
|
39
|
+
};
|
|
40
|
+
'tour-error': TourErrorDetail;
|
|
41
|
+
}
|
|
42
|
+
export declare function emitTourEvent<K extends keyof TourEventDetailMap>(host: EventTarget, name: K, detail: TourEventDetailMap[K]): void;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ``<steppy-tour-host>`` — mount it once in the app shell and call
|
|
3
|
+
* ``start(tourId)``. The element interprets the engine's effects (``engine.ts``)
|
|
4
|
+
* and owns the browser-facing pieces: loader, resolver, tracker, triggers,
|
|
5
|
+
* focus, the live region and the resume record. It renders nothing while no
|
|
6
|
+
* tour runs.
|
|
7
|
+
*
|
|
8
|
+
* Two switches keep it out of the way: the global
|
|
9
|
+
* ``__STEPPY_TOURS_DISABLED__`` (set by generated E2E tests so an app that
|
|
10
|
+
* auto-starts a tour cannot cover what a test clicks; read at call time) and
|
|
11
|
+
* the ``disabled`` property. Either makes ``start()`` a no-op.
|
|
12
|
+
*/
|
|
13
|
+
import { LitElement, nothing, type PropertyValues } from 'lit';
|
|
14
|
+
import type { TourHostApi, TourNavigate, TourPosition, TourStartOptions } from './api.js';
|
|
15
|
+
import { type DomAdapter } from './dom.js';
|
|
16
|
+
import { type TourEventDetailMap } from './events.js';
|
|
17
|
+
import { type TourSource } from './loader.js';
|
|
18
|
+
import { type TourStringOverrides } from './strings.js';
|
|
19
|
+
export interface SteppyTourHost {
|
|
20
|
+
addEventListener<K extends keyof TourEventDetailMap>(type: K, listener: (this: SteppyTourHost, ev: CustomEvent<TourEventDetailMap[K]>) => void, options?: boolean | AddEventListenerOptions): void;
|
|
21
|
+
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
|
22
|
+
}
|
|
23
|
+
export declare class SteppyTourHost extends LitElement implements TourHostApi {
|
|
24
|
+
static styles: import("lit").CSSResult;
|
|
25
|
+
static properties: {
|
|
26
|
+
manifest: {
|
|
27
|
+
type: StringConstructor;
|
|
28
|
+
};
|
|
29
|
+
fallbackManifest: {
|
|
30
|
+
type: StringConstructor;
|
|
31
|
+
attribute: string;
|
|
32
|
+
};
|
|
33
|
+
buildId: {
|
|
34
|
+
type: StringConstructor;
|
|
35
|
+
attribute: string;
|
|
36
|
+
};
|
|
37
|
+
locale: {
|
|
38
|
+
type: StringConstructor;
|
|
39
|
+
};
|
|
40
|
+
guideOrigin: {
|
|
41
|
+
type: StringConstructor;
|
|
42
|
+
attribute: string;
|
|
43
|
+
};
|
|
44
|
+
baseZIndex: {
|
|
45
|
+
type: NumberConstructor;
|
|
46
|
+
attribute: string;
|
|
47
|
+
};
|
|
48
|
+
targetTimeout: {
|
|
49
|
+
type: NumberConstructor;
|
|
50
|
+
attribute: string;
|
|
51
|
+
};
|
|
52
|
+
disabled: {
|
|
53
|
+
type: BooleanConstructor;
|
|
54
|
+
reflect: boolean;
|
|
55
|
+
};
|
|
56
|
+
source: {
|
|
57
|
+
attribute: boolean;
|
|
58
|
+
};
|
|
59
|
+
navigate: {
|
|
60
|
+
attribute: boolean;
|
|
61
|
+
};
|
|
62
|
+
strings: {
|
|
63
|
+
attribute: boolean;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
/** Test seam: geometry and timing. */
|
|
67
|
+
static dom: DomAdapter;
|
|
68
|
+
manifest: string;
|
|
69
|
+
fallbackManifest: string;
|
|
70
|
+
buildId: string;
|
|
71
|
+
locale: string;
|
|
72
|
+
guideOrigin: string;
|
|
73
|
+
baseZIndex: number;
|
|
74
|
+
targetTimeout: number;
|
|
75
|
+
disabled: boolean;
|
|
76
|
+
source: TourSource | null;
|
|
77
|
+
navigate: TourNavigate | null;
|
|
78
|
+
strings: TourStringOverrides | undefined;
|
|
79
|
+
private _s;
|
|
80
|
+
private _queue;
|
|
81
|
+
private _dispatching;
|
|
82
|
+
private _run;
|
|
83
|
+
private _cache;
|
|
84
|
+
private _resolver;
|
|
85
|
+
private _cancelWait;
|
|
86
|
+
private _target;
|
|
87
|
+
private _tracker;
|
|
88
|
+
private _detach;
|
|
89
|
+
private _change;
|
|
90
|
+
private _shownKey;
|
|
91
|
+
private _shownAt;
|
|
92
|
+
private _pressToken;
|
|
93
|
+
private _focusPending;
|
|
94
|
+
private _appFocus;
|
|
95
|
+
private _swapSeq;
|
|
96
|
+
/** While a tour runs: an app that switches language through ``<html lang>`` alone. */
|
|
97
|
+
private _lang;
|
|
98
|
+
private _policy;
|
|
99
|
+
private _handlers;
|
|
100
|
+
private get _dom();
|
|
101
|
+
get isActive(): boolean;
|
|
102
|
+
get current(): TourPosition | null;
|
|
103
|
+
start(tourId: string, options?: TourStartOptions): Promise<boolean>;
|
|
104
|
+
stop(reason?: string): void;
|
|
105
|
+
next(): void;
|
|
106
|
+
connectedCallback(): void;
|
|
107
|
+
disconnectedCallback(): void;
|
|
108
|
+
protected willUpdate(changed: PropertyValues<this>): void;
|
|
109
|
+
protected updated(changed: PropertyValues<this>): void;
|
|
110
|
+
protected firstUpdated(): void;
|
|
111
|
+
protected render(): import("lit-html").TemplateResult | typeof nothing;
|
|
112
|
+
private _start;
|
|
113
|
+
private _fail;
|
|
114
|
+
private _tryResume;
|
|
115
|
+
private _manifestKey;
|
|
116
|
+
/** The host's ``locale``, else the page's ``<html lang>``. */
|
|
117
|
+
private _preferredLocale;
|
|
118
|
+
private _cfg;
|
|
119
|
+
/** Reload the running tour in the preferred locale; the last request wins. */
|
|
120
|
+
private _swapLocale;
|
|
121
|
+
private _dispatch;
|
|
122
|
+
private _effect;
|
|
123
|
+
private _persist;
|
|
124
|
+
private _enter;
|
|
125
|
+
private _timeout;
|
|
126
|
+
private _wait;
|
|
127
|
+
private _layer;
|
|
128
|
+
private _bubble;
|
|
129
|
+
private _afterRender;
|
|
130
|
+
/** Manual and fallback steps take focus — once placed: a hidden bubble cannot be focused. */
|
|
131
|
+
private _applyPendingFocus;
|
|
132
|
+
private _onStepShown;
|
|
133
|
+
private _announce;
|
|
134
|
+
private _makeTracker;
|
|
135
|
+
private _preferredSide;
|
|
136
|
+
private _nextTargetRect;
|
|
137
|
+
/** The app's open dialogs (the overlay's own bubble is in the shadow root, so never one of them). */
|
|
138
|
+
private _openDialogs;
|
|
139
|
+
private _ensureTriggers;
|
|
140
|
+
private _onFocusIn;
|
|
141
|
+
/** Our buttons: a click counts only if it was pressed on this step (or, by keyboard, after a short grace). */
|
|
142
|
+
private _guarded;
|
|
143
|
+
private _deepActive;
|
|
144
|
+
private _teardown;
|
|
145
|
+
private _link;
|
|
146
|
+
}
|
|
147
|
+
/** Register ``<steppy-tour-host>`` once; a second call (or a second copy of the bundle) is a no-op. */
|
|
148
|
+
export declare function defineTourHost(): void;
|
|
149
|
+
declare global {
|
|
150
|
+
interface HTMLElementTagNameMap {
|
|
151
|
+
'steppy-tour-host': SteppyTourHost;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ``@steppy/player/tour`` — the in-app tour overlay.
|
|
3
|
+
*
|
|
4
|
+
* Importing this module registers ``<steppy-tour-host>`` (once, even if two
|
|
5
|
+
* copies of the bundle load). It touches ``HTMLElement`` at load, so a
|
|
6
|
+
* server-rendered app imports it in the browser only.
|
|
7
|
+
*/
|
|
8
|
+
export { SteppyTourHost, defineTourHost } from './host.js';
|
|
9
|
+
export type { TourHostApi, TourNavigate, TourNavigateContext, TourPosition, TourSource, TourStartOptions, TourStringOverrides } from './api.js';
|
|
10
|
+
export type { TourEventDetailMap, TourEventMeta, TourStepPosition } from './events.js';
|
|
11
|
+
export type { DismissVia, StepReason, TourErrorDetail, TourSourceKind } from './engine.js';
|
|
12
|
+
export { ADVANCE_KINDS, PLACEMENTS, TOUR_ERROR_REASONS, TOUR_EVENTS, TOUR_PARTS, TOUR_SCHEMA_VERSION, type AdvanceKind, type Placement, type TourErrorReason, type TourEventName, type TourPart, } from './contract.js';
|
|
13
|
+
export { TOUR_HOST_TAG, TOURS_KILL_SWITCH_GLOBAL } from '../ui/names.js';
|
|
14
|
+
export { DEFAULT_TOUR_STRINGS, type TourStrings } from './strings.js';
|
|
15
|
+
export { validateManifest, validateTour, type TourFile, type TourManifest, type TourStep } from './validate.js';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stacking. Where the popover API exists the layer is a ``popover="manual"``
|
|
3
|
+
* element in the browser's top layer — above any z-index the app uses — and
|
|
4
|
+
* every step re-raises it (hide, then show) so it stays above an app dialog
|
|
5
|
+
* opened after it. Elsewhere the layer falls back to ``z-index: var(--_pw-z)``.
|
|
6
|
+
*
|
|
7
|
+
* Known limit: a native ``<dialog>`` opened with ``showModal()`` makes
|
|
8
|
+
* everything outside it inert, the layer included; re-raising does not lift
|
|
9
|
+
* that. Steps advanced by the app's own events still work, the overlay's
|
|
10
|
+
* buttons do not until the dialog closes.
|
|
11
|
+
*/
|
|
12
|
+
import type { DomAdapter } from './dom.js';
|
|
13
|
+
export declare function isLayerOpen(el: HTMLElement): boolean;
|
|
14
|
+
export declare function showLayer(el: HTMLElement, dom: DomAdapter): void;
|
|
15
|
+
/** Move the layer to the top of the top layer, keeping the app's focus where it was. */
|
|
16
|
+
export declare function raiseLayer(el: HTMLElement, dom: DomAdapter): void;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads a tour: the manifest(s), then the tour file for the best locale.
|
|
3
|
+
*
|
|
4
|
+
* Two manifests can be configured. ``fallbackManifest`` is the copy bundled
|
|
5
|
+
* with the app (same origin only). ``manifest`` may point at a remote guide
|
|
6
|
+
* site and may contain ``{buildId}``, filled with the app's build id — the
|
|
7
|
+
* host's ``buildId`` property, else the bundled manifest's — so remote mode
|
|
8
|
+
* only ever fetches the tours of the app's own build. The remote copy is
|
|
9
|
+
* tried first and the bundled copy is the fallback; a remote failure is only
|
|
10
|
+
* logged.
|
|
11
|
+
*
|
|
12
|
+
* Cross-origin requests send no credentials, refuse redirects, and pass the
|
|
13
|
+
* bundled manifest's SRI hash when both copies are the same build, so a
|
|
14
|
+
* tampered remote file is rejected by the browser. Every body is read with a
|
|
15
|
+
* byte cap and an HTML sniff (an SPA answering a missing JSON file with its
|
|
16
|
+
* index page is a load failure, not bad data). Nothing a cross-origin
|
|
17
|
+
* manifest says widens what the bundled one allows: when the bundled manifest
|
|
18
|
+
* is for the same build, any other manifest (remote, or a same-origin proxy)
|
|
19
|
+
* only locates files — the tour, the locale and the SRI hash all come from the
|
|
20
|
+
* bundle, so a tour, locale or case variant the bundle does not pin is never
|
|
21
|
+
* fetched from it.
|
|
22
|
+
*/
|
|
23
|
+
import type { TourSourceKind } from './engine.js';
|
|
24
|
+
import { type FailReason, type Issue, type Result, type TourFile, type TourManifest } from './validate.js';
|
|
25
|
+
export type TourSource = (tourId: string, locale: string, ctx: {
|
|
26
|
+
buildId: string;
|
|
27
|
+
manifestUrl: string;
|
|
28
|
+
}) => Promise<string | Response | object> | string | Response | object;
|
|
29
|
+
export interface LoaderConfig {
|
|
30
|
+
manifest: string;
|
|
31
|
+
fallbackManifest: string;
|
|
32
|
+
buildId: string;
|
|
33
|
+
/** The locale asked for (host ``locale``, else the page's ``lang``). */
|
|
34
|
+
locale: string;
|
|
35
|
+
/** The host's ``guide-origin``: the only origin trusted without a manifest's say-so. */
|
|
36
|
+
guideOrigin: string;
|
|
37
|
+
source?: TourSource | null;
|
|
38
|
+
fetch?: typeof fetch;
|
|
39
|
+
origin?: string;
|
|
40
|
+
baseURI?: string;
|
|
41
|
+
timeoutMs?: number;
|
|
42
|
+
/** Manifests per resolved URL, kept by the host for the page's lifetime. */
|
|
43
|
+
cache?: Map<string, Promise<ManifestLoad>>;
|
|
44
|
+
}
|
|
45
|
+
export interface LoadOk {
|
|
46
|
+
ok: true;
|
|
47
|
+
manifest: TourManifest;
|
|
48
|
+
manifestUrl: string;
|
|
49
|
+
tour: TourFile;
|
|
50
|
+
locale: string;
|
|
51
|
+
source: TourSourceKind;
|
|
52
|
+
testIdAttribute: string;
|
|
53
|
+
guideOrigin: string | null;
|
|
54
|
+
}
|
|
55
|
+
export interface LoadErr {
|
|
56
|
+
ok: false;
|
|
57
|
+
reason: FailReason;
|
|
58
|
+
message: string;
|
|
59
|
+
issues?: Issue[];
|
|
60
|
+
}
|
|
61
|
+
export type ManifestLoad = (Result<TourManifest> & {
|
|
62
|
+
url: string;
|
|
63
|
+
}) | (LoadErr & {
|
|
64
|
+
url: string;
|
|
65
|
+
});
|
|
66
|
+
/** Exact tag, then its base language, then the manifest's default, then the first file. */
|
|
67
|
+
export declare function pickLocale(available: readonly string[], requested: string, fallback: string): string;
|
|
68
|
+
/** The body as text, refusing more than ``cap`` bytes and invalid UTF-8. */
|
|
69
|
+
export declare function readCapped(res: Response, cap: number): Promise<string>;
|
|
70
|
+
export declare function loadTour(cfg: LoaderConfig, tourId: string): Promise<LoadOk | LoadErr>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test-id resolution: ``[data-testid="<id>"]`` (the manifest's attribute)
|
|
3
|
+
* against the document — light DOM only — skipping the overlay itself, first
|
|
4
|
+
* *visible* match wins.
|
|
5
|
+
*
|
|
6
|
+
* Waiting is event-driven and only runs while the tour needs an element: a
|
|
7
|
+
* document MutationObserver (filtered to attributes that can change
|
|
8
|
+
* visibility, coalesced to one check per frame) plus a slow poll for changes
|
|
9
|
+
* no mutation reports (a stylesheet swap, a CSS transition finishing). Time
|
|
10
|
+
* spent in a background tab does not count against the deadline.
|
|
11
|
+
*/
|
|
12
|
+
import type { DomAdapter } from './dom.js';
|
|
13
|
+
export interface WaitRequest {
|
|
14
|
+
ids: readonly string[];
|
|
15
|
+
/** Milliseconds to wait, or null for no deadline. */
|
|
16
|
+
timeoutMs: number | null;
|
|
17
|
+
onFound(found: Map<string, Element>): void;
|
|
18
|
+
/** Called with the first id still missing. */
|
|
19
|
+
onTimeout(missing: string): void;
|
|
20
|
+
}
|
|
21
|
+
export interface Resolver {
|
|
22
|
+
find(id: string): Element | null;
|
|
23
|
+
/** Start waiting; returns the cancel function. */
|
|
24
|
+
wait(req: WaitRequest): () => void;
|
|
25
|
+
}
|
|
26
|
+
export declare function createResolver(opts: {
|
|
27
|
+
attr: string;
|
|
28
|
+
host: Element;
|
|
29
|
+
dom: DomAdapter;
|
|
30
|
+
}): Resolver;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entry routes: match the current location against a tour's ``entry.route``
|
|
3
|
+
* (``/invoices/:invoiceId``), fill its parameters, and translate between app
|
|
4
|
+
* paths (relative to ``<base href>``) and document paths.
|
|
5
|
+
*
|
|
6
|
+
* The compiler strips the app's full URL (path included), so routes are
|
|
7
|
+
* base-relative; an app served under ``/app/`` has documents at
|
|
8
|
+
* ``/app/invoices`` for the route ``/invoices``. Both forms of the current
|
|
9
|
+
* path are tried, so an app without a base element works either way.
|
|
10
|
+
*/
|
|
11
|
+
export type RouteParams = Record<string, string>;
|
|
12
|
+
/** Parameters of ``path`` under ``pattern``, or null when it does not match. */
|
|
13
|
+
export declare function matchRoute(pattern: string, path: string): RouteParams | null;
|
|
14
|
+
/** The route's names, in order (``/a/:x/b/:y`` -> ``['x', 'y']``). */
|
|
15
|
+
export declare function routeParams(pattern: string): string[];
|
|
16
|
+
/** ``pattern`` with its parameters filled (URI-encoded), or the names missing a value. */
|
|
17
|
+
export declare function buildRoute(pattern: string, params: Record<string, unknown>): {
|
|
18
|
+
path: string;
|
|
19
|
+
} | {
|
|
20
|
+
missing: string[];
|
|
21
|
+
};
|
|
22
|
+
/** The document's base path (``/`` or ``/app/``); ``/`` when the base is cross-origin. */
|
|
23
|
+
export declare function basePath(baseURI: string, origin: string): string;
|
|
24
|
+
/** ``/app/invoices`` under base ``/app/`` -> ``/invoices``; paths outside the base are unchanged. */
|
|
25
|
+
export declare function toAppPath(documentPath: string, base: string): string;
|
|
26
|
+
/** ``/invoices`` under base ``/app/`` -> ``/app/invoices``. */
|
|
27
|
+
export declare function toDocumentPath(appPath: string, base: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* A link target the tour may follow in the same tab: a site-relative path —
|
|
30
|
+
* not protocol-relative (``//host``, ``/\host``), no backslash, whitespace or
|
|
31
|
+
* control characters.
|
|
32
|
+
*/
|
|
33
|
+
export declare function isSafeRelativeUrl(url: string): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* The same-tab href for a link in step text, or null. The URL is normalised
|
|
36
|
+
* first and the *result* checked: dot segments (``/.//host``, ``/a/..//host``,
|
|
37
|
+
* ``/%2e%2e//host``) collapse into a protocol-relative ``//host``, which the raw
|
|
38
|
+
* string never looked like.
|
|
39
|
+
*/
|
|
40
|
+
export declare function linkHref(url: string, base: string, origin: string): string | null;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Readers for the ``input`` and ``change`` advance kinds.
|
|
3
|
+
*
|
|
4
|
+
* ``valueSignature`` summarises everything a "pick a value" widget can change:
|
|
5
|
+
* native controls inside the target, ARIA state, and the visible label. A
|
|
6
|
+
* component library that sets a value programmatically fires no ``change``
|
|
7
|
+
* event (PrimeNG's select does exactly that), so the overlay compares
|
|
8
|
+
* signatures instead of listening for the event.
|
|
9
|
+
*/
|
|
10
|
+
export declare function valueSignature(target: Element): string;
|
|
11
|
+
/** True while the target, or anything in it, reports an open popup (a dropdown in use). */
|
|
12
|
+
export declare function isExpanded(target: Element): boolean;
|
|
13
|
+
/** The value an ``input`` step watches: the target's own, else its first field. */
|
|
14
|
+
export declare function readFieldValue(target: Element): string;
|
|
15
|
+
/** A select-like widget, whose popup the bubble should leave room for (placed on top). */
|
|
16
|
+
export declare function isComboboxLike(target: Element): boolean;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The resume record: which step of which tour is running, kept in
|
|
3
|
+
* ``sessionStorage`` so a full page load (a classic form post, a link the app
|
|
4
|
+
* does not route) picks the tour up where it was. Per tab by design.
|
|
5
|
+
*
|
|
6
|
+
* Every access is wrapped: storage throws in private modes and sandboxed
|
|
7
|
+
* frames, and a tour must keep working without it.
|
|
8
|
+
*/
|
|
9
|
+
export declare const RESUME_TTL_MS: number;
|
|
10
|
+
export interface ResumeRecord {
|
|
11
|
+
v: 1;
|
|
12
|
+
tourId: string;
|
|
13
|
+
stepId: string;
|
|
14
|
+
locale: string;
|
|
15
|
+
params: Record<string, string>;
|
|
16
|
+
buildId: string;
|
|
17
|
+
/** The manifest URL(s) the record was made with; another host config ignores it. */
|
|
18
|
+
manifestKey: string;
|
|
19
|
+
savedAt: number;
|
|
20
|
+
}
|
|
21
|
+
export declare function writeResume(record: ResumeRecord): void;
|
|
22
|
+
export declare function clearResume(): void;
|
|
23
|
+
/**
|
|
24
|
+
* The stored record when it is well formed, fresh and made for ``manifestKey``.
|
|
25
|
+
* A malformed or expired record is removed; one made by another host
|
|
26
|
+
* configuration is left for that host.
|
|
27
|
+
*/
|
|
28
|
+
export declare function readResume(manifestKey: string, now: number): ResumeRecord | null;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The overlay's chrome strings. They resolve against the *loaded tour file's*
|
|
3
|
+
* locale, never the page's, so the buttons and the step text are always in
|
|
4
|
+
* one language. ``next`` and ``progress`` are the guide player's words
|
|
5
|
+
* (``ui/strings.ts``); a host overrides any key per locale with ``strings``.
|
|
6
|
+
*
|
|
7
|
+
* The ``instruction*`` keys are the generic hint a step shows when its
|
|
8
|
+
* scenario authored no text for that locale (the compiler warns about each).
|
|
9
|
+
*/
|
|
10
|
+
import type { AdvanceKind } from './contract.js';
|
|
11
|
+
export interface TourStrings {
|
|
12
|
+
next: string;
|
|
13
|
+
/** Placeholders: ``{current}`` and ``{total}``. */
|
|
14
|
+
progress: string;
|
|
15
|
+
/** The primary button on the last step. */
|
|
16
|
+
done: string;
|
|
17
|
+
close: string;
|
|
18
|
+
skipStep: string;
|
|
19
|
+
endTour: string;
|
|
20
|
+
resume: string;
|
|
21
|
+
paused: string;
|
|
22
|
+
dialogLabel: string;
|
|
23
|
+
openGuide: string;
|
|
24
|
+
notFoundTitle: string;
|
|
25
|
+
notFoundBody: string;
|
|
26
|
+
instructionClick: string;
|
|
27
|
+
instructionInput: string;
|
|
28
|
+
instructionChange: string;
|
|
29
|
+
instructionVisible: string;
|
|
30
|
+
instructionManual: string;
|
|
31
|
+
}
|
|
32
|
+
export type TourStringOverrides = Partial<Record<string, Partial<TourStrings>>>;
|
|
33
|
+
export declare const DEFAULT_TOUR_STRINGS: Record<string, TourStrings>;
|
|
34
|
+
export declare function resolveTourStrings(contentLocale: string, overrides?: TourStringOverrides): TourStrings;
|
|
35
|
+
/**
|
|
36
|
+
* Writing direction of a locale tag: a script subtag decides (``az-Arab``,
|
|
37
|
+
* ``sd-Deva``), else the language. Set on the overlay with ``lang``, so a tour
|
|
38
|
+
* shown in another language than the page is read and laid out as its own.
|
|
39
|
+
*/
|
|
40
|
+
export declare function textDir(tag: string): 'rtl' | 'ltr';
|
|
41
|
+
/** The generic hint for a step without authored text. */
|
|
42
|
+
export declare function instructionFor(kind: AdvanceKind, strings: TourStrings): string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overlay styles. Theming goes through public ``--steppy-*`` custom
|
|
3
|
+
* properties, read into private ``--_pw-*`` ones on ``:host`` and never
|
|
4
|
+
* declared themselves — so a page sets them on ``:root`` or on the element.
|
|
5
|
+
* Geometry comes from properties the tracker writes through CSSOM.
|
|
6
|
+
*
|
|
7
|
+
* Defaults meet WCAG AA (guard: ``test/tour/small-modules.test.ts``). The bubble
|
|
8
|
+
* inherits the app's font unless ``--steppy-font-family`` is set. The
|
|
9
|
+
* bubble's inner layout uses logical properties, so it mirrors under the
|
|
10
|
+
* layer's ``dir`` (an Arabic or Hebrew tour); its placement around the target
|
|
11
|
+
* stays physical.
|
|
12
|
+
*/
|
|
13
|
+
export declare const tourStyles: import("lit").CSSResult;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The overlay's DOM. Data is only ever bound as text or attribute values
|
|
3
|
+
* (never ``style=``, never HTML), and the UI never carries the app's test-id
|
|
4
|
+
* attribute, so app locators cannot match it.
|
|
5
|
+
*
|
|
6
|
+
* DOM contract (the CI replay and the docs rely on it): the bubble is
|
|
7
|
+
* ``section[part~="bubble"][role="dialog"]`` with ``data-tour-id``,
|
|
8
|
+
* ``data-step-id``, ``data-state`` (``shown`` | ``fallback``) and
|
|
9
|
+
* ``data-advance``; Next is ``part="button next primary"`` on input and manual
|
|
10
|
+
* steps and ``"button next secondary"`` on change steps, absent on click and
|
|
11
|
+
* visible steps; the fallback has ``skip`` and ``end``; the pill holds
|
|
12
|
+
* ``resume`` and ``close``.
|
|
13
|
+
*/
|
|
14
|
+
import { type TemplateResult } from 'lit';
|
|
15
|
+
import { type LinkPolicy } from '../ui/markdown.js';
|
|
16
|
+
import type { TourStrings } from './strings.js';
|
|
17
|
+
import type { TourStep } from './validate.js';
|
|
18
|
+
export interface ViewModel {
|
|
19
|
+
mode: 'bubble' | 'pill' | 'none';
|
|
20
|
+
/** The tour file's language and direction, set on the layer (the page's may differ). */
|
|
21
|
+
lang: string;
|
|
22
|
+
dir: 'ltr' | 'rtl';
|
|
23
|
+
tourId: string;
|
|
24
|
+
step: TourStep;
|
|
25
|
+
state: 'shown' | 'fallback';
|
|
26
|
+
/** Spotlight drawn (an anchored step that is showing). */
|
|
27
|
+
anchored: boolean;
|
|
28
|
+
position: {
|
|
29
|
+
index: number;
|
|
30
|
+
total: number;
|
|
31
|
+
};
|
|
32
|
+
last: boolean;
|
|
33
|
+
nextEnabled: boolean;
|
|
34
|
+
guideUrl: string | null;
|
|
35
|
+
strings: TourStrings;
|
|
36
|
+
policy: LinkPolicy;
|
|
37
|
+
popover: boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface Handlers {
|
|
40
|
+
press(e: Event): void;
|
|
41
|
+
next(e: MouseEvent): void;
|
|
42
|
+
skip(e: MouseEvent): void;
|
|
43
|
+
end(e: MouseEvent): void;
|
|
44
|
+
close(e: MouseEvent): void;
|
|
45
|
+
resume(e: MouseEvent): void;
|
|
46
|
+
}
|
|
47
|
+
export declare function renderLayer(v: ViewModel, h: Handlers): TemplateResult;
|