diff --git a/apps/bare-expo/ios/Podfile.lock b/apps/bare-expo/ios/Podfile.lock
index eda94b27d4c904..0fca4da59da7ce 100644
--- a/apps/bare-expo/ios/Podfile.lock
+++ b/apps/bare-expo/ios/Podfile.lock
@@ -476,6 +476,7 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
+ - RNWorklets
- Yoga
- ExpoModulesCore/Tests (3.0.16):
- ExpoModulesJSI
@@ -501,6 +502,7 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
+ - RNWorklets
- Yoga
- ExpoModulesJSI (3.0.16):
- hermes-engine
@@ -3796,7 +3798,7 @@ SPEC CHECKSUMS:
ExpoMaps: ac5024fd6b3db82da997bdc4074bda5c3e2e7764
ExpoMediaLibrary: 648cee3f5dcba13410ec9cc8ac9a426e89a61a31
ExpoMeshGradient: 763087d3b1e6e9a0974e9700ea24cb598816d93c
- ExpoModulesCore: 22f2efcefda2486b06a0b9f0dcaab8eccdfaf0b4
+ ExpoModulesCore: 75234558d61ae2c7b81d21526a9a37a0ec957c0f
ExpoModulesJSI: c470ea2ed825fce73bdc4ef060c8a22e3f664092
ExpoModulesTestCore: e65555b75a4ed7dd3bcf421ad01d7748bd372c88
ExpoNetwork: 97073786edfe405aba5d0987a544617ed0671ad1
diff --git a/apps/expo-go/android/expoview/src/main/java/versioned/host/exp/exponent/ExperiencePackagePicker.kt b/apps/expo-go/android/expoview/src/main/java/versioned/host/exp/exponent/ExperiencePackagePicker.kt
index acb02972facde6..8ff0b2d778ae25 100644
--- a/apps/expo-go/android/expoview/src/main/java/versioned/host/exp/exponent/ExperiencePackagePicker.kt
+++ b/apps/expo-go/android/expoview/src/main/java/versioned/host/exp/exponent/ExperiencePackagePicker.kt
@@ -16,6 +16,7 @@ import expo.modules.clipboard.ClipboardModule
import expo.modules.constants.ConstantsModule
import expo.modules.constants.ConstantsService
import expo.modules.contacts.ContactsModule
+import expo.modules.contacts.next.ContactsNextModule
import expo.modules.core.interfaces.Package
import expo.modules.crypto.CryptoModule
import expo.modules.crypto.aes.AesCryptoModule
@@ -146,6 +147,7 @@ object ExperiencePackagePicker : ModulesProvider {
CryptoModule::class.java to null,
ConstantsModule::class.java to null,
ContactsModule::class.java to null,
+ ContactsNextModule::class.java to null,
DeviceModule::class.java to null,
DocumentPickerModule::class.java to null,
EASClientModule::class.java to null,
diff --git a/apps/native-component-list/src/navigation/ExpoApisStackNavigator.tsx b/apps/native-component-list/src/navigation/ExpoApisStackNavigator.tsx
index 31d856709d8993..e0c306318645ec 100644
--- a/apps/native-component-list/src/navigation/ExpoApisStackNavigator.tsx
+++ b/apps/native-component-list/src/navigation/ExpoApisStackNavigator.tsx
@@ -17,6 +17,7 @@ import { CryptoScreens } from '../screens/Crypto/CryptoScreen';
import ExpoApis from '../screens/ExpoApisScreen';
import { MediaLibraryScreens } from '../screens/MediaLibrary@Next/MediaLibraryScreens';
import { ModulesCoreScreens } from '../screens/ModulesCore/ModulesCoreScreen';
+import { WorkletsScreens } from '../screens/Worklets/WorkletsScreen';
import { type ScreenConfig } from '../types/ScreenConfig';
const Stack = createNativeStackNavigator();
@@ -446,6 +447,13 @@ export const ScreensList: ScreenConfig[] = [
},
name: 'Updates Reload Screen',
},
+ {
+ getComponent() {
+ return optionalRequire(() => require('../screens/Worklets/WorkletsScreen'));
+ },
+ name: 'Worklets integration',
+ route: 'worklets',
+ },
{
getComponent() {
return optionalRequire(() => require('../screens/WebBrowser/WebBrowserScreen'));
@@ -476,6 +484,7 @@ export const Screens: ScreenConfig[] = [
...CalendarsScreens,
...CalendarsNextScreens,
...CryptoScreens,
+ ...WorkletsScreens,
];
export const screenApiItems = apiScreensToListElements(ScreensList);
diff --git a/apps/native-component-list/src/screens/Worklets/WorkletsInitScreen.tsx b/apps/native-component-list/src/screens/Worklets/WorkletsInitScreen.tsx
new file mode 100644
index 00000000000000..95cfe4d951ce5c
--- /dev/null
+++ b/apps/native-component-list/src/screens/Worklets/WorkletsInitScreen.tsx
@@ -0,0 +1,95 @@
+import { installOnUIRuntime } from 'expo';
+import React, { useEffect, useState } from 'react';
+import { View, StyleSheet, Text } from 'react-native';
+import { runOnJS, runOnUI } from 'react-native-worklets';
+import 'react-native-reanimated';
+
+installOnUIRuntime();
+
+export default function WorkletsInitScreen() {
+ const [isExpoObjectAvailable, setIsExpoObjectAvailable] = useState(false);
+ const [isEventEmitterAvailable, setIsEventEmitterAvailable] = useState(false);
+ const [isNativeModuleAvailable, setIsNativeModuleAvailable] = useState(false);
+
+ useEffect(() => {
+ runOnUI(() => {
+ runOnJS(setIsExpoObjectAvailable)(!!globalThis.expo);
+ runOnJS(setIsEventEmitterAvailable)(!!globalThis.expo?.EventEmitter);
+ runOnJS(setIsNativeModuleAvailable)(!!globalThis.expo?.NativeModule);
+ })();
+ }, []);
+
+ return (
+
+
+ Worklets UI Runtime Status
+
+
+
+
+
+
+ );
+}
+
+function StatusRow({ label, available }: { label: string; available: boolean }) {
+ return (
+
+ {label}:
+
+ {available ? '✓ Available' : '✗ Unavailable'}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ padding: 16,
+ backgroundColor: '#f5f5f5',
+ },
+ section: {
+ marginBottom: 20,
+ },
+ title: {
+ fontSize: 20,
+ fontWeight: '600',
+ color: '#333',
+ },
+ row: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingVertical: 12,
+ paddingHorizontal: 16,
+ backgroundColor: '#fff',
+ borderRadius: 8,
+ marginBottom: 8,
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: 1 },
+ shadowOpacity: 0.1,
+ shadowRadius: 2,
+ elevation: 2,
+ },
+ label: {
+ fontSize: 16,
+ fontWeight: '500',
+ color: '#333',
+ flex: 1,
+ },
+ status: {
+ paddingHorizontal: 12,
+ paddingVertical: 6,
+ borderRadius: 6,
+ },
+ statusAvailable: {
+ backgroundColor: '#d4edda',
+ },
+ statusUnavailable: {
+ backgroundColor: '#f8d7da',
+ },
+ statusText: {
+ fontSize: 14,
+ fontWeight: '500',
+ },
+});
diff --git a/apps/native-component-list/src/screens/Worklets/WorkletsScreen.tsx b/apps/native-component-list/src/screens/Worklets/WorkletsScreen.tsx
new file mode 100644
index 00000000000000..50b0cc4b2ea831
--- /dev/null
+++ b/apps/native-component-list/src/screens/Worklets/WorkletsScreen.tsx
@@ -0,0 +1,17 @@
+import { optionalRequire } from '../../navigation/routeBuilder';
+import ComponentListScreen, { apiScreensToListElements } from '../ComponentListScreen';
+
+export const WorkletsScreens = [
+ {
+ name: 'Worklets Initialization',
+ route: 'worklets/init',
+ getComponent() {
+ return optionalRequire(() => require('./WorkletsInitScreen'));
+ },
+ },
+];
+
+export default function WorkletsScreen() {
+ const apis = apiScreensToListElements(WorkletsScreens);
+ return ;
+}
diff --git a/apps/router-e2e/__e2e__/native-navigation/app/header-items.tsx b/apps/router-e2e/__e2e__/native-navigation/app/header-items.tsx
index 7abbe314ce7115..5eb03739f4862a 100644
--- a/apps/router-e2e/__e2e__/native-navigation/app/header-items.tsx
+++ b/apps/router-e2e/__e2e__/native-navigation/app/header-items.tsx
@@ -99,225 +99,221 @@ export default function HeaderItemsScreen() {
return (
<>
-
-
- Header Items Test
-
- {/* Left header items */}
-
- {/* TODO: Hidden prop is missing for bar items */}
- {showLeftButton1 && (
-
- )}
- {showLeftButton2 && (
-
+
+ Header Items Test
+
+ {/* Left header items */}
+
+
+
+ Button 2
+
+ 33
+
+
+
+
+
+
+
+ Left Menu
+
+
+ 99
+
+ Alert.alert('Option 1')}>
+ Option 1
+
+
+ Alert.alert('Option 2')}>
+ Option 2
+
+
+
+
+
+ {/* Right header items */}
+
+
+ Menu
+
- Button 2
-
- 33
-
- )}
-
- {showLeftCustomItem && (
-
-
-
- )}
-
- {showLeftMenu && (
-
- Left Menu
-
-
- 99
-
- Alert.alert('Option 1')}>
- Option 1
-
-
- Alert.alert('Option 2')}>
- Option 2
-
-
-
- )}
-
-
- {/* Right header items */}
-
- {showRightMenu1 && (
-
- Menu
-
- 99
-
-
- {/* Simple actions */}
-
- Send email
-
-
-
- Delete email
-
-
-
- {/* Toggle action */}
-
-
- {emailsArchived ? 'Unarchive emails' : 'Archive emails'}
-
-
-
-
- {/* Nested inline menu - View mode */}
-
- View Mode
- handleViewModeSelect('icons')}>
- Icons
-
-
- handleViewModeSelect('list')}>
- List
-
-
-
-
- {/* Nested inline menu - Sort by */}
-
- Sort By
- handleSortBySelect('name')}>
- Name
-
- handleSortBySelect('kind')}>
- Kind
-
- handleSortBySelect('date')}>
- Date
-
- handleSortBySelect('size')}>
- Size
-
- handleSortBySelect('tags')}>
- Tags
-
-
-
- {/* Nested menu - Preferences */}
-
-
-
- {notificationsEnabled ? 'Disable notifications' : 'Enable notifications'}
-
-
-
-
- {/* Color selection submenu */}
-
- handleColorSelect('red')}>
- Red
-
-
- handleColorSelect('blue')}>
- Blue
-
-
- handleColorSelect('green')}>
- Green
-
-
-
-
-
- {/* Palette menu */}
-
-
- Alert.alert('Star')}>
- Star
-
- Alert.alert('Flag')}>
- Flag
-
- Alert.alert('Pin')}>
- Pin
-
-
-
- {/* Disabled action */}
- {}}>
- Locked action
-
-
-
- )}
-
- {showRightMenu2 && (
-
- Second
-
- )}
-
- {showRightButton && (
-
- Right
-
- )}
-
- {showSearchButton && (
-
- )}
-
-
+ 99
+
+
+ {/* Simple actions */}
+
+ Send email
+
+
+
+ Delete email
+
+
+
+ {/* Toggle action */}
+
+
+ {emailsArchived ? 'Unarchive emails' : 'Archive emails'}
+
+
+
+
+ {/* Nested inline menu - View mode */}
+
+ View Mode
+ handleViewModeSelect('icons')}>
+ Icons
+
+
+ handleViewModeSelect('list')}>
+ List
+
+
+
+
+ {/* Nested inline menu - Sort by */}
+
+ Sort By
+ handleSortBySelect('name')}>
+ Name
+
+ handleSortBySelect('kind')}>
+ Kind
+
+ handleSortBySelect('date')}>
+ Date
+
+ handleSortBySelect('size')}>
+ Size
+
+ handleSortBySelect('tags')}>
+ Tags
+
+
+
+ {/* Nested menu - Preferences */}
+
+
+
+ {notificationsEnabled ? 'Disable notifications' : 'Enable notifications'}
+
+
+
+
+ {/* Color selection submenu */}
+
+ handleColorSelect('red')}>
+ Red
+
+
+ handleColorSelect('blue')}>
+ Blue
+
+
+ handleColorSelect('green')}>
+ Green
+
+
+
+
+
+ {/* Palette menu */}
+
+
+ Alert.alert('Star')}>
+ Star
+
+ Alert.alert('Flag')}>
+ Flag
+
+ Alert.alert('Pin')}>
+ Pin
+
+
+
+ {/* Disabled action */}
+ {}}>
+ Locked action
+
+
+
+
+
+ Second
+
+
+
+ Right
+
+
+
+
- Form Sheet Modal Content - Start
- Form Sheet Modal Content - End
-
+ <>
+
+
+ console.log('Checkmark pressed')} />
+
+
+ console.log('Xmark pressed')} />
+
+
+ Form Sheet Modal Content - Start
+ Form Sheet Modal Content - End
+
+ >
);
}
diff --git a/apps/router-e2e/__e2e__/native-navigation/app/modals/index.tsx b/apps/router-e2e/__e2e__/native-navigation/app/modals/index.tsx
index a94fbc4f054916..b1be667de2fa7d 100644
--- a/apps/router-e2e/__e2e__/native-navigation/app/modals/index.tsx
+++ b/apps/router-e2e/__e2e__/native-navigation/app/modals/index.tsx
@@ -1,4 +1,4 @@
-import { Link, usePathname, type Href } from 'expo-router';
+import { Link, Stack, usePathname, type Href } from 'expo-router';
import React from 'react';
import { Text, Pressable, ScrollView, View } from 'react-native';
import { featureFlags } from 'react-native-screens';
@@ -11,6 +11,16 @@ const HomeIndex = () => {
style={{ flex: 1, backgroundColor: '#fff' }}
contentContainerStyle={{ alignItems: 'center', gap: 16 }}
contentInsetAdjustmentBehavior="automatic">
+
+
+ console.log('Index Checkmark pressed')}
+ />
+
+
+ console.log('Index Xmark pressed')} />
+
Modals
Current Path: {pathname}
diff --git a/apps/router-e2e/__e2e__/native-navigation/app/toolbar.tsx b/apps/router-e2e/__e2e__/native-navigation/app/toolbar.tsx
index dec13634a489e5..80743800998ac1 100644
--- a/apps/router-e2e/__e2e__/native-navigation/app/toolbar.tsx
+++ b/apps/router-e2e/__e2e__/native-navigation/app/toolbar.tsx
@@ -1,6 +1,5 @@
import { useImage } from 'expo-image';
import { Color, Stack, useLocalSearchParams } from 'expo-router';
-import { Toolbar } from 'expo-router/unstable-toolbar';
import { SymbolView } from 'expo-symbols';
import { useState, useRef } from 'react';
import {
@@ -101,7 +100,6 @@ export default function ToolbarScreen() {
contentContainerStyle={styles.contentContainer}
contentInsetAdjustmentBehavior="automatic">
Toolbar E2E Test Screen
-
setIsSearchFocused(true)}
onBlur={() => setIsSearchFocused(false)}
@@ -271,19 +269,19 @@ export default function ToolbarScreen() {
-
+
{/* Flexible spacer at the start */}
-
+
{/* Search bar */}
-
{/* Search button */}
-
-
+
{/* Fixed width spacer */}
{showFixedSpacer && (
-
+
-
+
{/* Conditional buttons based on search focus */}
{!isSearchFocused && (
-
+
Alert.alert('Custom Button', 'Plus button pressed!')}
@@ -354,129 +352,140 @@ export default function ToolbarScreen() {
name="plus"
/>
-
+
{/* Nested menu with dynamic content */}
{showMenu && (
-
+
{/* Simple actions */}
-
+
Send email
-
-
+
+
Delete email
-
+
{/* Toggle action with isOn state */}
-
{emailsArchived ? 'Unarchive emails' : 'Archive emails'}
-
+
{/* Nested inline menu */}
-
-
+ Alert.alert('Move', 'Moving to folder...')}>
Move to folder
-
- Alert.alert('Tag', 'Adding tag...')}>
+
+ Alert.alert('Tag', 'Adding tag...')}>
Add tag
-
-
+
+
{/* Nested menu with state-based selections */}
-
-
+
{notificationsEnabled ? 'Disable notifications' : 'Enable notifications'}
-
+
{/* Color selection submenu */}
-
-
+ handleColorSelect('red')}>
Red
-
-
+ handleColorSelect('blue')}>
Blue
-
-
+ handleColorSelect('green')}>
Green
-
-
-
+
+
+
{/* Palette menu example (small icons only) */}
-
- Alert.alert('Star')}>
+
+ Alert.alert('Star')}>
Star-palette
-
- Alert.alert('Flag')}>
+
+ Alert.alert('Flag')}>
Flag-palette
-
- Alert.alert('Pin')}>
+
+ Alert.alert('Pin')}>
Pin-palette
-
-
+
+
-
- Alert.alert('Star')}>
+
+ Alert.alert('Star')}>
Star
-
- Alert.alert('Flag')}>
+
+ Alert.alert('Flag')}>
Flag
-
- Alert.alert('Pin')}>
+
+ Alert.alert('Pin')}>
Pin
-
-
+
+
{/* elementSize="medium" displays actions horizontally with titles (iOS 16+) */}
-
- Alert.alert('Refreshing')}>
+
+ Alert.alert('Refreshing')}>
Refresh
-
- Alert.alert('Resuming')}>
+
+ Alert.alert('Resuming')}>
Resume
-
- Alert.alert('Pin')}>
+
+ Alert.alert('Pin')}>
Pin
-
-
+
+
{/* elementSize="large" displays actions with larger icons and titles */}
-
- Alert.alert('Sharing')}>
+
+ Alert.alert('Sharing')}>
Share
-
- Alert.alert('Copying')}>
+
+ Alert.alert('Copying')}>
Copy
-
-
+
+
{/* Disabled action */}
- {}}>
+ {}}>
Locked action
-
-
+
+
)}
{/* Flexible spacer at the end */}
-
-
+
+
>
);
}
diff --git a/apps/router-e2e/__e2e__/stack/app/[id].tsx b/apps/router-e2e/__e2e__/stack/app/[id].tsx
index 372ed89d0a4437..aa998459409449 100644
--- a/apps/router-e2e/__e2e__/stack/app/[id].tsx
+++ b/apps/router-e2e/__e2e__/stack/app/[id].tsx
@@ -1,5 +1,4 @@
import { Color, Link, Stack, useRouter } from 'expo-router';
-import { Toolbar } from 'expo-router/unstable-toolbar';
import { useRef, useState } from 'react';
import { Button, Text, View } from 'react-native';
import type { SearchBarCommands } from 'react-native-screens';
@@ -24,27 +23,27 @@ export default function Modal() {