react-native-lumen 1.0.1 → 1.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/README.md +763 -231
- package/lib/module/components/TourOverlay.js +43 -3
- package/lib/module/components/TourOverlay.js.map +1 -1
- package/lib/module/components/TourProvider.js +318 -61
- package/lib/module/components/TourProvider.js.map +1 -1
- package/lib/module/components/TourTooltip.js +113 -73
- package/lib/module/components/TourTooltip.js.map +1 -1
- package/lib/module/components/TourZone.js +186 -119
- package/lib/module/components/TourZone.js.map +1 -1
- package/lib/module/constants/defaults.js +43 -0
- package/lib/module/constants/defaults.js.map +1 -1
- package/lib/module/context/TourContext.js +5 -0
- package/lib/module/context/TourContext.js.map +1 -0
- package/lib/module/hooks/useTour.js +1 -1
- package/lib/module/hooks/useTour.js.map +1 -1
- package/lib/module/hooks/useTourScrollView.js +71 -0
- package/lib/module/hooks/useTourScrollView.js.map +1 -0
- package/lib/module/index.js +6 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/storage.js +188 -0
- package/lib/module/utils/storage.js.map +1 -0
- package/lib/typescript/src/components/TourOverlay.d.ts.map +1 -1
- package/lib/typescript/src/components/TourProvider.d.ts +21 -4
- package/lib/typescript/src/components/TourProvider.d.ts.map +1 -1
- package/lib/typescript/src/components/TourTooltip.d.ts.map +1 -1
- package/lib/typescript/src/components/TourZone.d.ts +19 -1
- package/lib/typescript/src/components/TourZone.d.ts.map +1 -1
- package/lib/typescript/src/constants/defaults.d.ts +10 -0
- package/lib/typescript/src/constants/defaults.d.ts.map +1 -1
- package/lib/typescript/src/context/TourContext.d.ts +3 -0
- package/lib/typescript/src/context/TourContext.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useTourScrollView.d.ts +65 -0
- package/lib/typescript/src/hooks/useTourScrollView.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +4 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types/index.d.ts +296 -1
- package/lib/typescript/src/types/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/storage.d.ts +51 -0
- package/lib/typescript/src/utils/storage.d.ts.map +1 -0
- package/package.json +173 -171
- package/src/components/TourOverlay.tsx +45 -2
- package/src/components/TourProvider.tsx +408 -56
- package/src/components/TourTooltip.tsx +144 -71
- package/src/components/TourZone.tsx +238 -140
- package/src/constants/defaults.ts +51 -0
- package/src/context/TourContext.ts +4 -0
- package/src/hooks/useTour.ts +1 -1
- package/src/hooks/useTourScrollView.ts +111 -0
- package/src/index.tsx +27 -0
- package/src/types/index.ts +306 -1
- package/src/utils/storage.ts +226 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { memo } from 'react';
|
|
3
|
+
import { memo, useMemo } from 'react';
|
|
4
4
|
import { StyleSheet, Dimensions } from 'react-native';
|
|
5
5
|
import Svg, { Path } from 'react-native-svg';
|
|
6
6
|
import Animated, { useAnimatedProps, useAnimatedStyle } from 'react-native-reanimated';
|
|
7
7
|
import { useTour } from "../hooks/useTour.js";
|
|
8
|
+
import { DEFAULT_ZONE_STYLE } from "../constants/defaults.js";
|
|
8
9
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
9
10
|
const {
|
|
10
11
|
width: SCREEN_WIDTH,
|
|
@@ -43,11 +44,22 @@ export const TourOverlay = /*#__PURE__*/memo(() => {
|
|
|
43
44
|
targetHeight,
|
|
44
45
|
targetRadius,
|
|
45
46
|
opacity,
|
|
47
|
+
zoneBorderWidth,
|
|
46
48
|
config,
|
|
47
49
|
currentStep,
|
|
48
|
-
steps
|
|
50
|
+
steps,
|
|
51
|
+
currentZoneStyle
|
|
49
52
|
} = useTour();
|
|
50
53
|
|
|
54
|
+
// Get resolved zone style for styling the glow/border
|
|
55
|
+
const zoneStyle = useMemo(() => {
|
|
56
|
+
return {
|
|
57
|
+
...DEFAULT_ZONE_STYLE,
|
|
58
|
+
...config?.zoneStyle,
|
|
59
|
+
...currentZoneStyle
|
|
60
|
+
};
|
|
61
|
+
}, [config?.zoneStyle, currentZoneStyle]);
|
|
62
|
+
|
|
51
63
|
// Create the d string for the mask
|
|
52
64
|
// Outer rectangle covers the whole screen
|
|
53
65
|
// Inner shape is the "hole"
|
|
@@ -86,7 +98,8 @@ export const TourOverlay = /*#__PURE__*/memo(() => {
|
|
|
86
98
|
// - Overlay shouldn't block anything?
|
|
87
99
|
// - pointerEvents='none' on the whole container.
|
|
88
100
|
|
|
89
|
-
|
|
101
|
+
// Check per-step preventInteraction first, then fall back to global config
|
|
102
|
+
const shouldBlockOutside = step?.preventInteraction ?? config?.preventInteraction ?? false;
|
|
90
103
|
|
|
91
104
|
// If we don't want to block outside, we just let everything pass.
|
|
92
105
|
// But wait, if we let everything pass, we can't implement 'clickable=false' strictness?
|
|
@@ -108,6 +121,30 @@ export const TourOverlay = /*#__PURE__*/memo(() => {
|
|
|
108
121
|
borderRadius: targetRadius.value
|
|
109
122
|
};
|
|
110
123
|
});
|
|
124
|
+
|
|
125
|
+
// Animated style for the zone border/glow ring
|
|
126
|
+
const zoneBorderStyle = useAnimatedStyle(() => {
|
|
127
|
+
const isGlowEnabled = config?.enableGlow === true;
|
|
128
|
+
const borderW = zoneBorderWidth?.value ?? zoneStyle.borderWidth;
|
|
129
|
+
return {
|
|
130
|
+
position: 'absolute',
|
|
131
|
+
left: targetX.value,
|
|
132
|
+
top: targetY.value,
|
|
133
|
+
width: targetWidth.value,
|
|
134
|
+
height: targetHeight.value,
|
|
135
|
+
borderRadius: targetRadius.value,
|
|
136
|
+
borderWidth: borderW,
|
|
137
|
+
borderColor: zoneStyle.borderColor,
|
|
138
|
+
backgroundColor: 'transparent',
|
|
139
|
+
...(isGlowEnabled && {
|
|
140
|
+
// Glow effect using React Native 0.76+ boxShadow API
|
|
141
|
+
boxShadow: `${zoneStyle.glowOffsetX}px ${zoneStyle.glowOffsetY}px ${zoneStyle.glowRadius}px ${zoneStyle.glowSpread}px ${zoneStyle.glowColor}`
|
|
142
|
+
})
|
|
143
|
+
};
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Determine if we should show the border/glow
|
|
147
|
+
const showBorder = config?.enableGlow === true || zoneStyle.borderWidth > 0;
|
|
111
148
|
return /*#__PURE__*/_jsxs(AnimatedView, {
|
|
112
149
|
pointerEvents: containerPointerEvents,
|
|
113
150
|
style: StyleSheet.absoluteFill,
|
|
@@ -128,6 +165,9 @@ export const TourOverlay = /*#__PURE__*/memo(() => {
|
|
|
128
165
|
style: blockerStyle,
|
|
129
166
|
pointerEvents: "auto" // Catch touches
|
|
130
167
|
// backgroundColor="transparent" // Default
|
|
168
|
+
}), showBorder && currentStep && /*#__PURE__*/_jsx(AnimatedView, {
|
|
169
|
+
style: zoneBorderStyle,
|
|
170
|
+
pointerEvents: "none"
|
|
131
171
|
})]
|
|
132
172
|
});
|
|
133
173
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["memo","StyleSheet","Dimensions","Svg","Path","Animated","useAnimatedProps","useAnimatedStyle","useTour","jsx","_jsx","jsxs","_jsxs","width","SCREEN_WIDTH","height","SCREEN_HEIGHT","get","AnimatedPath","createAnimatedComponent","AnimatedView","View","createRoundedRectPath","x","y","w","h","r","radius","Math","min","TourOverlay","targetX","targetY","targetWidth","targetHeight","targetRadius","opacity","config","currentStep","steps","animatedProps","holePath","value","path","d","fillOpacity","step","isClickable","clickable","shouldBlockOutside","preventInteraction","containerPointerEvents","blockerStyle","position","left","top","borderRadius","pointerEvents","style","absoluteFill","children","fill","fillRule","onPress"],"sourceRoot":"..\\..\\..\\src","sources":["components/TourOverlay.tsx"],"mappings":";;AAAA,SAASA,IAAI,
|
|
1
|
+
{"version":3,"names":["memo","useMemo","StyleSheet","Dimensions","Svg","Path","Animated","useAnimatedProps","useAnimatedStyle","useTour","DEFAULT_ZONE_STYLE","jsx","_jsx","jsxs","_jsxs","width","SCREEN_WIDTH","height","SCREEN_HEIGHT","get","AnimatedPath","createAnimatedComponent","AnimatedView","View","createRoundedRectPath","x","y","w","h","r","radius","Math","min","TourOverlay","targetX","targetY","targetWidth","targetHeight","targetRadius","opacity","zoneBorderWidth","config","currentStep","steps","currentZoneStyle","zoneStyle","animatedProps","holePath","value","path","d","fillOpacity","step","isClickable","clickable","shouldBlockOutside","preventInteraction","containerPointerEvents","blockerStyle","position","left","top","borderRadius","zoneBorderStyle","isGlowEnabled","enableGlow","borderW","borderWidth","borderColor","backgroundColor","boxShadow","glowOffsetX","glowOffsetY","glowRadius","glowSpread","glowColor","showBorder","pointerEvents","style","absoluteFill","children","fill","fillRule","onPress"],"sourceRoot":"..\\..\\..\\src","sources":["components/TourOverlay.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,13 +1,88 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import React, {
|
|
3
|
+
import React, { useState, useCallback, useMemo, useRef, useEffect } from 'react';
|
|
4
4
|
import { useSharedValue, withSpring, withTiming, useAnimatedRef, default as Animated } from 'react-native-reanimated';
|
|
5
|
-
import { StyleSheet } from 'react-native';
|
|
5
|
+
import { StyleSheet, Dimensions } from 'react-native';
|
|
6
|
+
import { TourContext } from "../context/TourContext.js";
|
|
6
7
|
import { TourOverlay } from "./TourOverlay.js";
|
|
7
8
|
import { TourTooltip } from "./TourTooltip.js";
|
|
8
|
-
import { DEFAULT_BACKDROP_OPACITY, DEFAULT_SPRING_CONFIG } from "../constants/defaults.js";
|
|
9
|
+
import { DEFAULT_BACKDROP_OPACITY, DEFAULT_SPRING_CONFIG, DEFAULT_ZONE_STYLE, resolveZoneStyle } from "../constants/defaults.js";
|
|
10
|
+
import { detectStorage, saveTourProgress, loadTourProgress, clearTourProgress } from "../utils/storage.js";
|
|
9
11
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
10
|
-
|
|
12
|
+
const {
|
|
13
|
+
width: SCREEN_WIDTH,
|
|
14
|
+
height: SCREEN_HEIGHT
|
|
15
|
+
} = Dimensions.get('window');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Computes the zone geometry based on element bounds and zone style.
|
|
19
|
+
* Handles different shapes: rounded-rect, circle, pill.
|
|
20
|
+
*/
|
|
21
|
+
function computeZoneGeometry(element, style) {
|
|
22
|
+
const {
|
|
23
|
+
paddingTop,
|
|
24
|
+
paddingRight,
|
|
25
|
+
paddingBottom,
|
|
26
|
+
paddingLeft,
|
|
27
|
+
shape,
|
|
28
|
+
borderRadius
|
|
29
|
+
} = style;
|
|
30
|
+
let sx, sy, sw, sh, sr;
|
|
31
|
+
switch (shape) {
|
|
32
|
+
case 'circle':
|
|
33
|
+
{
|
|
34
|
+
// Create a circular zone that encompasses the element
|
|
35
|
+
const cx = element.x + element.width / 2;
|
|
36
|
+
const cy = element.y + element.height / 2;
|
|
37
|
+
// Use the larger dimension to create a circle, plus padding
|
|
38
|
+
const radius = Math.max(element.width, element.height) / 2 + style.padding;
|
|
39
|
+
sx = cx - radius;
|
|
40
|
+
sy = cy - radius;
|
|
41
|
+
sw = radius * 2;
|
|
42
|
+
sh = radius * 2;
|
|
43
|
+
sr = radius;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
case 'pill':
|
|
47
|
+
{
|
|
48
|
+
// Pill shape with fully rounded ends
|
|
49
|
+
sx = element.x - paddingLeft;
|
|
50
|
+
sy = element.y - paddingTop;
|
|
51
|
+
sw = element.width + paddingLeft + paddingRight;
|
|
52
|
+
sh = element.height + paddingTop + paddingBottom;
|
|
53
|
+
sr = sh / 2; // Fully rounded based on height
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
case 'rounded-rect':
|
|
57
|
+
default:
|
|
58
|
+
{
|
|
59
|
+
sx = element.x - paddingLeft;
|
|
60
|
+
sy = element.y - paddingTop;
|
|
61
|
+
sw = element.width + paddingLeft + paddingRight;
|
|
62
|
+
sh = element.height + paddingTop + paddingBottom;
|
|
63
|
+
sr = borderRadius;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Clamp to screen bounds
|
|
69
|
+
sx = Math.max(0, Math.min(sx, SCREEN_WIDTH - sw));
|
|
70
|
+
sy = Math.max(0, Math.min(sy, SCREEN_HEIGHT - sh));
|
|
71
|
+
sw = Math.min(sw, SCREEN_WIDTH - sx);
|
|
72
|
+
sh = Math.min(sh, SCREEN_HEIGHT - sy);
|
|
73
|
+
|
|
74
|
+
// Ensure minimum size
|
|
75
|
+
const minSize = 40;
|
|
76
|
+
sw = Math.max(sw, minSize);
|
|
77
|
+
sh = Math.max(sh, minSize);
|
|
78
|
+
return {
|
|
79
|
+
x: sx,
|
|
80
|
+
y: sy,
|
|
81
|
+
width: sw,
|
|
82
|
+
height: sh,
|
|
83
|
+
borderRadius: sr
|
|
84
|
+
};
|
|
85
|
+
}
|
|
11
86
|
const AnimatedView = Animated.View;
|
|
12
87
|
export const TourProvider = ({
|
|
13
88
|
children,
|
|
@@ -17,11 +92,55 @@ export const TourProvider = ({
|
|
|
17
92
|
}) => {
|
|
18
93
|
const [steps, setSteps] = useState({});
|
|
19
94
|
const [currentStep, setCurrentStep] = useState(null);
|
|
95
|
+
const [hasSavedProgress, setHasSavedProgress] = useState(false);
|
|
20
96
|
|
|
21
97
|
// ref to access latest measurements without causing re-renders
|
|
22
98
|
const measurements = useRef({});
|
|
23
99
|
const containerRef = useAnimatedRef();
|
|
24
100
|
|
|
101
|
+
// ─── Persistence Setup ─────────────────────────────────────────────────────
|
|
102
|
+
const persistenceConfig = config?.persistence;
|
|
103
|
+
const isPersistenceEnabled = persistenceConfig?.enabled ?? false;
|
|
104
|
+
const tourId = persistenceConfig?.tourId ?? 'default';
|
|
105
|
+
const autoResume = persistenceConfig?.autoResume ?? true;
|
|
106
|
+
const clearOnComplete = persistenceConfig?.clearOnComplete ?? true;
|
|
107
|
+
const maxAge = persistenceConfig?.maxAge;
|
|
108
|
+
|
|
109
|
+
// Get storage adapter (custom or auto-detected)
|
|
110
|
+
const storageAdapter = useMemo(() => {
|
|
111
|
+
if (!isPersistenceEnabled) return null;
|
|
112
|
+
if (persistenceConfig?.storage) return persistenceConfig.storage;
|
|
113
|
+
const detected = detectStorage();
|
|
114
|
+
return detected.adapter;
|
|
115
|
+
}, [isPersistenceEnabled, persistenceConfig?.storage]);
|
|
116
|
+
|
|
117
|
+
// Check for saved progress on mount
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (!isPersistenceEnabled || !storageAdapter) {
|
|
120
|
+
setHasSavedProgress(false);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const checkSavedProgress = async () => {
|
|
124
|
+
try {
|
|
125
|
+
const savedProgress = await loadTourProgress(storageAdapter, tourId);
|
|
126
|
+
if (savedProgress) {
|
|
127
|
+
// Check if progress is expired
|
|
128
|
+
if (maxAge && Date.now() - savedProgress.timestamp > maxAge) {
|
|
129
|
+
await clearTourProgress(storageAdapter, tourId);
|
|
130
|
+
setHasSavedProgress(false);
|
|
131
|
+
} else {
|
|
132
|
+
setHasSavedProgress(true);
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
setHasSavedProgress(false);
|
|
136
|
+
}
|
|
137
|
+
} catch {
|
|
138
|
+
setHasSavedProgress(false);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
checkSavedProgress();
|
|
142
|
+
}, [isPersistenceEnabled, storageAdapter, tourId, maxAge]);
|
|
143
|
+
|
|
25
144
|
// --- Shared Values for Animations (Zero Bridge Crossing) ---
|
|
26
145
|
// Initialize off-screen or 0
|
|
27
146
|
const targetX = useSharedValue(0);
|
|
@@ -30,6 +149,35 @@ export const TourProvider = ({
|
|
|
30
149
|
const targetHeight = useSharedValue(0);
|
|
31
150
|
const targetRadius = useSharedValue(10); // Default border radius
|
|
32
151
|
const opacity = useSharedValue(0); // 0 = hidden, 1 = visible
|
|
152
|
+
const zoneBorderWidth = useSharedValue(DEFAULT_ZONE_STYLE.borderWidth);
|
|
153
|
+
|
|
154
|
+
// Track current step's resolved zone style
|
|
155
|
+
const currentZoneStyle = useMemo(() => {
|
|
156
|
+
if (!currentStep) return null;
|
|
157
|
+
const step = steps[currentStep];
|
|
158
|
+
if (!step) return null;
|
|
159
|
+
return resolveZoneStyle(config?.zoneStyle, step.zoneStyle);
|
|
160
|
+
}, [currentStep, steps, config?.zoneStyle]);
|
|
161
|
+
|
|
162
|
+
// Helper to get spring config for a step (supports per-step overrides)
|
|
163
|
+
const getSpringConfigForStep = useCallback(stepKey => {
|
|
164
|
+
const step = steps[stepKey];
|
|
165
|
+
const stepStyle = step?.zoneStyle;
|
|
166
|
+
const baseConfig = config?.springConfig ?? DEFAULT_SPRING_CONFIG;
|
|
167
|
+
|
|
168
|
+
// Allow per-step spring overrides
|
|
169
|
+
if (stepStyle?.springDamping !== undefined || stepStyle?.springStiffness !== undefined) {
|
|
170
|
+
return {
|
|
171
|
+
damping: stepStyle.springDamping ?? baseConfig.damping,
|
|
172
|
+
stiffness: stepStyle.springStiffness ?? baseConfig.stiffness,
|
|
173
|
+
mass: baseConfig.mass,
|
|
174
|
+
overshootClamping: baseConfig.overshootClamping,
|
|
175
|
+
restDisplacementThreshold: baseConfig.restDisplacementThreshold,
|
|
176
|
+
restSpeedThreshold: baseConfig.restSpeedThreshold
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return baseConfig;
|
|
180
|
+
}, [steps, config?.springConfig]);
|
|
33
181
|
|
|
34
182
|
// Helper to animate to a specific step's layout
|
|
35
183
|
const animateToStep = useCallback(stepKey => {
|
|
@@ -40,18 +188,18 @@ export const TourProvider = ({
|
|
|
40
188
|
console.warn('[TourProvider] Invalid measurements for step:', stepKey, measure);
|
|
41
189
|
return;
|
|
42
190
|
}
|
|
43
|
-
const springConfig = config?.springConfig ?? DEFAULT_SPRING_CONFIG;
|
|
44
|
-
targetX.value = withSpring(measure.x, springConfig);
|
|
45
|
-
targetY.value = withSpring(measure.y, springConfig);
|
|
46
|
-
targetWidth.value = withSpring(measure.width, springConfig);
|
|
47
|
-
targetHeight.value = withSpring(measure.height, springConfig);
|
|
48
|
-
|
|
49
|
-
// If measure result has radius or step meta has radius, use it.
|
|
50
|
-
// For now defaulting to 10 or meta reading if we passed it back in measure?
|
|
51
|
-
// Let's assume meta is in steps state.
|
|
52
191
|
const step = steps[stepKey];
|
|
53
|
-
const
|
|
54
|
-
|
|
192
|
+
const resolvedStyle = resolveZoneStyle(config?.zoneStyle, step?.zoneStyle);
|
|
193
|
+
const springConfig = getSpringConfigForStep(stepKey);
|
|
194
|
+
|
|
195
|
+
// Compute zone geometry based on style (handles shapes and padding)
|
|
196
|
+
const geo = computeZoneGeometry(measure, resolvedStyle);
|
|
197
|
+
targetX.value = withSpring(geo.x, springConfig);
|
|
198
|
+
targetY.value = withSpring(geo.y, springConfig);
|
|
199
|
+
targetWidth.value = withSpring(geo.width, springConfig);
|
|
200
|
+
targetHeight.value = withSpring(geo.height, springConfig);
|
|
201
|
+
targetRadius.value = withSpring(geo.borderRadius, springConfig);
|
|
202
|
+
zoneBorderWidth.value = withSpring(resolvedStyle.borderWidth, springConfig);
|
|
55
203
|
|
|
56
204
|
// Ensure overlay is visible
|
|
57
205
|
opacity.value = withTiming(backdropOpacity, {
|
|
@@ -60,12 +208,18 @@ export const TourProvider = ({
|
|
|
60
208
|
} else {
|
|
61
209
|
console.warn('[TourProvider] No measurements found for step:', stepKey);
|
|
62
210
|
}
|
|
63
|
-
}, [backdropOpacity, targetX, targetY, targetWidth, targetHeight, targetRadius, opacity,
|
|
211
|
+
}, [backdropOpacity, targetX, targetY, targetWidth, targetHeight, targetRadius, zoneBorderWidth, opacity, getSpringConfigForStep, steps, config?.zoneStyle]);
|
|
64
212
|
const registerStep = useCallback(step => {
|
|
65
213
|
setSteps(prev => ({
|
|
66
214
|
...prev,
|
|
67
215
|
[step.key]: step
|
|
68
216
|
}));
|
|
217
|
+
// If this step was pending (waiting for cross-screen mount), activate it
|
|
218
|
+
if (pendingStepRef.current === step.key) {
|
|
219
|
+
pendingStepRef.current = null;
|
|
220
|
+
setCurrentStep(step.key);
|
|
221
|
+
// Overlay opacity will be set by updateStepLayout when measurement arrives
|
|
222
|
+
}
|
|
69
223
|
}, []);
|
|
70
224
|
const unregisterStep = useCallback(key => {
|
|
71
225
|
setSteps(prev => {
|
|
@@ -85,25 +239,35 @@ export const TourProvider = ({
|
|
|
85
239
|
measurements.current[key] = measure;
|
|
86
240
|
// If this step is currently active (e.g. scroll happened or resize), update shared values on the fly
|
|
87
241
|
if (currentStep === key) {
|
|
88
|
-
const springConfig = config?.springConfig ?? DEFAULT_SPRING_CONFIG;
|
|
89
|
-
targetX.value = withSpring(measure.x, springConfig);
|
|
90
|
-
targetY.value = withSpring(measure.y, springConfig);
|
|
91
|
-
targetWidth.value = withSpring(measure.width, springConfig);
|
|
92
|
-
targetHeight.value = withSpring(measure.height, springConfig);
|
|
93
|
-
|
|
94
|
-
// Update radius if available
|
|
95
242
|
const step = steps[key];
|
|
96
|
-
const
|
|
97
|
-
|
|
243
|
+
const resolvedStyle = resolveZoneStyle(config?.zoneStyle, step?.zoneStyle);
|
|
244
|
+
const springConfig = getSpringConfigForStep(key);
|
|
245
|
+
|
|
246
|
+
// Compute zone geometry based on style
|
|
247
|
+
const geo = computeZoneGeometry(measure, resolvedStyle);
|
|
248
|
+
targetX.value = withSpring(geo.x, springConfig);
|
|
249
|
+
targetY.value = withSpring(geo.y, springConfig);
|
|
250
|
+
targetWidth.value = withSpring(geo.width, springConfig);
|
|
251
|
+
targetHeight.value = withSpring(geo.height, springConfig);
|
|
252
|
+
targetRadius.value = withSpring(geo.borderRadius, springConfig);
|
|
253
|
+
zoneBorderWidth.value = withSpring(resolvedStyle.borderWidth, springConfig);
|
|
98
254
|
|
|
99
255
|
// Ensure overlay is visible (fixes race condition where start() was called before measure)
|
|
100
256
|
opacity.value = withTiming(backdropOpacity, {
|
|
101
257
|
duration: 300
|
|
102
258
|
});
|
|
103
259
|
}
|
|
104
|
-
}, [currentStep, targetX, targetY, targetWidth, targetHeight, targetRadius, opacity, backdropOpacity, config?.
|
|
260
|
+
}, [currentStep, targetX, targetY, targetWidth, targetHeight, targetRadius, zoneBorderWidth, opacity, backdropOpacity, getSpringConfigForStep, config?.zoneStyle, steps]);
|
|
261
|
+
|
|
262
|
+
// Flatten stepsOrder (supports both string[] and Record<string, string[]>)
|
|
263
|
+
const flatStepsOrder = useMemo(() => {
|
|
264
|
+
if (!initialStepsOrder) return undefined;
|
|
265
|
+
if (Array.isArray(initialStepsOrder)) return initialStepsOrder;
|
|
266
|
+
// Object format: flatten values in key order
|
|
267
|
+
return Object.values(initialStepsOrder).flat();
|
|
268
|
+
}, [initialStepsOrder]);
|
|
105
269
|
const getOrderedSteps = useCallback(() => {
|
|
106
|
-
if (
|
|
270
|
+
if (flatStepsOrder) return flatStepsOrder;
|
|
107
271
|
// If order property exists on steps, sort by it.
|
|
108
272
|
const stepKeys = Object.keys(steps);
|
|
109
273
|
if (stepKeys.length > 0) {
|
|
@@ -114,64 +278,149 @@ export const TourProvider = ({
|
|
|
114
278
|
}
|
|
115
279
|
}
|
|
116
280
|
return stepKeys;
|
|
117
|
-
}, [
|
|
118
|
-
|
|
281
|
+
}, [flatStepsOrder, steps]);
|
|
282
|
+
|
|
283
|
+
// Pending step for cross-screen navigation
|
|
284
|
+
// When next/prev targets a step that isn't mounted yet, we store it here
|
|
285
|
+
// and resume when that step's TourZone registers.
|
|
286
|
+
const pendingStepRef = useRef(null);
|
|
287
|
+
|
|
288
|
+
// Save progress when step changes
|
|
289
|
+
useEffect(() => {
|
|
290
|
+
if (!isPersistenceEnabled || !storageAdapter || !currentStep) return;
|
|
119
291
|
const ordered = getOrderedSteps();
|
|
120
|
-
const
|
|
292
|
+
const stepIndex = ordered.indexOf(currentStep);
|
|
293
|
+
if (stepIndex >= 0) {
|
|
294
|
+
saveTourProgress(storageAdapter, tourId, currentStep, stepIndex).catch(() => {
|
|
295
|
+
// Silently ignore save errors
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}, [currentStep, isPersistenceEnabled, storageAdapter, tourId, getOrderedSteps]);
|
|
299
|
+
const start = useCallback(async stepKey => {
|
|
300
|
+
const ordered = getOrderedSteps();
|
|
301
|
+
let targetStep = stepKey;
|
|
302
|
+
|
|
303
|
+
// If no specific step and autoResume is enabled, try to restore from storage
|
|
304
|
+
if (!targetStep && isPersistenceEnabled && storageAdapter && autoResume) {
|
|
305
|
+
try {
|
|
306
|
+
const savedProgress = await loadTourProgress(storageAdapter, tourId);
|
|
307
|
+
if (savedProgress) {
|
|
308
|
+
// Check if progress is expired
|
|
309
|
+
if (maxAge && Date.now() - savedProgress.timestamp > maxAge) {
|
|
310
|
+
await clearTourProgress(storageAdapter, tourId);
|
|
311
|
+
setHasSavedProgress(false);
|
|
312
|
+
} else if (ordered.includes(savedProgress.currentStepKey)) {
|
|
313
|
+
// Verify the saved step still exists in order
|
|
314
|
+
targetStep = savedProgress.currentStepKey;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
} catch {
|
|
318
|
+
// Ignore load errors, start from beginning
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const firstStep = targetStep || ordered[0];
|
|
121
322
|
if (firstStep) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
323
|
+
// Check if the target step is registered (mounted)
|
|
324
|
+
if (steps[firstStep]) {
|
|
325
|
+
setCurrentStep(firstStep);
|
|
326
|
+
setTimeout(() => animateToStep(firstStep), 0);
|
|
327
|
+
} else {
|
|
328
|
+
// Step not mounted yet (on a different screen) - set as pending
|
|
329
|
+
pendingStepRef.current = firstStep;
|
|
330
|
+
// Don't set currentStep or opacity - wait for TourZone to mount
|
|
331
|
+
}
|
|
130
332
|
}
|
|
131
|
-
}, [getOrderedSteps, animateToStep]);
|
|
333
|
+
}, [getOrderedSteps, animateToStep, steps, isPersistenceEnabled, storageAdapter, autoResume, tourId, maxAge]);
|
|
132
334
|
const stop = useCallback(() => {
|
|
133
335
|
setCurrentStep(null);
|
|
134
336
|
opacity.value = withTiming(0, {
|
|
135
337
|
duration: 300
|
|
136
338
|
});
|
|
339
|
+
// Note: We do NOT clear progress on stop - only on complete or explicit clearProgress
|
|
137
340
|
}, [opacity]);
|
|
341
|
+
|
|
342
|
+
// Clear progress helper
|
|
343
|
+
const clearProgress = useCallback(async () => {
|
|
344
|
+
if (!isPersistenceEnabled || !storageAdapter) return;
|
|
345
|
+
try {
|
|
346
|
+
await clearTourProgress(storageAdapter, tourId);
|
|
347
|
+
setHasSavedProgress(false);
|
|
348
|
+
} catch {
|
|
349
|
+
// Silently ignore clear errors
|
|
350
|
+
}
|
|
351
|
+
}, [isPersistenceEnabled, storageAdapter, tourId]);
|
|
138
352
|
const next = useCallback(() => {
|
|
139
353
|
if (!currentStep) return;
|
|
354
|
+
|
|
355
|
+
// Block navigation if current step has completed === false
|
|
356
|
+
const currentStepData = steps[currentStep];
|
|
357
|
+
if (currentStepData?.completed === false) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
140
360
|
const ordered = getOrderedSteps();
|
|
141
361
|
const currentIndex = ordered.indexOf(currentStep);
|
|
142
362
|
if (currentIndex < ordered.length - 1) {
|
|
143
|
-
const
|
|
144
|
-
if (
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
363
|
+
const nextStepKey = ordered[currentIndex + 1];
|
|
364
|
+
if (nextStepKey) {
|
|
365
|
+
// Check if the next step is registered (mounted)
|
|
366
|
+
if (steps[nextStepKey]) {
|
|
367
|
+
setCurrentStep(nextStepKey);
|
|
368
|
+
// Don't call animateToStep here - it uses cached measurements that may be stale
|
|
369
|
+
// after scroll. The useFrameCallback in TourZone will handle position tracking
|
|
370
|
+
// using measure() with correct screen coordinates (pageX/pageY).
|
|
371
|
+
// Just ensure the overlay is visible.
|
|
372
|
+
opacity.value = withTiming(backdropOpacity, {
|
|
373
|
+
duration: 300
|
|
374
|
+
});
|
|
375
|
+
} else {
|
|
376
|
+
// Step not mounted yet (on a different screen) - set as pending
|
|
377
|
+
pendingStepRef.current = nextStepKey;
|
|
378
|
+
setCurrentStep(null);
|
|
379
|
+
opacity.value = withTiming(0, {
|
|
380
|
+
duration: 300
|
|
381
|
+
});
|
|
382
|
+
// Persist pending step so it can be resumed
|
|
383
|
+
if (isPersistenceEnabled && storageAdapter) {
|
|
384
|
+
const stepIndex = ordered.indexOf(nextStepKey);
|
|
385
|
+
saveTourProgress(storageAdapter, tourId, nextStepKey, stepIndex).catch(() => {});
|
|
386
|
+
}
|
|
387
|
+
}
|
|
153
388
|
} else {
|
|
154
389
|
stop();
|
|
155
390
|
}
|
|
156
391
|
} else {
|
|
157
|
-
|
|
392
|
+
// End of tour - clear progress if configured
|
|
393
|
+
if (isPersistenceEnabled && clearOnComplete && storageAdapter) {
|
|
394
|
+
clearTourProgress(storageAdapter, tourId).then(() => setHasSavedProgress(false)).catch(() => {});
|
|
395
|
+
}
|
|
396
|
+
stop();
|
|
158
397
|
}
|
|
159
|
-
}, [currentStep, getOrderedSteps, stop, opacity, backdropOpacity]);
|
|
398
|
+
}, [currentStep, steps, getOrderedSteps, stop, opacity, backdropOpacity, isPersistenceEnabled, clearOnComplete, storageAdapter, tourId]);
|
|
160
399
|
const prev = useCallback(() => {
|
|
161
400
|
if (!currentStep) return;
|
|
162
401
|
const ordered = getOrderedSteps();
|
|
163
402
|
const currentIndex = ordered.indexOf(currentStep);
|
|
164
403
|
if (currentIndex > 0) {
|
|
165
|
-
const
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
404
|
+
const prevStepKey = ordered[currentIndex - 1];
|
|
405
|
+
if (prevStepKey) {
|
|
406
|
+
// Check if the previous step is registered (mounted)
|
|
407
|
+
if (steps[prevStepKey]) {
|
|
408
|
+
setCurrentStep(prevStepKey);
|
|
409
|
+
// Don't call animateToStep - let useFrameCallback handle position tracking
|
|
410
|
+
opacity.value = withTiming(backdropOpacity, {
|
|
411
|
+
duration: 300
|
|
412
|
+
});
|
|
413
|
+
} else {
|
|
414
|
+
// Step not mounted (on a different screen) - set as pending
|
|
415
|
+
pendingStepRef.current = prevStepKey;
|
|
416
|
+
setCurrentStep(null);
|
|
417
|
+
opacity.value = withTiming(0, {
|
|
418
|
+
duration: 300
|
|
419
|
+
});
|
|
420
|
+
}
|
|
172
421
|
}
|
|
173
422
|
}
|
|
174
|
-
}, [currentStep, getOrderedSteps, opacity, backdropOpacity]);
|
|
423
|
+
}, [currentStep, steps, getOrderedSteps, opacity, backdropOpacity]);
|
|
175
424
|
const scrollViewRef = useAnimatedRef();
|
|
176
425
|
const setScrollViewRef = useCallback(_ref => {
|
|
177
426
|
// If user passes a ref, we might want to sync it?
|
|
@@ -192,6 +441,9 @@ export const TourProvider = ({
|
|
|
192
441
|
// Let's make `setScrollViewRef` actually do something if possible, or just document "Use exposed scrollViewRef".
|
|
193
442
|
// For now, let's just return the `scrollViewRef` we created.
|
|
194
443
|
}, []);
|
|
444
|
+
|
|
445
|
+
// Expose ordered step keys for tooltip and external use
|
|
446
|
+
const orderedStepKeys = useMemo(() => getOrderedSteps(), [getOrderedSteps]);
|
|
195
447
|
const value = useMemo(() => ({
|
|
196
448
|
start,
|
|
197
449
|
stop,
|
|
@@ -207,12 +459,17 @@ export const TourProvider = ({
|
|
|
207
459
|
targetHeight,
|
|
208
460
|
targetRadius,
|
|
209
461
|
opacity,
|
|
462
|
+
zoneBorderWidth,
|
|
210
463
|
steps,
|
|
211
464
|
config,
|
|
212
465
|
containerRef,
|
|
213
466
|
scrollViewRef,
|
|
214
|
-
setScrollViewRef
|
|
215
|
-
|
|
467
|
+
setScrollViewRef,
|
|
468
|
+
currentZoneStyle,
|
|
469
|
+
clearProgress,
|
|
470
|
+
hasSavedProgress,
|
|
471
|
+
orderedStepKeys
|
|
472
|
+
}), [start, stop, next, prev, registerStep, unregisterStep, updateStepLayout, currentStep, targetX, targetY, targetWidth, targetHeight, targetRadius, opacity, zoneBorderWidth, steps, config, containerRef, scrollViewRef, setScrollViewRef, currentZoneStyle, clearProgress, hasSavedProgress, orderedStepKeys]);
|
|
216
473
|
return /*#__PURE__*/_jsx(TourContext.Provider, {
|
|
217
474
|
value: value,
|
|
218
475
|
children: /*#__PURE__*/_jsxs(AnimatedView, {
|