'use client'; import * as React from 'react'; import { useRefWithInit } from '../../hooks/useRefWithInit'; import { useTransitionStatus } from '../../internals/useTransitionStatus'; import { usePopupRootHandle } from '../../utils/popups/usePopupRootHandle'; import { useStableCallback } from '../../hooks/useStableCallback'; import { DialogStore } from '../store/DialogStore'; import type { DialogRootProps } from './DialogRoot'; import { DialogNestingContext, useDialogNestingContext } from './DialogNestingContext'; import { DialogRootContext } from './DialogRootContext'; import { DialogTransitionContext } from './DialogTransitionContext'; import { useContextCallback, useControlledProp, useStoreState, useSyncedValues } from '../../store/ReactStore'; export type DialogRootMode = 'dialog' | 'alert-dialog'; /** * The shared Root implementation behind `Dialog.Root` and `AlertDialog.Root`. * * An alert dialog is a dialog that never dismisses on an outside press and * announces itself as `alertdialog`. */ export function useRenderDialogRoot( props: DialogRootProps, mode: DialogRootMode = 'dialog', ) { const { actionsRef, children, defaultOpen = false, defaultTriggerId = null, disablePointerDismissal: disablePointerDismissalProp = false, handle, onOpenChange, onOpenChangeComplete, open, triggerId, } = props; const isAlertDialog = mode === 'alert-dialog'; const disablePointerDismissal = isAlertDialog || disablePointerDismissalProp; const role: 'dialog' | 'alertdialog' = isAlertDialog ? 'alertdialog' : 'dialog'; const parent = useDialogNestingContext(); const nested = parent !== undefined; const store = useRefWithInit( () => new DialogStore({ open: defaultOpen, openProp: open, disablePointerDismissal, role, triggerId: defaultTriggerId, triggerIdProp: triggerId, nested, }), ).current; useControlledProp(store, 'openProp', open); useControlledProp(store, 'triggerIdProp', triggerId); useContextCallback(store, 'onOpenChange', onOpenChange); useContextCallback(store, 'onOpenChangeComplete', onOpenChangeComplete); useSyncedValues(store, { disablePointerDismissal, role, nested }); usePopupRootHandle({ store, handle, actionsRef }); const resolvedOpen = useStoreState(store, 'open'); const { transitionStatus } = useTransitionStatus(resolvedOpen, false, true); // Tell the dialog above this one whether it now has an open descendant. The // cleanup is what keeps the parent's count right when this root unmounts // while still open. React.useEffect(() => { if (parent === undefined || !resolvedOpen) { return undefined; } parent.onNestedOpenChange(true); return () => { parent.onNestedOpenChange(false); }; }, [parent, resolvedOpen]); const onNestedOpenChange = useStableCallback((open: boolean) => { store.setNestedOpen(open); // A dialog two levels down is still nested inside this one's parent, so the // count propagates all the way up. parent?.onNestedOpenChange(open); }); const payload = useStoreState(store, 'payload') as Payload; const transitionContextValue = React.useMemo( () => ({ transitionStatus }), [transitionStatus], ); const nestingContextValue = React.useMemo( () => ({ onNestedOpenChange }), [onNestedOpenChange], ); return ( {typeof children === 'function' ? children(payload) : children} ); }