feat(overlays): add managed overlay package
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
let nextScopeId = 0
|
||||
let nextLayerOrder = 0
|
||||
|
||||
export interface CreateOverlayScopeOptions {
|
||||
name?: string
|
||||
}
|
||||
|
||||
export interface OverlayLayer {
|
||||
dismiss: () => void
|
||||
dismissible: boolean
|
||||
id: string
|
||||
order: number
|
||||
}
|
||||
|
||||
/** UI-agnostic storage shared by independently imported overlay features. */
|
||||
export class OverlayScope {
|
||||
readonly id: string
|
||||
readonly name?: string
|
||||
|
||||
private readonly channels = new Map<symbol, unknown>()
|
||||
private readonly layers = new Map<string, OverlayLayer>()
|
||||
|
||||
constructor({ name }: CreateOverlayScopeOptions = {}) {
|
||||
this.id = `overlay-scope-${nextScopeId++}`
|
||||
this.name = name
|
||||
}
|
||||
|
||||
/** @internal Used by atomic overlay feature modules. */
|
||||
getChannel<Channel>(key: symbol, create: () => Channel): Channel {
|
||||
const existing = this.channels.get(key)
|
||||
if (existing) return existing as Channel
|
||||
|
||||
const channel = create()
|
||||
this.channels.set(key, channel)
|
||||
return channel
|
||||
}
|
||||
|
||||
/** @internal Registers a visible layer for navigation interception. */
|
||||
registerLayer(layer: Omit<OverlayLayer, "order">) {
|
||||
const registered = { ...layer, order: nextLayerOrder++ }
|
||||
this.layers.set(registered.id, registered)
|
||||
return registered.order
|
||||
}
|
||||
|
||||
/** @internal Removes a layer after it has settled. */
|
||||
unregisterLayer(id: string) {
|
||||
this.layers.delete(id)
|
||||
}
|
||||
|
||||
getTopmostLayer() {
|
||||
let topmost: OverlayLayer | undefined
|
||||
for (const layer of this.layers.values()) {
|
||||
if (!topmost || layer.order > topmost.order) topmost = layer
|
||||
}
|
||||
return topmost
|
||||
}
|
||||
|
||||
/** Cancels navigation while a layer is open and dismisses it when allowed. */
|
||||
interceptNavigation() {
|
||||
const layer = this.getTopmostLayer()
|
||||
if (!layer) return false
|
||||
if (layer.dismissible) layer.dismiss()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
export function createOverlayScope(options?: CreateOverlayScopeOptions) {
|
||||
return new OverlayScope(options)
|
||||
}
|
||||
|
||||
export const globalOverlayScope = createOverlayScope({ name: "global" })
|
||||
Reference in New Issue
Block a user