rampkit-expo-dev 0.0.76 → 0.0.77

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.
@@ -45,7 +45,8 @@ const jsx_runtime_1 = require("react/jsx-runtime");
45
45
  const react_1 = __importStar(require("react"));
46
46
  const react_native_1 = require("react-native");
47
47
  const react_native_root_siblings_1 = __importDefault(require("react-native-root-siblings"));
48
- const react_native_pager_view_1 = __importDefault(require("react-native-pager-view"));
48
+ // PagerView removed - we now render all screens in a stack to ensure first paint completes
49
+ // before any navigation. This fixes the "glitch on first open" bug.
49
50
  const react_native_webview_1 = require("react-native-webview");
50
51
  const RampKitNative_1 = require("./RampKitNative");
51
52
  const OnboardingResponseStorage_1 = require("./OnboardingResponseStorage");
@@ -1004,7 +1005,6 @@ const SLIDE_FADE_DURATION = 320;
1004
1005
  function Overlay(props) {
1005
1006
  // Get explicit window dimensions to prevent flex-based layout recalculations during transitions
1006
1007
  const { width: windowWidth, height: windowHeight } = (0, react_native_1.useWindowDimensions)();
1007
- const pagerRef = (0, react_1.useRef)(null);
1008
1008
  const [index, setIndex] = (0, react_1.useState)(0);
1009
1009
  const [loadedCount, setLoadedCount] = (0, react_1.useState)(0);
1010
1010
  const [firstPageLoaded, setFirstPageLoaded] = (0, react_1.useState)(false);
@@ -1014,9 +1014,14 @@ function Overlay(props) {
1014
1014
  const [onboardingCompleted, setOnboardingCompleted] = (0, react_1.useState)(false);
1015
1015
  const overlayOpacity = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
1016
1016
  const fadeOpacity = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
1017
- // slideFade animation values - animates the PagerView container
1018
- const pagerOpacity = (0, react_1.useRef)(new react_native_1.Animated.Value(1)).current;
1019
- const pagerTranslateX = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
1017
+ // Per-screen animation values - each screen has its own opacity and translateX
1018
+ // This replaces PagerView and ensures ALL screens are rendered (forcing first paint)
1019
+ // First screen starts visible (opacity: 1), others start hidden (opacity: 0)
1020
+ const screenAnimsRef = (0, react_1.useRef)(props.screens.map((_, i) => ({
1021
+ opacity: new react_native_1.Animated.Value(i === 0 ? 1 : 0),
1022
+ translateX: new react_native_1.Animated.Value(0),
1023
+ })));
1024
+ const screenAnims = screenAnimsRef.current;
1020
1025
  const allLoaded = loadedCount >= props.screens.length;
1021
1026
  const hasTrackedInitialScreen = (0, react_1.useRef)(false);
1022
1027
  // shared vars across all webviews - INITIALIZE from props.variables!
@@ -1203,7 +1208,20 @@ function Overlay(props) {
1203
1208
  (_a = props.onRegisterClose) === null || _a === void 0 ? void 0 : _a.call(props, null);
1204
1209
  };
1205
1210
  }, [handleRequestClose, props.onRegisterClose]);
1211
+ // Helper to complete a screen transition - updates state and sends data
1212
+ const completeTransition = (nextIndex) => {
1213
+ setIndex(nextIndex);
1214
+ sendVarsToWebView(nextIndex);
1215
+ sendOnboardingStateToWebView(nextIndex);
1216
+ // Track screen change event
1217
+ if (props.onScreenChange && props.screens[nextIndex]) {
1218
+ props.onScreenChange(nextIndex, props.screens[nextIndex].id);
1219
+ }
1220
+ };
1206
1221
  // Android hardware back goes to previous page, then closes
