@eintrek/erp-shell 0.1.66 → 0.1.67
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/dist/guide/dom.d.ts +2 -0
- package/dist/guide/machine.d.ts +35 -0
- package/dist/guide/types.d.ts +25 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.esm.js +130 -20
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +130 -20
- package/dist/index.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/guide/dom.d.ts
CHANGED
|
@@ -38,6 +38,8 @@ export declare const OPEN_DIALOG_SELECTOR = "[role=\"dialog\"][data-state=\"open
|
|
|
38
38
|
* behind it.
|
|
39
39
|
*/
|
|
40
40
|
export declare function coveredByDialog(el: Element | null): boolean;
|
|
41
|
+
/** An app dialog (not the tour's) is open on the page. */
|
|
42
|
+
export declare function appDialogOpen(): boolean;
|
|
41
43
|
export declare function openPoppers(): HTMLElement[];
|
|
42
44
|
export declare function inGuideLayer(node: EventTarget | null): boolean;
|
|
43
45
|
export declare function prefersReducedMotion(): boolean;
|
package/dist/guide/machine.d.ts
CHANGED
|
@@ -44,6 +44,9 @@ export type GuideScreen = {
|
|
|
44
44
|
/** A `hidden` trigger's anchor has been seen on this step, so its
|
|
45
45
|
* going away counts. */
|
|
46
46
|
armed: boolean;
|
|
47
|
+
/** A commit step shown in its read form: nothing to save
|
|
48
|
+
* (GuideStep.nothingToSave). */
|
|
49
|
+
nothingToSave: boolean;
|
|
47
50
|
} | {
|
|
48
51
|
name: "celebrate";
|
|
49
52
|
mission: string;
|
|
@@ -63,6 +66,18 @@ export interface GuideState {
|
|
|
63
66
|
nudge: number;
|
|
64
67
|
/** The gate card was put away for this visit ("ไว้ทีหลัง"). */
|
|
65
68
|
gateDismissed: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* What the user already had open when the current replay started: those
|
|
71
|
+
* are theirs, and ending the replay leaves them alone (see pushAbort).
|
|
72
|
+
*/
|
|
73
|
+
replayPage: ReplayPage | null;
|
|
74
|
+
}
|
|
75
|
+
export interface ReplayPage {
|
|
76
|
+
mission: string;
|
|
77
|
+
/** The mission's abort anchors that were on the page at the start. */
|
|
78
|
+
anchors: string[];
|
|
79
|
+
/** An app dialog was open at the start. */
|
|
80
|
+
dialog: boolean;
|
|
66
81
|
}
|
|
67
82
|
/** What the machine may ask about the page. */
|
|
68
83
|
export interface GuideEnv {
|
|
@@ -77,6 +92,8 @@ export interface GuideEnv {
|
|
|
77
92
|
sidebarOpen: boolean;
|
|
78
93
|
/** The current value of the step target's field. */
|
|
79
94
|
targetValue: () => string;
|
|
95
|
+
/** An app dialog (not the tour's) is open. */
|
|
96
|
+
dialogOpen?: () => boolean;
|
|
80
97
|
}
|
|
81
98
|
export type GuideSignal = {
|
|
82
99
|
kind: "api";
|
|
@@ -213,6 +230,16 @@ export declare function triggersOf(step: GuideStep): GuideTrigger[];
|
|
|
213
230
|
export declare function stepKind(step: GuideStep): import("./types").GuideStepKind;
|
|
214
231
|
/** The last checkpoint at or before `index` (a mission's first step always is one). */
|
|
215
232
|
export declare function checkpointAtOrBefore(steps: GuideStep[], index: number): number;
|
|
233
|
+
/**
|
|
234
|
+
* A commit step with nothing to save: its button is not on the page, and what
|
|
235
|
+
* is saved is (GuideStep.nothingToSave).
|
|
236
|
+
*/
|
|
237
|
+
export declare function nothingToSaveHolds(step: GuideStep, env: GuideEnv, ctx: GuideContext): boolean;
|
|
238
|
+
/**
|
|
239
|
+
* The step as it is shown: a commit step with nothing to save is a read step
|
|
240
|
+
* on what is saved, with its own title (ถัดไป moves on).
|
|
241
|
+
*/
|
|
242
|
+
export declare function shownStep(step: GuideStep, nothingToSave: boolean): GuideStep;
|
|
216
243
|
/** Whether the step's target (or the bridge's) is on screen. */
|
|
217
244
|
export declare function stepTargetVisible(step: GuideStep, env: GuideEnv, ctx: GuideContext, bridge: boolean): boolean;
|
|
218
245
|
/** The anchor the spotlight is on for the current step. */
|
|
@@ -228,6 +255,14 @@ export declare function spotlightTarget(step: GuideStep, ctx: GuideContext, brid
|
|
|
228
255
|
export declare function stepHomeRoute(steps: GuideStep[], index: number): string | undefined;
|
|
229
256
|
/** What puts the page back when a replay ends at this step (see GuideStep.abort). */
|
|
230
257
|
export declare function abortActionsFor(mission: GuideMission, index: number): GuideAbortAction[];
|
|
258
|
+
/** Every anchor the mission's abort actions press. */
|
|
259
|
+
export declare function abortAnchorsOf(mission: GuideMission): string[];
|
|
260
|
+
/**
|
|
261
|
+
* What the replay may do to put the page back: not press what the user had
|
|
262
|
+
* open before it started, not Escape out of a dialog that was already open,
|
|
263
|
+
* and no app event while anything of theirs was open.
|
|
264
|
+
*/
|
|
265
|
+
export declare function abortActionsLeft(actions: GuideAbortAction[], before: ReplayPage | null): GuideAbortAction[];
|
|
231
266
|
/** Status of each mission as the panel shows it. */
|
|
232
267
|
export type MissionView = "todo" | "next" | "active" | "skipped" | "done" | "already" | "locked" | "waiting";
|
|
233
268
|
export declare function missionSettled(m: GuideMission, entry: MissionEntry | undefined, ctx: GuideContext): boolean;
|
package/dist/guide/types.d.ts
CHANGED
|
@@ -180,6 +180,17 @@ export interface GuideStep {
|
|
|
180
180
|
* or Escape to the open dialog. Overrides the mission's `abort`.
|
|
181
181
|
*/
|
|
182
182
|
abort?: GuideAbortAction | GuideAbortAction[];
|
|
183
|
+
/**
|
|
184
|
+
* "commit" steps whose button only shows after a change (a save bar that
|
|
185
|
+
* appears once the form is edited). When `target` is not on the page but
|
|
186
|
+
* `nothingToSave.target` is (what is saved is on screen and nothing is
|
|
187
|
+
* changed), the step is a read step on that anchor, with ถัดไป, which moves
|
|
188
|
+
* on as if saved. The moment `target` shows (the user changed something)
|
|
189
|
+
* it is the commit step again. Name an anchor that only exists when the
|
|
190
|
+
* saved state is real, so a first run never passes without a save that
|
|
191
|
+
* was needed (shell ≥ 0.1.67).
|
|
192
|
+
*/
|
|
193
|
+
nothingToSave?: GuideNothingToSave;
|
|
183
194
|
/**
|
|
184
195
|
* "commit" steps: what a refused save means. Return the key of the step to
|
|
185
196
|
* go back to (a field) and/or the line to show. Default: stay, show the
|
|
@@ -190,10 +201,20 @@ export interface GuideStep {
|
|
|
190
201
|
message?: string;
|
|
191
202
|
} | void;
|
|
192
203
|
}
|
|
204
|
+
/** A commit step's read form when there is nothing to save (GuideStep.nothingToSave). */
|
|
205
|
+
export interface GuideNothingToSave {
|
|
206
|
+
/** What is saved, on screen (the placed point's card, the saved list). */
|
|
207
|
+
target: GuideAnchorKey;
|
|
208
|
+
title: GuideText;
|
|
209
|
+
why?: GuideText;
|
|
210
|
+
/** Anchors the user may use meanwhile; a change there brings the save back. */
|
|
211
|
+
allow?: GuideAnchorKey[];
|
|
212
|
+
}
|
|
193
213
|
/**
|
|
194
214
|
* One thing the engine does to put the page back when a replay ends early.
|
|
195
215
|
* Run in order, after the tour has let go of the page; an anchor that is not
|
|
196
|
-
* on the page is passed over
|
|
216
|
+
* on the page is passed over, and so is one that was already on the page
|
|
217
|
+
* when the replay started (the user's own open form: shell ≥ 0.1.67).
|
|
197
218
|
*/
|
|
198
219
|
export type GuideAbortAction =
|
|
199
220
|
/** Press this anchor (the form's ยกเลิก, a dialog's close). */
|
|
@@ -251,6 +272,9 @@ export interface GuideMission {
|
|
|
251
272
|
* How to put the page back when a replay of this mission ends without
|
|
252
273
|
* saving (a step's own `abort` wins). Default: Escape, and only from a step
|
|
253
274
|
* that is not a checkpoint (inside the dialog or form the tour opened).
|
|
275
|
+
* Whatever was already open when the replay started is the user's and is
|
|
276
|
+
* left alone: an anchor on the page then is never pressed, and no Escape
|
|
277
|
+
* is sent when an app dialog was open (shell ≥ 0.1.67).
|
|
254
278
|
*/
|
|
255
279
|
abort?: GuideAbortAction | GuideAbortAction[];
|
|
256
280
|
steps: GuideStep[];
|
package/dist/index.d.ts
CHANGED
|
@@ -33,7 +33,7 @@ export { HelpDialogHost, type HelpDialogHostProps } from "./help/help-dialog";
|
|
|
33
33
|
export { openHelp, closeHelp, type HelpAppId } from "./help/help-store";
|
|
34
34
|
export { describeBrowser, describeAttached, detectHelpApp, type HelpContext, } from "./help/help-context";
|
|
35
35
|
export { SHELL_VERSION } from "./version";
|
|
36
|
-
export type { GuideAbortAction, GuideAnchorKey, GuideApiFailure, GuideApiMatch, GuideAppId, GuideCaptureEvent, GuideConfig, GuideContext, GuideFacts, GuideGate, GuideMission, GuideOnMissing, GuideStep, GuideStepKind, GuideSuite, GuideText, GuideTrigger, } from "./guide/types";
|
|
36
|
+
export type { GuideAbortAction, GuideAnchorKey, GuideApiFailure, GuideApiMatch, GuideAppId, GuideCaptureEvent, GuideConfig, GuideContext, GuideFacts, GuideGate, GuideMission, GuideNothingToSave, GuideOnMissing, GuideStep, GuideStepKind, GuideSuite, GuideText, GuideTrigger, } from "./guide/types";
|
|
37
37
|
export { API_EVENT as GUIDE_API_EVENT, GUIDE_EVENT_PREFIX, GUIDE_LAYER_ATTRIBUTE, emitGuideEvent, guideAnchor, reportApiEvent, type ApiEventDetail, type GuideAnchorProps, } from "./guide/events";
|
|
38
38
|
export { openGuidePanel, useGuideEntry, type GuideEntry } from "./guide/guide-store";
|
|
39
39
|
export { GuideHost, type GuideHostProps } from "./guide/guide-host";
|
package/dist/index.esm.js
CHANGED
|
@@ -1956,7 +1956,7 @@ function platformApiBase(app) {
|
|
|
1956
1956
|
* This package's version, attached to help messages so the back office can
|
|
1957
1957
|
* tell which shell a report came from. Keep in step with package.json.
|
|
1958
1958
|
*/
|
|
1959
|
-
const SHELL_VERSION = "0.1.
|
|
1959
|
+
const SHELL_VERSION = "0.1.67";
|
|
1960
1960
|
|
|
1961
1961
|
const HELP_APP_LABEL = {
|
|
1962
1962
|
platform: "แพลตฟอร์ม",
|
|
@@ -3813,6 +3813,7 @@ function initialGuideState(audience, progress) {
|
|
|
3813
3813
|
chipPulse: 0,
|
|
3814
3814
|
nudge: 0,
|
|
3815
3815
|
gateDismissed: false,
|
|
3816
|
+
replayPage: null,
|
|
3816
3817
|
};
|
|
3817
3818
|
}
|
|
3818
3819
|
function findMission(suite, key) {
|
|
@@ -3870,6 +3871,38 @@ function targetIdOf(step, ctx) {
|
|
|
3870
3871
|
return safe(() => step.targetId(ctx), undefined);
|
|
3871
3872
|
return step.targetId;
|
|
3872
3873
|
}
|
|
3874
|
+
/**
|
|
3875
|
+
* A commit step with nothing to save: its button is not on the page, and what
|
|
3876
|
+
* is saved is (GuideStep.nothingToSave).
|
|
3877
|
+
*/
|
|
3878
|
+
function nothingToSaveHolds(step, env, ctx) {
|
|
3879
|
+
const n = step.nothingToSave;
|
|
3880
|
+
if (!n || !step.target)
|
|
3881
|
+
return false;
|
|
3882
|
+
return !env.isVisible(step.target, targetIdOf(step, ctx)) && env.isVisible(n.target);
|
|
3883
|
+
}
|
|
3884
|
+
/**
|
|
3885
|
+
* The step as it is shown: a commit step with nothing to save is a read step
|
|
3886
|
+
* on what is saved, with its own title (ถัดไป moves on).
|
|
3887
|
+
*/
|
|
3888
|
+
function shownStep(step, nothingToSave) {
|
|
3889
|
+
const n = step.nothingToSave;
|
|
3890
|
+
if (!nothingToSave || !n)
|
|
3891
|
+
return step;
|
|
3892
|
+
return {
|
|
3893
|
+
key: step.key,
|
|
3894
|
+
kind: "view",
|
|
3895
|
+
target: n.target,
|
|
3896
|
+
title: n.title,
|
|
3897
|
+
why: n.why,
|
|
3898
|
+
allow: n.allow,
|
|
3899
|
+
route: step.route,
|
|
3900
|
+
resumeUrl: step.resumeUrl,
|
|
3901
|
+
checkpoint: step.checkpoint,
|
|
3902
|
+
timeoutMs: step.timeoutMs,
|
|
3903
|
+
abort: step.abort,
|
|
3904
|
+
};
|
|
3905
|
+
}
|
|
3873
3906
|
/** Whether the step's target (or the bridge's) is on screen. */
|
|
3874
3907
|
function stepTargetVisible(step, env, ctx, bridge) {
|
|
3875
3908
|
if (bridge)
|
|
@@ -3938,6 +3971,30 @@ function abortActionsFor(mission, index) {
|
|
|
3938
3971
|
return [];
|
|
3939
3972
|
return [{ escape: true }];
|
|
3940
3973
|
}
|
|
3974
|
+
/** Every anchor the mission's abort actions press. */
|
|
3975
|
+
function abortAnchorsOf(mission) {
|
|
3976
|
+
const out = new Set();
|
|
3977
|
+
const add = (a) => {
|
|
3978
|
+
for (const x of a ? (Array.isArray(a) ? a : [a]) : [])
|
|
3979
|
+
if ("click" in x)
|
|
3980
|
+
out.add(x.click);
|
|
3981
|
+
};
|
|
3982
|
+
add(mission.abort);
|
|
3983
|
+
for (const st of mission.steps)
|
|
3984
|
+
add(st.abort);
|
|
3985
|
+
return Array.from(out);
|
|
3986
|
+
}
|
|
3987
|
+
/**
|
|
3988
|
+
* What the replay may do to put the page back: not press what the user had
|
|
3989
|
+
* open before it started, not Escape out of a dialog that was already open,
|
|
3990
|
+
* and no app event while anything of theirs was open.
|
|
3991
|
+
*/
|
|
3992
|
+
function abortActionsLeft(actions, before) {
|
|
3993
|
+
if (!before)
|
|
3994
|
+
return actions;
|
|
3995
|
+
const busy = before.anchors.length > 0 || before.dialog;
|
|
3996
|
+
return actions.filter(a => "click" in a ? !before.anchors.includes(a.click) : "escape" in a ? !before.dialog : !busy);
|
|
3997
|
+
}
|
|
3941
3998
|
function routeParamsFor(step, env) {
|
|
3942
3999
|
if (!step.route)
|
|
3943
4000
|
return {};
|
|
@@ -4076,7 +4133,7 @@ function announceStep(d, suite, env) {
|
|
|
4076
4133
|
const mission = findMission(suite, s.mission);
|
|
4077
4134
|
if (!mission)
|
|
4078
4135
|
return;
|
|
4079
|
-
const step = mission.steps[s.index];
|
|
4136
|
+
const step = shownStep(mission.steps[s.index], s.nothingToSave);
|
|
4080
4137
|
const ctx = guideContext(env, d.state.progress.context, routeParamsFor(step, env));
|
|
4081
4138
|
const title = s.bridge ? COPY.bridgeTitle : plainTitle(resolveText(step.title, ctx));
|
|
4082
4139
|
const why = s.bridge ? COPY.bridgeWhy(suite.appName) : plainTitle(resolveText(step.why, ctx));
|
|
@@ -4144,6 +4201,7 @@ function enterStep(d, suite, env, mission, index, opts) {
|
|
|
4144
4201
|
confirmExit: false,
|
|
4145
4202
|
misses: 0,
|
|
4146
4203
|
armed: hiddenAnchorShown(step, env),
|
|
4204
|
+
nothingToSave: !away && !bridge && nothingToSaveHolds(step, env, ctx),
|
|
4147
4205
|
});
|
|
4148
4206
|
if (!opts.replay) {
|
|
4149
4207
|
const cp = checkpointAtOrBefore(steps, i);
|
|
@@ -4161,6 +4219,28 @@ function enterStep(d, suite, env, mission, index, opts) {
|
|
|
4161
4219
|
markReadyIfVisible(d, suite, env);
|
|
4162
4220
|
}
|
|
4163
4221
|
}
|
|
4222
|
+
/**
|
|
4223
|
+
* The save button came (a change) or went (nothing left to save): show the
|
|
4224
|
+
* commit step's other form. A ready step whose new target is not there is
|
|
4225
|
+
* left to the target-lost path (back to the checkpoint).
|
|
4226
|
+
*/
|
|
4227
|
+
function setNothingToSave(d, suite, env, on) {
|
|
4228
|
+
const s = d.state.screen;
|
|
4229
|
+
if (s.name !== "step" || s.nothingToSave === on)
|
|
4230
|
+
return;
|
|
4231
|
+
setScreen(d, { ...s, nothingToSave: on, error: null });
|
|
4232
|
+
if (s.phase !== "ready") {
|
|
4233
|
+
markReadyIfVisible(d, suite, env);
|
|
4234
|
+
return;
|
|
4235
|
+
}
|
|
4236
|
+
const mission = findMission(suite, s.mission);
|
|
4237
|
+
if (!mission)
|
|
4238
|
+
return;
|
|
4239
|
+
const step = shownStep(mission.steps[s.index], on);
|
|
4240
|
+
const ctx = guideContext(env, d.state.progress.context, routeParamsFor(step, env));
|
|
4241
|
+
if (stepTargetVisible(step, env, ctx, s.bridge))
|
|
4242
|
+
announceStep(d, suite, env);
|
|
4243
|
+
}
|
|
4164
4244
|
function markReadyIfVisible(d, suite, env) {
|
|
4165
4245
|
const s = d.state.screen;
|
|
4166
4246
|
if (s.name !== "step" || s.phase !== "waiting")
|
|
@@ -4168,7 +4248,7 @@ function markReadyIfVisible(d, suite, env) {
|
|
|
4168
4248
|
const mission = findMission(suite, s.mission);
|
|
4169
4249
|
if (!mission)
|
|
4170
4250
|
return;
|
|
4171
|
-
const step = mission.steps[s.index];
|
|
4251
|
+
const step = shownStep(mission.steps[s.index], s.nothingToSave);
|
|
4172
4252
|
const ctx = guideContext(env, d.state.progress.context, routeParamsFor(step, env));
|
|
4173
4253
|
if (stepTargetVisible(step, env, ctx, s.bridge)) {
|
|
4174
4254
|
setScreen(d, { ...s, phase: "ready" });
|
|
@@ -4290,6 +4370,17 @@ function startMission(d, suite, env, key, replay) {
|
|
|
4290
4370
|
if (at > 0)
|
|
4291
4371
|
index = checkpointAtOrBefore(mission.steps, at);
|
|
4292
4372
|
}
|
|
4373
|
+
if (isReplay) {
|
|
4374
|
+
// What is open now is the user's: ending the replay leaves it alone.
|
|
4375
|
+
d.state = {
|
|
4376
|
+
...d.state,
|
|
4377
|
+
replayPage: {
|
|
4378
|
+
mission: key,
|
|
4379
|
+
anchors: abortAnchorsOf(mission).filter(a => safe(() => env.isVisible(a), false)),
|
|
4380
|
+
dialog: safe(() => env.dialogOpen?.() === true, false),
|
|
4381
|
+
},
|
|
4382
|
+
};
|
|
4383
|
+
}
|
|
4293
4384
|
enterStep(d, suite, env, mission, index, { replay: isReplay });
|
|
4294
4385
|
// A replay never writes progress; only what starting it changed (the
|
|
4295
4386
|
// welcome, first seen through a replay) is saved.
|
|
@@ -4350,7 +4441,8 @@ function pushAbort(d, suite, s) {
|
|
|
4350
4441
|
const mission = findMission(suite, s.mission);
|
|
4351
4442
|
if (!mission)
|
|
4352
4443
|
return;
|
|
4353
|
-
const
|
|
4444
|
+
const before = d.state.replayPage?.mission === s.mission ? d.state.replayPage : null;
|
|
4445
|
+
const actions = abortActionsLeft(abortActionsFor(mission, s.index), before);
|
|
4354
4446
|
if (actions.length)
|
|
4355
4447
|
d.effects.push({ type: "abort", actions });
|
|
4356
4448
|
}
|
|
@@ -4443,7 +4535,12 @@ function reduceGuide(state, action, suite, env) {
|
|
|
4443
4535
|
}
|
|
4444
4536
|
if (s.confirmExit)
|
|
4445
4537
|
break;
|
|
4446
|
-
const
|
|
4538
|
+
const nts = !s.bridge && nothingToSaveHolds(step, env, ctx);
|
|
4539
|
+
if (nts !== s.nothingToSave) {
|
|
4540
|
+
setNothingToSave(d, suite, env, nts);
|
|
4541
|
+
break;
|
|
4542
|
+
}
|
|
4543
|
+
const kind = stepKind(shownStep(step, s.nothingToSave));
|
|
4447
4544
|
const armed = s.armed || hiddenAnchorShown(step, env);
|
|
4448
4545
|
if (kind !== "input" && kind !== "view") {
|
|
4449
4546
|
const held = syncTriggerHolds(step, env, armed);
|
|
@@ -4452,7 +4549,7 @@ function reduceGuide(state, action, suite, env) {
|
|
|
4452
4549
|
break;
|
|
4453
4550
|
}
|
|
4454
4551
|
}
|
|
4455
|
-
if (!s.bridge && !stepTargetVisible(step, env, ctx, false) && offRoute(mission.steps, s.index, env)) {
|
|
4552
|
+
if (!s.bridge && !stepTargetVisible(shownStep(step, s.nothingToSave), env, ctx, false) && offRoute(mission.steps, s.index, env)) {
|
|
4456
4553
|
// The target is missing because the page is another one.
|
|
4457
4554
|
recoverOffRoute(d, suite, env, false);
|
|
4458
4555
|
break;
|
|
@@ -4523,8 +4620,9 @@ function reduceGuide(state, action, suite, env) {
|
|
|
4523
4620
|
if (!mission)
|
|
4524
4621
|
break;
|
|
4525
4622
|
const step = mission.steps[s.index];
|
|
4526
|
-
const kind = stepKind(step);
|
|
4623
|
+
const kind = stepKind(shownStep(step, s.nothingToSave));
|
|
4527
4624
|
if (kind === "view") {
|
|
4625
|
+
// A read step, or a commit step with nothing to save.
|
|
4528
4626
|
advance(d, suite, env);
|
|
4529
4627
|
break;
|
|
4530
4628
|
}
|
|
@@ -4553,21 +4651,24 @@ function reduceGuide(state, action, suite, env) {
|
|
|
4553
4651
|
const mission = findMission(suite, s.mission);
|
|
4554
4652
|
if (!mission)
|
|
4555
4653
|
break;
|
|
4556
|
-
const
|
|
4557
|
-
const ctx = guideContext(env, d.state.progress.context, routeParamsFor(
|
|
4558
|
-
|
|
4654
|
+
const raw = mission.steps[s.index];
|
|
4655
|
+
const ctx = guideContext(env, d.state.progress.context, routeParamsFor(raw, env));
|
|
4656
|
+
// The save went with nothing left to save, or came back: the other form.
|
|
4657
|
+
setNothingToSave(d, suite, env, !s.bridge && nothingToSaveHolds(raw, env, ctx));
|
|
4658
|
+
const cur = d.state.screen;
|
|
4659
|
+
if (stepTargetVisible(shownStep(raw, cur.nothingToSave), env, ctx, cur.bridge))
|
|
4559
4660
|
break;
|
|
4560
|
-
if (!
|
|
4661
|
+
if (!cur.bridge && offRoute(mission.steps, cur.index, env)) {
|
|
4561
4662
|
recoverOffRoute(d, suite, env, false);
|
|
4562
4663
|
break;
|
|
4563
4664
|
}
|
|
4564
|
-
const cp = checkpointAtOrBefore(mission.steps,
|
|
4565
|
-
if (cp <
|
|
4665
|
+
const cp = checkpointAtOrBefore(mission.steps, cur.index);
|
|
4666
|
+
if (cp < cur.index && !cur.bridge) {
|
|
4566
4667
|
// A dialog or a menu closed under the step: back to what opens it.
|
|
4567
|
-
enterStep(d, suite, env, mission, cp, { replay:
|
|
4668
|
+
enterStep(d, suite, env, mission, cp, { replay: cur.replay, noAutoPass: false });
|
|
4568
4669
|
}
|
|
4569
4670
|
else {
|
|
4570
|
-
setScreen(d, { ...
|
|
4671
|
+
setScreen(d, { ...cur, phase: "waiting" });
|
|
4571
4672
|
}
|
|
4572
4673
|
break;
|
|
4573
4674
|
}
|
|
@@ -5159,6 +5260,12 @@ function coveredByDialog(el) {
|
|
|
5159
5260
|
}
|
|
5160
5261
|
return false;
|
|
5161
5262
|
}
|
|
5263
|
+
/** An app dialog (not the tour's) is open on the page. */
|
|
5264
|
+
function appDialogOpen() {
|
|
5265
|
+
if (!hasDocument())
|
|
5266
|
+
return false;
|
|
5267
|
+
return Array.from(document.querySelectorAll(OPEN_DIALOG_SELECTOR)).some(d => !inGuideLayer(d) && isPresent(d));
|
|
5268
|
+
}
|
|
5162
5269
|
function openPoppers() {
|
|
5163
5270
|
if (!hasDocument())
|
|
5164
5271
|
return [];
|
|
@@ -6197,6 +6304,7 @@ function GuideHostInner({ config, organizationId, code }) {
|
|
|
6197
6304
|
isVisible: (anchor, id) => anchorVisible(anchor, id),
|
|
6198
6305
|
sidebarOpen: pg.sidebarOpen,
|
|
6199
6306
|
targetValue: () => fieldValue(targetElRef.current),
|
|
6307
|
+
dialogOpen: () => appDialogOpen(),
|
|
6200
6308
|
};
|
|
6201
6309
|
}, [code]);
|
|
6202
6310
|
/* ---------------- effects: save, announce, toast, navigate ---------------- */
|
|
@@ -6373,11 +6481,13 @@ function GuideHostInner({ config, organizationId, code }) {
|
|
|
6373
6481
|
const mission = findMission(suite, screen.mission);
|
|
6374
6482
|
if (!mission)
|
|
6375
6483
|
return null;
|
|
6376
|
-
const
|
|
6377
|
-
if (!
|
|
6484
|
+
const raw = mission.steps[screen.index];
|
|
6485
|
+
if (!raw)
|
|
6378
6486
|
return null;
|
|
6487
|
+
// A commit step with nothing to save is shown as its read form.
|
|
6488
|
+
const step = shownStep(raw, screen.nothingToSave);
|
|
6379
6489
|
const missionNo = suite.missions.indexOf(mission) + 1;
|
|
6380
|
-
return { mission, step, missionNo };
|
|
6490
|
+
return { mission, step, raw, missionNo };
|
|
6381
6491
|
}, [state, screen, suite]);
|
|
6382
6492
|
const stepCtx = useMemo(() => {
|
|
6383
6493
|
if (!state)
|
|
@@ -6393,7 +6503,7 @@ function GuideHostInner({ config, organizationId, code }) {
|
|
|
6393
6503
|
: null;
|
|
6394
6504
|
const targetKey = target ? `${target.anchor}|${target.id ?? ""}` : "";
|
|
6395
6505
|
const phase = screen.name === "step" ? screen.phase : null;
|
|
6396
|
-
const stepId = screen.name === "step" ? `${screen.mission}:${screen.index}:${screen.bridge ? "b" : ""}` : "";
|
|
6506
|
+
const stepId = screen.name === "step" ? `${screen.mission}:${screen.index}:${screen.bridge ? "b" : ""}${screen.nothingToSave ? "n" : ""}` : "";
|
|
6397
6507
|
/* Measure every frame while a step is shown: the spotlight follows
|
|
6398
6508
|
scrolling, dialogs animating in, the drawer sliding, the keyboard. */
|
|
6399
6509
|
const [layout, setLayout] = useState({
|
|
@@ -6556,7 +6666,7 @@ function GuideHostInner({ config, organizationId, code }) {
|
|
|
6556
6666
|
dispatchRef.current({ type: "signal", signal: { kind: "api", method: d.method, path: d.path, status: d.status, id: d.id, message: d.message } });
|
|
6557
6667
|
};
|
|
6558
6668
|
window.addEventListener(API_EVENT, onApi);
|
|
6559
|
-
const names = triggersOf(stepInfo.
|
|
6669
|
+
const names = triggersOf(stepInfo.raw).flatMap(t => ("event" in t ? [t.event] : []));
|
|
6560
6670
|
const onApp = (name) => (e) => dispatchRef.current({ type: "signal", signal: { kind: "event", name, detail: e.detail ?? undefined } });
|
|
6561
6671
|
const appHandlers = names.map(n => [GUIDE_EVENT_PREFIX + n, onApp(n)]);
|
|
6562
6672
|
appHandlers.forEach(([n, h]) => window.addEventListener(n, h));
|