import type { EventMapBase, NavigationState } from '@react-navigation/native'
import React from 'react'
import { useContextKey } from '../Route'
import type { PickPartial } from '../types'
import { useSortedScreens, type ScreenProps } from '../useScreens'
import { Screen } from '../views/Screen'
export function useFilterScreenChildren(
children: React.ReactNode,
{
isCustomNavigator,
contextKey,
}: {
isCustomNavigator?: boolean
/** Used for sending developer hints */
contextKey?: string
} = {}
) {
return React.useMemo(() => {
const customChildren: any[] = []
const screens = React.Children.map(children, (child) => {
if (React.isValidElement(child) && child && child.type === Screen) {
if (!child.props.name) {
throw new Error(
` component in \`default export\` at \`app${contextKey}/_layout\` must have a \`name\` prop when used as a child of a Layout Route.`
)
}
if (process.env.NODE_ENV !== 'production') {
if (['children', 'component', 'getComponent'].some((key) => key in child.props)) {
throw new Error(
` component in \`default export\` at \`app${contextKey}/_layout\` must not have a \`children\`, \`component\`, or \`getComponent\` prop when used as a child of a Layout Route`
)
}
}
return child.props
}
if (isCustomNavigator) {
customChildren.push(child)
} else {
console.warn(
`Layout children must be of type Screen, all other children are ignored. To use custom children, create a custom . Update Layout Route at: "app${contextKey}/_layout"`
)
}
})
// Add an assertion for development
if (process.env.NODE_ENV !== 'production') {
// Assert if names are not unique
const names = screens?.map((screen) => screen.name)
if (names && new Set(names).size !== names.length) {
throw new Error('Screen names must be unique: ' + names)
}
}
return {
screens,
children: customChildren,
}
}, [children, contextKey, isCustomNavigator])
}
/** Return a navigator that automatically injects matched routes and renders nothing when there are no children. Return type with children prop optional */
export function withLayoutContext<
TOptions extends object,
T extends React.ComponentType,
State extends NavigationState,
EventMap extends EventMapBase,
>(
Nav: T,
processor?: (
options: ScreenProps[]
) => ScreenProps[]
): React.ForwardRefExoticComponent<
React.PropsWithoutRef, 'children'>> &
React.RefAttributes
> & {
Screen: (props: ScreenProps) => null
} {
const Navigator = React.forwardRef(
(
{ children: userDefinedChildren, ...props }: PickPartial, 'children'>,
ref
) => {
const contextKey = useContextKey()
const { screens } = useFilterScreenChildren(userDefinedChildren, {
contextKey,
})
const processed = processor ? processor(screens ?? []) : screens
const sorted = useSortedScreens(processed ?? [])
// Prevent throwing an error when there are no screens.
if (!sorted.length) {
return null
}
return (
// @ts-expect-error
// biome-ignore lint/correctness/noChildrenProp:
)
}
)
// @ts-expect-error
Navigator.Screen = Screen
// @ts-expect-error
return Navigator
}