1222
+ // NOTE: This function no longer uses PagerView. Instead, all screens are rendered
1223
+ // in a stack and we animate individual screen opacity/transform values.
1224
+ // This ensures all WebViews complete their first paint before any navigation.
1207
1225
  const navigateToIndex = (nextIndex, animation = "fade") => {
1208
1226
  if (nextIndex === index ||
1209
1227
  nextIndex < 0 ||
@@ -1211,108 +1229,103 @@ function Overlay(props) {
1211
1229
  return;
1212
1230
  if (isTransitioning)
1213
1231
  return;
1232
+ // Update active screen index and activation time FIRST
1233
+ activeScreenIndexRef.current = nextIndex;
1234
+ screenActivationTimeRef.current[nextIndex] = Date.now();
1214
1235
  // Parse animation type case-insensitively
1215
1236
  const animationType = (animation === null || animation === void 0 ? void 0 : animation.toLowerCase()) || "fade";
1216
- // Slide animation: use PagerView's built-in animated page change
1217
- // and skip the fade curtain overlay.
1237
+ const currentScreenAnim = screenAnims[index];
1238
+ const nextScreenAnim = screenAnims[nextIndex];
1239
+ const isForward = nextIndex > index;
1240
+ const direction = isForward ? 1 : -1;
1241
+ // Slide animation: animate both screens simultaneously
1218
1242
  if (animationType === "slide") {
1219
- // Update active screen index and activation time FIRST
1220
- activeScreenIndexRef.current = nextIndex;
1221
- screenActivationTimeRef.current[nextIndex] = Date.now();
1222
- // @ts-ignore: methods exist on PagerView instance
1223
- const pager = pagerRef.current;
1224
- if (!pager)
1225
- return;
1226
- if (typeof pager.setPage === "function") {
1227
- pager.setPage(nextIndex);
1228
- }
1229
- else if (typeof pager.setPageWithoutAnimation === "function") {
1230
- pager.setPageWithoutAnimation(nextIndex);
1231
- }
1232
- // Explicitly send vars to the new page after setting it
1233
- requestAnimationFrame(() => {
1234
- sendVarsToWebView(nextIndex);
1235
- sendOnboardingStateToWebView(nextIndex);
1243
+ setIsTransitioning(true);
1244
+ // Set up next screen starting position (offscreen in direction of navigation)
1245
+ nextScreenAnim.translateX.setValue(SLIDE_FADE_OFFSET * direction);
1246
+ nextScreenAnim.opacity.setValue(1);
1247
+ // Animate both screens
1248
+ react_native_1.Animated.parallel([
1249
+ // Current screen slides out
1250
+ react_native_1.Animated.timing(currentScreenAnim.translateX, {
1251
+ toValue: -SLIDE_FADE_OFFSET * direction,
1252
+ duration: SLIDE_FADE_DURATION,
1253
+ easing: react_native_1.Easing.out(react_native_1.Easing.ease),
1254
+ useNativeDriver: true,
1255
+ }),
1256
+ // Next screen slides in
1257
+ react_native_1.Animated.timing(nextScreenAnim.translateX, {
1258
+ toValue: 0,
1259
+ duration: SLIDE_FADE_DURATION,
1260
+ easing: react_native_1.Easing.out(react_native_1.Easing.ease),
1261
+ useNativeDriver: true,
1262
+ }),
1263
+ ]).start(() => {
1264
+ // Hide old screen and reset its position
1265
+ currentScreenAnim.opacity.setValue(0);
1266
+ currentScreenAnim.translateX.setValue(0);
1267
+ completeTransition(nextIndex);
1268
+ setIsTransitioning(false);
1236
1269
  });
1237
1270
  return;
1238
1271
  }
1239
- // slideFade animation: smooth slide + fade transition
1240
- // Animates the PagerView container out, switches page, then animates back in
1272
+ // slideFade animation: smooth slide + fade transition on both screens
1241
1273
  if (animationType === "slidefade") {
1242
1274
  setIsTransitioning(true);
1243
- // Update active screen index and activation time FIRST
1244
- activeScreenIndexRef.current = nextIndex;
1245
- screenActivationTimeRef.current[nextIndex] = Date.now();
1246
- // Determine direction: forward (nextIndex > index) or backward
1247
- const isForward = nextIndex > index;
1248
- const direction = isForward ? 1 : -1;
1249
1275
  const halfDuration = SLIDE_FADE_DURATION / 2;
1250
- const timingConfig = {
1251
- duration: halfDuration,
1252
- easing: react_native_1.Easing.out(react_native_1.Easing.ease),
1253
- useNativeDriver: true,
1254
- };
1255
- // Phase 1: Fade out and slide the current page in exit direction
1276
+ // Set up next screen starting position
1277
+ nextScreenAnim.translateX.setValue(SLIDE_FADE_OFFSET * direction * 0.5);
1278
+ nextScreenAnim.opacity.setValue(0);
1279
+ // Animate both screens simultaneously with crossfade
1256
1280
  react_native_1.Animated.parallel([
1257
- react_native_1.Animated.timing(pagerOpacity, {
1281
+ // Current screen fades out and slides away
1282
+ react_native_1.Animated.timing(currentScreenAnim.opacity, {
1258
1283
  toValue: 0,
1259
- ...timingConfig,
1284
+ duration: halfDuration,
1285
+ easing: react_native_1.Easing.out(react_native_1.Easing.ease),
1286
+ useNativeDriver: true,
1260
1287
  }),
1261
- react_native_1.Animated.timing(pagerTranslateX, {
1262
- toValue: -SLIDE_FADE_OFFSET * direction * 0.5, // Slide out in opposite direction
1263
- ...timingConfig,
1288
+ react_native_1.Animated.timing(currentScreenAnim.translateX, {
1289
+ toValue: -SLIDE_FADE_OFFSET * direction * 0.5,
1290
+ duration: halfDuration,
1291
+ easing: react_native_1.Easing.out(react_native_1.Easing.ease),
1292
+ useNativeDriver: true,
1293
+ }),
1294
+ // Next screen fades in and slides to center
1295
+ react_native_1.Animated.timing(nextScreenAnim.opacity, {
1296
+ toValue: 1,
1297
+ duration: halfDuration,
1298
+ easing: react_native_1.Easing.out(react_native_1.Easing.ease),
1299
+ useNativeDriver: true,
1300
+ }),
1301
+ react_native_1.Animated.timing(nextScreenAnim.translateX, {
1302
+ toValue: 0,
1303
+ duration: halfDuration,
1304
+ easing: react_native_1.Easing.out(react_native_1.Easing.ease),
1305
+ useNativeDriver: true,
1264
1306
  }),
1265
1307
  ]).start(() => {
1266
- var _a, _b, _c, _d;
1267
- // Switch page instantly while invisible
1268
- // @ts-ignore: method exists on PagerView instance
1269
- (_c = (_b = (_a = pagerRef.current) === null || _a === void 0 ? void 0 : _a.setPageWithoutAnimation) === null || _b === void 0 ? void 0 : _b.call(_a, nextIndex)) !== null && _c !== void 0 ? _c : (_d = pagerRef.current) === null || _d === void 0 ? void 0 : _d.setPage(nextIndex);
1270
- // Set up for incoming animation - start from the direction we're navigating from
1271
- pagerTranslateX.setValue(SLIDE_FADE_OFFSET * direction * 0.5);
1272
- // Phase 2: Fade in and slide the new page to center
1273
- react_native_1.Animated.parallel([
1274
- react_native_1.Animated.timing(pagerOpacity, {
1275
- toValue: 1,
1276
- duration: halfDuration,
1277
- easing: react_native_1.Easing.out(react_native_1.Easing.ease),
1278
- useNativeDriver: true,
1279
- }),
1280
- react_native_1.Animated.timing(pagerTranslateX, {
1281
- toValue: 0,
1282
- duration: halfDuration,
1283
- easing: react_native_1.Easing.out(react_native_1.Easing.ease),
1284
- useNativeDriver: true,
1285
- }),
1286
- ]).start(() => {
1287
- // Send vars and onboarding state to the new page
1288
- sendVarsToWebView(nextIndex);
1289
- sendOnboardingStateToWebView(nextIndex);
1290
- setIsTransitioning(false);
1291
- });
1308
+ // Reset old screen position
1309
+ currentScreenAnim.translateX.setValue(0);
1310
+ completeTransition(nextIndex);
1311
+ setIsTransitioning(false);
1292
1312
  });
1293
1313
  return;
1294
1314
  }
1295
1315
  // Default fade animation: uses a white curtain overlay
1296
1316
  setIsTransitioning(true);
1297
- // Update active screen index and activation time FIRST
1298
- activeScreenIndexRef.current = nextIndex;
1299
- screenActivationTimeRef.current[nextIndex] = Date.now();
1300
1317
  react_native_1.Animated.timing(fadeOpacity, {
1301
1318
  toValue: 1,
1302
1319
  duration: 160,
1303
1320
  easing: react_native_1.Easing.out(react_native_1.Easing.quad),
1304
1321
  useNativeDriver: true,
1305
1322
  }).start(() => {
1306
- var _a, _b, _c, _d;
1307
- // switch page without built-in slide animation
1308
- // @ts-ignore: method exists on PagerView instance
1309
- (_c = (_b = (_a = pagerRef.current) === null || _a === void 0 ? void 0 : _a.setPageWithoutAnimation) === null || _b === void 0 ? void 0 : _b.call(_a, nextIndex)) !== null && _c !== void 0 ? _c : (_d = pagerRef.current) === null || _d === void 0 ? void 0 : _d.setPage(nextIndex);
1323
+ // Swap screens instantly while curtain is opaque
1324
+ currentScreenAnim.opacity.setValue(0);
1325
+ nextScreenAnim.opacity.setValue(1);
1310
1326
  requestAnimationFrame(() => {
1311
- // Explicitly send vars and onboarding state to the new page after the page switch completes
1312
- // This ensures the webview receives the latest state even if onPageSelected
1313
- // timing was off during the transition
1314
- sendVarsToWebView(nextIndex);
1315
- sendOnboardingStateToWebView(nextIndex);
1327
+ completeTransition(nextIndex);
1328
+ // Fade curtain out to reveal new screen
1316
1329
  react_native_1.Animated.timing(fadeOpacity, {
1317
1330
  toValue: 0,
1318
1331
  duration: 160,
@@ -1447,7 +1460,7 @@ function Overlay(props) {
1447
1460
  wv.injectJavaScript(buildDirectVarsScript(varsRef.current));
1448
1461
  // NOTE: Do NOT call sendOnboardingStateToWebView here - it would cause infinite loops
1449
1462
  // because the WebView echoes back variables which triggers another sendVarsToWebView.
1450
- // Onboarding state is sent separately in onLoadEnd and onPageSelected.
1463
+ // Onboarding state is sent separately in onLoadEnd and navigateToIndex.
1451
1464
  }
1452
1465
  react_1.default.useEffect(() => {
1453
1466
  const sub = react_native_1.BackHandler.addEventListener("hardwareBackPress", () => {
@@ -1490,24 +1503,8 @@ function Overlay(props) {
1490
1503
  }, 600);
1491
1504
  return () => clearTimeout(tid);
1492
1505
  }, [docs.length, firstPageLoaded]);
1493
- const onPageSelected = (e) => {
1494
- const pos = e.nativeEvent.position;
1495
- setIndex(pos);
1496
- // Update active screen index and activation time FIRST
1497
- activeScreenIndexRef.current = pos;
1498
- screenActivationTimeRef.current[pos] = Date.now();
1499
- if (__DEV__)
1500
- console.log("[Rampkit] onPageSelected - activating screen", pos);
1501
- // Send vars and onboarding state to the newly active screen
1502
- requestAnimationFrame(() => {
1503
- sendVarsToWebView(pos);
1504
- sendOnboardingStateToWebView(pos);
1505
- });
1506
- // Track screen change event
1507
- if (props.onScreenChange && props.screens[pos]) {
1508
- props.onScreenChange(pos, props.screens[pos].id);
1509
- }
1510
- };
1506
+ // NOTE: onPageSelected callback removed - we no longer use PagerView.
1507
+ // Screen transitions are now handled directly in navigateToIndex via completeTransition().
1511
1508
  const handleAdvance = (i, animation = "fade") => {
1512
1509
  var _a;
1513
1510
  const currentScreenId = (_a = props.screens[i]) === null || _a === void 0 ? void 0 : _a.id;
@@ -1623,13 +1620,19 @@ function Overlay(props) {
1623
1620
  styles.root,
1624
1621
  !visible && styles.invisible,
1625
1622
  visible && { opacity: overlayOpacity },
1626
- ], pointerEvents: visible && !isClosing ? "auto" : "none", children: [(0, jsx_runtime_1.jsx)(react_native_1.Animated.View, { style: [
1627
- react_native_1.StyleSheet.absoluteFill,
1628
- {
1629
- opacity: pagerOpacity,
1630
- transform: [{ translateX: pagerTranslateX }],
1631
- },
1632
- ], children: (0, jsx_runtime_1.jsx)(react_native_pager_view_1.default, { ref: pagerRef, style: react_native_1.StyleSheet.absoluteFill, scrollEnabled: false, initialPage: 0, onPageSelected: onPageSelected, offscreenPageLimit: props.screens.length, overScrollMode: "never", children: docs.map((doc, i) => ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: { width: windowWidth, height: windowHeight }, renderToHardwareTextureAndroid: true, children: (0, jsx_runtime_1.jsx)(react_native_webview_1.WebView, { ref: (r) => (webviewsRef.current[i] = r), style: { width: windowWidth, height: windowHeight }, originWhitelist: ["*"], source: { html: doc }, injectedJavaScriptBeforeContentLoaded: exports.injectedHardening + exports.injectedDynamicTapHandler + exports.injectedButtonAnimations, injectedJavaScript: exports.injectedNoSelect + exports.injectedVarsHandler + exports.injectedButtonAnimations, automaticallyAdjustContentInsets: false, contentInsetAdjustmentBehavior: "never", bounces: false, scrollEnabled: false, overScrollMode: "never", scalesPageToFit: false, showsHorizontalScrollIndicator: false, dataDetectorTypes: "none", allowsLinkPreview: false, allowsInlineMediaPlayback: true, mediaPlaybackRequiresUserAction: false, cacheEnabled: true, javaScriptEnabled: true, domStorageEnabled: true, hideKeyboardAccessoryView: true, onLoadEnd: () => {
1623
+ ], pointerEvents: visible && !isClosing ? "auto" : "none", children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: react_native_1.StyleSheet.absoluteFill, children: docs.map((doc, i) => {
1624
+ var _a, _b;
1625
+ return ((0, jsx_runtime_1.jsx)(react_native_1.Animated.View, { style: [
1626
+ react_native_1.StyleSheet.absoluteFill,
1627
+ {
1628
+ opacity: (_a = screenAnims[i]) === null || _a === void 0 ? void 0 : _a.opacity,
1629
+ transform: [{ translateX: ((_b = screenAnims[i]) === null || _b === void 0 ? void 0 : _b.translateX) || 0 }],
1630
+ // Active screen renders on top
1631
+ zIndex: i === index ? 1 : 0,
1632
+ },
1633
+ ],
1634
+ // Only the active screen receives touch events
1635
+ pointerEvents: i === index ? 'auto' : 'none', children: (0, jsx_runtime_1.jsx)(react_native_webview_1.WebView, { ref: (r) => (webviewsRef.current[i] = r), style: { width: windowWidth, height: windowHeight }, originWhitelist: ["*"], source: { html: doc }, injectedJavaScriptBeforeContentLoaded: exports.injectedHardening + exports.injectedDynamicTapHandler + exports.injectedButtonAnimations, injectedJavaScript: exports.injectedNoSelect + exports.injectedVarsHandler + exports.injectedButtonAnimations, automaticallyAdjustContentInsets: false, contentInsetAdjustmentBehavior: "never", bounces: false, scrollEnabled: false, overScrollMode: "never", scalesPageToFit: false, showsHorizontalScrollIndicator: false, dataDetectorTypes: "none", allowsLinkPreview: false, allowsInlineMediaPlayback: true, mediaPlaybackRequiresUserAction: false, cacheEnabled: true, javaScriptEnabled: true, domStorageEnabled: true, hideKeyboardAccessoryView: true, onLoadEnd: () => {
1633
1636
  // Only initialize each screen ONCE to avoid repeated processing
1634
1637
  if (initializedScreensRef.current.has(i)) {
1635
1638
  if (__DEV__)
@@ -1930,7 +1933,8 @@ function Overlay(props) {
1930
1933
  }, onError: (e) => {
1931
1934
  // You can surface an inline error UI here if you want
1932
1935
  console.warn("WebView error:", e.nativeEvent);
1933
- } }) }, props.screens[i].id))) }) }), (0, jsx_runtime_1.jsx)(react_native_1.Animated.View, { pointerEvents: isTransitioning ? "auto" : "none", style: [
1936
+ } }) }, props.screens[i].id));
1937
+ }) }), (0, jsx_runtime_1.jsx)(react_native_1.Animated.View, { pointerEvents: isTransitioning ? "auto" : "none", style: [
1934
1938
  react_native_1.StyleSheet.absoluteFillObject,
1935
1939
  styles.curtain,
1936
1940
  { opacity: fadeOpacity },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rampkit-expo-dev",
3
- "version": "0.0.76",
3
+ "version": "0.0.77",
4
4
  "description": "The Expo SDK for RampKit. Build, test, and personalize app onboardings with instant updates.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",