react-native-lumen 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +75 -712
  3. package/lib/module/components/TourOverlay.js +10 -50
  4. package/lib/module/components/TourOverlay.js.map +1 -1
  5. package/lib/module/components/TourProvider.js +41 -33
  6. package/lib/module/components/TourProvider.js.map +1 -1
  7. package/lib/module/components/TourTooltip.js +1 -1
  8. package/lib/module/components/TourZone.js +67 -30
  9. package/lib/module/components/TourZone.js.map +1 -1
  10. package/lib/module/constants/animations.js +16 -16
  11. package/lib/module/constants/defaults.js +5 -5
  12. package/lib/module/hooks/useTourScrollView.js +28 -25
  13. package/lib/module/hooks/useTourScrollView.js.map +1 -1
  14. package/lib/module/utils/storage.js +26 -26
  15. package/lib/typescript/src/components/TourOverlay.d.ts.map +1 -1
  16. package/lib/typescript/src/components/TourProvider.d.ts.map +1 -1
  17. package/lib/typescript/src/components/TourZone.d.ts.map +1 -1
  18. package/lib/typescript/src/hooks/useTourScrollView.d.ts +12 -1
  19. package/lib/typescript/src/hooks/useTourScrollView.d.ts.map +1 -1
  20. package/lib/typescript/src/types/index.d.ts +20 -0
  21. package/lib/typescript/src/types/index.d.ts.map +1 -1
  22. package/package.json +2 -6
  23. package/src/components/TourOverlay.tsx +0 -196
  24. package/src/components/TourProvider.tsx +0 -713
  25. package/src/components/TourTooltip.tsx +0 -329
  26. package/src/components/TourZone.tsx +0 -469
  27. package/src/constants/animations.ts +0 -71
  28. package/src/constants/defaults.ts +0 -66
  29. package/src/context/TourContext.ts +0 -4
  30. package/src/hooks/useTour.ts +0 -10
  31. package/src/hooks/useTourScrollView.ts +0 -111
  32. package/src/index.tsx +0 -35
  33. package/src/types/index.ts +0 -447
  34. package/src/utils/storage.ts +0 -226
