feat(overlays): add managed overlay package

This commit is contained in:
Maofeng
2026-09-28 23:13:44 +08:00
parent bca874fd30
commit 0ec968c344
34 changed files with 2340 additions and 0 deletions
+80
View File
@@ -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
}