import { createPortal, onCleanup } from "@solidrt/core"
import type { Color, PointerEvent } from "@solidrt/core"
import { theme } from "./theme"
import { pushNavScope } from "./focus-nav"
export interface ModalProps {
// Called when the backdrop (the area around the content) is pressed, unless
// `dismissable` is false.
onClose?: () => void
// The modal content, centered over the backdrop.
children?: any
// Scrim color behind the content. Defaults to the theme scrim; pass
// "transparent" for no dim.
backdropColor?: Color
// Whether pressing the backdrop calls onClose. Defaults to true.
dismissable?: boolean
}
/**
* A centered overlay rendered at the window root via core's createPortal, so it
* escapes the layout and stacking of its surrounding tree. It fills the window
* with a dimming backdrop and centers `children` on top. Control visibility by
* mounting/unmounting it, e.g. ``: the
* portal's onCleanup removes it when the surrounding scope disposes. The
* gating signal must start false: portals cannot mount during the app's
* initial render (see createPortal), so a modal visible at startup throws.
*
* Pressing the backdrop calls `onClose`; pressing the content does not. This
* works because pointer events dispatch to the whole hit path with no
* stopPropagation, so the content is kept a sibling of the backdrop (not a
* child): a press on the content never has the backdrop on its path.
*/
export function Modal(props: ModalProps) {
let dismiss = (_e: PointerEvent) => {
if (props.dismissable !== false) props.onClose?.()
}
// While mounted, the modal is a focus-navigation trap: its container tops
// the nav scope stack, so createFocusNav only reaches controls inside it.
let popNavScope: (() => void) | null = null
onCleanup(() => popNavScope?.())
return createPortal(
{
popNavScope = pushNavScope(n)
}}
position="absolute"
top={0}
left={0}
right={0}
bottom={0}
alignItems="center"
justifyContent="center"
>
{props.children}
)
}