@flowsterix/react 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/adapters/radixDialog.d.ts +18 -0
  2. package/dist/adapters/radixDialog.d.ts.map +1 -0
  3. package/dist/chunk-RCASLQRS.mjs +24 -0
  4. package/dist/chunk-RPA2S5UP.mjs +280 -0
  5. package/dist/chunk-U757YVZP.mjs +50 -0
  6. package/dist/components/OverlayBackdrop.d.ts +34 -0
  7. package/dist/components/OverlayBackdrop.d.ts.map +1 -0
  8. package/dist/components/TourFocusManager.d.ts +11 -0
  9. package/dist/components/TourFocusManager.d.ts.map +1 -0
  10. package/dist/components/TourPopoverPortal.d.ts +57 -0
  11. package/dist/components/TourPopoverPortal.d.ts.map +1 -0
  12. package/dist/components/__tests__/TourFocusManager.test.d.ts +2 -0
  13. package/dist/components/__tests__/TourFocusManager.test.d.ts.map +1 -0
  14. package/dist/context.d.ts +56 -0
  15. package/dist/context.d.ts.map +1 -0
  16. package/dist/hooks/__tests__/scrollMargin.test.d.ts +2 -0
  17. package/dist/hooks/__tests__/scrollMargin.test.d.ts.map +1 -0
  18. package/dist/hooks/__tests__/useBodyScrollLock.test.d.ts +2 -0
  19. package/dist/hooks/__tests__/useBodyScrollLock.test.d.ts.map +1 -0
  20. package/dist/hooks/__tests__/useHiddenTargetFallback.test.d.ts +2 -0
  21. package/dist/hooks/__tests__/useHiddenTargetFallback.test.d.ts.map +1 -0
  22. package/dist/hooks/__tests__/waitForPredicate.test.d.ts +2 -0
  23. package/dist/hooks/__tests__/waitForPredicate.test.d.ts.map +1 -0
  24. package/dist/hooks/scrollMargin.d.ts +15 -0
  25. package/dist/hooks/scrollMargin.d.ts.map +1 -0
  26. package/dist/hooks/useAdvanceRules.d.ts +3 -0
  27. package/dist/hooks/useAdvanceRules.d.ts.map +1 -0
  28. package/dist/hooks/useBodyScrollLock.d.ts +2 -0
  29. package/dist/hooks/useBodyScrollLock.d.ts.map +1 -0
  30. package/dist/hooks/useDelayAdvance.d.ts +14 -0
  31. package/dist/hooks/useDelayAdvance.d.ts.map +1 -0
  32. package/dist/hooks/useHiddenTargetFallback.d.ts +16 -0
  33. package/dist/hooks/useHiddenTargetFallback.d.ts.map +1 -0
  34. package/dist/hooks/useHudDescription.d.ts +13 -0
  35. package/dist/hooks/useHudDescription.d.ts.map +1 -0
  36. package/dist/hooks/useHudShortcuts.d.ts +7 -0
  37. package/dist/hooks/useHudShortcuts.d.ts.map +1 -0
  38. package/dist/hooks/useHudState.d.ts +27 -0
  39. package/dist/hooks/useHudState.d.ts.map +1 -0
  40. package/dist/hooks/useHudTargetIssue.d.ts +21 -0
  41. package/dist/hooks/useHudTargetIssue.d.ts.map +1 -0
  42. package/dist/hooks/useTourControls.d.ts +17 -0
  43. package/dist/hooks/useTourControls.d.ts.map +1 -0
  44. package/dist/hooks/useTourFocusDominance.d.ts +9 -0
  45. package/dist/hooks/useTourFocusDominance.d.ts.map +1 -0
  46. package/dist/hooks/useTourHud.d.ts +61 -0
  47. package/dist/hooks/useTourHud.d.ts.map +1 -0
  48. package/dist/hooks/useTourOverlay.d.ts +70 -0
  49. package/dist/hooks/useTourOverlay.d.ts.map +1 -0
  50. package/dist/hooks/useTourTarget.d.ts +16 -0
  51. package/dist/hooks/useTourTarget.d.ts.map +1 -0
  52. package/dist/hooks/useViewportRect.d.ts +3 -0
  53. package/dist/hooks/useViewportRect.d.ts.map +1 -0
  54. package/dist/hooks/waitForPredicate.d.ts +15 -0
  55. package/dist/hooks/waitForPredicate.d.ts.map +1 -0
  56. package/dist/index.cjs +4207 -0
  57. package/dist/index.d.ts +45 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.mjs +3851 -0
  60. package/dist/labels.d.ts +22 -0
  61. package/dist/labels.d.ts.map +1 -0
  62. package/dist/motion/animationAdapter.d.ts +40 -0
  63. package/dist/motion/animationAdapter.d.ts.map +1 -0
  64. package/dist/motion/useHudMotion.d.ts +14 -0
  65. package/dist/motion/useHudMotion.d.ts.map +1 -0
  66. package/dist/router/index.cjs +275 -0
  67. package/dist/router/index.d.ts +5 -0
  68. package/dist/router/index.d.ts.map +1 -0
  69. package/dist/router/index.mjs +30 -0
  70. package/dist/router/nextAppRouterAdapter.cjs +224 -0
  71. package/dist/router/nextAppRouterAdapter.d.ts +2 -0
  72. package/dist/router/nextAppRouterAdapter.d.ts.map +1 -0
  73. package/dist/router/nextAppRouterAdapter.mjs +35 -0
  74. package/dist/router/nextPagesRouterAdapter.cjs +217 -0
  75. package/dist/router/nextPagesRouterAdapter.d.ts +2 -0
  76. package/dist/router/nextPagesRouterAdapter.d.ts.map +1 -0
  77. package/dist/router/nextPagesRouterAdapter.mjs +28 -0
  78. package/dist/router/reactRouterAdapter.cjs +220 -0
  79. package/dist/router/reactRouterAdapter.d.ts +2 -0
  80. package/dist/router/reactRouterAdapter.d.ts.map +1 -0
  81. package/dist/router/reactRouterAdapter.mjs +31 -0
  82. package/dist/router/routeGating.d.ts +13 -0
  83. package/dist/router/routeGating.d.ts.map +1 -0
  84. package/dist/router/tanstackRouterAdapter.cjs +202 -0
  85. package/dist/router/tanstackRouterAdapter.d.ts +2 -0
  86. package/dist/router/tanstackRouterAdapter.d.ts.map +1 -0
  87. package/dist/router/tanstackRouterAdapter.mjs +7 -0
  88. package/dist/router/tanstackRouterSync.d.ts +20 -0
  89. package/dist/router/tanstackRouterSync.d.ts.map +1 -0
  90. package/dist/router/utils.d.ts +2 -0
  91. package/dist/router/utils.d.ts.map +1 -0
  92. package/dist/utils/dom.d.ts +22 -0
  93. package/dist/utils/dom.d.ts.map +1 -0
  94. package/dist/utils/focus.d.ts +4 -0
  95. package/dist/utils/focus.d.ts.map +1 -0
  96. package/package.json +96 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,4207 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.tsx
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ AnimationAdapterProvider: () => AnimationAdapterProvider,
34
+ OverlayBackdrop: () => OverlayBackdrop,
35
+ TanStackRouterSync: () => TanStackRouterSync,
36
+ TourFocusManager: () => TourFocusManager,
37
+ TourPopoverPortal: () => TourPopoverPortal,
38
+ TourProvider: () => TourProvider,
39
+ createPathString: () => createPathString,
40
+ createWaitForPredicateController: () => createWaitForPredicateController,
41
+ defaultAnimationAdapter: () => defaultAnimationAdapter,
42
+ defaultLabels: () => defaultLabels,
43
+ getCurrentRoutePath: () => getCurrentRoutePath,
44
+ getTanStackRouter: () => getTanStackRouter,
45
+ getTourRouter: () => getTourRouter,
46
+ notifyRouteChange: () => notifyRouteChange,
47
+ reducedMotionAnimationAdapter: () => reducedMotionAnimationAdapter,
48
+ setTanStackRouter: () => setTanStackRouter,
49
+ setTourRouter: () => setTourRouter,
50
+ subscribeToRouteChanges: () => subscribeToRouteChanges,
51
+ useAdvanceRules: () => useAdvanceRules,
52
+ useAnimationAdapter: () => useAnimationAdapter,
53
+ useBodyScrollLock: () => useBodyScrollLock,
54
+ useDelayAdvance: () => useDelayAdvance,
55
+ useHiddenTargetFallback: () => useHiddenTargetFallback,
56
+ useHudDescription: () => useHudDescription,
57
+ useHudMotion: () => useHudMotion,
58
+ useHudShortcuts: () => useHudShortcuts,
59
+ useHudState: () => useHudState,
60
+ useHudTargetIssue: () => useHudTargetIssue,
61
+ usePreferredAnimationAdapter: () => usePreferredAnimationAdapter,
62
+ useRadixDialogAdapter: () => useRadixDialogAdapter,
63
+ useTanStackRouterTourAdapter: () => useTanStackRouterTourAdapter,
64
+ useTour: () => useTour,
65
+ useTourControls: () => useTourControls,
66
+ useTourEvents: () => useTourEvents,
67
+ useTourFocusDominance: () => useTourFocusDominance,
68
+ useTourHud: () => useTourHud,
69
+ useTourLabels: () => useTourLabels,
70
+ useTourOverlay: () => useTourOverlay,
71
+ useTourTarget: () => useTourTarget,
72
+ useViewportRect: () => useViewportRect
73
+ });
74
+ module.exports = __toCommonJS(index_exports);
75
+
76
+ // src/context.tsx
77
+ var import_core = require("@flowsterix/core");
78
+ var import_react4 = require("react");
79
+
80
+ // src/labels.ts
81
+ var import_react = require("react");
82
+ var defaultLabels = {
83
+ back: "Back",
84
+ next: "Next",
85
+ finish: "Finish",
86
+ skip: "Skip tour",
87
+ holdToConfirm: "Hold to confirm",
88
+ ariaStepProgress: ({ current, total }) => `Step ${current} of ${total}`,
89
+ ariaTimeRemaining: ({ ms }) => `${Math.ceil(ms / 1e3)} seconds remaining`,
90
+ ariaDelayProgress: "Auto-advance progress",
91
+ formatTimeRemaining: ({ ms }) => `${Math.ceil(ms / 1e3)}s remaining`
92
+ };
93
+ var LabelsContext = (0, import_react.createContext)(defaultLabels);
94
+ var LabelsProvider = LabelsContext.Provider;
95
+ function useTourLabels() {
96
+ return (0, import_react.useContext)(LabelsContext);
97
+ }
98
+
99
+ // src/motion/animationAdapter.tsx
100
+ var import_react2 = require("motion/react");
101
+ var import_react3 = require("react");
102
+ var import_jsx_runtime = require("react/jsx-runtime");
103
+ var defaultAdapter = {
104
+ components: {
105
+ MotionDiv: import_react2.motion.div,
106
+ MotionSection: import_react2.motion.section,
107
+ MotionSvg: import_react2.motion.svg,
108
+ MotionDefs: import_react2.motion.defs,
109
+ MotionMask: import_react2.motion.mask,
110
+ MotionRect: import_react2.motion.rect,
111
+ MotionSpan: import_react2.motion.span,
112
+ MotionButton: import_react2.motion.button
113
+ },
114
+ transitions: {
115
+ overlayHighlight: {
116
+ duration: 0.35,
117
+ ease: "easeOut",
118
+ type: "spring",
119
+ damping: 25,
120
+ stiffness: 300,
121
+ mass: 0.7
122
+ },
123
+ overlayFade: {
124
+ duration: 0.35,
125
+ ease: "easeOut"
126
+ },
127
+ popoverEntrance: {
128
+ duration: 0.25,
129
+ ease: "easeOut"
130
+ },
131
+ popoverExit: {
132
+ duration: 0.2,
133
+ ease: "easeOut"
134
+ },
135
+ popoverContent: {
136
+ duration: 0.2,
137
+ ease: "easeOut"
138
+ },
139
+ delayIndicator: {
140
+ duration: 0.18,
141
+ ease: "easeOut"
142
+ }
143
+ }
144
+ };
145
+ var AnimationAdapterContext = (0, import_react3.createContext)(defaultAdapter);
146
+ var AnimationAdapterProvider = ({
147
+ adapter,
148
+ children
149
+ }) => {
150
+ const value = (0, import_react3.useMemo)(() => adapter ?? defaultAdapter, [adapter]);
151
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AnimationAdapterContext.Provider, { value, children });
152
+ };
153
+ var useAnimationAdapter = () => {
154
+ return (0, import_react3.useContext)(AnimationAdapterContext);
155
+ };
156
+ var defaultAnimationAdapter = defaultAdapter;
157
+ var reducedMotionAnimationAdapter = {
158
+ components: defaultAdapter.components,
159
+ transitions: {
160
+ overlayHighlight: {
161
+ duration: 1e-3,
162
+ ease: "linear",
163
+ type: "tween"
164
+ },
165
+ overlayFade: {
166
+ duration: 1e-3,
167
+ ease: "linear"
168
+ },
169
+ popoverEntrance: {
170
+ duration: 1e-3,
171
+ ease: "linear"
172
+ },
173
+ popoverExit: {
174
+ duration: 1e-3,
175
+ ease: "linear"
176
+ },
177
+ popoverContent: {
178
+ duration: 1e-3,
179
+ ease: "linear"
180
+ },
181
+ delayIndicator: {
182
+ duration: 1e-3,
183
+ ease: "linear"
184
+ }
185
+ }
186
+ };
187
+ var REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
188
+ var usePreferredAnimationAdapter = (options) => {
189
+ const {
190
+ defaultAdapter: defaultOption = defaultAnimationAdapter,
191
+ reducedMotionAdapter: reducedOption = reducedMotionAnimationAdapter,
192
+ enabled = true
193
+ } = options ?? {};
194
+ const [prefersReduced, setPrefersReduced] = (0, import_react3.useState)(() => {
195
+ if (!enabled || typeof window === "undefined") return false;
196
+ return window.matchMedia(REDUCED_MOTION_QUERY).matches;
197
+ });
198
+ (0, import_react3.useEffect)(() => {
199
+ if (!enabled || typeof window === "undefined") return;
200
+ const mediaQuery = window.matchMedia(REDUCED_MOTION_QUERY);
201
+ const handleChange = (event) => {
202
+ setPrefersReduced(event.matches);
203
+ };
204
+ setPrefersReduced(mediaQuery.matches);
205
+ mediaQuery.addEventListener("change", handleChange);
206
+ return () => mediaQuery.removeEventListener("change", handleChange);
207
+ }, [enabled]);
208
+ if (!enabled) {
209
+ return defaultOption;
210
+ }
211
+ return prefersReduced ? reducedOption : defaultOption;
212
+ };
213
+
214
+ // src/utils/dom.ts
215
+ var isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
216
+ var createRect = ({
217
+ top,
218
+ left,
219
+ width,
220
+ height
221
+ }) => ({
222
+ top,
223
+ left,
224
+ width,
225
+ height,
226
+ right: left + width,
227
+ bottom: top + height
228
+ });
229
+ var toClientRect = (rect) => createRect({
230
+ top: rect.top,
231
+ left: rect.left,
232
+ width: rect.width,
233
+ height: rect.height
234
+ });
235
+ var getViewportRect = () => {
236
+ if (!isBrowser) {
237
+ return createRect({ top: 0, left: 0, width: 0, height: 0 });
238
+ }
239
+ const clientWidth = document.documentElement.clientWidth;
240
+ const clientHeight = document.documentElement.clientHeight;
241
+ const width = clientWidth > 0 ? clientWidth : window.innerWidth;
242
+ const height = clientHeight > 0 ? clientHeight : window.innerHeight;
243
+ return createRect({
244
+ top: 0,
245
+ left: 0,
246
+ width,
247
+ height
248
+ });
249
+ };
250
+ var expandRect = (rect, padding) => {
251
+ if (!isBrowser) return rect;
252
+ const viewport = getViewportRect();
253
+ const top = Math.max(0, rect.top - padding);
254
+ const left = Math.max(0, rect.left - padding);
255
+ const width = Math.min(viewport.width - left, rect.width + padding * 2);
256
+ const height = Math.min(viewport.height - top, rect.height + padding * 2);
257
+ return createRect({
258
+ top,
259
+ left,
260
+ width: Math.max(0, width),
261
+ height: Math.max(0, height)
262
+ });
263
+ };
264
+ var getClientRect = (element) => toClientRect(element.getBoundingClientRect());
265
+ var SCROLLABLE_OVERFLOW = /(auto|scroll|overlay)/i;
266
+ var isElementScrollable = (node) => {
267
+ if (!isBrowser) return false;
268
+ const style = window.getComputedStyle(node);
269
+ if (style.position === "fixed") return false;
270
+ return SCROLLABLE_OVERFLOW.test(style.overflow) || SCROLLABLE_OVERFLOW.test(style.overflowX) || SCROLLABLE_OVERFLOW.test(style.overflowY);
271
+ };
272
+ var getScrollParents = (element) => {
273
+ if (!isBrowser) return [];
274
+ const parents = [];
275
+ const seen = /* @__PURE__ */ new Set();
276
+ let current = element.parentElement;
277
+ while (current && current !== document.documentElement) {
278
+ if (current !== document.body && isElementScrollable(current)) {
279
+ if (!seen.has(current)) {
280
+ parents.push(current);
281
+ seen.add(current);
282
+ }
283
+ }
284
+ current = current.parentElement;
285
+ }
286
+ const scrollingElement = document.scrollingElement;
287
+ if (scrollingElement && scrollingElement instanceof Element && !seen.has(scrollingElement) && isElementScrollable(scrollingElement)) {
288
+ parents.push(scrollingElement);
289
+ seen.add(scrollingElement);
290
+ }
291
+ return parents;
292
+ };
293
+ var portalHost = () => isBrowser ? document.body : null;
294
+ var cachedMaskSupport = null;
295
+ var detectMaskSupport = () => {
296
+ if (!isBrowser) return false;
297
+ if ("CSS" in window) {
298
+ try {
299
+ if (window.CSS.supports("mask-image", "linear-gradient(#000,#000)")) {
300
+ return true;
301
+ }
302
+ if (window.CSS.supports("-webkit-mask-image", "linear-gradient(#000,#000)")) {
303
+ return true;
304
+ }
305
+ } catch (error) {
306
+ if (typeof console !== "undefined") {
307
+ console.warn("Flowsterix: CSS.supports check failed", error);
308
+ }
309
+ }
310
+ }
311
+ const probe = document.createElement("div");
312
+ return "maskImage" in probe.style || "webkitMaskImage" in probe.style;
313
+ };
314
+ var supportsMasking = () => {
315
+ if (!isBrowser) return false;
316
+ if (cachedMaskSupport === null) {
317
+ cachedMaskSupport = detectMaskSupport();
318
+ }
319
+ return cachedMaskSupport;
320
+ };
321
+
322
+ // src/context.tsx
323
+ var import_jsx_runtime2 = require("react/jsx-runtime");
324
+ var TourContext = (0, import_react4.createContext)(void 0);
325
+ var DEFAULT_STORAGE_PREFIX = "tour";
326
+ var useFlowMap = (flows) => {
327
+ return (0, import_react4.useMemo)(() => {
328
+ const map = /* @__PURE__ */ new Map();
329
+ for (const flow of flows) {
330
+ map.set(flow.id, flow);
331
+ }
332
+ return map;
333
+ }, [flows]);
334
+ };
335
+ var TourProvider = ({
336
+ flows,
337
+ children,
338
+ storageAdapter,
339
+ storageNamespace,
340
+ persistOnChange = true,
341
+ defaultDebug = false,
342
+ animationAdapter: animationAdapterProp = defaultAnimationAdapter,
343
+ reducedMotionAdapter,
344
+ autoDetectReducedMotion = true,
345
+ analytics,
346
+ backdropInteraction: backdropInteractionProp = "passthrough",
347
+ lockBodyScroll: lockBodyScrollProp = false,
348
+ labels: labelsProp,
349
+ onVersionMismatch
350
+ }) => {
351
+ const mergedLabels = (0, import_react4.useMemo)(
352
+ () => ({ ...defaultLabels, ...labelsProp }),
353
+ [labelsProp]
354
+ );
355
+ const flowMap = useFlowMap(flows);
356
+ const storeRef = (0, import_react4.useRef)(null);
357
+ const unsubscribeRef = (0, import_react4.useRef)(null);
358
+ const stepHooksUnsubscribeRef = (0, import_react4.useRef)(null);
359
+ const fallbackStorageRef = (0, import_react4.useRef)(void 0);
360
+ const pendingResumeRef = (0, import_react4.useRef)(/* @__PURE__ */ new Set());
361
+ const autoStartRequestedRef = (0, import_react4.useRef)(null);
362
+ const [activeFlowId, setActiveFlowId] = (0, import_react4.useState)(null);
363
+ const [state, setState] = (0, import_react4.useState)(null);
364
+ const [events, setEvents] = (0, import_react4.useState)(
365
+ null
366
+ );
367
+ const [debugEnabled, setDebugEnabled] = (0, import_react4.useState)(defaultDebug);
368
+ const [delayInfo, setDelayInfo] = (0, import_react4.useState)(null);
369
+ const autoStartFlow = (0, import_react4.useMemo)(() => {
370
+ return flows.find((flow) => flow.autoStart);
371
+ }, [flows]);
372
+ const teardownStore = (0, import_react4.useCallback)(() => {
373
+ unsubscribeRef.current?.();
374
+ unsubscribeRef.current = null;
375
+ stepHooksUnsubscribeRef.current?.();
376
+ stepHooksUnsubscribeRef.current = null;
377
+ storeRef.current?.destroy();
378
+ storeRef.current = null;
379
+ setDelayInfo(null);
380
+ pendingResumeRef.current.clear();
381
+ }, []);
382
+ (0, import_react4.useEffect)(() => {
383
+ return () => {
384
+ teardownStore();
385
+ setState(null);
386
+ setEvents(null);
387
+ setActiveFlowId(null);
388
+ };
389
+ }, [teardownStore]);
390
+ (0, import_react4.useEffect)(() => {
391
+ if (!activeFlowId) return;
392
+ const definition = flowMap.get(activeFlowId);
393
+ if (!definition) {
394
+ teardownStore();
395
+ setState(null);
396
+ setEvents(null);
397
+ setActiveFlowId(null);
398
+ }
399
+ }, [activeFlowId, flowMap, teardownStore]);
400
+ const invokeStepHookSync = (hook, context, phase) => {
401
+ if (!hook) return;
402
+ try {
403
+ const result = hook(context);
404
+ if (typeof result === "object" && result !== null && typeof result.then === "function") {
405
+ ;
406
+ result.catch((error) => {
407
+ console.warn(`[tour][step] ${phase} hook rejected`, error);
408
+ });
409
+ }
410
+ } catch (error) {
411
+ console.warn(`[tour][step] ${phase} hook failed`, error);
412
+ }
413
+ };
414
+ const ensureStore = (0, import_react4.useCallback)(
415
+ (flowId) => {
416
+ const existing = storeRef.current;
417
+ if (existing && existing.definition.id === flowId) {
418
+ return existing;
419
+ }
420
+ teardownStore();
421
+ const definition = flowMap.get(flowId);
422
+ if (!definition) {
423
+ throw new Error(`Flow with id "${flowId}" is not registered.`);
424
+ }
425
+ if (!storageAdapter && !fallbackStorageRef.current && isBrowser) {
426
+ fallbackStorageRef.current = (0, import_core.createLocalStorageAdapter)();
427
+ }
428
+ const resolvedStorageAdapter = storageAdapter ? storageAdapter : fallbackStorageRef.current;
429
+ const store = (0, import_core.createFlowStore)(definition, {
430
+ storageAdapter: resolvedStorageAdapter,
431
+ storageKey: storageNamespace ? `${storageNamespace}:${definition.id}` : void 0,
432
+ persistOnChange,
433
+ analytics,
434
+ onVersionMismatch
435
+ });
436
+ const unsubscribeEnter = store.events.on("stepEnter", (payload) => {
437
+ const step = payload.currentStep;
438
+ if (!step.onEnter) return;
439
+ invokeStepHookSync(
440
+ step.onEnter,
441
+ { flow: definition, state: payload.state, step },
442
+ "enter"
443
+ );
444
+ });
445
+ const unsubscribeExit = store.events.on("stepExit", (payload) => {
446
+ const step = payload.previousStep;
447
+ if (!step.onExit) return;
448
+ invokeStepHookSync(
449
+ step.onExit,
450
+ { flow: definition, state: payload.state, step },
451
+ "exit"
452
+ );
453
+ });
454
+ stepHooksUnsubscribeRef.current = () => {
455
+ unsubscribeEnter();
456
+ unsubscribeExit();
457
+ };
458
+ unsubscribeRef.current = store.subscribe(setState);
459
+ setEvents(store.events);
460
+ storeRef.current = store;
461
+ return store;
462
+ },
463
+ [
464
+ analytics,
465
+ flowMap,
466
+ onVersionMismatch,
467
+ persistOnChange,
468
+ storageAdapter,
469
+ storageNamespace,
470
+ teardownStore
471
+ ]
472
+ );
473
+ const getActiveStore = (0, import_react4.useCallback)(() => {
474
+ const store = storeRef.current;
475
+ if (!store) {
476
+ throw new Error(
477
+ "No active flow. Call startFlow before controlling progression."
478
+ );
479
+ }
480
+ return store;
481
+ }, []);
482
+ const isPromiseLike2 = (value) => {
483
+ return typeof value === "object" && value !== null && typeof value.then === "function";
484
+ };
485
+ const invokeStepHook = (0, import_react4.useCallback)(
486
+ async (hook, context, phase) => {
487
+ if (!hook) return;
488
+ try {
489
+ const result = hook(context);
490
+ if (isPromiseLike2(result)) {
491
+ await result;
492
+ }
493
+ } catch (error) {
494
+ console.warn(`[tour][step] ${phase} hook failed`, error);
495
+ }
496
+ },
497
+ []
498
+ );
499
+ const runResumeHooks = (0, import_react4.useCallback)(
500
+ async (definition, flowState, strategy) => {
501
+ if (flowState.status !== "running") return;
502
+ if (strategy === "current") {
503
+ const index = flowState.stepIndex;
504
+ if (index < 0 || index >= definition.steps.length) return;
505
+ const step = definition.steps[index];
506
+ if (!step.onResume) return;
507
+ await invokeStepHook(
508
+ step.onResume,
509
+ {
510
+ flow: definition,
511
+ state: flowState,
512
+ step
513
+ },
514
+ "resume"
515
+ );
516
+ return;
517
+ }
518
+ const maxIndex = Math.min(
519
+ flowState.stepIndex,
520
+ definition.steps.length - 1
521
+ );
522
+ if (maxIndex < 0) return;
523
+ for (let index = 0; index <= maxIndex; index += 1) {
524
+ const step = definition.steps[index];
525
+ if (!step.onResume) continue;
526
+ await invokeStepHook(
527
+ step.onResume,
528
+ {
529
+ flow: definition,
530
+ state: flowState,
531
+ step
532
+ },
533
+ "resume"
534
+ );
535
+ }
536
+ },
537
+ [invokeStepHook]
538
+ );
539
+ const resolveResumeStrategy = (0, import_react4.useCallback)(
540
+ (definition, options) => {
541
+ return options?.resumeStrategy ?? definition.resumeStrategy ?? "chain";
542
+ },
543
+ []
544
+ );
545
+ const startFlow = (0, import_react4.useCallback)(
546
+ (flowId, options) => {
547
+ const store = ensureStore(flowId);
548
+ const previousState = store.getState();
549
+ setActiveFlowId(flowId);
550
+ if (options?.resume) {
551
+ pendingResumeRef.current.add(flowId);
552
+ } else {
553
+ pendingResumeRef.current.delete(flowId);
554
+ }
555
+ const nextState = store.start(options);
556
+ if (!options?.resume) {
557
+ return nextState;
558
+ }
559
+ if (previousState.stepIndex >= 0 && nextState.status === "running") {
560
+ pendingResumeRef.current.delete(flowId);
561
+ const resumeStrategy = resolveResumeStrategy(store.definition, options);
562
+ const shouldRunResumeHooks = resumeStrategy === "current" ? nextState.stepIndex >= 0 : nextState.stepIndex > 0;
563
+ if (shouldRunResumeHooks) {
564
+ void runResumeHooks(store.definition, nextState, resumeStrategy);
565
+ }
566
+ } else if (nextState.status !== "idle" && nextState.stepIndex <= 0) {
567
+ pendingResumeRef.current.delete(flowId);
568
+ }
569
+ return nextState;
570
+ },
571
+ [ensureStore, resolveResumeStrategy, runResumeHooks]
572
+ );
573
+ (0, import_react4.useEffect)(() => {
574
+ if (!autoStartFlow) {
575
+ autoStartRequestedRef.current = null;
576
+ return;
577
+ }
578
+ if (activeFlowId) return;
579
+ if (autoStartRequestedRef.current === autoStartFlow.id) return;
580
+ autoStartRequestedRef.current = autoStartFlow.id;
581
+ let cancelled = false;
582
+ const maybeAutoStart = async () => {
583
+ if (!storageAdapter && !fallbackStorageRef.current && isBrowser) {
584
+ fallbackStorageRef.current = (0, import_core.createLocalStorageAdapter)();
585
+ }
586
+ const resolvedStorageAdapter = storageAdapter ? storageAdapter : fallbackStorageRef.current;
587
+ if (!resolvedStorageAdapter) {
588
+ startFlow(autoStartFlow.id, { resume: true });
589
+ return;
590
+ }
591
+ const storageKey = storageNamespace ? `${storageNamespace}:${autoStartFlow.id}` : `${DEFAULT_STORAGE_PREFIX}:${autoStartFlow.id}`;
592
+ const snapshot = await (0, import_core.resolveMaybePromise)(
593
+ resolvedStorageAdapter.get(storageKey)
594
+ );
595
+ if (cancelled) return;
596
+ const currentVersionStr = (0, import_core.serializeVersion)(autoStartFlow.version);
597
+ const storedVersionStr = typeof snapshot?.version === "number" ? (0, import_core.serializeVersion)({ major: snapshot.version, minor: 0 }) : snapshot?.version;
598
+ if (snapshot && storedVersionStr === currentVersionStr) {
599
+ const storedState = snapshot.value;
600
+ const isFinished = storedState.status === "completed";
601
+ const isSkipped = storedState.status === "cancelled" && storedState.cancelReason === "skipped";
602
+ if (isFinished || isSkipped) {
603
+ return;
604
+ }
605
+ }
606
+ startFlow(autoStartFlow.id, { resume: true });
607
+ };
608
+ void maybeAutoStart();
609
+ return () => {
610
+ cancelled = true;
611
+ if (!activeFlowId) {
612
+ autoStartRequestedRef.current = null;
613
+ }
614
+ };
615
+ }, [activeFlowId, autoStartFlow, startFlow, storageAdapter, storageNamespace]);
616
+ const next = (0, import_react4.useCallback)(() => getActiveStore().next(), [getActiveStore]);
617
+ const back = (0, import_react4.useCallback)(() => getActiveStore().back(), [getActiveStore]);
618
+ const goToStep = (0, import_react4.useCallback)(
619
+ (step) => getActiveStore().goToStep(step),
620
+ [getActiveStore]
621
+ );
622
+ const pause = (0, import_react4.useCallback)(() => getActiveStore().pause(), [getActiveStore]);
623
+ const resume = (0, import_react4.useCallback)(() => {
624
+ const store = getActiveStore();
625
+ const previousState = store.getState();
626
+ if (previousState.status === "paused") {
627
+ pendingResumeRef.current.add(store.definition.id);
628
+ }
629
+ const nextState = store.resume();
630
+ if (previousState.status === "paused" && nextState.status === "running" && nextState.stepIndex >= 0) {
631
+ pendingResumeRef.current.delete(store.definition.id);
632
+ const resumeStrategy = resolveResumeStrategy(store.definition);
633
+ const shouldRunResumeHooks = resumeStrategy === "current" ? true : nextState.stepIndex > 0;
634
+ if (shouldRunResumeHooks) {
635
+ void runResumeHooks(store.definition, nextState, resumeStrategy);
636
+ }
637
+ }
638
+ return nextState;
639
+ }, [getActiveStore, resolveResumeStrategy, runResumeHooks]);
640
+ const cancel = (0, import_react4.useCallback)(
641
+ (reason) => getActiveStore().cancel(reason),
642
+ [getActiveStore]
643
+ );
644
+ const complete = (0, import_react4.useCallback)(
645
+ () => getActiveStore().complete(),
646
+ [getActiveStore]
647
+ );
648
+ const toggleDebug = (0, import_react4.useCallback)(() => {
649
+ setDebugEnabled((previous) => !previous);
650
+ }, []);
651
+ const activeStep = (0, import_react4.useMemo)(() => {
652
+ if (!state || !storeRef.current) return null;
653
+ if (state.stepIndex < 0) return null;
654
+ return storeRef.current.definition.steps[state.stepIndex] ?? null;
655
+ }, [state]);
656
+ (0, import_react4.useEffect)(() => {
657
+ if (!activeFlowId) return;
658
+ if (!pendingResumeRef.current.has(activeFlowId)) return;
659
+ if (!state || state.status !== "running") return;
660
+ const definition = flowMap.get(activeFlowId);
661
+ if (!definition) return;
662
+ const resumeStrategy = resolveResumeStrategy(definition);
663
+ const shouldRunResumeHooks = resumeStrategy === "current" ? state.stepIndex >= 0 : state.stepIndex > 0;
664
+ if (!shouldRunResumeHooks) {
665
+ pendingResumeRef.current.delete(activeFlowId);
666
+ return;
667
+ }
668
+ pendingResumeRef.current.delete(activeFlowId);
669
+ void runResumeHooks(definition, state, resumeStrategy);
670
+ }, [activeFlowId, flowMap, resolveResumeStrategy, runResumeHooks, state]);
671
+ const contextValue = (0, import_react4.useMemo)(
672
+ () => ({
673
+ flows: flowMap,
674
+ activeFlowId,
675
+ state,
676
+ activeStep,
677
+ startFlow,
678
+ next,
679
+ back,
680
+ goToStep,
681
+ pause,
682
+ resume,
683
+ cancel,
684
+ complete,
685
+ events,
686
+ debugEnabled,
687
+ setDebugEnabled,
688
+ toggleDebug,
689
+ delayInfo,
690
+ setDelayInfo,
691
+ backdropInteraction: backdropInteractionProp,
692
+ lockBodyScroll: lockBodyScrollProp
693
+ }),
694
+ [
695
+ activeFlowId,
696
+ activeStep,
697
+ back,
698
+ cancel,
699
+ complete,
700
+ debugEnabled,
701
+ events,
702
+ flowMap,
703
+ goToStep,
704
+ next,
705
+ pause,
706
+ resume,
707
+ delayInfo,
708
+ setDelayInfo,
709
+ setDebugEnabled,
710
+ startFlow,
711
+ state,
712
+ toggleDebug,
713
+ backdropInteractionProp,
714
+ lockBodyScrollProp
715
+ ]
716
+ );
717
+ const resolvedAnimationAdapter = usePreferredAnimationAdapter({
718
+ defaultAdapter: animationAdapterProp,
719
+ reducedMotionAdapter,
720
+ enabled: autoDetectReducedMotion
721
+ });
722
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AnimationAdapterProvider, { adapter: resolvedAnimationAdapter, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LabelsProvider, { value: mergedLabels, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(TourContext.Provider, { value: contextValue, children }) }) });
723
+ };
724
+ var useTour = () => {
725
+ const context = (0, import_react4.useContext)(TourContext);
726
+ if (!context) {
727
+ throw new Error("useTour must be used within a TourProvider");
728
+ }
729
+ return context;
730
+ };
731
+ var useTourEvents = (event, handler) => {
732
+ const { events } = useTour();
733
+ (0, import_react4.useEffect)(() => {
734
+ if (!events) return;
735
+ return events.on(event, handler);
736
+ }, [event, events, handler]);
737
+ };
738
+
739
+ // src/hooks/useTourTarget.ts
740
+ var import_react5 = require("react");
741
+
742
+ // src/hooks/scrollMargin.ts
743
+ var DEFAULT_SCROLL_MARGIN = 16;
744
+ var sanitize = (value, fallback) => {
745
+ if (typeof value !== "number" || !Number.isFinite(value)) {
746
+ return fallback;
747
+ }
748
+ return value < 0 ? 0 : value;
749
+ };
750
+ var resolveScrollMargin = (margin, fallback = DEFAULT_SCROLL_MARGIN) => {
751
+ if (typeof margin === "number") {
752
+ const safe = sanitize(margin, fallback);
753
+ return {
754
+ top: safe,
755
+ bottom: safe,
756
+ left: safe,
757
+ right: safe
758
+ };
759
+ }
760
+ return {
761
+ top: sanitize(margin?.top, fallback),
762
+ bottom: sanitize(margin?.bottom, fallback),
763
+ left: sanitize(margin?.left, fallback),
764
+ right: sanitize(margin?.right, fallback)
765
+ };
766
+ };
767
+
768
+ // src/hooks/waitForPredicate.ts
769
+ var defaultWarn = (...args) => {
770
+ if (typeof console !== "undefined" && typeof console.warn === "function") {
771
+ console.warn(...args);
772
+ }
773
+ };
774
+ var isPromiseLike = (value) => {
775
+ return typeof value === "object" && value !== null && typeof value.then === "function";
776
+ };
777
+ var createWaitForPredicateController = ({
778
+ waitFor,
779
+ context,
780
+ onChange,
781
+ warn = defaultWarn
782
+ }) => {
783
+ const hasPredicate = Boolean(waitFor?.predicate && context);
784
+ const hasSubscription = Boolean(waitFor?.subscribe && context);
785
+ let satisfied = !hasPredicate && !hasSubscription;
786
+ let destroyed = false;
787
+ let pollId = null;
788
+ let subscriptionCleanup = null;
789
+ let lastCheckId = 0;
790
+ const update = (nextValue) => {
791
+ const normalized = Boolean(nextValue);
792
+ if (satisfied === normalized) return;
793
+ satisfied = normalized;
794
+ onChange?.(satisfied);
795
+ };
796
+ const evaluate = () => {
797
+ if (!waitFor?.predicate || !context || destroyed) {
798
+ return;
799
+ }
800
+ const checkId = ++lastCheckId;
801
+ let result;
802
+ try {
803
+ result = waitFor.predicate(context);
804
+ } catch (error) {
805
+ warn("[tour][waitFor] predicate threw an error", error);
806
+ update(false);
807
+ return;
808
+ }
809
+ if (isPromiseLike(result)) {
810
+ result.then(
811
+ (value) => {
812
+ if (destroyed || checkId !== lastCheckId) return;
813
+ update(Boolean(value));
814
+ },
815
+ (error) => {
816
+ if (destroyed || checkId !== lastCheckId) return;
817
+ warn("[tour][waitFor] predicate rejected", error);
818
+ update(false);
819
+ }
820
+ );
821
+ return;
822
+ }
823
+ update(Boolean(result));
824
+ };
825
+ const attachSubscription = () => {
826
+ if (!waitFor?.subscribe || !context) return;
827
+ try {
828
+ const cleanup = waitFor.subscribe({
829
+ ...context,
830
+ notify: (nextValue) => {
831
+ if (destroyed) return;
832
+ if (typeof nextValue === "boolean") {
833
+ update(nextValue);
834
+ return;
835
+ }
836
+ evaluate();
837
+ }
838
+ });
839
+ if (typeof cleanup === "function") {
840
+ subscriptionCleanup = cleanup;
841
+ }
842
+ } catch (error) {
843
+ warn("[tour][waitFor] subscribe handler threw an error", error);
844
+ }
845
+ };
846
+ const start = () => {
847
+ destroyed = false;
848
+ satisfied = !hasPredicate && !hasSubscription;
849
+ lastCheckId = 0;
850
+ if (waitFor?.predicate && context) {
851
+ evaluate();
852
+ const pollMs = Math.max(0, waitFor.pollMs ?? 200);
853
+ if (pollMs > 0) {
854
+ pollId = window.setInterval(evaluate, pollMs);
855
+ }
856
+ }
857
+ attachSubscription();
858
+ };
859
+ const stop = () => {
860
+ destroyed = true;
861
+ if (pollId !== null) {
862
+ window.clearInterval(pollId);
863
+ pollId = null;
864
+ }
865
+ if (subscriptionCleanup) {
866
+ try {
867
+ subscriptionCleanup();
868
+ } catch (error) {
869
+ warn("[tour][waitFor] subscribe cleanup threw an error", error);
870
+ }
871
+ subscriptionCleanup = null;
872
+ }
873
+ };
874
+ const isSatisfied = () => {
875
+ if (!waitFor) return true;
876
+ if (!hasPredicate && !hasSubscription) return true;
877
+ return satisfied;
878
+ };
879
+ return {
880
+ start,
881
+ stop,
882
+ evaluate,
883
+ isSatisfied
884
+ };
885
+ };
886
+
887
+ // src/hooks/useTourTarget.ts
888
+ var INITIAL_TARGET_INFO = {
889
+ element: null,
890
+ rect: null,
891
+ lastResolvedRect: null,
892
+ isScreen: false,
893
+ status: "idle",
894
+ stepId: null,
895
+ lastUpdated: 0,
896
+ visibility: "unknown",
897
+ rectSource: "none"
898
+ };
899
+ var DEFAULT_SCROLL_MODE = "center";
900
+ var MAX_AUTO_SCROLL_CHECKS = 10;
901
+ var STALLED_CHECKS_BEFORE_AUTO = 4;
902
+ var RECT_PROGRESS_THRESHOLD = 0.5;
903
+ var lastResolvedRectByStep = /* @__PURE__ */ new Map();
904
+ var rectHasMeaningfulSize = (rect) => !!rect && rect.width > 0 && rect.height > 0 && Number.isFinite(rect.top) && Number.isFinite(rect.left);
905
+ var computeRectSource = (rect, storedRect, isScreen) => {
906
+ if (isScreen) return "viewport";
907
+ if (rectHasMeaningfulSize(rect)) return "live";
908
+ if (storedRect) return "stored";
909
+ return "none";
910
+ };
911
+ var computeVisibilityState = (element, rect, isScreen) => {
912
+ if (!isBrowser) return "unknown";
913
+ if (isScreen) return "visible";
914
+ if (!element) return "missing";
915
+ if (!document.documentElement.contains(element)) return "detached";
916
+ const style = window.getComputedStyle(element);
917
+ const hiddenByStyle = style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse";
918
+ const transparent = Number.parseFloat(style.opacity || "1") === 0;
919
+ if (hiddenByStyle || transparent) {
920
+ return "hidden";
921
+ }
922
+ if (!rectHasMeaningfulSize(rect)) {
923
+ return "hidden";
924
+ }
925
+ return "visible";
926
+ };
927
+ var scrollContainerBy = (container, topDelta, leftDelta, behavior) => {
928
+ if (!isBrowser) return;
929
+ if (Math.abs(topDelta) < 0.5 && Math.abs(leftDelta) < 0.5) {
930
+ return;
931
+ }
932
+ const isRootContainer = container === document.body || container === document.documentElement || container === document.scrollingElement;
933
+ if (isRootContainer) {
934
+ window.scrollBy({
935
+ top: topDelta,
936
+ left: leftDelta,
937
+ behavior: behavior ?? "auto"
938
+ });
939
+ return;
940
+ }
941
+ const elementContainer = container;
942
+ if (typeof elementContainer.scrollBy === "function") {
943
+ elementContainer.scrollBy({
944
+ top: topDelta,
945
+ left: leftDelta,
946
+ behavior: behavior ?? "auto"
947
+ });
948
+ return;
949
+ }
950
+ elementContainer.scrollTop += topDelta;
951
+ elementContainer.scrollLeft += leftDelta;
952
+ };
953
+ var alignWithinViewport = (element, margin, behavior, mode) => {
954
+ if (mode === "preserve") return;
955
+ const viewportRect = getViewportRect();
956
+ const finalRect = getClientRect(element);
957
+ const availableHeight = viewportRect.height - (margin.top + margin.bottom);
958
+ if (availableHeight <= 0) return;
959
+ const desiredTop = mode === "center" ? margin.top + (availableHeight - finalRect.height) / 2 : margin.top;
960
+ const delta = finalRect.top - desiredTop;
961
+ if (Math.abs(delta) < 0.5) return;
962
+ window.scrollBy({
963
+ top: delta,
964
+ behavior: behavior ?? "auto"
965
+ });
966
+ };
967
+ var ensureElementInView = (element, margin, options) => {
968
+ const behavior = options?.behavior ?? "auto";
969
+ const mode = options?.mode ?? "preserve";
970
+ if (!isBrowser) return;
971
+ const scrollParents = getScrollParents(element);
972
+ const rootScroller = document.scrollingElement;
973
+ if (rootScroller && !scrollParents.includes(rootScroller)) {
974
+ scrollParents.push(rootScroller);
975
+ }
976
+ for (const container of scrollParents) {
977
+ const containerRect = container === rootScroller || container === document.body || container === document.documentElement ? getViewportRect() : getClientRect(container);
978
+ const targetRect = getClientRect(element);
979
+ let topDelta = 0;
980
+ if (targetRect.top < containerRect.top + margin.top) {
981
+ topDelta = targetRect.top - (containerRect.top + margin.top);
982
+ } else if (targetRect.bottom > containerRect.bottom - margin.bottom) {
983
+ topDelta = targetRect.bottom - (containerRect.bottom - margin.bottom);
984
+ }
985
+ let leftDelta = 0;
986
+ if (targetRect.left < containerRect.left + margin.left) {
987
+ leftDelta = targetRect.left - (containerRect.left + margin.left);
988
+ } else if (targetRect.right > containerRect.right - margin.right) {
989
+ leftDelta = targetRect.right - (containerRect.right - margin.right);
990
+ }
991
+ if (topDelta !== 0 || leftDelta !== 0) {
992
+ scrollContainerBy(container, topDelta, leftDelta, behavior);
993
+ }
994
+ }
995
+ const viewportRect = getViewportRect();
996
+ const finalRect = getClientRect(element);
997
+ let viewportTopDelta = 0;
998
+ if (finalRect.top < viewportRect.top + margin.top) {
999
+ viewportTopDelta = finalRect.top - (viewportRect.top + margin.top);
1000
+ } else if (finalRect.bottom > viewportRect.bottom - margin.bottom) {
1001
+ viewportTopDelta = finalRect.bottom - (viewportRect.bottom - margin.bottom);
1002
+ }
1003
+ let viewportLeftDelta = 0;
1004
+ if (finalRect.left < viewportRect.left + margin.left) {
1005
+ viewportLeftDelta = finalRect.left - (viewportRect.left + margin.left);
1006
+ } else if (finalRect.right > viewportRect.right - margin.right) {
1007
+ viewportLeftDelta = finalRect.right - (viewportRect.right - margin.right);
1008
+ }
1009
+ if (viewportTopDelta !== 0 || viewportLeftDelta !== 0) {
1010
+ window.scrollBy({
1011
+ top: viewportTopDelta,
1012
+ left: viewportLeftDelta,
1013
+ behavior
1014
+ });
1015
+ }
1016
+ alignWithinViewport(element, margin, behavior, mode);
1017
+ };
1018
+ var resolveStepTarget = (target) => {
1019
+ if (!isBrowser) return null;
1020
+ if (target === "screen") {
1021
+ return document.body;
1022
+ }
1023
+ if (target.getNode) {
1024
+ try {
1025
+ const node = target.getNode();
1026
+ if (node) {
1027
+ return node;
1028
+ }
1029
+ } catch {
1030
+ }
1031
+ }
1032
+ if (target.selector) {
1033
+ try {
1034
+ return document.querySelector(target.selector);
1035
+ } catch {
1036
+ return null;
1037
+ }
1038
+ }
1039
+ return null;
1040
+ };
1041
+ var useTourTarget = () => {
1042
+ const { activeStep, state, activeFlowId, flows } = useTour();
1043
+ const [targetInfo, setTargetInfo] = (0, import_react5.useState)(INITIAL_TARGET_INFO);
1044
+ const autoScrollStateRef = (0, import_react5.useRef)({ stepId: null, checks: 0, stalledChecks: 0, done: false, lastRect: null });
1045
+ const autoScrollRafRef = (0, import_react5.useRef)(null);
1046
+ const autoScrollTimeoutRef = (0, import_react5.useRef)(null);
1047
+ const lastRectRef = (0, import_react5.useRef)(null);
1048
+ const initialScrollStepRef = (0, import_react5.useRef)(null);
1049
+ const cancelAutoScrollLoop = () => {
1050
+ if (!isBrowser) return;
1051
+ if (autoScrollTimeoutRef.current !== null) {
1052
+ globalThis.clearTimeout(autoScrollTimeoutRef.current);
1053
+ autoScrollTimeoutRef.current = null;
1054
+ }
1055
+ if (autoScrollRafRef.current !== null) {
1056
+ window.cancelAnimationFrame(autoScrollRafRef.current);
1057
+ autoScrollRafRef.current = null;
1058
+ }
1059
+ };
1060
+ (0, import_react5.useEffect)(() => {
1061
+ if (!activeStep) {
1062
+ initialScrollStepRef.current = null;
1063
+ }
1064
+ return () => {
1065
+ initialScrollStepRef.current = null;
1066
+ };
1067
+ }, [activeStep?.id]);
1068
+ (0, import_react5.useLayoutEffect)(() => {
1069
+ if (!isBrowser) return;
1070
+ if (!activeStep) return;
1071
+ if (targetInfo.status !== "ready") return;
1072
+ if (targetInfo.isScreen) return;
1073
+ if (!targetInfo.element) return;
1074
+ if (initialScrollStepRef.current === activeStep.id) {
1075
+ return;
1076
+ }
1077
+ initialScrollStepRef.current = activeStep.id;
1078
+ const margin = resolveScrollMargin(
1079
+ activeStep.targetBehavior?.scrollMargin,
1080
+ DEFAULT_SCROLL_MARGIN
1081
+ );
1082
+ const scrollMode = activeStep.targetBehavior?.scrollMode ?? DEFAULT_SCROLL_MODE;
1083
+ const hasLiveRect = targetInfo.rectSource === "live";
1084
+ const scrollBehavior = hasLiveRect ? "smooth" : "auto";
1085
+ ensureElementInView(targetInfo.element, margin, {
1086
+ behavior: scrollBehavior,
1087
+ mode: scrollMode
1088
+ });
1089
+ }, [
1090
+ activeStep?.id,
1091
+ activeStep?.targetBehavior?.scrollMargin,
1092
+ activeStep?.targetBehavior?.scrollMode,
1093
+ targetInfo.rect,
1094
+ targetInfo.lastResolvedRect,
1095
+ targetInfo.element,
1096
+ targetInfo.isScreen,
1097
+ targetInfo.status,
1098
+ targetInfo.rectSource
1099
+ ]);
1100
+ (0, import_react5.useEffect)(() => {
1101
+ if (!activeStep || !state || state.status !== "running") {
1102
+ setTargetInfo(INITIAL_TARGET_INFO);
1103
+ autoScrollStateRef.current = {
1104
+ stepId: null,
1105
+ checks: 0,
1106
+ stalledChecks: 0,
1107
+ done: false,
1108
+ lastRect: null
1109
+ };
1110
+ cancelAutoScrollLoop();
1111
+ return;
1112
+ }
1113
+ if (!isBrowser) {
1114
+ const storedRect = lastResolvedRectByStep.get(activeStep.id) ?? null;
1115
+ setTargetInfo({
1116
+ element: null,
1117
+ rect: null,
1118
+ lastResolvedRect: storedRect ? { ...storedRect } : null,
1119
+ isScreen: activeStep.target === "screen",
1120
+ status: "resolving",
1121
+ stepId: activeStep.id,
1122
+ lastUpdated: Date.now(),
1123
+ visibility: "unknown",
1124
+ rectSource: storedRect ? "stored" : "none"
1125
+ });
1126
+ return;
1127
+ }
1128
+ const currentStep = activeStep;
1129
+ const activeFlow = activeFlowId ? flows.get(activeFlowId) ?? null : null;
1130
+ const isScreen = currentStep.target === "screen";
1131
+ const waitForSelectorRaw = currentStep.waitFor?.selector;
1132
+ const waitForSelector = typeof waitForSelectorRaw === "string" ? waitForSelectorRaw.trim() : void 0;
1133
+ const hasWaitForSelector = Boolean(waitForSelector);
1134
+ const waitForTimeout = Math.max(0, currentStep.waitFor?.timeout ?? 8e3);
1135
+ const waitContext = activeFlow ? {
1136
+ flow: activeFlow,
1137
+ state,
1138
+ step: currentStep
1139
+ } : null;
1140
+ let cancelled = false;
1141
+ let resolvePollId = null;
1142
+ let waitForPollId = null;
1143
+ let resizeObserver = null;
1144
+ let mutationObserver = null;
1145
+ let scrollParents = [];
1146
+ const cleanupFns = [];
1147
+ let element = null;
1148
+ let rafId = null;
1149
+ let lastStatus = "idle";
1150
+ let lastElement = null;
1151
+ let hasEmitted = false;
1152
+ let waitForStartedAt = null;
1153
+ let waitForTimedOut = false;
1154
+ let waitForSelectorWarned = false;
1155
+ let waitForTimeoutWarned = false;
1156
+ let waitForPredicateController = null;
1157
+ lastRectRef.current = null;
1158
+ function clearResolvePolling() {
1159
+ if (resolvePollId !== null) {
1160
+ window.clearInterval(resolvePollId);
1161
+ resolvePollId = null;
1162
+ }
1163
+ }
1164
+ function clearWaitForPoll() {
1165
+ if (waitForPollId !== null) {
1166
+ window.clearInterval(waitForPollId);
1167
+ waitForPollId = null;
1168
+ }
1169
+ }
1170
+ const rectChanged = (nextRect) => {
1171
+ const previous = lastRectRef.current;
1172
+ if (!previous || !nextRect) {
1173
+ return previous !== nextRect;
1174
+ }
1175
+ const threshold = 0.25;
1176
+ return Math.abs(previous.top - nextRect.top) > threshold || Math.abs(previous.left - nextRect.left) > threshold || Math.abs(previous.width - nextRect.width) > threshold || Math.abs(previous.height - nextRect.height) > threshold;
1177
+ };
1178
+ const isWaitForSelectorSatisfied = () => {
1179
+ if (!hasWaitForSelector) return true;
1180
+ try {
1181
+ return document.querySelector(waitForSelector) !== null;
1182
+ } catch (error) {
1183
+ if (!waitForSelectorWarned && typeof console !== "undefined") {
1184
+ console.warn(
1185
+ "[tour][waitFor] selector lookup failed",
1186
+ waitForSelector,
1187
+ error
1188
+ );
1189
+ waitForSelectorWarned = true;
1190
+ }
1191
+ return false;
1192
+ }
1193
+ };
1194
+ const isWaitForSatisfied = () => {
1195
+ const selectorReady = !hasWaitForSelector || isWaitForSelectorSatisfied();
1196
+ const predicateReady = waitForPredicateController?.isSatisfied() ?? true;
1197
+ return selectorReady && predicateReady;
1198
+ };
1199
+ function updateTargetState(status, rectOverride) {
1200
+ if (cancelled) return;
1201
+ const rect = typeof rectOverride !== "undefined" ? rectOverride : isScreen ? getViewportRect() : element ? getClientRect(element) : null;
1202
+ if (status === "ready" && hasWaitForSelector && waitForStartedAt === null) {
1203
+ waitForStartedAt = Date.now();
1204
+ }
1205
+ if (!waitForTimedOut && waitForTimeout > 0 && waitForStartedAt !== null && Date.now() - waitForStartedAt >= waitForTimeout) {
1206
+ waitForTimedOut = true;
1207
+ if (!waitForTimeoutWarned && typeof console !== "undefined") {
1208
+ console.warn(
1209
+ "[tour][waitFor] timeout exceeded for step",
1210
+ currentStep.id,
1211
+ "selector:",
1212
+ waitForSelector
1213
+ );
1214
+ waitForTimeoutWarned = true;
1215
+ }
1216
+ }
1217
+ const hasRect = rectHasMeaningfulSize(rect);
1218
+ const waitConditionMet = waitForTimedOut || isWaitForSatisfied();
1219
+ const nextStatus = status === "ready" && (isScreen || hasRect) && waitConditionMet ? "ready" : "resolving";
1220
+ if (waitConditionMet) {
1221
+ clearWaitForPoll();
1222
+ }
1223
+ const storedRect = lastResolvedRectByStep.get(currentStep.id) ?? null;
1224
+ const shouldUpdate = !hasEmitted || rectChanged(rect) || lastStatus !== nextStatus || element !== lastElement;
1225
+ if (!shouldUpdate) {
1226
+ return;
1227
+ }
1228
+ lastRectRef.current = rect ? { ...rect } : null;
1229
+ lastStatus = nextStatus;
1230
+ hasEmitted = true;
1231
+ lastElement = element ?? null;
1232
+ const shouldPersistRect = nextStatus === "ready" && !isScreen && rectHasMeaningfulSize(rect);
1233
+ if (shouldPersistRect && rect) {
1234
+ lastResolvedRectByStep.set(currentStep.id, { ...rect });
1235
+ }
1236
+ const lastResolvedRect = shouldPersistRect && rect ? { ...rect } : storedRect ? { ...storedRect } : null;
1237
+ const visibility = computeVisibilityState(element ?? null, rect, isScreen);
1238
+ const rectSource = computeRectSource(rect, lastResolvedRect, isScreen);
1239
+ setTargetInfo({
1240
+ element: element ?? null,
1241
+ rect,
1242
+ lastResolvedRect,
1243
+ isScreen,
1244
+ status: nextStatus,
1245
+ stepId: currentStep.id,
1246
+ lastUpdated: Date.now(),
1247
+ visibility,
1248
+ rectSource
1249
+ });
1250
+ }
1251
+ const commitInfo = (status) => {
1252
+ updateTargetState(status);
1253
+ };
1254
+ waitForPredicateController = createWaitForPredicateController({
1255
+ waitFor: currentStep.waitFor,
1256
+ context: waitContext,
1257
+ onChange: () => {
1258
+ updateTargetState(element ? "ready" : "resolving");
1259
+ }
1260
+ });
1261
+ waitForPredicateController.start();
1262
+ function stopRaf() {
1263
+ if (rafId !== null) {
1264
+ window.cancelAnimationFrame(rafId);
1265
+ rafId = null;
1266
+ }
1267
+ }
1268
+ function startRafMonitor() {
1269
+ if (isScreen || !isBrowser) return;
1270
+ stopRaf();
1271
+ const tick = () => {
1272
+ if (cancelled) return;
1273
+ if (!element) {
1274
+ updateTargetState("resolving", null);
1275
+ } else {
1276
+ const rect = getClientRect(element);
1277
+ if (rectChanged(rect)) {
1278
+ updateTargetState("ready", rect);
1279
+ }
1280
+ }
1281
+ rafId = window.requestAnimationFrame(tick);
1282
+ };
1283
+ rafId = window.requestAnimationFrame(tick);
1284
+ cleanupFns.push(stopRaf);
1285
+ }
1286
+ function resetObservers() {
1287
+ cleanupFns.forEach((dispose) => dispose());
1288
+ cleanupFns.length = 0;
1289
+ if (resizeObserver) {
1290
+ resizeObserver.disconnect();
1291
+ resizeObserver = null;
1292
+ }
1293
+ if (mutationObserver) {
1294
+ mutationObserver.disconnect();
1295
+ mutationObserver = null;
1296
+ }
1297
+ scrollParents = [];
1298
+ clearWaitForPoll();
1299
+ stopRaf();
1300
+ }
1301
+ function startObservers() {
1302
+ if (cancelled) return;
1303
+ resetObservers();
1304
+ clearResolvePolling();
1305
+ if (isScreen) {
1306
+ const onResize = () => commitInfo("ready");
1307
+ window.addEventListener("resize", onResize);
1308
+ window.addEventListener("scroll", onResize, true);
1309
+ cleanupFns.push(() => {
1310
+ window.removeEventListener("resize", onResize);
1311
+ window.removeEventListener("scroll", onResize, true);
1312
+ });
1313
+ if (typeof window !== "undefined" && window.visualViewport) {
1314
+ const onViewportChange = () => commitInfo("ready");
1315
+ window.visualViewport.addEventListener("resize", onViewportChange);
1316
+ window.visualViewport.addEventListener("scroll", onViewportChange);
1317
+ cleanupFns.push(() => {
1318
+ window.visualViewport?.removeEventListener(
1319
+ "resize",
1320
+ onViewportChange
1321
+ );
1322
+ window.visualViewport?.removeEventListener(
1323
+ "scroll",
1324
+ onViewportChange
1325
+ );
1326
+ });
1327
+ }
1328
+ } else if (element) {
1329
+ if (typeof ResizeObserver === "function") {
1330
+ resizeObserver = new ResizeObserver(() => updateTargetState("ready"));
1331
+ resizeObserver.observe(element);
1332
+ }
1333
+ const onReposition = () => commitInfo("ready");
1334
+ window.addEventListener("resize", onReposition);
1335
+ window.addEventListener("scroll", onReposition, true);
1336
+ cleanupFns.push(() => {
1337
+ window.removeEventListener("resize", onReposition);
1338
+ window.removeEventListener("scroll", onReposition, true);
1339
+ });
1340
+ const onAncestorScroll = () => commitInfo("ready");
1341
+ scrollParents = getScrollParents(element);
1342
+ if (scrollParents.length > 0) {
1343
+ scrollParents.forEach(
1344
+ (parent) => parent.addEventListener("scroll", onAncestorScroll, {
1345
+ passive: true
1346
+ })
1347
+ );
1348
+ cleanupFns.push(() => {
1349
+ scrollParents.forEach(
1350
+ (parent) => parent.removeEventListener("scroll", onAncestorScroll)
1351
+ );
1352
+ scrollParents = [];
1353
+ });
1354
+ }
1355
+ startRafMonitor();
1356
+ if (typeof MutationObserver === "function") {
1357
+ mutationObserver = new MutationObserver(() => {
1358
+ if (cancelled) return;
1359
+ if (element && document.documentElement.contains(element)) {
1360
+ return;
1361
+ }
1362
+ resetObservers();
1363
+ element = null;
1364
+ commitInfo("resolving");
1365
+ startResolvePolling();
1366
+ });
1367
+ mutationObserver.observe(document.body, {
1368
+ childList: true,
1369
+ subtree: true
1370
+ });
1371
+ cleanupFns.push(() => {
1372
+ mutationObserver?.disconnect();
1373
+ mutationObserver = null;
1374
+ });
1375
+ }
1376
+ }
1377
+ if (hasWaitForSelector) {
1378
+ const pollWaitFor = () => updateTargetState("ready");
1379
+ pollWaitFor();
1380
+ clearWaitForPoll();
1381
+ waitForPollId = window.setInterval(pollWaitFor, 150);
1382
+ cleanupFns.push(clearWaitForPoll);
1383
+ }
1384
+ commitInfo("ready");
1385
+ }
1386
+ function attemptAttach() {
1387
+ const nextElement = resolveStepTarget(currentStep.target);
1388
+ if (isScreen || nextElement) {
1389
+ element = nextElement;
1390
+ startObservers();
1391
+ return true;
1392
+ }
1393
+ commitInfo("resolving");
1394
+ return false;
1395
+ }
1396
+ function startResolvePolling() {
1397
+ clearResolvePolling();
1398
+ const pollInterval = 200;
1399
+ const timeout = waitForTimeout;
1400
+ const startedAt = Date.now();
1401
+ resolvePollId = window.setInterval(() => {
1402
+ if (attemptAttach()) {
1403
+ clearResolvePolling();
1404
+ return;
1405
+ }
1406
+ if (timeout > 0 && Date.now() - startedAt >= timeout) {
1407
+ clearResolvePolling();
1408
+ }
1409
+ }, pollInterval);
1410
+ }
1411
+ if (!attemptAttach()) {
1412
+ startResolvePolling();
1413
+ }
1414
+ return () => {
1415
+ cancelled = true;
1416
+ clearResolvePolling();
1417
+ clearWaitForPoll();
1418
+ resetObservers();
1419
+ waitForPredicateController?.stop();
1420
+ waitForPredicateController = null;
1421
+ };
1422
+ }, [activeStep, activeFlowId, flows, state]);
1423
+ (0, import_react5.useEffect)(() => {
1424
+ if (!isBrowser) return;
1425
+ if (!activeStep) {
1426
+ cancelAutoScrollLoop();
1427
+ return;
1428
+ }
1429
+ if (targetInfo.status !== "ready") {
1430
+ cancelAutoScrollLoop();
1431
+ return;
1432
+ }
1433
+ if (targetInfo.isScreen) {
1434
+ cancelAutoScrollLoop();
1435
+ return;
1436
+ }
1437
+ if (!targetInfo.element) {
1438
+ cancelAutoScrollLoop();
1439
+ return;
1440
+ }
1441
+ const autoState = autoScrollStateRef.current;
1442
+ if (autoState.stepId !== activeStep.id) {
1443
+ autoState.stepId = activeStep.id;
1444
+ autoState.checks = 0;
1445
+ autoState.stalledChecks = 0;
1446
+ autoState.done = false;
1447
+ autoState.lastRect = null;
1448
+ cancelAutoScrollLoop();
1449
+ } else if (autoState.done) {
1450
+ cancelAutoScrollLoop();
1451
+ return;
1452
+ }
1453
+ const { element } = targetInfo;
1454
+ const scrollMode = activeStep.targetBehavior?.scrollMode ?? "center";
1455
+ const runCheck = () => {
1456
+ autoScrollRafRef.current = null;
1457
+ if (!isBrowser) return;
1458
+ if (autoState.stepId !== activeStep.id) return;
1459
+ if (!element.isConnected) return;
1460
+ const rect = getClientRect(element);
1461
+ const viewport = getViewportRect();
1462
+ const margin = resolveScrollMargin(
1463
+ activeStep.targetBehavior?.scrollMargin,
1464
+ DEFAULT_SCROLL_MARGIN
1465
+ );
1466
+ const fitsHeight = rect.height <= viewport.height - (margin.top + margin.bottom);
1467
+ const fitsWidth = rect.width <= viewport.width - (margin.left + margin.right);
1468
+ const verticalSatisfied = fitsHeight ? rect.top >= margin.top && rect.bottom <= viewport.height - margin.bottom : rect.top <= margin.top && rect.bottom >= viewport.height - margin.bottom;
1469
+ const horizontalSatisfied = fitsWidth ? rect.left >= margin.left && rect.right <= viewport.width - margin.right : rect.left <= margin.left && rect.right >= viewport.width - margin.right;
1470
+ if (verticalSatisfied && horizontalSatisfied) {
1471
+ autoState.done = true;
1472
+ return;
1473
+ }
1474
+ autoState.checks += 1;
1475
+ if (autoState.checks >= MAX_AUTO_SCROLL_CHECKS) {
1476
+ autoState.done = true;
1477
+ return;
1478
+ }
1479
+ const previousRect = autoState.lastRect;
1480
+ const hasProgress = !previousRect || Math.abs(previousRect.top - rect.top) > RECT_PROGRESS_THRESHOLD || Math.abs(previousRect.left - rect.left) > RECT_PROGRESS_THRESHOLD || Math.abs(previousRect.bottom - rect.bottom) > RECT_PROGRESS_THRESHOLD || Math.abs(previousRect.right - rect.right) > RECT_PROGRESS_THRESHOLD;
1481
+ autoState.lastRect = rect;
1482
+ if (hasProgress) {
1483
+ autoState.stalledChecks = 0;
1484
+ } else {
1485
+ autoState.stalledChecks += 1;
1486
+ }
1487
+ const behavior = autoState.stalledChecks >= STALLED_CHECKS_BEFORE_AUTO ? "auto" : "smooth";
1488
+ ensureElementInView(element, margin, {
1489
+ behavior,
1490
+ mode: scrollMode
1491
+ });
1492
+ autoScrollTimeoutRef.current = globalThis.setTimeout(() => {
1493
+ autoScrollRafRef.current = window.requestAnimationFrame(runCheck);
1494
+ }, 120);
1495
+ };
1496
+ cancelAutoScrollLoop();
1497
+ autoScrollRafRef.current = window.requestAnimationFrame(runCheck);
1498
+ return cancelAutoScrollLoop;
1499
+ }, [activeStep, activeStep?.targetBehavior?.scrollMode, targetInfo]);
1500
+ return targetInfo;
1501
+ };
1502
+
1503
+ // src/hooks/useHudState.ts
1504
+ var import_react9 = require("react");
1505
+
1506
+ // src/hooks/useAdvanceRules.ts
1507
+ var import_react6 = require("react");
1508
+
1509
+ // src/router/routeGating.ts
1510
+ var DEFAULT_POLL_MS = 150;
1511
+ var normalizePathname = (pathname) => {
1512
+ if (typeof pathname !== "string" || pathname.length === 0) {
1513
+ return "/";
1514
+ }
1515
+ return pathname.startsWith("/") ? pathname : `/${pathname}`;
1516
+ };
1517
+ var normalizePrefixedSegment = (value, prefix) => {
1518
+ if (typeof value !== "string" || value.length === 0) {
1519
+ return "";
1520
+ }
1521
+ return value.startsWith(prefix) ? value : `${prefix}${value}`;
1522
+ };
1523
+ var getWindowPath = () => {
1524
+ if (!isBrowser) return "/";
1525
+ const { pathname, search, hash } = window.location;
1526
+ return normalizePathname(pathname) + normalizePrefixedSegment(search, "?") + normalizePrefixedSegment(hash, "#");
1527
+ };
1528
+ var normalizeExternalPath = (path) => {
1529
+ if (path.length === 0) {
1530
+ return "/";
1531
+ }
1532
+ try {
1533
+ const parsed = new URL(path, "http://flowsterix.local");
1534
+ return normalizePathname(parsed.pathname) + normalizePrefixedSegment(parsed.search, "?") + normalizePrefixedSegment(parsed.hash, "#");
1535
+ } catch {
1536
+ const [withoutHash, hash = ""] = path.split("#");
1537
+ const [base, search = ""] = withoutHash.split("?");
1538
+ return normalizePathname(base) + normalizePrefixedSegment(search ? `?${search}` : "", "?") + normalizePrefixedSegment(hash ? `#${hash}` : "", "#");
1539
+ }
1540
+ };
1541
+ var RouteGatingChannel = class {
1542
+ #listeners = /* @__PURE__ */ new Set();
1543
+ #currentPath = getWindowPath();
1544
+ #teardown = null;
1545
+ #attachDefaultListeners() {
1546
+ if (!isBrowser) return;
1547
+ if (this.#teardown) return;
1548
+ let lastPath = getWindowPath();
1549
+ const emitIfChanged = () => {
1550
+ const nextPath = getWindowPath();
1551
+ if (nextPath === lastPath) return;
1552
+ lastPath = nextPath;
1553
+ this.notify(nextPath);
1554
+ };
1555
+ const handler = () => emitIfChanged();
1556
+ window.addEventListener("popstate", handler);
1557
+ window.addEventListener("hashchange", handler);
1558
+ const pollId = window.setInterval(emitIfChanged, DEFAULT_POLL_MS);
1559
+ this.#teardown = () => {
1560
+ window.removeEventListener("popstate", handler);
1561
+ window.removeEventListener("hashchange", handler);
1562
+ window.clearInterval(pollId);
1563
+ this.#teardown = null;
1564
+ };
1565
+ }
1566
+ #detachDefaultListeners() {
1567
+ if (this.#listeners.size > 0) return;
1568
+ this.#teardown?.();
1569
+ this.#teardown = null;
1570
+ }
1571
+ getCurrentPath() {
1572
+ if (isBrowser) {
1573
+ this.#currentPath = getWindowPath();
1574
+ }
1575
+ return this.#currentPath;
1576
+ }
1577
+ notify(path) {
1578
+ const resolved = typeof path === "string" && path.length > 0 ? normalizeExternalPath(path) : this.getCurrentPath();
1579
+ if (resolved === this.#currentPath) {
1580
+ this.#currentPath = resolved;
1581
+ return;
1582
+ }
1583
+ this.#currentPath = resolved;
1584
+ for (const listener of Array.from(this.#listeners)) {
1585
+ try {
1586
+ listener(resolved);
1587
+ } catch (error) {
1588
+ console.warn("[tour][route-gating] listener error", error);
1589
+ }
1590
+ }
1591
+ }
1592
+ subscribe(listener) {
1593
+ if (this.#listeners.has(listener)) {
1594
+ return () => {
1595
+ this.#listeners.delete(listener);
1596
+ this.#detachDefaultListeners();
1597
+ };
1598
+ }
1599
+ this.#listeners.add(listener);
1600
+ if (this.#listeners.size === 1) {
1601
+ this.#attachDefaultListeners();
1602
+ }
1603
+ const current = this.getCurrentPath();
1604
+ try {
1605
+ listener(current);
1606
+ } catch (error) {
1607
+ console.warn("[tour][route-gating] listener error", error);
1608
+ }
1609
+ return () => {
1610
+ this.#listeners.delete(listener);
1611
+ this.#detachDefaultListeners();
1612
+ };
1613
+ }
1614
+ };
1615
+ var routeGatingChannel = new RouteGatingChannel();
1616
+ var getCurrentRoutePath = () => routeGatingChannel.getCurrentPath();
1617
+ var notifyRouteChange = (path) => {
1618
+ routeGatingChannel.notify(path);
1619
+ };
1620
+ var subscribeToRouteChanges = (listener) => {
1621
+ return routeGatingChannel.subscribe(listener);
1622
+ };
1623
+
1624
+ // src/hooks/useAdvanceRules.ts
1625
+ var DEFAULT_POLL_MS2 = 250;
1626
+ var isListenerTarget = (value) => {
1627
+ return !!value && typeof value.addEventListener === "function" && typeof value.removeEventListener === "function";
1628
+ };
1629
+ var resolveEventTarget = (rule, target) => {
1630
+ if (!isBrowser) return null;
1631
+ if (!rule.on || rule.on === "target") {
1632
+ return target.element ?? null;
1633
+ }
1634
+ if (rule.on === "window") {
1635
+ return window;
1636
+ }
1637
+ if (rule.on === "document") {
1638
+ return document;
1639
+ }
1640
+ try {
1641
+ return document.querySelector(rule.on);
1642
+ } catch {
1643
+ return null;
1644
+ }
1645
+ };
1646
+ var matchesRouteRule = (rule, path) => {
1647
+ if (!rule.to) return true;
1648
+ if (typeof rule.to === "string") {
1649
+ return path === rule.to;
1650
+ }
1651
+ return rule.to.test(path);
1652
+ };
1653
+ var useAdvanceRules = (target) => {
1654
+ const {
1655
+ activeFlowId,
1656
+ flows,
1657
+ activeStep,
1658
+ state,
1659
+ next,
1660
+ complete,
1661
+ setDelayInfo
1662
+ } = useTour();
1663
+ (0, import_react6.useEffect)(() => {
1664
+ if (!isBrowser) return;
1665
+ if (!state || state.status !== "running") return;
1666
+ if (!activeStep) return;
1667
+ const definition = activeFlowId ? flows.get(activeFlowId) : void 0;
1668
+ if (!definition) return;
1669
+ const rules = activeStep.advance;
1670
+ if (!rules || rules.length === 0) return;
1671
+ let resolved = false;
1672
+ const hasResolved = () => resolved;
1673
+ const cleanupFns = [];
1674
+ const runCleanup = () => {
1675
+ while (cleanupFns.length > 0) {
1676
+ const dispose = cleanupFns.pop();
1677
+ try {
1678
+ dispose?.();
1679
+ } catch (error) {
1680
+ console.warn("[tour][advance] cleanup failed", error);
1681
+ }
1682
+ }
1683
+ };
1684
+ const addCleanup = (fn) => {
1685
+ if (fn) {
1686
+ cleanupFns.push(fn);
1687
+ }
1688
+ };
1689
+ const clearDelayInfo = () => {
1690
+ setDelayInfo((info) => {
1691
+ if (!info) return null;
1692
+ if (info.flowId !== definition.id) return info;
1693
+ if (info.stepId !== activeStep.id) return info;
1694
+ return null;
1695
+ });
1696
+ };
1697
+ const finish = () => {
1698
+ if (resolved) return;
1699
+ resolved = true;
1700
+ runCleanup();
1701
+ clearDelayInfo();
1702
+ const totalSteps = definition.steps.length;
1703
+ if (totalSteps > 0 && state.stepIndex >= totalSteps - 1) {
1704
+ complete();
1705
+ } else {
1706
+ next();
1707
+ }
1708
+ };
1709
+ const predicateCtx = {
1710
+ flow: definition,
1711
+ state,
1712
+ step: activeStep
1713
+ };
1714
+ for (const rule of rules) {
1715
+ if (hasResolved()) break;
1716
+ switch (rule.type) {
1717
+ case "manual": {
1718
+ break;
1719
+ }
1720
+ case "delay": {
1721
+ const totalMs = Math.max(0, rule.ms);
1722
+ const startedAt = typeof performance !== "undefined" ? performance.now() : Date.now();
1723
+ const endsAt = startedAt + totalMs;
1724
+ setDelayInfo({
1725
+ flowId: definition.id,
1726
+ stepId: activeStep.id,
1727
+ totalMs,
1728
+ startedAt,
1729
+ endsAt
1730
+ });
1731
+ const timer = window.setTimeout(() => {
1732
+ finish();
1733
+ }, totalMs);
1734
+ addCleanup(() => window.clearTimeout(timer));
1735
+ break;
1736
+ }
1737
+ case "event": {
1738
+ const eventTarget = resolveEventTarget(rule, target);
1739
+ if (!isListenerTarget(eventTarget)) {
1740
+ continue;
1741
+ }
1742
+ const handler = () => finish();
1743
+ eventTarget.addEventListener(rule.event, handler);
1744
+ addCleanup(() => {
1745
+ eventTarget.removeEventListener(rule.event, handler);
1746
+ });
1747
+ if (rule.event === "click") {
1748
+ const keyupHandler = (event) => {
1749
+ if (!(event instanceof KeyboardEvent)) return;
1750
+ if (event.repeat) return;
1751
+ const key = event.key;
1752
+ if (key === "Enter" || key === " " || key === "Spacebar") {
1753
+ finish();
1754
+ }
1755
+ };
1756
+ eventTarget.addEventListener("keyup", keyupHandler);
1757
+ addCleanup(() => {
1758
+ eventTarget.removeEventListener("keyup", keyupHandler);
1759
+ });
1760
+ }
1761
+ break;
1762
+ }
1763
+ case "predicate": {
1764
+ const pollMs = Math.max(50, rule.pollMs ?? DEFAULT_POLL_MS2);
1765
+ const timeoutMs = rule.timeoutMs;
1766
+ const executeCheck = () => {
1767
+ if (resolved) return;
1768
+ try {
1769
+ if (rule.check(predicateCtx)) {
1770
+ finish();
1771
+ }
1772
+ } catch (error) {
1773
+ console.warn("[tour][advance] predicate check failed", error);
1774
+ }
1775
+ };
1776
+ executeCheck();
1777
+ if (hasResolved()) {
1778
+ break;
1779
+ }
1780
+ const intervalId = window.setInterval(executeCheck, pollMs);
1781
+ addCleanup(() => window.clearInterval(intervalId));
1782
+ if (typeof timeoutMs === "number" && timeoutMs > 0) {
1783
+ const timeoutId = window.setTimeout(() => {
1784
+ window.clearInterval(intervalId);
1785
+ }, timeoutMs);
1786
+ addCleanup(() => window.clearTimeout(timeoutId));
1787
+ }
1788
+ break;
1789
+ }
1790
+ case "route": {
1791
+ const checkRoute = (path) => {
1792
+ if (resolved) return;
1793
+ if (matchesRouteRule(rule, path)) {
1794
+ finish();
1795
+ }
1796
+ };
1797
+ const initialPath = getCurrentRoutePath();
1798
+ checkRoute(initialPath);
1799
+ if (hasResolved()) {
1800
+ break;
1801
+ }
1802
+ const unsubscribe = subscribeToRouteChanges((path) => {
1803
+ checkRoute(path);
1804
+ });
1805
+ addCleanup(unsubscribe);
1806
+ break;
1807
+ }
1808
+ default: {
1809
+ const neverRule = rule;
1810
+ console.warn("[tour][advance] unsupported advance rule", neverRule);
1811
+ }
1812
+ }
1813
+ }
1814
+ return () => {
1815
+ resolved = true;
1816
+ runCleanup();
1817
+ clearDelayInfo();
1818
+ };
1819
+ }, [
1820
+ activeFlowId,
1821
+ activeStep,
1822
+ complete,
1823
+ flows,
1824
+ next,
1825
+ state,
1826
+ target.element,
1827
+ setDelayInfo,
1828
+ target.lastUpdated,
1829
+ target.status
1830
+ ]);
1831
+ };
1832
+
1833
+ // src/hooks/useHiddenTargetFallback.ts
1834
+ var import_react7 = require("react");
1835
+ var DEFAULT_DELAY_MS = 900;
1836
+ var HIDDEN_VISIBILITIES = [
1837
+ "hidden",
1838
+ "detached"
1839
+ ];
1840
+ var useHiddenTargetFallback = ({
1841
+ step,
1842
+ target,
1843
+ viewportRect,
1844
+ onSkip
1845
+ }) => {
1846
+ const [usingScreenFallback, setUsingScreenFallback] = (0, import_react7.useState)(false);
1847
+ const timeoutRef = (0, import_react7.useRef)(null);
1848
+ const skipTriggeredRef = (0, import_react7.useRef)(false);
1849
+ const hiddenMode = step?.targetBehavior?.hidden ?? "screen";
1850
+ const hiddenDelayMs = Math.max(
1851
+ 0,
1852
+ step?.targetBehavior?.hiddenDelayMs ?? DEFAULT_DELAY_MS
1853
+ );
1854
+ const clearPendingTimeout = () => {
1855
+ if (timeoutRef.current !== null) {
1856
+ globalThis.clearTimeout(timeoutRef.current);
1857
+ timeoutRef.current = null;
1858
+ }
1859
+ };
1860
+ (0, import_react7.useEffect)(() => {
1861
+ skipTriggeredRef.current = false;
1862
+ setUsingScreenFallback(false);
1863
+ clearPendingTimeout();
1864
+ return clearPendingTimeout;
1865
+ }, [step?.id]);
1866
+ (0, import_react7.useEffect)(() => {
1867
+ if (!isBrowser) return void 0;
1868
+ if (!step) return void 0;
1869
+ clearPendingTimeout();
1870
+ const shouldHandleHiddenTarget = HIDDEN_VISIBILITIES.includes(target.visibility) && !target.isScreen && target.status === "ready";
1871
+ if (!shouldHandleHiddenTarget) {
1872
+ setUsingScreenFallback(false);
1873
+ return void 0;
1874
+ }
1875
+ if (hiddenMode !== "screen") {
1876
+ setUsingScreenFallback(false);
1877
+ }
1878
+ timeoutRef.current = globalThis.setTimeout(() => {
1879
+ if (hiddenMode === "screen") {
1880
+ setUsingScreenFallback(true);
1881
+ return;
1882
+ }
1883
+ if (!skipTriggeredRef.current) {
1884
+ skipTriggeredRef.current = true;
1885
+ onSkip();
1886
+ }
1887
+ }, hiddenDelayMs);
1888
+ return clearPendingTimeout;
1889
+ }, [
1890
+ step,
1891
+ target.visibility,
1892
+ target.isScreen,
1893
+ target.status,
1894
+ hiddenMode,
1895
+ hiddenDelayMs,
1896
+ onSkip
1897
+ ]);
1898
+ const resolvedTarget = (0, import_react7.useMemo)(() => {
1899
+ if (!usingScreenFallback) {
1900
+ return target;
1901
+ }
1902
+ return {
1903
+ ...target,
1904
+ element: null,
1905
+ rect: viewportRect,
1906
+ lastResolvedRect: viewportRect,
1907
+ isScreen: true,
1908
+ rectSource: "viewport",
1909
+ visibility: "visible"
1910
+ };
1911
+ }, [target, usingScreenFallback, viewportRect]);
1912
+ return {
1913
+ target: resolvedTarget,
1914
+ usingScreenFallback
1915
+ };
1916
+ };
1917
+
1918
+ // src/hooks/useViewportRect.ts
1919
+ var import_react8 = require("react");
1920
+ var useViewportRect = () => {
1921
+ const [viewport, setViewport] = (0, import_react8.useState)(
1922
+ () => getViewportRect()
1923
+ );
1924
+ const rafRef = (0, import_react8.useRef)(null);
1925
+ (0, import_react8.useEffect)(() => {
1926
+ if (!isBrowser) return;
1927
+ const updateViewport = () => {
1928
+ rafRef.current = null;
1929
+ setViewport(getViewportRect());
1930
+ };
1931
+ const scheduleUpdate = () => {
1932
+ if (rafRef.current !== null) return;
1933
+ rafRef.current = window.requestAnimationFrame(updateViewport);
1934
+ };
1935
+ const listeners = [];
1936
+ const addListener = (target, type, handler) => {
1937
+ if (!target) return;
1938
+ target.addEventListener(type, handler);
1939
+ listeners.push({ target, type, handler });
1940
+ };
1941
+ addListener(window, "resize", scheduleUpdate);
1942
+ addListener(window, "orientationchange", scheduleUpdate);
1943
+ addListener(window, "scroll", scheduleUpdate);
1944
+ addListener(window.visualViewport ?? null, "resize", scheduleUpdate);
1945
+ addListener(window.visualViewport ?? null, "scroll", scheduleUpdate);
1946
+ return () => {
1947
+ listeners.forEach(
1948
+ ({ target, type, handler }) => target.removeEventListener(type, handler)
1949
+ );
1950
+ if (rafRef.current !== null) {
1951
+ window.cancelAnimationFrame(rafRef.current);
1952
+ rafRef.current = null;
1953
+ }
1954
+ };
1955
+ }, []);
1956
+ return viewport;
1957
+ };
1958
+
1959
+ // src/hooks/useHudState.ts
1960
+ var EXIT_BUFFER_MS = 450;
1961
+ var normalizeFlowFilter = (value) => {
1962
+ if (!value) return null;
1963
+ return Array.isArray(value) ? value : [value];
1964
+ };
1965
+ var useHudState = (options = {}) => {
1966
+ const { flowId } = options;
1967
+ const flowFilter = (0, import_react9.useMemo)(() => normalizeFlowFilter(flowId), [flowId]);
1968
+ const { state, activeStep, activeFlowId, flows, next, complete } = useTour();
1969
+ const target = useTourTarget();
1970
+ const viewportRect = useViewportRect();
1971
+ useAdvanceRules(target);
1972
+ const matchesFlowFilter = (0, import_react9.useMemo)(() => {
1973
+ if (!flowFilter || flowFilter.length === 0) return true;
1974
+ if (!activeFlowId) return false;
1975
+ return flowFilter.includes(activeFlowId);
1976
+ }, [activeFlowId, flowFilter]);
1977
+ const isRunning = state?.status === "running";
1978
+ const runningState = isRunning && matchesFlowFilter ? state : null;
1979
+ const runningStep = runningState && activeStep ? activeStep : null;
1980
+ const [shouldRender, setShouldRender] = (0, import_react9.useState)(
1981
+ Boolean(runningStep)
1982
+ );
1983
+ (0, import_react9.useEffect)(() => {
1984
+ if (runningStep) {
1985
+ setShouldRender(true);
1986
+ }
1987
+ }, [runningStep?.id]);
1988
+ (0, import_react9.useEffect)(() => {
1989
+ if (!shouldRender) return;
1990
+ if (runningStep) return;
1991
+ if (target.status !== "idle") return;
1992
+ const timeoutId = window.setTimeout(() => {
1993
+ setShouldRender(false);
1994
+ }, EXIT_BUFFER_MS);
1995
+ return () => {
1996
+ window.clearTimeout(timeoutId);
1997
+ };
1998
+ }, [runningStep, shouldRender, target.status]);
1999
+ const skipHiddenStep = (0, import_react9.useCallback)(() => {
2000
+ if (!runningState || runningState.status !== "running") return;
2001
+ if (!activeFlowId) return;
2002
+ const flow = flows.get(activeFlowId);
2003
+ if (!flow) return;
2004
+ const isLastStep = runningState.stepIndex >= flow.steps.length - 1 && flow.steps.length > 0;
2005
+ if (isLastStep) {
2006
+ complete();
2007
+ } else {
2008
+ next();
2009
+ }
2010
+ }, [activeFlowId, complete, flows, next, runningState]);
2011
+ const { target: hudTarget } = useHiddenTargetFallback({
2012
+ step: runningStep,
2013
+ target,
2014
+ viewportRect,
2015
+ onSkip: skipHiddenStep
2016
+ });
2017
+ const canRenderStep = Boolean(runningStep && runningState);
2018
+ const focusTrapActive = canRenderStep;
2019
+ const flowHudOptions = matchesFlowFilter && activeFlowId ? flows.get(activeFlowId)?.hud ?? null : null;
2020
+ const hudRenderMode = flowHudOptions?.render ?? "default";
2021
+ return {
2022
+ state,
2023
+ runningState,
2024
+ runningStep,
2025
+ shouldRender,
2026
+ canRenderStep,
2027
+ focusTrapActive,
2028
+ target,
2029
+ hudTarget,
2030
+ flowHudOptions,
2031
+ hudRenderMode,
2032
+ matchesFlowFilter,
2033
+ activeFlowId
2034
+ };
2035
+ };
2036
+
2037
+ // src/hooks/useHudDescription.ts
2038
+ var import_react10 = require("react");
2039
+ var sanitizeForId = (value) => {
2040
+ const normalized = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
2041
+ return normalized.length > 0 ? normalized : "step";
2042
+ };
2043
+ var useHudDescription = (options) => {
2044
+ const { step, fallbackAriaDescribedBy } = options;
2045
+ const targetDescription = (0, import_react10.useMemo)(() => {
2046
+ if (!step) return null;
2047
+ if (typeof step.target !== "object") return null;
2048
+ const description = step.target.description;
2049
+ return typeof description === "string" ? description : null;
2050
+ }, [step]);
2051
+ const descriptionId = (0, import_react10.useMemo)(() => {
2052
+ if (!step || !targetDescription) return void 0;
2053
+ return `tour-step-${sanitizeForId(step.id)}-description`;
2054
+ }, [step, targetDescription]);
2055
+ const combinedAriaDescribedBy = (0, import_react10.useMemo)(() => {
2056
+ const parts = [fallbackAriaDescribedBy, descriptionId].filter(Boolean);
2057
+ return parts.length > 0 ? parts.join(" ") : void 0;
2058
+ }, [descriptionId, fallbackAriaDescribedBy]);
2059
+ return {
2060
+ targetDescription,
2061
+ descriptionId,
2062
+ combinedAriaDescribedBy
2063
+ };
2064
+ };
2065
+
2066
+ // src/hooks/useHudShortcuts.ts
2067
+ var import_react12 = require("react");
2068
+
2069
+ // src/hooks/useTourControls.ts
2070
+ var import_react11 = require("react");
2071
+ var hasManualAdvance = (rules) => rules.some((rule) => rule.type === "manual");
2072
+ var didPreviousAdvanceViaRoute = (rules) => rules.some((rule) => rule.type === "route");
2073
+ var didPreviousAdvanceViaTargetEvent = (rules) => rules.some((rule) => rule.type === "event" && rule.on === "target");
2074
+ var useTourControls = () => {
2075
+ const tour = useTour();
2076
+ const {
2077
+ back,
2078
+ next,
2079
+ cancel,
2080
+ complete,
2081
+ state,
2082
+ activeFlowId,
2083
+ flows,
2084
+ activeStep
2085
+ } = tour;
2086
+ const computed = (0, import_react11.useMemo)(() => {
2087
+ if (!state || state.status !== "running" || !activeStep) {
2088
+ return {
2089
+ isActive: false,
2090
+ isFirst: true,
2091
+ isLast: true,
2092
+ showBackButton: false,
2093
+ backDisabled: true,
2094
+ showNextButton: false,
2095
+ nextDisabled: true
2096
+ };
2097
+ }
2098
+ const definition = activeFlowId ? flows.get(activeFlowId) : null;
2099
+ const totalSteps = definition?.steps.length ?? 0;
2100
+ const stepIndex = state.stepIndex;
2101
+ const isFirst2 = stepIndex <= 0;
2102
+ const isLast2 = totalSteps > 0 && stepIndex >= totalSteps - 1;
2103
+ const previousStep = !definition || stepIndex <= 0 ? null : definition.steps[stepIndex - 1];
2104
+ const advanceRules = activeStep.advance ?? [];
2105
+ const hasAdvanceRules = advanceRules.length > 0;
2106
+ const previousAdvanceRules = previousStep?.advance ?? [];
2107
+ const backControlState = activeStep.controls?.back ?? "auto";
2108
+ const nextControlState = activeStep.controls?.next ?? "auto";
2109
+ const showBackButton2 = backControlState !== "hidden" && !isFirst2 && !didPreviousAdvanceViaRoute(previousAdvanceRules) && !didPreviousAdvanceViaTargetEvent(previousAdvanceRules);
2110
+ const backDisabled2 = backControlState === "disabled";
2111
+ const manualAdvancePresent = hasManualAdvance(advanceRules);
2112
+ const showNextButton2 = nextControlState !== "hidden" && (isLast2 || !hasAdvanceRules || manualAdvancePresent);
2113
+ const nextDisabled2 = nextControlState === "disabled";
2114
+ return {
2115
+ isActive: true,
2116
+ isFirst: isFirst2,
2117
+ isLast: isLast2,
2118
+ showBackButton: showBackButton2,
2119
+ backDisabled: backDisabled2,
2120
+ showNextButton: showNextButton2,
2121
+ nextDisabled: nextDisabled2
2122
+ };
2123
+ }, [activeFlowId, activeStep, flows, state]);
2124
+ const {
2125
+ isActive,
2126
+ isFirst,
2127
+ isLast,
2128
+ showBackButton,
2129
+ backDisabled,
2130
+ showNextButton,
2131
+ nextDisabled
2132
+ } = computed;
2133
+ const canGoBack = showBackButton && !backDisabled;
2134
+ const canGoNext = showNextButton && !nextDisabled;
2135
+ const goBack = (0, import_react11.useCallback)(() => {
2136
+ if (!canGoBack) return;
2137
+ back();
2138
+ }, [back, canGoBack]);
2139
+ const goNext = (0, import_react11.useCallback)(() => {
2140
+ if (!canGoNext) return;
2141
+ if (isLast) {
2142
+ complete();
2143
+ } else {
2144
+ next();
2145
+ }
2146
+ }, [canGoNext, complete, isLast, next]);
2147
+ return (0, import_react11.useMemo)(
2148
+ () => ({
2149
+ showBackButton,
2150
+ backDisabled,
2151
+ canGoBack,
2152
+ showNextButton,
2153
+ nextDisabled,
2154
+ canGoNext,
2155
+ isFirst,
2156
+ isLast,
2157
+ isActive,
2158
+ goBack,
2159
+ goNext,
2160
+ cancel
2161
+ }),
2162
+ [
2163
+ backDisabled,
2164
+ canGoBack,
2165
+ canGoNext,
2166
+ cancel,
2167
+ goBack,
2168
+ goNext,
2169
+ isFirst,
2170
+ isLast,
2171
+ isActive,
2172
+ nextDisabled,
2173
+ showBackButton,
2174
+ showNextButton
2175
+ ]
2176
+ );
2177
+ };
2178
+
2179
+ // src/hooks/useHudShortcuts.ts
2180
+ var isInteractiveElement = (node) => {
2181
+ if (!node) return false;
2182
+ if (node.getAttribute("role") === "button") return true;
2183
+ if (node.hasAttribute("contenteditable")) return true;
2184
+ const interactiveSelector = 'button, a[href], input, textarea, select, summary, [role="button"], [data-tour-prevent-shortcut="true"]';
2185
+ return Boolean(node.closest(interactiveSelector));
2186
+ };
2187
+ var useHudShortcuts = (target, options) => {
2188
+ const enabled = options?.enabled ?? true;
2189
+ const escapeEnabled = options?.escape ?? true;
2190
+ const { state } = useTour();
2191
+ const { cancel, canGoBack, goBack, canGoNext, goNext, isActive } = useTourControls();
2192
+ (0, import_react12.useEffect)(() => {
2193
+ if (!isBrowser) return void 0;
2194
+ if (!enabled) return void 0;
2195
+ if (!target) return void 0;
2196
+ if (!state || state.status !== "running") return void 0;
2197
+ if (!isActive) return void 0;
2198
+ const handler = (event) => {
2199
+ if (event.defaultPrevented) return;
2200
+ if (event.key === "Escape" && escapeEnabled) {
2201
+ cancel("keyboard");
2202
+ event.preventDefault();
2203
+ return;
2204
+ }
2205
+ if (event.key === "ArrowLeft") {
2206
+ if (canGoBack) {
2207
+ goBack();
2208
+ event.preventDefault();
2209
+ }
2210
+ return;
2211
+ }
2212
+ if (event.key === "ArrowRight") {
2213
+ if (canGoNext) {
2214
+ goNext();
2215
+ event.preventDefault();
2216
+ }
2217
+ return;
2218
+ }
2219
+ if (event.key === "Enter" || event.key === " ") {
2220
+ if (target.status !== "ready") return;
2221
+ if (event.target instanceof Element) {
2222
+ if (target.element && target.element.contains(event.target)) {
2223
+ return;
2224
+ }
2225
+ if (event.target.closest("[data-tour-popover]")) {
2226
+ return;
2227
+ }
2228
+ if (isInteractiveElement(event.target)) {
2229
+ return;
2230
+ }
2231
+ }
2232
+ if (canGoNext) {
2233
+ goNext();
2234
+ event.preventDefault();
2235
+ }
2236
+ }
2237
+ };
2238
+ window.addEventListener("keydown", handler);
2239
+ return () => {
2240
+ window.removeEventListener("keydown", handler);
2241
+ };
2242
+ }, [
2243
+ canGoBack,
2244
+ canGoNext,
2245
+ cancel,
2246
+ enabled,
2247
+ goBack,
2248
+ goNext,
2249
+ isActive,
2250
+ state,
2251
+ target
2252
+ ]);
2253
+ };
2254
+
2255
+ // src/hooks/useTourHud.ts
2256
+ var import_react15 = require("react");
2257
+
2258
+ // src/hooks/useBodyScrollLock.ts
2259
+ var import_react13 = require("react");
2260
+ var lockCount = 0;
2261
+ var previousOverflow = null;
2262
+ var acquireLock = () => {
2263
+ if (!isBrowser) return;
2264
+ if (lockCount === 0) {
2265
+ previousOverflow = document.body.style.overflow;
2266
+ document.body.style.overflow = "hidden";
2267
+ }
2268
+ lockCount += 1;
2269
+ };
2270
+ var releaseLock = () => {
2271
+ if (!isBrowser) return;
2272
+ if (lockCount === 0) return;
2273
+ lockCount -= 1;
2274
+ if (lockCount === 0) {
2275
+ document.body.style.overflow = previousOverflow ?? "";
2276
+ previousOverflow = null;
2277
+ }
2278
+ };
2279
+ var useBodyScrollLock = (enabled) => {
2280
+ (0, import_react13.useEffect)(() => {
2281
+ if (!enabled) return;
2282
+ acquireLock();
2283
+ return () => {
2284
+ releaseLock();
2285
+ };
2286
+ }, [enabled]);
2287
+ };
2288
+
2289
+ // src/hooks/useHudTargetIssue.ts
2290
+ var import_react14 = require("react");
2291
+ var deriveTargetIssue = (target) => {
2292
+ if (target.isScreen) return null;
2293
+ if (target.status === "idle") return null;
2294
+ switch (target.visibility) {
2295
+ case "missing":
2296
+ return {
2297
+ type: "missing",
2298
+ title: "Looking for the target",
2299
+ body: "Flowsterix is still trying to find this element. Make sure the UI piece is mounted or adjust the selector.",
2300
+ hint: target.rectSource === "stored" ? "Showing the last known position until the element returns." : void 0
2301
+ };
2302
+ case "hidden":
2303
+ return {
2304
+ type: "hidden",
2305
+ title: "Target is hidden",
2306
+ body: "The element exists but is currently hidden, collapsed, or zero-sized. Expand it so the highlight can lock on."
2307
+ };
2308
+ case "detached":
2309
+ return {
2310
+ type: "detached",
2311
+ title: "Target left the page",
2312
+ body: "Navigate back to the screen that contains this element or reopen it before continuing the tour."
2313
+ };
2314
+ default:
2315
+ return null;
2316
+ }
2317
+ };
2318
+ var useHudTargetIssue = (target, options) => {
2319
+ const delayMs = Math.max(0, options?.delayMs ?? 500);
2320
+ const [armed, setArmed] = (0, import_react14.useState)(false);
2321
+ const rawIssue = (0, import_react14.useMemo)(
2322
+ () => deriveTargetIssue(target),
2323
+ [target.isScreen, target.rectSource, target.status, target.visibility]
2324
+ );
2325
+ (0, import_react14.useEffect)(() => {
2326
+ if (!rawIssue) {
2327
+ setArmed(false);
2328
+ return;
2329
+ }
2330
+ if (!isBrowser) {
2331
+ setArmed(true);
2332
+ return;
2333
+ }
2334
+ const timeoutId = globalThis.setTimeout(() => setArmed(true), delayMs);
2335
+ return () => {
2336
+ setArmed(false);
2337
+ globalThis.clearTimeout(timeoutId);
2338
+ };
2339
+ }, [delayMs, rawIssue?.type]);
2340
+ return {
2341
+ issue: armed ? rawIssue : null,
2342
+ rawIssue
2343
+ };
2344
+ };
2345
+
2346
+ // src/hooks/useTourHud.ts
2347
+ var DEFAULT_SHORTCUTS = true;
2348
+ var DEFAULT_BODY_SCROLL_LOCK = true;
2349
+ var useTourHud = (options = {}) => {
2350
+ const {
2351
+ overlayPadding,
2352
+ overlayRadius,
2353
+ bodyScrollLock = DEFAULT_BODY_SCROLL_LOCK
2354
+ } = options;
2355
+ const shortcuts = options.shortcuts ?? DEFAULT_SHORTCUTS;
2356
+ const { backdropInteraction, lockBodyScroll } = useTour();
2357
+ const hudState = useHudState();
2358
+ const disableDefaultHud = hudState.hudRenderMode === "none";
2359
+ const [popoverNode, setPopoverNode] = (0, import_react15.useState)(null);
2360
+ const popoverOptions = hudState.flowHudOptions?.popover;
2361
+ const description = useHudDescription({
2362
+ step: hudState.runningStep,
2363
+ fallbackAriaDescribedBy: popoverOptions?.ariaDescribedBy
2364
+ });
2365
+ const targetIssue = useHudTargetIssue(hudState.hudTarget);
2366
+ const shouldLockBodyScroll = Boolean(
2367
+ bodyScrollLock && (hudState.flowHudOptions?.behavior?.lockBodyScroll ?? lockBodyScroll) && hudState.focusTrapActive
2368
+ );
2369
+ useBodyScrollLock(shouldLockBodyScroll);
2370
+ const shortcutOptions = typeof shortcuts === "object" ? shortcuts : {};
2371
+ const shortcutsEnabled = Boolean(
2372
+ (typeof shortcuts === "boolean" ? shortcuts : shortcuts.enabled ?? true) && hudState.shouldRender
2373
+ );
2374
+ useHudShortcuts(shortcutsEnabled ? hudState.hudTarget : null, {
2375
+ ...shortcutOptions,
2376
+ enabled: shortcutsEnabled
2377
+ });
2378
+ const overlay = {
2379
+ padding: overlayPadding,
2380
+ radius: overlayRadius,
2381
+ interactionMode: hudState.flowHudOptions?.backdrop?.interaction ?? backdropInteraction
2382
+ };
2383
+ const popover = (0, import_react15.useMemo)(() => {
2384
+ return {
2385
+ offset: popoverOptions?.offset ?? 16,
2386
+ role: popoverOptions?.role ?? "dialog",
2387
+ ariaLabel: popoverOptions?.ariaLabel,
2388
+ ariaDescribedBy: popoverOptions?.ariaDescribedBy,
2389
+ ariaModal: popoverOptions?.ariaModal ?? false,
2390
+ width: popoverOptions?.width,
2391
+ maxWidth: popoverOptions?.maxWidth,
2392
+ placement: hudState.runningStep?.placement
2393
+ };
2394
+ }, [hudState.runningStep?.placement, popoverOptions]);
2395
+ const descriptionResult = (0, import_react15.useMemo)(() => {
2396
+ return {
2397
+ ...description,
2398
+ text: description.targetDescription
2399
+ };
2400
+ }, [description]);
2401
+ const focusManager = (0, import_react15.useMemo)(
2402
+ () => ({
2403
+ active: hudState.focusTrapActive,
2404
+ target: hudState.hudTarget,
2405
+ popoverNode,
2406
+ setPopoverNode
2407
+ }),
2408
+ [hudState.focusTrapActive, hudState.hudTarget, popoverNode, setPopoverNode]
2409
+ );
2410
+ return {
2411
+ hudState,
2412
+ disableDefaultHud,
2413
+ overlay,
2414
+ popover,
2415
+ description: descriptionResult,
2416
+ focusManager,
2417
+ targetIssue,
2418
+ shouldLockBodyScroll,
2419
+ shortcutsEnabled
2420
+ };
2421
+ };
2422
+
2423
+ // src/hooks/useTourOverlay.ts
2424
+ var import_react16 = require("react");
2425
+ var DEFAULT_PADDING = 12;
2426
+ var DEFAULT_RADIUS = 12;
2427
+ var DEFAULT_EDGE_BUFFER = 0;
2428
+ var useTourOverlay = (options) => {
2429
+ const {
2430
+ target,
2431
+ padding = DEFAULT_PADDING,
2432
+ radius = DEFAULT_RADIUS,
2433
+ edgeBuffer = DEFAULT_EDGE_BUFFER,
2434
+ interactionMode = "passthrough"
2435
+ } = options;
2436
+ const hasShownRef = (0, import_react16.useRef)(false);
2437
+ const lastReadyTargetRef = (0, import_react16.useRef)(null);
2438
+ (0, import_react16.useEffect)(() => {
2439
+ if (!isBrowser) return;
2440
+ if (target.status === "ready") {
2441
+ hasShownRef.current = true;
2442
+ lastReadyTargetRef.current = {
2443
+ ...target,
2444
+ rect: target.rect ? { ...target.rect } : null
2445
+ };
2446
+ return;
2447
+ }
2448
+ if (target.status === "idle") {
2449
+ hasShownRef.current = false;
2450
+ lastReadyTargetRef.current = null;
2451
+ }
2452
+ }, [target]);
2453
+ const viewport = getViewportRect();
2454
+ const cachedTarget = lastReadyTargetRef.current;
2455
+ const highlightTarget = target.status === "ready" ? target : cachedTarget;
2456
+ const resolvedRect = highlightTarget?.rect ?? target.rect;
2457
+ const resolvedIsScreen = highlightTarget?.isScreen ?? target.isScreen;
2458
+ const expandedRect = resolvedIsScreen || !resolvedRect ? viewport : expandRect(resolvedRect, padding);
2459
+ const safeBuffer = Math.max(0, edgeBuffer);
2460
+ const insetTop = expandedRect.top <= 0 ? Math.min(safeBuffer, Math.max(0, expandedRect.height) / 2) : 0;
2461
+ const insetLeft = expandedRect.left <= 0 ? Math.min(safeBuffer, Math.max(0, expandedRect.width) / 2) : 0;
2462
+ const insetBottom = expandedRect.top + expandedRect.height >= viewport.height ? Math.min(safeBuffer, Math.max(0, expandedRect.height) / 2) : 0;
2463
+ const insetRight = expandedRect.left + expandedRect.width >= viewport.width ? Math.min(safeBuffer, Math.max(0, expandedRect.width) / 2) : 0;
2464
+ const highlightTop = expandedRect.top + insetTop;
2465
+ const highlightLeft = expandedRect.left + insetLeft;
2466
+ const highlightWidth = Math.max(
2467
+ 0,
2468
+ expandedRect.width - insetLeft - insetRight
2469
+ );
2470
+ const highlightHeight = Math.max(
2471
+ 0,
2472
+ expandedRect.height - insetTop - insetBottom
2473
+ );
2474
+ const highlightRadius = Math.max(
2475
+ 0,
2476
+ Math.min(radius, highlightWidth / 2, highlightHeight / 2)
2477
+ );
2478
+ const highlightCenterX = highlightLeft + highlightWidth / 2;
2479
+ const highlightCenterY = highlightTop + highlightHeight / 2;
2480
+ const hasHighlightBounds = !!highlightTarget && !resolvedIsScreen && highlightWidth > 0 && highlightHeight > 0;
2481
+ const highlightRect = hasHighlightBounds ? {
2482
+ top: highlightTop,
2483
+ left: highlightLeft,
2484
+ width: highlightWidth,
2485
+ height: highlightHeight,
2486
+ radius: highlightRadius
2487
+ } : null;
2488
+ const maskCapable = (0, import_react16.useMemo)(() => supportsMasking(), []);
2489
+ const isActive = target.status === "ready" || target.status === "resolving" && cachedTarget !== null;
2490
+ const shouldMask = maskCapable && isActive;
2491
+ const maskId = (0, import_react16.useMemo)(
2492
+ () => `tour-overlay-mask-${Math.random().toString(36).slice(2, 10)}`,
2493
+ []
2494
+ );
2495
+ const maskUrl = shouldMask ? `url(#${maskId})` : void 0;
2496
+ const fallbackSegments = (0, import_react16.useMemo)(() => {
2497
+ if (!isActive || shouldMask || !hasHighlightBounds || !highlightRect) {
2498
+ return null;
2499
+ }
2500
+ const topEdge = Math.max(0, Math.min(highlightRect.top, viewport.height));
2501
+ const bottomEdge = Math.max(
2502
+ topEdge,
2503
+ Math.min(highlightRect.top + highlightRect.height, viewport.height)
2504
+ );
2505
+ const leftEdge = Math.max(0, Math.min(highlightRect.left, viewport.width));
2506
+ const rightEdge = Math.max(
2507
+ leftEdge,
2508
+ Math.min(highlightRect.left + highlightRect.width, viewport.width)
2509
+ );
2510
+ const middleHeight = Math.max(0, bottomEdge - topEdge);
2511
+ return [
2512
+ {
2513
+ key: "top",
2514
+ top: 0,
2515
+ left: 0,
2516
+ width: viewport.width,
2517
+ height: topEdge
2518
+ },
2519
+ {
2520
+ key: "bottom",
2521
+ top: bottomEdge,
2522
+ left: 0,
2523
+ width: viewport.width,
2524
+ height: Math.max(0, viewport.height - bottomEdge)
2525
+ },
2526
+ {
2527
+ key: "left",
2528
+ top: topEdge,
2529
+ left: 0,
2530
+ width: leftEdge,
2531
+ height: middleHeight
2532
+ },
2533
+ {
2534
+ key: "right",
2535
+ top: topEdge,
2536
+ left: rightEdge,
2537
+ width: Math.max(0, viewport.width - rightEdge),
2538
+ height: middleHeight
2539
+ }
2540
+ ].filter((segment) => segment.width > 0 && segment.height > 0);
2541
+ }, [
2542
+ hasHighlightBounds,
2543
+ highlightRect,
2544
+ isActive,
2545
+ shouldMask,
2546
+ viewport.height,
2547
+ viewport.width
2548
+ ]);
2549
+ const blockerSegments = (0, import_react16.useMemo)(() => {
2550
+ if (interactionMode !== "block") {
2551
+ return null;
2552
+ }
2553
+ if (!hasHighlightBounds || !highlightRect) {
2554
+ return [
2555
+ {
2556
+ key: "blocker-full",
2557
+ top: 0,
2558
+ left: 0,
2559
+ width: viewport.width,
2560
+ height: viewport.height
2561
+ }
2562
+ ];
2563
+ }
2564
+ const topEdge = Math.max(0, Math.min(highlightRect.top, viewport.height));
2565
+ const bottomEdge = Math.max(
2566
+ topEdge,
2567
+ Math.min(highlightRect.top + highlightRect.height, viewport.height)
2568
+ );
2569
+ const leftEdge = Math.max(0, Math.min(highlightRect.left, viewport.width));
2570
+ const rightEdge = Math.max(
2571
+ leftEdge,
2572
+ Math.min(highlightRect.left + highlightRect.width, viewport.width)
2573
+ );
2574
+ const middleHeight = Math.max(0, bottomEdge - topEdge);
2575
+ return [
2576
+ {
2577
+ key: "blocker-top",
2578
+ top: 0,
2579
+ left: 0,
2580
+ width: viewport.width,
2581
+ height: topEdge
2582
+ },
2583
+ {
2584
+ key: "blocker-bottom",
2585
+ top: bottomEdge,
2586
+ left: 0,
2587
+ width: viewport.width,
2588
+ height: Math.max(0, viewport.height - bottomEdge)
2589
+ },
2590
+ {
2591
+ key: "blocker-left",
2592
+ top: topEdge,
2593
+ left: 0,
2594
+ width: leftEdge,
2595
+ height: middleHeight
2596
+ },
2597
+ {
2598
+ key: "blocker-right",
2599
+ top: topEdge,
2600
+ left: rightEdge,
2601
+ width: Math.max(0, viewport.width - rightEdge),
2602
+ height: middleHeight
2603
+ }
2604
+ ].filter((segment) => segment.width > 0 && segment.height > 0);
2605
+ }, [
2606
+ hasHighlightBounds,
2607
+ highlightRect,
2608
+ interactionMode,
2609
+ viewport.height,
2610
+ viewport.width
2611
+ ]);
2612
+ const showBaseOverlay = isActive && (shouldMask || !hasHighlightBounds);
2613
+ return {
2614
+ isActive,
2615
+ highlight: {
2616
+ rect: highlightRect,
2617
+ centerX: highlightCenterX,
2618
+ centerY: highlightCenterY,
2619
+ target: highlightTarget ?? null,
2620
+ isScreen: resolvedIsScreen
2621
+ },
2622
+ shouldMask,
2623
+ maskId: shouldMask ? maskId : null,
2624
+ maskUrl,
2625
+ fallbackSegments,
2626
+ blockerSegments,
2627
+ showBaseOverlay,
2628
+ viewport
2629
+ };
2630
+ };
2631
+
2632
+ // src/hooks/useTourFocusDominance.ts
2633
+ var DEFAULT_ENABLED = true;
2634
+ var useTourFocusDominance = (options = {}) => {
2635
+ const { enabled = DEFAULT_ENABLED } = options;
2636
+ const { state } = useTour();
2637
+ const isRunning = state?.status === "running";
2638
+ const active = Boolean(enabled && isRunning);
2639
+ return {
2640
+ active,
2641
+ suspendExternalFocusTrap: active
2642
+ };
2643
+ };
2644
+
2645
+ // src/adapters/radixDialog.ts
2646
+ var useRadixDialogAdapter = (options = {}) => {
2647
+ const { suspendExternalFocusTrap } = useTourFocusDominance(options);
2648
+ const { disableEscapeClose = false } = options;
2649
+ const preventDismiss = (event) => {
2650
+ if (suspendExternalFocusTrap) {
2651
+ event.preventDefault();
2652
+ }
2653
+ };
2654
+ const preventEscape = (event) => {
2655
+ if (suspendExternalFocusTrap && disableEscapeClose) {
2656
+ event.preventDefault();
2657
+ }
2658
+ };
2659
+ return {
2660
+ dialogProps: {
2661
+ modal: !suspendExternalFocusTrap
2662
+ },
2663
+ contentProps: {
2664
+ trapFocus: !suspendExternalFocusTrap,
2665
+ onInteractOutside: preventDismiss,
2666
+ onFocusOutside: preventDismiss,
2667
+ onEscapeKeyDown: preventEscape
2668
+ },
2669
+ suspendExternalFocusTrap
2670
+ };
2671
+ };
2672
+
2673
+ // src/hooks/useDelayAdvance.ts
2674
+ var import_react17 = require("react");
2675
+ var getTimestamp = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
2676
+ var useDelayAdvance = () => {
2677
+ const { delayInfo, activeStep, state } = useTour();
2678
+ const [now, setNow] = (0, import_react17.useState)(() => getTimestamp());
2679
+ (0, import_react17.useEffect)(() => {
2680
+ if (!delayInfo) return;
2681
+ if (!activeStep || activeStep.id !== delayInfo.stepId) return;
2682
+ if (!state || state.status !== "running") return;
2683
+ if (!isBrowser) return;
2684
+ let frameId = null;
2685
+ const tick = () => {
2686
+ setNow(getTimestamp());
2687
+ frameId = window.requestAnimationFrame(tick);
2688
+ };
2689
+ frameId = window.requestAnimationFrame(tick);
2690
+ return () => {
2691
+ if (frameId !== null) {
2692
+ window.cancelAnimationFrame(frameId);
2693
+ }
2694
+ };
2695
+ }, [delayInfo, activeStep, state]);
2696
+ (0, import_react17.useEffect)(() => {
2697
+ if (!delayInfo) {
2698
+ setNow(getTimestamp());
2699
+ }
2700
+ }, [delayInfo]);
2701
+ return (0, import_react17.useMemo)(() => {
2702
+ const matchingStep = !!delayInfo && !!activeStep && activeStep.id === delayInfo.stepId;
2703
+ const isRunning = matchingStep && state?.status === "running";
2704
+ if (!delayInfo) {
2705
+ return {
2706
+ isActive: false,
2707
+ flowId: null,
2708
+ stepId: null,
2709
+ totalMs: 0,
2710
+ remainingMs: 0,
2711
+ elapsedMs: 0,
2712
+ fractionElapsed: 0,
2713
+ fractionRemaining: 1,
2714
+ startedAt: null,
2715
+ endsAt: null
2716
+ };
2717
+ }
2718
+ if (!isRunning) {
2719
+ return {
2720
+ isActive: false,
2721
+ flowId: delayInfo.flowId,
2722
+ stepId: delayInfo.stepId,
2723
+ totalMs: delayInfo.totalMs,
2724
+ remainingMs: delayInfo.totalMs,
2725
+ elapsedMs: 0,
2726
+ fractionElapsed: 0,
2727
+ fractionRemaining: 1,
2728
+ startedAt: delayInfo.startedAt,
2729
+ endsAt: delayInfo.endsAt
2730
+ };
2731
+ }
2732
+ const clampedNow = Math.min(now, delayInfo.endsAt);
2733
+ const elapsedMs = Math.max(0, clampedNow - delayInfo.startedAt);
2734
+ const remainingMs = Math.max(0, delayInfo.endsAt - now);
2735
+ const totalMs = delayInfo.totalMs;
2736
+ const fractionElapsed = totalMs > 0 ? Math.min(1, Math.max(0, elapsedMs / totalMs)) : 1;
2737
+ const fractionRemaining = 1 - fractionElapsed;
2738
+ return {
2739
+ isActive: true,
2740
+ flowId: delayInfo.flowId,
2741
+ stepId: delayInfo.stepId,
2742
+ totalMs,
2743
+ remainingMs,
2744
+ elapsedMs,
2745
+ fractionElapsed,
2746
+ fractionRemaining,
2747
+ startedAt: delayInfo.startedAt,
2748
+ endsAt: delayInfo.endsAt
2749
+ };
2750
+ }, [activeStep, delayInfo, now, state]);
2751
+ };
2752
+
2753
+ // src/components/OverlayBackdrop.tsx
2754
+ var import_react18 = require("react");
2755
+ var import_react_dom = require("react-dom");
2756
+ var import_react19 = require("motion/react");
2757
+ var import_jsx_runtime3 = require("react/jsx-runtime");
2758
+ var styles = {
2759
+ root: {
2760
+ position: "fixed",
2761
+ inset: 0,
2762
+ pointerEvents: "none"
2763
+ },
2764
+ svgMask: {
2765
+ position: "absolute"
2766
+ },
2767
+ overlay: {
2768
+ position: "absolute",
2769
+ transformOrigin: "center",
2770
+ inset: 0,
2771
+ pointerEvents: "none"
2772
+ },
2773
+ segment: {
2774
+ position: "absolute",
2775
+ transformOrigin: "center",
2776
+ pointerEvents: "none"
2777
+ },
2778
+ blockerContainer: {
2779
+ position: "absolute",
2780
+ inset: 0,
2781
+ pointerEvents: "none"
2782
+ },
2783
+ blockerSegment: {
2784
+ position: "absolute",
2785
+ pointerEvents: "auto"
2786
+ },
2787
+ highlightRing: {
2788
+ position: "absolute",
2789
+ transformOrigin: "center",
2790
+ pointerEvents: "none"
2791
+ }
2792
+ };
2793
+ var DEFAULT_HIGHLIGHT_TRANSITION = {
2794
+ duration: 0.35,
2795
+ ease: "easeOut",
2796
+ type: "spring",
2797
+ damping: 25,
2798
+ stiffness: 300,
2799
+ mass: 0.7
2800
+ };
2801
+ var DEFAULT_HIGHLIGHT_COLLAPSE_TRANSITION = {
2802
+ duration: 0.18,
2803
+ ease: "easeOut",
2804
+ type: "tween"
2805
+ };
2806
+ var DEFAULT_OVERLAY_TRANSITION = {
2807
+ duration: 0.35,
2808
+ ease: "easeOut"
2809
+ };
2810
+ var OverlayBackdrop = ({
2811
+ overlay,
2812
+ zIndex = 1e3,
2813
+ color,
2814
+ colorClassName: _colorClassName,
2815
+ opacity = 1,
2816
+ shadow,
2817
+ blurAmount,
2818
+ ariaHidden,
2819
+ rootClassName,
2820
+ overlayClassName,
2821
+ segmentClassName,
2822
+ ringClassName,
2823
+ showHighlightRing = true,
2824
+ showInteractionBlocker = true,
2825
+ transitionsOverride
2826
+ }) => {
2827
+ if (!isBrowser) return null;
2828
+ const host = portalHost();
2829
+ if (!host) return null;
2830
+ const adapter = useAnimationAdapter();
2831
+ const {
2832
+ highlight,
2833
+ shouldMask,
2834
+ maskId,
2835
+ maskUrl,
2836
+ fallbackSegments,
2837
+ blockerSegments,
2838
+ showBaseOverlay,
2839
+ isActive,
2840
+ viewport
2841
+ } = overlay;
2842
+ const hasHighlightBounds = Boolean(highlight.rect);
2843
+ const prevScreenTargetRef = (0, import_react18.useRef)(null);
2844
+ const shouldSnapHighlight = prevScreenTargetRef.current === true && !highlight.isScreen && hasHighlightBounds;
2845
+ (0, import_react18.useEffect)(() => {
2846
+ prevScreenTargetRef.current = highlight.isScreen;
2847
+ }, [highlight.isScreen]);
2848
+ const resolvedBlur = typeof blurAmount === "number" ? `${blurAmount}px` : "0px";
2849
+ const defaultInsetShadow = "inset 0 0 0 2px rgba(56,189,248,0.4), inset 0 0 0 8px rgba(15,23,42,0.3)";
2850
+ const highlightAppearance = shadow ? { boxShadow: shadow } : { boxShadow: defaultInsetShadow };
2851
+ const { MotionDiv, MotionSvg, MotionDefs, MotionMask, MotionRect } = adapter.components;
2852
+ const highlightTransition = transitionsOverride?.overlayHighlight ?? adapter.transitions.overlayHighlight ?? DEFAULT_HIGHLIGHT_TRANSITION;
2853
+ const snapTransition = { type: "tween", duration: 0 };
2854
+ const resolvedHighlightTransition = shouldSnapHighlight ? snapTransition : highlightTransition;
2855
+ const overlayTransition = transitionsOverride?.overlayFade ?? adapter.transitions.overlayFade ?? DEFAULT_OVERLAY_TRANSITION;
2856
+ const highlightCollapseTransition = highlight.isScreen ? snapTransition : transitionsOverride?.overlayHighlightCollapse ?? DEFAULT_HIGHLIGHT_COLLAPSE_TRANSITION;
2857
+ const highlightRectTransition = hasHighlightBounds ? resolvedHighlightTransition : highlightCollapseTransition;
2858
+ const highlightRectAnimation = shouldMask ? {
2859
+ x: highlight.rect?.left ?? highlight.centerX,
2860
+ y: highlight.rect?.top ?? highlight.centerY,
2861
+ width: highlight.rect?.width ?? 0,
2862
+ height: highlight.rect?.height ?? 0,
2863
+ rx: highlight.rect?.radius ?? 0,
2864
+ ry: highlight.rect?.radius ?? 0
2865
+ } : {
2866
+ x: highlight.centerX,
2867
+ y: highlight.centerY,
2868
+ width: 0,
2869
+ height: 0,
2870
+ rx: 0,
2871
+ ry: 0
2872
+ };
2873
+ const highlightRingAnimation = hasHighlightBounds ? {
2874
+ top: highlight.centerY,
2875
+ left: highlight.centerX,
2876
+ width: highlight.rect?.width ?? 0,
2877
+ height: highlight.rect?.height ?? 0,
2878
+ borderRadius: highlight.rect?.radius ?? 0,
2879
+ opacity: 1,
2880
+ transform: "translate(-50%, -50%)"
2881
+ } : {
2882
+ top: highlight.centerY,
2883
+ left: highlight.centerX,
2884
+ width: 0,
2885
+ height: 0,
2886
+ borderRadius: 0,
2887
+ opacity: 0,
2888
+ transform: "translate(-50%, -50%)"
2889
+ };
2890
+ const overlayStyle = {};
2891
+ if (shouldMask) {
2892
+ overlayStyle.maskRepeat = "no-repeat";
2893
+ overlayStyle.WebkitMaskRepeat = "no-repeat";
2894
+ overlayStyle.maskSize = "100% 100%";
2895
+ overlayStyle.WebkitMaskSize = "100% 100%";
2896
+ }
2897
+ if (maskUrl) {
2898
+ overlayStyle.mask = maskUrl;
2899
+ overlayStyle.WebkitMask = maskUrl;
2900
+ }
2901
+ if (color) {
2902
+ overlayStyle.backgroundColor = color;
2903
+ }
2904
+ return (0, import_react_dom.createPortal)(
2905
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2906
+ MotionDiv,
2907
+ {
2908
+ className: rootClassName,
2909
+ style: { ...styles.root, zIndex },
2910
+ "aria-hidden": ariaHidden,
2911
+ "data-tour-overlay": "",
2912
+ children: [
2913
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react19.AnimatePresence, { mode: "popLayout", children: shouldMask ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2914
+ MotionSvg,
2915
+ {
2916
+ width: "0",
2917
+ height: "0",
2918
+ "aria-hidden": true,
2919
+ focusable: "false",
2920
+ style: styles.svgMask,
2921
+ initial: { opacity: 0 },
2922
+ animate: { opacity: 1 },
2923
+ exit: { opacity: 0 },
2924
+ transition: overlayTransition,
2925
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MotionDefs, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2926
+ MotionMask,
2927
+ {
2928
+ id: maskId ?? void 0,
2929
+ initial: false,
2930
+ maskUnits: "userSpaceOnUse",
2931
+ maskContentUnits: "userSpaceOnUse",
2932
+ x: "0",
2933
+ y: "0",
2934
+ animate: { width: viewport.width, height: viewport.height },
2935
+ transition: highlightTransition,
2936
+ children: [
2937
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2938
+ MotionRect,
2939
+ {
2940
+ x: "0",
2941
+ y: "0",
2942
+ initial: false,
2943
+ animate: {
2944
+ width: viewport.width,
2945
+ height: viewport.height,
2946
+ opacity: 1
2947
+ },
2948
+ fill: "white",
2949
+ transition: highlightTransition,
2950
+ exit: { opacity: 0 }
2951
+ }
2952
+ ),
2953
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2954
+ MotionRect,
2955
+ {
2956
+ initial: false,
2957
+ animate: highlightRectAnimation,
2958
+ exit: {
2959
+ x: highlight.centerX,
2960
+ y: highlight.centerY
2961
+ },
2962
+ transition: highlightRectTransition,
2963
+ fill: "black"
2964
+ }
2965
+ )
2966
+ ]
2967
+ }
2968
+ ) })
2969
+ },
2970
+ "tour-mask"
2971
+ ) : null }),
2972
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react19.AnimatePresence, { mode: "popLayout", children: showBaseOverlay ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2973
+ MotionDiv,
2974
+ {
2975
+ className: overlayClassName,
2976
+ "data-tour-overlay-layer": "backdrop",
2977
+ style: {
2978
+ ...styles.overlay,
2979
+ ...overlayStyle,
2980
+ zIndex,
2981
+ backgroundColor: color ?? void 0
2982
+ },
2983
+ initial: {
2984
+ opacity: 0,
2985
+ transition: overlayTransition,
2986
+ backdropFilter: `blur(0px)`
2987
+ },
2988
+ animate: {
2989
+ opacity,
2990
+ backdropFilter: `blur(${resolvedBlur})`
2991
+ },
2992
+ exit: {
2993
+ opacity: 0,
2994
+ backdropFilter: `blur(${resolvedBlur})`
2995
+ },
2996
+ transition: overlayTransition
2997
+ },
2998
+ "tour-overlay"
2999
+ ) : null }),
3000
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react19.AnimatePresence, { mode: "popLayout", children: fallbackSegments ? fallbackSegments.map((segment) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3001
+ MotionDiv,
3002
+ {
3003
+ className: segmentClassName,
3004
+ "data-tour-overlay-layer": "segment",
3005
+ style: {
3006
+ ...styles.segment,
3007
+ zIndex,
3008
+ top: segment.top,
3009
+ left: segment.left,
3010
+ width: segment.width,
3011
+ height: segment.height,
3012
+ backgroundColor: color ?? void 0
3013
+ },
3014
+ initial: {
3015
+ opacity: 0,
3016
+ backdropFilter: `blur(0px)`
3017
+ },
3018
+ animate: {
3019
+ opacity,
3020
+ backdropFilter: `blur(${resolvedBlur})`
3021
+ },
3022
+ exit: {
3023
+ opacity: 0,
3024
+ backdropFilter: `blur(0px)`
3025
+ },
3026
+ transition: overlayTransition
3027
+ },
3028
+ `tour-overlay-fallback-${segment.key}`
3029
+ )) : null }),
3030
+ showInteractionBlocker && blockerSegments ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3031
+ "div",
3032
+ {
3033
+ style: { ...styles.blockerContainer, zIndex },
3034
+ "data-tour-overlay-layer": "interaction-blocker",
3035
+ "aria-hidden": true,
3036
+ children: blockerSegments.map((segment) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3037
+ "div",
3038
+ {
3039
+ style: {
3040
+ ...styles.blockerSegment,
3041
+ top: segment.top,
3042
+ left: segment.left,
3043
+ width: segment.width,
3044
+ height: segment.height
3045
+ }
3046
+ },
3047
+ segment.key
3048
+ ))
3049
+ }
3050
+ ) : null,
3051
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react19.AnimatePresence, { mode: "popLayout", children: showHighlightRing && isActive && hasHighlightBounds ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3052
+ MotionDiv,
3053
+ {
3054
+ className: ringClassName,
3055
+ style: {
3056
+ ...styles.highlightRing,
3057
+ zIndex: zIndex + 1,
3058
+ ...highlightAppearance
3059
+ },
3060
+ "data-tour-overlay-layer": "highlight-ring",
3061
+ initial: false,
3062
+ animate: highlightRingAnimation,
3063
+ exit: {
3064
+ opacity: 0,
3065
+ transition: {
3066
+ duration: 0.35,
3067
+ ease: "easeOut"
3068
+ }
3069
+ },
3070
+ transition: resolvedHighlightTransition
3071
+ },
3072
+ "tour-ring"
3073
+ ) : null })
3074
+ ]
3075
+ }
3076
+ ),
3077
+ host
3078
+ );
3079
+ };
3080
+
3081
+ // src/components/TourPopoverPortal.tsx
3082
+ var import_react20 = require("react");
3083
+ var import_react_dom2 = require("react-dom");
3084
+ var import_dom13 = require("@floating-ui/dom");
3085
+ var FLOATING_OFFSET = 8;
3086
+ var DOCKED_MARGIN = 24;
3087
+ var MOBILE_BREAKPOINT = 640;
3088
+ var MOBILE_HEIGHT_BREAKPOINT = 560;
3089
+ var MOBILE_HORIZONTAL_GUTTER = 12;
3090
+ var DEFAULT_POPOVER_ENTRANCE_TRANSITION = {
3091
+ duration: 0.25,
3092
+ ease: "easeOut"
3093
+ };
3094
+ var DEFAULT_POPOVER_EXIT_TRANSITION = {
3095
+ duration: 0.2,
3096
+ ease: "easeOut"
3097
+ };
3098
+ var DEFAULT_POPOVER_CONTENT_TRANSITION = {
3099
+ duration: 0.4,
3100
+ ease: "easeOut"
3101
+ };
3102
+ var floatingPositionCache = /* @__PURE__ */ new Map();
3103
+ var getFloatingCacheKey = (target) => {
3104
+ if (target.stepId) {
3105
+ return `step:${target.stepId}`;
3106
+ }
3107
+ if (target.isScreen) {
3108
+ return "screen";
3109
+ }
3110
+ return null;
3111
+ };
3112
+ var TourPopoverPortal = ({
3113
+ target,
3114
+ children,
3115
+ offset = 16,
3116
+ width,
3117
+ maxWidth,
3118
+ zIndex = 1001,
3119
+ placement,
3120
+ role,
3121
+ ariaLabel,
3122
+ ariaDescribedBy,
3123
+ ariaModal,
3124
+ descriptionId,
3125
+ descriptionText,
3126
+ onContainerChange,
3127
+ layoutId,
3128
+ containerComponent,
3129
+ contentComponent,
3130
+ transitionsOverride
3131
+ }) => {
3132
+ if (!isBrowser) return null;
3133
+ const host = portalHost();
3134
+ if (!host) return null;
3135
+ const adapter = useAnimationAdapter();
3136
+ const Container = containerComponent ?? adapter.components.MotionDiv;
3137
+ const Content = contentComponent ?? adapter.components.MotionDiv;
3138
+ const popoverEntranceTransition = transitionsOverride?.popoverEntrance ?? adapter.transitions.popoverEntrance ?? DEFAULT_POPOVER_ENTRANCE_TRANSITION;
3139
+ const popoverExitTransition = transitionsOverride?.popoverExit ?? adapter.transitions.popoverExit ?? DEFAULT_POPOVER_EXIT_TRANSITION;
3140
+ const popoverContentTransition = transitionsOverride?.popoverContent ?? adapter.transitions.popoverContent ?? DEFAULT_POPOVER_CONTENT_TRANSITION;
3141
+ const viewport = useViewportRect();
3142
+ const prefersMobileLayout = viewport.width <= MOBILE_BREAKPOINT || viewport.height <= MOBILE_HEIGHT_BREAKPOINT;
3143
+ const prefersMobileRef = (0, import_react20.useRef)(prefersMobileLayout);
3144
+ (0, import_react20.useEffect)(() => {
3145
+ prefersMobileRef.current = prefersMobileLayout;
3146
+ }, [prefersMobileLayout]);
3147
+ const lastReadyTargetRef = (0, import_react20.useRef)(null);
3148
+ (0, import_react20.useEffect)(() => {
3149
+ if (target.status === "ready" && target.rect) {
3150
+ lastReadyTargetRef.current = {
3151
+ rect: { ...target.rect },
3152
+ isScreen: target.isScreen
3153
+ };
3154
+ } else if (target.status === "idle") {
3155
+ lastReadyTargetRef.current = null;
3156
+ }
3157
+ }, [target.isScreen, target.rect, target.status]);
3158
+ const cachedTarget = lastReadyTargetRef.current;
3159
+ const resolvedRect = target.rect ?? target.lastResolvedRect ?? cachedTarget?.rect ?? null;
3160
+ const resolvedIsScreen = target.status === "ready" ? target.isScreen : cachedTarget?.isScreen ?? target.isScreen;
3161
+ const fallbackRect = resolvedRect ?? viewport;
3162
+ const fallbackIsScreen = resolvedIsScreen;
3163
+ const [floatingSize, setFloatingSize] = (0, import_react20.useState)(null);
3164
+ const clampVertical = (value) => Math.min(viewport.height - 24, Math.max(24, value));
3165
+ const clampHorizontal = (value) => Math.min(viewport.width - 24, Math.max(24, value));
3166
+ const screenCenteredTop = viewport.height / 2 - (floatingSize?.height ?? 0) / 2;
3167
+ const screenCenteredLeft = viewport.width / 2 - (floatingSize?.width ?? 0) / 2;
3168
+ const floatingWidth = floatingSize?.width ?? 0;
3169
+ const baseTop = fallbackIsScreen ? screenCenteredTop : fallbackRect.top + fallbackRect.height + offset;
3170
+ const top = fallbackIsScreen ? clampVertical(screenCenteredTop) : clampVertical(baseTop);
3171
+ const leftBase = fallbackIsScreen ? screenCenteredLeft : fallbackRect.left + fallbackRect.width / 2 - floatingWidth / 2;
3172
+ const left = clampHorizontal(leftBase);
3173
+ const fallbackTransform = "translate3d(0px, 0px, 0px)";
3174
+ const fallbackPosition = (0, import_react20.useMemo)(
3175
+ () => ({
3176
+ top,
3177
+ left,
3178
+ transform: fallbackTransform
3179
+ }),
3180
+ [fallbackTransform, left, top]
3181
+ );
3182
+ const centerInitialPosition = (0, import_react20.useMemo)(
3183
+ () => ({
3184
+ top: viewport.height / 2,
3185
+ left: viewport.width / 2,
3186
+ transform: "translate3d(-50%, -50%, 0px)"
3187
+ }),
3188
+ [viewport.height, viewport.width]
3189
+ );
3190
+ const floatingRef = (0, import_react20.useRef)(null);
3191
+ const cachedFloatingPositionRef = (0, import_react20.useRef)(null);
3192
+ const appliedFloatingCacheRef = (0, import_react20.useRef)(null);
3193
+ const deferredScreenSnapRef = (0, import_react20.useRef)(null);
3194
+ const [layoutMode, setLayoutMode] = (0, import_react20.useState)(
3195
+ () => prefersMobileLayout ? "mobile" : "floating"
3196
+ );
3197
+ const [floatingPosition, setFloatingPosition] = (0, import_react20.useState)(fallbackPosition);
3198
+ const [dragPosition, setDragPosition] = (0, import_react20.useState)(null);
3199
+ const [isDragging, setIsDragging] = (0, import_react20.useState)(false);
3200
+ const dragStateRef = (0, import_react20.useRef)(null);
3201
+ const overflowRetryRef = (0, import_react20.useRef)({
3202
+ stepId: null,
3203
+ attempts: 0
3204
+ });
3205
+ const overflowRetryTimeoutRef = (0, import_react20.useRef)(null);
3206
+ (0, import_react20.useLayoutEffect)(() => {
3207
+ if (!isBrowser) return;
3208
+ const node = floatingRef.current;
3209
+ if (!node) return;
3210
+ const updateSize = () => {
3211
+ const rect = node.getBoundingClientRect();
3212
+ setFloatingSize({ width: rect.width, height: rect.height });
3213
+ };
3214
+ updateSize();
3215
+ if (typeof ResizeObserver === "undefined") return;
3216
+ const observer = new ResizeObserver(updateSize);
3217
+ observer.observe(node);
3218
+ return () => observer.disconnect();
3219
+ }, [target.stepId]);
3220
+ const resolvedPlacement = placement ?? "bottom";
3221
+ const isAutoPlacement = resolvedPlacement.startsWith("auto");
3222
+ const autoAlignment = resolvedPlacement.endsWith(
3223
+ "-start"
3224
+ ) ? "start" : resolvedPlacement.endsWith("-end") ? "end" : void 0;
3225
+ (0, import_react20.useEffect)(() => {
3226
+ setDragPosition(null);
3227
+ setLayoutMode(prefersMobileRef.current ? "mobile" : "floating");
3228
+ cachedFloatingPositionRef.current = null;
3229
+ appliedFloatingCacheRef.current = null;
3230
+ }, [target.stepId]);
3231
+ (0, import_react20.useEffect)(() => {
3232
+ if (layoutMode !== "manual") {
3233
+ setDragPosition(null);
3234
+ }
3235
+ }, [layoutMode]);
3236
+ (0, import_react20.useEffect)(() => {
3237
+ cachedFloatingPositionRef.current = floatingPosition;
3238
+ const cacheKey = getFloatingCacheKey(target);
3239
+ if (cacheKey) {
3240
+ floatingPositionCache.set(cacheKey, floatingPosition);
3241
+ }
3242
+ }, [floatingPosition, target.isScreen, target.stepId]);
3243
+ const dockedPosition = (0, import_react20.useMemo)(
3244
+ () => ({
3245
+ top: viewport.height - DOCKED_MARGIN,
3246
+ left: viewport.width - DOCKED_MARGIN,
3247
+ transform: "translate3d(-100%, -100%, 0px)"
3248
+ }),
3249
+ [viewport.height, viewport.width]
3250
+ );
3251
+ const mobilePosition = (0, import_react20.useMemo)(
3252
+ () => ({
3253
+ top: viewport.height - MOBILE_HORIZONTAL_GUTTER,
3254
+ left: viewport.width / 2,
3255
+ transform: "translate3d(-50%, -100%, 0px)"
3256
+ }),
3257
+ [viewport.height, viewport.width]
3258
+ );
3259
+ (0, import_react20.useEffect)(() => {
3260
+ if (layoutMode === "docked") {
3261
+ setFloatingPosition(dockedPosition);
3262
+ }
3263
+ }, [dockedPosition, layoutMode]);
3264
+ (0, import_react20.useEffect)(() => {
3265
+ if (layoutMode === "mobile") {
3266
+ setFloatingPosition(mobilePosition);
3267
+ }
3268
+ }, [layoutMode, mobilePosition]);
3269
+ (0, import_react20.useEffect)(() => {
3270
+ if (prefersMobileLayout) {
3271
+ if (layoutMode !== "mobile") {
3272
+ setLayoutMode("mobile");
3273
+ setDragPosition(null);
3274
+ }
3275
+ return;
3276
+ }
3277
+ if (layoutMode === "mobile") {
3278
+ setLayoutMode("floating");
3279
+ setFloatingPosition(fallbackPosition);
3280
+ }
3281
+ }, [fallbackPosition, layoutMode, prefersMobileLayout]);
3282
+ (0, import_react20.useEffect)(() => {
3283
+ if (layoutMode !== "floating") return;
3284
+ const stepId = target.stepId;
3285
+ if (!stepId) return;
3286
+ if (appliedFloatingCacheRef.current === stepId) return;
3287
+ const cacheKey = getFloatingCacheKey(target);
3288
+ const cached = cacheKey ? floatingPositionCache.get(cacheKey) ?? null : null;
3289
+ if (cached) {
3290
+ appliedFloatingCacheRef.current = stepId;
3291
+ setFloatingPosition(cached);
3292
+ return;
3293
+ }
3294
+ appliedFloatingCacheRef.current = stepId;
3295
+ if (target.status !== "ready" || target.isScreen) {
3296
+ setFloatingPosition(fallbackPosition);
3297
+ }
3298
+ }, [
3299
+ fallbackPosition,
3300
+ layoutMode,
3301
+ target.isScreen,
3302
+ target.status,
3303
+ target.stepId
3304
+ ]);
3305
+ const shouldDeferScreenSnap = layoutMode === "floating" && target.isScreen && Boolean(layoutId);
3306
+ (0, import_react20.useEffect)(() => {
3307
+ return () => {
3308
+ if (deferredScreenSnapRef.current !== null) {
3309
+ cancelAnimationFrame(deferredScreenSnapRef.current);
3310
+ deferredScreenSnapRef.current = null;
3311
+ }
3312
+ };
3313
+ }, []);
3314
+ (0, import_react20.useLayoutEffect)(() => {
3315
+ if (layoutMode !== "floating") return;
3316
+ if (target.status === "ready" && !target.isScreen) return;
3317
+ if (shouldDeferScreenSnap) return;
3318
+ setFloatingPosition(fallbackPosition);
3319
+ }, [
3320
+ fallbackPosition,
3321
+ layoutMode,
3322
+ shouldDeferScreenSnap,
3323
+ target.isScreen,
3324
+ target.status
3325
+ ]);
3326
+ (0, import_react20.useEffect)(() => {
3327
+ if (!shouldDeferScreenSnap) return;
3328
+ if (deferredScreenSnapRef.current !== null) {
3329
+ cancelAnimationFrame(deferredScreenSnapRef.current);
3330
+ deferredScreenSnapRef.current = null;
3331
+ }
3332
+ let nextFrame = null;
3333
+ deferredScreenSnapRef.current = requestAnimationFrame(() => {
3334
+ nextFrame = requestAnimationFrame(() => {
3335
+ setFloatingPosition(fallbackPosition);
3336
+ deferredScreenSnapRef.current = null;
3337
+ if (nextFrame !== null) {
3338
+ cancelAnimationFrame(nextFrame);
3339
+ }
3340
+ });
3341
+ });
3342
+ return () => {
3343
+ if (deferredScreenSnapRef.current !== null) {
3344
+ cancelAnimationFrame(deferredScreenSnapRef.current);
3345
+ deferredScreenSnapRef.current = null;
3346
+ }
3347
+ if (nextFrame !== null) {
3348
+ cancelAnimationFrame(nextFrame);
3349
+ nextFrame = null;
3350
+ }
3351
+ };
3352
+ }, [fallbackPosition, shouldDeferScreenSnap]);
3353
+ (0, import_react20.useEffect)(() => {
3354
+ return () => {
3355
+ if (overflowRetryTimeoutRef.current !== null) {
3356
+ window.clearTimeout(overflowRetryTimeoutRef.current);
3357
+ }
3358
+ };
3359
+ }, []);
3360
+ (0, import_react20.useLayoutEffect)(() => {
3361
+ if (!isBrowser) return;
3362
+ const floatingEl = floatingRef.current;
3363
+ const rectInfo = target.rect;
3364
+ if (!floatingEl) return;
3365
+ if (target.status !== "ready") return;
3366
+ if (!rectInfo || target.isScreen) return;
3367
+ if (layoutMode === "mobile" || layoutMode === "manual") return;
3368
+ const cancelState = { cancelled: false };
3369
+ const retryState = overflowRetryRef.current;
3370
+ const currentStepId = target.stepId ?? null;
3371
+ if (retryState.stepId !== currentStepId) {
3372
+ retryState.stepId = currentStepId;
3373
+ retryState.attempts = 0;
3374
+ }
3375
+ const clearRetryTimeout = () => {
3376
+ if (overflowRetryTimeoutRef.current !== null) {
3377
+ window.clearTimeout(overflowRetryTimeoutRef.current);
3378
+ overflowRetryTimeoutRef.current = null;
3379
+ }
3380
+ };
3381
+ const virtualReference = {
3382
+ contextElement: target.element ?? void 0,
3383
+ getBoundingClientRect: () => DOMRectReadOnly.fromRect({
3384
+ width: rectInfo.width,
3385
+ height: rectInfo.height,
3386
+ x: rectInfo.left,
3387
+ y: rectInfo.top
3388
+ })
3389
+ };
3390
+ const computePlacement = isAutoPlacement ? void 0 : resolvedPlacement;
3391
+ const middleware = [
3392
+ (0, import_dom13.offset)(offset),
3393
+ ...isAutoPlacement ? [
3394
+ (0, import_dom13.autoPlacement)({
3395
+ padding: FLOATING_OFFSET,
3396
+ alignment: autoAlignment
3397
+ })
3398
+ ] : [(0, import_dom13.flip)({ padding: FLOATING_OFFSET })],
3399
+ (0, import_dom13.shift)({ padding: FLOATING_OFFSET })
3400
+ ];
3401
+ const updatePosition = async () => {
3402
+ const { x, y } = await (0, import_dom13.computePosition)(virtualReference, floatingEl, {
3403
+ placement: computePlacement,
3404
+ strategy: "fixed",
3405
+ middleware
3406
+ });
3407
+ if (cancelState.cancelled) return;
3408
+ const floatingBox = floatingEl.getBoundingClientRect();
3409
+ const viewportRect = getViewportRect();
3410
+ const viewportTop = viewportRect.top;
3411
+ const viewportBottom = viewportRect.top + viewportRect.height;
3412
+ const viewportLeft = viewportRect.left;
3413
+ const viewportRight = viewportRect.left + viewportRect.width;
3414
+ const overflowLeft = Math.max(0, viewportRect.left + FLOATING_OFFSET - x);
3415
+ const overflowRight = Math.max(
3416
+ 0,
3417
+ x + floatingBox.width + FLOATING_OFFSET - (viewportRect.left + viewportRect.width)
3418
+ );
3419
+ const overflowTop = Math.max(0, viewportRect.top + FLOATING_OFFSET - y);
3420
+ const overflowBottom = Math.max(
3421
+ 0,
3422
+ y + floatingBox.height + FLOATING_OFFSET - (viewportRect.top + viewportRect.height)
3423
+ );
3424
+ const maxOverflow = Math.max(
3425
+ overflowTop,
3426
+ overflowRight,
3427
+ overflowBottom,
3428
+ overflowLeft
3429
+ );
3430
+ const viewportHeight = viewportRect.height;
3431
+ const viewportWidth = viewportRect.width;
3432
+ const overflowThreshold = Math.max(
3433
+ FLOATING_OFFSET * 2,
3434
+ viewportHeight * 0.05,
3435
+ viewportWidth * 0.05
3436
+ );
3437
+ const targetRect = rectInfo;
3438
+ const intersectsViewport = targetRect.bottom > viewportTop + FLOATING_OFFSET && targetRect.top < viewportBottom - FLOATING_OFFSET && targetRect.right > viewportLeft + FLOATING_OFFSET && targetRect.left < viewportRight - FLOATING_OFFSET;
3439
+ const spaceAbove = targetRect.top - viewportTop;
3440
+ const spaceBelow = viewportBottom - targetRect.bottom;
3441
+ const spaceLeft = targetRect.left - viewportLeft;
3442
+ const spaceRight = viewportRight - targetRect.right;
3443
+ const minSpaceNeeded = floatingBox.height + FLOATING_OFFSET * 2;
3444
+ const hasVerticalSpace = spaceAbove >= minSpaceNeeded || spaceBelow >= minSpaceNeeded;
3445
+ const hasHorizontalSpace = spaceLeft >= minSpaceNeeded || spaceRight >= minSpaceNeeded;
3446
+ const targetNearlyFillsViewport = !target.isScreen && !hasVerticalSpace && !hasHorizontalSpace;
3447
+ const shouldDock = intersectsViewport && (targetNearlyFillsViewport || maxOverflow > overflowThreshold);
3448
+ if (shouldDock) {
3449
+ if (!targetNearlyFillsViewport && retryState.attempts < 2) {
3450
+ retryState.attempts += 1;
3451
+ clearRetryTimeout();
3452
+ overflowRetryTimeoutRef.current = window.setTimeout(() => {
3453
+ overflowRetryTimeoutRef.current = null;
3454
+ if (cancelState.cancelled) return;
3455
+ void updatePosition();
3456
+ }, 120);
3457
+ return;
3458
+ }
3459
+ retryState.attempts = 0;
3460
+ if (layoutMode !== "docked") {
3461
+ setLayoutMode("docked");
3462
+ setFloatingPosition(dockedPosition);
3463
+ }
3464
+ return;
3465
+ }
3466
+ retryState.attempts = 0;
3467
+ if (layoutMode !== "floating") {
3468
+ setLayoutMode("floating");
3469
+ }
3470
+ setFloatingPosition({
3471
+ top: y,
3472
+ left: x,
3473
+ transform: "translate3d(0px, 0px, 0px)"
3474
+ });
3475
+ };
3476
+ void updatePosition();
3477
+ return () => {
3478
+ cancelState.cancelled = true;
3479
+ clearRetryTimeout();
3480
+ };
3481
+ }, [
3482
+ autoAlignment,
3483
+ dockedPosition,
3484
+ isAutoPlacement,
3485
+ layoutMode,
3486
+ offset,
3487
+ resolvedPlacement,
3488
+ target.element,
3489
+ target.isScreen,
3490
+ target.lastUpdated,
3491
+ target.status,
3492
+ target.stepId
3493
+ ]);
3494
+ (0, import_react20.useLayoutEffect)(() => {
3495
+ if (layoutMode !== "manual" || !dragPosition) return;
3496
+ setFloatingPosition({
3497
+ top: dragPosition.top,
3498
+ left: dragPosition.left,
3499
+ transform: "translate3d(0px, 0px, 0px)"
3500
+ });
3501
+ }, [dragPosition, layoutMode]);
3502
+ const clampToViewport = (rawLeft, rawTop) => {
3503
+ const rect = getViewportRect();
3504
+ const floatingEl = floatingRef.current;
3505
+ const floatingElWidth = floatingEl?.offsetWidth ?? 0;
3506
+ const floatingElHeight = floatingEl?.offsetHeight ?? 0;
3507
+ const minLeft = rect.left + FLOATING_OFFSET;
3508
+ const maxLeft = rect.left + rect.width - floatingElWidth - FLOATING_OFFSET;
3509
+ const minTop = rect.top + FLOATING_OFFSET;
3510
+ const maxTop = rect.top + rect.height - floatingElHeight - FLOATING_OFFSET;
3511
+ return {
3512
+ left: Math.min(Math.max(rawLeft, minLeft), Math.max(minLeft, maxLeft)),
3513
+ top: Math.min(Math.max(rawTop, minTop), Math.max(minTop, maxTop))
3514
+ };
3515
+ };
3516
+ const onPointerMove = (event) => {
3517
+ const dragState = dragStateRef.current;
3518
+ if (!dragState) return;
3519
+ if (event.pointerId !== dragState.pointerId) return;
3520
+ const rawLeft = event.clientX - dragState.offsetX;
3521
+ const rawTop = event.clientY - dragState.offsetY;
3522
+ const next = clampToViewport(rawLeft, rawTop);
3523
+ setLayoutMode("manual");
3524
+ setDragPosition(next);
3525
+ };
3526
+ const handlePointerEnd = (event) => {
3527
+ const dragState = dragStateRef.current;
3528
+ if (!dragState) return;
3529
+ if (event.pointerId !== dragState.pointerId) return;
3530
+ endDrag();
3531
+ };
3532
+ const endDrag = () => {
3533
+ const dragState = dragStateRef.current;
3534
+ if (!dragState) return;
3535
+ const floatingEl = floatingRef.current;
3536
+ if (floatingEl) {
3537
+ const supportsRelease = typeof floatingEl.releasePointerCapture === "function";
3538
+ const supportsHasCapture = typeof floatingEl.hasPointerCapture === "function";
3539
+ const hasCapture = supportsHasCapture ? floatingEl.hasPointerCapture(dragState.pointerId) : false;
3540
+ if (supportsRelease && hasCapture) {
3541
+ try {
3542
+ floatingEl.releasePointerCapture(dragState.pointerId);
3543
+ } catch {
3544
+ }
3545
+ }
3546
+ }
3547
+ dragStateRef.current = null;
3548
+ setIsDragging(false);
3549
+ window.removeEventListener("pointermove", onPointerMove);
3550
+ window.removeEventListener("pointerup", handlePointerEnd);
3551
+ window.removeEventListener("pointercancel", handlePointerEnd);
3552
+ };
3553
+ const startDrag = (event) => {
3554
+ if (event.button !== 0) return;
3555
+ const floatingEl = floatingRef.current;
3556
+ if (!floatingEl) return;
3557
+ const rect = floatingEl.getBoundingClientRect();
3558
+ dragStateRef.current = {
3559
+ pointerId: event.pointerId,
3560
+ offsetX: event.clientX - rect.left,
3561
+ offsetY: event.clientY - rect.top
3562
+ };
3563
+ const next = clampToViewport(rect.left, rect.top);
3564
+ setLayoutMode("manual");
3565
+ setDragPosition(next);
3566
+ setIsDragging(true);
3567
+ window.addEventListener("pointermove", onPointerMove);
3568
+ window.addEventListener("pointerup", handlePointerEnd);
3569
+ window.addEventListener("pointercancel", handlePointerEnd);
3570
+ const supportsPointerCapture = typeof floatingEl.setPointerCapture === "function";
3571
+ if (supportsPointerCapture) {
3572
+ try {
3573
+ floatingEl.setPointerCapture(event.pointerId);
3574
+ } catch {
3575
+ }
3576
+ }
3577
+ event.preventDefault();
3578
+ };
3579
+ (0, import_react20.useEffect)(() => endDrag, []);
3580
+ const shouldUseFallbackInitial = layoutMode !== "mobile" && (Boolean(target.lastResolvedRect) || Boolean(cachedTarget));
3581
+ const floatingCacheKey = layoutMode === "mobile" ? null : getFloatingCacheKey(target);
3582
+ const persistedFloatingInitial = floatingCacheKey && floatingPositionCache.has(floatingCacheKey) ? floatingPositionCache.get(floatingCacheKey) ?? null : null;
3583
+ const cachedFloatingInitial = layoutMode === "mobile" ? null : cachedFloatingPositionRef.current ?? persistedFloatingInitial;
3584
+ const hasCachedFloatingInitial = Boolean(cachedFloatingInitial);
3585
+ const resolvedInitialPosition = layoutMode === "mobile" ? mobilePosition : hasCachedFloatingInitial && cachedFloatingInitial ? cachedFloatingInitial : shouldUseFallbackInitial ? fallbackPosition : centerInitialPosition;
3586
+ const initialTop = resolvedInitialPosition.top;
3587
+ const initialLeft = resolvedInitialPosition.left;
3588
+ const initialTransform = resolvedInitialPosition.transform;
3589
+ const containerStyle = {
3590
+ position: "fixed",
3591
+ pointerEvents: "auto",
3592
+ zIndex,
3593
+ maxWidth: layoutMode === "mobile" || typeof maxWidth === "undefined" ? void 0 : maxWidth,
3594
+ width: layoutMode === "mobile" || typeof width === "undefined" ? void 0 : width,
3595
+ cursor: isDragging ? "grabbing" : void 0
3596
+ };
3597
+ const setFloatingNode = (node) => {
3598
+ floatingRef.current = node;
3599
+ onContainerChange?.(node);
3600
+ };
3601
+ const containerProps = {
3602
+ ref: setFloatingNode,
3603
+ role: role ?? "dialog",
3604
+ "aria-modal": ariaModal ?? false,
3605
+ "aria-label": ariaLabel,
3606
+ "aria-describedby": ariaDescribedBy,
3607
+ tabIndex: -1,
3608
+ "aria-live": "polite",
3609
+ "data-tour-popover": "",
3610
+ "data-layout": layoutMode,
3611
+ "data-target-visibility": target.visibility,
3612
+ "data-rect-source": target.rectSource,
3613
+ style: containerStyle,
3614
+ initial: {
3615
+ filter: "blur(4px)",
3616
+ opacity: 0,
3617
+ top: initialTop,
3618
+ left: initialLeft,
3619
+ transform: initialTransform
3620
+ },
3621
+ animate: {
3622
+ filter: "blur(0px)",
3623
+ opacity: 1,
3624
+ top: floatingPosition.top,
3625
+ left: floatingPosition.left,
3626
+ transform: floatingPosition.transform
3627
+ },
3628
+ exit: {
3629
+ filter: "blur(4px)",
3630
+ opacity: 0,
3631
+ transition: popoverExitTransition
3632
+ },
3633
+ transition: popoverEntranceTransition,
3634
+ ...layoutId ? { layoutId } : {}
3635
+ };
3636
+ const contentProps = {
3637
+ key: target.stepId ?? void 0,
3638
+ "data-tour-popover-content": "",
3639
+ initial: { opacity: 0, translateX: 0, filter: "blur(4px)" },
3640
+ animate: { opacity: 1, translateX: 0, filter: "blur(0px)" },
3641
+ exit: { opacity: 0, translateX: 0, filter: "blur(4px)" },
3642
+ transition: popoverContentTransition
3643
+ };
3644
+ const showDragHandle = layoutMode === "docked" || layoutMode === "manual";
3645
+ const dragHandleProps = {
3646
+ type: "button",
3647
+ "aria-label": "Move tour popover",
3648
+ style: { touchAction: "none" },
3649
+ onPointerDown: startDrag
3650
+ };
3651
+ const descriptionProps = {
3652
+ id: descriptionId,
3653
+ text: descriptionText
3654
+ };
3655
+ const context = {
3656
+ Container,
3657
+ Content,
3658
+ containerProps,
3659
+ contentProps,
3660
+ layoutMode,
3661
+ isDragging,
3662
+ showDragHandle,
3663
+ dragHandleProps,
3664
+ descriptionProps
3665
+ };
3666
+ return (0, import_react_dom2.createPortal)(children(context), host);
3667
+ };
3668
+
3669
+ // src/components/TourFocusManager.tsx
3670
+ var import_react21 = require("react");
3671
+ var import_react_dom3 = require("react-dom");
3672
+
3673
+ // src/utils/focus.ts
3674
+ var FOCUSABLE_SELECTOR = 'a[href], area[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), summary, [contenteditable="true"], [tabindex]';
3675
+ var isElementVisible = (element) => {
3676
+ if (element.offsetParent !== null) return true;
3677
+ if (element.getClientRects().length > 0) return true;
3678
+ return false;
3679
+ };
3680
+ var isFocusableElement = (element) => {
3681
+ if (!(element instanceof HTMLElement)) return false;
3682
+ if (element.hasAttribute("disabled")) return false;
3683
+ if (element.getAttribute("aria-hidden") === "true") return false;
3684
+ if (element.tabIndex >= 0) return true;
3685
+ const tagName = element.tagName.toLowerCase();
3686
+ if (tagName === "input") {
3687
+ const type = element.getAttribute("type");
3688
+ if (type === "hidden") return false;
3689
+ return !element.hasAttribute("disabled");
3690
+ }
3691
+ if (tagName === "button" || tagName === "select" || tagName === "textarea") {
3692
+ return !element.hasAttribute("disabled");
3693
+ }
3694
+ if (tagName === "a" || tagName === "area") {
3695
+ return element.hasAttribute("href");
3696
+ }
3697
+ if (element.hasAttribute("contenteditable")) {
3698
+ return element.getAttribute("contenteditable") !== "false";
3699
+ }
3700
+ if (!element.hasAttribute("tabindex")) return false;
3701
+ return element.tabIndex >= 0;
3702
+ };
3703
+ var getFocusableIn = (root) => {
3704
+ if (!root) return [];
3705
+ const scope = root instanceof Document ? root.body : root;
3706
+ const matched = Array.from(
3707
+ scope.querySelectorAll(FOCUSABLE_SELECTOR)
3708
+ );
3709
+ const results = [];
3710
+ const seen = /* @__PURE__ */ new Set();
3711
+ for (const element of matched) {
3712
+ if (element.tabIndex < 0 && !element.hasAttribute("tabindex")) continue;
3713
+ if (!isElementVisible(element)) continue;
3714
+ if (!isFocusableElement(element)) continue;
3715
+ if (element.closest('[data-tour-focus-skip="true"]')) continue;
3716
+ if (seen.has(element)) continue;
3717
+ seen.add(element);
3718
+ results.push(element);
3719
+ }
3720
+ return results;
3721
+ };
3722
+ var focusElement = (element, options) => {
3723
+ if (!element) return;
3724
+ try {
3725
+ element.focus(options ?? { preventScroll: true });
3726
+ } catch {
3727
+ }
3728
+ };
3729
+
3730
+ // src/components/TourFocusManager.tsx
3731
+ var import_jsx_runtime4 = require("react/jsx-runtime");
3732
+ var runMicrotask = (callback) => {
3733
+ if (typeof queueMicrotask === "function") {
3734
+ queueMicrotask(callback);
3735
+ } else {
3736
+ setTimeout(callback, 0);
3737
+ }
3738
+ };
3739
+ var TourFocusManager = ({
3740
+ active,
3741
+ target,
3742
+ popoverNode,
3743
+ highlightRect,
3744
+ targetRingOffset = -2
3745
+ }) => {
3746
+ const previousFocusRef = (0, import_react21.useRef)(null);
3747
+ const guardNodesRef = (0, import_react21.useRef)({
3748
+ "target-start": null,
3749
+ "target-end": null,
3750
+ "popover-start": null,
3751
+ "popover-end": null
3752
+ });
3753
+ const lastTabDirectionRef = (0, import_react21.useRef)("forward");
3754
+ const suppressGuardHopRef = (0, import_react21.useRef)(null);
3755
+ const ringStylesRef = (0, import_react21.useRef)(
3756
+ /* @__PURE__ */ new WeakMap()
3757
+ );
3758
+ const [targetRingActive, setTargetRingActive] = (0, import_react21.useState)(false);
3759
+ const restoreFocus = () => {
3760
+ const previous = previousFocusRef.current;
3761
+ previousFocusRef.current = null;
3762
+ if (previous && previous.isConnected) {
3763
+ runMicrotask(() => {
3764
+ focusElement(previous, { preventScroll: true });
3765
+ });
3766
+ }
3767
+ };
3768
+ (0, import_react21.useLayoutEffect)(() => {
3769
+ if (!isBrowser) return;
3770
+ if (!active) {
3771
+ restoreFocus();
3772
+ return;
3773
+ }
3774
+ if (previousFocusRef.current) return;
3775
+ const doc = popoverNode?.ownerDocument ?? target.element?.ownerDocument;
3776
+ const activeEl = (doc ?? document).activeElement;
3777
+ if (activeEl instanceof HTMLElement) {
3778
+ previousFocusRef.current = activeEl;
3779
+ }
3780
+ return () => {
3781
+ restoreFocus();
3782
+ };
3783
+ }, [active, popoverNode, target.element]);
3784
+ (0, import_react21.useEffect)(() => {
3785
+ if (!isBrowser) return;
3786
+ if (!active) return;
3787
+ const doc = popoverNode?.ownerDocument ?? target.element?.ownerDocument ?? document;
3788
+ const createGuard = (key) => {
3789
+ const node = doc.createElement("div");
3790
+ node.tabIndex = 0;
3791
+ node.setAttribute("data-tour-focus-guard", key);
3792
+ node.setAttribute("data-tour-prevent-shortcut", "true");
3793
+ const label = key.startsWith("target") ? "Tour highlight boundary" : "Tour popover boundary";
3794
+ node.setAttribute("aria-label", label);
3795
+ Object.assign(node.style, {
3796
+ position: "fixed",
3797
+ top: "0",
3798
+ left: "0",
3799
+ width: "1px",
3800
+ height: "1px",
3801
+ opacity: "0",
3802
+ outline: "none",
3803
+ padding: "0",
3804
+ margin: "0",
3805
+ border: "0",
3806
+ pointerEvents: "auto"
3807
+ });
3808
+ return node;
3809
+ };
3810
+ const applyRing = (element, activeRing) => {
3811
+ if (!element) return;
3812
+ const cache = ringStylesRef.current;
3813
+ if (activeRing) {
3814
+ if (!cache.has(element)) {
3815
+ cache.set(element, {
3816
+ outline: element.style.outline,
3817
+ outlineOffset: element.style.outlineOffset
3818
+ });
3819
+ }
3820
+ element.style.outline = "2px solid var(--tour-focus-ring-color, rgba(59, 130, 246, 0.8))";
3821
+ element.style.outlineOffset = "3px";
3822
+ return;
3823
+ }
3824
+ const previous = cache.get(element);
3825
+ if (previous) {
3826
+ element.style.outline = previous.outline;
3827
+ element.style.outlineOffset = previous.outlineOffset;
3828
+ cache.delete(element);
3829
+ } else {
3830
+ element.style.outline = "";
3831
+ element.style.outlineOffset = "";
3832
+ }
3833
+ };
3834
+ const clearRings = () => {
3835
+ setTargetRingActive(false);
3836
+ applyRing(popoverNode, false);
3837
+ };
3838
+ const removeGuards = () => {
3839
+ for (const key of Object.keys(guardNodesRef.current)) {
3840
+ const node = guardNodesRef.current[key];
3841
+ if (node?.parentNode) {
3842
+ node.parentNode.removeChild(node);
3843
+ }
3844
+ guardNodesRef.current[key] = null;
3845
+ }
3846
+ clearRings();
3847
+ };
3848
+ const ensureGuards = () => {
3849
+ const targetElement = !target.isScreen && target.element instanceof HTMLElement ? target.element : null;
3850
+ const popoverElement = popoverNode ?? null;
3851
+ if (targetElement) {
3852
+ if (!guardNodesRef.current["target-start"]) {
3853
+ guardNodesRef.current["target-start"] = createGuard("target-start");
3854
+ }
3855
+ if (!guardNodesRef.current["target-end"]) {
3856
+ guardNodesRef.current["target-end"] = createGuard("target-end");
3857
+ }
3858
+ const startGuard = guardNodesRef.current["target-start"];
3859
+ const endGuard = guardNodesRef.current["target-end"];
3860
+ if (startGuard.parentElement !== targetElement.parentElement) {
3861
+ targetElement.insertAdjacentElement("beforebegin", startGuard);
3862
+ }
3863
+ if (endGuard.parentElement !== targetElement.parentElement) {
3864
+ targetElement.insertAdjacentElement("afterend", endGuard);
3865
+ }
3866
+ } else {
3867
+ for (const key of ["target-start", "target-end"]) {
3868
+ const node = guardNodesRef.current[key];
3869
+ if (node?.parentNode) {
3870
+ node.parentNode.removeChild(node);
3871
+ }
3872
+ guardNodesRef.current[key] = null;
3873
+ }
3874
+ }
3875
+ if (popoverElement) {
3876
+ if (!guardNodesRef.current["popover-start"]) {
3877
+ guardNodesRef.current["popover-start"] = createGuard("popover-start");
3878
+ }
3879
+ if (!guardNodesRef.current["popover-end"]) {
3880
+ guardNodesRef.current["popover-end"] = createGuard("popover-end");
3881
+ }
3882
+ const startGuard = guardNodesRef.current["popover-start"];
3883
+ const endGuard = guardNodesRef.current["popover-end"];
3884
+ if (startGuard.parentElement !== popoverElement) {
3885
+ popoverElement.prepend(startGuard);
3886
+ }
3887
+ if (endGuard.parentElement !== popoverElement) {
3888
+ popoverElement.append(endGuard);
3889
+ }
3890
+ } else {
3891
+ for (const key of ["popover-start", "popover-end"]) {
3892
+ const node = guardNodesRef.current[key];
3893
+ if (node?.parentNode) {
3894
+ node.parentNode.removeChild(node);
3895
+ }
3896
+ guardNodesRef.current[key] = null;
3897
+ }
3898
+ }
3899
+ };
3900
+ const deriveFocusables = () => {
3901
+ const nodes = [];
3902
+ if (popoverNode) {
3903
+ nodes.push(...getFocusableIn(popoverNode));
3904
+ }
3905
+ const targetIsFocusable = !target.isScreen && target.visibility === "visible" && target.element instanceof HTMLElement;
3906
+ if (targetIsFocusable) {
3907
+ const targetElement = target.element;
3908
+ if (!targetElement.closest('[data-tour-focus-skip="true"]') && isFocusableElement(targetElement)) {
3909
+ nodes.push(targetElement);
3910
+ }
3911
+ nodes.push(...getFocusableIn(targetElement));
3912
+ }
3913
+ if (nodes.length === 0 && popoverNode?.hasAttribute("tabindex")) {
3914
+ nodes.push(popoverNode);
3915
+ }
3916
+ const unique = [];
3917
+ const seen = /* @__PURE__ */ new Set();
3918
+ for (const node of nodes) {
3919
+ if (seen.has(node)) continue;
3920
+ seen.add(node);
3921
+ unique.push(node);
3922
+ }
3923
+ return unique;
3924
+ };
3925
+ const isWithinTrap = (element) => {
3926
+ if (!(element instanceof HTMLElement)) return false;
3927
+ if (popoverNode?.contains(element)) return true;
3928
+ if (target.element instanceof HTMLElement && target.element.contains(element)) {
3929
+ return true;
3930
+ }
3931
+ return Object.values(guardNodesRef.current).some(
3932
+ (node) => node === element
3933
+ );
3934
+ };
3935
+ const ensureFocus = () => {
3936
+ const firstGuard = guardNodesRef.current["target-start"] ?? guardNodesRef.current["popover-start"];
3937
+ if (firstGuard) {
3938
+ focusElement(firstGuard);
3939
+ return;
3940
+ }
3941
+ const focusables = deriveFocusables();
3942
+ if (focusables.length === 0) return;
3943
+ focusElement(focusables[0]);
3944
+ };
3945
+ const handleKeyDown = (event) => {
3946
+ if (event.key !== "Tab") return;
3947
+ lastTabDirectionRef.current = event.shiftKey ? "backward" : "forward";
3948
+ };
3949
+ const handleFocusIn = (event) => {
3950
+ const targetNode = event.target;
3951
+ if (!(targetNode instanceof HTMLElement)) return;
3952
+ if (targetNode.hasAttribute("data-tour-focus-guard")) {
3953
+ if (suppressGuardHopRef.current === targetNode) {
3954
+ suppressGuardHopRef.current = null;
3955
+ } else {
3956
+ const direction = lastTabDirectionRef.current;
3957
+ const key2 = targetNode.getAttribute("data-tour-focus-guard");
3958
+ const targetStart = guardNodesRef.current["target-start"];
3959
+ const targetEnd = guardNodesRef.current["target-end"];
3960
+ const popoverStart = guardNodesRef.current["popover-start"];
3961
+ const popoverEnd = guardNodesRef.current["popover-end"];
3962
+ const hasTargetGuards = Boolean(targetStart && targetEnd);
3963
+ const nextGuard = direction === "forward" ? key2 === "target-end" ? popoverStart : key2 === "popover-end" ? hasTargetGuards ? targetStart : popoverStart : null : key2 === "popover-start" ? hasTargetGuards ? targetEnd : popoverEnd : key2 === "target-start" ? popoverEnd : null;
3964
+ if (nextGuard) {
3965
+ suppressGuardHopRef.current = nextGuard;
3966
+ focusElement(nextGuard);
3967
+ return;
3968
+ }
3969
+ }
3970
+ const key = targetNode.getAttribute("data-tour-focus-guard");
3971
+ if (key?.startsWith("target")) {
3972
+ setTargetRingActive(true);
3973
+ applyRing(popoverNode, false);
3974
+ } else if (key?.startsWith("popover")) {
3975
+ setTargetRingActive(false);
3976
+ applyRing(popoverNode, true);
3977
+ }
3978
+ return;
3979
+ }
3980
+ clearRings();
3981
+ if (isWithinTrap(targetNode)) return;
3982
+ ensureFocus();
3983
+ };
3984
+ ensureGuards();
3985
+ doc.addEventListener("keydown", handleKeyDown, true);
3986
+ doc.addEventListener("focusin", handleFocusIn, true);
3987
+ return () => {
3988
+ doc.removeEventListener("keydown", handleKeyDown, true);
3989
+ doc.removeEventListener("focusin", handleFocusIn, true);
3990
+ removeGuards();
3991
+ };
3992
+ }, [
3993
+ active,
3994
+ popoverNode,
3995
+ target.element,
3996
+ target.isScreen,
3997
+ target.lastUpdated,
3998
+ target.status,
3999
+ target.stepId,
4000
+ target.visibility
4001
+ ]);
4002
+ if (!isBrowser) return null;
4003
+ const host = portalHost();
4004
+ if (!host || !highlightRect || !targetRingActive) return null;
4005
+ const offset = Math.max(0, targetRingOffset);
4006
+ const ringStyle = {
4007
+ position: "fixed",
4008
+ top: highlightRect.top - offset,
4009
+ left: highlightRect.left - offset,
4010
+ width: highlightRect.width + offset * 2,
4011
+ height: highlightRect.height + offset * 2,
4012
+ borderRadius: highlightRect.radius + offset,
4013
+ boxShadow: [
4014
+ "0 0 0 2px var(--tour-focus-ring-offset-color, transparent)",
4015
+ "0 0 0 2px var(--tour-focus-ring-color, rgba(59, 130, 246, 0.8))"
4016
+ ].join(", "),
4017
+ pointerEvents: "none",
4018
+ zIndex: 2001
4019
+ };
4020
+ return (0, import_react_dom3.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: ringStyle, "aria-hidden": true }), host);
4021
+ };
4022
+
4023
+ // src/motion/useHudMotion.ts
4024
+ var import_react22 = require("react");
4025
+ var DEFAULT_HIGHLIGHT_TRANSITION2 = {
4026
+ duration: 0.35,
4027
+ ease: "easeOut",
4028
+ type: "spring",
4029
+ damping: 25,
4030
+ stiffness: 300,
4031
+ mass: 0.7
4032
+ };
4033
+ var DEFAULT_OVERLAY_TRANSITION2 = {
4034
+ duration: 0.35,
4035
+ ease: "easeOut"
4036
+ };
4037
+ var DEFAULT_POPOVER_ENTRANCE_TRANSITION2 = {
4038
+ duration: 0.25,
4039
+ ease: "easeOut"
4040
+ };
4041
+ var DEFAULT_POPOVER_EXIT_TRANSITION2 = {
4042
+ duration: 0.2,
4043
+ ease: "easeOut"
4044
+ };
4045
+ var DEFAULT_POPOVER_CONTENT_TRANSITION2 = {
4046
+ duration: 0.6,
4047
+ ease: "easeOut"
4048
+ };
4049
+ var useHudMotion = () => {
4050
+ const adapter = useAnimationAdapter();
4051
+ return (0, import_react22.useMemo)(() => {
4052
+ const components = {
4053
+ ...adapter.components
4054
+ };
4055
+ return {
4056
+ components,
4057
+ transitions: {
4058
+ highlight: adapter.transitions.overlayHighlight ?? DEFAULT_HIGHLIGHT_TRANSITION2,
4059
+ overlayFade: adapter.transitions.overlayFade ?? DEFAULT_OVERLAY_TRANSITION2,
4060
+ popoverEntrance: adapter.transitions.popoverEntrance ?? DEFAULT_POPOVER_ENTRANCE_TRANSITION2,
4061
+ popoverExit: adapter.transitions.popoverExit ?? DEFAULT_POPOVER_EXIT_TRANSITION2,
4062
+ popoverContent: adapter.transitions.popoverContent ?? DEFAULT_POPOVER_CONTENT_TRANSITION2
4063
+ }
4064
+ };
4065
+ }, [adapter]);
4066
+ };
4067
+
4068
+ // src/router/utils.ts
4069
+ var ensurePrefix = (value, prefix) => value.startsWith(prefix) ? value : `${prefix}${value}`;
4070
+ var isNonEmptyString = (value) => typeof value === "string" && value.length > 0;
4071
+ var toSearchString = (value) => {
4072
+ if (!isNonEmptyString(value)) {
4073
+ if (value instanceof URLSearchParams) {
4074
+ const serialized = value.toString();
4075
+ return serialized.length > 0 ? `?${serialized}` : "";
4076
+ }
4077
+ if (typeof value === "object" && value !== null) {
4078
+ try {
4079
+ const params = new URLSearchParams();
4080
+ for (const [key, raw] of Object.entries(
4081
+ value
4082
+ )) {
4083
+ if (raw === void 0 || raw === null) continue;
4084
+ params.set(key, String(raw));
4085
+ }
4086
+ const serialized = params.toString();
4087
+ return serialized.length > 0 ? `?${serialized}` : "";
4088
+ } catch {
4089
+ return "";
4090
+ }
4091
+ }
4092
+ return "";
4093
+ }
4094
+ if (value === "?") return "";
4095
+ return value.startsWith("?") ? value : ensurePrefix(value, "?");
4096
+ };
4097
+ var toHashString = (value) => {
4098
+ if (!isNonEmptyString(value)) {
4099
+ return "";
4100
+ }
4101
+ if (value === "#") return "";
4102
+ return value.startsWith("#") ? value : ensurePrefix(value, "#");
4103
+ };
4104
+ var createPathString = (pathname, search, hash) => {
4105
+ const normalizedPath = isNonEmptyString(pathname) ? pathname.startsWith("/") ? pathname : `/${pathname}` : "/";
4106
+ const searchPart = toSearchString(search);
4107
+ const hashPart = toHashString(hash);
4108
+ return `${normalizedPath}${searchPart}${hashPart}`;
4109
+ };
4110
+
4111
+ // src/router/tanstackRouterAdapter.tsx
4112
+ var import_react_router = require("@tanstack/react-router");
4113
+ var import_react23 = require("react");
4114
+ var useTanStackRouterTourAdapter = () => {
4115
+ const location = (0, import_react_router.useRouterState)({
4116
+ select: (state) => state.location
4117
+ });
4118
+ (0, import_react23.useEffect)(() => {
4119
+ const resolvedPathname = typeof location.pathname === "string" && location.pathname.length > 0 ? location.pathname : "/";
4120
+ const resolvedSearch = "searchStr" in location && typeof location.searchStr === "string" ? location.searchStr : typeof location.search === "string" ? location.search : "";
4121
+ const resolvedHash = typeof location.hash === "string" ? location.hash : "";
4122
+ const path = typeof location.href === "string" && location.href.length > 0 ? location.href : createPathString(resolvedPathname, resolvedSearch, resolvedHash);
4123
+ notifyRouteChange(path);
4124
+ }, [location]);
4125
+ };
4126
+
4127
+ // src/router/tanstackRouterSync.tsx
4128
+ var TanStackRouter = __toESM(require("@tanstack/react-router"), 1);
4129
+ var import_react24 = require("react");
4130
+ var currentRouter = null;
4131
+ var setTourRouter = (router) => {
4132
+ currentRouter = router;
4133
+ };
4134
+ var setTanStackRouter = setTourRouter;
4135
+ var getTourRouter = () => {
4136
+ return currentRouter;
4137
+ };
4138
+ var getTanStackRouter = getTourRouter;
4139
+ var TanStackRouterSync = ({
4140
+ onRouterAvailable
4141
+ }) => {
4142
+ const useRouter2 = TanStackRouter.useRouter;
4143
+ if (typeof useRouter2 !== "function") {
4144
+ if (typeof console !== "undefined") {
4145
+ console.warn(
4146
+ "[tour][router] TanStackRouterSync requires @tanstack/react-router. Call this component only inside TanStack Router environments."
4147
+ );
4148
+ }
4149
+ return null;
4150
+ }
4151
+ useTanStackRouterTourAdapter();
4152
+ const router = useRouter2();
4153
+ (0, import_react24.useEffect)(() => {
4154
+ setTourRouter(router);
4155
+ onRouterAvailable?.(router);
4156
+ return () => {
4157
+ if (getTourRouter() === router) {
4158
+ setTourRouter(null);
4159
+ }
4160
+ onRouterAvailable?.(null);
4161
+ };
4162
+ }, [router, onRouterAvailable]);
4163
+ return null;
4164
+ };
4165
+ // Annotate the CommonJS export names for ESM import in node:
4166
+ 0 && (module.exports = {
4167
+ AnimationAdapterProvider,
4168
+ OverlayBackdrop,
4169
+ TanStackRouterSync,
4170
+ TourFocusManager,
4171
+ TourPopoverPortal,
4172
+ TourProvider,
4173
+ createPathString,
4174
+ createWaitForPredicateController,
4175
+ defaultAnimationAdapter,
4176
+ defaultLabels,
4177
+ getCurrentRoutePath,
4178
+ getTanStackRouter,
4179
+ getTourRouter,
4180
+ notifyRouteChange,
4181
+ reducedMotionAnimationAdapter,
4182
+ setTanStackRouter,
4183
+ setTourRouter,
4184
+ subscribeToRouteChanges,
4185
+ useAdvanceRules,
4186
+ useAnimationAdapter,
4187
+ useBodyScrollLock,
4188
+ useDelayAdvance,
4189
+ useHiddenTargetFallback,
4190
+ useHudDescription,
4191
+ useHudMotion,
4192
+ useHudShortcuts,
4193
+ useHudState,
4194
+ useHudTargetIssue,
4195
+ usePreferredAnimationAdapter,
4196
+ useRadixDialogAdapter,
4197
+ useTanStackRouterTourAdapter,
4198
+ useTour,
4199
+ useTourControls,
4200
+ useTourEvents,
4201
+ useTourFocusDominance,
4202
+ useTourHud,
4203
+ useTourLabels,
4204
+ useTourOverlay,
4205
+ useTourTarget,
4206
+ useViewportRect
4207
+ });