feat(overlays): add managed overlay package
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user