@mpgd/tutorial 0.1.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 +21 -0
- package/README.md +9 -0
- package/dist/definition.d.ts +38 -0
- package/dist/definition.js +33 -0
- package/dist/director.d.ts +39 -0
- package/dist/director.js +230 -0
- package/dist/driver.d.ts +50 -0
- package/dist/driver.js +755 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +19 -0
- package/dist/platform-storage.d.ts +14 -0
- package/dist/platform-storage.js +74 -0
- package/dist/progress.d.ts +23 -0
- package/dist/progress.js +80 -0
- package/dist/testing.d.ts +59 -0
- package/dist/testing.js +156 -0
- package/driver.css +91 -0
- package/package.json +71 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 imjlk
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# @mpgd/tutorial
|
|
2
|
+
|
|
3
|
+
Reusable tutorial engine for mpgd games. It provides a DOM-free tutorial
|
|
4
|
+
director, platform storage adapter, optional Driver.js presenter, and opt-in
|
|
5
|
+
local reproduction helpers. Games keep ownership of tutorial content, gameplay
|
|
6
|
+
signals, simulation blocking, localization, and any production replay button.
|
|
7
|
+
|
|
8
|
+
See the [Tutorial Engine guide](https://github.com/imjlk/mpgd-kit/blob/main/docs/TUTORIAL_ENGINE.md)
|
|
9
|
+
for boundaries, API usage, optional replay UI, and the local browser harness.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type TutorialInteraction = 'blocked' | 'target' | 'gameplay' | 'hidden';
|
|
2
|
+
export type TutorialAdvance<TAction extends string = string, TSignal extends string = string> = {
|
|
3
|
+
readonly kind: 'acknowledge';
|
|
4
|
+
} | {
|
|
5
|
+
readonly action: TAction;
|
|
6
|
+
readonly kind: 'action';
|
|
7
|
+
} | {
|
|
8
|
+
readonly kind: 'signal';
|
|
9
|
+
readonly signal: TSignal;
|
|
10
|
+
};
|
|
11
|
+
export interface TutorialStep<TStepId extends string = string, TScene extends string = string, TTarget extends string = string, TAction extends string = string, TSignal extends string = string, TContent = unknown, TMeta = unknown> {
|
|
12
|
+
readonly advance: TutorialAdvance<TAction, TSignal>;
|
|
13
|
+
readonly align?: 'start' | 'center' | 'end';
|
|
14
|
+
readonly content?: TContent;
|
|
15
|
+
readonly id: TStepId;
|
|
16
|
+
readonly interaction: TutorialInteraction;
|
|
17
|
+
readonly meta?: TMeta;
|
|
18
|
+
readonly scene: TScene;
|
|
19
|
+
readonly side?: 'top' | 'right' | 'bottom' | 'left';
|
|
20
|
+
readonly target: TTarget | null;
|
|
21
|
+
}
|
|
22
|
+
export interface TutorialDefinition<TStep extends TutorialStep = TutorialStep> {
|
|
23
|
+
readonly id: string;
|
|
24
|
+
readonly initialScene: TStep['scene'];
|
|
25
|
+
readonly revision: number;
|
|
26
|
+
readonly steps: readonly TStep[];
|
|
27
|
+
}
|
|
28
|
+
export type TutorialStepOf<TDefinition extends TutorialDefinition> = TDefinition['steps'][number];
|
|
29
|
+
export type TutorialStepIdOf<TDefinition extends TutorialDefinition> = TutorialStepOf<TDefinition>['id'];
|
|
30
|
+
export type TutorialSceneOf<TDefinition extends TutorialDefinition> = TutorialStepOf<TDefinition>['scene'];
|
|
31
|
+
export type TutorialActionOf<TDefinition extends TutorialDefinition> = Extract<TutorialStepOf<TDefinition>['advance'], {
|
|
32
|
+
readonly kind: 'action';
|
|
33
|
+
}>['action'];
|
|
34
|
+
export type TutorialSignalOf<TDefinition extends TutorialDefinition> = Extract<TutorialStepOf<TDefinition>['advance'], {
|
|
35
|
+
readonly kind: 'signal';
|
|
36
|
+
}>['signal'];
|
|
37
|
+
export declare function defineTutorial<const TDefinition extends TutorialDefinition>(definition: TDefinition): TDefinition;
|
|
38
|
+
export declare function findTutorialStep<TDefinition extends TutorialDefinition>(definition: TDefinition, stepId: TutorialStepIdOf<TDefinition>): TutorialStepOf<TDefinition> | null;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export function defineTutorial(definition) {
|
|
2
|
+
if (definition.id.trim().length === 0) {
|
|
3
|
+
throw new Error('Tutorial id must not be empty.');
|
|
4
|
+
}
|
|
5
|
+
if (!Number.isSafeInteger(definition.revision) || definition.revision <= 0) {
|
|
6
|
+
throw new Error('Tutorial revision must be a positive safe integer.');
|
|
7
|
+
}
|
|
8
|
+
if (definition.steps.length === 0) {
|
|
9
|
+
throw new Error('Tutorial must define at least one step.');
|
|
10
|
+
}
|
|
11
|
+
const stepIds = new Set();
|
|
12
|
+
const scenes = new Set();
|
|
13
|
+
for (const step of definition.steps) {
|
|
14
|
+
if (step.id.trim().length === 0) {
|
|
15
|
+
throw new Error('Tutorial step id must not be empty.');
|
|
16
|
+
}
|
|
17
|
+
if (stepIds.has(step.id)) {
|
|
18
|
+
throw new Error(`Duplicate tutorial step id: ${step.id}`);
|
|
19
|
+
}
|
|
20
|
+
if (step.scene.trim().length === 0) {
|
|
21
|
+
throw new Error(`Tutorial step scene must not be empty: ${step.id}`);
|
|
22
|
+
}
|
|
23
|
+
stepIds.add(step.id);
|
|
24
|
+
scenes.add(step.scene);
|
|
25
|
+
}
|
|
26
|
+
if (!scenes.has(definition.initialScene)) {
|
|
27
|
+
throw new Error(`Tutorial initial scene is not used by any step: ${definition.initialScene}`);
|
|
28
|
+
}
|
|
29
|
+
return definition;
|
|
30
|
+
}
|
|
31
|
+
export function findTutorialStep(definition, stepId) {
|
|
32
|
+
return definition.steps.find((step) => step.id === stepId) ?? null;
|
|
33
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { TutorialActionOf, TutorialDefinition, TutorialSceneOf, TutorialSignalOf, TutorialStepIdOf, TutorialStepOf } from './definition.js';
|
|
2
|
+
import { type TutorialProgressOf, type TutorialProgressStatus, type TutorialProgressStore } from './progress.js';
|
|
3
|
+
export interface TutorialDirectorSnapshot<TDefinition extends TutorialDefinition> {
|
|
4
|
+
readonly active: boolean;
|
|
5
|
+
readonly currentScene: string;
|
|
6
|
+
readonly currentStep: TutorialStepOf<TDefinition> | null;
|
|
7
|
+
readonly currentStepId: TutorialStepIdOf<TDefinition> | null;
|
|
8
|
+
readonly presentedStep: TutorialStepOf<TDefinition> | null;
|
|
9
|
+
readonly replaying: boolean;
|
|
10
|
+
readonly requiredScene: TutorialSceneOf<TDefinition> | null;
|
|
11
|
+
readonly status: TutorialProgressStatus | 'inactive';
|
|
12
|
+
readonly suspended: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface TutorialReplayOptions<TDefinition extends TutorialDefinition> {
|
|
15
|
+
readonly fromStepId?: TutorialStepIdOf<TDefinition>;
|
|
16
|
+
}
|
|
17
|
+
export interface TutorialDirector<TDefinition extends TutorialDefinition> {
|
|
18
|
+
acknowledge(stepId: TutorialStepIdOf<TDefinition>): void;
|
|
19
|
+
destroy(): void;
|
|
20
|
+
flush(): Promise<void>;
|
|
21
|
+
getSnapshot(): TutorialDirectorSnapshot<TDefinition>;
|
|
22
|
+
observeAction(action: TutorialActionOf<TDefinition>): void;
|
|
23
|
+
observeScene(scene: string): void;
|
|
24
|
+
observeSignal(signal: TutorialSignalOf<TDefinition>): void;
|
|
25
|
+
replay(options?: TutorialReplayOptions<TDefinition>): Promise<void>;
|
|
26
|
+
setSuspended(suspended: boolean): void;
|
|
27
|
+
skip(): Promise<void>;
|
|
28
|
+
subscribe(listener: (snapshot: TutorialDirectorSnapshot<TDefinition>) => void): () => void;
|
|
29
|
+
}
|
|
30
|
+
export interface CreateTutorialDirectorInput<TDefinition extends TutorialDefinition> {
|
|
31
|
+
readonly autoStart: boolean;
|
|
32
|
+
readonly clock?: () => Date;
|
|
33
|
+
readonly definition: TDefinition;
|
|
34
|
+
readonly initialScene?: string;
|
|
35
|
+
readonly onError?: (error: unknown) => void;
|
|
36
|
+
readonly progressStore: TutorialProgressStore<TutorialProgressOf<TDefinition>>;
|
|
37
|
+
readonly suspended?: boolean;
|
|
38
|
+
}
|
|
39
|
+
export declare function createTutorialDirector<TDefinition extends TutorialDefinition>(input: CreateTutorialDirectorInput<TDefinition>): TutorialDirector<TDefinition>;
|
package/dist/director.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { createInitialTutorialProgress, createTutorialProgressAtStep, } from './progress.js';
|
|
2
|
+
export function createTutorialDirector(input) {
|
|
3
|
+
const { definition, progressStore } = input;
|
|
4
|
+
const listeners = new Set();
|
|
5
|
+
const clock = input.clock ?? (() => new Date());
|
|
6
|
+
const stored = progressStore.getSnapshot();
|
|
7
|
+
let durableBeforeReplay = null;
|
|
8
|
+
let replayRestorePending = false;
|
|
9
|
+
let progress = stored ?? (input.autoStart && progressStore.available
|
|
10
|
+
? createInitialTutorialProgress(definition, now())
|
|
11
|
+
: null);
|
|
12
|
+
let replaying = false;
|
|
13
|
+
let durableFallbackDuringPublish;
|
|
14
|
+
let suspended = input.suspended ?? false;
|
|
15
|
+
let destroyed = false;
|
|
16
|
+
let currentScene = input.initialScene ?? definition.initialScene;
|
|
17
|
+
let publishing = false;
|
|
18
|
+
const pendingSnapshots = [];
|
|
19
|
+
if (stored === null && progress !== null) {
|
|
20
|
+
persist(progress);
|
|
21
|
+
}
|
|
22
|
+
let snapshot = createSnapshot(definition, progress, replaying, suspended, currentScene);
|
|
23
|
+
function now() {
|
|
24
|
+
return clock().toISOString();
|
|
25
|
+
}
|
|
26
|
+
function persist(next) {
|
|
27
|
+
if (destroyed || replaying) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
void progressStore.save(next).catch((error) => {
|
|
31
|
+
input.onError?.(error);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function publish() {
|
|
35
|
+
const publishedSnapshot = createSnapshot(definition, progress, replaying, suspended, currentScene);
|
|
36
|
+
pendingSnapshots.push(publishedSnapshot);
|
|
37
|
+
if (publishing) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
publishing = true;
|
|
41
|
+
try {
|
|
42
|
+
while (pendingSnapshots.length > 0) {
|
|
43
|
+
const pendingSnapshot = pendingSnapshots.shift();
|
|
44
|
+
if (pendingSnapshot === undefined) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
snapshot = pendingSnapshot;
|
|
48
|
+
const recipients = [...listeners];
|
|
49
|
+
for (const subscription of recipients) {
|
|
50
|
+
if (listeners.has(subscription)) {
|
|
51
|
+
subscription.listener(pendingSnapshot);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
publishing = false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function getCurrentStep() {
|
|
61
|
+
if (progress?.status !== 'active') {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return definition.steps[progress.completedStepIds.length] ?? null;
|
|
65
|
+
}
|
|
66
|
+
function canAdvance(step) {
|
|
67
|
+
return !suspended
|
|
68
|
+
&& progress?.status === 'active'
|
|
69
|
+
&& step !== null
|
|
70
|
+
&& step.scene === currentScene;
|
|
71
|
+
}
|
|
72
|
+
function advanceCurrent() {
|
|
73
|
+
const current = getCurrentStep();
|
|
74
|
+
if (!canAdvance(current) || progress === null) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const completedStepIds = [...progress.completedStepIds, current.id];
|
|
78
|
+
const timestamp = now();
|
|
79
|
+
const complete = completedStepIds.length === definition.steps.length;
|
|
80
|
+
progress = {
|
|
81
|
+
...progress,
|
|
82
|
+
checkpoint: current.scene,
|
|
83
|
+
completedAt: complete ? timestamp : null,
|
|
84
|
+
completedStepIds,
|
|
85
|
+
skippedAt: null,
|
|
86
|
+
status: complete ? 'completed' : 'active',
|
|
87
|
+
updatedAt: timestamp,
|
|
88
|
+
};
|
|
89
|
+
if (complete && replaying && replayRestorePending) {
|
|
90
|
+
progress = durableBeforeReplay;
|
|
91
|
+
replaying = false;
|
|
92
|
+
replayRestorePending = false;
|
|
93
|
+
durableBeforeReplay = null;
|
|
94
|
+
publish();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (complete) {
|
|
98
|
+
replaying = false;
|
|
99
|
+
replayRestorePending = false;
|
|
100
|
+
durableBeforeReplay = null;
|
|
101
|
+
}
|
|
102
|
+
persist(progress);
|
|
103
|
+
publish();
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
acknowledge(stepId) {
|
|
107
|
+
const step = getCurrentStep();
|
|
108
|
+
if (step?.id === stepId && step.advance.kind === 'acknowledge') {
|
|
109
|
+
advanceCurrent();
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
destroy() {
|
|
113
|
+
destroyed = true;
|
|
114
|
+
listeners.clear();
|
|
115
|
+
},
|
|
116
|
+
flush: () => progressStore.flush(),
|
|
117
|
+
getSnapshot: () => snapshot,
|
|
118
|
+
observeAction(action) {
|
|
119
|
+
const step = getCurrentStep();
|
|
120
|
+
if (canAdvance(step) && step.advance.kind === 'action' && step.advance.action === action) {
|
|
121
|
+
advanceCurrent();
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
observeScene(scene) {
|
|
125
|
+
currentScene = scene;
|
|
126
|
+
const step = getCurrentStep();
|
|
127
|
+
if (progress?.status === 'active' && step?.scene === scene && progress.checkpoint !== scene) {
|
|
128
|
+
progress = { ...progress, checkpoint: scene, updatedAt: now() };
|
|
129
|
+
persist(progress);
|
|
130
|
+
}
|
|
131
|
+
publish();
|
|
132
|
+
},
|
|
133
|
+
observeSignal(signal) {
|
|
134
|
+
const step = getCurrentStep();
|
|
135
|
+
if (canAdvance(step) && step.advance.kind === 'signal' && step.advance.signal === signal) {
|
|
136
|
+
advanceCurrent();
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
async replay(options = {}) {
|
|
140
|
+
const replayProgress = options.fromStepId === undefined
|
|
141
|
+
? createInitialTutorialProgress(definition, now())
|
|
142
|
+
: createTutorialProgressAtStep(definition, options.fromStepId, now());
|
|
143
|
+
if (!replaying) {
|
|
144
|
+
durableBeforeReplay = durableFallbackDuringPublish === undefined
|
|
145
|
+
? progress
|
|
146
|
+
: durableFallbackDuringPublish;
|
|
147
|
+
replayRestorePending = true;
|
|
148
|
+
}
|
|
149
|
+
replaying = true;
|
|
150
|
+
suspended = false;
|
|
151
|
+
progress = replayProgress;
|
|
152
|
+
publish();
|
|
153
|
+
},
|
|
154
|
+
setSuspended(nextSuspended) {
|
|
155
|
+
if (suspended === nextSuspended) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
suspended = nextSuspended;
|
|
159
|
+
publish();
|
|
160
|
+
},
|
|
161
|
+
async skip() {
|
|
162
|
+
if (progress === null) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (replaying && replayRestorePending) {
|
|
166
|
+
progress = durableBeforeReplay;
|
|
167
|
+
durableBeforeReplay = null;
|
|
168
|
+
replayRestorePending = false;
|
|
169
|
+
replaying = false;
|
|
170
|
+
publish();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (progress.status !== 'active') {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const progressBeforeSkip = progress;
|
|
177
|
+
const timestamp = now();
|
|
178
|
+
progress = {
|
|
179
|
+
...progress,
|
|
180
|
+
completedAt: null,
|
|
181
|
+
skippedAt: timestamp,
|
|
182
|
+
status: 'skipped',
|
|
183
|
+
updatedAt: timestamp,
|
|
184
|
+
};
|
|
185
|
+
replaying = false;
|
|
186
|
+
durableBeforeReplay = null;
|
|
187
|
+
replayRestorePending = false;
|
|
188
|
+
durableFallbackDuringPublish = progressBeforeSkip;
|
|
189
|
+
try {
|
|
190
|
+
publish();
|
|
191
|
+
}
|
|
192
|
+
finally {
|
|
193
|
+
durableFallbackDuringPublish = undefined;
|
|
194
|
+
}
|
|
195
|
+
if (destroyed || replaying) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
await progressStore.save(progress);
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
input.onError?.(error);
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
subscribe(listener) {
|
|
206
|
+
const subscription = { listener };
|
|
207
|
+
listeners.add(subscription);
|
|
208
|
+
listener(snapshot);
|
|
209
|
+
return () => listeners.delete(subscription);
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function createSnapshot(definition, progress, replaying, suspended, currentScene) {
|
|
214
|
+
const currentStep = progress?.status === 'active'
|
|
215
|
+
? (definition.steps[progress.completedStepIds.length] ?? null)
|
|
216
|
+
: null;
|
|
217
|
+
const active = !suspended && currentStep !== null;
|
|
218
|
+
const inCurrentScene = active && currentStep.scene === currentScene;
|
|
219
|
+
return {
|
|
220
|
+
active,
|
|
221
|
+
currentScene,
|
|
222
|
+
currentStep,
|
|
223
|
+
currentStepId: currentStep?.id ?? null,
|
|
224
|
+
presentedStep: inCurrentScene && currentStep.interaction !== 'hidden' ? currentStep : null,
|
|
225
|
+
replaying,
|
|
226
|
+
requiredScene: currentStep?.scene ?? null,
|
|
227
|
+
status: progress?.status ?? 'inactive',
|
|
228
|
+
suspended,
|
|
229
|
+
};
|
|
230
|
+
}
|
package/dist/driver.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { TutorialDefinition, TutorialStep } from './definition.js';
|
|
2
|
+
import type { TutorialDirector } from './director.js';
|
|
3
|
+
export interface DriverTutorialCopy {
|
|
4
|
+
readonly description: string;
|
|
5
|
+
readonly done: string;
|
|
6
|
+
readonly next: string;
|
|
7
|
+
readonly skip: string;
|
|
8
|
+
readonly title: string;
|
|
9
|
+
}
|
|
10
|
+
export interface DriverTutorialPresentation<TStep extends TutorialStep = TutorialStep> {
|
|
11
|
+
readonly acknowledgeOnTargetClick?: boolean;
|
|
12
|
+
readonly allowTargetInteraction?: boolean;
|
|
13
|
+
readonly copy: DriverTutorialCopy;
|
|
14
|
+
readonly finalStep?: boolean;
|
|
15
|
+
readonly step: TStep;
|
|
16
|
+
}
|
|
17
|
+
export interface DriverTutorialPresenterInput<TStep extends TutorialStep = TutorialStep> {
|
|
18
|
+
readonly missingTarget?: 'error' | 'unanchored' | 'wait';
|
|
19
|
+
readonly modalSelector?: string;
|
|
20
|
+
readonly onAcknowledge: (stepId: TStep['id']) => void;
|
|
21
|
+
readonly onActiveChange?: (active: boolean) => void;
|
|
22
|
+
readonly onError?: (error: unknown) => void;
|
|
23
|
+
readonly onSkip: () => Promise<void> | void;
|
|
24
|
+
readonly resolveTarget?: (target: TStep['target'], root: Document | HTMLElement) => HTMLElement | null;
|
|
25
|
+
readonly root?: Document | HTMLElement;
|
|
26
|
+
readonly stateHost?: HTMLElement;
|
|
27
|
+
readonly targetAttribute?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface DriverTutorialPresenter<TStep extends TutorialStep = TutorialStep> {
|
|
30
|
+
destroy(): void;
|
|
31
|
+
present(presentation: DriverTutorialPresentation<TStep> | null): void;
|
|
32
|
+
refresh(): void;
|
|
33
|
+
resetForReplay(): void;
|
|
34
|
+
waitForPendingSkip(): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
export declare function createDriverTutorialPresenter<TStep extends TutorialStep>(input: DriverTutorialPresenterInput<TStep>): DriverTutorialPresenter<TStep>;
|
|
37
|
+
export declare function resolveVisibleTutorialTarget(input: {
|
|
38
|
+
readonly root?: Document | HTMLElement;
|
|
39
|
+
readonly target: string;
|
|
40
|
+
readonly targetAttribute?: string;
|
|
41
|
+
readonly view?: Window;
|
|
42
|
+
}): HTMLElement | null;
|
|
43
|
+
export declare function isVisibleTutorialTarget(element: HTMLElement, view?: Window): boolean;
|
|
44
|
+
export declare function bindTutorialReplayTrigger<TDefinition extends TutorialDefinition>(input: {
|
|
45
|
+
readonly beforeReplay?: () => Promise<void> | void;
|
|
46
|
+
readonly director: TutorialDirector<TDefinition>;
|
|
47
|
+
readonly element: HTMLElement;
|
|
48
|
+
readonly onError?: (error: unknown) => void;
|
|
49
|
+
readonly presenter: Pick<DriverTutorialPresenter, 'resetForReplay' | 'waitForPendingSkip'>;
|
|
50
|
+
}): () => void;
|