@numidev/react-joyride 1.0.1
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 +9 -0
- package/README.md +93 -0
- package/dist/index.cjs +2677 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +753 -0
- package/dist/index.d.mts +753 -0
- package/dist/index.mjs +2638 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +158 -0
- package/src/components/Arrow.tsx +138 -0
- package/src/components/Beacon.tsx +141 -0
- package/src/components/Floater.tsx +381 -0
- package/src/components/Loader.tsx +80 -0
- package/src/components/Overlay.tsx +167 -0
- package/src/components/Portal.tsx +17 -0
- package/src/components/Step.tsx +72 -0
- package/src/components/Tooltip/CloseButton.tsx +29 -0
- package/src/components/Tooltip/DefaultTooltip.tsx +82 -0
- package/src/components/Tooltip/index.tsx +163 -0
- package/src/components/TourRenderer.tsx +157 -0
- package/src/defaults.ts +64 -0
- package/src/global.d.ts +8 -0
- package/src/hooks/useControls.ts +219 -0
- package/src/hooks/useDebugLogger.ts +58 -0
- package/src/hooks/useEventEmitter.ts +55 -0
- package/src/hooks/useFocusTrap.ts +72 -0
- package/src/hooks/useJoyride.tsx +32 -0
- package/src/hooks/useLifecycleEffect.ts +512 -0
- package/src/hooks/usePortalElement.ts +49 -0
- package/src/hooks/usePropSync.ts +84 -0
- package/src/hooks/useScrollEffect.ts +217 -0
- package/src/hooks/useTargetPosition.ts +154 -0
- package/src/hooks/useTourEngine.ts +106 -0
- package/src/index.tsx +23 -0
- package/src/literals/index.ts +61 -0
- package/src/modules/changes.ts +20 -0
- package/src/modules/dom.ts +359 -0
- package/src/modules/helpers.tsx +230 -0
- package/src/modules/step.ts +156 -0
- package/src/modules/store.ts +215 -0
- package/src/modules/svg.ts +40 -0
- package/src/styles.ts +183 -0
- package/src/types/common.ts +315 -0
- package/src/types/components.ts +84 -0
- package/src/types/events.ts +89 -0
- package/src/types/floating.ts +60 -0
- package/src/types/index.ts +8 -0
- package/src/types/props.ts +124 -0
- package/src/types/state.ts +49 -0
- package/src/types/step.ts +108 -0
- package/src/types/utilities.ts +26 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2638 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import React, { cloneElement, isValidElement, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { useMemoDeepCompare, useMount, usePrevious, useUpdateEffect, useWindowSize } from "@gilbarbara/hooks";
|
|
4
|
+
import { useSyncExternalStore } from "use-sync-external-store/shim";
|
|
5
|
+
import is from "is-lite";
|
|
6
|
+
import innerText from "react-innertext";
|
|
7
|
+
import deepmergeFactory from "@fastify/deepmerge";
|
|
8
|
+
import deepEqual from "@gilbarbara/deep-equal";
|
|
9
|
+
import scroll from "scroll";
|
|
10
|
+
import scrollParent from "scrollparent";
|
|
11
|
+
import { createPortal } from "react-dom";
|
|
12
|
+
import { arrow, autoPlacement, autoUpdate, flip, offset, shift, useFloating } from "@floating-ui/react-dom";
|
|
13
|
+
//#region src/defaults.ts
|
|
14
|
+
const defaultOptions = {
|
|
15
|
+
arrowBase: 32,
|
|
16
|
+
arrowColor: "#ffffff",
|
|
17
|
+
arrowSize: 16,
|
|
18
|
+
arrowSpacing: 12,
|
|
19
|
+
backgroundColor: "#ffffff",
|
|
20
|
+
beaconSize: 36,
|
|
21
|
+
beaconTrigger: "click",
|
|
22
|
+
beforeTimeout: 5e3,
|
|
23
|
+
blockTargetInteraction: false,
|
|
24
|
+
buttons: [
|
|
25
|
+
"back",
|
|
26
|
+
"close",
|
|
27
|
+
"primary"
|
|
28
|
+
],
|
|
29
|
+
closeButtonAction: "close",
|
|
30
|
+
disableFocusTrap: false,
|
|
31
|
+
dismissKeyAction: "close",
|
|
32
|
+
hideOverlay: false,
|
|
33
|
+
loaderDelay: 300,
|
|
34
|
+
offset: 10,
|
|
35
|
+
overlayClickAction: "close",
|
|
36
|
+
overlayColor: "#00000080",
|
|
37
|
+
primaryColor: "#000000",
|
|
38
|
+
scrollDuration: 300,
|
|
39
|
+
scrollOffset: 20,
|
|
40
|
+
showProgress: false,
|
|
41
|
+
skipBeacon: false,
|
|
42
|
+
skipScroll: false,
|
|
43
|
+
spotlightPadding: 10,
|
|
44
|
+
spotlightRadius: 4,
|
|
45
|
+
targetWaitTimeout: 1e3,
|
|
46
|
+
textColor: "#000000",
|
|
47
|
+
width: 380,
|
|
48
|
+
zIndex: 100
|
|
49
|
+
};
|
|
50
|
+
const defaultFloatingOptions = { beaconOptions: { offset: -18 } };
|
|
51
|
+
const defaultLocale = {
|
|
52
|
+
back: "Back",
|
|
53
|
+
close: "Close",
|
|
54
|
+
last: "Last",
|
|
55
|
+
next: "Next",
|
|
56
|
+
nextWithProgress: "Next ({current} of {total})",
|
|
57
|
+
open: "Open the dialog",
|
|
58
|
+
skip: "Skip"
|
|
59
|
+
};
|
|
60
|
+
const defaultStep = {
|
|
61
|
+
isFixed: false,
|
|
62
|
+
locale: defaultLocale,
|
|
63
|
+
placement: "bottom"
|
|
64
|
+
};
|
|
65
|
+
const defaultProps = {
|
|
66
|
+
continuous: false,
|
|
67
|
+
debug: false,
|
|
68
|
+
run: false,
|
|
69
|
+
scrollToFirstStep: false,
|
|
70
|
+
steps: []
|
|
71
|
+
};
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/literals/index.ts
|
|
74
|
+
const ACTIONS = {
|
|
75
|
+
INIT: "init",
|
|
76
|
+
START: "start",
|
|
77
|
+
STOP: "stop",
|
|
78
|
+
RESET: "reset",
|
|
79
|
+
PREV: "prev",
|
|
80
|
+
NEXT: "next",
|
|
81
|
+
GO: "go",
|
|
82
|
+
CLOSE: "close",
|
|
83
|
+
SKIP: "skip",
|
|
84
|
+
REPLAY: "replay",
|
|
85
|
+
UPDATE: "update",
|
|
86
|
+
COMPLETE: "complete"
|
|
87
|
+
};
|
|
88
|
+
const EVENTS = {
|
|
89
|
+
TOUR_START: "tour:start",
|
|
90
|
+
STEP_BEFORE_HOOK: "step:before_hook",
|
|
91
|
+
STEP_BEFORE: "step:before",
|
|
92
|
+
SCROLL_START: "scroll:start",
|
|
93
|
+
SCROLL_END: "scroll:end",
|
|
94
|
+
BEACON: "beacon",
|
|
95
|
+
TOOLTIP: "tooltip",
|
|
96
|
+
STEP_AFTER: "step:after",
|
|
97
|
+
STEP_AFTER_HOOK: "step:after_hook",
|
|
98
|
+
TOUR_END: "tour:end",
|
|
99
|
+
TOUR_STATUS: "tour:status",
|
|
100
|
+
TARGET_NOT_FOUND: "error:target_not_found",
|
|
101
|
+
ERROR: "error"
|
|
102
|
+
};
|
|
103
|
+
const LIFECYCLE = {
|
|
104
|
+
INIT: "init",
|
|
105
|
+
READY: "ready",
|
|
106
|
+
BEACON_BEFORE: "beacon_before",
|
|
107
|
+
BEACON: "beacon",
|
|
108
|
+
TOOLTIP_BEFORE: "tooltip_before",
|
|
109
|
+
TOOLTIP: "tooltip",
|
|
110
|
+
COMPLETE: "complete"
|
|
111
|
+
};
|
|
112
|
+
const ORIGIN = {
|
|
113
|
+
BUTTON_BACK: "button_back",
|
|
114
|
+
BUTTON_CLOSE: "button_close",
|
|
115
|
+
BUTTON_PRIMARY: "button_primary",
|
|
116
|
+
BUTTON_SKIP: "button_skip",
|
|
117
|
+
KEYBOARD: "keyboard",
|
|
118
|
+
OVERLAY: "overlay"
|
|
119
|
+
};
|
|
120
|
+
const STATUS = {
|
|
121
|
+
IDLE: "idle",
|
|
122
|
+
READY: "ready",
|
|
123
|
+
WAITING: "waiting",
|
|
124
|
+
RUNNING: "running",
|
|
125
|
+
PAUSED: "paused",
|
|
126
|
+
SKIPPED: "skipped",
|
|
127
|
+
FINISHED: "finished"
|
|
128
|
+
};
|
|
129
|
+
const PORTAL_ELEMENT_ID = "react-joyride-portal";
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region src/modules/helpers.tsx
|
|
132
|
+
/**
|
|
133
|
+
* Remove properties with undefined value from an object
|
|
134
|
+
*/
|
|
135
|
+
function cleanUpObject(input) {
|
|
136
|
+
const output = {};
|
|
137
|
+
for (const key in input) if (input[key] !== void 0) output[key] = input[key];
|
|
138
|
+
return output;
|
|
139
|
+
}
|
|
140
|
+
function deepMerge(...objects) {
|
|
141
|
+
return deepmergeFactory({
|
|
142
|
+
all: true,
|
|
143
|
+
isMergeableObject: (value) => !(!is.plainObject(value) || isValidElement(value))
|
|
144
|
+
})(...objects);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Get Object type
|
|
148
|
+
*/
|
|
149
|
+
function getObjectType(value) {
|
|
150
|
+
return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
|
|
151
|
+
}
|
|
152
|
+
function getReactNodeText(input, options = {}) {
|
|
153
|
+
const { defaultValue, step, steps } = options;
|
|
154
|
+
let text = innerText(input);
|
|
155
|
+
if (!text) if (isValidElement(input) && !Object.values(input.props).length && getObjectType(input.type) === "function") try {
|
|
156
|
+
text = getReactNodeText(input.type({}), options);
|
|
157
|
+
} catch {
|
|
158
|
+
text = innerText(defaultValue);
|
|
159
|
+
}
|
|
160
|
+
else text = innerText(defaultValue);
|
|
161
|
+
else if ((text.includes("{current}") || text.includes("{total}")) && step && steps) text = text.replace("{current}", step.toString()).replace("{total}", steps.toString());
|
|
162
|
+
return text;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Log method calls if debug is enabled
|
|
166
|
+
*/
|
|
167
|
+
function log(debug, scope, title, ...data) {
|
|
168
|
+
if (!debug) return;
|
|
169
|
+
const now = /* @__PURE__ */ new Date();
|
|
170
|
+
const time = `${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}:${String(now.getSeconds()).padStart(2, "0")}.${String(now.getMilliseconds()).padStart(3, "0")}`;
|
|
171
|
+
console.log(`${scope} %c${title}%c ${time}`, "font-weight: bold", "color: gray; font-weight: normal", ...data);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Merges the defaultProps with literal values with the incoming props, removing undefined values from it that would override the defaultProps.
|
|
175
|
+
* The result is a type-safe object with the defaultProps as required properties.
|
|
176
|
+
*/
|
|
177
|
+
function mergeProps(defaultProps, props) {
|
|
178
|
+
const cleanProps = cleanUpObject(props);
|
|
179
|
+
return {
|
|
180
|
+
...defaultProps,
|
|
181
|
+
...cleanProps
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* A function that does nothing.
|
|
186
|
+
*/
|
|
187
|
+
function noop() {}
|
|
188
|
+
/**
|
|
189
|
+
* Type-safe Object.keys()
|
|
190
|
+
*/
|
|
191
|
+
function objectKeys(input) {
|
|
192
|
+
return Object.keys(input);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Remove properties from an object
|
|
196
|
+
*/
|
|
197
|
+
function omit(input, ...filter) {
|
|
198
|
+
if (!is.plainObject(input)) throw new TypeError("Expected an object");
|
|
199
|
+
const output = {};
|
|
200
|
+
for (const key in input)
|
|
201
|
+
/* istanbul ignore else */
|
|
202
|
+
if ({}.hasOwnProperty.call(input, key) && !filter.includes(key)) output[key] = input[key];
|
|
203
|
+
return output;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Select properties from an object
|
|
207
|
+
*/
|
|
208
|
+
function pick(input, ...filter) {
|
|
209
|
+
if (!is.plainObject(input)) throw new TypeError("Expected an object");
|
|
210
|
+
if (!filter.length) return input;
|
|
211
|
+
const output = {};
|
|
212
|
+
for (const key in input)
|
|
213
|
+
/* istanbul ignore else */
|
|
214
|
+
if ({}.hasOwnProperty.call(input, key) && filter.includes(key)) output[key] = input[key];
|
|
215
|
+
return output;
|
|
216
|
+
}
|
|
217
|
+
function replaceLocaleContent(input, step, steps) {
|
|
218
|
+
const replacer = (text) => text.replace("{current}", String(step)).replace("{total}", String(steps));
|
|
219
|
+
if (getObjectType(input) === "string") return replacer(input);
|
|
220
|
+
if (!isValidElement(input)) return input;
|
|
221
|
+
const { children } = input.props;
|
|
222
|
+
if (is.string(children) && children.includes("{current}")) return cloneElement(input, { children: replacer(children) });
|
|
223
|
+
if (Array.isArray(children)) return cloneElement(input, { children: children.map((child) => {
|
|
224
|
+
if (typeof child === "string") return replacer(child);
|
|
225
|
+
return replaceLocaleContent(child, step, steps);
|
|
226
|
+
}) });
|
|
227
|
+
if (is.function(input.type) && !Object.values(input.props).length) try {
|
|
228
|
+
return replaceLocaleContent(input.type({}), step, steps);
|
|
229
|
+
} catch {
|
|
230
|
+
return input;
|
|
231
|
+
}
|
|
232
|
+
return input;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Sort object keys
|
|
236
|
+
*/
|
|
237
|
+
function sortObjectKeys(input) {
|
|
238
|
+
return objectKeys(input).sort().reduce((acc, key) => {
|
|
239
|
+
acc[key] = input[key];
|
|
240
|
+
return acc;
|
|
241
|
+
}, {});
|
|
242
|
+
}
|
|
243
|
+
//#endregion
|
|
244
|
+
//#region src/modules/dom.ts
|
|
245
|
+
function canUseDOM() {
|
|
246
|
+
return !!(typeof window !== "undefined" && window.document?.createElement);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Get the absolute document-relative offset of an element by walking up the offsetParent chain.
|
|
250
|
+
*/
|
|
251
|
+
function getAbsoluteOffset(element) {
|
|
252
|
+
let top = 0;
|
|
253
|
+
let left = 0;
|
|
254
|
+
let current = element;
|
|
255
|
+
while (current) {
|
|
256
|
+
top += current.offsetTop;
|
|
257
|
+
left += current.offsetLeft;
|
|
258
|
+
current = current.offsetParent;
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
left,
|
|
262
|
+
top
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Find the bounding client rect
|
|
267
|
+
*/
|
|
268
|
+
function getClientRect(element) {
|
|
269
|
+
if (!element) return null;
|
|
270
|
+
return element.getBoundingClientRect();
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Helper function to get the browser-normalized "document height"
|
|
274
|
+
*/
|
|
275
|
+
function getDocumentHeight(median = false) {
|
|
276
|
+
const { body, documentElement } = document;
|
|
277
|
+
if (!body || !documentElement) return 0;
|
|
278
|
+
if (median) {
|
|
279
|
+
const heights = [
|
|
280
|
+
body.scrollHeight,
|
|
281
|
+
body.offsetHeight,
|
|
282
|
+
documentElement.clientHeight,
|
|
283
|
+
documentElement.scrollHeight,
|
|
284
|
+
documentElement.offsetHeight
|
|
285
|
+
].sort((a, b) => a - b);
|
|
286
|
+
const middle = Math.floor(heights.length / 2);
|
|
287
|
+
if (heights.length % 2 === 0) return (heights[middle - 1] + heights[middle]) / 2;
|
|
288
|
+
return heights[middle];
|
|
289
|
+
}
|
|
290
|
+
return Math.max(body.scrollHeight, body.offsetHeight, documentElement.clientHeight, documentElement.scrollHeight, documentElement.offsetHeight);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Find and return the target DOM element based on a step's 'target'.
|
|
294
|
+
*/
|
|
295
|
+
function getElement(element) {
|
|
296
|
+
if (!element) return null;
|
|
297
|
+
if (typeof element === "function") try {
|
|
298
|
+
return element();
|
|
299
|
+
} catch (error) {
|
|
300
|
+
if (process.env.NODE_ENV !== "production") console.error(error);
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
if (typeof element === "object" && "current" in element) return element.current;
|
|
304
|
+
if (typeof element === "string") try {
|
|
305
|
+
return document.querySelector(element);
|
|
306
|
+
} catch (error) {
|
|
307
|
+
if (process.env.NODE_ENV !== "production") console.error(error);
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
return element;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Find and return the target DOM element based on a step's 'target'.
|
|
314
|
+
*/
|
|
315
|
+
function getElementPosition(element, offset, isFixed) {
|
|
316
|
+
const elementRect = getClientRect(element);
|
|
317
|
+
const parent = getScrollParent(element);
|
|
318
|
+
const hasScrollParent = parent ? !parent.isSameNode(scrollDocument()) : false;
|
|
319
|
+
const isFixedTarget = isFixed ?? hasPosition(element);
|
|
320
|
+
let parentTop = 0;
|
|
321
|
+
let top = elementRect?.top ?? 0;
|
|
322
|
+
if (hasScrollParent && isFixedTarget) top = elementRect?.top ?? 0;
|
|
323
|
+
else if (parent instanceof HTMLElement) {
|
|
324
|
+
parentTop = parent.scrollTop;
|
|
325
|
+
if (!hasScrollParent && !isFixedTarget) top += parentTop;
|
|
326
|
+
if (!parent.isSameNode(scrollDocument())) top += scrollDocument().scrollTop;
|
|
327
|
+
}
|
|
328
|
+
return Math.floor(top - offset);
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Get the scroll parent of an element.
|
|
332
|
+
* If the detected parent doesn't actually scroll, fall back to the document.
|
|
333
|
+
*/
|
|
334
|
+
function getScrollParent(element, forListener) {
|
|
335
|
+
if (!element) return scrollDocument();
|
|
336
|
+
const parent = scrollParent(element);
|
|
337
|
+
if (parent) {
|
|
338
|
+
if (parent.isSameNode(scrollDocument())) {
|
|
339
|
+
if (forListener) return document;
|
|
340
|
+
return scrollDocument();
|
|
341
|
+
}
|
|
342
|
+
if (!(parent.scrollHeight > parent.offsetHeight)) return scrollDocument();
|
|
343
|
+
}
|
|
344
|
+
return parent;
|
|
345
|
+
}
|
|
346
|
+
function getScrollTargetToCenter(element) {
|
|
347
|
+
const rect = element.getBoundingClientRect();
|
|
348
|
+
const documentElement = scrollDocument();
|
|
349
|
+
const containerCenter = rect.top + rect.height / 2;
|
|
350
|
+
const viewportCenter = window.innerHeight / 2;
|
|
351
|
+
return Math.max(0, documentElement.scrollTop + containerCenter - viewportCenter);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Get the scrollTop position
|
|
355
|
+
*/
|
|
356
|
+
function getScrollTo(element, offset) {
|
|
357
|
+
if (!element) return 0;
|
|
358
|
+
const parentElement = scrollParent(element) ?? scrollDocument();
|
|
359
|
+
const scrollMarginTop = parseFloat(getComputedStyle(element).scrollMarginTop) || 0;
|
|
360
|
+
const parentRect = getClientRect(parentElement);
|
|
361
|
+
const parentScrollTop = parentElement.scrollTop ?? 0;
|
|
362
|
+
const { offsetTop = 0, scrollTop = 0 } = parentElement;
|
|
363
|
+
let top = element.getBoundingClientRect().top + scrollTop;
|
|
364
|
+
if (!!offsetTop && (hasCustomScrollParent(element) || hasCustomOffsetParent(element))) {
|
|
365
|
+
const elementRect = element.getBoundingClientRect();
|
|
366
|
+
const elementTopInContainer = elementRect.top - (parentRect?.top ?? 0);
|
|
367
|
+
const elementBottomInContainer = elementTopInContainer + elementRect.height;
|
|
368
|
+
const containerHeight = parentElement.clientHeight;
|
|
369
|
+
const margin = containerHeight * .2;
|
|
370
|
+
if (elementTopInContainer >= margin && elementBottomInContainer <= containerHeight - margin) top = parentScrollTop;
|
|
371
|
+
else top = elementTopInContainer + parentScrollTop;
|
|
372
|
+
}
|
|
373
|
+
const output = Math.floor(top - offset - scrollMarginTop);
|
|
374
|
+
return output < 0 ? 0 : output;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Check if the element has custom offset parent
|
|
378
|
+
*/
|
|
379
|
+
function hasCustomOffsetParent(element) {
|
|
380
|
+
return element.offsetParent !== document.body;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Check if the element has custom scroll parent
|
|
384
|
+
*/
|
|
385
|
+
function hasCustomScrollParent(element) {
|
|
386
|
+
if (!element) return false;
|
|
387
|
+
const parent = getScrollParent(element);
|
|
388
|
+
return parent ? !parent.isSameNode(scrollDocument()) : false;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Check if an element has fixed/sticky position
|
|
392
|
+
*/
|
|
393
|
+
function hasPosition(el, type = "fixed") {
|
|
394
|
+
if (!el || !(el instanceof Element)) return false;
|
|
395
|
+
const { nodeName } = el;
|
|
396
|
+
if (nodeName === "BODY" || nodeName === "HTML") return false;
|
|
397
|
+
if (getComputedStyle(el).position === type) return true;
|
|
398
|
+
if (!el.parentNode) return false;
|
|
399
|
+
return hasPosition(el.parentNode, type);
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Check if the element is visible
|
|
403
|
+
*/
|
|
404
|
+
function isElementVisible(element) {
|
|
405
|
+
if (!element) return false;
|
|
406
|
+
let parentElement = element;
|
|
407
|
+
while (parentElement) {
|
|
408
|
+
if (parentElement === document.body) break;
|
|
409
|
+
if (parentElement instanceof HTMLElement) {
|
|
410
|
+
const { display, visibility } = getComputedStyle(parentElement);
|
|
411
|
+
if (display === "none" || visibility === "hidden") return false;
|
|
412
|
+
}
|
|
413
|
+
parentElement = parentElement.parentElement ?? null;
|
|
414
|
+
}
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
function needsScrolling(options) {
|
|
418
|
+
const { isFirstStep, scrollToFirstStep, step, target, targetLifecycle } = options;
|
|
419
|
+
if (step.skipScroll || isFirstStep && !scrollToFirstStep && targetLifecycle !== LIFECYCLE.TOOLTIP || step.placement === "center") return false;
|
|
420
|
+
const parent = target?.isConnected ? getScrollParent(target) : scrollDocument();
|
|
421
|
+
const isCustomScrollParent = parent ? !parent.isSameNode(scrollDocument()) : false;
|
|
422
|
+
if ((step.isFixed || hasPosition(target)) && !isCustomScrollParent) return false;
|
|
423
|
+
return parent.scrollHeight > parent.clientHeight;
|
|
424
|
+
}
|
|
425
|
+
function scrollDocument() {
|
|
426
|
+
return document.scrollingElement ?? document.documentElement;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Scroll to position
|
|
430
|
+
*/
|
|
431
|
+
function scrollTo(value, options) {
|
|
432
|
+
const { duration, element } = options;
|
|
433
|
+
let cancel = () => {};
|
|
434
|
+
const promise = new Promise((resolve) => {
|
|
435
|
+
const { scrollTop } = element;
|
|
436
|
+
const limit = value > scrollTop ? value - scrollTop : scrollTop - value;
|
|
437
|
+
cancel = scroll.top(element, value, { duration: limit < 100 ? 50 : duration }, () => {
|
|
438
|
+
resolve();
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
return {
|
|
442
|
+
cancel,
|
|
443
|
+
promise
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
//#endregion
|
|
447
|
+
//#region src/styles.ts
|
|
448
|
+
/**
|
|
449
|
+
* Convert hex to RGB
|
|
450
|
+
*/
|
|
451
|
+
function hexToRGB(hex) {
|
|
452
|
+
const properHex = hex.replace(/^#?([\da-f])([\da-f])([\da-f])$/i, (_m, r, g, b) => r + r + g + g + b + b);
|
|
453
|
+
const result = /^#?([\da-f]{2})([\da-f]{2})([\da-f]{2})/i.exec(properHex);
|
|
454
|
+
return result ? [
|
|
455
|
+
parseInt(result[1], 16),
|
|
456
|
+
parseInt(result[2], 16),
|
|
457
|
+
parseInt(result[3], 16)
|
|
458
|
+
] : [];
|
|
459
|
+
}
|
|
460
|
+
const buttonReset = {
|
|
461
|
+
backgroundColor: "transparent",
|
|
462
|
+
border: 0,
|
|
463
|
+
borderRadius: 0,
|
|
464
|
+
color: "#555555",
|
|
465
|
+
cursor: "pointer",
|
|
466
|
+
fontSize: 16,
|
|
467
|
+
lineHeight: 1,
|
|
468
|
+
padding: 0,
|
|
469
|
+
WebkitAppearance: "none"
|
|
470
|
+
};
|
|
471
|
+
const buttonBase = {
|
|
472
|
+
...buttonReset,
|
|
473
|
+
borderRadius: 4,
|
|
474
|
+
padding: 8
|
|
475
|
+
};
|
|
476
|
+
function getStyles(props, step) {
|
|
477
|
+
const { styles } = props;
|
|
478
|
+
const mergedStyles = deepMerge(styles ?? {}, step.styles ?? {});
|
|
479
|
+
let { width } = step;
|
|
480
|
+
if (canUseDOM()) width = typeof width === "number" && window.innerWidth < width ? window.innerWidth - 30 : width;
|
|
481
|
+
const overlay = {
|
|
482
|
+
bottom: 0,
|
|
483
|
+
left: 0,
|
|
484
|
+
overflow: "hidden",
|
|
485
|
+
position: "absolute",
|
|
486
|
+
right: 0,
|
|
487
|
+
top: 0,
|
|
488
|
+
zIndex: step.zIndex
|
|
489
|
+
};
|
|
490
|
+
return deepMerge({
|
|
491
|
+
arrow: {
|
|
492
|
+
alignItems: "center",
|
|
493
|
+
color: step.arrowColor,
|
|
494
|
+
display: "inline-flex",
|
|
495
|
+
justifyContent: "center",
|
|
496
|
+
position: "absolute"
|
|
497
|
+
},
|
|
498
|
+
beaconWrapper: {
|
|
499
|
+
...buttonReset,
|
|
500
|
+
display: "inline-flex",
|
|
501
|
+
borderRadius: "50%",
|
|
502
|
+
position: "relative"
|
|
503
|
+
},
|
|
504
|
+
beacon: {
|
|
505
|
+
height: step.beaconSize,
|
|
506
|
+
width: step.beaconSize
|
|
507
|
+
},
|
|
508
|
+
beaconInner: {
|
|
509
|
+
animation: "joyride-beacon-inner 1.2s infinite ease-in-out",
|
|
510
|
+
backgroundColor: step.primaryColor,
|
|
511
|
+
borderRadius: "50%",
|
|
512
|
+
display: "block",
|
|
513
|
+
height: "50%",
|
|
514
|
+
left: "50%",
|
|
515
|
+
opacity: .7,
|
|
516
|
+
position: "absolute",
|
|
517
|
+
top: "50%",
|
|
518
|
+
transform: "translate(-50%, -50%)",
|
|
519
|
+
width: "50%"
|
|
520
|
+
},
|
|
521
|
+
beaconOuter: {
|
|
522
|
+
animation: "joyride-beacon-outer 1.2s infinite ease-in-out",
|
|
523
|
+
backgroundColor: `rgba(${hexToRGB(step.primaryColor).join(",")}, 0.2)`,
|
|
524
|
+
border: `2px solid ${step.primaryColor}`,
|
|
525
|
+
borderRadius: "50%",
|
|
526
|
+
boxSizing: "border-box",
|
|
527
|
+
display: "block",
|
|
528
|
+
height: "100%",
|
|
529
|
+
left: 0,
|
|
530
|
+
opacity: .9,
|
|
531
|
+
position: "absolute",
|
|
532
|
+
top: 0,
|
|
533
|
+
transformOrigin: "center",
|
|
534
|
+
width: "100%"
|
|
535
|
+
},
|
|
536
|
+
buttonBack: {
|
|
537
|
+
...buttonBase,
|
|
538
|
+
color: step.primaryColor,
|
|
539
|
+
marginLeft: "auto",
|
|
540
|
+
marginRight: 5
|
|
541
|
+
},
|
|
542
|
+
buttonClose: {
|
|
543
|
+
...buttonBase,
|
|
544
|
+
color: step.textColor,
|
|
545
|
+
height: 12,
|
|
546
|
+
padding: 8,
|
|
547
|
+
position: "absolute",
|
|
548
|
+
right: 0,
|
|
549
|
+
top: 0,
|
|
550
|
+
width: 12
|
|
551
|
+
},
|
|
552
|
+
buttonPrimary: {
|
|
553
|
+
...buttonBase,
|
|
554
|
+
backgroundColor: step.primaryColor,
|
|
555
|
+
color: step.backgroundColor
|
|
556
|
+
},
|
|
557
|
+
buttonSkip: {
|
|
558
|
+
...buttonBase,
|
|
559
|
+
color: step.textColor,
|
|
560
|
+
fontSize: 14
|
|
561
|
+
},
|
|
562
|
+
floater: {
|
|
563
|
+
display: "inline-block",
|
|
564
|
+
filter: "drop-shadow(0 0 3px rgba(0, 0, 0, 0.3))",
|
|
565
|
+
maxWidth: "100%",
|
|
566
|
+
transition: "opacity 0.3s"
|
|
567
|
+
},
|
|
568
|
+
loader: {
|
|
569
|
+
alignItems: "center",
|
|
570
|
+
display: "flex",
|
|
571
|
+
height: 48,
|
|
572
|
+
inset: 0,
|
|
573
|
+
justifyContent: "center",
|
|
574
|
+
pointerEvents: "none",
|
|
575
|
+
position: "fixed",
|
|
576
|
+
width: 48,
|
|
577
|
+
zIndex: step.zIndex + 1
|
|
578
|
+
},
|
|
579
|
+
overlay: {
|
|
580
|
+
...overlay,
|
|
581
|
+
backgroundColor: step.overlayColor
|
|
582
|
+
},
|
|
583
|
+
spotlight: {},
|
|
584
|
+
tooltip: {
|
|
585
|
+
backgroundColor: step.backgroundColor,
|
|
586
|
+
borderRadius: 5,
|
|
587
|
+
boxSizing: "border-box",
|
|
588
|
+
color: step.textColor,
|
|
589
|
+
fontSize: 16,
|
|
590
|
+
maxWidth: "100%",
|
|
591
|
+
padding: 12,
|
|
592
|
+
position: "relative",
|
|
593
|
+
width
|
|
594
|
+
},
|
|
595
|
+
tooltipContainer: {
|
|
596
|
+
lineHeight: 1.4,
|
|
597
|
+
textAlign: "center"
|
|
598
|
+
},
|
|
599
|
+
tooltipTitle: {
|
|
600
|
+
fontSize: 18,
|
|
601
|
+
margin: 0
|
|
602
|
+
},
|
|
603
|
+
tooltipContent: {
|
|
604
|
+
paddingBottom: 12,
|
|
605
|
+
paddingTop: 12
|
|
606
|
+
},
|
|
607
|
+
tooltipFooter: {
|
|
608
|
+
alignItems: "center",
|
|
609
|
+
display: "flex",
|
|
610
|
+
justifyContent: "flex-end"
|
|
611
|
+
},
|
|
612
|
+
tooltipFooterSpacer: { flex: 1 }
|
|
613
|
+
}, mergedStyles);
|
|
614
|
+
}
|
|
615
|
+
//#endregion
|
|
616
|
+
//#region src/modules/step.ts
|
|
617
|
+
const optionFieldNames = [
|
|
618
|
+
"after",
|
|
619
|
+
"arrowBase",
|
|
620
|
+
"arrowColor",
|
|
621
|
+
"arrowSize",
|
|
622
|
+
"arrowSpacing",
|
|
623
|
+
"backgroundColor",
|
|
624
|
+
"beaconSize",
|
|
625
|
+
"beaconTrigger",
|
|
626
|
+
"before",
|
|
627
|
+
"beforeTimeout",
|
|
628
|
+
"buttons",
|
|
629
|
+
"closeButtonAction",
|
|
630
|
+
"skipBeacon",
|
|
631
|
+
"dismissKeyAction",
|
|
632
|
+
"disableFocusTrap",
|
|
633
|
+
"hideOverlay",
|
|
634
|
+
"skipScroll",
|
|
635
|
+
"blockTargetInteraction",
|
|
636
|
+
"loaderDelay",
|
|
637
|
+
"offset",
|
|
638
|
+
"overlayClickAction",
|
|
639
|
+
"overlayColor",
|
|
640
|
+
"primaryColor",
|
|
641
|
+
"scrollDuration",
|
|
642
|
+
"scrollOffset",
|
|
643
|
+
"showProgress",
|
|
644
|
+
"spotlightPadding",
|
|
645
|
+
"spotlightRadius",
|
|
646
|
+
"targetWaitTimeout",
|
|
647
|
+
"textColor",
|
|
648
|
+
"width",
|
|
649
|
+
"zIndex"
|
|
650
|
+
];
|
|
651
|
+
function getMergedStep(props, currentStep) {
|
|
652
|
+
if (!currentStep) return null;
|
|
653
|
+
const mergedStep = deepMerge(defaultStep, pick(props, "arrowComponent", "beaconComponent", "floatingOptions", "loaderComponent", "locale", "styles", "tooltipComponent"), currentStep);
|
|
654
|
+
const mergedOptions = deepMerge(defaultOptions, props.options ?? {}, pick(currentStep, ...optionFieldNames));
|
|
655
|
+
const mergedStyles = getStyles(props, {
|
|
656
|
+
...mergedStep,
|
|
657
|
+
...mergedOptions
|
|
658
|
+
});
|
|
659
|
+
const floatingOptions = deepMerge(defaultFloatingOptions, props.floatingOptions ?? {}, mergedStep.floatingOptions ?? {});
|
|
660
|
+
return {
|
|
661
|
+
...mergedStep,
|
|
662
|
+
...mergedOptions,
|
|
663
|
+
locale: deepMerge(defaultLocale, props.locale ?? {}, mergedStep.locale || {}),
|
|
664
|
+
floatingOptions,
|
|
665
|
+
spotlightPadding: normalizeSpotlightPadding(mergedOptions.spotlightPadding),
|
|
666
|
+
styles: mergedStyles
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
function normalizeSpotlightPadding(value) {
|
|
670
|
+
if (typeof value === "number") return {
|
|
671
|
+
top: value,
|
|
672
|
+
right: value,
|
|
673
|
+
bottom: value,
|
|
674
|
+
left: value
|
|
675
|
+
};
|
|
676
|
+
return {
|
|
677
|
+
top: value?.top ?? 0,
|
|
678
|
+
right: value?.right ?? 0,
|
|
679
|
+
bottom: value?.bottom ?? 0,
|
|
680
|
+
left: value?.left ?? 0
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Decide if the step shouldn't skip the beacon
|
|
685
|
+
*/
|
|
686
|
+
function shouldHideBeacon(step, state, continuous) {
|
|
687
|
+
const { action } = state;
|
|
688
|
+
const withContinuous = continuous && [ACTIONS.PREV, ACTIONS.NEXT].includes(action);
|
|
689
|
+
return step.skipBeacon || step.placement === "center" || withContinuous;
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Validate if a step is valid
|
|
693
|
+
*/
|
|
694
|
+
function validateStep(step, debug = false) {
|
|
695
|
+
if (!is.plainObject(step)) {
|
|
696
|
+
log(debug, "tour", "step must be an object");
|
|
697
|
+
return false;
|
|
698
|
+
}
|
|
699
|
+
if (!step.target) {
|
|
700
|
+
log(debug, "tour", "target is missing from the step");
|
|
701
|
+
return false;
|
|
702
|
+
}
|
|
703
|
+
return true;
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Validate if steps are valid
|
|
707
|
+
*/
|
|
708
|
+
function validateSteps(steps, debug = false) {
|
|
709
|
+
if (!is.array(steps)) {
|
|
710
|
+
log(debug, "tour", "steps must be an array");
|
|
711
|
+
return false;
|
|
712
|
+
}
|
|
713
|
+
return steps.every((d) => validateStep(d, debug));
|
|
714
|
+
}
|
|
715
|
+
//#endregion
|
|
716
|
+
//#region src/modules/store.ts
|
|
717
|
+
var Store = class {
|
|
718
|
+
beaconPosition = null;
|
|
719
|
+
debug;
|
|
720
|
+
eventListeners = /* @__PURE__ */ new Map();
|
|
721
|
+
listeners = /* @__PURE__ */ new Set();
|
|
722
|
+
props;
|
|
723
|
+
snapshot;
|
|
724
|
+
state;
|
|
725
|
+
steps;
|
|
726
|
+
tooltipPosition = null;
|
|
727
|
+
constructor(options) {
|
|
728
|
+
const { initialStepIndex, stepIndex, steps = [] } = options ?? {};
|
|
729
|
+
const isControlled = is.number(stepIndex);
|
|
730
|
+
let startIndex = 0;
|
|
731
|
+
this.debug = options?.debug ?? false;
|
|
732
|
+
if (isControlled) {
|
|
733
|
+
startIndex = stepIndex;
|
|
734
|
+
if (is.number(initialStepIndex)) log(this.debug, "tour", "initialStepIndex is ignored in controlled mode");
|
|
735
|
+
} else if (is.number(initialStepIndex)) {
|
|
736
|
+
if (initialStepIndex >= 0 && initialStepIndex < steps.length) startIndex = initialStepIndex;
|
|
737
|
+
else if (steps.length > 0) log(this.debug, "tour", "initialStepIndex is out of bounds");
|
|
738
|
+
}
|
|
739
|
+
this.props = options ?? { steps: [] };
|
|
740
|
+
this.steps = steps;
|
|
741
|
+
this.state = {
|
|
742
|
+
action: ACTIONS.INIT,
|
|
743
|
+
controlled: isControlled,
|
|
744
|
+
index: startIndex,
|
|
745
|
+
lifecycle: LIFECYCLE.INIT,
|
|
746
|
+
origin: null,
|
|
747
|
+
positioned: false,
|
|
748
|
+
scrolling: false,
|
|
749
|
+
size: steps.length,
|
|
750
|
+
status: steps.length ? STATUS.READY : STATUS.IDLE,
|
|
751
|
+
waiting: false
|
|
752
|
+
};
|
|
753
|
+
this.snapshot = Object.freeze({ ...this.state });
|
|
754
|
+
}
|
|
755
|
+
applyTransitions(draft) {
|
|
756
|
+
if (draft.status === STATUS.WAITING && draft.size > 0) return {
|
|
757
|
+
...draft,
|
|
758
|
+
status: STATUS.RUNNING
|
|
759
|
+
};
|
|
760
|
+
return draft;
|
|
761
|
+
}
|
|
762
|
+
getStep(nextIndex) {
|
|
763
|
+
return getMergedStep(this.props, this.steps[nextIndex ?? this.state.index]);
|
|
764
|
+
}
|
|
765
|
+
cleanupPositionData = () => {
|
|
766
|
+
this.beaconPosition = null;
|
|
767
|
+
this.tooltipPosition = null;
|
|
768
|
+
};
|
|
769
|
+
getPositionData = (name) => {
|
|
770
|
+
if (name === "beacon") return this.beaconPosition;
|
|
771
|
+
return this.tooltipPosition;
|
|
772
|
+
};
|
|
773
|
+
getServerSnapshot = () => this.snapshot;
|
|
774
|
+
getSnapshot = () => this.snapshot;
|
|
775
|
+
getEventState = () => omit(this.snapshot, "positioned");
|
|
776
|
+
getState = () => omit(this.snapshot, "positioned");
|
|
777
|
+
setPositionData = (name, data) => {
|
|
778
|
+
if ((name === "beacon" ? this.beaconPosition : this.tooltipPosition)?.placement !== data.placement) log(this.debug, `step:${this.state.index}`, "positioned", `${name} ${data.placement}`);
|
|
779
|
+
if (name === "beacon") this.beaconPosition = data;
|
|
780
|
+
else this.tooltipPosition = data;
|
|
781
|
+
if ((this.state.lifecycle === LIFECYCLE.BEACON_BEFORE || this.state.lifecycle === LIFECYCLE.TOOLTIP_BEFORE) && !this.state.positioned) this.updateState({ positioned: true });
|
|
782
|
+
const onPosition = this.getStep()?.floatingOptions?.onPosition;
|
|
783
|
+
if (onPosition) onPosition(data);
|
|
784
|
+
};
|
|
785
|
+
setSteps = (steps) => {
|
|
786
|
+
this.steps = steps;
|
|
787
|
+
this.updateState({ size: steps.length });
|
|
788
|
+
};
|
|
789
|
+
dispatch = (data, controls) => {
|
|
790
|
+
const handlers = this.eventListeners.get(data.type);
|
|
791
|
+
if (handlers) for (const handler of handlers) try {
|
|
792
|
+
handler(data, controls);
|
|
793
|
+
} catch {}
|
|
794
|
+
};
|
|
795
|
+
on = (eventType, handler) => {
|
|
796
|
+
let handlers = this.eventListeners.get(eventType);
|
|
797
|
+
if (!handlers) {
|
|
798
|
+
handlers = /* @__PURE__ */ new Set();
|
|
799
|
+
this.eventListeners.set(eventType, handlers);
|
|
800
|
+
}
|
|
801
|
+
handlers.add(handler);
|
|
802
|
+
return () => {
|
|
803
|
+
handlers.delete(handler);
|
|
804
|
+
};
|
|
805
|
+
};
|
|
806
|
+
subscribe = (listener) => {
|
|
807
|
+
this.listeners.add(listener);
|
|
808
|
+
return () => {
|
|
809
|
+
this.listeners.delete(listener);
|
|
810
|
+
};
|
|
811
|
+
};
|
|
812
|
+
updateState = (patch, forceIndex = false) => {
|
|
813
|
+
const { controlled, index } = this.state;
|
|
814
|
+
const previousSnapshot = this.snapshot;
|
|
815
|
+
const resolvedIndex = controlled && !forceIndex && patch.index !== void 0 ? index : patch.index ?? index;
|
|
816
|
+
const merged = {
|
|
817
|
+
action: patch.action ?? this.state.action,
|
|
818
|
+
controlled,
|
|
819
|
+
index: resolvedIndex,
|
|
820
|
+
lifecycle: patch.lifecycle ?? this.state.lifecycle,
|
|
821
|
+
origin: patch.origin ?? null,
|
|
822
|
+
positioned: patch.positioned ?? this.state.positioned,
|
|
823
|
+
scrolling: patch.scrolling ?? this.state.scrolling,
|
|
824
|
+
size: patch.size ?? this.state.size,
|
|
825
|
+
status: patch.status ?? this.state.status,
|
|
826
|
+
waiting: patch.waiting ?? this.state.waiting
|
|
827
|
+
};
|
|
828
|
+
const final = this.applyTransitions(merged);
|
|
829
|
+
this.state = final;
|
|
830
|
+
if (!deepEqual(previousSnapshot, final)) {
|
|
831
|
+
this.snapshot = Object.freeze({ ...final });
|
|
832
|
+
for (const listener of this.listeners) listener(this.snapshot);
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
};
|
|
836
|
+
function createStore(options) {
|
|
837
|
+
return new Store(options);
|
|
838
|
+
}
|
|
839
|
+
//#endregion
|
|
840
|
+
//#region src/hooks/useControls.ts
|
|
841
|
+
function getUpdatedIndex(nextIndex, size) {
|
|
842
|
+
return Math.min(Math.max(nextIndex, 0), size);
|
|
843
|
+
}
|
|
844
|
+
function useControls(store, debug, clearFailures) {
|
|
845
|
+
const debugRef = useRef(debug);
|
|
846
|
+
const clearFailuresRef = useRef(clearFailures);
|
|
847
|
+
debugRef.current = debug;
|
|
848
|
+
clearFailuresRef.current = clearFailures;
|
|
849
|
+
return useMemo(() => {
|
|
850
|
+
const getState = () => store.current.getSnapshot();
|
|
851
|
+
const close = (origin = null) => {
|
|
852
|
+
const { index, status } = getState();
|
|
853
|
+
if (status !== STATUS.RUNNING) return;
|
|
854
|
+
store.current.updateState({
|
|
855
|
+
action: ACTIONS.CLOSE,
|
|
856
|
+
index: index + 1,
|
|
857
|
+
origin,
|
|
858
|
+
lifecycle: LIFECYCLE.COMPLETE,
|
|
859
|
+
positioned: false,
|
|
860
|
+
scrolling: false,
|
|
861
|
+
waiting: false
|
|
862
|
+
});
|
|
863
|
+
};
|
|
864
|
+
const go = (nextIndex) => {
|
|
865
|
+
const { controlled, size, status } = getState();
|
|
866
|
+
if (controlled) {
|
|
867
|
+
log(debugRef.current, "tour", "go() is not supported in controlled mode");
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
if (status !== STATUS.RUNNING) return;
|
|
871
|
+
store.current.updateState({
|
|
872
|
+
action: ACTIONS.GO,
|
|
873
|
+
index: nextIndex,
|
|
874
|
+
lifecycle: LIFECYCLE.COMPLETE,
|
|
875
|
+
positioned: false,
|
|
876
|
+
scrolling: false,
|
|
877
|
+
status: nextIndex < size ? status : STATUS.FINISHED,
|
|
878
|
+
waiting: false
|
|
879
|
+
});
|
|
880
|
+
};
|
|
881
|
+
const info = () => omit(store.current.getSnapshot(), "positioned");
|
|
882
|
+
const next = (origin) => {
|
|
883
|
+
const { index, size, status } = getState();
|
|
884
|
+
if (status !== STATUS.RUNNING) return;
|
|
885
|
+
store.current.updateState({
|
|
886
|
+
action: ACTIONS.NEXT,
|
|
887
|
+
index: getUpdatedIndex(index + 1, size),
|
|
888
|
+
lifecycle: LIFECYCLE.COMPLETE,
|
|
889
|
+
origin,
|
|
890
|
+
positioned: false,
|
|
891
|
+
scrolling: false,
|
|
892
|
+
waiting: false
|
|
893
|
+
});
|
|
894
|
+
};
|
|
895
|
+
const open = () => {
|
|
896
|
+
const { status } = getState();
|
|
897
|
+
if (status !== STATUS.RUNNING) return;
|
|
898
|
+
store.current.updateState({
|
|
899
|
+
action: ACTIONS.UPDATE,
|
|
900
|
+
lifecycle: LIFECYCLE.TOOLTIP_BEFORE,
|
|
901
|
+
positioned: false,
|
|
902
|
+
scrolling: false,
|
|
903
|
+
waiting: false
|
|
904
|
+
});
|
|
905
|
+
};
|
|
906
|
+
const previous = (origin) => {
|
|
907
|
+
const { index, size, status } = getState();
|
|
908
|
+
if (status !== STATUS.RUNNING) return;
|
|
909
|
+
store.current.updateState({
|
|
910
|
+
action: ACTIONS.PREV,
|
|
911
|
+
index: getUpdatedIndex(index - 1, size),
|
|
912
|
+
lifecycle: LIFECYCLE.COMPLETE,
|
|
913
|
+
origin,
|
|
914
|
+
positioned: false,
|
|
915
|
+
scrolling: false,
|
|
916
|
+
waiting: false
|
|
917
|
+
});
|
|
918
|
+
};
|
|
919
|
+
const replay = (origin) => {
|
|
920
|
+
const { lifecycle, status } = getState();
|
|
921
|
+
if (status !== STATUS.RUNNING || lifecycle !== LIFECYCLE.TOOLTIP) return;
|
|
922
|
+
store.current.updateState({
|
|
923
|
+
action: ACTIONS.REPLAY,
|
|
924
|
+
lifecycle: LIFECYCLE.COMPLETE,
|
|
925
|
+
origin,
|
|
926
|
+
positioned: false,
|
|
927
|
+
scrolling: false,
|
|
928
|
+
waiting: false
|
|
929
|
+
});
|
|
930
|
+
};
|
|
931
|
+
const reset = (restart = false) => {
|
|
932
|
+
const { controlled } = getState();
|
|
933
|
+
if (controlled) {
|
|
934
|
+
log(debugRef.current, "tour", "reset() is not supported in controlled mode");
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
clearFailuresRef.current();
|
|
938
|
+
store.current.updateState({
|
|
939
|
+
action: ACTIONS.RESET,
|
|
940
|
+
index: 0,
|
|
941
|
+
lifecycle: LIFECYCLE.INIT,
|
|
942
|
+
positioned: false,
|
|
943
|
+
scrolling: false,
|
|
944
|
+
status: restart ? STATUS.RUNNING : STATUS.READY,
|
|
945
|
+
waiting: false
|
|
946
|
+
});
|
|
947
|
+
};
|
|
948
|
+
const skip = (origin) => {
|
|
949
|
+
const { status } = getState();
|
|
950
|
+
if (status !== STATUS.RUNNING) return;
|
|
951
|
+
store.current.updateState({
|
|
952
|
+
action: ACTIONS.SKIP,
|
|
953
|
+
lifecycle: LIFECYCLE.COMPLETE,
|
|
954
|
+
origin,
|
|
955
|
+
positioned: false,
|
|
956
|
+
scrolling: false,
|
|
957
|
+
status: STATUS.SKIPPED,
|
|
958
|
+
waiting: false
|
|
959
|
+
});
|
|
960
|
+
};
|
|
961
|
+
const start = (nextIndex) => {
|
|
962
|
+
const { index, size } = getState();
|
|
963
|
+
clearFailuresRef.current();
|
|
964
|
+
store.current.updateState({
|
|
965
|
+
action: ACTIONS.START,
|
|
966
|
+
index: is.number(nextIndex) ? nextIndex : index,
|
|
967
|
+
lifecycle: LIFECYCLE.INIT,
|
|
968
|
+
positioned: false,
|
|
969
|
+
scrolling: false,
|
|
970
|
+
status: size ? STATUS.RUNNING : STATUS.WAITING,
|
|
971
|
+
waiting: false
|
|
972
|
+
}, true);
|
|
973
|
+
};
|
|
974
|
+
const stop = (advance = false) => {
|
|
975
|
+
const { index, status } = getState();
|
|
976
|
+
if ([STATUS.FINISHED, STATUS.SKIPPED].includes(status)) return;
|
|
977
|
+
store.current.updateState({
|
|
978
|
+
action: ACTIONS.STOP,
|
|
979
|
+
index: index + (advance ? 1 : 0),
|
|
980
|
+
lifecycle: LIFECYCLE.COMPLETE,
|
|
981
|
+
positioned: false,
|
|
982
|
+
scrolling: false,
|
|
983
|
+
status: STATUS.PAUSED,
|
|
984
|
+
waiting: false
|
|
985
|
+
});
|
|
986
|
+
};
|
|
987
|
+
return {
|
|
988
|
+
close,
|
|
989
|
+
go,
|
|
990
|
+
info,
|
|
991
|
+
next,
|
|
992
|
+
open,
|
|
993
|
+
prev: previous,
|
|
994
|
+
replay,
|
|
995
|
+
reset,
|
|
996
|
+
skip,
|
|
997
|
+
start,
|
|
998
|
+
stop
|
|
999
|
+
};
|
|
1000
|
+
}, [store]);
|
|
1001
|
+
}
|
|
1002
|
+
//#endregion
|
|
1003
|
+
//#region src/hooks/useDebugLogger.ts
|
|
1004
|
+
const skipFields = new Set(["origin", "positioned"]);
|
|
1005
|
+
function useDebugLogger(store, debug) {
|
|
1006
|
+
const previousRef = useRef(null);
|
|
1007
|
+
useEffect(() => {
|
|
1008
|
+
if (!debug) return;
|
|
1009
|
+
const current = store.current.getSnapshot();
|
|
1010
|
+
log(true, "tour", "init", current);
|
|
1011
|
+
previousRef.current = current;
|
|
1012
|
+
return store.current.subscribe((state) => {
|
|
1013
|
+
const previous = previousRef.current;
|
|
1014
|
+
previousRef.current = state;
|
|
1015
|
+
if (!previous) return;
|
|
1016
|
+
const changes = {};
|
|
1017
|
+
let isTourLevel = false;
|
|
1018
|
+
for (const key of Object.keys(state)) if (state[key] !== previous[key] && !skipFields.has(key)) {
|
|
1019
|
+
changes[key] = {
|
|
1020
|
+
from: previous[key],
|
|
1021
|
+
to: state[key]
|
|
1022
|
+
};
|
|
1023
|
+
if (key === "status" || key === "size") isTourLevel = true;
|
|
1024
|
+
}
|
|
1025
|
+
if (Object.keys(changes).length) {
|
|
1026
|
+
if (!(!isTourLevel && state.index >= state.size)) log(true, isTourLevel ? "tour" : `step:${state.index}`, "state", changes);
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
1029
|
+
}, [debug, store]);
|
|
1030
|
+
}
|
|
1031
|
+
//#endregion
|
|
1032
|
+
//#region src/hooks/useEventEmitter.ts
|
|
1033
|
+
function useEventEmitter(onEvent, controls, store) {
|
|
1034
|
+
const onEventRef = useRef(onEvent);
|
|
1035
|
+
const controlsRef = useRef(controls);
|
|
1036
|
+
onEventRef.current = onEvent;
|
|
1037
|
+
controlsRef.current = controls;
|
|
1038
|
+
return useCallback((type, step, overrides) => {
|
|
1039
|
+
const data = {
|
|
1040
|
+
...store.current.getEventState(),
|
|
1041
|
+
error: null,
|
|
1042
|
+
scroll: null,
|
|
1043
|
+
step,
|
|
1044
|
+
type,
|
|
1045
|
+
...overrides
|
|
1046
|
+
};
|
|
1047
|
+
onEventRef.current?.(data, controlsRef.current);
|
|
1048
|
+
store.current.dispatch(data, controlsRef.current);
|
|
1049
|
+
}, [store]);
|
|
1050
|
+
}
|
|
1051
|
+
//#endregion
|
|
1052
|
+
//#region src/modules/changes.ts
|
|
1053
|
+
function treeChanges(state, previous) {
|
|
1054
|
+
return {
|
|
1055
|
+
hasChanged(key) {
|
|
1056
|
+
return state[key] !== previous[key];
|
|
1057
|
+
},
|
|
1058
|
+
hasChangedTo(key, value) {
|
|
1059
|
+
const current = state[key];
|
|
1060
|
+
const previousValue = previous[key];
|
|
1061
|
+
if (Array.isArray(value)) return value.includes(current) && !value.includes(previousValue);
|
|
1062
|
+
return current === value && previousValue !== value;
|
|
1063
|
+
},
|
|
1064
|
+
previous
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
//#endregion
|
|
1068
|
+
//#region src/hooks/useLifecycleEffect.ts
|
|
1069
|
+
function useLifecycleEffect(options) {
|
|
1070
|
+
const { addFailure, controls, emitEvent, previousState, props, state, step, store } = options;
|
|
1071
|
+
const { action, index, lifecycle, positioned, scrolling, size, status } = state;
|
|
1072
|
+
const previousStep = usePrevious(step) ?? null;
|
|
1073
|
+
const lastAction = useRef(null);
|
|
1074
|
+
const propsRef = useRef(props);
|
|
1075
|
+
const stateRef = useRef(state);
|
|
1076
|
+
const previousStateRef = useRef(previousState);
|
|
1077
|
+
const stepRef = useRef(step);
|
|
1078
|
+
const previousStepRef = useRef(previousStep);
|
|
1079
|
+
const controlsRef = useRef(controls);
|
|
1080
|
+
const pollingRef = useRef(null);
|
|
1081
|
+
const pollingTargetRef = useRef(null);
|
|
1082
|
+
const beforeRef = useRef(null);
|
|
1083
|
+
propsRef.current = props;
|
|
1084
|
+
stateRef.current = state;
|
|
1085
|
+
previousStateRef.current = previousState;
|
|
1086
|
+
stepRef.current = step;
|
|
1087
|
+
previousStepRef.current = previousStep;
|
|
1088
|
+
controlsRef.current = controls;
|
|
1089
|
+
const cleanup = () => {
|
|
1090
|
+
if (pollingRef.current) {
|
|
1091
|
+
clearInterval(pollingRef.current);
|
|
1092
|
+
pollingRef.current = null;
|
|
1093
|
+
}
|
|
1094
|
+
pollingTargetRef.current = null;
|
|
1095
|
+
if (beforeRef.current) {
|
|
1096
|
+
beforeRef.current.cancel();
|
|
1097
|
+
beforeRef.current = null;
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
useEffect(() => {
|
|
1101
|
+
if (!previousStateRef.current) return;
|
|
1102
|
+
const { hasChangedTo } = treeChanges(stateRef.current, previousStateRef.current);
|
|
1103
|
+
const isAfterAction = hasChangedTo("action", [
|
|
1104
|
+
ACTIONS.NEXT,
|
|
1105
|
+
ACTIONS.PREV,
|
|
1106
|
+
ACTIONS.SKIP,
|
|
1107
|
+
ACTIONS.CLOSE,
|
|
1108
|
+
ACTIONS.REPLAY
|
|
1109
|
+
]);
|
|
1110
|
+
const isStaleAfterStart = action === ACTIONS.START && (lastAction.current === ACTIONS.CLOSE || lastAction.current === ACTIONS.REPLAY);
|
|
1111
|
+
if (isAfterAction || isStaleAfterStart) lastAction.current = action;
|
|
1112
|
+
}, [action]);
|
|
1113
|
+
useEffect(() => {
|
|
1114
|
+
if (!previousStateRef.current) return () => {
|
|
1115
|
+
cleanup();
|
|
1116
|
+
};
|
|
1117
|
+
const { hasChanged } = treeChanges(stateRef.current, previousStateRef.current);
|
|
1118
|
+
const currentStep = stepRef.current;
|
|
1119
|
+
if (hasChanged("index")) cleanup();
|
|
1120
|
+
if (status !== STATUS.RUNNING || !currentStep || lifecycle !== LIFECYCLE.INIT) return () => {
|
|
1121
|
+
cleanup();
|
|
1122
|
+
};
|
|
1123
|
+
const { hasChangedTo: hasStatusChangedTo } = treeChanges(stateRef.current, previousStateRef.current);
|
|
1124
|
+
if (hasStatusChangedTo("status", STATUS.RUNNING) && [
|
|
1125
|
+
STATUS.IDLE,
|
|
1126
|
+
STATUS.READY,
|
|
1127
|
+
STATUS.PAUSED
|
|
1128
|
+
].includes(previousStateRef.current.status)) emitEvent(EVENTS.TOUR_START, currentStep);
|
|
1129
|
+
store.current.cleanupPositionData();
|
|
1130
|
+
const { debug } = propsRef.current;
|
|
1131
|
+
if (currentStep.before && !beforeRef.current) {
|
|
1132
|
+
log(debug, `step:${index}`, "before()", currentStep);
|
|
1133
|
+
beforeRef.current = { cancel: () => {} };
|
|
1134
|
+
store.current.updateState({ waiting: true });
|
|
1135
|
+
emitEvent(EVENTS.STEP_BEFORE_HOOK, currentStep, { action: lastAction.current ?? stateRef.current.action });
|
|
1136
|
+
const proceed = () => {
|
|
1137
|
+
beforeRef.current = null;
|
|
1138
|
+
store.current.updateState({
|
|
1139
|
+
action: lastAction.current ?? stateRef.current.action,
|
|
1140
|
+
waiting: false,
|
|
1141
|
+
lifecycle: LIFECYCLE.READY
|
|
1142
|
+
});
|
|
1143
|
+
};
|
|
1144
|
+
const abortController = new AbortController();
|
|
1145
|
+
const timeout = currentStep.beforeTimeout;
|
|
1146
|
+
beforeRef.current = { cancel: () => abortController.abort() };
|
|
1147
|
+
const timeoutId = timeout ? setTimeout(() => {
|
|
1148
|
+
if (!abortController.signal.aborted) {
|
|
1149
|
+
log(debug, `step:${index}`, "before()", "timed out", `${timeout}ms`);
|
|
1150
|
+
abortController.abort();
|
|
1151
|
+
addFailure(currentStep, "before_hook");
|
|
1152
|
+
emitEvent(EVENTS.ERROR, currentStep, { error: /* @__PURE__ */ new Error("Step before hook timed out") });
|
|
1153
|
+
proceed();
|
|
1154
|
+
}
|
|
1155
|
+
}, timeout) : null;
|
|
1156
|
+
currentStep.before({
|
|
1157
|
+
...store.current.getState(),
|
|
1158
|
+
action: lastAction.current ?? store.current.getState().action,
|
|
1159
|
+
step: currentStep
|
|
1160
|
+
}).then(() => {
|
|
1161
|
+
if (!abortController.signal.aborted) {
|
|
1162
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
1163
|
+
proceed();
|
|
1164
|
+
}
|
|
1165
|
+
}).catch((error) => {
|
|
1166
|
+
if (!abortController.signal.aborted) {
|
|
1167
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
1168
|
+
addFailure(currentStep, "before_hook");
|
|
1169
|
+
emitEvent(EVENTS.ERROR, currentStep, { error: error instanceof Error ? error : new Error(String(error)) });
|
|
1170
|
+
proceed();
|
|
1171
|
+
}
|
|
1172
|
+
});
|
|
1173
|
+
} else if (!beforeRef.current) {
|
|
1174
|
+
if (pollingRef.current && pollingTargetRef.current !== currentStep.target) cleanup();
|
|
1175
|
+
const element = getElement(currentStep.target);
|
|
1176
|
+
if (element && isElementVisible(element)) {
|
|
1177
|
+
cleanup();
|
|
1178
|
+
store.current.updateState({
|
|
1179
|
+
action: lastAction.current ?? ACTIONS.UPDATE,
|
|
1180
|
+
lifecycle: LIFECYCLE.READY,
|
|
1181
|
+
waiting: false
|
|
1182
|
+
});
|
|
1183
|
+
} else if (currentStep.targetWaitTimeout === 0) store.current.updateState({
|
|
1184
|
+
action: lastAction.current ?? ACTIONS.UPDATE,
|
|
1185
|
+
lifecycle: LIFECYCLE.READY,
|
|
1186
|
+
waiting: false
|
|
1187
|
+
});
|
|
1188
|
+
else if (!pollingRef.current) {
|
|
1189
|
+
const { targetWaitTimeout } = currentStep;
|
|
1190
|
+
const startTime = Date.now();
|
|
1191
|
+
pollingTargetRef.current = currentStep.target;
|
|
1192
|
+
log(debug, `step:${index}`, "polling", "started", `${targetWaitTimeout}ms`);
|
|
1193
|
+
store.current.updateState({ waiting: true });
|
|
1194
|
+
pollingRef.current = setInterval(() => {
|
|
1195
|
+
const el = getElement(currentStep.target);
|
|
1196
|
+
const elapsed = Date.now() - startTime;
|
|
1197
|
+
const timedOut = elapsed >= targetWaitTimeout;
|
|
1198
|
+
if (el && isElementVisible(el) || timedOut) {
|
|
1199
|
+
log(debug, `step:${index}`, "polling", el && isElementVisible(el) ? "found" : "timed out", `${elapsed}ms`);
|
|
1200
|
+
cleanup();
|
|
1201
|
+
store.current.updateState({
|
|
1202
|
+
action: lastAction.current ?? ACTIONS.UPDATE,
|
|
1203
|
+
lifecycle: LIFECYCLE.READY,
|
|
1204
|
+
waiting: false
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
}, 100);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
return () => {
|
|
1211
|
+
cleanup();
|
|
1212
|
+
};
|
|
1213
|
+
}, [
|
|
1214
|
+
addFailure,
|
|
1215
|
+
emitEvent,
|
|
1216
|
+
index,
|
|
1217
|
+
lifecycle,
|
|
1218
|
+
status,
|
|
1219
|
+
store
|
|
1220
|
+
]);
|
|
1221
|
+
useEffect(() => {
|
|
1222
|
+
if (!previousStateRef.current) return;
|
|
1223
|
+
const { hasChanged, hasChangedTo, previous } = treeChanges(stateRef.current, previousStateRef.current);
|
|
1224
|
+
const currentStep = stepRef.current;
|
|
1225
|
+
if (!currentStep) return;
|
|
1226
|
+
const element = getElement(currentStep.target);
|
|
1227
|
+
const elementExists = !!element;
|
|
1228
|
+
if (elementExists && isElementVisible(element)) {
|
|
1229
|
+
if (hasChangedTo("lifecycle", LIFECYCLE.READY) && previous.lifecycle === LIFECYCLE.INIT) emitEvent(EVENTS.STEP_BEFORE, currentStep, { action: lastAction.current ?? stateRef.current.action });
|
|
1230
|
+
if (hasChangedTo("lifecycle", LIFECYCLE.READY)) {
|
|
1231
|
+
const currentState = stateRef.current;
|
|
1232
|
+
const finalLifecycle = shouldHideBeacon(currentStep, currentState, propsRef.current.continuous) ? LIFECYCLE.TOOLTIP : LIFECYCLE.BEACON;
|
|
1233
|
+
const target = getElement(currentStep.scrollTarget ?? currentStep.spotlightTarget ?? currentStep.target);
|
|
1234
|
+
const willScroll = needsScrolling({
|
|
1235
|
+
isFirstStep: currentState.index === 0,
|
|
1236
|
+
scrollToFirstStep: propsRef.current.scrollToFirstStep,
|
|
1237
|
+
step: currentStep,
|
|
1238
|
+
target,
|
|
1239
|
+
targetLifecycle: finalLifecycle
|
|
1240
|
+
});
|
|
1241
|
+
const beforeLifecycle = finalLifecycle === LIFECYCLE.TOOLTIP ? LIFECYCLE.TOOLTIP_BEFORE : LIFECYCLE.BEACON_BEFORE;
|
|
1242
|
+
log(propsRef.current.debug, `step:${index}`, "scroll", willScroll ? "needed" : "skipped");
|
|
1243
|
+
store.current.updateState({
|
|
1244
|
+
action: ACTIONS.UPDATE,
|
|
1245
|
+
lifecycle: beforeLifecycle,
|
|
1246
|
+
scrolling: willScroll
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
} else if (stateRef.current.status === STATUS.RUNNING && lifecycle !== LIFECYCLE.INIT && lifecycle !== LIFECYCLE.COMPLETE && hasChanged("lifecycle")) {
|
|
1250
|
+
log(propsRef.current.debug, `step:${index}`, elementExists ? "Target not visible" : "Target not mounted", currentStep);
|
|
1251
|
+
addFailure(currentStep, "target_not_found");
|
|
1252
|
+
emitEvent(EVENTS.TARGET_NOT_FOUND, currentStep);
|
|
1253
|
+
const currentState = stateRef.current;
|
|
1254
|
+
if (!currentState.controlled) store.current.updateState({
|
|
1255
|
+
action: ACTIONS.UPDATE,
|
|
1256
|
+
index: currentState.index + (currentState.action === ACTIONS.PREV ? -1 : 1),
|
|
1257
|
+
lifecycle: LIFECYCLE.INIT
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
}, [
|
|
1261
|
+
addFailure,
|
|
1262
|
+
emitEvent,
|
|
1263
|
+
index,
|
|
1264
|
+
lifecycle,
|
|
1265
|
+
store
|
|
1266
|
+
]);
|
|
1267
|
+
useEffect(() => {
|
|
1268
|
+
if (!previousStateRef.current) return;
|
|
1269
|
+
const { hasChangedTo, previous } = treeChanges(stateRef.current, previousStateRef.current);
|
|
1270
|
+
const currentStep = stepRef.current;
|
|
1271
|
+
const previousStepValue = previousStepRef.current;
|
|
1272
|
+
if (currentStep && hasChangedTo("lifecycle", LIFECYCLE.TOOLTIP_BEFORE) && previous.lifecycle === LIFECYCLE.BEACON) {
|
|
1273
|
+
const target = getElement(currentStep.scrollTarget ?? currentStep.spotlightTarget ?? currentStep.target);
|
|
1274
|
+
if (needsScrolling({
|
|
1275
|
+
isFirstStep: stateRef.current.index === 0,
|
|
1276
|
+
scrollToFirstStep: propsRef.current.scrollToFirstStep,
|
|
1277
|
+
step: currentStep,
|
|
1278
|
+
target,
|
|
1279
|
+
targetLifecycle: LIFECYCLE.TOOLTIP
|
|
1280
|
+
})) {
|
|
1281
|
+
store.current.updateState({
|
|
1282
|
+
scrolling: true,
|
|
1283
|
+
positioned: false
|
|
1284
|
+
});
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
const isBeforePhase = lifecycle === LIFECYCLE.BEACON_BEFORE || lifecycle === LIFECYCLE.TOOLTIP_BEFORE;
|
|
1289
|
+
if (currentStep && isBeforePhase && !scrolling) {
|
|
1290
|
+
const finalLifecycle = lifecycle === LIFECYCLE.TOOLTIP_BEFORE ? LIFECYCLE.TOOLTIP : LIFECYCLE.BEACON;
|
|
1291
|
+
store.current.updateState({
|
|
1292
|
+
action: ACTIONS.UPDATE,
|
|
1293
|
+
lifecycle: finalLifecycle
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
if (currentStep && hasChangedTo("lifecycle", LIFECYCLE.BEACON)) emitEvent(EVENTS.BEACON, currentStep);
|
|
1297
|
+
if (currentStep && hasChangedTo("lifecycle", LIFECYCLE.TOOLTIP)) emitEvent(EVENTS.TOOLTIP, currentStep);
|
|
1298
|
+
const currentState = stateRef.current;
|
|
1299
|
+
if ((currentState.status === STATUS.RUNNING || currentState.controlled && currentState.status === STATUS.PAUSED && !!currentStep) && previousStepValue && hasChangedTo("lifecycle", LIFECYCLE.COMPLETE) && previous.lifecycle === LIFECYCLE.TOOLTIP) {
|
|
1300
|
+
emitEvent(EVENTS.STEP_AFTER, previousStepValue, {
|
|
1301
|
+
action: lastAction.current ?? ACTIONS.UPDATE,
|
|
1302
|
+
index: previous.index ?? currentState.index,
|
|
1303
|
+
lifecycle: currentState.lifecycle
|
|
1304
|
+
});
|
|
1305
|
+
if (previousStepValue.after) {
|
|
1306
|
+
emitEvent(EVENTS.STEP_AFTER_HOOK, previousStepValue, {
|
|
1307
|
+
action: lastAction.current ?? ACTIONS.UPDATE,
|
|
1308
|
+
index: previous.index ?? currentState.index,
|
|
1309
|
+
lifecycle: currentState.lifecycle
|
|
1310
|
+
});
|
|
1311
|
+
try {
|
|
1312
|
+
previousStepValue.after({
|
|
1313
|
+
...store.current.getState(),
|
|
1314
|
+
action: lastAction.current ?? ACTIONS.UPDATE,
|
|
1315
|
+
index: previous.index ?? currentState.index,
|
|
1316
|
+
lifecycle: currentState.lifecycle,
|
|
1317
|
+
step: previousStepValue
|
|
1318
|
+
});
|
|
1319
|
+
} catch {}
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
}, [
|
|
1323
|
+
emitEvent,
|
|
1324
|
+
lifecycle,
|
|
1325
|
+
positioned,
|
|
1326
|
+
scrolling,
|
|
1327
|
+
store
|
|
1328
|
+
]);
|
|
1329
|
+
useEffect(() => {
|
|
1330
|
+
if (!previousStateRef.current) return;
|
|
1331
|
+
const { hasChangedTo, previous } = treeChanges(stateRef.current, previousStateRef.current);
|
|
1332
|
+
const currentStep = stepRef.current;
|
|
1333
|
+
const previousStepValue = previousStepRef.current;
|
|
1334
|
+
if (hasChangedTo("action", ACTIONS.REPLAY) && hasChangedTo("lifecycle", LIFECYCLE.COMPLETE)) {
|
|
1335
|
+
store.current.updateState({ lifecycle: LIFECYCLE.INIT });
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
if (size && !currentStep && lifecycle === LIFECYCLE.INIT) store.current.updateState({
|
|
1339
|
+
action: ACTIONS.UPDATE,
|
|
1340
|
+
lifecycle: LIFECYCLE.COMPLETE,
|
|
1341
|
+
status: STATUS.FINISHED
|
|
1342
|
+
});
|
|
1343
|
+
if (!stateRef.current.controlled && status === STATUS.RUNNING && hasChangedTo("lifecycle", LIFECYCLE.COMPLETE) && index < size) store.current.updateState({
|
|
1344
|
+
action: ACTIONS.UPDATE,
|
|
1345
|
+
lifecycle: LIFECYCLE.INIT
|
|
1346
|
+
});
|
|
1347
|
+
if (hasChangedTo("lifecycle", LIFECYCLE.COMPLETE) && index >= size) store.current.updateState({
|
|
1348
|
+
action: ACTIONS.UPDATE,
|
|
1349
|
+
lifecycle: LIFECYCLE.COMPLETE,
|
|
1350
|
+
status: STATUS.FINISHED
|
|
1351
|
+
});
|
|
1352
|
+
const tourEndStep = currentStep ?? previousStepValue ?? getMergedStep(propsRef.current, propsRef.current.steps[index - 1]);
|
|
1353
|
+
if (tourEndStep && hasChangedTo("status", [STATUS.FINISHED, STATUS.SKIPPED])) {
|
|
1354
|
+
let tourEndIndex;
|
|
1355
|
+
if (currentStep) tourEndIndex = index;
|
|
1356
|
+
else if (previousStepValue) tourEndIndex = previous.index ?? index;
|
|
1357
|
+
else tourEndIndex = index - 1;
|
|
1358
|
+
emitEvent(EVENTS.TOUR_END, tourEndStep, { index: tourEndIndex });
|
|
1359
|
+
if (!stateRef.current.controlled) controlsRef.current.reset();
|
|
1360
|
+
lastAction.current = null;
|
|
1361
|
+
}
|
|
1362
|
+
if (currentStep && hasChangedTo("action", ACTIONS.STOP)) {
|
|
1363
|
+
lastAction.current = null;
|
|
1364
|
+
emitEvent(EVENTS.TOUR_STATUS, currentStep);
|
|
1365
|
+
}
|
|
1366
|
+
if (currentStep && hasChangedTo("action", ACTIONS.RESET)) {
|
|
1367
|
+
emitEvent(EVENTS.TOUR_STATUS, currentStep);
|
|
1368
|
+
lastAction.current = null;
|
|
1369
|
+
}
|
|
1370
|
+
}, [
|
|
1371
|
+
action,
|
|
1372
|
+
emitEvent,
|
|
1373
|
+
index,
|
|
1374
|
+
lifecycle,
|
|
1375
|
+
size,
|
|
1376
|
+
status,
|
|
1377
|
+
store
|
|
1378
|
+
]);
|
|
1379
|
+
}
|
|
1380
|
+
//#endregion
|
|
1381
|
+
//#region src/hooks/usePropSync.ts
|
|
1382
|
+
function usePropSync({ controls, emitEvent, props, state, store }) {
|
|
1383
|
+
const { debug, initialStepIndex, run, stepIndex, steps } = props;
|
|
1384
|
+
const previousPropsRef = useRef(void 0);
|
|
1385
|
+
const stateRef = useRef(state);
|
|
1386
|
+
const controlsRef = useRef(controls);
|
|
1387
|
+
stateRef.current = state;
|
|
1388
|
+
controlsRef.current = controls;
|
|
1389
|
+
useEffect(() => {
|
|
1390
|
+
const previousProps = previousPropsRef.current;
|
|
1391
|
+
previousPropsRef.current = props;
|
|
1392
|
+
if (!previousProps || props === previousProps) return;
|
|
1393
|
+
const { hasChanged } = treeChanges(props, previousProps);
|
|
1394
|
+
if (!deepEqual(previousProps.steps, steps)) if (validateSteps(steps, debug)) store.current.setSteps(steps);
|
|
1395
|
+
else {
|
|
1396
|
+
log(debug, "tour", "Steps are not valid", steps);
|
|
1397
|
+
emitEvent(EVENTS.ERROR, steps[0] ?? {
|
|
1398
|
+
target: "",
|
|
1399
|
+
content: ""
|
|
1400
|
+
}, { error: /* @__PURE__ */ new Error("Steps are not valid") });
|
|
1401
|
+
}
|
|
1402
|
+
if (hasChanged("run")) if (run) {
|
|
1403
|
+
if (store.current.getState().size) controlsRef.current.start(stepIndex ?? initialStepIndex);
|
|
1404
|
+
} else controlsRef.current.stop();
|
|
1405
|
+
else if (is.number(stepIndex) && hasChanged("stepIndex")) {
|
|
1406
|
+
const nextAction = is.number(previousProps.stepIndex) && previousProps.stepIndex < stepIndex ? ACTIONS.NEXT : ACTIONS.PREV;
|
|
1407
|
+
if (![STATUS.FINISHED, STATUS.SKIPPED].includes(stateRef.current.status)) store.current.updateState({
|
|
1408
|
+
action: nextAction,
|
|
1409
|
+
index: stepIndex,
|
|
1410
|
+
lifecycle: LIFECYCLE.INIT,
|
|
1411
|
+
positioned: false
|
|
1412
|
+
}, true);
|
|
1413
|
+
}
|
|
1414
|
+
}, [
|
|
1415
|
+
debug,
|
|
1416
|
+
emitEvent,
|
|
1417
|
+
initialStepIndex,
|
|
1418
|
+
props,
|
|
1419
|
+
run,
|
|
1420
|
+
stepIndex,
|
|
1421
|
+
steps,
|
|
1422
|
+
store
|
|
1423
|
+
]);
|
|
1424
|
+
}
|
|
1425
|
+
//#endregion
|
|
1426
|
+
//#region src/hooks/useScrollEffect.ts
|
|
1427
|
+
function adjustForPlacement(scrollY, options) {
|
|
1428
|
+
const { beaconPosition, lifecycle, scrollOffset, step } = options;
|
|
1429
|
+
if (step.scrollTarget || step.spotlightTarget) return Math.max(0, scrollY);
|
|
1430
|
+
let adjustedY = scrollY - step.spotlightPadding.top;
|
|
1431
|
+
if (lifecycle === LIFECYCLE.BEACON_BEFORE && beaconPosition?.placement) {
|
|
1432
|
+
const y = getMainAxisOffset(beaconPosition);
|
|
1433
|
+
if (!["bottom"].includes(beaconPosition.placement)) adjustedY += Math.floor(y - scrollOffset);
|
|
1434
|
+
} else if (lifecycle === LIFECYCLE.TOOLTIP_BEFORE) {
|
|
1435
|
+
const { placement } = step;
|
|
1436
|
+
if (placement === "top") {
|
|
1437
|
+
const floaterHeight = document.querySelector(".react-joyride__floater")?.getBoundingClientRect().height ?? 0;
|
|
1438
|
+
const arrowSize = step.floatingOptions?.hideArrow ? 0 : step.arrowSize;
|
|
1439
|
+
const gap = step.offset + step.spotlightPadding.top + arrowSize;
|
|
1440
|
+
adjustedY -= floaterHeight + gap;
|
|
1441
|
+
} else if (placement === "left" || placement === "right") {
|
|
1442
|
+
const floaterHeight = document.querySelector(".react-joyride__floater")?.getBoundingClientRect().height ?? 0;
|
|
1443
|
+
const targetHeight = getElement(step.target)?.getBoundingClientRect().height ?? 0;
|
|
1444
|
+
const floaterTopY = scrollOffset + step.spotlightPadding.top + targetHeight / 2 - floaterHeight / 2;
|
|
1445
|
+
if (floaterTopY < scrollOffset) adjustedY -= scrollOffset - floaterTopY;
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
return Math.max(0, adjustedY);
|
|
1449
|
+
}
|
|
1450
|
+
function getMainAxisOffset(data) {
|
|
1451
|
+
const offsetData = data.middlewareData?.offset;
|
|
1452
|
+
if (!offsetData) return 0;
|
|
1453
|
+
return ["left", "right"].some((p) => data.placement.startsWith(p)) ? offsetData.x : offsetData.y;
|
|
1454
|
+
}
|
|
1455
|
+
function useScrollEffect({ emitEvent, previousState, props, state, step, store }) {
|
|
1456
|
+
const { index, lifecycle, positioned, scrolling, status } = state;
|
|
1457
|
+
const cancelScrollRef = useRef(null);
|
|
1458
|
+
const stateRef = useRef(state);
|
|
1459
|
+
const previousStateRef = useRef(previousState);
|
|
1460
|
+
const propsRef = useRef(props);
|
|
1461
|
+
const stepRef = useRef(step);
|
|
1462
|
+
stateRef.current = state;
|
|
1463
|
+
previousStateRef.current = previousState;
|
|
1464
|
+
propsRef.current = props;
|
|
1465
|
+
stepRef.current = step;
|
|
1466
|
+
useEffect(() => {
|
|
1467
|
+
return () => {
|
|
1468
|
+
cancelScrollRef.current?.();
|
|
1469
|
+
};
|
|
1470
|
+
}, []);
|
|
1471
|
+
useEffect(() => {
|
|
1472
|
+
if (!previousStateRef.current || !stepRef.current) return;
|
|
1473
|
+
const { hasChangedTo } = treeChanges(stateRef.current, previousStateRef.current);
|
|
1474
|
+
const currentStep = stepRef.current;
|
|
1475
|
+
const { debug } = propsRef.current;
|
|
1476
|
+
const { scrollDuration } = currentStep;
|
|
1477
|
+
const isBeforePhase = lifecycle === LIFECYCLE.BEACON_BEFORE || lifecycle === LIFECYCLE.TOOLTIP_BEFORE;
|
|
1478
|
+
if (status === STATUS.RUNNING && isBeforePhase && scrolling && hasChangedTo("positioned", true)) {
|
|
1479
|
+
const target = getElement(currentStep.scrollTarget ?? currentStep.spotlightTarget ?? currentStep.target);
|
|
1480
|
+
const beaconPosition = store.current.getPositionData("beacon");
|
|
1481
|
+
const scrollParent = getScrollParent(target);
|
|
1482
|
+
const hasCustomScroll = scrollParent ? !scrollParent.isSameNode(scrollDocument()) : false;
|
|
1483
|
+
cancelScrollRef.current?.();
|
|
1484
|
+
const handleScroll = async () => {
|
|
1485
|
+
if (hasCustomScroll && !hasPosition(scrollParent)) {
|
|
1486
|
+
const pageElement = scrollDocument();
|
|
1487
|
+
const pageScrollY = getScrollTargetToCenter(scrollParent);
|
|
1488
|
+
const pageScrollData = {
|
|
1489
|
+
initial: pageElement.scrollTop,
|
|
1490
|
+
target: pageScrollY,
|
|
1491
|
+
element: pageElement,
|
|
1492
|
+
duration: scrollDuration
|
|
1493
|
+
};
|
|
1494
|
+
emitEvent(EVENTS.SCROLL_START, currentStep, { scroll: pageScrollData });
|
|
1495
|
+
const { cancel: cancelPage, promise: pagePromise } = scrollTo(pageScrollY, {
|
|
1496
|
+
element: pageElement,
|
|
1497
|
+
duration: scrollDuration
|
|
1498
|
+
});
|
|
1499
|
+
cancelScrollRef.current = cancelPage;
|
|
1500
|
+
await pagePromise;
|
|
1501
|
+
emitEvent(EVENTS.SCROLL_END, currentStep, { scroll: pageScrollData });
|
|
1502
|
+
}
|
|
1503
|
+
const baseScrollY = Math.floor(getScrollTo(target, currentStep.scrollOffset)) || 0;
|
|
1504
|
+
const scrollY = hasCustomScroll ? baseScrollY : adjustForPlacement(baseScrollY, {
|
|
1505
|
+
beaconPosition,
|
|
1506
|
+
lifecycle,
|
|
1507
|
+
scrollOffset: currentStep.scrollOffset,
|
|
1508
|
+
step: currentStep
|
|
1509
|
+
});
|
|
1510
|
+
log(debug, `step:${index}`, "scroll", hasCustomScroll ? "custom" : "document", `${baseScrollY} → ${scrollY}`);
|
|
1511
|
+
const scrollElement = scrollParent;
|
|
1512
|
+
const scrollData = {
|
|
1513
|
+
initial: scrollElement.scrollTop,
|
|
1514
|
+
target: scrollY,
|
|
1515
|
+
element: scrollElement,
|
|
1516
|
+
duration: scrollDuration
|
|
1517
|
+
};
|
|
1518
|
+
emitEvent(EVENTS.SCROLL_START, currentStep, { scroll: scrollData });
|
|
1519
|
+
const { cancel, promise } = scrollTo(scrollY, {
|
|
1520
|
+
element: scrollElement,
|
|
1521
|
+
duration: scrollDuration
|
|
1522
|
+
});
|
|
1523
|
+
cancelScrollRef.current = cancel;
|
|
1524
|
+
await promise;
|
|
1525
|
+
emitEvent(EVENTS.SCROLL_END, currentStep, { scroll: scrollData });
|
|
1526
|
+
store.current.updateState({ scrolling: false });
|
|
1527
|
+
};
|
|
1528
|
+
handleScroll().catch(() => {
|
|
1529
|
+
store.current.updateState({ scrolling: false });
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
}, [
|
|
1533
|
+
emitEvent,
|
|
1534
|
+
index,
|
|
1535
|
+
lifecycle,
|
|
1536
|
+
positioned,
|
|
1537
|
+
scrolling,
|
|
1538
|
+
status,
|
|
1539
|
+
store
|
|
1540
|
+
]);
|
|
1541
|
+
}
|
|
1542
|
+
//#endregion
|
|
1543
|
+
//#region src/hooks/useTourEngine.ts
|
|
1544
|
+
function useTourEngine(props) {
|
|
1545
|
+
const mergedProps = useMemoDeepCompare(() => mergeProps(defaultProps, props), [props]);
|
|
1546
|
+
const { debug, initialStepIndex, onEvent, run, stepIndex, steps } = mergedProps;
|
|
1547
|
+
const store = useRef(createStore(mergedProps));
|
|
1548
|
+
const state = useSyncExternalStore(store.current.subscribe, store.current.getSnapshot, store.current.getServerSnapshot);
|
|
1549
|
+
const [failures, setFailures] = useState([]);
|
|
1550
|
+
const addFailure = useCallback((failedStep, reason) => {
|
|
1551
|
+
setFailures((previous) => [...previous, {
|
|
1552
|
+
reason,
|
|
1553
|
+
step: failedStep
|
|
1554
|
+
}]);
|
|
1555
|
+
}, []);
|
|
1556
|
+
const clearFailures = useCallback(() => {
|
|
1557
|
+
setFailures([]);
|
|
1558
|
+
}, []);
|
|
1559
|
+
useDebugLogger(store, debug);
|
|
1560
|
+
const controls = useControls(store, debug, clearFailures);
|
|
1561
|
+
const emitEvent = useEventEmitter(onEvent, controls, store);
|
|
1562
|
+
const { index, size, status } = state;
|
|
1563
|
+
const previousState = usePrevious(state);
|
|
1564
|
+
const step = useMemo(() => getMergedStep(mergedProps, steps[index]), [
|
|
1565
|
+
index,
|
|
1566
|
+
mergedProps,
|
|
1567
|
+
steps
|
|
1568
|
+
]);
|
|
1569
|
+
useMount(() => {
|
|
1570
|
+
if (run && size && validateSteps(steps, debug)) controls.start(stepIndex ?? initialStepIndex);
|
|
1571
|
+
});
|
|
1572
|
+
useUpdateEffect(() => {
|
|
1573
|
+
if (run && size && status === STATUS.IDLE) store.current.updateState({ status: STATUS.READY });
|
|
1574
|
+
}, [
|
|
1575
|
+
run,
|
|
1576
|
+
size,
|
|
1577
|
+
status
|
|
1578
|
+
]);
|
|
1579
|
+
usePropSync({
|
|
1580
|
+
controls,
|
|
1581
|
+
emitEvent,
|
|
1582
|
+
props: mergedProps,
|
|
1583
|
+
state,
|
|
1584
|
+
store
|
|
1585
|
+
});
|
|
1586
|
+
useLifecycleEffect({
|
|
1587
|
+
addFailure,
|
|
1588
|
+
controls,
|
|
1589
|
+
emitEvent,
|
|
1590
|
+
previousState,
|
|
1591
|
+
props: mergedProps,
|
|
1592
|
+
state,
|
|
1593
|
+
step,
|
|
1594
|
+
store
|
|
1595
|
+
});
|
|
1596
|
+
useScrollEffect({
|
|
1597
|
+
emitEvent,
|
|
1598
|
+
previousState,
|
|
1599
|
+
props: mergedProps,
|
|
1600
|
+
state,
|
|
1601
|
+
step,
|
|
1602
|
+
store
|
|
1603
|
+
});
|
|
1604
|
+
return {
|
|
1605
|
+
controls,
|
|
1606
|
+
failures,
|
|
1607
|
+
mergedProps,
|
|
1608
|
+
state,
|
|
1609
|
+
step,
|
|
1610
|
+
store
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
//#endregion
|
|
1614
|
+
//#region src/hooks/usePortalElement.ts
|
|
1615
|
+
function usePortalElement(portalElement) {
|
|
1616
|
+
const [element, setElement] = useState(null);
|
|
1617
|
+
useEffect(() => {
|
|
1618
|
+
let createdElement = null;
|
|
1619
|
+
let isExternal = false;
|
|
1620
|
+
if (portalElement) if (is.domElement(portalElement)) {
|
|
1621
|
+
createdElement = portalElement;
|
|
1622
|
+
isExternal = true;
|
|
1623
|
+
} else {
|
|
1624
|
+
const portal = document.querySelector(portalElement);
|
|
1625
|
+
if (portal) createdElement = portal;
|
|
1626
|
+
}
|
|
1627
|
+
else {
|
|
1628
|
+
const portal = document.createElement("div");
|
|
1629
|
+
portal.id = PORTAL_ELEMENT_ID;
|
|
1630
|
+
document.body.appendChild(portal);
|
|
1631
|
+
createdElement = portal;
|
|
1632
|
+
}
|
|
1633
|
+
setElement(createdElement);
|
|
1634
|
+
return () => {
|
|
1635
|
+
if (!createdElement || isExternal) return;
|
|
1636
|
+
if (createdElement.parentNode === document.body) document.body.removeChild(createdElement);
|
|
1637
|
+
};
|
|
1638
|
+
}, [portalElement]);
|
|
1639
|
+
return element;
|
|
1640
|
+
}
|
|
1641
|
+
//#endregion
|
|
1642
|
+
//#region src/components/Loader.tsx
|
|
1643
|
+
const spinnerStyles = {
|
|
1644
|
+
animation: "joyride-loader-spin 1s linear infinite",
|
|
1645
|
+
border: "5px solid rgba(0, 0, 0, 0.1)",
|
|
1646
|
+
borderRadius: "50%",
|
|
1647
|
+
borderTopColor: "#555"
|
|
1648
|
+
};
|
|
1649
|
+
function JoyrideLoader({ nonce, step }) {
|
|
1650
|
+
const { loaderComponent } = step;
|
|
1651
|
+
const hasLoaderComponent = Boolean(loaderComponent);
|
|
1652
|
+
useEffect(() => {
|
|
1653
|
+
if (hasLoaderComponent) return noop;
|
|
1654
|
+
if (document.getElementById("joyride-loader-animation")) return noop;
|
|
1655
|
+
const style = document.createElement("style");
|
|
1656
|
+
style.id = "joyride-loader-animation";
|
|
1657
|
+
if (nonce) style.setAttribute("nonce", nonce);
|
|
1658
|
+
style.appendChild(document.createTextNode(`
|
|
1659
|
+
@keyframes joyride-loader-spin {
|
|
1660
|
+
to { transform: rotate(360deg); }
|
|
1661
|
+
}
|
|
1662
|
+
`));
|
|
1663
|
+
document.head.appendChild(style);
|
|
1664
|
+
return () => {
|
|
1665
|
+
const insertedStyle = document.getElementById("joyride-loader-animation");
|
|
1666
|
+
if (insertedStyle?.parentNode) insertedStyle.parentNode.removeChild(insertedStyle);
|
|
1667
|
+
};
|
|
1668
|
+
}, [hasLoaderComponent, nonce]);
|
|
1669
|
+
if (loaderComponent === null) return null;
|
|
1670
|
+
const { height, width, ...loaderStyle } = step.styles.loader;
|
|
1671
|
+
let content;
|
|
1672
|
+
if (loaderComponent) {
|
|
1673
|
+
const CustomLoader = loaderComponent;
|
|
1674
|
+
content = /* @__PURE__ */ React.createElement(CustomLoader, { step });
|
|
1675
|
+
} else content = /* @__PURE__ */ React.createElement("div", { style: {
|
|
1676
|
+
...spinnerStyles,
|
|
1677
|
+
height,
|
|
1678
|
+
width,
|
|
1679
|
+
borderTopColor: step.primaryColor
|
|
1680
|
+
} });
|
|
1681
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
1682
|
+
className: "react-joyride__loader",
|
|
1683
|
+
"data-testid": "loader",
|
|
1684
|
+
style: loaderStyle
|
|
1685
|
+
}, content);
|
|
1686
|
+
}
|
|
1687
|
+
//#endregion
|
|
1688
|
+
//#region src/hooks/useTargetPosition.ts
|
|
1689
|
+
const defaultRect = {
|
|
1690
|
+
height: 0,
|
|
1691
|
+
isFixed: false,
|
|
1692
|
+
left: 0,
|
|
1693
|
+
top: 0,
|
|
1694
|
+
width: 0
|
|
1695
|
+
};
|
|
1696
|
+
function computeRect(target, spotlightPadding) {
|
|
1697
|
+
const element = getElement(target);
|
|
1698
|
+
if (!element) return defaultRect;
|
|
1699
|
+
const elementRect = getClientRect(element);
|
|
1700
|
+
const isFixed = hasPosition(element);
|
|
1701
|
+
const top = getElementPosition(element, spotlightPadding.top, isFixed);
|
|
1702
|
+
return {
|
|
1703
|
+
height: Math.round((elementRect?.height ?? 0) + spotlightPadding.top + spotlightPadding.bottom),
|
|
1704
|
+
isFixed,
|
|
1705
|
+
left: Math.round((elementRect?.left ?? 0) - spotlightPadding.left),
|
|
1706
|
+
top,
|
|
1707
|
+
width: Math.round((elementRect?.width ?? 0) + spotlightPadding.left + spotlightPadding.right)
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1710
|
+
function useTargetPosition(target, spotlightPadding, force) {
|
|
1711
|
+
const [rect, setRect] = useState(() => computeRect(target, spotlightPadding));
|
|
1712
|
+
const timeoutRef = useRef(void 0);
|
|
1713
|
+
const scrollParentRef = useRef(null);
|
|
1714
|
+
const previousForceRef = useRef(force);
|
|
1715
|
+
const observerRef = useRef(null);
|
|
1716
|
+
const updateRect = useCallback(() => {
|
|
1717
|
+
clearTimeout(timeoutRef.current);
|
|
1718
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
1719
|
+
setRect((previous) => {
|
|
1720
|
+
const next = computeRect(target, spotlightPadding);
|
|
1721
|
+
if (previous.top === next.top && previous.left === next.left && previous.width === next.width && previous.height === next.height && previous.isFixed === next.isFixed) return previous;
|
|
1722
|
+
return next;
|
|
1723
|
+
});
|
|
1724
|
+
}, 100);
|
|
1725
|
+
}, [target, spotlightPadding]);
|
|
1726
|
+
useEffect(() => {
|
|
1727
|
+
let mutationObserver = null;
|
|
1728
|
+
const setup = (element) => {
|
|
1729
|
+
scrollParentRef.current = getScrollParent(element, true);
|
|
1730
|
+
if (scrollParentRef.current) scrollParentRef.current.addEventListener("scroll", updateRect, { passive: true });
|
|
1731
|
+
window.addEventListener("scroll", updateRect, { passive: true });
|
|
1732
|
+
window.addEventListener("resize", updateRect);
|
|
1733
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
1734
|
+
observerRef.current = new ResizeObserver(updateRect);
|
|
1735
|
+
observerRef.current.observe(element);
|
|
1736
|
+
}
|
|
1737
|
+
setRect(computeRect(target, spotlightPadding));
|
|
1738
|
+
};
|
|
1739
|
+
const element = getElement(target);
|
|
1740
|
+
if (element) setup(element);
|
|
1741
|
+
else {
|
|
1742
|
+
mutationObserver = new MutationObserver(() => {
|
|
1743
|
+
const el = getElement(target);
|
|
1744
|
+
if (el) {
|
|
1745
|
+
mutationObserver?.disconnect();
|
|
1746
|
+
mutationObserver = null;
|
|
1747
|
+
setup(el);
|
|
1748
|
+
}
|
|
1749
|
+
});
|
|
1750
|
+
mutationObserver.observe(document.body, {
|
|
1751
|
+
childList: true,
|
|
1752
|
+
subtree: true
|
|
1753
|
+
});
|
|
1754
|
+
}
|
|
1755
|
+
return () => {
|
|
1756
|
+
mutationObserver?.disconnect();
|
|
1757
|
+
if (scrollParentRef.current) scrollParentRef.current.removeEventListener("scroll", updateRect);
|
|
1758
|
+
window.removeEventListener("scroll", updateRect);
|
|
1759
|
+
window.removeEventListener("resize", updateRect);
|
|
1760
|
+
observerRef.current?.disconnect();
|
|
1761
|
+
clearTimeout(timeoutRef.current);
|
|
1762
|
+
};
|
|
1763
|
+
}, [
|
|
1764
|
+
target,
|
|
1765
|
+
spotlightPadding,
|
|
1766
|
+
updateRect
|
|
1767
|
+
]);
|
|
1768
|
+
useEffect(() => {
|
|
1769
|
+
if (previousForceRef.current && !force) setRect(computeRect(target, spotlightPadding));
|
|
1770
|
+
previousForceRef.current = force;
|
|
1771
|
+
}, [
|
|
1772
|
+
force,
|
|
1773
|
+
target,
|
|
1774
|
+
spotlightPadding
|
|
1775
|
+
]);
|
|
1776
|
+
let finalRect = rect;
|
|
1777
|
+
if (previousForceRef.current && !force) finalRect = computeRect(target, spotlightPadding);
|
|
1778
|
+
return finalRect;
|
|
1779
|
+
}
|
|
1780
|
+
//#endregion
|
|
1781
|
+
//#region src/modules/svg.ts
|
|
1782
|
+
function generateOverlayPath(overlayWidth, overlayHeight, cutout) {
|
|
1783
|
+
let path = `M0 0H${overlayWidth}V${overlayHeight}H0Z`;
|
|
1784
|
+
if (cutout) path += ` ${cutout}`;
|
|
1785
|
+
return path;
|
|
1786
|
+
}
|
|
1787
|
+
function generateSpotlightPath(x, y, width, height, borderRadius) {
|
|
1788
|
+
if (width <= 0 || height <= 0) return "";
|
|
1789
|
+
const r = Math.max(0, Math.min(borderRadius, width / 2, height / 2));
|
|
1790
|
+
let path = `M${x + r} ${y}`;
|
|
1791
|
+
path += `H${x + width - r}`;
|
|
1792
|
+
path += `A${r} ${r} 0 0 1 ${x + width} ${y + r}`;
|
|
1793
|
+
path += `V${y + height - r}`;
|
|
1794
|
+
path += `A${r} ${r} 0 0 1 ${x + width - r} ${y + height}`;
|
|
1795
|
+
path += `H${x + r}`;
|
|
1796
|
+
path += `A${r} ${r} 0 0 1 ${x} ${y + height - r}`;
|
|
1797
|
+
path += `V${y + r}`;
|
|
1798
|
+
path += `A${r} ${r} 0 0 1 ${x + r} ${y}Z`;
|
|
1799
|
+
return path;
|
|
1800
|
+
}
|
|
1801
|
+
//#endregion
|
|
1802
|
+
//#region src/components/Overlay.tsx
|
|
1803
|
+
const hiddenLifecycles = [LIFECYCLE.BEACON_BEFORE, LIFECYCLE.BEACON];
|
|
1804
|
+
function JoyrideOverlay(props) {
|
|
1805
|
+
const { blockTargetInteraction, continuous, hideOverlay, lifecycle, onClickOverlay, overlayClickAction, placement, portalElement, scrolling, spotlightPadding, spotlightRadius, spotlightTarget, styles, target, waiting } = props;
|
|
1806
|
+
const windowSize = useWindowSize();
|
|
1807
|
+
const targetRect = useTargetPosition(spotlightTarget ?? target, spotlightPadding, scrolling || waiting);
|
|
1808
|
+
const overlayRef = useRef(null);
|
|
1809
|
+
const showSpotlight = (lifecycle === LIFECYCLE.TOOLTIP || lifecycle === LIFECYCLE.TOOLTIP_BEFORE) && placement !== "center";
|
|
1810
|
+
const [spotlightReady, setSpotlightReady] = useState(false);
|
|
1811
|
+
const container = portalElement ? overlayRef.current?.offsetParent : null;
|
|
1812
|
+
const overlayWidth = container?.clientWidth ?? windowSize.width;
|
|
1813
|
+
const overlayHeight = container?.clientHeight ?? getDocumentHeight() ?? windowSize.height;
|
|
1814
|
+
const overlayColor = styles.overlay?.backgroundColor ?? "rgba(0, 0, 0, 0.5)";
|
|
1815
|
+
const overlayStyles = useMemo(() => {
|
|
1816
|
+
const { backgroundColor: _bg, mixBlendMode: _mbm, ...rest } = styles.overlay;
|
|
1817
|
+
return {
|
|
1818
|
+
height: overlayHeight,
|
|
1819
|
+
pointerEvents: "none",
|
|
1820
|
+
...rest
|
|
1821
|
+
};
|
|
1822
|
+
}, [overlayHeight, styles.overlay]);
|
|
1823
|
+
const showCutout = showSpotlight && !scrolling && !waiting;
|
|
1824
|
+
useEffect(() => {
|
|
1825
|
+
if (showCutout) requestAnimationFrame(() => setSpotlightReady(true));
|
|
1826
|
+
else setSpotlightReady(false);
|
|
1827
|
+
}, [showCutout]);
|
|
1828
|
+
const isHiddenInContinuous = continuous && hiddenLifecycles.includes(lifecycle);
|
|
1829
|
+
const isHiddenInNonContinuous = !continuous && lifecycle !== LIFECYCLE.TOOLTIP;
|
|
1830
|
+
if (hideOverlay || !waiting && (isHiddenInContinuous || isHiddenInNonContinuous)) return null;
|
|
1831
|
+
let coverPath = "";
|
|
1832
|
+
if (showCutout) if (portalElement && container) {
|
|
1833
|
+
const targetEl = getElement(spotlightTarget ?? target);
|
|
1834
|
+
if (targetEl) {
|
|
1835
|
+
const targetOffset = getAbsoluteOffset(targetEl);
|
|
1836
|
+
const containerOffset = getAbsoluteOffset(container);
|
|
1837
|
+
coverPath = generateSpotlightPath(targetOffset.left - containerOffset.left - spotlightPadding.left, targetOffset.top - containerOffset.top - spotlightPadding.top, targetEl.offsetWidth + spotlightPadding.left + spotlightPadding.right, targetEl.offsetHeight + spotlightPadding.top + spotlightPadding.bottom, spotlightRadius);
|
|
1838
|
+
}
|
|
1839
|
+
} else coverPath = generateSpotlightPath(targetRect.left, targetRect.top, targetRect.width, targetRect.height, spotlightRadius);
|
|
1840
|
+
const path = generateOverlayPath(overlayWidth, overlayHeight, coverPath);
|
|
1841
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
1842
|
+
ref: overlayRef,
|
|
1843
|
+
"aria-hidden": "true",
|
|
1844
|
+
className: "react-joyride__overlay",
|
|
1845
|
+
"data-testid": "overlay",
|
|
1846
|
+
style: overlayStyles
|
|
1847
|
+
}, /* @__PURE__ */ React.createElement("svg", {
|
|
1848
|
+
className: "react-joyride__spotlight",
|
|
1849
|
+
"data-testid": "spotlight",
|
|
1850
|
+
style: {
|
|
1851
|
+
height: overlayHeight,
|
|
1852
|
+
left: 0,
|
|
1853
|
+
position: targetRect.isFixed ? "fixed" : "absolute",
|
|
1854
|
+
top: 0,
|
|
1855
|
+
width: overlayWidth
|
|
1856
|
+
}
|
|
1857
|
+
}, /* @__PURE__ */ React.createElement("path", {
|
|
1858
|
+
d: path,
|
|
1859
|
+
fill: overlayColor,
|
|
1860
|
+
fillRule: "evenodd",
|
|
1861
|
+
onClick: onClickOverlay,
|
|
1862
|
+
style: {
|
|
1863
|
+
cursor: overlayClickAction ? "pointer" : "default",
|
|
1864
|
+
pointerEvents: "auto"
|
|
1865
|
+
}
|
|
1866
|
+
}), coverPath && /* @__PURE__ */ React.createElement("path", {
|
|
1867
|
+
d: coverPath,
|
|
1868
|
+
fill: overlayColor,
|
|
1869
|
+
style: {
|
|
1870
|
+
opacity: spotlightReady ? 0 : 1,
|
|
1871
|
+
pointerEvents: blockTargetInteraction ? "auto" : "none",
|
|
1872
|
+
transition: "opacity 0.2s"
|
|
1873
|
+
}
|
|
1874
|
+
}), coverPath && Object.keys(styles.spotlight).length > 0 && /* @__PURE__ */ React.createElement("path", {
|
|
1875
|
+
d: coverPath,
|
|
1876
|
+
fill: "none",
|
|
1877
|
+
style: { pointerEvents: "none" },
|
|
1878
|
+
...styles.spotlight
|
|
1879
|
+
})));
|
|
1880
|
+
}
|
|
1881
|
+
//#endregion
|
|
1882
|
+
//#region src/components/Portal.tsx
|
|
1883
|
+
function JoyridePortal(props) {
|
|
1884
|
+
const { children, element } = props;
|
|
1885
|
+
if (!element) return null;
|
|
1886
|
+
return createPortal(children, element);
|
|
1887
|
+
}
|
|
1888
|
+
//#endregion
|
|
1889
|
+
//#region src/hooks/useFocusTrap.ts
|
|
1890
|
+
const TABBABLE_SELECTOR = "a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), area[href], [tabindex]:not([tabindex=\"-1\"]), [contenteditable]";
|
|
1891
|
+
function useFocusTrap(element, selector) {
|
|
1892
|
+
const previousFocus = useRef(null);
|
|
1893
|
+
useEffect(() => {
|
|
1894
|
+
if (!element) return noop;
|
|
1895
|
+
previousFocus.current = document.activeElement;
|
|
1896
|
+
const handleKeyDown = (event) => {
|
|
1897
|
+
if (event.key !== "Tab") return;
|
|
1898
|
+
const elements = [...element.querySelectorAll(TABBABLE_SELECTOR)];
|
|
1899
|
+
const { shiftKey } = event;
|
|
1900
|
+
if (!elements.length) return;
|
|
1901
|
+
event.preventDefault();
|
|
1902
|
+
let index = document.activeElement ? elements.indexOf(document.activeElement) : 0;
|
|
1903
|
+
if (index === -1 || !shiftKey && index + 1 === elements.length) index = 0;
|
|
1904
|
+
else if (shiftKey && index === 0) index = elements.length - 1;
|
|
1905
|
+
else index += shiftKey ? -1 : 1;
|
|
1906
|
+
elements[index].focus();
|
|
1907
|
+
};
|
|
1908
|
+
element.addEventListener("keydown", handleKeyDown, false);
|
|
1909
|
+
let timerId;
|
|
1910
|
+
if (selector) {
|
|
1911
|
+
const target = element.querySelector(selector);
|
|
1912
|
+
if (target) timerId = setTimeout(() => {
|
|
1913
|
+
target.focus({ preventScroll: true });
|
|
1914
|
+
}, 100);
|
|
1915
|
+
}
|
|
1916
|
+
return () => {
|
|
1917
|
+
element.removeEventListener("keydown", handleKeyDown);
|
|
1918
|
+
if (timerId !== void 0) clearTimeout(timerId);
|
|
1919
|
+
previousFocus.current?.focus({ preventScroll: true });
|
|
1920
|
+
};
|
|
1921
|
+
}, [element, selector]);
|
|
1922
|
+
}
|
|
1923
|
+
//#endregion
|
|
1924
|
+
//#region src/components/Arrow.tsx
|
|
1925
|
+
function getDimensions(placement, base, size) {
|
|
1926
|
+
const [side] = placement.split("-");
|
|
1927
|
+
switch (side) {
|
|
1928
|
+
case "top":
|
|
1929
|
+
case "bottom": return {
|
|
1930
|
+
width: base,
|
|
1931
|
+
height: size
|
|
1932
|
+
};
|
|
1933
|
+
case "left":
|
|
1934
|
+
case "right": return {
|
|
1935
|
+
width: size,
|
|
1936
|
+
height: base
|
|
1937
|
+
};
|
|
1938
|
+
default: return null;
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
function getPoints(placement, base, size) {
|
|
1942
|
+
const [side] = placement.split("-");
|
|
1943
|
+
switch (side) {
|
|
1944
|
+
case "top": return {
|
|
1945
|
+
points: `0,0 ${base / 2},${size} ${base},0`,
|
|
1946
|
+
...getDimensions(placement, base, size)
|
|
1947
|
+
};
|
|
1948
|
+
case "bottom": return {
|
|
1949
|
+
points: `${base},${size} ${base / 2},0 0,${size}`,
|
|
1950
|
+
...getDimensions(placement, base, size)
|
|
1951
|
+
};
|
|
1952
|
+
case "left": return {
|
|
1953
|
+
points: `0,0 ${size},${base / 2} 0,${base}`,
|
|
1954
|
+
...getDimensions(placement, base, size)
|
|
1955
|
+
};
|
|
1956
|
+
case "right": return {
|
|
1957
|
+
points: `${size},${base} ${size},0 0,${base / 2}`,
|
|
1958
|
+
...getDimensions(placement, base, size)
|
|
1959
|
+
};
|
|
1960
|
+
default: return null;
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
function getPositionStyle(placement, position, size, base) {
|
|
1964
|
+
if (!position) return {};
|
|
1965
|
+
const [side] = placement.split("-");
|
|
1966
|
+
switch (side) {
|
|
1967
|
+
case "top": return {
|
|
1968
|
+
bottom: -size,
|
|
1969
|
+
left: position.x ?? 0,
|
|
1970
|
+
...getDimensions(placement, base, size)
|
|
1971
|
+
};
|
|
1972
|
+
case "bottom": return {
|
|
1973
|
+
top: -size,
|
|
1974
|
+
left: position.x ?? 0,
|
|
1975
|
+
...getDimensions(placement, base, size)
|
|
1976
|
+
};
|
|
1977
|
+
case "left": return {
|
|
1978
|
+
right: -size,
|
|
1979
|
+
top: position.y ?? 0,
|
|
1980
|
+
...getDimensions(placement, base, size)
|
|
1981
|
+
};
|
|
1982
|
+
case "right": return {
|
|
1983
|
+
left: -size,
|
|
1984
|
+
top: position.y ?? 0,
|
|
1985
|
+
...getDimensions(placement, base, size)
|
|
1986
|
+
};
|
|
1987
|
+
default: return {};
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
function Arrow({ arrowComponent, arrowRef, base, placement, position, size, styles }) {
|
|
1991
|
+
const ArrowComponent = arrowComponent;
|
|
1992
|
+
let content = null;
|
|
1993
|
+
if (ArrowComponent) {
|
|
1994
|
+
if (!getDimensions(placement, base, size)) return null;
|
|
1995
|
+
content = /* @__PURE__ */ React.createElement("span", { style: { flexShrink: 0 } }, /* @__PURE__ */ React.createElement(ArrowComponent, {
|
|
1996
|
+
base,
|
|
1997
|
+
placement,
|
|
1998
|
+
size
|
|
1999
|
+
}));
|
|
2000
|
+
} else {
|
|
2001
|
+
const svg = getPoints(placement, base, size);
|
|
2002
|
+
if (!svg) return null;
|
|
2003
|
+
content = /* @__PURE__ */ React.createElement("svg", {
|
|
2004
|
+
height: svg.height,
|
|
2005
|
+
width: svg.width,
|
|
2006
|
+
xmlns: "http://www.w3.org/2000/svg"
|
|
2007
|
+
}, /* @__PURE__ */ React.createElement("polygon", {
|
|
2008
|
+
fill: "currentColor",
|
|
2009
|
+
points: svg.points
|
|
2010
|
+
}));
|
|
2011
|
+
}
|
|
2012
|
+
return /* @__PURE__ */ React.createElement("span", {
|
|
2013
|
+
ref: arrowRef,
|
|
2014
|
+
className: "react-joyride__arrow",
|
|
2015
|
+
"data-testid": "arrow",
|
|
2016
|
+
style: {
|
|
2017
|
+
...styles,
|
|
2018
|
+
...getPositionStyle(placement, position, size, base),
|
|
2019
|
+
...position ? {} : { visibility: "hidden" }
|
|
2020
|
+
}
|
|
2021
|
+
}, content);
|
|
2022
|
+
}
|
|
2023
|
+
//#endregion
|
|
2024
|
+
//#region src/components/Beacon.tsx
|
|
2025
|
+
function JoyrideBeacon(props) {
|
|
2026
|
+
const { beaconComponent, continuous, index, isLastStep, locale, nonce, onInteract, shouldFocus, size, step, styles } = props;
|
|
2027
|
+
const beaconRef = useRef(null);
|
|
2028
|
+
const hasBeaconComponent = Boolean(beaconComponent);
|
|
2029
|
+
useEffect(() => {
|
|
2030
|
+
if (hasBeaconComponent) return noop;
|
|
2031
|
+
if (document.getElementById("joyride-beacon-animation")) return noop;
|
|
2032
|
+
const style = document.createElement("style");
|
|
2033
|
+
style.id = "joyride-beacon-animation";
|
|
2034
|
+
if (nonce) style.setAttribute("nonce", nonce);
|
|
2035
|
+
style.appendChild(document.createTextNode(`
|
|
2036
|
+
@keyframes joyride-beacon-inner {
|
|
2037
|
+
20% {
|
|
2038
|
+
opacity: 0.9;
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
90% {
|
|
2042
|
+
opacity: 0.7;
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
@keyframes joyride-beacon-outer {
|
|
2047
|
+
0% {
|
|
2048
|
+
transform: scale(1);
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
45% {
|
|
2052
|
+
opacity: 0.7;
|
|
2053
|
+
transform: scale(0.75);
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
100% {
|
|
2057
|
+
opacity: 0.9;
|
|
2058
|
+
transform: scale(1);
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
`));
|
|
2062
|
+
document.head.appendChild(style);
|
|
2063
|
+
const focusTimer = setTimeout(() => {
|
|
2064
|
+
if (is.domElement(beaconRef.current) && shouldFocus) beaconRef.current.focus();
|
|
2065
|
+
}, 0);
|
|
2066
|
+
return () => {
|
|
2067
|
+
clearTimeout(focusTimer);
|
|
2068
|
+
const insertedStyle = document.getElementById("joyride-beacon-animation");
|
|
2069
|
+
if (insertedStyle?.parentNode) insertedStyle.parentNode.removeChild(insertedStyle);
|
|
2070
|
+
};
|
|
2071
|
+
}, [
|
|
2072
|
+
hasBeaconComponent,
|
|
2073
|
+
nonce,
|
|
2074
|
+
shouldFocus
|
|
2075
|
+
]);
|
|
2076
|
+
const title = getReactNodeText(locale.open);
|
|
2077
|
+
let content;
|
|
2078
|
+
if (beaconComponent) {
|
|
2079
|
+
const BeaconComponent = beaconComponent;
|
|
2080
|
+
content = /* @__PURE__ */ React.createElement(BeaconComponent, {
|
|
2081
|
+
continuous,
|
|
2082
|
+
index,
|
|
2083
|
+
isLastStep,
|
|
2084
|
+
size,
|
|
2085
|
+
step
|
|
2086
|
+
});
|
|
2087
|
+
} else content = /* @__PURE__ */ React.createElement("span", { style: styles.beacon }, /* @__PURE__ */ React.createElement("span", { style: styles.beaconOuter }), /* @__PURE__ */ React.createElement("span", { style: styles.beaconInner }));
|
|
2088
|
+
return /* @__PURE__ */ React.createElement("button", {
|
|
2089
|
+
ref: beaconRef,
|
|
2090
|
+
"aria-label": title,
|
|
2091
|
+
className: "react-joyride__beacon",
|
|
2092
|
+
"data-testid": "button-beacon",
|
|
2093
|
+
onClick: onInteract,
|
|
2094
|
+
onMouseEnter: onInteract,
|
|
2095
|
+
style: styles.beaconWrapper,
|
|
2096
|
+
title,
|
|
2097
|
+
type: "button"
|
|
2098
|
+
}, content);
|
|
2099
|
+
}
|
|
2100
|
+
//#endregion
|
|
2101
|
+
//#region src/components/Tooltip/CloseButton.tsx
|
|
2102
|
+
function JoyrideTooltipCloseButton({ styles, ...props }) {
|
|
2103
|
+
const { color, height, width, ...style } = styles;
|
|
2104
|
+
return /* @__PURE__ */ React.createElement("button", {
|
|
2105
|
+
style,
|
|
2106
|
+
type: "button",
|
|
2107
|
+
...props
|
|
2108
|
+
}, /* @__PURE__ */ React.createElement("svg", {
|
|
2109
|
+
height: typeof height === "number" ? `${height}px` : height,
|
|
2110
|
+
preserveAspectRatio: "xMidYMid",
|
|
2111
|
+
version: "1.1",
|
|
2112
|
+
viewBox: "0 0 18 18",
|
|
2113
|
+
width: typeof width === "number" ? `${width}px` : width,
|
|
2114
|
+
xmlns: "http://www.w3.org/2000/svg"
|
|
2115
|
+
}, /* @__PURE__ */ React.createElement("g", null, /* @__PURE__ */ React.createElement("path", {
|
|
2116
|
+
d: "M8.13911129,9.00268191 L0.171521827,17.0258467 C-0.0498027049,17.248715 -0.0498027049,17.6098394 0.171521827,17.8327545 C0.28204354,17.9443526 0.427188206,17.9998706 0.572051765,17.9998706 C0.71714958,17.9998706 0.862013139,17.9443526 0.972581703,17.8327545 L9.0000937,9.74924618 L17.0276057,17.8327545 C17.1384085,17.9443526 17.2832721,17.9998706 17.4281356,17.9998706 C17.5729992,17.9998706 17.718097,17.9443526 17.8286656,17.8327545 C18.0499901,17.6098862 18.0499901,17.2487618 17.8286656,17.0258467 L9.86135722,9.00268191 L17.8340066,0.973848225 C18.0553311,0.750979934 18.0553311,0.389855532 17.8340066,0.16694039 C17.6126821,-0.0556467968 17.254037,-0.0556467968 17.0329467,0.16694039 L9.00042166,8.25611765 L0.967006424,0.167268345 C0.745681892,-0.0553188426 0.387317931,-0.0553188426 0.165993399,0.167268345 C-0.0553311331,0.390136635 -0.0553311331,0.751261038 0.165993399,0.974176179 L8.13920499,9.00268191 L8.13911129,9.00268191 Z",
|
|
2117
|
+
fill: color
|
|
2118
|
+
}))));
|
|
2119
|
+
}
|
|
2120
|
+
//#endregion
|
|
2121
|
+
//#region src/components/Tooltip/DefaultTooltip.tsx
|
|
2122
|
+
function JoyrideDefaultTooltip(props) {
|
|
2123
|
+
const { backProps, closeProps, index, isLastStep, primaryProps, skipProps, step, tooltipProps } = props;
|
|
2124
|
+
const { buttons, content, styles, title } = step;
|
|
2125
|
+
const buttonElements = {};
|
|
2126
|
+
if (buttons.includes("primary")) buttonElements.primary = /* @__PURE__ */ React.createElement("button", {
|
|
2127
|
+
"data-testid": "button-primary",
|
|
2128
|
+
style: styles.buttonPrimary,
|
|
2129
|
+
type: "button",
|
|
2130
|
+
...primaryProps
|
|
2131
|
+
});
|
|
2132
|
+
if (buttons.includes("skip") && !isLastStep) buttonElements.skip = /* @__PURE__ */ React.createElement("button", {
|
|
2133
|
+
"aria-live": "off",
|
|
2134
|
+
"data-testid": "button-skip",
|
|
2135
|
+
style: styles.buttonSkip,
|
|
2136
|
+
type: "button",
|
|
2137
|
+
...skipProps
|
|
2138
|
+
});
|
|
2139
|
+
if (buttons.includes("back") && index > 0) buttonElements.back = /* @__PURE__ */ React.createElement("button", {
|
|
2140
|
+
"data-testid": "button-back",
|
|
2141
|
+
style: styles.buttonBack,
|
|
2142
|
+
type: "button",
|
|
2143
|
+
...backProps
|
|
2144
|
+
});
|
|
2145
|
+
buttonElements.close = buttons.includes("close") && /* @__PURE__ */ React.createElement(JoyrideTooltipCloseButton, {
|
|
2146
|
+
"data-testid": "button-close",
|
|
2147
|
+
styles: styles.buttonClose,
|
|
2148
|
+
...closeProps
|
|
2149
|
+
});
|
|
2150
|
+
const ariaProps = title ? {
|
|
2151
|
+
"aria-labelledby": "joyride-tooltip-title",
|
|
2152
|
+
"aria-describedby": "joyride-tooltip-content"
|
|
2153
|
+
} : {
|
|
2154
|
+
"aria-label": getReactNodeText(content),
|
|
2155
|
+
"aria-describedby": "joyride-tooltip-content"
|
|
2156
|
+
};
|
|
2157
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
2158
|
+
key: "JoyrideTooltip",
|
|
2159
|
+
className: "react-joyride__tooltip",
|
|
2160
|
+
"data-joyride-step": index,
|
|
2161
|
+
...step.id && { "data-joyride-id": step.id },
|
|
2162
|
+
style: styles.tooltip,
|
|
2163
|
+
...tooltipProps,
|
|
2164
|
+
...ariaProps
|
|
2165
|
+
}, /* @__PURE__ */ React.createElement("div", { style: styles.tooltipContainer }, title && /* @__PURE__ */ React.createElement("h4", {
|
|
2166
|
+
id: "joyride-tooltip-title",
|
|
2167
|
+
style: styles.tooltipTitle
|
|
2168
|
+
}, title), /* @__PURE__ */ React.createElement("div", {
|
|
2169
|
+
id: "joyride-tooltip-content",
|
|
2170
|
+
style: styles.tooltipContent
|
|
2171
|
+
}, content)), buttons.some((b) => b === "back" || b === "primary" || b === "skip") && /* @__PURE__ */ React.createElement("div", { style: styles.tooltipFooter }, /* @__PURE__ */ React.createElement("div", { style: styles.tooltipFooterSpacer }, buttonElements.skip), buttonElements.back, buttonElements.primary), buttonElements.close);
|
|
2172
|
+
}
|
|
2173
|
+
//#endregion
|
|
2174
|
+
//#region src/components/Tooltip/index.tsx
|
|
2175
|
+
function Tooltip(props) {
|
|
2176
|
+
const { continuous, controls, index, isLastStep, size, step } = props;
|
|
2177
|
+
const handleClickBack = (event) => {
|
|
2178
|
+
event.preventDefault();
|
|
2179
|
+
controls.prev(ORIGIN.BUTTON_BACK);
|
|
2180
|
+
};
|
|
2181
|
+
const handleClickClose = (event) => {
|
|
2182
|
+
event.preventDefault();
|
|
2183
|
+
if (step.closeButtonAction === "skip") controls.skip(ORIGIN.BUTTON_CLOSE);
|
|
2184
|
+
else if (step.closeButtonAction === "replay") controls.replay(ORIGIN.BUTTON_CLOSE);
|
|
2185
|
+
else controls.close(ORIGIN.BUTTON_CLOSE);
|
|
2186
|
+
};
|
|
2187
|
+
const handleClickPrimary = (event) => {
|
|
2188
|
+
event.preventDefault();
|
|
2189
|
+
if (!continuous) {
|
|
2190
|
+
controls.close(ORIGIN.BUTTON_PRIMARY);
|
|
2191
|
+
return;
|
|
2192
|
+
}
|
|
2193
|
+
controls.next(ORIGIN.BUTTON_PRIMARY);
|
|
2194
|
+
};
|
|
2195
|
+
const handleClickSkip = (event) => {
|
|
2196
|
+
event.preventDefault();
|
|
2197
|
+
controls.skip(ORIGIN.BUTTON_SKIP);
|
|
2198
|
+
};
|
|
2199
|
+
const getElementsProps = () => {
|
|
2200
|
+
const { back, close, last, next, nextWithProgress, skip } = step.locale;
|
|
2201
|
+
const backText = getReactNodeText(back);
|
|
2202
|
+
const closeText = getReactNodeText(close);
|
|
2203
|
+
const lastText = getReactNodeText(last);
|
|
2204
|
+
const nextText = getReactNodeText(next);
|
|
2205
|
+
const skipText = getReactNodeText(skip);
|
|
2206
|
+
let primary = close;
|
|
2207
|
+
let primaryText = closeText;
|
|
2208
|
+
if (continuous) {
|
|
2209
|
+
primary = next;
|
|
2210
|
+
primaryText = nextText;
|
|
2211
|
+
if (step.showProgress && !isLastStep) {
|
|
2212
|
+
const labelWithProgress = getReactNodeText(nextWithProgress, {
|
|
2213
|
+
step: index + 1,
|
|
2214
|
+
steps: size
|
|
2215
|
+
});
|
|
2216
|
+
primary = replaceLocaleContent(nextWithProgress, index + 1, size);
|
|
2217
|
+
primaryText = labelWithProgress;
|
|
2218
|
+
}
|
|
2219
|
+
if (isLastStep) {
|
|
2220
|
+
primary = last;
|
|
2221
|
+
primaryText = lastText;
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
return {
|
|
2225
|
+
backProps: {
|
|
2226
|
+
"aria-label": backText,
|
|
2227
|
+
children: back,
|
|
2228
|
+
"data-action": "back",
|
|
2229
|
+
onClick: handleClickBack,
|
|
2230
|
+
role: "button",
|
|
2231
|
+
title: backText
|
|
2232
|
+
},
|
|
2233
|
+
closeProps: {
|
|
2234
|
+
"aria-label": closeText,
|
|
2235
|
+
children: close,
|
|
2236
|
+
"data-action": "close",
|
|
2237
|
+
onClick: handleClickClose,
|
|
2238
|
+
role: "button",
|
|
2239
|
+
title: closeText
|
|
2240
|
+
},
|
|
2241
|
+
primaryProps: {
|
|
2242
|
+
"aria-label": primaryText,
|
|
2243
|
+
children: primary,
|
|
2244
|
+
"data-action": "primary",
|
|
2245
|
+
onClick: handleClickPrimary,
|
|
2246
|
+
role: "button",
|
|
2247
|
+
title: primaryText
|
|
2248
|
+
},
|
|
2249
|
+
skipProps: {
|
|
2250
|
+
"aria-label": skipText,
|
|
2251
|
+
children: skip,
|
|
2252
|
+
"data-action": "skip",
|
|
2253
|
+
onClick: handleClickSkip,
|
|
2254
|
+
role: "button",
|
|
2255
|
+
title: skipText
|
|
2256
|
+
},
|
|
2257
|
+
tooltipProps: {
|
|
2258
|
+
"aria-modal": true,
|
|
2259
|
+
role: "alertdialog"
|
|
2260
|
+
}
|
|
2261
|
+
};
|
|
2262
|
+
};
|
|
2263
|
+
const { arrowComponent, beaconComponent, tooltipComponent, ...stepProps } = step;
|
|
2264
|
+
let component;
|
|
2265
|
+
if (tooltipComponent) {
|
|
2266
|
+
const TooltipComponent = tooltipComponent;
|
|
2267
|
+
component = /* @__PURE__ */ React.createElement(TooltipComponent, {
|
|
2268
|
+
...getElementsProps(),
|
|
2269
|
+
continuous,
|
|
2270
|
+
controls,
|
|
2271
|
+
index,
|
|
2272
|
+
isLastStep,
|
|
2273
|
+
size,
|
|
2274
|
+
step: stepProps
|
|
2275
|
+
});
|
|
2276
|
+
} else component = /* @__PURE__ */ React.createElement(JoyrideDefaultTooltip, {
|
|
2277
|
+
...getElementsProps(),
|
|
2278
|
+
continuous,
|
|
2279
|
+
controls,
|
|
2280
|
+
index,
|
|
2281
|
+
isLastStep,
|
|
2282
|
+
size,
|
|
2283
|
+
step: stepProps
|
|
2284
|
+
});
|
|
2285
|
+
return component;
|
|
2286
|
+
}
|
|
2287
|
+
//#endregion
|
|
2288
|
+
//#region src/components/Floater.tsx
|
|
2289
|
+
function getFallbackPlacements(placement) {
|
|
2290
|
+
if (placement.startsWith("left")) return ["top", "bottom"];
|
|
2291
|
+
if (placement.startsWith("right")) return ["bottom", "top"];
|
|
2292
|
+
}
|
|
2293
|
+
function getFlipMiddleware(isAuto, step, tooltipPlacement) {
|
|
2294
|
+
if (isAuto) return [autoPlacement()];
|
|
2295
|
+
if (step.floatingOptions?.flipOptions === false) return [];
|
|
2296
|
+
return [flip({
|
|
2297
|
+
crossAxis: false,
|
|
2298
|
+
fallbackPlacements: getFallbackPlacements(tooltipPlacement),
|
|
2299
|
+
padding: 20,
|
|
2300
|
+
...step.floatingOptions?.flipOptions
|
|
2301
|
+
})];
|
|
2302
|
+
}
|
|
2303
|
+
function JoyrideFloater(props) {
|
|
2304
|
+
const { continuous, controls, index, lifecycle, nonce, open, portalElement, setPositionData, setTooltipRef, shouldScroll, size, step, target, updateState } = props;
|
|
2305
|
+
const arrowRef = useRef(null);
|
|
2306
|
+
const beaconMiddlewareRef = useRef({});
|
|
2307
|
+
const tooltipMiddlewareRef = useRef({});
|
|
2308
|
+
const isCenter = step.placement === "center";
|
|
2309
|
+
const isAuto = step.placement === "auto";
|
|
2310
|
+
const centerReference = useMemo(() => ({ getBoundingClientRect: () => ({
|
|
2311
|
+
x: window.innerWidth / 2,
|
|
2312
|
+
y: window.innerHeight / 2,
|
|
2313
|
+
top: window.innerHeight / 2,
|
|
2314
|
+
left: window.innerWidth / 2,
|
|
2315
|
+
bottom: window.innerHeight / 2,
|
|
2316
|
+
right: window.innerWidth / 2,
|
|
2317
|
+
width: 0,
|
|
2318
|
+
height: 0
|
|
2319
|
+
}) }), []);
|
|
2320
|
+
const scrollParent = useMemo(() => hasCustomScrollParent(target) ? getScrollParent(target) : void 0, [target]);
|
|
2321
|
+
const isFixedTarget = useMemo(() => hasPosition(target), [target]);
|
|
2322
|
+
const boundaryOptions = useMemo(() => scrollParent ? {
|
|
2323
|
+
boundary: scrollParent,
|
|
2324
|
+
rootBoundary: "viewport"
|
|
2325
|
+
} : {}, [scrollParent]);
|
|
2326
|
+
const tooltipPlacement = isCenter || isAuto ? "bottom" : step.placement;
|
|
2327
|
+
const strategy = isCenter ? "fixed" : step.floatingOptions?.strategy ?? (step.isFixed || isFixedTarget ? "fixed" : "absolute");
|
|
2328
|
+
const tooltipMiddleware = useMemo(() => isCenter ? [{
|
|
2329
|
+
name: "center",
|
|
2330
|
+
fn: ({ rects }) => ({
|
|
2331
|
+
x: (window.innerWidth - rects.floating.width) / 2,
|
|
2332
|
+
y: (window.innerHeight - rects.floating.height) / 2
|
|
2333
|
+
})
|
|
2334
|
+
}] : [
|
|
2335
|
+
offset(({ placement: currentPlacement }) => {
|
|
2336
|
+
let side = "right";
|
|
2337
|
+
if (currentPlacement.startsWith("top")) side = "top";
|
|
2338
|
+
else if (currentPlacement.startsWith("bottom")) side = "bottom";
|
|
2339
|
+
else if (currentPlacement.startsWith("left")) side = "left";
|
|
2340
|
+
const padding = step.spotlightTarget ? 0 : step.spotlightPadding[side];
|
|
2341
|
+
return step.offset + padding + (step.floatingOptions?.hideArrow ? 0 : step.arrowSize);
|
|
2342
|
+
}, [
|
|
2343
|
+
step.offset,
|
|
2344
|
+
step.spotlightPadding,
|
|
2345
|
+
step.spotlightTarget,
|
|
2346
|
+
step.arrowSize,
|
|
2347
|
+
step.floatingOptions?.hideArrow
|
|
2348
|
+
]),
|
|
2349
|
+
...getFlipMiddleware(isAuto, step, tooltipPlacement),
|
|
2350
|
+
shift({
|
|
2351
|
+
padding: 10,
|
|
2352
|
+
...boundaryOptions,
|
|
2353
|
+
...step.floatingOptions?.shiftOptions
|
|
2354
|
+
}),
|
|
2355
|
+
...step.floatingOptions?.hideArrow ? [] : [arrow({
|
|
2356
|
+
element: arrowRef,
|
|
2357
|
+
padding: step.arrowSpacing
|
|
2358
|
+
}, [step.arrowSpacing, step.arrowBase])],
|
|
2359
|
+
...step.floatingOptions?.middleware ?? []
|
|
2360
|
+
], [
|
|
2361
|
+
isCenter,
|
|
2362
|
+
step,
|
|
2363
|
+
isAuto,
|
|
2364
|
+
tooltipPlacement,
|
|
2365
|
+
boundaryOptions
|
|
2366
|
+
]);
|
|
2367
|
+
const tooltipFloating = useFloating({
|
|
2368
|
+
...isCenter ? { elements: { reference: centerReference } } : {},
|
|
2369
|
+
placement: tooltipPlacement,
|
|
2370
|
+
strategy,
|
|
2371
|
+
middleware: tooltipMiddleware
|
|
2372
|
+
});
|
|
2373
|
+
const beaconFloating = useFloating({
|
|
2374
|
+
strategy,
|
|
2375
|
+
placement: step.beaconPlacement ?? (isAuto || isCenter ? "bottom" : step.placement),
|
|
2376
|
+
middleware: useMemo(() => [offset(step.floatingOptions?.beaconOptions?.offset ?? -18)], [step.floatingOptions?.beaconOptions?.offset]),
|
|
2377
|
+
whileElementsMounted: autoUpdate
|
|
2378
|
+
});
|
|
2379
|
+
tooltipMiddlewareRef.current = tooltipFloating.middlewareData;
|
|
2380
|
+
beaconMiddlewareRef.current = beaconFloating.middlewareData;
|
|
2381
|
+
useEffect(() => {
|
|
2382
|
+
const { floating, reference } = tooltipFloating.elements;
|
|
2383
|
+
if (!reference || !floating || lifecycle !== LIFECYCLE.TOOLTIP) return;
|
|
2384
|
+
return autoUpdate(reference, floating, tooltipFloating.update, step.floatingOptions?.autoUpdate);
|
|
2385
|
+
}, [
|
|
2386
|
+
lifecycle,
|
|
2387
|
+
tooltipFloating.update,
|
|
2388
|
+
step.floatingOptions?.autoUpdate,
|
|
2389
|
+
step.target,
|
|
2390
|
+
tooltipFloating.elements
|
|
2391
|
+
]);
|
|
2392
|
+
useEffect(() => {
|
|
2393
|
+
if (!isCenter && target) tooltipFloating.refs.setReference(target);
|
|
2394
|
+
if (target) beaconFloating.refs.setReference(target);
|
|
2395
|
+
}, [
|
|
2396
|
+
beaconFloating.refs,
|
|
2397
|
+
isCenter,
|
|
2398
|
+
target,
|
|
2399
|
+
tooltipFloating.refs
|
|
2400
|
+
]);
|
|
2401
|
+
useEffect(() => {
|
|
2402
|
+
if (tooltipFloating.isPositioned) setPositionData("tooltip", {
|
|
2403
|
+
placement: tooltipFloating.placement,
|
|
2404
|
+
x: tooltipFloating.x ?? 0,
|
|
2405
|
+
y: tooltipFloating.y ?? 0,
|
|
2406
|
+
middlewareData: tooltipMiddlewareRef.current
|
|
2407
|
+
});
|
|
2408
|
+
}, [
|
|
2409
|
+
setPositionData,
|
|
2410
|
+
tooltipFloating.isPositioned,
|
|
2411
|
+
tooltipFloating.placement,
|
|
2412
|
+
tooltipFloating.x,
|
|
2413
|
+
tooltipFloating.y
|
|
2414
|
+
]);
|
|
2415
|
+
useEffect(() => {
|
|
2416
|
+
if (beaconFloating.isPositioned) setPositionData("beacon", {
|
|
2417
|
+
placement: beaconFloating.placement,
|
|
2418
|
+
x: beaconFloating.x ?? 0,
|
|
2419
|
+
y: beaconFloating.y ?? 0,
|
|
2420
|
+
middlewareData: beaconMiddlewareRef.current
|
|
2421
|
+
});
|
|
2422
|
+
}, [
|
|
2423
|
+
setPositionData,
|
|
2424
|
+
beaconFloating.isPositioned,
|
|
2425
|
+
beaconFloating.placement,
|
|
2426
|
+
beaconFloating.x,
|
|
2427
|
+
beaconFloating.y
|
|
2428
|
+
]);
|
|
2429
|
+
const zIndex = step.zIndex + 1;
|
|
2430
|
+
const handleBeaconInteraction = useCallback((event) => {
|
|
2431
|
+
if (event.type === "mouseenter" && step.beaconTrigger !== "hover") return;
|
|
2432
|
+
updateState({
|
|
2433
|
+
lifecycle: LIFECYCLE.TOOLTIP_BEFORE,
|
|
2434
|
+
positioned: false
|
|
2435
|
+
});
|
|
2436
|
+
}, [step.beaconTrigger, updateState]);
|
|
2437
|
+
const floaterRef = useCallback((node) => {
|
|
2438
|
+
if (node) {
|
|
2439
|
+
tooltipFloating.refs.setFloating(node);
|
|
2440
|
+
setTooltipRef(node);
|
|
2441
|
+
}
|
|
2442
|
+
}, [tooltipFloating.refs, setTooltipRef]);
|
|
2443
|
+
const { arrow: arrowStyles, floater: floaterStyles } = step.styles;
|
|
2444
|
+
let content = null;
|
|
2445
|
+
if (lifecycle === LIFECYCLE.TOOLTIP || lifecycle === LIFECYCLE.TOOLTIP_BEFORE) {
|
|
2446
|
+
const styles = sortObjectKeys({
|
|
2447
|
+
...floaterStyles,
|
|
2448
|
+
...tooltipFloating.floatingStyles,
|
|
2449
|
+
zIndex,
|
|
2450
|
+
opacity: open && tooltipFloating.isPositioned ? 1 : 0,
|
|
2451
|
+
...!open && { transition: "none" }
|
|
2452
|
+
});
|
|
2453
|
+
content = /* @__PURE__ */ React.createElement("div", {
|
|
2454
|
+
ref: floaterRef,
|
|
2455
|
+
className: "react-joyride__floater",
|
|
2456
|
+
"data-testid": "floater",
|
|
2457
|
+
id: `react-joyride-step-${index}`,
|
|
2458
|
+
style: styles
|
|
2459
|
+
}, /* @__PURE__ */ React.createElement(Tooltip, {
|
|
2460
|
+
continuous,
|
|
2461
|
+
controls,
|
|
2462
|
+
index,
|
|
2463
|
+
isLastStep: index + 1 === size,
|
|
2464
|
+
size,
|
|
2465
|
+
step
|
|
2466
|
+
}), !isCenter && !step.floatingOptions?.hideArrow && /* @__PURE__ */ React.createElement(Arrow, {
|
|
2467
|
+
arrowComponent: step.arrowComponent,
|
|
2468
|
+
arrowRef,
|
|
2469
|
+
base: step.arrowBase,
|
|
2470
|
+
placement: tooltipFloating.placement,
|
|
2471
|
+
position: tooltipFloating.middlewareData.arrow,
|
|
2472
|
+
size: step.arrowSize,
|
|
2473
|
+
styles: arrowStyles
|
|
2474
|
+
}));
|
|
2475
|
+
} else if (lifecycle === LIFECYCLE.BEACON || lifecycle === LIFECYCLE.BEACON_BEFORE) content = /* @__PURE__ */ React.createElement("div", {
|
|
2476
|
+
ref: beaconFloating.refs.setFloating,
|
|
2477
|
+
className: "react-joyride__floater",
|
|
2478
|
+
"data-testid": "floater-beacon",
|
|
2479
|
+
id: `react-joyride-step-${index}-beacon`,
|
|
2480
|
+
style: sortObjectKeys({
|
|
2481
|
+
...beaconFloating.floatingStyles,
|
|
2482
|
+
zIndex
|
|
2483
|
+
})
|
|
2484
|
+
}, /* @__PURE__ */ React.createElement(JoyrideBeacon, {
|
|
2485
|
+
beaconComponent: step.beaconComponent,
|
|
2486
|
+
continuous,
|
|
2487
|
+
index,
|
|
2488
|
+
isLastStep: index + 1 === size,
|
|
2489
|
+
locale: step.locale,
|
|
2490
|
+
nonce,
|
|
2491
|
+
onInteract: handleBeaconInteraction,
|
|
2492
|
+
shouldFocus: shouldScroll,
|
|
2493
|
+
size,
|
|
2494
|
+
step,
|
|
2495
|
+
styles: step.styles
|
|
2496
|
+
}));
|
|
2497
|
+
return /* @__PURE__ */ React.createElement(JoyridePortal, { element: portalElement }, content);
|
|
2498
|
+
}
|
|
2499
|
+
//#endregion
|
|
2500
|
+
//#region src/components/Step.tsx
|
|
2501
|
+
function JoyrideStep(props) {
|
|
2502
|
+
const { continuous, controls, index, lifecycle, nonce, portalElement, setPositionData, shouldScroll, size, step, updateState } = props;
|
|
2503
|
+
const [tooltipElement, setTooltipElement] = useState(null);
|
|
2504
|
+
useFocusTrap(step.disableFocusTrap ? null : tooltipElement, "[data-action=primary]");
|
|
2505
|
+
const target = getElement(step.target);
|
|
2506
|
+
const open = lifecycle === LIFECYCLE.TOOLTIP;
|
|
2507
|
+
if (!validateStep(step) || !is.domElement(target)) return null;
|
|
2508
|
+
return /* @__PURE__ */ React.createElement(JoyrideFloater, {
|
|
2509
|
+
key: `JoyrideStep-${index}`,
|
|
2510
|
+
continuous,
|
|
2511
|
+
controls,
|
|
2512
|
+
index,
|
|
2513
|
+
lifecycle,
|
|
2514
|
+
nonce,
|
|
2515
|
+
open,
|
|
2516
|
+
portalElement,
|
|
2517
|
+
setPositionData,
|
|
2518
|
+
setTooltipRef: setTooltipElement,
|
|
2519
|
+
shouldScroll,
|
|
2520
|
+
size,
|
|
2521
|
+
step,
|
|
2522
|
+
target,
|
|
2523
|
+
updateState
|
|
2524
|
+
});
|
|
2525
|
+
}
|
|
2526
|
+
//#endregion
|
|
2527
|
+
//#region src/components/TourRenderer.tsx
|
|
2528
|
+
function TourRenderer({ controls, mergedProps, state, step, store }) {
|
|
2529
|
+
const { continuous, debug, nonce, portalElement, scrollToFirstStep } = mergedProps;
|
|
2530
|
+
const element = usePortalElement(portalElement);
|
|
2531
|
+
const { index, lifecycle, status } = state;
|
|
2532
|
+
const isRunning = status === STATUS.RUNNING;
|
|
2533
|
+
const [showLoader, setShowLoader] = useState(false);
|
|
2534
|
+
const loaderTimerRef = useRef(null);
|
|
2535
|
+
const loaderDelay = step?.loaderDelay ?? 0;
|
|
2536
|
+
useEffect(() => {
|
|
2537
|
+
if (state.waiting) if (loaderDelay === 0) setShowLoader(true);
|
|
2538
|
+
else loaderTimerRef.current = setTimeout(() => {
|
|
2539
|
+
setShowLoader(true);
|
|
2540
|
+
}, loaderDelay);
|
|
2541
|
+
else setShowLoader(false);
|
|
2542
|
+
return () => {
|
|
2543
|
+
if (loaderTimerRef.current) {
|
|
2544
|
+
clearTimeout(loaderTimerRef.current);
|
|
2545
|
+
loaderTimerRef.current = null;
|
|
2546
|
+
}
|
|
2547
|
+
};
|
|
2548
|
+
}, [loaderDelay, state.waiting]);
|
|
2549
|
+
useEffect(() => {
|
|
2550
|
+
if (!isRunning) return;
|
|
2551
|
+
const handleKeyboard = (event) => {
|
|
2552
|
+
if (!step || lifecycle !== LIFECYCLE.TOOLTIP) return;
|
|
2553
|
+
if (event.key === "Escape" && step.dismissKeyAction) if (step.dismissKeyAction === "next") controls.next(ORIGIN.KEYBOARD);
|
|
2554
|
+
else if (step.dismissKeyAction === "replay") controls.replay(ORIGIN.KEYBOARD);
|
|
2555
|
+
else controls.close(ORIGIN.KEYBOARD);
|
|
2556
|
+
};
|
|
2557
|
+
document.body.addEventListener("keydown", handleKeyboard, { passive: true });
|
|
2558
|
+
return () => {
|
|
2559
|
+
document.body.removeEventListener("keydown", handleKeyboard);
|
|
2560
|
+
};
|
|
2561
|
+
}, [
|
|
2562
|
+
controls,
|
|
2563
|
+
isRunning,
|
|
2564
|
+
lifecycle,
|
|
2565
|
+
step
|
|
2566
|
+
]);
|
|
2567
|
+
const handleClickOverlay = useCallback(() => {
|
|
2568
|
+
switch (step?.overlayClickAction) {
|
|
2569
|
+
case "close":
|
|
2570
|
+
controls.close(ORIGIN.OVERLAY);
|
|
2571
|
+
break;
|
|
2572
|
+
case "next":
|
|
2573
|
+
controls.next(ORIGIN.OVERLAY);
|
|
2574
|
+
break;
|
|
2575
|
+
case "replay":
|
|
2576
|
+
controls.replay(ORIGIN.OVERLAY);
|
|
2577
|
+
break;
|
|
2578
|
+
}
|
|
2579
|
+
}, [controls, step?.overlayClickAction]);
|
|
2580
|
+
if (!step || !isRunning) return null;
|
|
2581
|
+
const hideOverlay = state.action === ACTIONS.START && !step.skipBeacon && step.placement !== "center";
|
|
2582
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, lifecycle !== LIFECYCLE.INIT && /* @__PURE__ */ React.createElement(JoyrideStep, {
|
|
2583
|
+
...state,
|
|
2584
|
+
continuous,
|
|
2585
|
+
controls,
|
|
2586
|
+
debug,
|
|
2587
|
+
nonce,
|
|
2588
|
+
portalElement: element,
|
|
2589
|
+
setPositionData: store.current.setPositionData,
|
|
2590
|
+
shouldScroll: !step.skipScroll && (index !== 0 || scrollToFirstStep),
|
|
2591
|
+
step,
|
|
2592
|
+
updateState: store.current.updateState
|
|
2593
|
+
}), /* @__PURE__ */ React.createElement(JoyridePortal, { element }, /* @__PURE__ */ React.createElement(React.Fragment, null, showLoader && /* @__PURE__ */ React.createElement(JoyrideLoader, {
|
|
2594
|
+
nonce,
|
|
2595
|
+
step
|
|
2596
|
+
}), !hideOverlay && /* @__PURE__ */ React.createElement(JoyrideOverlay, {
|
|
2597
|
+
...step,
|
|
2598
|
+
continuous,
|
|
2599
|
+
lifecycle,
|
|
2600
|
+
onClickOverlay: handleClickOverlay,
|
|
2601
|
+
portalElement: portalElement ? element : null,
|
|
2602
|
+
scrolling: state.scrolling,
|
|
2603
|
+
waiting: state.waiting
|
|
2604
|
+
}))));
|
|
2605
|
+
}
|
|
2606
|
+
//#endregion
|
|
2607
|
+
//#region src/hooks/useJoyride.tsx
|
|
2608
|
+
function useJoyride(props) {
|
|
2609
|
+
const { controls, failures, mergedProps, state, step, store } = useTourEngine(props);
|
|
2610
|
+
return {
|
|
2611
|
+
controls,
|
|
2612
|
+
failures,
|
|
2613
|
+
on: useCallback((eventType, handler) => store.current.on(eventType, handler), [store]),
|
|
2614
|
+
state: useMemo(() => omit(state, "positioned"), [state]),
|
|
2615
|
+
step,
|
|
2616
|
+
Tour: canUseDOM() ? /* @__PURE__ */ React.createElement(TourRenderer, {
|
|
2617
|
+
controls,
|
|
2618
|
+
mergedProps,
|
|
2619
|
+
state,
|
|
2620
|
+
step,
|
|
2621
|
+
store
|
|
2622
|
+
}) : null
|
|
2623
|
+
};
|
|
2624
|
+
}
|
|
2625
|
+
//#endregion
|
|
2626
|
+
//#region src/index.tsx
|
|
2627
|
+
function JoyrideTour(props) {
|
|
2628
|
+
const { Tour } = useJoyride(props);
|
|
2629
|
+
return Tour;
|
|
2630
|
+
}
|
|
2631
|
+
function Joyride(props) {
|
|
2632
|
+
if (!canUseDOM()) return null;
|
|
2633
|
+
return /* @__PURE__ */ React.createElement(JoyrideTour, props);
|
|
2634
|
+
}
|
|
2635
|
+
//#endregion
|
|
2636
|
+
export { ACTIONS, EVENTS, Joyride, LIFECYCLE, ORIGIN, PORTAL_ELEMENT_ID, STATUS, defaultLocale, defaultOptions, useJoyride };
|
|
2637
|
+
|
|
2638
|
+
//# sourceMappingURL=index.mjs.map
|