@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.
- package/dist/adapters/radixDialog.d.ts +18 -0
- package/dist/adapters/radixDialog.d.ts.map +1 -0
- package/dist/chunk-RCASLQRS.mjs +24 -0
- package/dist/chunk-RPA2S5UP.mjs +280 -0
- package/dist/chunk-U757YVZP.mjs +50 -0
- package/dist/components/OverlayBackdrop.d.ts +34 -0
- package/dist/components/OverlayBackdrop.d.ts.map +1 -0
- package/dist/components/TourFocusManager.d.ts +11 -0
- package/dist/components/TourFocusManager.d.ts.map +1 -0
- package/dist/components/TourPopoverPortal.d.ts +57 -0
- package/dist/components/TourPopoverPortal.d.ts.map +1 -0
- package/dist/components/__tests__/TourFocusManager.test.d.ts +2 -0
- package/dist/components/__tests__/TourFocusManager.test.d.ts.map +1 -0
- package/dist/context.d.ts +56 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/hooks/__tests__/scrollMargin.test.d.ts +2 -0
- package/dist/hooks/__tests__/scrollMargin.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/useBodyScrollLock.test.d.ts +2 -0
- package/dist/hooks/__tests__/useBodyScrollLock.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/useHiddenTargetFallback.test.d.ts +2 -0
- package/dist/hooks/__tests__/useHiddenTargetFallback.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/waitForPredicate.test.d.ts +2 -0
- package/dist/hooks/__tests__/waitForPredicate.test.d.ts.map +1 -0
- package/dist/hooks/scrollMargin.d.ts +15 -0
- package/dist/hooks/scrollMargin.d.ts.map +1 -0
- package/dist/hooks/useAdvanceRules.d.ts +3 -0
- package/dist/hooks/useAdvanceRules.d.ts.map +1 -0
- package/dist/hooks/useBodyScrollLock.d.ts +2 -0
- package/dist/hooks/useBodyScrollLock.d.ts.map +1 -0
- package/dist/hooks/useDelayAdvance.d.ts +14 -0
- package/dist/hooks/useDelayAdvance.d.ts.map +1 -0
- package/dist/hooks/useHiddenTargetFallback.d.ts +16 -0
- package/dist/hooks/useHiddenTargetFallback.d.ts.map +1 -0
- package/dist/hooks/useHudDescription.d.ts +13 -0
- package/dist/hooks/useHudDescription.d.ts.map +1 -0
- package/dist/hooks/useHudShortcuts.d.ts +7 -0
- package/dist/hooks/useHudShortcuts.d.ts.map +1 -0
- package/dist/hooks/useHudState.d.ts +27 -0
- package/dist/hooks/useHudState.d.ts.map +1 -0
- package/dist/hooks/useHudTargetIssue.d.ts +21 -0
- package/dist/hooks/useHudTargetIssue.d.ts.map +1 -0
- package/dist/hooks/useTourControls.d.ts +17 -0
- package/dist/hooks/useTourControls.d.ts.map +1 -0
- package/dist/hooks/useTourFocusDominance.d.ts +9 -0
- package/dist/hooks/useTourFocusDominance.d.ts.map +1 -0
- package/dist/hooks/useTourHud.d.ts +61 -0
- package/dist/hooks/useTourHud.d.ts.map +1 -0
- package/dist/hooks/useTourOverlay.d.ts +70 -0
- package/dist/hooks/useTourOverlay.d.ts.map +1 -0
- package/dist/hooks/useTourTarget.d.ts +16 -0
- package/dist/hooks/useTourTarget.d.ts.map +1 -0
- package/dist/hooks/useViewportRect.d.ts +3 -0
- package/dist/hooks/useViewportRect.d.ts.map +1 -0
- package/dist/hooks/waitForPredicate.d.ts +15 -0
- package/dist/hooks/waitForPredicate.d.ts.map +1 -0
- package/dist/index.cjs +4207 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +3851 -0
- package/dist/labels.d.ts +22 -0
- package/dist/labels.d.ts.map +1 -0
- package/dist/motion/animationAdapter.d.ts +40 -0
- package/dist/motion/animationAdapter.d.ts.map +1 -0
- package/dist/motion/useHudMotion.d.ts +14 -0
- package/dist/motion/useHudMotion.d.ts.map +1 -0
- package/dist/router/index.cjs +275 -0
- package/dist/router/index.d.ts +5 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.mjs +30 -0
- package/dist/router/nextAppRouterAdapter.cjs +224 -0
- package/dist/router/nextAppRouterAdapter.d.ts +2 -0
- package/dist/router/nextAppRouterAdapter.d.ts.map +1 -0
- package/dist/router/nextAppRouterAdapter.mjs +35 -0
- package/dist/router/nextPagesRouterAdapter.cjs +217 -0
- package/dist/router/nextPagesRouterAdapter.d.ts +2 -0
- package/dist/router/nextPagesRouterAdapter.d.ts.map +1 -0
- package/dist/router/nextPagesRouterAdapter.mjs +28 -0
- package/dist/router/reactRouterAdapter.cjs +220 -0
- package/dist/router/reactRouterAdapter.d.ts +2 -0
- package/dist/router/reactRouterAdapter.d.ts.map +1 -0
- package/dist/router/reactRouterAdapter.mjs +31 -0
- package/dist/router/routeGating.d.ts +13 -0
- package/dist/router/routeGating.d.ts.map +1 -0
- package/dist/router/tanstackRouterAdapter.cjs +202 -0
- package/dist/router/tanstackRouterAdapter.d.ts +2 -0
- package/dist/router/tanstackRouterAdapter.d.ts.map +1 -0
- package/dist/router/tanstackRouterAdapter.mjs +7 -0
- package/dist/router/tanstackRouterSync.d.ts +20 -0
- package/dist/router/tanstackRouterSync.d.ts.map +1 -0
- package/dist/router/utils.d.ts +2 -0
- package/dist/router/utils.d.ts.map +1 -0
- package/dist/utils/dom.d.ts +22 -0
- package/dist/utils/dom.d.ts.map +1 -0
- package/dist/utils/focus.d.ts +4 -0
- package/dist/utils/focus.d.ts.map +1 -0
- 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
|
+
});
|