import * as React from "react" import { useOverlayScope, useOverlayScopes } from "./scope-context" import type { OverlayScope } from "./scope" /** A router-owned navigation attempt that is currently blocked. */ export interface OverlayNavigationAttempt { cancel: () => void /** Continues this exact attempt without notifying the adapter again. */ proceed: () => void } /** Implement this bridge with a router's blocker API. */ export interface OverlayNavigationAdapter { subscribe: ( listener: (attempt: OverlayNavigationAttempt) => void ) => VoidFunction } export interface OverlayNavigationContext { attempt: OverlayNavigationAttempt scopes: readonly OverlayScope[] } export type OverlayNavigationInterceptor = ( context: OverlayNavigationContext ) => void export const dismissOverlayNavigationInterceptor: OverlayNavigationInterceptor = ({ attempt, scopes }) => { let topmostScope: OverlayScope | undefined let topmostOrder = -1 for (const scope of scopes) { const order = scope.getTopmostLayer()?.order ?? -1 if (order > topmostOrder) { topmostScope = scope topmostOrder = order } } if (topmostScope?.interceptNavigation()) attempt.cancel() else attempt.proceed() } export interface OverlayNavigationGuardProps { adapter: OverlayNavigationAdapter interceptor?: OverlayNavigationInterceptor scope?: OverlayScope scopes?: readonly OverlayScope[] } export function OverlayNavigationGuard({ adapter, interceptor = dismissOverlayNavigationInterceptor, scope, scopes, }: OverlayNavigationGuardProps) { const contextualScope = useOverlayScope() const namedScopes = useOverlayScopes() const resolvedScopes = React.useMemo( () => Array.from( new Set( scopes ?? [scope ?? contextualScope, ...Object.values(namedScopes)] ) ), [contextualScope, namedScopes, scope, scopes] ) React.useEffect( () => adapter.subscribe((attempt) => { interceptor({ attempt, scopes: resolvedScopes }) }), [adapter, interceptor, resolvedScopes] ) return null }