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