Files
simple-react-app-kit/packages/overlays/src/scope-context.tsx
T

71 lines
1.9 KiB
TypeScript
Raw Normal View History

2026-09-28 23:13:44 +08:00
import * as React from "react"
import {
createOverlayScope,
globalOverlayScope,
type OverlayScope,
} from "./scope"
interface OverlayScopeContextValue {
scope: OverlayScope
scopes: Readonly<Record<string, OverlayScope>>
}
const OverlayScopeContext = React.createContext<OverlayScopeContextValue>({
scope: globalOverlayScope,
scopes: { global: globalOverlayScope },
})
OverlayScopeContext.displayName = "OverlayScopeContext"
export interface OverlayScopeProviderProps {
children?: React.ReactNode
/** Creates a local scope with this name when `scope` is omitted. */
name?: string
scope?: OverlayScope
scopes?: Readonly<Record<string, OverlayScope>>
}
/** Provides scope lookup only; feature hosts remain explicit and tree-shakeable. */
export function OverlayScopeProvider({
children,
name,
scope,
scopes,
}: OverlayScopeProviderProps) {
const parent = React.useContext(OverlayScopeContext)
const [generatedScope] = React.useState(() => createOverlayScope({ name }))
const currentScope = scope ?? (name ? generatedScope : parent.scope)
const namedScopes = React.useMemo(() => {
const next = { ...parent.scopes, ...scopes }
if (name) next[name] = currentScope
return next
}, [currentScope, name, parent.scopes, scopes])
const value = React.useMemo(
() => ({ scope: currentScope, scopes: namedScopes }),
[currentScope, namedScopes]
)
return (
<OverlayScopeContext.Provider value={value}>
{children}
</OverlayScopeContext.Provider>
)
}
export function useOverlayScope(name?: string) {
const context = React.useContext(OverlayScopeContext)
if (!name) return context.scope
const scope = context.scopes[name]
if (!scope) {
throw new Error(`No overlay scope named "${name}" exists in this subtree`)
}
return scope
}
export function useOverlayScopes() {
return React.useContext(OverlayScopeContext).scopes
}