81 lines
2.1 KiB
TypeScript
81 lines
2.1 KiB
TypeScript
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
|
|
}
|