rn-smart-tour 1.0.4 → 1.0.5

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 CHANGED
@@ -112,6 +112,14 @@ When `autoStart: true` is enabled, the overlay waits **300ms** after registratio
112
112
  | `autoStart` | `boolean` | Trigger as soon as the first target mounts. |
113
113
  | `steps` | `TourStep[]` | Sequence of highlight steps. |
114
114
 
115
+ ### DapTarget
116
+ | Property | Type | Description |
117
+ |:---|:---|:---|
118
+ | `name` | `string` | Unique identifier that matches a `targetId` in a tour step. |
119
+ | `children` | `ReactElement` | The UI element to wrap and highlight. |
120
+ | `asChild` | `boolean` | **New!** If true, clones the child to avoid an extra View wrapper. (Crucial for flex/percentage layouts). |
121
+ | `...props` | `ViewProps` | All standard React Native `View` props are forwarded. |
122
+
115
123
  ### TourStep
116
124
  | Property | Type | Default | Description |
117
125
  |:---|:---|:---|:---|
@@ -126,9 +126,9 @@ const DapOverlay = () => {
126
126
  </react_native_1.Text>)}
127
127
 
128
128
  <react_native_1.View style={styles.actions}>
129
- <react_native_1.TouchableOpacity onPress={() => stopTour()} style={styles.actionBtn}>
130
- <react_native_1.Text style={styles.actionText}>Skip</react_native_1.Text>
131
- </react_native_1.TouchableOpacity>
129
+ {!isLastStep && (<react_native_1.TouchableOpacity onPress={() => stopTour()} style={styles.actionBtn}>
130
+ <react_native_1.Text style={styles.actionText}>Skip</react_native_1.Text>
131
+ </react_native_1.TouchableOpacity>)}
132
132
 
