@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.
Files changed (51) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +93 -0
  3. package/dist/index.cjs +2677 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +753 -0
  6. package/dist/index.d.mts +753 -0
  7. package/dist/index.mjs +2638 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/package.json +158 -0
  10. package/src/components/Arrow.tsx +138 -0
  11. package/src/components/Beacon.tsx +141 -0
  12. package/src/components/Floater.tsx +381 -0
  13. package/src/components/Loader.tsx +80 -0
  14. package/src/components/Overlay.tsx +167 -0
  15. package/src/components/Portal.tsx +17 -0
  16. package/src/components/Step.tsx +72 -0
  17. package/src/components/Tooltip/CloseButton.tsx +29 -0
  18. package/src/components/Tooltip/DefaultTooltip.tsx +82 -0
  19. package/src/components/Tooltip/index.tsx +163 -0
  20. package/src/components/TourRenderer.tsx +157 -0
  21. package/src/defaults.ts +64 -0
  22. package/src/global.d.ts +8 -0
  23. package/src/hooks/useControls.ts +219 -0
  24. package/src/hooks/useDebugLogger.ts +58 -0
  25. package/src/hooks/useEventEmitter.ts +55 -0
  26. package/src/hooks/useFocusTrap.ts +72 -0
  27. package/src/hooks/useJoyride.tsx +32 -0
  28. package/src/hooks/useLifecycleEffect.ts +512 -0
  29. package/src/hooks/usePortalElement.ts +49 -0
  30. package/src/hooks/usePropSync.ts +84 -0
  31. package/src/hooks/useScrollEffect.ts +217 -0
  32. package/src/hooks/useTargetPosition.ts +154 -0
  33. package/src/hooks/useTourEngine.ts +106 -0
  34. package/src/index.tsx +23 -0
  35. package/src/literals/index.ts +61 -0
  36. package/src/modules/changes.ts +20 -0
  37. package/src/modules/dom.ts +359 -0
  38. package/src/modules/helpers.tsx +230 -0
  39. package/src/modules/step.ts +156 -0
  40. package/src/modules/store.ts +215 -0
  41. package/src/modules/svg.ts +40 -0
  42. package/src/styles.ts +183 -0
  43. package/src/types/common.ts +315 -0
  44. package/src/types/components.ts +84 -0
  45. package/src/types/events.ts +89 -0
  46. package/src/types/floating.ts +60 -0
  47. package/src/types/index.ts +8 -0
  48. package/src/types/props.ts +124 -0
  49. package/src/types/state.ts +49 -0
  50. package/src/types/step.ts +108 -0
  51. 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