import { useContainer, useValue } from '@tldraw/editor'
import { Dialog as _Dialog } from 'radix-ui'
import { memo, useCallback } from 'react'
import { TLUiDialog, useDialogs } from '../context/dialogs'
import { useDirection } from '../hooks/useTranslation/useTranslation'
/** @internal */
const TldrawUiDialog = ({ id, component: ModalContent, preventBackgroundClose }: TLUiDialog) => {
const { removeDialog } = useDialogs()
const container = useContainer()
const dir = useDirection()
const handleOpenChange = useCallback(
(isOpen: boolean) => {
if (!isOpen) {
removeDialog(id)
}
},
[id, removeDialog]
)
return (
<_Dialog.Root onOpenChange={handleOpenChange} defaultOpen>
<_Dialog.Portal container={container}>
{/* Radix renders the scrim and content as siblings. The positioner sits on
top of the scrim and centers the content, scrolling and padding it when
it's taller than the viewport. */}
<_Dialog.Overlay dir={dir} className="tlui-dialog__overlay" />
<_Dialog.Content
dir={dir}
className="tlui-dialog__content"
aria-describedby={undefined}
onInteractOutside={(e) => {
// Radix's dismissable layers are layer-aware: an interaction outside the
// topmost layer dismisses only that layer — an open select, or a dialog
// stacked on top — leaving lower layers open. onInteractOutside fires for
// both a pointer press and a focus shift outside the dialog; opt out of both
// when the dialog asks to stay open on background interactions (escape still
// closes it).
if (preventBackgroundClose) {
e.preventDefault()
}
}}
>
handleOpenChange(false)} />
)
}
/** @public @react */
export const DefaultDialogs = memo(function DefaultDialogs() {
const { dialogs } = useDialogs()
const dialogsArray = useValue('dialogs', () => dialogs.get(), [dialogs])
// Every stacked dialog stays modal (Radix's default). That's what keeps dismiss
// layer-aware: a background press or escape closes only the topmost layer, because
// Radix's focus-scope stack pauses lower dialogs rather than dismissing them. Don't
// make a lower dialog non-modal to work around anything — without a focus scope it
// dismisses on the focus shift when a nested dialog or select opens, collapsing the
// whole stack.
return dialogsArray.map((dialog) => )
})