@@ -1,713 +0,0 @@
1
- import React, {
2
- useState,
3
- useCallback,
4
- useMemo,
5
- useRef,
6
- useEffect,
7
- type ComponentType,
8
- } from 'react';
9
- import {
10
- useSharedValue,
11
- withSpring,
12
- withTiming,
13
- useAnimatedRef,
14
- default as Animated,
15
- type WithSpringConfig,
16
- } from 'react-native-reanimated';
17
- import { StyleSheet, Dimensions } from 'react-native';
18
- import type {
19
- TourStep,
20
- MeasureResult,
21
- TourConfig,
22
- InternalTourContextType,
23
- ZoneStyle,
24
- StorageAdapter,
25
- StepsOrder,
26
- } from '../types';
27
- import { TourContext } from '../context/TourContext';
28
- import { TourOverlay } from './TourOverlay';
29
- import { TourTooltip } from './TourTooltip';
30
- import {
31
- DEFAULT_BACKDROP_OPACITY,
32
- DEFAULT_SPRING_CONFIG,
33
- DEFAULT_ZONE_STYLE,
34
- resolveZoneStyle,
35
- } from '../constants/defaults';
36
- import {
37
- detectStorage,
38
- saveTourProgress,
39
- loadTourProgress,
40
- clearTourProgress,
41
- } from '../utils/storage';
42
-
43
- const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');
44
-
45
- /**
46
- * Computes the zone geometry based on element bounds and zone style.
47
- * Handles different shapes: rounded-rect, circle, pill.
48
- */
49
- function computeZoneGeometry(
50
- element: MeasureResult,
51
- style: Required<ZoneStyle>
52
- ): {
53
- x: number;
54
- y: number;
55
- width: number;
56
- height: number;
57
- borderRadius: number;
58
- } {
59
- const {
60
- paddingTop,
61
- paddingRight,
62
- paddingBottom,
63
- paddingLeft,
64
- shape,
65
- borderRadius,
66
- } = style;
67
-
68
- let sx: number, sy: number, sw: number, sh: number, sr: number;
69
-
70
- switch (shape) {
71
- case 'circle': {
72
- // Create a circular zone that encompasses the element
73
- const cx = element.x + element.width / 2;
74
- const cy = element.y + element.height / 2;
75
- // Use the larger dimension to create a circle, plus padding
76
- const radius =
77
- Math.max(element.width, element.height) / 2 + style.padding;
78
- sx = cx - radius;
79
- sy = cy - radius;
80
- sw = radius * 2;
81
- sh = radius * 2;
82
- sr = radius;
83
- break;
84
- }
85
- case 'pill': {
86
- // Pill shape with fully rounded ends
87
- sx = element.x - paddingLeft;
88
- sy = element.y - paddingTop;
89
- sw = element.width + paddingLeft + paddingRight;
90
- sh = element.height + paddingTop + paddingBottom;
91
- sr = sh / 2; // Fully rounded based on height
92
- break;
93
- }
94
- case 'rounded-rect':
95
- default: {
96
- sx = element.x - paddingLeft;
97
- sy = element.y - paddingTop;
98
- sw = element.width + paddingLeft + paddingRight;
99
- sh = element.height + paddingTop + paddingBottom;
100
- sr = borderRadius;
101
- break;
102
- }
103
- }
104
-
105
- // Clamp to screen bounds
106
- sx = Math.max(0, Math.min(sx, SCREEN_WIDTH - sw));
107
- sy = Math.max(0, Math.min(sy, SCREEN_HEIGHT - sh));
108
- sw = Math.min(sw, SCREEN_WIDTH - sx);
109
- sh = Math.min(sh, SCREEN_HEIGHT - sy);
110
-
111
- // Ensure minimum size
112
- const minSize = 40;
113
- sw = Math.max(sw, minSize);
114
- sh = Math.max(sh, minSize);
115
-
116
- return { x: sx, y: sy, width: sw, height: sh, borderRadius: sr };
117
- }
118
-
119
- interface TourProviderProps {
120
- children: React.ReactNode;
121
- /**
122
- * Optional custom steps order. Supports two formats:
123
- *
124
- * **Flat array** (single-screen or simple multi-screen):
125
- * ```
126
- * stepsOrder={['bio', 'prompt', 'poll', 'filters', 'swipeableCards']}
127
- * ```
128
- *
129
- * **Screen-grouped object** (multi-screen tours):
130
- * ```
131
- * stepsOrder={{
132
- * ProfileSelf: ['bio', 'prompt', 'poll'],
133
- * HomeSwipe: ['filters'],
134
- * SwipeableCards: ['swipeableCards'],
135
- * }}
136
- * ```
137
- *
138
- * When using the object format, steps are flattened in the order the screens appear.
139
- * The tour automatically waits when advancing to a step on an unmounted screen,
140
- * and resumes when that step's TourZone mounts.
141
- */
142
- stepsOrder?: StepsOrder;
143
- /**
144
- * Initial overlay opacity. Default 0.5
145
- */
146
- backdropOpacity?: number;
147
- /**
148
- * Global configuration for the tour.
149
- */
150
- config?: TourConfig;
151
- }
152
-
153
- const AnimatedView = Animated.View as unknown as ComponentType<any>;
154
-
155
- export const TourProvider: React.FC<TourProviderProps> = ({
156
- children,
157
- stepsOrder: initialStepsOrder,
158
- backdropOpacity = DEFAULT_BACKDROP_OPACITY,
159
- config,
160
- }) => {
161
- const [steps, setSteps] = useState<Record<string, TourStep>>({});
162
- const [currentStep, setCurrentStep] = useState<string | null>(null);
163
- const [hasSavedProgress, setHasSavedProgress] = useState(false);
164
-
165
- // ref to access latest measurements without causing re-renders
166
- const measurements = useRef<Record<string, MeasureResult>>({});
167
- const containerRef = useAnimatedRef<any>();
168
-
169
- // ─── Persistence Setup ─────────────────────────────────────────────────────
170
- const persistenceConfig = config?.persistence;
171
- const isPersistenceEnabled = persistenceConfig?.enabled ?? false;
172
- const tourId = persistenceConfig?.tourId ?? 'default';
173
- const autoResume = persistenceConfig?.autoResume ?? true;
174
- const clearOnComplete = persistenceConfig?.clearOnComplete ?? true;
175
- const maxAge = persistenceConfig?.maxAge;
176
-
177
- // Get storage adapter (custom or auto-detected)
178
- const storageAdapter = useMemo<StorageAdapter | null>(() => {
179
- if (!isPersistenceEnabled) return null;
180
- if (persistenceConfig?.storage) return persistenceConfig.storage;
181
- const detected = detectStorage();
182
- return detected.adapter;
183
- }, [isPersistenceEnabled, persistenceConfig?.storage]);
184
-
185
- // Check for saved progress on mount
186
- useEffect(() => {
187
- if (!isPersistenceEnabled || !storageAdapter) {
188
- setHasSavedProgress(false);
189
- return;
190
- }
191
-
192
- const checkSavedProgress = async () => {
193
- try {
194
- const savedProgress = await loadTourProgress(storageAdapter, tourId);
195
- if (savedProgress) {
196
- // Check if progress is expired
197
- if (maxAge && Date.now() - savedProgress.timestamp > maxAge) {
198
- await clearTourProgress(storageAdapter, tourId);
199
- setHasSavedProgress(false);
200
- } else {
201
- setHasSavedProgress(true);
202
- }
203
- } else {
204
- setHasSavedProgress(false);
205
- }
206
- } catch {
207
- setHasSavedProgress(false);
208
- }
209
- };
210
-
211
- checkSavedProgress();
212
- }, [isPersistenceEnabled, storageAdapter, tourId, maxAge]);
213
-
214
- // --- Shared Values for Animations (Zero Bridge Crossing) ---
215
- // Initialize off-screen or 0
216
- const targetX = useSharedValue(0);
217
- const targetY = useSharedValue(0);
218
- const targetWidth = useSharedValue(0);
219
- const targetHeight = useSharedValue(0);
220
- const targetRadius = useSharedValue(10); // Default border radius
221
- const opacity = useSharedValue(0); // 0 = hidden, 1 = visible
222
- const zoneBorderWidth = useSharedValue(DEFAULT_ZONE_STYLE.borderWidth);
223
-
224
- // Track current step's resolved zone style
225
- const currentZoneStyle = useMemo<ZoneStyle | null>(() => {
226
- if (!currentStep) return null;
227
- const step = steps[currentStep];
228
- if (!step) return null;
229
- return resolveZoneStyle(config?.zoneStyle, step.zoneStyle);
230
- }, [currentStep, steps, config?.zoneStyle]);
231
-
232
- // Helper to get spring config for a step (supports per-step overrides)
233
- const getSpringConfigForStep = useCallback(
234
- (stepKey: string): WithSpringConfig => {
235
- const step = steps[stepKey];
236
- const stepStyle = step?.zoneStyle;
237
- const baseConfig = config?.springConfig ?? DEFAULT_SPRING_CONFIG;
238
-
239
- // Allow per-step spring overrides
240
- if (
241
- stepStyle?.springDamping !== undefined ||
242
- stepStyle?.springStiffness !== undefined
243
- ) {
244
- return {
245
- damping: stepStyle.springDamping ?? baseConfig.damping,
246
- stiffness: stepStyle.springStiffness ?? baseConfig.stiffness,
247
- mass: baseConfig.mass,
248
- overshootClamping: baseConfig.overshootClamping,
249
- restDisplacementThreshold: baseConfig.restDisplacementThreshold,
250
- restSpeedThreshold: baseConfig.restSpeedThreshold,
251
- };
252
- }
253
- return baseConfig;
254
- },
255
- [steps, config?.springConfig]
256
- );
257
-
258
- // Helper to animate to a specific step's layout
259
- const animateToStep = useCallback(
260
- (stepKey: string) => {
261
- const measure = measurements.current[stepKey];
262
- if (measure) {
263
- // Validate measurements before animating
264
- if (
265
- !measure.width ||
266
- !measure.height ||
267
- measure.width <= 0 ||
268
- measure.height <= 0 ||
269
- isNaN(measure.x) ||
270
- isNaN(measure.y) ||
271
- isNaN(measure.width) ||
272
- isNaN(measure.height)
273
- ) {
274
- console.warn(
275
- '[TourProvider] Invalid measurements for step:',
276
- stepKey,
277
- measure
278
- );
279
- return;
280
- }
281
-
282
- const step = steps[stepKey];
283
- const resolvedStyle = resolveZoneStyle(
284
- config?.zoneStyle,
285
- step?.zoneStyle
286
- );
287
- const springConfig = getSpringConfigForStep(stepKey);
288
-
289
- // Compute zone geometry based on style (handles shapes and padding)
290
- const geo = computeZoneGeometry(measure, resolvedStyle);
291
-
292
- targetX.value = withSpring(geo.x, springConfig);
293
- targetY.value = withSpring(geo.y, springConfig);
294
- targetWidth.value = withSpring(geo.width, springConfig);
295
- targetHeight.value = withSpring(geo.height, springConfig);
296
- targetRadius.value = withSpring(geo.borderRadius, springConfig);
297
- zoneBorderWidth.value = withSpring(
298
- resolvedStyle.borderWidth,
299
- springConfig
300
- );
301
-
302
- // Ensure overlay is visible
303
- opacity.value = withTiming(backdropOpacity, { duration: 300 });
304
- } else {
305
- console.warn('[TourProvider] No measurements found for step:', stepKey);
306
- }
307
- },
308
- [
309
- backdropOpacity,
310
- targetX,
311
- targetY,
312
- targetWidth,
313
- targetHeight,
314
- targetRadius,
315
- zoneBorderWidth,
316
- opacity,
317
- getSpringConfigForStep,
318
- steps,
319
- config?.zoneStyle,
320
- ]
321
- );
322
-
323
- const registerStep = useCallback((step: TourStep) => {
324
- setSteps((prev) => ({ ...prev, [step.key]: step }));
325
- // If this step was pending (waiting for cross-screen mount), activate it
326
- if (pendingStepRef.current === step.key) {
327
- pendingStepRef.current = null;
328
- setCurrentStep(step.key);
329
- // Overlay opacity will be set by updateStepLayout when measurement arrives
330
- }
331
- }, []);
332
-
333
- const unregisterStep = useCallback((key: string) => {
334
- setSteps((prev) => {
335
- const newSteps = { ...prev };
336
- delete newSteps[key];
337
- return newSteps;
338
- });
339
- }, []);
340
-
341
- const updateStepLayout = useCallback(
342
- (key: string, measure: MeasureResult) => {
343
- // Validate measurements before storing
344
- if (
345
- !measure.width ||
346
- !measure.height ||
347
- measure.width <= 0 ||
348
- measure.height <= 0 ||
349
- isNaN(measure.x) ||
350
- isNaN(measure.y) ||
351
- isNaN(measure.width) ||
352
- isNaN(measure.height) ||
353
- !isFinite(measure.x) ||
354
- !isFinite(measure.y) ||
355
- !isFinite(measure.width) ||
356
- !isFinite(measure.height)
357
- ) {
358
- console.warn(
359
- '[TourProvider] Invalid measurement update for step:',
360
- key,
361
- measure
362
- );
363
- return;
364
- }
365
-
366
- measurements.current[key] = measure;
367
- // If this step is currently active (e.g. scroll happened or resize), update shared values on the fly
368
- if (currentStep === key) {
369
- const step = steps[key];
370
- const resolvedStyle = resolveZoneStyle(
371
- config?.zoneStyle,
372
- step?.zoneStyle
373
- );
374
- const springConfig = getSpringConfigForStep(key);
375
-
376
- // Compute zone geometry based on style
377
- const geo = computeZoneGeometry(measure, resolvedStyle);
378
-
379
- targetX.value = withSpring(geo.x, springConfig);
380
- targetY.value = withSpring(geo.y, springConfig);
381
- targetWidth.value = withSpring(geo.width, springConfig);
382
- targetHeight.value = withSpring(geo.height, springConfig);
383
- targetRadius.value = withSpring(geo.borderRadius, springConfig);
384
- zoneBorderWidth.value = withSpring(
385
- resolvedStyle.borderWidth,
386
- springConfig
387
- );
388
-
389
- // Ensure overlay is visible (fixes race condition where start() was called before measure)
390
- opacity.value = withTiming(backdropOpacity, { duration: 300 });
391
- }
392
- },
393
- [
394
- currentStep,
395
- targetX,
396
- targetY,
397
- targetWidth,
398
- targetHeight,
399
- targetRadius,
400
- zoneBorderWidth,
401
- opacity,
402
- backdropOpacity,
403
- getSpringConfigForStep,
404
- config?.zoneStyle,
405
- steps,
406
- ]
407
- );
408
-
409
- // Flatten stepsOrder (supports both string[] and Record<string, string[]>)
410
- const flatStepsOrder = useMemo<string[] | undefined>(() => {
411
- if (!initialStepsOrder) return undefined;
412
- if (Array.isArray(initialStepsOrder)) return initialStepsOrder;
413
- // Object format: flatten values in key order
414
- return Object.values(initialStepsOrder).flat();
415
- }, [initialStepsOrder]);
416
-
417
- const getOrderedSteps = useCallback(() => {
418
- if (flatStepsOrder) return flatStepsOrder;
419
- // If order property exists on steps, sort by it.
420
- const stepKeys = Object.keys(steps);
421
- if (stepKeys.length > 0) {
422
- // Check if any step has order
423
- const hasOrder = stepKeys.some(
424
- (key) => typeof steps[key]?.order === 'number'
425
- );
426
- if (hasOrder) {
427
- return stepKeys.sort(
428
- (a, b) => (steps[a]?.order ?? 0) - (steps[b]?.order ?? 0)
429
- );
430
- }
431
- }
432
- return stepKeys;
433
- }, [flatStepsOrder, steps]);
434
-
435
- // Pending step for cross-screen navigation
436
- // When next/prev targets a step that isn't mounted yet, we store it here
437
- // and resume when that step's TourZone registers.
438
- const pendingStepRef = useRef<string | null>(null);
439
-
440
- // Save progress when step changes
441
- useEffect(() => {
442
- if (!isPersistenceEnabled || !storageAdapter || !currentStep) return;
443
-
444
- const ordered = getOrderedSteps();
445
- const stepIndex = ordered.indexOf(currentStep);
446
-
447
- if (stepIndex >= 0) {
448
- saveTourProgress(storageAdapter, tourId, currentStep, stepIndex).catch(
449
- () => {
450
- // Silently ignore save errors
451
- }
452
- );
453
- }
454
- }, [
455
- currentStep,
456
- isPersistenceEnabled,
457
- storageAdapter,
458
- tourId,
459
- getOrderedSteps,
460
- ]);
461
-
462
- const start = useCallback(
463
- async (stepKey?: string) => {
464
- const ordered = getOrderedSteps();
465
-
466
- let targetStep = stepKey;
467
-
468
- // If no specific step and autoResume is enabled, try to restore from storage
469
- if (!targetStep && isPersistenceEnabled && storageAdapter && autoResume) {
470
- try {
471
- const savedProgress = await loadTourProgress(storageAdapter, tourId);
472
- if (savedProgress) {
473
- // Check if progress is expired
474
- if (maxAge && Date.now() - savedProgress.timestamp > maxAge) {
475
- await clearTourProgress(storageAdapter, tourId);
476
- setHasSavedProgress(false);
477
- } else if (ordered.includes(savedProgress.currentStepKey)) {
478
- // Verify the saved step still exists in order
479
- targetStep = savedProgress.currentStepKey;
480
- }
481
- }
482
- } catch {
483
- // Ignore load errors, start from beginning
484
- }
485
- }
486
-
487
- const firstStep = targetStep || ordered[0];
488
- if (firstStep) {
489
- // Check if the target step is registered (mounted)
490
- if (steps[firstStep]) {
491
- setCurrentStep(firstStep);
492
- setTimeout(() => animateToStep(firstStep), 0);
493
- } else {
494
- // Step not mounted yet (on a different screen) - set as pending
495
- pendingStepRef.current = firstStep;
496
- // Don't set currentStep or opacity - wait for TourZone to mount
497
- }
498
- }
499
- },
500
- [
501
- getOrderedSteps,
502
- animateToStep,
503
- steps,
504
- isPersistenceEnabled,
505
- storageAdapter,
506
- autoResume,
507
- tourId,
508
- maxAge,
509
- ]
510
- );
511
-
512
- const stop = useCallback(() => {
513
- setCurrentStep(null);
514
- opacity.value = withTiming(0, { duration: 300 });
515
- // Note: We do NOT clear progress on stop - only on complete or explicit clearProgress
516
- }, [opacity]);
517
-
518
- // Clear progress helper
519
- const clearProgress = useCallback(async () => {
520
- if (!isPersistenceEnabled || !storageAdapter) return;
521
- try {
522
- await clearTourProgress(storageAdapter, tourId);
523
- setHasSavedProgress(false);
524
- } catch {
525
- // Silently ignore clear errors
526
- }
527
- }, [isPersistenceEnabled, storageAdapter, tourId]);
528
-
529
- const next = useCallback(() => {
530
- if (!currentStep) return;
531
-
532
- // Block navigation if current step has completed === false
533
- const currentStepData = steps[currentStep];
534
- if (currentStepData?.completed === false) {
535
- return;
536
- }
537
-
538
- const ordered = getOrderedSteps();
539
- const currentIndex = ordered.indexOf(currentStep);
540
- if (currentIndex < ordered.length - 1) {
541
- const nextStepKey = ordered[currentIndex + 1];
542
- if (nextStepKey) {
543
- // Check if the next step is registered (mounted)
544
- if (steps[nextStepKey]) {
545
- setCurrentStep(nextStepKey);
546
- // Don't call animateToStep here - it uses cached measurements that may be stale
547
- // after scroll. The useFrameCallback in TourZone will handle position tracking
548
- // using measure() with correct screen coordinates (pageX/pageY).
549
- // Just ensure the overlay is visible.
550
- opacity.value = withTiming(backdropOpacity, { duration: 300 });
551
- } else {
552
- // Step not mounted yet (on a different screen) - set as pending
553
- pendingStepRef.current = nextStepKey;
554
- setCurrentStep(null);
555
- opacity.value = withTiming(0, { duration: 300 });
556
- // Persist pending step so it can be resumed
557
- if (isPersistenceEnabled && storageAdapter) {
558
- const stepIndex = ordered.indexOf(nextStepKey);
559
- saveTourProgress(
560
- storageAdapter,
561
- tourId,
562
- nextStepKey,
563
- stepIndex
564
- ).catch(() => {});
565
- }
566
- }
567
- } else {
568
- stop();
569
- }
570
- } else {
571
- // End of tour - clear progress if configured
572
- if (isPersistenceEnabled && clearOnComplete && storageAdapter) {
573
- clearTourProgress(storageAdapter, tourId)
574
- .then(() => setHasSavedProgress(false))
575
- .catch(() => {});
576
- }
577
- stop();
578
- }
579
- }, [
580
- currentStep,
581
- steps,
582
- getOrderedSteps,
583
- stop,
584
- opacity,
585
- backdropOpacity,
586
- isPersistenceEnabled,
587
- clearOnComplete,
588
- storageAdapter,
589
- tourId,
590
- ]);
591
-
592
- const prev = useCallback(() => {
593
- if (!currentStep) return;
594
- const ordered = getOrderedSteps();
595
- const currentIndex = ordered.indexOf(currentStep);
596
- if (currentIndex > 0) {
597
- const prevStepKey = ordered[currentIndex - 1];
598
- if (prevStepKey) {
599
- // Check if the previous step is registered (mounted)
600
- if (steps[prevStepKey]) {
601
- setCurrentStep(prevStepKey);
602
- // Don't call animateToStep - let useFrameCallback handle position tracking
603
- opacity.value = withTiming(backdropOpacity, { duration: 300 });
604
- } else {
605
- // Step not mounted (on a different screen) - set as pending
606
- pendingStepRef.current = prevStepKey;
607
- setCurrentStep(null);
608
- opacity.value = withTiming(0, { duration: 300 });
609
- }
610
- }
611
- }
612
- }, [currentStep, steps, getOrderedSteps, opacity, backdropOpacity]);
613
-
614
- const scrollViewRef = useAnimatedRef<any>();
615
-
616
- const setScrollViewRef = useCallback((_ref: any) => {
617
- // If user passes a ref, we might want to sync it?
618
- // Or we just provide this function for them to give us the ref.
619
- // With useAnimatedRef, we can assign it if it's a function or object?
620
- // Actually, safest is to let them assign our ref to their component.
621
- // But they might have their own ref.
622
- // Let's assume they call this with their ref.
623
- // BUT useAnimatedRef cannot easily accept an external ref object to "become".
624
- // Pattern: They should use the ref we give them, OR we wrap their component?
625
- // Simpler: We just expose 'scrollViewRef' from context, and they attach it.
626
- // So 'setScrollViewRef' might be redundant if we just say "here is the ref, use it".
627
- // But if they have their own, they can't usage two refs easily without merging.
628
- // Let's stick to exposing `scrollViewRef` from context that they MUST use.
629
- // But wait, the interface says `setScrollViewRef`.
630
- // Let's keep `setScrollViewRef` as a no-op or a way to manually set it if needed (not RecAnimated friendly).
631
- // Actually, let's just expose `scrollViewRef` and `registerScrollView` which essentially does nothing if we expect them to use the ref object.
632
- // Let's make `setScrollViewRef` actually do something if possible, or just document "Use exposed scrollViewRef".
633
- // For now, let's just return the `scrollViewRef` we created.
634
- }, []);
635
-
636
- // Expose ordered step keys for tooltip and external use
637
- const orderedStepKeys = useMemo(() => getOrderedSteps(), [getOrderedSteps]);
638
-
639
- const value = useMemo<InternalTourContextType>(
640
- () => ({
641
- start,
642
- stop,
643
- next,
644
- prev,
645
- registerStep,
646
- unregisterStep,
647
- updateStepLayout,
648
- currentStep,
649
- targetX,
650
- targetY,
651
- targetWidth,
652
- targetHeight,
653
- targetRadius,
654
- opacity,
655
- zoneBorderWidth,
656
- steps,
657
- config,
658
- containerRef,
659
- scrollViewRef,
660
- setScrollViewRef,
661
- currentZoneStyle,
662
- clearProgress,
663
- hasSavedProgress,
664
- orderedStepKeys,
665
- }),
666
- [
667
- start,
668
- stop,
669
- next,
670
- prev,
671
- registerStep,
672
- unregisterStep,
673
- updateStepLayout,
674
- currentStep,
675
- targetX,
676
- targetY,
677
- targetWidth,
678
- targetHeight,
679
- targetRadius,
680
- opacity,
681
- zoneBorderWidth,
682
- steps,
683
- config,
684
- containerRef,
685
- scrollViewRef,
686
- setScrollViewRef,
687
- currentZoneStyle,
688
- clearProgress,
689
- hasSavedProgress,
690
- orderedStepKeys,
691
- ]
692
- );
693
-
694
- return (
695
- <TourContext.Provider value={value}>
696
- <AnimatedView
697
- ref={containerRef}
698
- style={styles.container}
699
- collapsable={false}
700
- >
701
- {children}
702
- <TourOverlay />
703
- <TourTooltip />
704
- </AnimatedView>
705
- </TourContext.Provider>
706
- );
707
- };
708
-
709
- const styles = StyleSheet.create({
710
- container: {
711
- flex: 1,
712
- },
713
- });