noboarding 1.0.6-beta → 1.0.8

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.
@@ -74,7 +74,7 @@ const detectEnvironment = () => {
74
74
  return 'test';
75
75
  return 'production';
76
76
  };
77
- const OnboardingFlow = ({ apiKey, testKey, productionKey, onComplete, onSkip, baseUrl, initialVariables, customComponents, onUserIdGenerated, }) => {
77
+ const OnboardingFlow = ({ apiKey, testKey, productionKey, onComplete, onSkip, baseUrl, initialVariables, customComponents, onUserIdGenerated, loadingComponent, }) => {
78
78
  // Determine which API key to use
79
79
  const getApiKey = () => {
80
80
  // If dual keys provided, use environment detection
@@ -268,6 +268,9 @@ const OnboardingFlow = ({ apiKey, testKey, productionKey, onComplete, onSkip, ba
268
268
  }
269
269
  };
270
270
  if (loading) {
271
+ if (loadingComponent) {
272
+ return <>{loadingComponent}</>;
273
+ }
271
274
  return (<react_native_1.View style={styles.centerContainer}>
272
275
  <react_native_1.ActivityIndicator size="large" color="#007AFF"/>
273
276
  <react_native_1.Text style={styles.loadingText}>Loading...</react_native_1.Text>
@@ -179,7 +179,7 @@ const ElementRenderer = ({ elements, analytics, screenId, onNavigate, onDismiss,
179
179
  };
180
180
  exports.ElementRenderer = ElementRenderer;
181
181
  const RenderNode = ({ element, toggledIds, groupSelections, onAction, variables, onSetVariable, inputValues, setInputValues, staggerDelay = 0, resolveAssetUrl }) => {
182
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13;
182
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20;
183
183
  // Variable-based conditions — hide element if condition is not met
184
184
  if ((_a = element.conditions) === null || _a === void 0 ? void 0 : _a.show_if) {
185
185
  const shouldShow = (0, variableUtils_1.evaluateCondition)(element.conditions.show_if, variables);
@@ -447,13 +447,21 @@ const RenderNode = ({ element, toggledIds, groupSelections, onAction, variables,
447
447
  {element.props.imageDescription}
448
448
  </react_native_1.Text>)}
449
449
  </react_native_1.View>);
450
- case 'video':
451
- // Video placeholder actual implementation would use expo-av or react-native-video
452
- // Resolve asset URL if present (for future implementation)
453
- if ((_1 = element.props) === null || _1 === void 0 ? void 0 : _1.url) {
454
- const resolvedUrl = resolveAssetUrl(element.props.url);
455
- // TODO: Implement actual video player with resolvedUrl
450
+ case 'video': {
451
+ const videoUrl = ((_1 = element.props) === null || _1 === void 0 ? void 0 : _1.url) ? resolveAssetUrl(element.props.url) : null;
452
+ let VideoComponent = null;
453
+ try {
454
+ const expoAv = require('expo-av');
455
+ VideoComponent = expoAv.Video;
456
+ }
457
+ catch (_21) { }
458
+ if (VideoComponent && videoUrl) {
459
+ const resizeMode = ((_2 = element.props) === null || _2 === void 0 ? void 0 : _2.resizeMode) || 'contain';
460
+ return (<react_native_1.View style={[style, { backgroundColor: style.backgroundColor || '#1a1a1a', overflow: 'hidden' }]}>
461
+ <VideoComponent source={{ uri: videoUrl }} style={{ width: '100%', height: '100%' }} resizeMode={resizeMode} shouldPlay={((_3 = element.props) === null || _3 === void 0 ? void 0 : _3.autoPlay) !== false} isLooping={((_4 = element.props) === null || _4 === void 0 ? void 0 : _4.loop) !== false} isMuted={((_5 = element.props) === null || _5 === void 0 ? void 0 : _5.muted) !== false} useNativeControls={false}/>
462
+ </react_native_1.View>);
456
463
  }
464
+ // Fallback when expo-av is unavailable or no URL
457
465
  return (<react_native_1.View style={[
458
466
  style,
459
467
  {
@@ -463,17 +471,39 @@ const RenderNode = ({ element, toggledIds, groupSelections, onAction, variables,
463
471
  },
464
472
  ]}>
465
473
  <react_native_1.Text style={{ fontSize: 48 }}>🎬</react_native_1.Text>
466
- {((_2 = element.props) === null || _2 === void 0 ? void 0 : _2.videoDescription) && (<react_native_1.Text style={{ fontSize: 11, color: '#aaa', textAlign: 'center', padding: 8 }}>
474
+ {((_6 = element.props) === null || _6 === void 0 ? void 0 : _6.videoDescription) && (<react_native_1.Text style={{ fontSize: 11, color: '#aaa', textAlign: 'center', padding: 8 }}>
467
475
  {element.props.videoDescription}
468
476
  </react_native_1.Text>)}
469
477
  </react_native_1.View>);
470
- case 'lottie':
471
- // Lottie placeholder — actual implementation would use lottie-react-native
472
- // Resolve asset URL if present (for future implementation)
473
- if ((_3 = element.props) === null || _3 === void 0 ? void 0 : _3.url) {
474
- const resolvedUrl = resolveAssetUrl(element.props.url);
475
- // TODO: Implement actual Lottie player with resolvedUrl
478
+ }
479
+ case 'lottie': {
480
+ const lottieUrl = ((_7 = element.props) === null || _7 === void 0 ? void 0 : _7.url) ? resolveAssetUrl(element.props.url) : null;
481
+ let LottieView = null;
482
+ try {
483
+ LottieView = require('lottie-react-native').default;
476
484
  }
485
+ catch (_22) { }
486
+ if (LottieView && lottieUrl) {
487
+ // Decode base64 data URLs to get the Lottie JSON object
488
+ let lottieSource;
489
+ if (lottieUrl.startsWith('data:application/json;base64,')) {
490
+ try {
491
+ const base64Data = lottieUrl.replace('data:application/json;base64,', '');
492
+ const jsonString = atob(base64Data);
493
+ lottieSource = JSON.parse(jsonString);
494
+ }
495
+ catch (_23) {
496
+ lottieSource = { uri: lottieUrl };
497
+ }
498
+ }
499
+ else {
500
+ lottieSource = { uri: lottieUrl };
501
+ }
502
+ return (<react_native_1.View style={[style, { backgroundColor: style.backgroundColor || '#f8f8ff', overflow: 'hidden' }]}>
503
+ <LottieView source={lottieSource} style={{ width: '100%', height: '100%' }} autoPlay={((_8 = element.props) === null || _8 === void 0 ? void 0 : _8.autoPlay) !== false} loop={((_9 = element.props) === null || _9 === void 0 ? void 0 : _9.loop) !== false} speed={((_10 = element.props) === null || _10 === void 0 ? void 0 : _10.speed) || 1}/>
504
+ </react_native_1.View>);
505
+ }
506
+ // Fallback when lottie-react-native is unavailable or no URL
477
507
  return (<react_native_1.View style={[
478
508
  style,
479
509
  {
@@ -483,23 +513,24 @@ const RenderNode = ({ element, toggledIds, groupSelections, onAction, variables,
483
513
  },
484
514
  ]}>
485
515
  <react_native_1.Text style={{ fontSize: 48 }}>✨</react_native_1.Text>
486
- {((_4 = element.props) === null || _4 === void 0 ? void 0 : _4.animationDescription) && (<react_native_1.Text style={{ fontSize: 11, color: '#666', textAlign: 'center', padding: 8 }}>
516
+ {((_11 = element.props) === null || _11 === void 0 ? void 0 : _11.animationDescription) && (<react_native_1.Text style={{ fontSize: 11, color: '#666', textAlign: 'center', padding: 8 }}>
487
517
  {element.props.animationDescription}
488
518
  </react_native_1.Text>)}
489
519
  </react_native_1.View>);
520
+ }
490
521
  case 'input': {
491
522
  // Only apply default border if borderWidth is not explicitly defined (including 0)
492
523
  const inputStyle = style;
493
524
  const defaultInputStyle = {};
494
- if (((_5 = element.style) === null || _5 === void 0 ? void 0 : _5.borderWidth) === undefined && ((_6 = element.style) === null || _6 === void 0 ? void 0 : _6.borderColor) === undefined) {
525
+ if (((_12 = element.style) === null || _12 === void 0 ? void 0 : _12.borderWidth) === undefined && ((_13 = element.style) === null || _13 === void 0 ? void 0 : _13.borderColor) === undefined) {
495
526
  defaultInputStyle.borderWidth = 1;
496
527
  defaultInputStyle.borderColor = '#E5E5E5';
497
528
  }
498
529
  // Get the variable name - use props.variable if specified, otherwise use element.id
499
- const variableName = ((_7 = element.props) === null || _7 === void 0 ? void 0 : _7.variable) || element.id;
530
+ const variableName = ((_14 = element.props) === null || _14 === void 0 ? void 0 : _14.variable) || element.id;
500
531
  // Use local state value, or fall back to variables, or empty string
501
- const currentValue = (_9 = (_8 = inputValues[variableName]) !== null && _8 !== void 0 ? _8 : variables[variableName]) !== null && _9 !== void 0 ? _9 : '';
502
- return (<react_native_1.TextInput style={[defaultInputStyle, inputStyle]} placeholder={((_10 = element.props) === null || _10 === void 0 ? void 0 : _10.placeholder) || 'Enter text...'} keyboardType={getKeyboardType((_11 = element.props) === null || _11 === void 0 ? void 0 : _11.type)} secureTextEntry={((_12 = element.props) === null || _12 === void 0 ? void 0 : _12.type) === 'password'} autoCapitalize={((_13 = element.props) === null || _13 === void 0 ? void 0 : _13.type) === 'email' ? 'none' : 'sentences'} value={currentValue} onChangeText={(text) => {
532
+ const currentValue = (_16 = (_15 = inputValues[variableName]) !== null && _15 !== void 0 ? _15 : variables[variableName]) !== null && _16 !== void 0 ? _16 : '';
533
+ return (<react_native_1.TextInput style={[defaultInputStyle, inputStyle]} placeholder={((_17 = element.props) === null || _17 === void 0 ? void 0 : _17.placeholder) || 'Enter text...'} keyboardType={getKeyboardType((_18 = element.props) === null || _18 === void 0 ? void 0 : _18.type)} secureTextEntry={((_19 = element.props) === null || _19 === void 0 ? void 0 : _19.type) === 'password'} autoCapitalize={((_20 = element.props) === null || _20 === void 0 ? void 0 : _20.type) === 'email' ? 'none' : 'sentences'} value={currentValue} onChangeText={(text) => {
503
534
  // Save to local state only - don't trigger parent re-render
504
535
  setInputValues(prev => (Object.assign(Object.assign({}, prev), { [variableName]: text })));
505
536
  }}/>);
package/lib/types.d.ts CHANGED
@@ -233,4 +233,5 @@ export interface OnboardingFlowProps {
233
233
  initialVariables?: Record<string, any>;
234
234
  customComponents?: Record<string, React.ComponentType<CustomScreenProps>>;
235
235
  onUserIdGenerated?: (userId: string) => void;
236
+ loadingComponent?: React.ReactNode;
236
237
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noboarding",
3
- "version": "1.0.6-beta",
3
+ "version": "1.0.8",
4
4
  "description": "Expo SDK for remote onboarding flow management",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -31,7 +31,9 @@
31
31
  "dependencies": {
32
32
  "@react-native-async-storage/async-storage": "^1.19.0",
33
33
  "expo-linear-gradient": ">=12.0.0",
34
- "@expo/vector-icons": ">=14.0.0"
34
+ "@expo/vector-icons": ">=14.0.0",
35
+ "expo-av": ">=14.0.0",
36
+ "lottie-react-native": ">=6.0.0"
35
37
  },
36
38
  "devDependencies": {
37
39
  "@types/node": "^25.2.3",
@@ -51,6 +51,7 @@ export const OnboardingFlow: React.FC<OnboardingFlowProps> = ({
51
51
  initialVariables,
52
52
  customComponents,
53
53
  onUserIdGenerated,
54
+ loadingComponent,
54
55
  }) => {
55
56
  // Determine which API key to use
56
57
  const getApiKey = (): string => {
@@ -285,6 +286,9 @@ export const OnboardingFlow: React.FC<OnboardingFlowProps> = ({
285
286
  };
286
287
 
287
288
  if (loading) {
289
+ if (loadingComponent) {
290
+ return <>{loadingComponent}</>;
291
+ }
288
292
  return (
289
293
  <View style={styles.centerContainer}>
290
294
  <ActivityIndicator size="large" color="#007AFF" />
@@ -591,13 +591,32 @@ const RenderNode: React.FC<RenderNodeProps> = ({ element, toggledIds, groupSelec
591
591
  </View>
592
592
  );
593
593
 
594
- case 'video':
595
- // Video placeholder actual implementation would use expo-av or react-native-video
596
- // Resolve asset URL if present (for future implementation)
597
- if (element.props?.url) {
598
- const resolvedUrl = resolveAssetUrl(element.props.url);
599
- // TODO: Implement actual video player with resolvedUrl
594
+ case 'video': {
595
+ const videoUrl = element.props?.url ? resolveAssetUrl(element.props.url) : null;
596
+ let VideoComponent: any = null;
597
+ try {
598
+ const expoAv = require('expo-av');
599
+ VideoComponent = expoAv.Video;
600
+ } catch {}
601
+
602
+ if (VideoComponent && videoUrl) {
603
+ const resizeMode = element.props?.resizeMode || 'contain';
604
+ return (
605
+ <View style={[style, { backgroundColor: (style as ViewStyle).backgroundColor || '#1a1a1a', overflow: 'hidden' }]}>
606
+ <VideoComponent
607
+ source={{ uri: videoUrl }}
608
+ style={{ width: '100%', height: '100%' }}
609
+ resizeMode={resizeMode}
610
+ shouldPlay={element.props?.autoPlay !== false}
611
+ isLooping={element.props?.loop !== false}
612
+ isMuted={element.props?.muted !== false}
613
+ useNativeControls={false}
614
+ />
615
+ </View>
616
+ );
600
617
  }
618
+
619
+ // Fallback when expo-av is unavailable or no URL
601
620
  return (
602
621
  <View
603
622
  style={[
@@ -617,14 +636,44 @@ const RenderNode: React.FC<RenderNodeProps> = ({ element, toggledIds, groupSelec
617
636
  )}
618
637
  </View>
619
638
  );
639
+ }
620
640
 
621
- case 'lottie':
622
- // Lottie placeholder actual implementation would use lottie-react-native
623
- // Resolve asset URL if present (for future implementation)
624
- if (element.props?.url) {
625
- const resolvedUrl = resolveAssetUrl(element.props.url);
626
- // TODO: Implement actual Lottie player with resolvedUrl
641
+ case 'lottie': {
642
+ const lottieUrl = element.props?.url ? resolveAssetUrl(element.props.url) : null;
643
+ let LottieView: any = null;
644
+ try {
645
+ LottieView = require('lottie-react-native').default;
646
+ } catch {}
647
+
648
+ if (LottieView && lottieUrl) {
649
+ // Decode base64 data URLs to get the Lottie JSON object
650
+ let lottieSource: any;
651
+ if (lottieUrl.startsWith('data:application/json;base64,')) {
652
+ try {
653
+ const base64Data = lottieUrl.replace('data:application/json;base64,', '');
654
+ const jsonString = atob(base64Data);
655
+ lottieSource = JSON.parse(jsonString);
656
+ } catch {
657
+ lottieSource = { uri: lottieUrl };
658
+ }
659
+ } else {
660
+ lottieSource = { uri: lottieUrl };
661
+ }
662
+
663
+ return (
664
+ <View style={[style, { backgroundColor: (style as ViewStyle).backgroundColor || '#f8f8ff', overflow: 'hidden' }]}>
665
+ <LottieView
666
+ source={lottieSource}
667
+ style={{ width: '100%', height: '100%' }}
668
+ autoPlay={element.props?.autoPlay !== false}
669
+ loop={element.props?.loop !== false}
670
+ speed={element.props?.speed || 1}
671
+ />
672
+ </View>
673
+ );
627
674
  }
675
+
676
+ // Fallback when lottie-react-native is unavailable or no URL
628
677
  return (
629
678
  <View
630
679
  style={[
@@ -644,6 +693,7 @@ const RenderNode: React.FC<RenderNodeProps> = ({ element, toggledIds, groupSelec
644
693
  )}
645
694
  </View>
646
695
  );
696
+ }
647
697
 
648
698
  case 'input': {
649
699
  // Only apply default border if borderWidth is not explicitly defined (including 0)
package/src/types.ts CHANGED
@@ -300,4 +300,5 @@ export interface OnboardingFlowProps {
300
300
  initialVariables?: Record<string, any>; // seed the variable store
301
301
  customComponents?: Record<string, React.ComponentType<CustomScreenProps>>;
302
302
  onUserIdGenerated?: (userId: string) => void; // Called when user ID is generated for analytics
303
+ loadingComponent?: React.ReactNode; // Custom loading UI shown while fetching flow config
303
304
  }