diff --git a/apps/bare-expo/App.tsx b/apps/bare-expo/App.tsx index 77d6d9b31641f0..62e271601ee9b9 100644 --- a/apps/bare-expo/App.tsx +++ b/apps/bare-expo/App.tsx @@ -1,5 +1,5 @@ import { ThemeProvider } from 'ThemeProvider'; -import { AppMetricsRoot } from 'expo-observe'; +import { ObserveRoot } from 'expo-observe'; import * as Splashscreen from 'expo-splash-screen'; import React from 'react'; import * as DevMenu from 'expo-dev-menu'; @@ -94,8 +94,8 @@ export default function Main() { const isLoaded = useLoaded(); return ( - + {isLoaded ? : null} - + ); } diff --git a/apps/expo-go/ios/Exponent-Bridging-Header.h b/apps/expo-go/ios/Exponent-Bridging-Header.h index 76ce886490061f..81499b4e29269b 100644 --- a/apps/expo-go/ios/Exponent-Bridging-Header.h +++ b/apps/expo-go/ios/Exponent-Bridging-Header.h @@ -30,3 +30,4 @@ #import "EXReactAppManager.h" #import "EXAbstractLoader.h" #import "EXProgressHUD.h" +#import "EXConstantsBinding.h" diff --git a/apps/expo-go/ios/Exponent/Versioned/Core/VersionManager.swift b/apps/expo-go/ios/Exponent/Versioned/Core/VersionManager.swift index 991ffc9e22221e..61566900d2163a 100644 --- a/apps/expo-go/ios/Exponent/Versioned/Core/VersionManager.swift +++ b/apps/expo-go/ios/Exponent/Versioned/Core/VersionManager.swift @@ -62,6 +62,13 @@ final class VersionManager: EXVersionManagerObjC { self.appContext = appContext self.legacyModuleRegistry = legacyModuleRegistry + // The ConstantsProvider hardcodes `executionEnvironment ti "bare"` and reads `manifest` from + // EXConstants.bundle. In Expo Go we need `executionEnvironment to be + // "storeClient". EXConstantsBinding merges them on top of the + // base constants, so installing it here makes `Constants.expoConfig`, + // `Constants.executionEnvironment` resolve correctly. + appContext.constants = EXConstantsBinding(params: params) + registerExpoModules(appContext) hasRegisteredExpoModules = true diff --git a/apps/native-component-list/src/screens/UI/DropdownMenuScreen.android.tsx b/apps/native-component-list/src/screens/UI/DropdownMenuScreen.android.tsx index 808756e2f7902b..a41c1593daba08 100644 --- a/apps/native-component-list/src/screens/UI/DropdownMenuScreen.android.tsx +++ b/apps/native-component-list/src/screens/UI/DropdownMenuScreen.android.tsx @@ -6,6 +6,7 @@ import { DropdownMenuItem, Host, Icon, + RNHostView, Row, Switch, Text as ComposeText, @@ -14,7 +15,7 @@ import { } from '@expo/ui/jetpack-compose'; import { background } from '@expo/ui/jetpack-compose/modifiers'; import * as React from 'react'; -import { View, Text, Alert } from 'react-native'; +import { View, Text, Alert, Pressable } from 'react-native'; import { Section } from '../../components/Page'; @@ -39,6 +40,7 @@ export default function DropdownMenuScreen() { const [colorfulMenuExpanded, setColorfulMenuExpanded] = React.useState(false); const [sectionsMenuExpanded, setSectionsMenuExpanded] = React.useState(false); const [submenuExpanded, setSubmenuExpanded] = React.useState(false); + const [rnTriggerMenuExpanded, setRnTriggerMenuExpanded] = React.useState(false); React.useEffect(() => { if (sectionsMenuExpanded === false) { @@ -319,6 +321,41 @@ export default function DropdownMenuScreen() { +
+ + setRnTriggerMenuExpanded(false)}> + + + setRnTriggerMenuExpanded(true)} + style={{ + alignSelf: 'flex-start', + paddingHorizontal: 16, + paddingVertical: 10, + borderRadius: 8, + backgroundColor: '#9B59B6', + }}> + RN Pressable Trigger + + + + + setRnTriggerMenuExpanded(false)}> + + Item 1 + + + setRnTriggerMenuExpanded(false)}> + + Item 2 + + + + + +
); } diff --git a/apps/native-component-list/src/screens/UI/MenuScreen.ios.tsx b/apps/native-component-list/src/screens/UI/MenuScreen.ios.tsx index 930d3bd542c0da..4175e4b5795de9 100644 --- a/apps/native-component-list/src/screens/UI/MenuScreen.ios.tsx +++ b/apps/native-component-list/src/screens/UI/MenuScreen.ios.tsx @@ -9,6 +9,7 @@ import { Section, Divider, Picker, + RNHostView, } from '@expo/ui/swift-ui'; import { buttonStyle, @@ -18,6 +19,7 @@ import { tag, } from '@expo/ui/swift-ui/modifiers'; import * as React from 'react'; +import { Pressable, Text as RNText } from 'react-native'; export default function MenuScreen() { const [selectedIndex, setSelectedIndex] = React.useState(1); @@ -54,6 +56,30 @@ export default function MenuScreen() { +
+ + console.log('RN trigger pressed')} + style={{ + paddingHorizontal: 16, + paddingVertical: 10, + borderRadius: 8, + alignSelf: 'flex-start', + backgroundColor: '#9B59B6', + }}> + + RN Pressable Trigger + + + + }> + +
+
{ const events = await AppMetrics.getStoredEntries(); @@ -28,8 +28,7 @@ export default function Index() { }, [updateStoredEntries]); async function handleMarkInteractive() { - await AppMetrics.markInteractive(); - await markPageInteractive(); + await markInteractive(); await updateStoredEntries(); } diff --git a/apps/observe-tester/app/(tabs)/(sessions)/sessions/[id].tsx b/apps/observe-tester/app/(tabs)/(sessions)/sessions/[id].tsx index 916fabbf5c75d4..1256c6f73d19e3 100644 --- a/apps/observe-tester/app/(tabs)/(sessions)/sessions/[id].tsx +++ b/apps/observe-tester/app/(tabs)/(sessions)/sessions/[id].tsx @@ -1,6 +1,7 @@ import AppMetrics, { type Session } from 'expo-app-metrics'; +import { useObserve } from 'expo-observe'; import { Stack, useFocusEffect, useLocalSearchParams } from 'expo-router'; -import { useCallback, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { Platform, Pressable, ScrollView, StyleSheet, Text } from 'react-native'; import { CallStackTreeView } from '@/components/CallStackTreeView'; @@ -20,6 +21,13 @@ export default function SessionDetail() { const [loaded, setLoaded] = useState(false); const [showRawJson, setShowRawJson] = useState(false); + const { markInteractive } = useObserve(); + useEffect(() => { + setTimeout(() => { + markInteractive(); + }, 100); + }, []); + useFocusEffect( useCallback(() => { AppMetrics.getAllSessions().then((sessions) => { diff --git a/apps/observe-tester/app/(tabs)/(sessions)/sessions/index.tsx b/apps/observe-tester/app/(tabs)/(sessions)/sessions/index.tsx index 42e0c402d91a4a..9465c70e224d33 100644 --- a/apps/observe-tester/app/(tabs)/(sessions)/sessions/index.tsx +++ b/apps/observe-tester/app/(tabs)/(sessions)/sessions/index.tsx @@ -1,6 +1,7 @@ import AppMetrics, { type Session } from 'expo-app-metrics'; +import { useObserve } from 'expo-observe'; import { router, Stack, useFocusEffect } from 'expo-router'; -import { useCallback, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { ActivityIndicator, Platform, @@ -23,6 +24,13 @@ export default function SessionsList() { const isActive = (s: Session) => !s.endDate && currentMainStart != null && s.startDate >= currentMainStart; + const { markInteractive } = useObserve(); + useEffect(() => { + setTimeout(() => { + markInteractive(); + }, 100); + }, []); + const refresh = useCallback(async () => { const result = await AppMetrics.getAllSessions(); const sorted = [...result].sort((a, b) => (a.startDate < b.startDate ? 1 : -1)); diff --git a/apps/observe-tester/app/(tabs)/debug/index.tsx b/apps/observe-tester/app/(tabs)/debug/index.tsx index e69ceb01b79fab..55f3179bdb25f1 100644 --- a/apps/observe-tester/app/(tabs)/debug/index.tsx +++ b/apps/observe-tester/app/(tabs)/debug/index.tsx @@ -1,4 +1,5 @@ import AppMetrics from 'expo-app-metrics'; +import { useObserve } from 'expo-observe'; import { useEffect, useState } from 'react'; import { ScrollView, StyleSheet } from 'react-native'; @@ -7,20 +8,19 @@ import { CrashReportsSection } from '@/components/CrashReportsSection'; import { Divider } from '@/components/Divider'; import { JSAnimation } from '@/components/JSAnimation'; import { LogEventsSection } from '@/components/LogEventsSection'; -import { useRouterMetricsHelpers } from '@/router-metrics-integration'; import { useTheme } from '@/utils/theme'; export default function Debug() { const theme = useTheme(); const [showAnimation, setShowAnimation] = useState(false); - const { markPageInteractive } = useRouterMetricsHelpers(); + const { markInteractive } = useObserve(); useEffect(() => { setTimeout(() => { - markPageInteractive(); + markInteractive(); }, 1000); - }, [markPageInteractive]); + }, []); return ( + - + ); } diff --git a/apps/observe-tester/components/SessionHeader.tsx b/apps/observe-tester/components/SessionHeader.tsx index 5b805f475b3c01..0e0dc7d9d0f90a 100644 --- a/apps/observe-tester/components/SessionHeader.tsx +++ b/apps/observe-tester/components/SessionHeader.tsx @@ -12,6 +12,7 @@ export function SessionHeader({ session }: { session: Session }) { ? formatDuration(endDate.getTime() - startDate.getTime()) : 'still running'; const crashed = session.type === 'main' && !!session.crashReport; + const routeName = session.metrics.find((m) => m.name === 'timeToInteractive')?.routeName; return ( @@ -32,6 +33,7 @@ export function SessionHeader({ session }: { session: Session }) { + {routeName ? : null} (); - -// This happens on the first render of the screen -// This can be either when the screen will be focused, but also when the screen is pre-rendered in the background -function handlePageInitialRender({ screenId, pathname }: EventPayload) { - const renderTime = Date.now(); - activeScreens.set(screenId, { renderTime, pathname }); - const sessionId = AppMetrics.startSession( - JSON.stringify({ - pathname, - }) - ); - const screenData = activeScreens.get(screenId); - if (screenData) { - screenData.sessionId = sessionId; - activeScreens.set(screenId, screenData); - } -} - -// Screen becomes visible to the user -// Can happen multiple times per screen lifecycle -function handlePageFocused(event: EventPayload) { - const focusTime = Date.now(); - const screenData = activeScreens.get(event.screenId); - if (screenData) { - screenData.focusTime = focusTime; - activeScreens.set(event.screenId, screenData); - } -} - -// Screen is no longer visible to the user -// Can happen multiple times per screen lifecycle -function handlePageBlurred(event: EventPayload) { - const blurTime = Date.now(); - const screenData = activeScreens.get(event.screenId); - if (screenData && screenData.focusTime) { - const durationMs = blurTime - screenData.focusTime; - if (screenData.sessionId) { - AppMetrics.addCustomMetricToSession(screenData.sessionId, { - category: 'routerMetrics', - name: 'timeOnScreen', - value: durationMs / 1000, - routeName: screenData.pathname, - }); - } - } -} - -// Screen is removed from the navigation stack -function handlePageRemoved(event: EventPayload) { - const screenData = activeScreens.get(event.screenId); - if (screenData) { - activeScreens.delete(event.screenId); - } -} - -let isEnabled = false; - -export function startLoggingRouterMetrics() { - if (isEnabled) { - return; - } - isEnabled = true; - unstable_navigationEvents.addListener('pageWillRender', handlePageInitialRender); - unstable_navigationEvents.addListener('pageFocused', handlePageFocused); - unstable_navigationEvents.addListener('pageBlurred', handlePageBlurred); - unstable_navigationEvents.addListener('pageRemoved', handlePageRemoved); -} - -export function useRouterMetricsHelpers() { - const route = useContext(NavigationRouteContext); - const screenId = route?.key; - const prevScreenId = useRef(screenId); - if (prevScreenId.current !== screenId) { - console.warn('[expo-app-metrics] Screen ID changed between renders. This should not happen.'); - } - const markPageInteractive = useCallback(async () => { - if (!isEnabled) { - return; - } - if (!screenId) { - console.warn( - 'No metadata available for the current screen. Make sure to call this hook inside a screen component.' - ); - return; - } - const screenData = activeScreens.get(screenId); - if (screenData && screenData.sessionId && screenData.renderTime) { - const interactiveTimeMs = Date.now() - screenData.renderTime; - const routeName = screenData.pathname; - const metrics = [ - { - category: 'routerMetrics', - name: 'timeToInteractive', - value: interactiveTimeMs / 1000, - routeName, - }, - ]; - if (screenData.focusTime) { - const noninteractiveTimeMs = Date.now() - screenData.focusTime; - metrics.push({ - category: 'routerMetrics', - name: 'noninteractiveTime', - value: noninteractiveTimeMs / 1000, - routeName, - }); - } - await Promise.all( - metrics.map((metric) => - AppMetrics.addCustomMetricToSession(screenData.sessionId!, { - category: metric.category, - name: metric.name, - value: metric.value, - routeName: metric.routeName, - }) - ) - ); - } - }, [screenId]); - - return useMemo(() => ({ markPageInteractive }), [markPageInteractive]); -} diff --git a/docs/components/plugins/api/APISectionTypes.tsx b/docs/components/plugins/api/APISectionTypes.tsx index a7a83513660a5b..ecb685b51ca922 100644 --- a/docs/components/plugins/api/APISectionTypes.tsx +++ b/docs/components/plugins/api/APISectionTypes.tsx @@ -178,7 +178,7 @@ const renderType = ( const defaultValueElement = defaultValue ? ( Default: - {defaultValue} + {defaultValue} ) : undefined; diff --git a/docs/components/plugins/api/APISectionUtils.tsx b/docs/components/plugins/api/APISectionUtils.tsx index 50bddbac5b7583..461d3fa467e8b7 100644 --- a/docs/components/plugins/api/APISectionUtils.tsx +++ b/docs/components/plugins/api/APISectionUtils.tsx @@ -425,7 +425,7 @@ export const renderDefaultValue = (defaultValue?: string) => defaultValue && defaultValue !== '...' ? (
Default: - {defaultValue} + {defaultValue}
) : undefined; diff --git a/docs/pages/eas/workflows/pre-packaged-jobs.mdx b/docs/pages/eas/workflows/pre-packaged-jobs.mdx index 21846482a4b707..0ba9a811b003dc 100644 --- a/docs/pages/eas/workflows/pre-packaged-jobs.mdx +++ b/docs/pages/eas/workflows/pre-packaged-jobs.mdx @@ -855,7 +855,8 @@ jobs: build_id: string # required flow_path: string | string[] # required shards: number # optional - defaults to 1 - retries: number # optional - defaults to 1 + retries: number # optional - defaults to 0 + smart_retry: boolean # optional - defaults to true record_screen: boolean # optional - defaults to false include_tags: string | string[] # optional exclude_tags: string | string[] # optional @@ -874,7 +875,8 @@ You can pass the following parameters into the `params` list: | build_id | string | Required. The ID of the build to test. | | flow_path | string or string[] | Required. The path to the Maestro flow file(s) or directory to run. | | shards | number | Optional and experimental. The number of shards to split the tests into. Defaults to 1. | -| retries | number | Optional. The number of times to retry failed tests. Defaults to 1. | +| retries | number | Optional. The number of times to retry the tests if they fail. Defaults to 0. | +| smart_retry | boolean | Optional. When true (default), retries will attempt to re-run only the flows that failed on the previous attempt when applicable. Set to false to re-run all flows on every retry. | | record_screen | boolean | Optional. Whether to record the screen. Defaults to false. Note: recording screen may impact emulator performance. You may want to use large runners when recording screen. | | include_tags | string or string[] | Optional. Flow tags to include in the tests. Will be passed to Maestro as `--include-tags`. | | exclude_tags | string or string[] | Optional. Flow tags to exclude from the tests. Will be passed to Maestro as `--exclude-tags`. | diff --git a/docs/pages/eas/workflows/syntax.mdx b/docs/pages/eas/workflows/syntax.mdx index 5239c019b96e83..88a330a7f16a40 100644 --- a/docs/pages/eas/workflows/syntax.mdx +++ b/docs/pages/eas/workflows/syntax.mdx @@ -1239,7 +1239,8 @@ jobs: build_id: string # required flow_path: string | string[] # required shards: number # optional, defaults to 1 - retries: number # optional, defaults to 1 + retries: number # optional, defaults to 0 + smart_retry: boolean # optional, defaults to true. When true, retries will attempt to re-run only the flows that failed on the previous attempt when applicable. record_screen: boolean # optional, defaults to false. If true, uploads a screen recording of the tests. include_tags: string | string[] # optional. Tags to include in the tests. Will be passed to Maestro as `--include-tags`. exclude_tags: string | string[] # optional. Tags to exclude from the tests. Will be passed to Maestro as `--exclude-tags`. diff --git a/docs/pages/guides/using-clerk.mdx b/docs/pages/guides/using-clerk.mdx index 1621dd2d99d257..85de508720ffb4 100644 --- a/docs/pages/guides/using-clerk.mdx +++ b/docs/pages/guides/using-clerk.mdx @@ -9,9 +9,11 @@ import { YesIcon, NoIcon } from '~/ui/components/DocIcons'; import { Prerequisites, Requirement } from '~/ui/components/Prerequisites'; import { Terminal } from '~/ui/components/Snippet'; import { Step } from '~/ui/components/Step'; -import { Tabs, Tab } from '~/ui/components/Tabs'; +import { TabsGroup, Tabs, Tab } from '~/ui/components/Tabs'; import { BookOpen02Icon } from '@expo/styleguide-icons/outline/BookOpen02Icon'; + + [Clerk](https://clerk.com/expo-authentication) is an authentication and user management platform that provides sign-up, sign-in, multi-factor authentication, social sign-in, organizations, and a hosted user database. The [`@clerk/expo`](https://www.npmjs.com/package/@clerk/expo) SDK gives you React hooks, control components, and prebuilt native UI components that render with Jetpack Compose on Android and SwiftUI on iOS. This guide shows you how to install `@clerk/expo`, wrap your app in ``, and choose the integration approach that fits your project. It targets `@clerk/expo` Core 3 (the 3.x release line), which supports Expo SDK 53, 54, and 55. @@ -137,7 +139,7 @@ In Core 3, `publishableKey` is required on `` for Expo apps. Envi The next step depends on which approach you chose. The tabs below show the minimum code for each. - + @@ -444,3 +446,5 @@ For the JavaScript-only approach, run the following command and open the project href="https://clerk.com/docs/guides/development/deployment/expo" Icon={BookOpen02Icon} /> + + diff --git a/docs/pages/router/migrate/from-react-navigation.mdx b/docs/pages/router/migrate/from-react-navigation.mdx index c3f8623f19a397..1d1aa905cc5f56 100644 --- a/docs/pages/router/migrate/from-react-navigation.mdx +++ b/docs/pages/router/migrate/from-react-navigation.mdx @@ -6,7 +6,9 @@ description: Learn how to migrate a project using React Navigation to Expo Route import { Collapsible } from '~/ui/components/Collapsible'; import { FileTree } from '~/ui/components/FileTree'; -import { Terminal, DiffBlock } from '~/ui/components/Snippet'; +import { DiffBlock } from '~/ui/components/Snippet'; + +> **info** This guide targets **SDK 56 and later**. In SDK 56, Expo Router stopped accepting application-code imports from `@react-navigation/*. They now come from `expo-router/\*` entry points. If you are upgrading an existing Expo Router app from SDK 55 or earlier, follow the [SDK 55 to 56 migration guide](/router/migrate/sdk-55-to-56). The samples below already use the SDK 56 and later import paths. ## Pitch @@ -334,7 +336,7 @@ The [`fallback`](https://reactnavigation.org/docs/navigation-container/#fallback In React Navigation, you set the theme for the entire app using the [``](https://reactnavigation.org/docs/navigation-container/#theme) component. Expo Router manages the root container for you, so instead you should set the theme using the `ThemeProvider` directly. ```tsx src/app/_layout.tsx -import { ThemeProvider, DarkTheme, DefaultTheme, useTheme } from '@react-navigation/native'; +import { ThemeProvider, DarkTheme, DefaultTheme, useTheme } from 'expo-router/react-navigation'; import { Slot } from 'expo-router'; export default function RootLayout() { @@ -348,7 +350,7 @@ export default function RootLayout() { } ``` -You can use this technique at any layer of the app to set the theme for a specific layout. The current theme can be accessed with the `useTheme` hook from `@react-navigation/native`. +You can use this technique at any layer of the app to set the theme for a specific layout. The current theme can be accessed with the `useTheme` hook from `expo-router/react-navigation`. #### `children` @@ -396,7 +398,7 @@ Custom layouts have an internal context that is ignored when using the ` {/* prettier-ignore */} ```jsx import { View } from 'react-native'; -import { TabRouter } from '@react-navigation/native'; +import { TabRouter } from 'expo-router/react-navigation'; import { Navigator, usePathname, Slot, Link } from 'expo-router'; @@ -457,12 +459,12 @@ In React Navigation, you can use the `initialRouteName` property of the linking You can use the [`reset`](https://reactnavigation.org/docs/navigation-actions/#reset) action from the React Navigation library to reset the navigation state. It is dispatched using the [`useNavigation`](/versions/latest/sdk/router/#usenavigation) hook from Expo Router to access the `navigation` prop. -In the below example, the `navigation` prop is accessible from the `useNavigation` hook and the `CommonActions.reset` action from `@react-navigation/native`. The object specified in the `reset` action replaces the existing navigation state with the new one. +In the below example, the `navigation` prop is accessible from the `useNavigation` hook and the `CommonActions.reset` action from `expo-router/react-navigation`. The object specified in the `reset` action replaces the existing navigation state with the new one. {/* prettier-ignore */} ```tsx src/app/screen.tsx import { useNavigation } from 'expo-router' -import { CommonActions } from '@react-navigation/native' +import { CommonActions } from 'expo-router/react-navigation' export default function Screen() { const navigation = useNavigation(); @@ -494,7 +496,7 @@ React Navigation navigators ``, ``, and `` use a shared app ```tsx src/app/_layout.tsx /* @info Import theme APIs from React Navigation directly. */ -import { ThemeProvider, DarkTheme, DefaultTheme, useTheme } from '@react-navigation/native'; +import { ThemeProvider, DarkTheme, DefaultTheme, useTheme } from 'expo-router/react-navigation'; /* @end */ import { Slot } from 'expo-router'; @@ -509,14 +511,16 @@ export default function RootLayout() { } ``` -You can use this technique at any layer of the app to set the theme for a specific layout. The current theme can be accessed via `useTheme` hook from `@react-navigation/native`. +You can use this technique at any layer of the app to set the theme for a specific layout. The current theme can be accessed via `useTheme` hook from `expo-router/react-navigation`. ### React Navigation Elements -The [`@react-navigation/elements`](https://reactnavigation.org/docs/elements/) library provides a set of UI elements and helpers that can be used to build a navigation UI. These components are designed to be composable and customizable. You can reuse the default functionality from the library or build your navigator's UI on top of it. +The [React Navigation Elements](https://reactnavigation.org/docs/elements/) library provides a set of UI elements and helpers that can be used to build a navigation UI. These components are designed to be composable and customizable. You can reuse the default functionality from the library or build your navigator's UI on top of it. -To use it with Expo Router, you need to install the library: +In SDK 56 and later, this library is re-exported from `expo-router/react-navigation` and there is no separate package to install: - +```tsx +import { Header, HeaderBackButton } from 'expo-router/react-navigation'; +``` To learn more about the components and utilities the library provides, see [Elements library](https://reactnavigation.org/docs/elements/) documentation. diff --git a/docs/pages/skills.mdx b/docs/pages/skills.mdx index f076a45b7ca640..b432005b3b0648 100644 --- a/docs/pages/skills.mdx +++ b/docs/pages/skills.mdx @@ -29,20 +29,30 @@ Run the following commands to add and install Expo Skills from the plugin market -Follow the steps below in your Cursor app to add Expo Skills as a remote rule: - {/* vale off */} -- Open **Settings** > **Rules & Command** > **Project Rules** > **Add Rule** and select **Remote Rule**. -- Enter the following URL of the remote rule and click **Add**. +If you have already installed Expo Skills for Claude Code, Codex, or another agent, recent versions of Cursor import them automatically. Open **Settings** > **Rules, Skills, Subagents**, make sure **Include third-party Plugins, Skills, and other configs** is enabled (it is on by default), and the Expo Skills appear in the **Skills** list. {/* vale on */} -```text Remote URL -https://github.com/expo/skills.git -``` +If you have not installed Expo Skills yet, run one of the following with the [skills CLI](https://skills.sh/docs/cli): + + + +{/* vale off */} + +Then reopen Cursor and verify the skills appear under **Settings** > **Rules, Skills, Subagents** > **Skills**. + +{/* vale on */} -> **important** **Note:** Skills in Cursor are not shown in the slash command (`/`) menu. They work via auto-discovery when you ask the agent Expo-related questions. +> **important** Skills in Cursor are not shown in the slash command (`/`) menu. They work via auto-discovery when you ask the agent Expo-related questions. diff --git a/docs/pages/versions/unversioned/sdk/router/experimental-stack.mdx b/docs/pages/versions/unversioned/sdk/router/experimental-stack.mdx new file mode 100644 index 00000000000000..93814007635cbb --- /dev/null +++ b/docs/pages/versions/unversioned/sdk/router/experimental-stack.mdx @@ -0,0 +1,120 @@ +--- +title: Router Experimental Stack +sidebar_title: Experimental Stack +description: An opt-in sibling to Stack built on the react-native-screens experimental gamma stack. Available for testing only. +sourceCodeUrl: 'https://github.com/expo/expo/tree/main/packages/expo-router/src/layouts/experimental-stack' +packageName: 'expo-router' +platforms: ['ios', 'android'] +isAlpha: true +--- + +import { BookOpen02Icon } from '@expo/styleguide-icons/outline/BookOpen02Icon'; + +import APISection from '~/components/plugins/APISection'; +import { BoxLink } from '~/ui/components/BoxLink'; +import { Collapsible } from '~/ui/components/Collapsible'; + +> **important** `ExperimentalStack` is an [alpha](/more/release-statuses/#alpha) API available in **Expo SDK 56** and later. It is for testing only — the API and feature set may change before it is ready for production use. + +`ExperimentalStack` is a sibling to [`Stack`](./stack) powered by the new `react-native-screens/experimental` stack. It is opt-in per navigator: replace `` with `` in the specific layout you want to migrate, and keep `` everywhere else. + +We are sharing it early so you can try it in your app and tell us what is missing. The supported option surface is intentionally narrow and will grow over time. + +> See the [Expo Router](./index) reference for more information about the file-based routing library for native and Web apps. + +## Supported features + +Supported screen options: + +- `title` +- `headerShown` +- `headerTransparent` +- `headerBackVisible` + +On Android, `ExperimentalStack` ships with [predictive back gesture](https://developer.android.com/guide/navigation/custom-back/predictive-back-gesture) support. You still need to enable it for your app by setting [`android.predictiveBackGestureEnabled`](/versions/latest/config/app/#predictivebackgestureenabled) to `true` in your [app config](/workflow/configuration/). + +## Platform support + +`ExperimentalStack` is native-only. On Web, it falls back to the standard `Stack`, so the same layout works across platforms without conditional code. + +## Basic usage + +```tsx app/_layout.tsx +import { ExperimentalStack as Stack } from 'expo-router'; + +export default function Layout() { + return ( + + + + + ); +} +``` + +You can compose `ExperimentalStack.Screen` and `ExperimentalStack.Protected` the same way you would with `Stack`. + +## Known limitations + + + +`ExperimentalStack` supports only `title`, `headerShown`, `headerTransparent`, and `headerBackVisible`. Passing any other option (for example, `headerLeft`, `headerRight`, `headerTitle`, `headerStyle`, `headerTintColor`, animation overrides, status bar options) logs a development warning and has no effect. Keep using `` for screens that need those options. + + + + + +`ExperimentalStack` does not yet support `presentation: 'modal'` or `transparentModal`. Screens always push onto the stack. + + + + + +`ExperimentalStack` does not yet support `formSheet` or the related sheet sizing/detent options. + + + + + +`ExperimentalStack` does not yet support custom header components or header tinting/styling. Only the four header options listed above take effect. + + + + + +`ExperimentalStack` does not yet honor per-screen animation overrides (`animation`, `animationDuration`) or status bar options. + + + + + +On Android, `ExperimentalStack` and the standard `Stack` cannot coexist in the same app — pick one navigator type for your native stacks. We hope to lift this restriction in a future release so you can migrate one navigator at a time. + + + + + +On Web, `` renders the standard `Stack` from `expo-router`. Native-only options have no effect on Web. + + + +> **info** We are actively developing `ExperimentalStack` and looking for feedback. You can share your thoughts on [Discord](https://chat.expo.dev), [open an issue on GitHub](https://github.com/expo/expo/issues), or use the **Feedback** button at the bottom of this page. + +## Installation + +`ExperimentalStack` ships as part of `expo-router`. Follow the Expo Router installation guide if you do not already have it in your project: + + + +## API + +```js +import { ExperimentalStack } from 'expo-router'; +``` + + diff --git a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/dropdownmenu.mdx b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/dropdownmenu.mdx index 2080ab8693a075..846de113a719a1 100644 --- a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/dropdownmenu.mdx +++ b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/dropdownmenu.mdx @@ -28,7 +28,7 @@ Expo UI DropdownMenu matches the official Jetpack Compose [Menu API](https://dev ### Basic dropdown menu ```tsx BasicDropdownMenuExample.tsx -import { Host, DropdownMenu, Button, Text, Icon } from '@expo/ui/jetpack-compose'; +import { Host, DropdownMenu, DropdownMenuItem, Button, Text, Icon } from '@expo/ui/jetpack-compose'; import { useState } from 'react'; export default function BasicDropdownMenuExample() { @@ -61,6 +61,59 @@ export default function BasicDropdownMenuExample() { } ``` +### React Native components as trigger + +You can use a React Native view (such as `Pressable`) as the dropdown's trigger by wrapping it in [`RNHostView`](rnhostview). + +```tsx RNTriggerDropdownMenuExample.tsx +import { + Host, + DropdownMenu, + DropdownMenuItem, + Text as ComposeText, + RNHostView, +} from '@expo/ui/jetpack-compose'; +import { useState } from 'react'; +import { Pressable, Text } from 'react-native'; + +export default function RNTriggerDropdownMenuExample() { + const [isExpanded, setIsExpanded] = useState(false); + return ( + + setIsExpanded(false)}> + + + setIsExpanded(true)} + style={{ + alignSelf: 'flex-start', + paddingHorizontal: 16, + paddingVertical: 10, + borderRadius: 8, + backgroundColor: '#9B59B6', + }}> + RN Pressable Trigger + + + + + setIsExpanded(false)}> + + Item 1 + + + setIsExpanded(false)}> + + Item 2 + + + + + + ); +} +``` + ## API ```tsx diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/menu.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/menu.mdx index 2da22237d5725c..dfa09bcd995dcd 100644 --- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/menu.mdx +++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/menu.mdx @@ -88,6 +88,41 @@ export default function CustomLabelMenuExample() { } ``` +### React Native components as label + +You can use a React Native view (such as `Pressable`) as the menu's label by wrapping it in [`RNHostView`](rnhostview). + +```tsx RNLabelMenuExample.tsx +import { Host, Menu, Button, RNHostView } from '@expo/ui/swift-ui'; +import { Pressable, Text } from 'react-native'; + +export default function RNLabelMenuExample() { + return ( + + + console.log('RN trigger pressed')} + style={{ + alignSelf: 'flex-start', + paddingHorizontal: 16, + paddingVertical: 10, + borderRadius: 8, + backgroundColor: '#9B59B6', + }}> + RN Pressable Trigger + + + }> + + + ); +} +``` + ### Nested menu Menus can be nested to create submenus. diff --git a/docs/pages/versions/v56.0.0/sdk/router/experimental-stack.mdx b/docs/pages/versions/v56.0.0/sdk/router/experimental-stack.mdx new file mode 100644 index 00000000000000..93814007635cbb --- /dev/null +++ b/docs/pages/versions/v56.0.0/sdk/router/experimental-stack.mdx @@ -0,0 +1,120 @@ +--- +title: Router Experimental Stack +sidebar_title: Experimental Stack +description: An opt-in sibling to Stack built on the react-native-screens experimental gamma stack. Available for testing only. +sourceCodeUrl: 'https://github.com/expo/expo/tree/main/packages/expo-router/src/layouts/experimental-stack' +packageName: 'expo-router' +platforms: ['ios', 'android'] +isAlpha: true +--- + +import { BookOpen02Icon } from '@expo/styleguide-icons/outline/BookOpen02Icon'; + +import APISection from '~/components/plugins/APISection'; +import { BoxLink } from '~/ui/components/BoxLink'; +import { Collapsible } from '~/ui/components/Collapsible'; + +> **important** `ExperimentalStack` is an [alpha](/more/release-statuses/#alpha) API available in **Expo SDK 56** and later. It is for testing only — the API and feature set may change before it is ready for production use. + +`ExperimentalStack` is a sibling to [`Stack`](./stack) powered by the new `react-native-screens/experimental` stack. It is opt-in per navigator: replace `` with `` in the specific layout you want to migrate, and keep `` everywhere else. + +We are sharing it early so you can try it in your app and tell us what is missing. The supported option surface is intentionally narrow and will grow over time. + +> See the [Expo Router](./index) reference for more information about the file-based routing library for native and Web apps. + +## Supported features + +Supported screen options: + +- `title` +- `headerShown` +- `headerTransparent` +- `headerBackVisible` + +On Android, `ExperimentalStack` ships with [predictive back gesture](https://developer.android.com/guide/navigation/custom-back/predictive-back-gesture) support. You still need to enable it for your app by setting [`android.predictiveBackGestureEnabled`](/versions/latest/config/app/#predictivebackgestureenabled) to `true` in your [app config](/workflow/configuration/). + +## Platform support + +`ExperimentalStack` is native-only. On Web, it falls back to the standard `Stack`, so the same layout works across platforms without conditional code. + +## Basic usage + +```tsx app/_layout.tsx +import { ExperimentalStack as Stack } from 'expo-router'; + +export default function Layout() { + return ( + + + + + ); +} +``` + +You can compose `ExperimentalStack.Screen` and `ExperimentalStack.Protected` the same way you would with `Stack`. + +## Known limitations + + + +`ExperimentalStack` supports only `title`, `headerShown`, `headerTransparent`, and `headerBackVisible`. Passing any other option (for example, `headerLeft`, `headerRight`, `headerTitle`, `headerStyle`, `headerTintColor`, animation overrides, status bar options) logs a development warning and has no effect. Keep using `` for screens that need those options. + + + + + +`ExperimentalStack` does not yet support `presentation: 'modal'` or `transparentModal`. Screens always push onto the stack. + + + + + +`ExperimentalStack` does not yet support `formSheet` or the related sheet sizing/detent options. + + + + + +`ExperimentalStack` does not yet support custom header components or header tinting/styling. Only the four header options listed above take effect. + + + + + +`ExperimentalStack` does not yet honor per-screen animation overrides (`animation`, `animationDuration`) or status bar options. + + + + + +On Android, `ExperimentalStack` and the standard `Stack` cannot coexist in the same app — pick one navigator type for your native stacks. We hope to lift this restriction in a future release so you can migrate one navigator at a time. + + + + + +On Web, `` renders the standard `Stack` from `expo-router`. Native-only options have no effect on Web. + + + +> **info** We are actively developing `ExperimentalStack` and looking for feedback. You can share your thoughts on [Discord](https://chat.expo.dev), [open an issue on GitHub](https://github.com/expo/expo/issues), or use the **Feedback** button at the bottom of this page. + +## Installation + +`ExperimentalStack` ships as part of `expo-router`. Follow the Expo Router installation guide if you do not already have it in your project: + + + +## API + +```js +import { ExperimentalStack } from 'expo-router'; +``` + + diff --git a/docs/pages/versions/v56.0.0/sdk/ui/jetpack-compose/dropdownmenu.mdx b/docs/pages/versions/v56.0.0/sdk/ui/jetpack-compose/dropdownmenu.mdx index 41be945ee93d1e..7cda68d6be3540 100644 --- a/docs/pages/versions/v56.0.0/sdk/ui/jetpack-compose/dropdownmenu.mdx +++ b/docs/pages/versions/v56.0.0/sdk/ui/jetpack-compose/dropdownmenu.mdx @@ -28,7 +28,7 @@ Expo UI DropdownMenu matches the official Jetpack Compose [Menu API](https://dev ### Basic dropdown menu ```tsx BasicDropdownMenuExample.tsx -import { Host, DropdownMenu, Button, Text, Icon } from '@expo/ui/jetpack-compose'; +import { Host, DropdownMenu, DropdownMenuItem, Button, Text, Icon } from '@expo/ui/jetpack-compose'; import { useState } from 'react'; export default function BasicDropdownMenuExample() { @@ -61,6 +61,59 @@ export default function BasicDropdownMenuExample() { } ``` +### React Native components as trigger + +You can use a React Native view (such as `Pressable`) as the dropdown's trigger by wrapping it in [`RNHostView`](rnhostview). + +```tsx RNTriggerDropdownMenuExample.tsx +import { + Host, + DropdownMenu, + DropdownMenuItem, + Text as ComposeText, + RNHostView, +} from '@expo/ui/jetpack-compose'; +import { useState } from 'react'; +import { Pressable, Text } from 'react-native'; + +export default function RNTriggerDropdownMenuExample() { + const [isExpanded, setIsExpanded] = useState(false); + return ( + + setIsExpanded(false)}> + + + setIsExpanded(true)} + style={{ + alignSelf: 'flex-start', + paddingHorizontal: 16, + paddingVertical: 10, + borderRadius: 8, + backgroundColor: '#9B59B6', + }}> + RN Pressable Trigger + + + + + setIsExpanded(false)}> + + Item 1 + + + setIsExpanded(false)}> + + Item 2 + + + + + + ); +} +``` + ## API ```tsx diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/menu.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/menu.mdx index e679d234f09a91..7ffe7b54bdf365 100644 --- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/menu.mdx +++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/menu.mdx @@ -88,6 +88,41 @@ export default function CustomLabelMenuExample() { } ``` +### React Native components as label + +You can use a React Native view (such as `Pressable`) as the menu's label by wrapping it in [`RNHostView`](rnhostview). + +```tsx RNLabelMenuExample.tsx +import { Host, Menu, Button, RNHostView } from '@expo/ui/swift-ui'; +import { Pressable, Text } from 'react-native'; + +export default function RNLabelMenuExample() { + return ( + + + console.log('RN trigger pressed')} + style={{ + alignSelf: 'flex-start', + paddingHorizontal: 16, + paddingVertical: 10, + borderRadius: 8, + backgroundColor: '#9B59B6', + }}> + RN Pressable Trigger + + + }> + + + ); +} +``` + ### Nested menu Menus can be nested to create submenus. diff --git a/docs/public/static/data/unversioned/expo-router/experimental-stack.json b/docs/public/static/data/unversioned/expo-router/experimental-stack.json new file mode 100644 index 00000000000000..0861a4d210ed1b --- /dev/null +++ b/docs/public/static/data/unversioned/expo-router/experimental-stack.json @@ -0,0 +1 @@ +{"schemaVersion":"2.0","name":"expo-router/experimental-stack","variant":"project","kind":1,"children":[{"name":"ExperimentalStackNavigationEventMap","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Navigator-level events emitted by "},{"kind":"code","text":"`ExperimentalStack`"},{"kind":"text","text":". Mirrors the subset of\n"},{"kind":"code","text":"`NativeStackNavigationEventMap`"},{"kind":"text","text":" that the gamma "},{"kind":"code","text":"`Stack.Screen`"},{"kind":"text","text":" lifecycle\ncallbacks can drive."}],"modifierTags":["@experimental"]},"children":[{"name":"gestureCancel","variant":"declaration","kind":1024,"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"children":[{"name":"data","variant":"declaration","kind":1024,"type":{"type":"intrinsic","name":"undefined"}}]}}},{"name":"transitionEnd","variant":"declaration","kind":1024,"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"children":[{"name":"data","variant":"declaration","kind":1024,"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"children":[{"name":"closing","variant":"declaration","kind":1024,"type":{"type":"intrinsic","name":"boolean"}}]}}}]}}},{"name":"transitionStart","variant":"declaration","kind":1024,"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"children":[{"name":"data","variant":"declaration","kind":1024,"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"children":[{"name":"closing","variant":"declaration","kind":1024,"type":{"type":"intrinsic","name":"boolean"}}]}}}]}}}]},{"name":"ExperimentalStackNavigationHelpers","variant":"declaration","kind":2097152,"type":{"type":"reference","target":{"packageName":"expo-router","packagePath":"src/react-navigation/core/types.tsx","qualifiedName":"NavigationHelpers"},"typeArguments":[{"type":"reference","target":{"packageName":"expo-router","packagePath":"src/react-navigation/routers/types.tsx","qualifiedName":"ParamListBase"},"name":"ParamListBase","package":"expo-router"},{"type":"reference","name":"ExperimentalStackNavigationEventMap","package":"expo-router"}],"name":"NavigationHelpers","package":"expo-router"}},{"name":"ExperimentalStackNavigationOptions","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Options accepted by "},{"kind":"code","text":"`ExperimentalStack`"},{"kind":"text","text":" screens. Mirrors the narrow option\nsurface of the gamma "},{"kind":"code","text":"``"},{"kind":"text","text":" component from\n"},{"kind":"code","text":"`react-native-screens/experimental`"},{"kind":"text","text":". Anything outside this shape is dropped\nwith a "},{"kind":"code","text":"`__DEV__`"},{"kind":"text","text":" warning at runtime."}],"modifierTags":["@experimental"]},"children":[{"name":"headerBackVisible","variant":"declaration","kind":1024,"flags":{"isOptional":true},"type":{"type":"intrinsic","name":"boolean"}},{"name":"headerShown","variant":"declaration","kind":1024,"flags":{"isOptional":true},"type":{"type":"intrinsic","name":"boolean"}},{"name":"headerTransparent","variant":"declaration","kind":1024,"flags":{"isOptional":true},"type":{"type":"intrinsic","name":"boolean"}},{"name":"title","variant":"declaration","kind":1024,"flags":{"isOptional":true},"type":{"type":"intrinsic","name":"string"}}]},{"name":"ExperimentalStackNavigationProp","variant":"declaration","kind":2097152,"typeParameters":[{"name":"ParamList","variant":"typeParam","kind":131072,"type":{"type":"reference","target":{"packageName":"expo-router","packagePath":"src/react-navigation/routers/types.tsx","qualifiedName":"ParamListBase"},"name":"ParamListBase","package":"expo-router"}},{"name":"RouteName","variant":"typeParam","kind":131072,"type":{"type":"typeOperator","operator":"keyof","target":{"type":"reference","name":"ParamList","package":"expo-router","refersToTypeParameter":true}},"default":{"type":"intrinsic","name":"string"}},{"name":"NavigatorID","variant":"typeParam","kind":131072,"type":{"type":"union","types":[{"type":"intrinsic","name":"string"},{"type":"intrinsic","name":"undefined"}]},"default":{"type":"intrinsic","name":"undefined"}}],"type":{"type":"intersection","types":[{"type":"reference","target":{"packageName":"expo-router","packagePath":"src/react-navigation/core/types.tsx","qualifiedName":"NavigationProp"},"typeArguments":[{"type":"reference","name":"ParamList","package":"expo-router","refersToTypeParameter":true},{"type":"reference","name":"RouteName","package":"expo-router","refersToTypeParameter":true},{"type":"reference","name":"NavigatorID","package":"expo-router","refersToTypeParameter":true},{"type":"reference","target":{"packageName":"expo-router","packagePath":"src/react-navigation/routers/StackRouter.tsx","qualifiedName":"StackNavigationState"},"typeArguments":[{"type":"reference","name":"ParamList","package":"expo-router","refersToTypeParameter":true}],"name":"StackNavigationState","package":"expo-router"},{"type":"reference","name":"ExperimentalStackNavigationOptions","package":"expo-router"},{"type":"reference","name":"ExperimentalStackNavigationEventMap","package":"expo-router"}],"name":"NavigationProp","package":"expo-router"},{"type":"reference","target":{"packageName":"expo-router","packagePath":"src/react-navigation/routers/StackRouter.tsx","qualifiedName":"StackActionHelpers"},"typeArguments":[{"type":"reference","name":"ParamList","package":"expo-router","refersToTypeParameter":true}],"name":"StackActionHelpers","package":"expo-router"}]}},{"name":"ExperimentalStackScreenProps","variant":"declaration","kind":2097152,"children":[{"name":"navigation","variant":"declaration","kind":1024,"type":{"type":"reference","typeArguments":[{"type":"reference","name":"ParamList","package":"expo-router","refersToTypeParameter":true},{"type":"reference","name":"RouteName","package":"expo-router","refersToTypeParameter":true},{"type":"reference","name":"NavigatorID","package":"expo-router","refersToTypeParameter":true}],"name":"ExperimentalStackNavigationProp","package":"expo-router"}},{"name":"route","variant":"declaration","kind":1024,"type":{"type":"reference","target":{"packageName":"expo-router","packagePath":"src/react-navigation/core/types.tsx","qualifiedName":"RouteProp"},"typeArguments":[{"type":"reference","name":"ParamList","package":"expo-router","refersToTypeParameter":true},{"type":"reference","name":"RouteName","package":"expo-router","refersToTypeParameter":true}],"name":"RouteProp","package":"expo-router"}}],"typeParameters":[{"name":"ParamList","variant":"typeParam","kind":131072,"type":{"type":"reference","target":{"packageName":"expo-router","packagePath":"src/react-navigation/routers/types.tsx","qualifiedName":"ParamListBase"},"name":"ParamListBase","package":"expo-router"}},{"name":"RouteName","variant":"typeParam","kind":131072,"type":{"type":"typeOperator","operator":"keyof","target":{"type":"reference","name":"ParamList","package":"expo-router","refersToTypeParameter":true}},"default":{"type":"intrinsic","name":"string"}},{"name":"NavigatorID","variant":"typeParam","kind":131072,"type":{"type":"union","types":[{"type":"intrinsic","name":"string"},{"type":"intrinsic","name":"undefined"}]},"default":{"type":"intrinsic","name":"undefined"}}]},{"name":"ExperimentalStack","variant":"declaration","kind":32,"flags":{"isConst":true},"comment":{"summary":[{"kind":"text","text":"Renders the new "},{"kind":"code","text":"`react-native-screens/experimental`"},{"kind":"text","text":" native stack.\n\nSibling to "},{"kind":"code","text":"`Stack`"},{"kind":"text","text":". Native-only — on web it falls back to the standard "},{"kind":"code","text":"`Stack`"},{"kind":"text","text":".\nOpt-in per navigator: replace "},{"kind":"code","text":"``"},{"kind":"text","text":" with "},{"kind":"code","text":"``"},{"kind":"text","text":" in the\nspecific layout you want to migrate."}],"modifierTags":["@experimental"]},"type":{"type":"intersection","types":[{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"parameters":[{"name":"props","variant":"param","kind":32768,"type":{"type":"intersection","types":[{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Omit"},"typeArguments":[{"type":"intersection","types":[{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Omit"},"typeArguments":[{"type":"reference","target":{"packageName":"expo-router","packagePath":"src/layouts/experimental-stack/types.ts","qualifiedName":"ExperimentalStackNavigatorProps"},"name":"ExperimentalStackNavigatorProps","package":"expo-router"},{"type":"union","types":[{"type":"literal","value":"children"},{"type":"literal","value":"initialRouteName"},{"type":"literal","value":"layout"},{"type":"literal","value":"screenListeners"},{"type":"literal","value":"screenOptions"},{"type":"literal","value":"screenLayout"},{"type":"literal","value":"UNSTABLE_router"},{"type":"literal","value":"UNSTABLE_routeNamesChangeBehavior"},{"type":"literal","value":"id"}]}],"name":"Omit","package":"typescript"},{"type":"reference","target":{"packageName":"expo-router","packagePath":"src/react-navigation/routers/types.tsx","qualifiedName":"DefaultRouterOptions"},"typeArguments":[{"type":"intrinsic","name":"string"}],"name":"DefaultRouterOptions","package":"expo-router"},{"type":"unknown","name":"{ children: ReactNode; layout?: ((props: { state: StackNavigationState; navigation: NavigationHelpers; descriptors: Record<...>; children: ReactNode; }) => ReactElement<...>) | undefined; ... 4 more ...; UNSTABLE_routeNamesChangeBehavior?: \"firstMatch\" | ... 1 more ... | undefined; ..."}]},{"type":"literal","value":"children"}],"name":"Omit","package":"typescript"},{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Partial"},"typeArguments":[{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Pick"},"typeArguments":[{"type":"intersection","types":[{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Omit"},"typeArguments":[{"type":"reference","target":{"packageName":"expo-router","packagePath":"src/layouts/experimental-stack/types.ts","qualifiedName":"ExperimentalStackNavigatorProps"},"name":"ExperimentalStackNavigatorProps","package":"expo-router"},{"type":"union","types":[{"type":"literal","value":"children"},{"type":"literal","value":"initialRouteName"},{"type":"literal","value":"layout"},{"type":"literal","value":"screenListeners"},{"type":"literal","value":"screenOptions"},{"type":"literal","value":"screenLayout"},{"type":"literal","value":"UNSTABLE_router"},{"type":"literal","value":"UNSTABLE_routeNamesChangeBehavior"},{"type":"literal","value":"id"}]}],"name":"Omit","package":"typescript"},{"type":"reference","target":{"packageName":"expo-router","packagePath":"src/react-navigation/routers/types.tsx","qualifiedName":"DefaultRouterOptions"},"typeArguments":[{"type":"intrinsic","name":"string"}],"name":"DefaultRouterOptions","package":"expo-router"},{"type":"unknown","name":"{ children: ReactNode; layout?: ((props: { state: StackNavigationState; navigation: NavigationHelpers; descriptors: Record<...>; children: ReactNode; }) => ReactElement<...>) | undefined; ... 4 more ...; UNSTABLE_routeNamesChangeBehavior?: \"firstMatch\" | ... 1 more ... | undefined; ..."}]},{"type":"literal","value":"children"}],"name":"Pick","package":"typescript"}],"name":"Partial","package":"typescript"},{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.RefAttributes"},"typeArguments":[{"type":"intrinsic","name":"unknown"}],"name":"RefAttributes","package":"@types/react","qualifiedName":"React.RefAttributes"}]}}],"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"jsx-runtime.d.ts","qualifiedName":"JSX.Element"},"name":"Element","package":"@types/react","qualifiedName":"JSX.Element"}}]}},{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"children":[{"name":"Protected","variant":"declaration","kind":1024,"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.FunctionComponent"},"typeArguments":[{"type":"reference","target":{"packageName":"expo-router","packagePath":"src/views/Protected.tsx","qualifiedName":"ProtectedProps"},"name":"ProtectedProps","package":"expo-router"}],"name":"FunctionComponent","package":"@types/react","qualifiedName":"React.FunctionComponent"}},{"name":"Screen","variant":"declaration","kind":1024,"type":{"type":"intersection","types":[{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"parameters":[{"name":"__namedParameters","variant":"param","kind":32768,"type":{"type":"reference","target":{"packageName":"expo-router","packagePath":"src/layouts/stack-utils/StackScreen.tsx","qualifiedName":"StackScreenProps"},"name":"StackScreenProps","package":"expo-router"}}],"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"jsx-runtime.d.ts","qualifiedName":"JSX.Element"},"name":"Element","package":"@types/react","qualifiedName":"JSX.Element"}}]}},{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"children":[{"name":"BackButton","variant":"declaration","kind":1024,"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Component to configure the back button.\n\nCan be used inside Stack.Screen in a layout or directly inside a screen component."}],"blockTags":[{"tag":"@example","content":[{"kind":"code","text":"```tsx\nimport { Stack } from 'expo-router';\n\nexport default function Layout() {\n return (\n \n \n Back\n \n \n );\n}\n```"}]},{"tag":"@example","content":[{"kind":"code","text":"```tsx\nimport { Stack } from 'expo-router';\n\nexport default function Page() {\n return (\n <>\n