@base-ui-components/react
Version:
Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.
115 lines (112 loc) • 4.14 kB
JavaScript
'use client';
import * as React from 'react';
import { FloatingFocusManager } from "../../floating-ui-react/index.js";
import { useDialogRootContext } from "../root/DialogRootContext.js";
import { useRenderElement } from "../../utils/useRenderElement.js";
import { popupStateMapping as baseMapping } from "../../utils/popupStateMapping.js";
import { transitionStatusMapping } from "../../utils/stateAttributesMapping.js";
import { DialogPopupCssVars } from "./DialogPopupCssVars.js";
import { DialogPopupDataAttributes } from "./DialogPopupDataAttributes.js";
import { useDialogPortalContext } from "../portal/DialogPortalContext.js";
import { useOpenChangeComplete } from "../../utils/useOpenChangeComplete.js";
import { COMPOSITE_KEYS } from "../../composite/composite.js";
import { jsx as _jsx } from "react/jsx-runtime";
const stateAttributesMapping = {
...baseMapping,
...transitionStatusMapping,
nestedDialogOpen(value) {
return value ? {
[DialogPopupDataAttributes.nestedDialogOpen]: ''
} : null;
}
};
/**
* A container for the dialog contents.
* Renders a `<div>` element.
*
* Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)
*/
export const DialogPopup = /*#__PURE__*/React.forwardRef(function DialogPopup(componentProps, forwardedRef) {
const {
className,
finalFocus,
initialFocus,
render,
...elementProps
} = componentProps;
const {
store
} = useDialogRootContext();
const descriptionElementId = store.useState('descriptionElementId');
const disablePointerDismissal = store.useState('disablePointerDismissal');
const floatingRootContext = store.useState('floatingRootContext');
const rootPopupProps = store.useState('popupProps');
const modal = store.useState('modal');
const mounted = store.useState('mounted');
const nested = store.useState('nested');
const nestedOpenDialogCount = store.useState('nestedOpenDialogCount');
const open = store.useState('open');
const openMethod = store.useState('openMethod');
const titleElementId = store.useState('titleElementId');
const transitionStatus = store.useState('transitionStatus');
const role = store.useState('role');
useDialogPortalContext();
useOpenChangeComplete({
open,
ref: store.context.popupRef,
onComplete() {
if (open) {
store.context.onOpenChangeComplete?.(true);
}
}
});
// Default initial focus logic:
// If opened by touch, focus the popup element to prevent the virtual keyboard from opening
// (this is required for Android specifically as iOS handles this automatically).
function defaultInitialFocus(interactionType) {
if (interactionType === 'touch') {
return store.context.popupRef.current;
}
return true;
}
const resolvedInitialFocus = initialFocus === undefined ? defaultInitialFocus : initialFocus;
const nestedDialogOpen = nestedOpenDialogCount > 0;
const state = React.useMemo(() => ({
open,
nested,
transitionStatus,
nestedDialogOpen
}), [open, nested, transitionStatus, nestedDialogOpen]);
const element = useRenderElement('div', componentProps, {
state,
props: [rootPopupProps, {
'aria-labelledby': titleElementId ?? undefined,
'aria-describedby': descriptionElementId ?? undefined,
role,
tabIndex: -1,
hidden: !mounted,
onKeyDown(event) {
if (COMPOSITE_KEYS.has(event.key)) {
event.stopPropagation();
}
},
style: {
[DialogPopupCssVars.nestedDialogs]: nestedOpenDialogCount
}
}, elementProps],
ref: [forwardedRef, store.context.popupRef, store.useStateSetter('popupElement')],
stateAttributesMapping
});
return /*#__PURE__*/_jsx(FloatingFocusManager, {
context: floatingRootContext,
openInteractionType: openMethod,
disabled: !mounted,
closeOnFocusOut: !disablePointerDismissal,
initialFocus: resolvedInitialFocus,
returnFocus: finalFocus,
modal: modal !== false,
restoreFocus: "popup",
children: element
});
});
if (process.env.NODE_ENV !== "production") DialogPopup.displayName = "DialogPopup";