133
133
  {!isFirstStep && (<react_native_1.TouchableOpacity onPress={prevStep} style={styles.actionBtn}>
134
134
  <react_native_1.Text style={styles.actionText}>Back</react_native_1.Text>
@@ -50,9 +50,15 @@ const DapProvider = ({ children, tours, storageAdapter }) => {
50
50
  const [currentStepIndex, setCurrentStepIndex] = (0, react_1.useState)(0);
51
51
  const [seenTours, setSeenTours] = (0, react_1.useState)({});
52
52
  const [isStorageLoaded, setIsStorageLoaded] = (0, react_1.useState)(!storageAdapter);
53
- // Ref to avoid stale closures — always holds the latest activeTourId.
53
+ // Ref to always hold the latest activeTourId, avoiding stale closures.
54
54
  const activeTourIdRef = (0, react_1.useRef)(activeTourId);
55
55
  activeTourIdRef.current = activeTourId;
56
+ /**
57
+ * Ref to track the ID of a tour that was just manually stopped/skipped.
58
+ * This prevents the auto-start engine from immediately restarting the same tour
59
+ * before the 'seenTours' state update has been processed.
60
+ */
61
+ const tourIdJustStoppedRef = (0, react_1.useRef)(null);
56
62
  // Load seen tours on mount if a storage adapter is provided
57
63
  (0, react_1.useEffect)(() => {
58
64
  const loadStorage = async () => {
@@ -102,11 +108,14 @@ const DapProvider = ({ children, tours, storageAdapter }) => {
102
108
  console.warn(`[rn-dap] Tour with id ${tourId} not found.`);
103
109
  }
104
110
  }, [tours, seenTours]);
105
- // Uses activeTourIdRef to avoid stale closures and prevent cascading re-renders.
106
111
  const stopTour = (0, react_1.useCallback)((markAsSeen = true) => {
107
112
  const currentTourId = activeTourIdRef.current;
108
- if (currentTourId && markAsSeen) {
109
- saveSeenTour(currentTourId);
113
+ if (currentTourId) {
114
+ if (markAsSeen) {
115
+ saveSeenTour(currentTourId);
116
+ }
117
+ // Block this specific tour from auto-restarting until coordinates or seenTours settle.
118
+ tourIdJustStoppedRef.current = currentTourId;
110
119
  }
111
120
  setActiveTourId(null);
112
121
  setCurrentStepIndex(0);
@@ -142,7 +151,9 @@ const DapProvider = ({ children, tours, storageAdapter }) => {
142
151
  autoStartTimerRef.current = setTimeout(() => {
143
152
  for (const tourId of Object.keys(tours)) {
144
153
  const tour = tours[tourId];
145
- if (tour.autoStart && !seenTours[tourId] && tour.steps.length > 0) {
154
+ // Skip if this tour was just manually stopped and haven't confirmed 'seen' yet.
155
+ const wasJustStopped = tourIdJustStoppedRef.current === tourId;
156
+ if (tour.autoStart && !seenTours[tourId] && !wasJustStopped && tour.steps.length > 0) {
146
157
  const firstTargetId = tour.steps[0].targetId;
147
158
  // If the first target of an unread, auto-starting tour is mounted
148
159
  if (targets[firstTargetId]) {
@@ -151,6 +162,10 @@ const DapProvider = ({ children, tours, storageAdapter }) => {
151
162
  }
152
163
  }
153
164
  }
165
+ // Cleanup the "just stopped" ref once the seenTours state finally reflects the closure.
166
+ if (tourIdJustStoppedRef.current && seenTours[tourIdJustStoppedRef.current]) {
167
+ tourIdJustStoppedRef.current = null;
168
+ }
154
169
  }, AUTO_START_DEBOUNCE_MS);
155
170
  return () => {
156
171
  if (autoStartTimerRef.current) {
@@ -3,6 +3,12 @@ import { ViewProps } from 'react-native';
3
3
  interface DapTargetProps extends ViewProps {
4
4
  name: string;
5
5
  children: ReactElement;
6
+ /**
7
+ * If true, DapTarget will not wrap your child in a View. Instead, it will
8
+ * clone the child and inject the ref/onLayout logic directly.
9
+ * Useful for maintaining flex layouts or percentage widths (e.g. 33%).
10
+ */
11
+ asChild?: boolean;
6
12
  }
7
13
  export declare const DapTarget: React.FC<DapTargetProps>;
8
14
  export {};
package/dist/DapTarget.js CHANGED
@@ -45,7 +45,22 @@ const DapContext_1 = require("./DapContext");
45
45
  const MEASUREMENT_DELAYS = [100, 500, 1000];
46
46
  /** Threshold in points — ignore sub-pixel drift to avoid unnecessary re-registers. */
47
47
  const POSITION_THRESHOLD = 1;
48
- const DapTarget = ({ name, children, ...props }) => {
48
+ /**
49
+ * Utility to merge multiple refs into a single callback ref.
50
+ */
51
+ function setRefs(...refs) {
52
+ return (value) => {
53
+ refs.forEach((ref) => {
54
+ if (typeof ref === 'function') {
55
+ ref(value);
56
+ }
57
+ else if (ref && typeof ref === 'object') {
58
+ ref.current = value;
59
+ }
60
+ });
61
+ };
62
+ }
63
+ const DapTarget = ({ name, children, asChild, ...props }) => {
49
64
  const viewRef = (0, react_1.useRef)(null);
50
65
  const context = (0, react_1.useContext)(DapContext_1.DapContext);
51
66
  /** Holds all scheduled timer IDs so we can cancel them on re-layout or unmount. */
@@ -116,7 +131,26 @@ const DapTarget = ({ name, children, ...props }) => {
116
131
  unregisterTarget?.(name);
117
132
  };
118
133
  }, [name, unregisterTarget, clearAllTimers]);
119
- // collapsable={false} is vital for Android, otherwise it gets optimized away and measure fails
134
+ // If asChild is enabled, clone the child and inject measurement logic.
135
+ if (asChild && (0, react_1.isValidElement)(children)) {
136
+ try {
137
+ const child = react_1.Children.only(children);
138
+ return (0, react_1.cloneElement)(child, {
139
+ ...props,
140
+ ref: setRefs(viewRef, child.ref),
141
+ onLayout: (e) => {
142
+ handleLayout(e);
143
+ child.props.onLayout?.(e);
144
+ },
145
+ // collapsable={false} is vital for Android measurement
146
+ collapsable: false,
147
+ });
148
+ }
149
+ catch (e) {
150
+ console.warn('[rn-dap] asChild requires a single React element as a child.');
151
+ }
152
+ }
153
+ // Fallback to standard View wrapper
120
154
  return (<react_native_1.View ref={viewRef} onLayout={handleLayout} collapsable={false} {...props}>
121
155
  {children}
122
156
  </react_native_1.View>);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rn-smart-tour",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Enterprise-grade Digital Adoption Platform (DAP) package for React Native. Provides guided walkthroughs, tooltips, and app tours.",
5
5
  "author": "Vishwas Gaur",
6
6
  "license": "MIT",
@@ -9,14 +9,6 @@
9
9
  "files": [
10
10
  "dist"
11
11
  ],
12
- "repository": {
13
- "type": "git",
14
- "url": "https://github.com/Vishwasgaur0819/rn-smart-tour"
15
- },
16
- "homepage": "https://github.com/Vishwasgaur0819/rn-smart-tour#readme",
17
- "bugs": {
18
- "url": "https://github.com/Vishwasgaur0819/rn-smart-tour/issues"
19
- },
20
12
  "keywords": [
21
13
  "react-native",
22
14
  "tour",