@sunboard/react 0.9.0 → 1.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 +1 -1
- package/README.md +234 -26
- package/dist/index.d.mts +915 -305
- package/dist/index.mjs +4322 -1735
- package/dist/styles.css +1076 -727
- package/package.json +41 -35
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,348 +1,958 @@
|
|
|
1
1
|
import { ReactNode } from "react";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
//#region src/adapters/navigation.d.ts
|
|
7
|
-
type NavigateCallback = (url: string) => void | Promise<void>;
|
|
8
|
-
//#endregion
|
|
9
|
-
//#region src/lib/detect-identities.d.ts
|
|
2
|
+
//#region ../core/src/guides.d.ts
|
|
3
|
+
type GuideType = "tour" | "tooltip" | "announcement" | "banner";
|
|
4
|
+
type StepPrimitive = "pin" | "modal" | "popover";
|
|
10
5
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* ride the bootstrap request so the server can join Sunboard's funnel data to
|
|
14
|
-
* the customer's analytics; they are inert until the project connects the
|
|
15
|
-
* provider in Sunboard.
|
|
16
|
-
*
|
|
17
|
-
* Everything here is defensive by construction: a broken or half-loaded
|
|
18
|
-
* analytics snippet on the customer's page must never break onboarding, so
|
|
19
|
-
* every probe is wrapped and failures return nothing.
|
|
6
|
+
* Where a pin dot sits ON its element: a 9-point grid over the element's box.
|
|
7
|
+
* Distinct from StepPlacement, which says which side a CARD opens on.
|
|
20
8
|
*/
|
|
9
|
+
type PinPosition = "top-left" | "top" | "top-right" | "left" | "center" | "right" | "bottom-left" | "bottom" | "bottom-right";
|
|
21
10
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* - `false`: detection disabled entirely, nothing is sent
|
|
11
|
+
* Which side of the anchor the card attaches to (floating-ui's placement
|
|
12
|
+
* vocabulary). Authored placement is a preference, not a promise: the overlay
|
|
13
|
+
* still flips/shifts it when the viewport runs out of room.
|
|
26
14
|
*/
|
|
27
|
-
type
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
type SunboardUser = {
|
|
15
|
+
type StepPlacement = "top" | "top-start" | "top-end" | "right" | "right-start" | "right-end" | "bottom" | "bottom-start" | "bottom-end" | "left" | "left-start" | "left-end";
|
|
16
|
+
interface GuideStep {
|
|
17
|
+
/** Unique within its guide and kept through edits. */
|
|
31
18
|
id: string;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
ok: false;
|
|
53
|
-
reason: "not-ready" | "missing-user" | "missing-tour";
|
|
54
|
-
};
|
|
55
|
-
type StepResult = {
|
|
56
|
-
ok: true;
|
|
57
|
-
} | {
|
|
58
|
-
ok: false;
|
|
59
|
-
reason: "not-ready" | "missing-user" | "missing-step" | "not-manual" | "network-error";
|
|
60
|
-
};
|
|
61
|
-
type TooltipResult = {
|
|
62
|
-
ok: true;
|
|
63
|
-
} | {
|
|
64
|
-
ok: false;
|
|
65
|
-
reason: "not-ready" | "missing-user" | "missing-tooltip";
|
|
66
|
-
};
|
|
67
|
-
type VisibleSunboardTooltip = {
|
|
19
|
+
/** Pathname the step lives on, e.g. "/settings". May contain :params. Absent for route-independent modal steps. */
|
|
20
|
+
route?: string;
|
|
21
|
+
/** Absent for modal steps (nothing to point at). */
|
|
22
|
+
anchor?: Anchor;
|
|
23
|
+
primitive: StepPrimitive;
|
|
24
|
+
/** Anchored steps only. Default DEFAULT_PLACEMENT. */
|
|
25
|
+
placement?: StepPlacement;
|
|
26
|
+
/** Pin steps only: where the dot sits on the element. Default "top-right". */
|
|
27
|
+
pinPosition?: PinPosition;
|
|
28
|
+
title: string;
|
|
29
|
+
body: string;
|
|
30
|
+
/** Image/video URL carried from the article, only when it shows what the live UI cannot. */
|
|
31
|
+
media?: string;
|
|
32
|
+
/** Heading of the article section this step came from (article-level provenance). */
|
|
33
|
+
sourceSection?: string;
|
|
34
|
+
/** Position in the plan's grounding record of the grounded step this step was written from. */
|
|
35
|
+
groundedIndex?: number;
|
|
36
|
+
}
|
|
37
|
+
interface GuideContent {
|
|
38
|
+
/** kebab-case identifier, unique per workspace. */
|
|
68
39
|
key: string;
|
|
69
|
-
|
|
40
|
+
type: GuideType;
|
|
41
|
+
title: string;
|
|
42
|
+
steps: GuideStep[];
|
|
70
43
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* tooltip
|
|
74
|
-
*
|
|
44
|
+
* Tooltip only: the tour its card's action starts ("Show me"). The pin+card
|
|
45
|
+
* is the one ambient launch surface, so a tour started "from a pin" is a
|
|
46
|
+
* tooltip that launches it. Curated in the dashboard, never generated (the
|
|
47
|
+
* write stage sees one article at a time and cannot know other tours).
|
|
75
48
|
*/
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
49
|
+
launches?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Stamped by the server at bootstrap, not authored: the guide's current
|
|
52
|
+
* revision, echoed back on every event so a regenerated guide's funnel
|
|
53
|
+
* never mixes two different step sequences.
|
|
54
|
+
*/
|
|
55
|
+
revision?: number;
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region ../core/src/pages.d.ts
|
|
59
|
+
interface AppPage {
|
|
60
|
+
/** The route pattern, e.g. "/invoices/:id". */
|
|
61
|
+
path: string;
|
|
62
|
+
/** What the page is called, or null when nothing has named it yet. */
|
|
63
|
+
title: string | null;
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region ../core/src/settings.d.ts
|
|
67
|
+
interface ComponentSettings {
|
|
68
|
+
launcher: {
|
|
69
|
+
showBranding: boolean;
|
|
70
|
+
supportUrl: string | null;
|
|
71
|
+
};
|
|
72
|
+
tour: {
|
|
73
|
+
showStepCount: boolean;
|
|
74
|
+
showBackButton: boolean;
|
|
75
|
+
};
|
|
76
|
+
banner: {
|
|
77
|
+
dismissible: boolean;
|
|
78
|
+
};
|
|
79
|
+
cards: {
|
|
80
|
+
/** The main button on a tour step, announcement or tooltip: a filled accent button, or accent text alone. */
|
|
81
|
+
primaryButton: "filled" | "text";
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region ../core/src/theme.d.ts
|
|
86
|
+
type ThemeMode = "light" | "dark";
|
|
87
|
+
/** A color that carries an opacity (0-100). */
|
|
88
|
+
interface AlphaColor {
|
|
89
|
+
hex: string;
|
|
90
|
+
alpha: number;
|
|
91
|
+
}
|
|
92
|
+
interface Palette {
|
|
93
|
+
accent: {
|
|
94
|
+
color: string;
|
|
95
|
+
textOn: string;
|
|
96
|
+
};
|
|
97
|
+
content: {
|
|
98
|
+
primary: string;
|
|
99
|
+
secondary: string;
|
|
100
|
+
};
|
|
101
|
+
background: {
|
|
102
|
+
primary: string;
|
|
103
|
+
primaryHover: string;
|
|
104
|
+
overlay: AlphaColor;
|
|
105
|
+
};
|
|
106
|
+
border: {
|
|
107
|
+
primary: string;
|
|
108
|
+
primaryHover: string;
|
|
109
|
+
};
|
|
110
|
+
focusRing: AlphaColor;
|
|
111
|
+
cardShadowColor: AlphaColor;
|
|
112
|
+
}
|
|
113
|
+
interface WidgetDims {
|
|
114
|
+
width: number;
|
|
115
|
+
maxHeight?: number;
|
|
116
|
+
}
|
|
117
|
+
interface Theme {
|
|
80
118
|
name: string;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
119
|
+
light: Palette;
|
|
120
|
+
dark: Palette;
|
|
121
|
+
typography: {
|
|
122
|
+
family: string;
|
|
123
|
+
size: {
|
|
124
|
+
sm: number;
|
|
125
|
+
base: number;
|
|
126
|
+
lg: number;
|
|
127
|
+
};
|
|
128
|
+
weight: {
|
|
129
|
+
regular: number;
|
|
130
|
+
medium: number;
|
|
131
|
+
semibold: number;
|
|
132
|
+
};
|
|
133
|
+
lineHeight: {
|
|
134
|
+
snug: number;
|
|
135
|
+
normal: number;
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
/** Every button and input, in the launcher panel and on the cards alike. */
|
|
139
|
+
formControls: {
|
|
140
|
+
height: number;
|
|
141
|
+
radius: number;
|
|
142
|
+
padding: number;
|
|
143
|
+
gap: number;
|
|
144
|
+
focusRingWidth: number;
|
|
145
|
+
};
|
|
146
|
+
cards: {
|
|
147
|
+
radius: number;
|
|
148
|
+
padding: number;
|
|
149
|
+
shadow: string;
|
|
150
|
+
};
|
|
151
|
+
widgets: {
|
|
152
|
+
announcement: WidgetDims;
|
|
153
|
+
tour: WidgetDims;
|
|
154
|
+
tooltip: WidgetDims;
|
|
155
|
+
launcher: WidgetDims;
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
//#endregion
|
|
159
|
+
//#region ../core/src/runtime.d.ts
|
|
160
|
+
interface UserState {
|
|
161
|
+
/** Tooltips this user has opened once; their pins are not shown again. */
|
|
162
|
+
openedTooltipKeys: string[];
|
|
163
|
+
}
|
|
164
|
+
interface BootstrapData {
|
|
165
|
+
/** Live guides (plus paused ones on preview sessions). */
|
|
166
|
+
guides: GuideContent[];
|
|
167
|
+
pages: AppPage[];
|
|
168
|
+
productName: string;
|
|
169
|
+
userState: UserState;
|
|
170
|
+
/**
|
|
171
|
+
* Dashboard-configured appearance. Null until the workspace configures it;
|
|
172
|
+
* when present, it wins over any client-side prop so dashboard edits reach
|
|
173
|
+
* the app without a deploy.
|
|
174
|
+
*/
|
|
175
|
+
settings: ComponentSettings | null;
|
|
176
|
+
theme: Theme | null;
|
|
177
|
+
/**
|
|
178
|
+
* The steps with an open miss, so the widget says when it finds one. Absent
|
|
179
|
+
* from an API older than the field.
|
|
180
|
+
*/
|
|
181
|
+
missedSteps?: StepRef[];
|
|
182
|
+
/**
|
|
183
|
+
* The anchored steps of the guides waiting to be seen, minus the steps seen
|
|
184
|
+
* at their revision, oldest guides first and at most 300. The widget looks
|
|
185
|
+
* for the ones on its page and shows nothing.
|
|
186
|
+
*/
|
|
187
|
+
checks: StepCheck[];
|
|
188
|
+
}
|
|
189
|
+
/** What bootstrap answers when the workspace's organization has no plan. The SDK shows nothing. */
|
|
190
|
+
interface BootstrapOff {
|
|
191
|
+
off: true;
|
|
192
|
+
}
|
|
193
|
+
type BootstrapAnswer = BootstrapData | BootstrapOff;
|
|
194
|
+
export declare function isBootstrapOff(answer: BootstrapAnswer): answer is BootstrapOff;
|
|
195
|
+
interface StepRef {
|
|
196
|
+
guideKey: string;
|
|
197
|
+
stepId: string;
|
|
198
|
+
}
|
|
199
|
+
type StateAction = {
|
|
200
|
+
action: "open-tooltip";
|
|
201
|
+
key: string;
|
|
84
202
|
};
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
203
|
+
interface AnchorMissReport extends Anchor {
|
|
204
|
+
guideKey: string;
|
|
205
|
+
/**
|
|
206
|
+
* The revision the SDK was serving when it missed, echoed like on events:
|
|
207
|
+
* health surfaces only count misses at the guide's current revision, so a
|
|
208
|
+
* regeneration clears them.
|
|
209
|
+
*/
|
|
210
|
+
guideRevision?: number;
|
|
211
|
+
stepId: string;
|
|
89
212
|
route?: string;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
* Complete a checklist step because the user checked it off. Only steps the
|
|
117
|
-
* spec allows (`manual` not set to false) accept it; the completion is
|
|
118
|
-
* recorded with a "manual" source, distinct from event/tour completions.
|
|
119
|
-
*/
|
|
120
|
-
completeChecklistStep: (stepKey: string) => Promise<StepResult>;
|
|
121
|
-
visibleHotspots: Array<{
|
|
122
|
-
key: string;
|
|
123
|
-
hotspot: SunboardHotspot;
|
|
124
|
-
}>;
|
|
125
|
-
dismissedTooltipKeys: ReadonlySet<string>;
|
|
126
|
-
/**
|
|
127
|
-
* Tooltips that qualify for the current route, requested-first. Renderers
|
|
128
|
-
* show ONE at a time: the first entry whose target resolves.
|
|
129
|
-
*/
|
|
130
|
-
visibleTooltips: VisibleSunboardTooltip[];
|
|
131
|
-
/**
|
|
132
|
-
* Open a tooltip on demand (the checklist's openTooltip action). Navigates
|
|
133
|
-
* are the caller's job; this just marks the tooltip requested so it renders
|
|
134
|
-
* once its route matches and its target is present, dismissal or not.
|
|
135
|
-
*/
|
|
136
|
-
openTooltip: (tooltipKey: string) => Promise<TooltipResult>;
|
|
137
|
-
/**
|
|
138
|
-
* Never show this tooltip again. `via: "close"` is the negative signal (the
|
|
139
|
-
* default); `via: "action"` records that the user took the tooltip's action;
|
|
140
|
-
* `via: "target"` records that the user clicked the tooltip's target element.
|
|
141
|
-
*/
|
|
142
|
-
dismissTooltip: (tooltipKey: string, options?: {
|
|
143
|
-
via?: TooltipDismissVia;
|
|
144
|
-
}) => Promise<TooltipResult>;
|
|
145
|
-
/** Report that a tooltip actually reached the user's eyes (deduped per session). */
|
|
146
|
-
markTooltipSeen: (tooltipKey: string) => void;
|
|
147
|
-
};
|
|
148
|
-
//#endregion
|
|
149
|
-
//#region src/components/SunboardProvider.d.ts
|
|
150
|
-
type SunboardProviderProps = {
|
|
151
|
-
spec?: NormalizedSunboardSpec | null;
|
|
152
|
-
user?: SunboardUser | null;
|
|
153
|
-
loading?: boolean;
|
|
213
|
+
/**
|
|
214
|
+
* How many elements the selector alone matched, inside the box when the
|
|
215
|
+
* anchor names one. Zero means the selector is wrong, or the box is not on
|
|
216
|
+
* the page; more than zero means the text filter removed every one of them.
|
|
217
|
+
*/
|
|
218
|
+
selectorMatches: number;
|
|
219
|
+
/**
|
|
220
|
+
* Elements that held the step's text, at most 5, looked for inside the box
|
|
221
|
+
* when the anchor names one, and across the page when that box is not on it.
|
|
222
|
+
*/
|
|
223
|
+
seen: SeenElement[];
|
|
224
|
+
}
|
|
225
|
+
interface AnchorFoundReport {
|
|
226
|
+
guideKey: string;
|
|
227
|
+
guideRevision: number;
|
|
228
|
+
stepId: string;
|
|
229
|
+
}
|
|
230
|
+
/** One step of a guide waiting to be seen, for the widget to look for on its page. */
|
|
231
|
+
interface StepCheck {
|
|
232
|
+
guideKey: string;
|
|
233
|
+
guideRevision: number;
|
|
234
|
+
stepId: string;
|
|
235
|
+
/**
|
|
236
|
+
* The page the step is on: its own route, or the route of the step before
|
|
237
|
+
* it that has one. Absent when no step up to it has a route.
|
|
238
|
+
*/
|
|
154
239
|
route?: string;
|
|
155
|
-
|
|
240
|
+
anchor: Anchor;
|
|
241
|
+
}
|
|
242
|
+
type MissEvidence = Pick<AnchorMissReport, "selectorMatches" | "seen">;
|
|
243
|
+
/** Found, or not found after the wait with what the page held instead. */
|
|
244
|
+
type StepCheckReport = AnchorFoundReport & ({
|
|
245
|
+
found: true;
|
|
246
|
+
} | ({
|
|
247
|
+
found: false;
|
|
248
|
+
} & MissEvidence));
|
|
249
|
+
declare const CONTAINERS: readonly ["dialog", "[role=\"dialog\"]", "[role=\"alertdialog\"]", "form", "fieldset", "section", "nav", "main", "aside", "tr", "[role=\"row\"]"];
|
|
250
|
+
type ContainerSelector = (typeof CONTAINERS)[number];
|
|
251
|
+
type SeenContainer = {
|
|
252
|
+
/** Its role when the role is one of `CONTAINERS`, else its tag. */
|
|
253
|
+
selector: ContainerSelector;
|
|
254
|
+
/**
|
|
255
|
+
* From `aria-labelledby`, `aria-label`, a fieldset's legend, a row's table
|
|
256
|
+
* caption, or its first heading. At most 80 characters.
|
|
257
|
+
*/
|
|
258
|
+
name?: string;
|
|
259
|
+
};
|
|
260
|
+
/** From `getBoundingClientRect`, rounded to whole pixels. */
|
|
261
|
+
type SeenBox = {
|
|
262
|
+
x: number;
|
|
263
|
+
y: number;
|
|
264
|
+
w: number;
|
|
265
|
+
h: number;
|
|
266
|
+
};
|
|
267
|
+
/** One element on the page as the widget saw it: a candidate for a missed step, or a line of a census. */
|
|
268
|
+
type SeenElement = {
|
|
269
|
+
/** Lower case. */
|
|
270
|
+
tag: string;
|
|
271
|
+
role?: string;
|
|
272
|
+
id?: string;
|
|
273
|
+
/** Its `data-testid`. */
|
|
274
|
+
testId?: string;
|
|
275
|
+
ariaLabel?: string;
|
|
276
|
+
/** An input's `type`. */
|
|
277
|
+
type?: string;
|
|
156
278
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
* `DEFAULT_RUNTIME_API_URL`.
|
|
279
|
+
* From `aria-labelledby`, `aria-label`, a form control's labels, a button
|
|
280
|
+
* input's value, a placeholder, or a title. At most 80 characters.
|
|
160
281
|
*/
|
|
161
|
-
|
|
162
|
-
|
|
282
|
+
name?: string;
|
|
283
|
+
/** At most 3. */
|
|
284
|
+
classes: string[];
|
|
163
285
|
/**
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
* provider that initializes late is still captured. Pass explicit ids to
|
|
168
|
-
* override detection; pass `false` to disable capture entirely. Captured ids
|
|
169
|
-
* are inert until the provider is connected to the project in Sunboard.
|
|
286
|
+
* Text with whitespace collapsed to single spaces, at most 80
|
|
287
|
+
* characters. Empty for a field, a select, or anything else that shows what
|
|
288
|
+
* a person typed or chose.
|
|
170
289
|
*/
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
290
|
+
text: string;
|
|
291
|
+
within?: SeenContainer;
|
|
292
|
+
/** A link's path on this site, without its query or hash. */
|
|
293
|
+
href?: string;
|
|
294
|
+
/** Absent when the element has no size. */
|
|
295
|
+
box?: SeenBox;
|
|
174
296
|
};
|
|
175
|
-
declare function SunboardProvider({
|
|
176
|
-
spec: specProp,
|
|
177
|
-
user,
|
|
178
|
-
loading: loadingProp,
|
|
179
|
-
route,
|
|
180
|
-
navigate,
|
|
181
|
-
apiUrl,
|
|
182
|
-
publishableKey,
|
|
183
|
-
identities: identitiesProp,
|
|
184
|
-
onTrack,
|
|
185
|
-
children
|
|
186
|
-
}: SunboardProviderProps): react_jsx_runtime0.JSX.Element;
|
|
187
|
-
//#endregion
|
|
188
|
-
//#region src/components/SunboardHotspots.d.ts
|
|
189
|
-
declare function SunboardHotspots(): react_jsx_runtime0.JSX.Element;
|
|
190
|
-
//#endregion
|
|
191
|
-
//#region src/components/SunboardTooltips.d.ts
|
|
192
297
|
/**
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
* of the session (without recording a dismissal — the user rejected nothing),
|
|
196
|
-
* so one stale selector can't block the tooltips behind it.
|
|
298
|
+
* The interactive elements on one page, or inside the dialog open on it.
|
|
299
|
+
* Structure and labels only, never what a person typed.
|
|
197
300
|
*/
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
301
|
+
interface PageReport {
|
|
302
|
+
pathname: string;
|
|
303
|
+
/** Every element listed sits inside an open dialog, so the page behind it is covered. */
|
|
304
|
+
dialog: boolean;
|
|
305
|
+
/** One per fingerprint, in document order, at most 300. */
|
|
306
|
+
elements: SeenElement[];
|
|
307
|
+
/** `pageHash(elements, dialog)`. */
|
|
308
|
+
hash: string;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* What end users did with the guide layer. Behaviour only: which guide, which
|
|
312
|
+
* step, when. Never anything about the page itself. `preview` and `origin` are
|
|
313
|
+
* stamped by the server from request headers, so they are absent here.
|
|
314
|
+
*/
|
|
315
|
+
type GuideEventType = "LAUNCHER_OPENED" | "GUIDE_PICKED" | "TOUR_STARTED" | "TOUR_STEP_SEEN" | "TOUR_COMPLETED" | "TOUR_DISMISSED" | "TOUR_RATED" | "TOOLTIP_OPENED" | "TOOLTIP_ACTED" | "ANNOUNCEMENT_SHOWN" | "ANNOUNCEMENT_DISMISSED" | "ANNOUNCEMENT_ACTED" | "BANNER_SHOWN" | "BANNER_DISMISSED";
|
|
316
|
+
/** The stars a visitor gives a tour they finished. */
|
|
317
|
+
type TourRating = 1 | 2 | 3 | 4 | 5;
|
|
318
|
+
/** A rating comes with TOUR_RATED and with nothing else. */
|
|
319
|
+
type GuideEventReport = {
|
|
320
|
+
guideKey?: string;
|
|
321
|
+
guideRevision?: number;
|
|
322
|
+
stepId?: string;
|
|
323
|
+
route?: string;
|
|
324
|
+
/** ISO timestamp of when it happened, since events are batched before send. */
|
|
325
|
+
occurredAt?: string;
|
|
326
|
+
} & ({
|
|
327
|
+
type: Exclude<GuideEventType, "TOUR_RATED">;
|
|
328
|
+
rating?: never;
|
|
329
|
+
} | {
|
|
330
|
+
type: "TOUR_RATED";
|
|
331
|
+
rating: TourRating;
|
|
332
|
+
});
|
|
333
|
+
/**
|
|
334
|
+
* ANSWERED is an answer written from the docs and checked against them.
|
|
335
|
+
* UNSUPPORTED is one the check did not back twice, so the visitor gets the
|
|
336
|
+
* not-covered reply. NONE found nothing to answer with.
|
|
337
|
+
* GUIDE offers a tour, with an answer when one was written and backed.
|
|
338
|
+
*/
|
|
339
|
+
type AskOutcome = "GUIDE" | "ANSWERED" | "UNSUPPORTED" | "NONE";
|
|
340
|
+
/** `id` is the stored question's id. `answer` is the whole text to show. */
|
|
341
|
+
interface AskReply {
|
|
342
|
+
id: string;
|
|
343
|
+
/** Sent back with the next question so it is read as a follow-up. */
|
|
344
|
+
conversationId: string;
|
|
345
|
+
/** This question opened its conversation, so the widget greets the visitor. */
|
|
346
|
+
firstTurn: boolean;
|
|
347
|
+
outcome: AskOutcome;
|
|
348
|
+
/**
|
|
349
|
+
* Plain text with blank lines between paragraphs, `1.` and `-` lists, and
|
|
350
|
+
* `**bold**`. Empty when a guide is offered with nothing written.
|
|
351
|
+
*/
|
|
352
|
+
answer: string;
|
|
353
|
+
guideKey?: string;
|
|
354
|
+
/** Set when the answer is the not-covered sentence and the project has a support link. */
|
|
355
|
+
supportUrl?: string;
|
|
356
|
+
}
|
|
357
|
+
/** What the server is doing while the visitor waits, in this order. */
|
|
358
|
+
type AskStage = "searching" | "writing" | "checking";
|
|
202
359
|
//#endregion
|
|
203
|
-
//#region src/
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
360
|
+
//#region ../core/src/anchors.d.ts
|
|
361
|
+
type AnchorPart = {
|
|
362
|
+
selector: string;
|
|
363
|
+
/**
|
|
364
|
+
* Case-sensitive substring match with whitespace ignored, on the element's
|
|
365
|
+
* text, or on its accessible name when no candidate's text holds it.
|
|
366
|
+
*/
|
|
367
|
+
text?: string;
|
|
368
|
+
};
|
|
369
|
+
type Anchor = AnchorPart & {
|
|
370
|
+
within?: AnchorPart;
|
|
212
371
|
};
|
|
213
372
|
//#endregion
|
|
214
|
-
//#region src/
|
|
215
|
-
|
|
373
|
+
//#region ../core/src/labels.d.ts
|
|
374
|
+
interface Labels {
|
|
375
|
+
launcher: {
|
|
376
|
+
/** The pill's text. */
|
|
377
|
+
open: string;
|
|
378
|
+
openAria: string;
|
|
379
|
+
panelAria: string;
|
|
380
|
+
/** The question box's own label, which the design hides visually. */
|
|
381
|
+
boxLabel: string;
|
|
382
|
+
/** In the box before the first question, and once the thread has one. */
|
|
383
|
+
placeholderFirst: string;
|
|
384
|
+
placeholderAgain: string;
|
|
385
|
+
/** Over the suggestions while nothing has been asked. */
|
|
386
|
+
emptyTitle: string;
|
|
387
|
+
/** The header button that clears the thread, and the box that asks first. */
|
|
388
|
+
startOver: string;
|
|
389
|
+
startOverTitle: string;
|
|
390
|
+
startOverBody: string;
|
|
391
|
+
cancel: string;
|
|
392
|
+
/** The button inside the box. */
|
|
393
|
+
send: string;
|
|
394
|
+
/** The pill that scrolls back down to the newest message. */
|
|
395
|
+
latest: string;
|
|
396
|
+
/** Over the first answer of a conversation. `name` is the connection's `user.firstName`. */
|
|
397
|
+
greeting: (name?: string) => string;
|
|
398
|
+
/** Under an answer that offers a guide, above Show me how. */
|
|
399
|
+
guideClosing: string;
|
|
400
|
+
/** The lines the thread gets when a tour it started ends. */
|
|
401
|
+
tourFinishedAsk: string;
|
|
402
|
+
tourStopped: (step: number, total: number) => string;
|
|
403
|
+
/** The stars under a finished tour, and each star's name. */
|
|
404
|
+
rateTour: string;
|
|
405
|
+
stars: (count: number) => string;
|
|
406
|
+
/** The line once the visitor picked a star. */
|
|
407
|
+
ratingThanks: string;
|
|
408
|
+
/** The thread's own name, for a screen reader. */
|
|
409
|
+
conversation: string;
|
|
410
|
+
/** Said once to a screen reader while a question is out. */
|
|
411
|
+
writingStatus: string;
|
|
412
|
+
/** While the question is out, one for each stage the server reports. */
|
|
413
|
+
stages: {
|
|
414
|
+
searching: (product: string) => string;
|
|
415
|
+
writing: string;
|
|
416
|
+
checking: string;
|
|
417
|
+
/** After six seconds of waiting. */
|
|
418
|
+
slow: string;
|
|
419
|
+
};
|
|
420
|
+
/** Over the guides listed before the first question, this page's first. */
|
|
421
|
+
commonQuestions: string;
|
|
422
|
+
/** The one button under an answer that has a guide behind it. */
|
|
423
|
+
showMeHow: string;
|
|
424
|
+
/** Said after a link that opens a new tab, for a screen reader. */
|
|
425
|
+
newTab: string;
|
|
426
|
+
/** Over the support link, when there is nothing else to offer. */
|
|
427
|
+
supportOffer: string;
|
|
428
|
+
contactSupport: string;
|
|
429
|
+
rateLimited: string;
|
|
430
|
+
askAgainIn: (minutes: number) => string;
|
|
431
|
+
askAgainAt: (time: string) => string;
|
|
432
|
+
/** No reply at all: the request never landed. The panel puts lostBody under it. */
|
|
433
|
+
lost: string;
|
|
434
|
+
lostBody: string;
|
|
435
|
+
tryAgain: string;
|
|
436
|
+
/**
|
|
437
|
+
* What the server answers when there is nothing to show: no guide and no
|
|
438
|
+
* section, or no guide and an answer the check did not back twice.
|
|
439
|
+
*/
|
|
440
|
+
notCovered: (product: string) => string;
|
|
441
|
+
closeAria: string;
|
|
442
|
+
poweredBy: string;
|
|
443
|
+
};
|
|
444
|
+
tour: {
|
|
445
|
+
back: string;
|
|
446
|
+
next: string;
|
|
447
|
+
done: string;
|
|
448
|
+
closeAria: string;
|
|
449
|
+
stepCount: (current: number, total: number) => string;
|
|
450
|
+
/** On the pill that stands in for the launcher while a tour runs. */
|
|
451
|
+
progress: (current: number, total: number) => string;
|
|
452
|
+
stop: string;
|
|
453
|
+
};
|
|
454
|
+
tooltip: {
|
|
455
|
+
openAria: (title: string) => string;
|
|
456
|
+
closeAria: string;
|
|
457
|
+
showMe: string;
|
|
458
|
+
};
|
|
459
|
+
announcement: {
|
|
460
|
+
remindMe: string;
|
|
461
|
+
showMe: string;
|
|
462
|
+
gotIt: string;
|
|
463
|
+
closeAria: string;
|
|
464
|
+
};
|
|
465
|
+
banner: {
|
|
466
|
+
dismissAria: string;
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
export declare const enLabels: Labels;
|
|
470
|
+
/** Any subset of the labels, group by group. The rest stay English. */
|
|
471
|
+
type LabelOverrides = { [Group in keyof Labels]?: Partial<Labels[Group]>; };
|
|
216
472
|
//#endregion
|
|
217
|
-
//#region src/
|
|
218
|
-
|
|
473
|
+
//#region ../core/src/launcher.d.ts
|
|
474
|
+
interface LauncherGroups {
|
|
475
|
+
/** Listed guides whose page matches the pathname, by title. */
|
|
476
|
+
onThisPage: GuideContent[];
|
|
477
|
+
/** Every other listed guide, by title. A guide with no page lands here. */
|
|
478
|
+
elsewhere: GuideContent[];
|
|
479
|
+
}
|
|
480
|
+
//#endregion
|
|
481
|
+
//#region src/active-tour.d.ts
|
|
482
|
+
interface ActiveTour {
|
|
483
|
+
key: string;
|
|
484
|
+
stepIndex: number;
|
|
485
|
+
}
|
|
486
|
+
type TourEnding = "completed" | "dismissed";
|
|
487
|
+
/** A tour that just ended: how, and the step it was on (1-based) of how many. */
|
|
488
|
+
interface TourEnd {
|
|
219
489
|
key: string;
|
|
220
490
|
title: string;
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
491
|
+
ending: TourEnding;
|
|
492
|
+
step: number;
|
|
493
|
+
total: number;
|
|
494
|
+
}
|
|
495
|
+
//#endregion
|
|
496
|
+
//#region src/mode.d.ts
|
|
497
|
+
type ThemeModeSetting = ThemeMode | "auto";
|
|
498
|
+
/** The host app's current mode: its own theme marker first, then the OS. */
|
|
499
|
+
export declare function detectThemeMode(): ThemeMode;
|
|
500
|
+
/**
|
|
501
|
+
* Resolve a mode setting to a concrete mode, live: "auto" tracks host theme
|
|
502
|
+
* switches (class/attribute flips) and OS preference changes as they happen.
|
|
503
|
+
* SSR renders light; the first client effect corrects it before anything
|
|
504
|
+
* user-visible depends on the difference.
|
|
505
|
+
*/
|
|
506
|
+
export declare function useThemeMode(setting: ThemeModeSetting): ThemeMode;
|
|
507
|
+
//#endregion
|
|
508
|
+
//#region src/transport.d.ts
|
|
509
|
+
interface AskQuestion {
|
|
510
|
+
question: string;
|
|
511
|
+
pathname: string;
|
|
512
|
+
/** The conversation the last reply named; left out on the first question. */
|
|
513
|
+
conversationId?: string;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* What one question got. A refusal (the question was too long, or too many
|
|
517
|
+
* came from this visitor) is a sentence the visitor reads, not a failure, so
|
|
518
|
+
* it is a value here rather than a thrown error. `retryAt` comes with the
|
|
519
|
+
* refusal the cap produces: the time the visitor is free to ask again.
|
|
520
|
+
*/
|
|
521
|
+
type AskResult = {
|
|
522
|
+
kind: "answer";
|
|
523
|
+
reply: AskReply;
|
|
524
|
+
} | {
|
|
525
|
+
kind: "refused";
|
|
526
|
+
message: string;
|
|
527
|
+
retryAt?: string;
|
|
528
|
+
};
|
|
529
|
+
interface GuidesTransport {
|
|
224
530
|
/**
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
* only through its instrumented rule; render its checkbox non-interactive.
|
|
531
|
+
* The identity a cached bootstrap belongs to. Undefined means this
|
|
532
|
+
* transport's data is never cached.
|
|
228
533
|
*/
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
534
|
+
readonly cacheKey?: string;
|
|
535
|
+
/** The visitor's details from the connection. Never sent anywhere. */
|
|
536
|
+
readonly user?: VisitorDetails;
|
|
537
|
+
bootstrap(): Promise<BootstrapAnswer>;
|
|
538
|
+
/** Persist a user-state mutation; the provider applies it optimistically. */
|
|
539
|
+
mutate(action: StateAction): Promise<void>;
|
|
540
|
+
reportAnchorMiss(report: AnchorMissReport): Promise<void>;
|
|
541
|
+
reportAnchorFound(report: AnchorFoundReport): Promise<void>;
|
|
542
|
+
/** Whether a step of a guide waiting to be seen was on the page the visitor is on. */
|
|
543
|
+
stepCheck(report: StepCheckReport): Promise<void>;
|
|
544
|
+
/**
|
|
545
|
+
* Usage telemetry, batched by the provider. Fire-and-forget by contract:
|
|
546
|
+
* analytics must never delay or fail anything the user is doing.
|
|
547
|
+
*/
|
|
548
|
+
recordEvents(events: GuideEventReport[]): Promise<void>;
|
|
549
|
+
/**
|
|
550
|
+
* Ask one question about the page the visitor is on. Optional because a
|
|
551
|
+
* MemoryTransport with no script has none, which is how the appearance
|
|
552
|
+
* canvas gets a panel with a box that does nothing. `HttpTransport` always
|
|
553
|
+
* has it. `onStage` hears each stage the server starts, in order, before
|
|
554
|
+
* the result settles.
|
|
555
|
+
*/
|
|
556
|
+
ask?(question: AskQuestion, signal: AbortSignal, onStage: (stage: AskStage) => void): Promise<AskResult>;
|
|
557
|
+
/**
|
|
558
|
+
* What is on the page the visitor is looking at. Optional: without it the
|
|
559
|
+
* provider never looks at the page.
|
|
560
|
+
*/
|
|
561
|
+
reportPage?(report: PageReport): Promise<void>;
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* A scripted reply. The transport's own conversation id fills in when it names
|
|
565
|
+
* none, and `firstTurn` is true when the question named no conversation.
|
|
566
|
+
*/
|
|
567
|
+
type ScriptedReply = Omit<AskReply, "conversationId" | "firstTurn"> & {
|
|
568
|
+
conversationId?: string;
|
|
569
|
+
firstTurn?: boolean;
|
|
236
570
|
};
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
hasAction: (step: ChecklistViewStep) => boolean;
|
|
249
|
-
startStep: (step: ChecklistViewStep) => void;
|
|
250
|
-
/**
|
|
251
|
-
* Mark the step complete because the user checked it off. A no-op for
|
|
252
|
-
* steps that are not manually completable or already complete.
|
|
253
|
-
*/
|
|
254
|
-
completeStep: (step: ChecklistViewStep) => void;
|
|
255
|
-
completion?: ChecklistCompletion;
|
|
256
|
-
dismissCompletion: () => void;
|
|
571
|
+
/**
|
|
572
|
+
* What a MemoryTransport answers with. Throwing from it stands for a server
|
|
573
|
+
* that could not be reached at all. `stagesMs` is the wait after each stage,
|
|
574
|
+
* `gapMs` when it is left out.
|
|
575
|
+
*/
|
|
576
|
+
type AskScript = (asked: AskQuestion) => {
|
|
577
|
+
reply: ScriptedReply;
|
|
578
|
+
stagesMs?: number;
|
|
579
|
+
} | {
|
|
580
|
+
refused: string;
|
|
581
|
+
retryAt?: string;
|
|
257
582
|
};
|
|
258
|
-
|
|
583
|
+
interface MemoryTransportOptions {
|
|
584
|
+
cacheKey?: string;
|
|
585
|
+
user?: VisitorDetails;
|
|
586
|
+
/** Opt in to the question box. Without a script there is no `ask` at all. */
|
|
587
|
+
ask?: AskScript;
|
|
588
|
+
/** Opt in to page reports. Without it there is no `reportPage`, and nothing looks at the page. */
|
|
589
|
+
census?: boolean;
|
|
590
|
+
/**
|
|
591
|
+
* The wait before a refusal, and after each stage of a reply unless the
|
|
592
|
+
* script names its own, so every stage of the waiting can be looked at.
|
|
593
|
+
*/
|
|
594
|
+
gapMs?: number;
|
|
595
|
+
}
|
|
596
|
+
export declare class MemoryTransport implements GuidesTransport {
|
|
597
|
+
private readonly data;
|
|
598
|
+
readonly misses: AnchorMissReport[];
|
|
599
|
+
readonly found: AnchorFoundReport[];
|
|
600
|
+
readonly stepChecks: StepCheckReport[];
|
|
601
|
+
readonly mutations: StateAction[];
|
|
602
|
+
readonly events: GuideEventReport[];
|
|
603
|
+
readonly questions: AskQuestion[];
|
|
604
|
+
readonly pages: PageReport[];
|
|
605
|
+
readonly cacheKey: string | undefined;
|
|
606
|
+
readonly user: VisitorDetails | undefined;
|
|
607
|
+
readonly ask?: GuidesTransport["ask"];
|
|
608
|
+
readonly reportPage?: GuidesTransport["reportPage"];
|
|
609
|
+
constructor(data: BootstrapData, options?: MemoryTransportOptions);
|
|
610
|
+
bootstrap(): Promise<BootstrapData>;
|
|
611
|
+
mutate(action: StateAction): Promise<void>;
|
|
612
|
+
reportAnchorMiss(report: AnchorMissReport): Promise<void>;
|
|
613
|
+
reportAnchorFound(report: AnchorFoundReport): Promise<void>;
|
|
614
|
+
stepCheck(report: StepCheckReport): Promise<void>;
|
|
615
|
+
recordEvents(events: GuideEventReport[]): Promise<void>;
|
|
616
|
+
}
|
|
617
|
+
/** The hosted runtime API. Overridden only for self-hosting or local dev. */
|
|
618
|
+
export declare const DEFAULT_SUNBOARD_API_URL = "https://api.sunboardhq.com";
|
|
619
|
+
/**
|
|
620
|
+
* Zero-code dark preview. The dashboard links the admin to their own app with
|
|
621
|
+
* `?sunboardPreview=<key>`; the SDK stores the key, strips the param from the
|
|
622
|
+
* URL, and sends the key on every runtime request from then on, so drafts stay
|
|
623
|
+
* visible in that browser until `?sunboardPreview=off` or the key is rotated.
|
|
624
|
+
* Nothing about preview ever enters the customer's source.
|
|
625
|
+
*/
|
|
626
|
+
export declare const PREVIEW_PARAM = "sunboardPreview";
|
|
627
|
+
export declare const PREVIEW_KEY_STORAGE = "sunboard:preview-key";
|
|
628
|
+
export declare const PREVIEW_EXPIRY_STORAGE = "sunboard:preview-until";
|
|
629
|
+
/** The visitor's details. They stay in the browser: none of them is sent to Sunboard. */
|
|
630
|
+
interface VisitorDetails {
|
|
631
|
+
/** The visitor's first name, as you want the assistant to greet them ("Hey Sam,"). */
|
|
632
|
+
firstName?: string;
|
|
633
|
+
}
|
|
634
|
+
interface HttpTransportConfig {
|
|
635
|
+
/** The workspace's publishable key. Safe in client bundles. */
|
|
636
|
+
publishableKey: string;
|
|
637
|
+
/** Customer-supplied end-user id. */
|
|
638
|
+
userId: string;
|
|
639
|
+
/** The visitor's details, kept in the browser. */
|
|
640
|
+
user?: VisitorDetails;
|
|
641
|
+
/** Origin of the runtime API. Defaults to the hosted API. */
|
|
642
|
+
baseUrl?: string;
|
|
643
|
+
/**
|
|
644
|
+
* Admin-only: unlocks paused guides for preview. Normally NOT set in code:
|
|
645
|
+
* the preview bookmarklet delivers it via localStorage instead (see
|
|
646
|
+
* PREVIEW_KEY_STORAGE). The prop exists for tests and unusual setups.
|
|
647
|
+
*/
|
|
648
|
+
previewKey?: string;
|
|
649
|
+
}
|
|
650
|
+
export declare class HttpTransport implements GuidesTransport {
|
|
651
|
+
private readonly config;
|
|
652
|
+
private readonly baseUrl;
|
|
653
|
+
private readonly previewKey;
|
|
654
|
+
readonly cacheKey: string | undefined;
|
|
655
|
+
readonly user: VisitorDetails | undefined;
|
|
656
|
+
constructor(config: HttpTransportConfig);
|
|
657
|
+
private headers;
|
|
658
|
+
private request;
|
|
659
|
+
bootstrap(): Promise<BootstrapAnswer>;
|
|
660
|
+
mutate(action: StateAction): Promise<void>;
|
|
661
|
+
reportAnchorMiss(report: AnchorMissReport): Promise<void>;
|
|
662
|
+
reportAnchorFound(report: AnchorFoundReport): Promise<void>;
|
|
663
|
+
stepCheck(report: StepCheckReport): Promise<void>;
|
|
664
|
+
recordEvents(events: GuideEventReport[]): Promise<void>;
|
|
665
|
+
reportPage(report: PageReport): Promise<void>;
|
|
666
|
+
ask(asked: AskQuestion, signal: AbortSignal, onStage: (stage: AskStage) => void): Promise<AskResult>;
|
|
667
|
+
}
|
|
259
668
|
//#endregion
|
|
260
|
-
//#region src/
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
type SurveyViewQuestion = {
|
|
266
|
-
key: string;
|
|
267
|
-
prompt: string;
|
|
268
|
-
type: SurveyQuestionType;
|
|
269
|
-
required: boolean;
|
|
270
|
-
options: SurveyViewOption[];
|
|
271
|
-
};
|
|
272
|
-
type SurveyViewStep = {
|
|
273
|
-
title?: string;
|
|
274
|
-
subtitle?: string;
|
|
275
|
-
questions: SurveyViewQuestion[];
|
|
276
|
-
};
|
|
277
|
-
type UseSurveyResult = {
|
|
278
|
-
visible: boolean;
|
|
279
|
-
title?: string;
|
|
280
|
-
subtitle?: string;
|
|
281
|
-
/** Every question across all steps — for all-at-once renderers (the built-in modal). */
|
|
282
|
-
questions: SurveyViewQuestion[];
|
|
283
|
-
/** The authored step grouping (one question per step by default). */
|
|
284
|
-
steps: SurveyViewStep[];
|
|
285
|
-
/** Hook-owned navigation. */
|
|
286
|
-
stepIndex: number;
|
|
287
|
-
totalSteps: number;
|
|
288
|
-
currentStep?: SurveyViewStep;
|
|
289
|
-
progress: number;
|
|
290
|
-
isFirstStep: boolean;
|
|
291
|
-
isLastStep: boolean;
|
|
292
|
-
/** True when the current step's required questions are answered. */
|
|
293
|
-
canAdvance: boolean;
|
|
294
|
-
/** Advance to the next step, or submit on the last step. */
|
|
295
|
-
next: () => void;
|
|
296
|
-
back: () => void;
|
|
297
|
-
answers: Record<string, unknown>;
|
|
298
|
-
setAnswer: (key: string, value: string) => void;
|
|
299
|
-
toggleAnswer: (key: string, value: string) => void;
|
|
300
|
-
isAnswered: (key: string) => boolean;
|
|
301
|
-
/** True when every required question (across all steps) is answered. */
|
|
302
|
-
canSubmit: boolean;
|
|
303
|
-
submitting: boolean;
|
|
669
|
+
//#region src/provider.d.ts
|
|
670
|
+
interface GuidesApi {
|
|
671
|
+
ready: boolean;
|
|
672
|
+
/** The workspace's organization has no plan, so nothing renders. */
|
|
673
|
+
off: boolean;
|
|
304
674
|
error: string | null;
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
675
|
+
getGuide: (key: string) => GuideContent | undefined;
|
|
676
|
+
/** Every bootstrapped guide; the politeness layer arbitrates push types from it. */
|
|
677
|
+
allGuides: () => GuideContent[];
|
|
678
|
+
productName: string;
|
|
679
|
+
/** A tooltip's pin shows until the user opens it once. */
|
|
680
|
+
hasOpenedTooltip: (guideKey: string) => boolean;
|
|
681
|
+
markTooltipOpened: (guideKey: string) => void;
|
|
682
|
+
/** Ignored for a guide with no steps: there would be nothing to show. */
|
|
683
|
+
launchTour: (guideKey: string, stepIndex?: number) => void;
|
|
684
|
+
activeTour: ActiveTour | null;
|
|
685
|
+
/** Show another step of the running tour. Ignored when none is running. */
|
|
686
|
+
setTourStep: (stepIndex: number) => void;
|
|
687
|
+
/** End the running tour. The event that says how it ended goes with it. */
|
|
688
|
+
endTour: (ending: TourEnding) => void;
|
|
689
|
+
/** Hear each tour end, however it ended. Returns the call that stops listening. */
|
|
690
|
+
onTourEnd: (listener: (ended: TourEnd) => void) => () => void;
|
|
691
|
+
/**
|
|
692
|
+
* The identity anything kept in the browser belongs to: the transport's
|
|
693
|
+
* `cacheKey`. Undefined for a preview session, which keeps nothing.
|
|
694
|
+
*/
|
|
695
|
+
cacheKey: string | undefined;
|
|
696
|
+
/** The visitor's first name from the connection, for the assistant's greeting. */
|
|
697
|
+
firstName: string | undefined;
|
|
698
|
+
reportAnchorMiss: GuidesTransport["reportAnchorMiss"];
|
|
699
|
+
/** Sent only for a step that missed before, once per step and revision per page load. */
|
|
700
|
+
reportAnchorFound: (report: AnchorFoundReport) => void;
|
|
701
|
+
/**
|
|
702
|
+
* Ask one question. Undefined when the transport has none or Sunboard is
|
|
703
|
+
* off, which is how `useLauncher` knows whether to offer the box at all. `HttpTransport`
|
|
704
|
+
* always has it; a `MemoryTransport` needs a script.
|
|
705
|
+
*/
|
|
706
|
+
ask: GuidesTransport["ask"];
|
|
707
|
+
/** Record a usage event. Batched, fire-and-forget, never awaited. */
|
|
708
|
+
track: (event: GuideEventReport) => void;
|
|
709
|
+
/**
|
|
710
|
+
* Navigate the host app. Uses the customer's `navigate` prop (e.g.
|
|
711
|
+
* router.push) and falls back to a full page load. Tours use it to reach a
|
|
712
|
+
* step's route.
|
|
713
|
+
*/
|
|
714
|
+
navigate: (path: string) => void;
|
|
715
|
+
}
|
|
716
|
+
interface SunboardProviderProps {
|
|
717
|
+
children: ReactNode;
|
|
718
|
+
/** Connection to the runtime API; or pass a transport directly (tests, demos). */
|
|
719
|
+
connection?: HttpTransportConfig;
|
|
720
|
+
transport?: GuidesTransport;
|
|
721
|
+
/**
|
|
722
|
+
* How the SDK navigates your app (e.g. Next: `(path) => router.push(path)`).
|
|
723
|
+
* Without it, cross-route tour steps fall back to a full page load; tours
|
|
724
|
+
* survive that via sessionStorage, but SPA navigation is smoother.
|
|
725
|
+
*/
|
|
726
|
+
navigate?: (path: string) => void | Promise<void>;
|
|
727
|
+
/**
|
|
728
|
+
* Labels are client-side by design: any subset overrides the English
|
|
729
|
+
* defaults. The customer owns per-user language policy; the server never
|
|
730
|
+
* sends strings.
|
|
731
|
+
*/
|
|
732
|
+
labels?: LabelOverrides;
|
|
733
|
+
/**
|
|
734
|
+
* Fallbacks only. Theme and settings are configured in the Sunboard
|
|
735
|
+
* dashboard and delivered by the API; when the server sends a value, it
|
|
736
|
+
* wins, so dashboard edits reach the app without a deploy. These props
|
|
737
|
+
* cover demos and workspaces that have not configured appearance yet.
|
|
738
|
+
*/
|
|
739
|
+
settings?: ComponentSettings;
|
|
740
|
+
theme?: Theme;
|
|
741
|
+
/**
|
|
742
|
+
* Light/dark selection. Default "auto": follow the host app's own theme
|
|
743
|
+
* marker (a `dark`/`light` class or data attribute on <html>/<body>, the
|
|
744
|
+
* Tailwind/next-themes idiom), falling back to the OS preference, and track
|
|
745
|
+
* changes live. Pass "light" or "dark" to wire the SDK to your own theme
|
|
746
|
+
* state instead.
|
|
747
|
+
*/
|
|
748
|
+
mode?: ThemeModeSetting;
|
|
749
|
+
/**
|
|
750
|
+
* Say what the SDK is doing in the browser console: what bootstrap
|
|
751
|
+
* delivered, each tour step and what it waits for, whether an anchor
|
|
752
|
+
* matched, and why a tour ended. For wiring up and debugging; off by
|
|
753
|
+
* default and silent. `?sunboardDebug=1` on the page URL turns it on for
|
|
754
|
+
* that page load.
|
|
755
|
+
*/
|
|
756
|
+
debug?: boolean;
|
|
757
|
+
}
|
|
758
|
+
export declare function SunboardProvider(props: SunboardProviderProps): import("react").JSX.Element;
|
|
308
759
|
//#endregion
|
|
309
|
-
//#region src/
|
|
310
|
-
|
|
311
|
-
|
|
760
|
+
//#region src/thread.d.ts
|
|
761
|
+
/** Why a question has no reply. Each takes the reply's place in the thread. */
|
|
762
|
+
type AskFailure = {
|
|
763
|
+
kind: "lost";
|
|
764
|
+
} | {
|
|
765
|
+
kind: "rateLimited";
|
|
766
|
+
retryAt: string;
|
|
767
|
+
} | {
|
|
768
|
+
kind: "refused";
|
|
769
|
+
message: string;
|
|
770
|
+
};
|
|
771
|
+
/**
|
|
772
|
+
* One entry in the thread. A question is always followed by the one thing it
|
|
773
|
+
* got (`waiting`, `reply` or `error`), which names it in `questionId`. A
|
|
774
|
+
* `tour` line follows the reply whose Show me how started that tour, and
|
|
775
|
+
* keeps the stars the visitor gave it once they finished it.
|
|
776
|
+
*/
|
|
777
|
+
type Message = {
|
|
778
|
+
kind: "question";
|
|
779
|
+
id: string;
|
|
780
|
+
text: string;
|
|
781
|
+
} | {
|
|
782
|
+
kind: "waiting";
|
|
783
|
+
id: string;
|
|
784
|
+
questionId: string;
|
|
785
|
+
stage: AskStage | null;
|
|
786
|
+
since: number;
|
|
787
|
+
} | {
|
|
788
|
+
kind: "reply";
|
|
789
|
+
id: string;
|
|
790
|
+
questionId: string;
|
|
791
|
+
reply: AskReply;
|
|
792
|
+
toured: boolean;
|
|
793
|
+
} | {
|
|
794
|
+
kind: "error";
|
|
795
|
+
id: string;
|
|
796
|
+
questionId: string;
|
|
797
|
+
failure: AskFailure;
|
|
798
|
+
} | {
|
|
799
|
+
kind: "tour";
|
|
800
|
+
id: string;
|
|
801
|
+
guideKey: string;
|
|
312
802
|
title: string;
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
/** True when a tour action is attached; `engage` starts it. */
|
|
318
|
-
hasAction: boolean;
|
|
319
|
-
actionLabel?: string;
|
|
320
|
-
/** True when the user asked for this tooltip via a checklist step. */
|
|
321
|
-
requested: boolean;
|
|
803
|
+
ending: TourEnding;
|
|
804
|
+
step: number;
|
|
805
|
+
total: number;
|
|
806
|
+
rating?: TourRating;
|
|
322
807
|
};
|
|
323
|
-
|
|
808
|
+
//#endregion
|
|
809
|
+
//#region src/hooks.d.ts
|
|
810
|
+
export declare function useGuidesApi(): GuidesApi;
|
|
811
|
+
interface LauncherOptions {
|
|
812
|
+
/**
|
|
813
|
+
* Keep the thread in session storage for the rest of the visit. On by
|
|
814
|
+
* default; nothing is kept for a preview session either way.
|
|
815
|
+
*/
|
|
816
|
+
remember?: boolean;
|
|
817
|
+
}
|
|
818
|
+
interface LauncherList extends LauncherGroups {
|
|
819
|
+
ready: boolean;
|
|
820
|
+
tourRunning: boolean;
|
|
821
|
+
/** New functions on every render, so call them from an event handler. */
|
|
822
|
+
opened: () => void;
|
|
823
|
+
/**
|
|
824
|
+
* Start a listed guide's tour. Pass the id of the reply that offered it,
|
|
825
|
+
* and the thread gets a line after that reply when the tour ends.
|
|
826
|
+
*/
|
|
827
|
+
start: (guideKey: string, replyId?: string) => void;
|
|
828
|
+
/** The thread, oldest first. Each hook instance holds its own. */
|
|
829
|
+
messages: readonly Message[];
|
|
830
|
+
/** A question is out and its reply has not come back yet. */
|
|
831
|
+
waiting: boolean;
|
|
832
|
+
/** When the visitor may ask again, while the newest question was capped. */
|
|
833
|
+
rateLimitedUntil: string | undefined;
|
|
834
|
+
/**
|
|
835
|
+
* A tour this thread started is running. It turns false when that tour
|
|
836
|
+
* ends, the moment a panel would come back.
|
|
837
|
+
*/
|
|
838
|
+
handedOff: boolean;
|
|
324
839
|
/**
|
|
325
|
-
*
|
|
326
|
-
*
|
|
840
|
+
* Ask a question, as a follow-up when the thread has one. Undefined on a
|
|
841
|
+
* transport with no `ask` and while Sunboard is off. Ignored when blank,
|
|
842
|
+
* while waiting, and while capped.
|
|
327
843
|
*/
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
|
|
333
|
-
/** Take the tooltip's action: starts its tour and retires the tooltip. */
|
|
334
|
-
engage: (key: string) => void;
|
|
844
|
+
ask: ((question: string) => void) | undefined;
|
|
845
|
+
/** Ask the newest question again when nothing came back for it. */
|
|
846
|
+
retry: () => void;
|
|
847
|
+
/** Clear the thread, its stored copy and its conversation. */
|
|
848
|
+
startOver: () => void;
|
|
335
849
|
/**
|
|
336
|
-
*
|
|
337
|
-
*
|
|
850
|
+
* Give a finished tour's line its stars and send them as `TOUR_RATED`.
|
|
851
|
+
* Ignored for a stopped tour and for a line already rated.
|
|
338
852
|
*/
|
|
339
|
-
|
|
853
|
+
rate: (lineId: string, rating: TourRating) => void;
|
|
854
|
+
}
|
|
855
|
+
export declare function useLauncher({ remember }?: LauncherOptions): LauncherList;
|
|
856
|
+
/** One guide's content plus launchTour; call with no key for launch-only. */
|
|
857
|
+
export declare function useGuide(key?: string): {
|
|
858
|
+
guide: GuideContent | undefined;
|
|
859
|
+
launchTour: (guideKey: string) => void;
|
|
860
|
+
};
|
|
861
|
+
/**
|
|
862
|
+
* Record a usage event. Public so a customer's own UI built on these hooks
|
|
863
|
+
* reports the same numbers the prebuilt components do.
|
|
864
|
+
*/
|
|
865
|
+
export declare function useTrack(): GuidesApi["track"];
|
|
866
|
+
/** Structure toggles for prebuilt components; custom UIs may ignore them. */
|
|
867
|
+
export declare function useComponentSettings(): ComponentSettings;
|
|
868
|
+
/** Built-in UI strings; override via SunboardProvider. */
|
|
869
|
+
export declare function useLabels(): Labels;
|
|
870
|
+
/** Theme + mode + ready-to-apply CSS variables (--sb-*). */
|
|
871
|
+
export declare function useTheme(): {
|
|
872
|
+
theme: Theme;
|
|
873
|
+
mode: ThemeMode;
|
|
874
|
+
cssVars: Record<string, string>;
|
|
875
|
+
};
|
|
876
|
+
//#endregion
|
|
877
|
+
//#region src/anchors.d.ts
|
|
878
|
+
type TieBreak = "open dialog" | "previous step's box" | "document order";
|
|
879
|
+
type AnchorMatch = {
|
|
880
|
+
element: null;
|
|
881
|
+
matchCount: 0;
|
|
882
|
+
} | {
|
|
883
|
+
/** Any element, so an anchor on an icon (an <svg>) resolves like one on a button. */
|
|
884
|
+
element: Element;
|
|
885
|
+
/** How many separate elements matched; >1 means the anchor is ambiguous. */
|
|
886
|
+
matchCount: number;
|
|
887
|
+
chosenBy: TieBreak;
|
|
340
888
|
};
|
|
889
|
+
interface Preference {
|
|
890
|
+
dialog: boolean;
|
|
891
|
+
near: Element | null;
|
|
892
|
+
}
|
|
341
893
|
/**
|
|
342
|
-
*
|
|
343
|
-
*
|
|
894
|
+
* Resolve an anchor against the live DOM: its boxes first, then the element
|
|
895
|
+
* inside them. When several elements match an anchor with no box, the first
|
|
896
|
+
* rule that keeps any of them picks, in document order.
|
|
344
897
|
*/
|
|
345
|
-
declare function
|
|
898
|
+
export declare function resolveAnchor(anchor: Anchor, root?: ParentNode, prefer?: Preference): AnchorMatch;
|
|
899
|
+
interface AnchorElementState {
|
|
900
|
+
element: Element | null;
|
|
901
|
+
timedOut: boolean;
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Wait for an anchor's element (UIs render late; routes change), and keep
|
|
905
|
+
* watching it: when the host replaces the node, the state moves to the new
|
|
906
|
+
* one. Ported from v1 useTargetElement, extended with the text predicate and
|
|
907
|
+
* an onMiss hook.
|
|
908
|
+
*/
|
|
909
|
+
export declare function useAnchorElement(anchor: Anchor | null, enabled: boolean, options?: {
|
|
910
|
+
timeoutMs?: number;
|
|
911
|
+
onMiss?: (anchor: Anchor, evidence: MissEvidence) => void;
|
|
912
|
+
prefer?: Preference;
|
|
913
|
+
}): AnchorElementState;
|
|
914
|
+
//#endregion
|
|
915
|
+
//#region src/theme.d.ts
|
|
916
|
+
/** The only bridge between theme config and styling. */
|
|
917
|
+
export declare function themeToCssVars(theme: Theme, mode: ThemeMode): Record<string, string>;
|
|
918
|
+
//#endregion
|
|
919
|
+
//#region src/url-params.d.ts
|
|
920
|
+
export declare const LAUNCH_PARAM = "sunboardLaunch";
|
|
921
|
+
//#endregion
|
|
922
|
+
//#region src/routes.d.ts
|
|
923
|
+
/** The host app's current pathname, updating on SPA navigation. */
|
|
924
|
+
export declare function useCurrentPathname(): string;
|
|
925
|
+
//#endregion
|
|
926
|
+
//#region src/politeness.d.ts
|
|
927
|
+
export declare function hasSeen(guideKey: string): boolean;
|
|
928
|
+
export declare function markSeen(guideKey: string): void;
|
|
929
|
+
export declare function cooldownActive(now?: number): boolean;
|
|
930
|
+
/** Any dismissal of a push surface quiets all push surfaces for a while. */
|
|
931
|
+
export declare function recordDismissal(cooldownMs?: number, now?: number): void;
|
|
932
|
+
/**
|
|
933
|
+
* Choose the single push surface allowed right now, or null. `tourRunning`
|
|
934
|
+
* suppresses all push surfaces (one thing on screen at a time).
|
|
935
|
+
*/
|
|
936
|
+
export declare function pickPushGuide(guides: GuideContent[], options?: {
|
|
937
|
+
tourRunning: boolean;
|
|
938
|
+
now?: number;
|
|
939
|
+
}): GuideContent | null;
|
|
940
|
+
//#endregion
|
|
941
|
+
//#region src/components/SunboardOverlay.d.ts
|
|
942
|
+
interface SunboardOverlayProps {
|
|
943
|
+
/** Mount the prebuilt pill. Pass false when your own UI is built on useLauncher. */
|
|
944
|
+
launcher?: boolean;
|
|
945
|
+
}
|
|
946
|
+
export declare function SunboardOverlay({ launcher }?: SunboardOverlayProps): import("react").JSX.Element;
|
|
947
|
+
//#endregion
|
|
948
|
+
//#region src/components/Launcher.d.ts
|
|
949
|
+
interface LauncherProps {
|
|
950
|
+
/**
|
|
951
|
+
* Render the panel open and in normal flow, never hidden: the appearance
|
|
952
|
+
* canvas in the Sunboard app. Nothing is kept in storage.
|
|
953
|
+
*/
|
|
954
|
+
inline?: boolean;
|
|
955
|
+
}
|
|
956
|
+
export declare function Launcher({ inline }?: LauncherProps): import("react").JSX.Element | null;
|
|
346
957
|
//#endregion
|
|
347
|
-
export {
|
|
348
|
-
//# sourceMappingURL=index.d.mts.map
|
|
958
|
+
export type { ActiveTour, Anchor, AnchorElementState, AnchorMatch, AskFailure, AskOutcome, AskQuestion, AskReply, AskResult, AskScript, AskStage, BootstrapAnswer, BootstrapData, BootstrapOff, ComponentSettings, GuideContent, GuideStep, GuideType, GuidesApi, GuidesTransport, HttpTransportConfig, LabelOverrides, Labels, LauncherList, LauncherOptions, LauncherProps, MemoryTransportOptions, Message, Palette, PinPosition, ScriptedReply, StepPlacement, StepPrimitive, SunboardOverlayProps, SunboardProviderProps, Theme, ThemeMode, ThemeModeSetting, TourEnd, TourEnding, UserState, VisitorDetails };
|