noboarding 1.0.2-beta → 1.0.6-beta
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/ANIMATIONS.md +446 -0
- package/README.md +356 -323
- package/lib/OnboardingFlow.js +106 -14
- package/lib/analytics.d.ts +6 -0
- package/lib/analytics.js +18 -1
- package/lib/animationUtils.d.ts +19 -0
- package/lib/animationUtils.js +252 -0
- package/lib/components/ElementRenderer.d.ts +5 -0
- package/lib/components/ElementRenderer.js +196 -36
- package/lib/types.d.ts +47 -0
- package/package.json +1 -1
- package/src/OnboardingFlow.tsx +116 -14
- package/src/analytics.ts +23 -1
- package/src/animationUtils.ts +292 -0
- package/src/components/ElementRenderer.tsx +233 -18
- package/src/types.ts +52 -0
package/lib/OnboardingFlow.js
CHANGED
|
@@ -32,18 +32,39 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
35
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
39
|
exports.OnboardingFlow = void 0;
|
|
37
40
|
const react_1 = __importStar(require("react"));
|
|
38
41
|
const react_native_1 = require("react-native");
|
|
42
|
+
const async_storage_1 = __importDefault(require("@react-native-async-storage/async-storage"));
|
|
39
43
|
const api_1 = require("./api");
|
|
40
44
|
const analytics_1 = require("./analytics");
|
|
41
45
|
const variableUtils_1 = require("./variableUtils");
|
|
42
46
|
const ElementRenderer_1 = require("./components/ElementRenderer");
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
const USER_ID_STORAGE_KEY = '@noboarding_user_id';
|
|
48
|
+
// Get or create persistent user ID
|
|
49
|
+
const getPersistentUserId = async () => {
|
|
50
|
+
try {
|
|
51
|
+
// Try to get existing user ID from storage
|
|
52
|
+
const existingUserId = await async_storage_1.default.getItem(USER_ID_STORAGE_KEY);
|
|
53
|
+
if (existingUserId) {
|
|
54
|
+
return existingUserId;
|
|
55
|
+
}
|
|
56
|
+
// Generate new user ID if none exists
|
|
57
|
+
const newUserId = `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
58
|
+
await async_storage_1.default.setItem(USER_ID_STORAGE_KEY, newUserId);
|
|
59
|
+
return newUserId;
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
console.error('Failed to get/set user ID from storage:', error);
|
|
63
|
+
// Fallback to generating a non-persistent ID
|
|
64
|
+
return `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
65
|
+
}
|
|
46
66
|
};
|
|
67
|
+
// Generate session ID (always new per session)
|
|
47
68
|
const generateSessionId = () => {
|
|
48
69
|
return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
49
70
|
};
|
|
@@ -75,19 +96,28 @@ const OnboardingFlow = ({ apiKey, testKey, productionKey, onComplete, onSkip, ba
|
|
|
75
96
|
const [loading, setLoading] = (0, react_1.useState)(true);
|
|
76
97
|
const [error, setError] = (0, react_1.useState)(null);
|
|
77
98
|
const [screens, setScreens] = (0, react_1.useState)([]);
|
|
99
|
+
const [assets, setAssets] = (0, react_1.useState)([]);
|
|
78
100
|
const [currentIndex, setCurrentIndex] = (0, react_1.useState)(0);
|
|
79
101
|
const [collectedData, setCollectedData] = (0, react_1.useState)({});
|
|
80
102
|
const [variables, setVariables] = (0, react_1.useState)(initialVariables || {});
|
|
81
103
|
const apiRef = (0, react_1.useRef)(null);
|
|
82
104
|
const analyticsRef = (0, react_1.useRef)(null);
|
|
83
|
-
const userIdRef = (0, react_1.useRef)(
|
|
105
|
+
const userIdRef = (0, react_1.useRef)(null);
|
|
84
106
|
const sessionIdRef = (0, react_1.useRef)(generateSessionId());
|
|
107
|
+
const flowIdRef = (0, react_1.useRef)(null);
|
|
85
108
|
(0, react_1.useEffect)(() => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
109
|
+
const initialize = async () => {
|
|
110
|
+
// Get or create persistent user ID
|
|
111
|
+
const userId = await getPersistentUserId();
|
|
112
|
+
userIdRef.current = userId;
|
|
113
|
+
// Notify parent of the user ID
|
|
114
|
+
if (onUserIdGenerated) {
|
|
115
|
+
onUserIdGenerated(userId);
|
|
116
|
+
}
|
|
117
|
+
// Initialize the flow
|
|
118
|
+
await initializeFlow();
|
|
119
|
+
};
|
|
120
|
+
initialize();
|
|
91
121
|
return () => {
|
|
92
122
|
// Cleanup: flush remaining analytics
|
|
93
123
|
if (analyticsRef.current) {
|
|
@@ -95,26 +125,81 @@ const OnboardingFlow = ({ apiKey, testKey, productionKey, onComplete, onSkip, ba
|
|
|
95
125
|
}
|
|
96
126
|
};
|
|
97
127
|
}, []);
|
|
128
|
+
// Track screen views whenever user navigates to a different screen
|
|
129
|
+
(0, react_1.useEffect)(() => {
|
|
130
|
+
if (screens.length > 0 && analyticsRef.current && currentIndex > 0) {
|
|
131
|
+
const currentScreen = screens[currentIndex];
|
|
132
|
+
analyticsRef.current.track('screen_viewed', {
|
|
133
|
+
flow_id: flowIdRef.current,
|
|
134
|
+
screen_id: currentScreen.id,
|
|
135
|
+
screen_index: currentIndex,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}, [currentIndex, screens]);
|
|
98
139
|
const initializeFlow = async () => {
|
|
140
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
99
141
|
try {
|
|
100
142
|
setLoading(true);
|
|
101
143
|
setError(null);
|
|
144
|
+
// Ensure user ID is set
|
|
145
|
+
if (!userIdRef.current) {
|
|
146
|
+
throw new Error('User ID not initialized');
|
|
147
|
+
}
|
|
102
148
|
// Initialize API client with detected API key
|
|
103
149
|
const api = new api_1.API(activeApiKey, baseUrl);
|
|
104
150
|
apiRef.current = api;
|
|
105
151
|
// Initialize analytics
|
|
106
152
|
const analytics = new analytics_1.AnalyticsManager(api, userIdRef.current, sessionIdRef.current);
|
|
107
153
|
analyticsRef.current = analytics;
|
|
108
|
-
// Track onboarding start
|
|
109
|
-
analytics.track('onboarding_started');
|
|
110
154
|
// Fetch configuration
|
|
111
155
|
const configResponse = await api.getConfig();
|
|
156
|
+
// Store flow_id for analytics
|
|
157
|
+
flowIdRef.current = configResponse.config_id;
|
|
158
|
+
// Store assets from config
|
|
159
|
+
if (configResponse.config.assets) {
|
|
160
|
+
setAssets(configResponse.config.assets);
|
|
161
|
+
}
|
|
162
|
+
// Handle A/B test experiment assignment
|
|
163
|
+
let screensToUse = configResponse.config.screens;
|
|
164
|
+
if (configResponse.experiments && configResponse.experiments.length > 0) {
|
|
165
|
+
// Assign user to the first active experiment
|
|
166
|
+
const experiment = configResponse.experiments[0];
|
|
167
|
+
try {
|
|
168
|
+
const assignment = await api.assignVariant(experiment.id, userIdRef.current);
|
|
169
|
+
console.log('🧪 A/B Test Assignment:', {
|
|
170
|
+
experiment_id: experiment.id,
|
|
171
|
+
experiment_name: experiment.name,
|
|
172
|
+
variant_id: assignment.variant_id,
|
|
173
|
+
has_variant_screens: ((_b = (_a = assignment.variant_config) === null || _a === void 0 ? void 0 : _a.screens) === null || _b === void 0 ? void 0 : _b.length) > 0,
|
|
174
|
+
variant_screen_count: ((_d = (_c = assignment.variant_config) === null || _c === void 0 ? void 0 : _c.screens) === null || _d === void 0 ? void 0 : _d.length) || 0,
|
|
175
|
+
cached: assignment.cached,
|
|
176
|
+
});
|
|
177
|
+
// Set experiment context so all events get tagged
|
|
178
|
+
analytics.setExperimentContext(experiment.id, assignment.variant_id);
|
|
179
|
+
// Use variant screens if available
|
|
180
|
+
if (((_f = (_e = assignment.variant_config) === null || _e === void 0 ? void 0 : _e.screens) === null || _f === void 0 ? void 0 : _f.length) > 0) {
|
|
181
|
+
screensToUse = assignment.variant_config.screens;
|
|
182
|
+
console.log('📱 Using variant screens:', assignment.variant_config.screens.length, 'screens');
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
console.log('📱 Using base flow screens (variant has no screens defined)');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
console.warn('Failed to assign experiment variant, using default flow:', err);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
112
192
|
// Backward compatibility: normalize legacy 'custom_screen' (with elements) to 'noboard_screen'
|
|
113
|
-
const normalizedScreens =
|
|
193
|
+
const normalizedScreens = screensToUse
|
|
114
194
|
.map(s => (Object.assign(Object.assign({}, s), { type: (s.type === 'custom_screen' && s.elements) ? 'noboard_screen' : s.type })))
|
|
115
195
|
// Filter out hidden screens (dashboard show/hide feature)
|
|
116
196
|
.filter(s => !s.hidden);
|
|
117
197
|
setScreens(normalizedScreens);
|
|
198
|
+
// Track onboarding start with first screen
|
|
199
|
+
analytics.track('onboarding_started', {
|
|
200
|
+
flow_id: flowIdRef.current,
|
|
201
|
+
screen_id: (_g = normalizedScreens[0]) === null || _g === void 0 ? void 0 : _g.id,
|
|
202
|
+
});
|
|
118
203
|
setLoading(false);
|
|
119
204
|
}
|
|
120
205
|
catch (err) {
|
|
@@ -155,18 +240,25 @@ const OnboardingFlow = ({ apiKey, testKey, productionKey, onComplete, onSkip, ba
|
|
|
155
240
|
setVariables(prev => (Object.assign(Object.assign({}, prev), { [name]: value })));
|
|
156
241
|
}, []);
|
|
157
242
|
const handleComplete = async (lastScreenData) => {
|
|
243
|
+
var _a;
|
|
158
244
|
const finalData = Object.assign(Object.assign(Object.assign({}, collectedData), (lastScreenData || {})), { _variables: variables });
|
|
159
245
|
// Track completion
|
|
160
246
|
if (analyticsRef.current) {
|
|
161
|
-
analyticsRef.current.track('onboarding_completed'
|
|
247
|
+
analyticsRef.current.track('onboarding_completed', {
|
|
248
|
+
flow_id: flowIdRef.current,
|
|
249
|
+
screen_id: (_a = screens[currentIndex]) === null || _a === void 0 ? void 0 : _a.id,
|
|
250
|
+
});
|
|
162
251
|
await analyticsRef.current.flush();
|
|
163
252
|
}
|
|
164
253
|
// Call developer's completion callback with collected data
|
|
165
254
|
onComplete(finalData);
|
|
166
255
|
};
|
|
167
256
|
const handleSkipAll = async () => {
|
|
257
|
+
var _a;
|
|
168
258
|
if (analyticsRef.current) {
|
|
169
259
|
analyticsRef.current.track('onboarding_abandoned', {
|
|
260
|
+
flow_id: flowIdRef.current,
|
|
261
|
+
screen_id: (_a = screens[currentIndex]) === null || _a === void 0 ? void 0 : _a.id,
|
|
170
262
|
current_screen_index: currentIndex,
|
|
171
263
|
});
|
|
172
264
|
await analyticsRef.current.flush();
|
|
@@ -227,7 +319,7 @@ const OnboardingFlow = ({ apiKey, testKey, productionKey, onComplete, onSkip, ba
|
|
|
227
319
|
}
|
|
228
320
|
};
|
|
229
321
|
return (<react_native_1.View style={styles.container}>
|
|
230
|
-
<ElementRenderer_1.ElementRenderer elements={currentScreen.elements} analytics={analyticsRef.current} screenId={currentScreen.id} onNavigate={handleElementNavigate} onDismiss={onSkip ? handleSkipAll : handleNext} variables={allVariables} onSetVariable={handleSetVariable}/>
|
|
322
|
+
<ElementRenderer_1.ElementRenderer elements={currentScreen.elements} analytics={analyticsRef.current} screenId={currentScreen.id} onNavigate={handleElementNavigate} onDismiss={onSkip ? handleSkipAll : handleNext} variables={allVariables} onSetVariable={handleSetVariable} assets={assets}/>
|
|
231
323
|
</react_native_1.View>);
|
|
232
324
|
}
|
|
233
325
|
// Handle custom_screen type — developer-registered React Native components
|
package/lib/analytics.d.ts
CHANGED
|
@@ -5,7 +5,13 @@ export declare class AnalyticsManager {
|
|
|
5
5
|
private userId;
|
|
6
6
|
private sessionId;
|
|
7
7
|
private flushTimer;
|
|
8
|
+
private experimentId;
|
|
9
|
+
private variantId;
|
|
8
10
|
constructor(api: API, userId: string, sessionId: string);
|
|
11
|
+
/**
|
|
12
|
+
* Set experiment context — all subsequent events will be tagged
|
|
13
|
+
*/
|
|
14
|
+
setExperimentContext(experimentId: string, variantId: string): void;
|
|
9
15
|
/**
|
|
10
16
|
* Track an analytics event
|
|
11
17
|
*/
|
package/lib/analytics.js
CHANGED
|
@@ -7,22 +7,39 @@ class AnalyticsManager {
|
|
|
7
7
|
constructor(api, userId, sessionId) {
|
|
8
8
|
this.events = [];
|
|
9
9
|
this.flushTimer = null;
|
|
10
|
+
this.experimentId = null;
|
|
11
|
+
this.variantId = null;
|
|
10
12
|
this.api = api;
|
|
11
13
|
this.userId = userId;
|
|
12
14
|
this.sessionId = sessionId;
|
|
13
15
|
// Start auto-flush timer
|
|
14
16
|
this.startFlushTimer();
|
|
15
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Set experiment context — all subsequent events will be tagged
|
|
20
|
+
*/
|
|
21
|
+
setExperimentContext(experimentId, variantId) {
|
|
22
|
+
this.experimentId = experimentId;
|
|
23
|
+
this.variantId = variantId;
|
|
24
|
+
}
|
|
16
25
|
/**
|
|
17
26
|
* Track an analytics event
|
|
18
27
|
*/
|
|
19
28
|
track(eventName, properties) {
|
|
29
|
+
const mergedProperties = Object.assign({}, (properties || {}));
|
|
30
|
+
// Auto-inject experiment context if set
|
|
31
|
+
if (this.experimentId) {
|
|
32
|
+
mergedProperties.experiment_id = this.experimentId;
|
|
33
|
+
}
|
|
34
|
+
if (this.variantId) {
|
|
35
|
+
mergedProperties.variant_id = this.variantId;
|
|
36
|
+
}
|
|
20
37
|
const event = {
|
|
21
38
|
event: eventName,
|
|
22
39
|
user_id: this.userId,
|
|
23
40
|
session_id: this.sessionId,
|
|
24
41
|
timestamp: Date.now(),
|
|
25
|
-
properties:
|
|
42
|
+
properties: mergedProperties,
|
|
26
43
|
};
|
|
27
44
|
this.events.push(event);
|
|
28
45
|
// Auto-flush if batch size reached
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Animated } from 'react-native';
|
|
2
|
+
import type { EntranceAnimation, InteractiveAnimation, HapticType } from './types';
|
|
3
|
+
export declare const triggerHaptic: (type?: HapticType) => void;
|
|
4
|
+
export declare const getEasing: (easingType?: string) => import("react-native").EasingFunction;
|
|
5
|
+
export interface EntranceAnimationValues {
|
|
6
|
+
opacity: Animated.Value;
|
|
7
|
+
translateY: Animated.Value;
|
|
8
|
+
translateX: Animated.Value;
|
|
9
|
+
scale: Animated.Value;
|
|
10
|
+
}
|
|
11
|
+
export declare const createEntranceAnimationValues: () => EntranceAnimationValues;
|
|
12
|
+
export declare const startEntranceAnimation: (config: EntranceAnimation, values: EntranceAnimationValues, delay?: number) => void;
|
|
13
|
+
export declare const startInteractiveAnimation: (config: InteractiveAnimation, animatedValue: Animated.Value) => void;
|
|
14
|
+
export interface TypewriterState {
|
|
15
|
+
displayedText: string;
|
|
16
|
+
currentIndex: number;
|
|
17
|
+
isComplete: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare const shouldTriggerHaptic: (index: number, frequency: string) => boolean;
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.shouldTriggerHaptic = exports.startInteractiveAnimation = exports.startEntranceAnimation = exports.createEntranceAnimationValues = exports.getEasing = exports.triggerHaptic = void 0;
|
|
4
|
+
const react_native_1 = require("react-native");
|
|
5
|
+
// Lazy load haptics to avoid errors if not installed
|
|
6
|
+
let Haptics = null;
|
|
7
|
+
try {
|
|
8
|
+
Haptics = require('expo-haptics');
|
|
9
|
+
}
|
|
10
|
+
catch (e) {
|
|
11
|
+
console.warn('expo-haptics not installed, haptic feedback will be disabled');
|
|
12
|
+
}
|
|
13
|
+
// ─── Haptic Feedback Helper ───
|
|
14
|
+
const triggerHaptic = (type = 'light') => {
|
|
15
|
+
if (!Haptics) {
|
|
16
|
+
// Silently skip if expo-haptics not available
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
switch (type) {
|
|
21
|
+
case 'light':
|
|
22
|
+
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
|
23
|
+
break;
|
|
24
|
+
case 'medium':
|
|
25
|
+
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
|
|
26
|
+
break;
|
|
27
|
+
case 'heavy':
|
|
28
|
+
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy);
|
|
29
|
+
break;
|
|
30
|
+
case 'success':
|
|
31
|
+
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
|
|
32
|
+
break;
|
|
33
|
+
case 'warning':
|
|
34
|
+
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning);
|
|
35
|
+
break;
|
|
36
|
+
case 'error':
|
|
37
|
+
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
console.warn('Haptic feedback failed:', error);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
exports.triggerHaptic = triggerHaptic;
|
|
46
|
+
// ─── Easing Function Mapper ───
|
|
47
|
+
const getEasing = (easingType) => {
|
|
48
|
+
switch (easingType) {
|
|
49
|
+
case 'linear':
|
|
50
|
+
return react_native_1.Easing.linear;
|
|
51
|
+
case 'ease-in':
|
|
52
|
+
return react_native_1.Easing.in(react_native_1.Easing.ease);
|
|
53
|
+
case 'ease-out':
|
|
54
|
+
return react_native_1.Easing.out(react_native_1.Easing.ease);
|
|
55
|
+
case 'ease-in-out':
|
|
56
|
+
return react_native_1.Easing.inOut(react_native_1.Easing.ease);
|
|
57
|
+
case 'spring':
|
|
58
|
+
return react_native_1.Easing.elastic(1);
|
|
59
|
+
default:
|
|
60
|
+
return react_native_1.Easing.inOut(react_native_1.Easing.ease);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
exports.getEasing = getEasing;
|
|
64
|
+
const createEntranceAnimationValues = () => ({
|
|
65
|
+
opacity: new react_native_1.Animated.Value(0),
|
|
66
|
+
translateY: new react_native_1.Animated.Value(0),
|
|
67
|
+
translateX: new react_native_1.Animated.Value(0),
|
|
68
|
+
scale: new react_native_1.Animated.Value(1),
|
|
69
|
+
});
|
|
70
|
+
exports.createEntranceAnimationValues = createEntranceAnimationValues;
|
|
71
|
+
const startEntranceAnimation = (config, values, delay = 0) => {
|
|
72
|
+
const duration = config.duration || 400;
|
|
73
|
+
const totalDelay = (config.delay || 0) + delay;
|
|
74
|
+
const easing = (0, exports.getEasing)(config.easing);
|
|
75
|
+
// Set initial values based on animation type
|
|
76
|
+
switch (config.type) {
|
|
77
|
+
case 'fadeIn':
|
|
78
|
+
values.opacity.setValue(0);
|
|
79
|
+
break;
|
|
80
|
+
case 'slideUp':
|
|
81
|
+
values.opacity.setValue(0);
|
|
82
|
+
values.translateY.setValue(30);
|
|
83
|
+
break;
|
|
84
|
+
case 'slideDown':
|
|
85
|
+
values.opacity.setValue(0);
|
|
86
|
+
values.translateY.setValue(-30);
|
|
87
|
+
break;
|
|
88
|
+
case 'slideLeft':
|
|
89
|
+
values.opacity.setValue(0);
|
|
90
|
+
values.translateX.setValue(30);
|
|
91
|
+
break;
|
|
92
|
+
case 'slideRight':
|
|
93
|
+
values.opacity.setValue(0);
|
|
94
|
+
values.translateX.setValue(-30);
|
|
95
|
+
break;
|
|
96
|
+
case 'scaleIn':
|
|
97
|
+
values.opacity.setValue(0);
|
|
98
|
+
values.scale.setValue(0.8);
|
|
99
|
+
break;
|
|
100
|
+
case 'none':
|
|
101
|
+
values.opacity.setValue(1);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
// Animate to final values
|
|
105
|
+
react_native_1.Animated.parallel([
|
|
106
|
+
react_native_1.Animated.timing(values.opacity, {
|
|
107
|
+
toValue: 1,
|
|
108
|
+
duration,
|
|
109
|
+
delay: totalDelay,
|
|
110
|
+
easing,
|
|
111
|
+
useNativeDriver: true,
|
|
112
|
+
}),
|
|
113
|
+
react_native_1.Animated.timing(values.translateY, {
|
|
114
|
+
toValue: 0,
|
|
115
|
+
duration,
|
|
116
|
+
delay: totalDelay,
|
|
117
|
+
easing,
|
|
118
|
+
useNativeDriver: true,
|
|
119
|
+
}),
|
|
120
|
+
react_native_1.Animated.timing(values.translateX, {
|
|
121
|
+
toValue: 0,
|
|
122
|
+
duration,
|
|
123
|
+
delay: totalDelay,
|
|
124
|
+
easing,
|
|
125
|
+
useNativeDriver: true,
|
|
126
|
+
}),
|
|
127
|
+
react_native_1.Animated.timing(values.scale, {
|
|
128
|
+
toValue: 1,
|
|
129
|
+
duration,
|
|
130
|
+
delay: totalDelay,
|
|
131
|
+
easing,
|
|
132
|
+
useNativeDriver: true,
|
|
133
|
+
}),
|
|
134
|
+
]).start();
|
|
135
|
+
};
|
|
136
|
+
exports.startEntranceAnimation = startEntranceAnimation;
|
|
137
|
+
// ─── Interactive Animations ───
|
|
138
|
+
const startInteractiveAnimation = (config, animatedValue) => {
|
|
139
|
+
const duration = config.duration || 200;
|
|
140
|
+
const intensity = config.intensity || (config.type === 'scale' ? 0.95 : 10);
|
|
141
|
+
// Trigger haptic if enabled
|
|
142
|
+
if (config.haptic && config.hapticType) {
|
|
143
|
+
(0, exports.triggerHaptic)(config.hapticType);
|
|
144
|
+
}
|
|
145
|
+
switch (config.type) {
|
|
146
|
+
case 'scale':
|
|
147
|
+
react_native_1.Animated.sequence([
|
|
148
|
+
react_native_1.Animated.timing(animatedValue, {
|
|
149
|
+
toValue: intensity,
|
|
150
|
+
duration: duration / 2,
|
|
151
|
+
easing: react_native_1.Easing.out(react_native_1.Easing.ease),
|
|
152
|
+
useNativeDriver: true,
|
|
153
|
+
}),
|
|
154
|
+
react_native_1.Animated.timing(animatedValue, {
|
|
155
|
+
toValue: 1,
|
|
156
|
+
duration: duration / 2,
|
|
157
|
+
easing: react_native_1.Easing.in(react_native_1.Easing.ease),
|
|
158
|
+
useNativeDriver: true,
|
|
159
|
+
}),
|
|
160
|
+
]).start();
|
|
161
|
+
break;
|
|
162
|
+
case 'pulse':
|
|
163
|
+
const pulseSequence = react_native_1.Animated.sequence([
|
|
164
|
+
react_native_1.Animated.timing(animatedValue, {
|
|
165
|
+
toValue: 1.05,
|
|
166
|
+
duration: duration,
|
|
167
|
+
easing: react_native_1.Easing.inOut(react_native_1.Easing.ease),
|
|
168
|
+
useNativeDriver: true,
|
|
169
|
+
}),
|
|
170
|
+
react_native_1.Animated.timing(animatedValue, {
|
|
171
|
+
toValue: 1,
|
|
172
|
+
duration: duration,
|
|
173
|
+
easing: react_native_1.Easing.inOut(react_native_1.Easing.ease),
|
|
174
|
+
useNativeDriver: true,
|
|
175
|
+
}),
|
|
176
|
+
]);
|
|
177
|
+
if (config.repeat) {
|
|
178
|
+
react_native_1.Animated.loop(pulseSequence).start();
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
pulseSequence.start();
|
|
182
|
+
}
|
|
183
|
+
break;
|
|
184
|
+
case 'shake':
|
|
185
|
+
react_native_1.Animated.sequence([
|
|
186
|
+
react_native_1.Animated.timing(animatedValue, {
|
|
187
|
+
toValue: intensity,
|
|
188
|
+
duration: duration / 8,
|
|
189
|
+
useNativeDriver: true,
|
|
190
|
+
}),
|
|
191
|
+
react_native_1.Animated.timing(animatedValue, {
|
|
192
|
+
toValue: -intensity,
|
|
193
|
+
duration: duration / 4,
|
|
194
|
+
useNativeDriver: true,
|
|
195
|
+
}),
|
|
196
|
+
react_native_1.Animated.timing(animatedValue, {
|
|
197
|
+
toValue: intensity,
|
|
198
|
+
duration: duration / 4,
|
|
199
|
+
useNativeDriver: true,
|
|
200
|
+
}),
|
|
201
|
+
react_native_1.Animated.timing(animatedValue, {
|
|
202
|
+
toValue: -intensity,
|
|
203
|
+
duration: duration / 4,
|
|
204
|
+
useNativeDriver: true,
|
|
205
|
+
}),
|
|
206
|
+
react_native_1.Animated.timing(animatedValue, {
|
|
207
|
+
toValue: 0,
|
|
208
|
+
duration: duration / 8,
|
|
209
|
+
useNativeDriver: true,
|
|
210
|
+
}),
|
|
211
|
+
]).start();
|
|
212
|
+
break;
|
|
213
|
+
case 'bounce':
|
|
214
|
+
react_native_1.Animated.sequence([
|
|
215
|
+
react_native_1.Animated.timing(animatedValue, {
|
|
216
|
+
toValue: -20,
|
|
217
|
+
duration: duration / 3,
|
|
218
|
+
easing: react_native_1.Easing.out(react_native_1.Easing.ease),
|
|
219
|
+
useNativeDriver: true,
|
|
220
|
+
}),
|
|
221
|
+
react_native_1.Animated.timing(animatedValue, {
|
|
222
|
+
toValue: 5,
|
|
223
|
+
duration: duration / 3,
|
|
224
|
+
easing: react_native_1.Easing.in(react_native_1.Easing.ease),
|
|
225
|
+
useNativeDriver: true,
|
|
226
|
+
}),
|
|
227
|
+
react_native_1.Animated.timing(animatedValue, {
|
|
228
|
+
toValue: 0,
|
|
229
|
+
duration: duration / 3,
|
|
230
|
+
easing: react_native_1.Easing.out(react_native_1.Easing.ease),
|
|
231
|
+
useNativeDriver: true,
|
|
232
|
+
}),
|
|
233
|
+
]).start();
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
exports.startInteractiveAnimation = startInteractiveAnimation;
|
|
238
|
+
const shouldTriggerHaptic = (index, frequency) => {
|
|
239
|
+
switch (frequency) {
|
|
240
|
+
case 'every':
|
|
241
|
+
return true;
|
|
242
|
+
case 'every-2':
|
|
243
|
+
return index % 2 === 0;
|
|
244
|
+
case 'every-3':
|
|
245
|
+
return index % 3 === 0;
|
|
246
|
+
case 'every-5':
|
|
247
|
+
return index % 5 === 0;
|
|
248
|
+
default:
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
exports.shouldTriggerHaptic = shouldTriggerHaptic;
|
|
@@ -8,6 +8,11 @@ interface ElementRendererProps {
|
|
|
8
8
|
onDismiss?: () => void;
|
|
9
9
|
variables?: Record<string, any>;
|
|
10
10
|
onSetVariable?: (name: string, value: any) => void;
|
|
11
|
+
assets?: Array<{
|
|
12
|
+
name: string;
|
|
13
|
+
type: string;
|
|
14
|
+
data: string;
|
|
15
|
+
}>;
|
|
11
16
|
}
|
|
12
17
|
export declare const ElementRenderer: React.FC<ElementRendererProps>;
|
|
13
18
|
export {};
|