import { createNavigatorFactory, createScreenFactory, type EventArg, NavigationMetaContext, type NavigatorTypeBagBase, type ParamListBase, type StackActionHelpers, StackActions, type StackNavigationState, StackRouter, type StackRouterOptions, type StaticConfig, type TypedNavigator, useNavigationBuilder, } from '@react-navigation/native'; import * as React from 'react'; import type { NativeStackNavigationEventMap, NativeStackNavigationOptions, NativeStackNavigationProp, NativeStackNavigatorProps, } from '../types'; import { NativeStackView } from '../views/NativeStackView'; function NativeStackNavigator({ id, initialRouteName, UNSTABLE_routeNamesChangeBehavior, children, layout, screenListeners, screenOptions, screenLayout, UNSTABLE_router, ...rest }: NativeStackNavigatorProps) { const { state, describe, descriptors, navigation, render } = useNavigationBuilder< StackNavigationState, StackRouterOptions, StackActionHelpers, NativeStackNavigationOptions, NativeStackNavigationEventMap >(StackRouter, { id, initialRouteName, UNSTABLE_routeNamesChangeBehavior, children, layout, screenListeners, screenOptions, screenLayout, UNSTABLE_router, }); const meta = React.useContext(NavigationMetaContext); React.useEffect(() => { if (meta && 'type' in meta && meta.type === 'native-tabs') { // If we're inside native tabs, we don't need to handle popToTop // It's handled natively by native tabs return; } let handle: ReturnType | undefined; // @ts-expect-error: there may not be a tab navigator in parent const unsubscribe = navigation.addListener?.('tabPress', (e) => { const isFocused = navigation.isFocused(); cancelAnimationFrame(handle); // Run the operation in the next frame so we're sure all listeners have been run // This is necessary to know if preventDefault() has been called handle = requestAnimationFrame(() => { const currentState = navigation.getState(); if ( isFocused && currentState.index > 0 && !(e as EventArg<'tabPress', true>).defaultPrevented ) { // When user taps on already focused tab and we're inside the tab, // reset the stack to replicate native behaviour navigation.dispatch({ ...StackActions.popToTop(), target: currentState.key, }); } }); }); return () => { cancelAnimationFrame(handle); unsubscribe?.(); }; }, [meta, navigation]); return render( ); } export type NativeStackTypeBag< ParamList extends ParamListBase = ParamListBase, NavigatorID extends string | undefined = string | undefined, > = { ParamList: ParamList; NavigatorID: NavigatorID; State: StackNavigationState; ScreenOptions: NativeStackNavigationOptions; EventMap: NativeStackNavigationEventMap; NavigationList: { [RouteName in keyof ParamList]: NativeStackNavigationProp< ParamList, RouteName, NavigatorID >; }; Navigator: typeof NativeStackNavigator; }; export function createNativeStackNavigator< const ParamList extends ParamListBase, const NavigatorID extends string | undefined = string | undefined, const TypeBag extends NavigatorTypeBagBase = NativeStackTypeBag< ParamList, NavigatorID >, const Config extends StaticConfig = StaticConfig, >(config?: Config): TypedNavigator { return createNavigatorFactory(NativeStackNavigator)(config); } export const createNativeStackScreen = createScreenFactory();