@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.
138 lines (135 loc) • 5.6 kB
JavaScript
;
'use client';
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useDialogRoot = useDialogRoot;
var React = _interopRequireWildcard(require("react"));
var _useStableCallback = require("@base-ui-components/utils/useStableCallback");
var _useScrollLock = require("@base-ui-components/utils/useScrollLock");
var _floatingUiReact = require("../../floating-ui-react");
var _utils = require("../../floating-ui-react/utils");
var _useOpenInteractionType = require("../../utils/useOpenInteractionType");
var _createBaseUIEventDetails = require("../../utils/createBaseUIEventDetails");
var _reasons = require("../../utils/reasons");
var _popups = require("../../utils/popups");
function useDialogRoot(params) {
const {
store,
parentContext,
actionsRef
} = params;
const open = store.useState('open');
const disablePointerDismissal = store.useState('disablePointerDismissal');
const modal = store.useState('modal');
const popupElement = store.useState('popupElement');
const {
openMethod,
triggerProps,
reset: resetOpenInteractionType
} = (0, _useOpenInteractionType.useOpenInteractionType)(open);
(0, _popups.useImplicitActiveTrigger)(store);
const {
forceUnmount
} = (0, _popups.useOpenStateTransitions)(open, store, () => {
resetOpenInteractionType();
});
const createDialogEventDetails = (0, _useStableCallback.useStableCallback)(reason => {
const details = (0, _createBaseUIEventDetails.createChangeEventDetails)(reason);
details.preventUnmountOnClose = () => {
store.set('preventUnmountingOnClose', true);
};
return details;
});
const handleImperativeClose = React.useCallback(() => {
store.setOpen(false, createDialogEventDetails(_reasons.REASONS.imperativeAction));
}, [store, createDialogEventDetails]);
React.useImperativeHandle(actionsRef, () => ({
unmount: forceUnmount,
close: handleImperativeClose
}), [forceUnmount, handleImperativeClose]);
const floatingRootContext = (0, _floatingUiReact.useSyncedFloatingRootContext)({
popupStore: store,
onOpenChange: store.setOpen,
treatPopupAsFloatingElement: true,
noEmit: true
});
const [ownNestedOpenDialogs, setOwnNestedOpenDialogs] = React.useState(0);
const isTopmost = ownNestedOpenDialogs === 0;
const role = (0, _floatingUiReact.useRole)(floatingRootContext);
const dismiss = (0, _floatingUiReact.useDismiss)(floatingRootContext, {
outsidePressEvent() {
if (store.context.internalBackdropRef.current || store.context.backdropRef.current) {
return 'intentional';
}
// Ensure `aria-hidden` on outside elements is removed immediately
// on outside press when trapping focus.
return {
mouse: modal === 'trap-focus' ? 'sloppy' : 'intentional',
touch: 'sloppy'
};
},
outsidePress(event) {
// For mouse events, only accept left button (button 0)
// For touch events, a single touch is equivalent to left button
if ('button' in event && event.button !== 0) {
return false;
}
if ('touches' in event && event.touches.length !== 1) {
return false;
}
const target = (0, _utils.getTarget)(event);
if (isTopmost && !disablePointerDismissal) {
const eventTarget = target;
// Only close if the click occurred on the dialog's owning backdrop.
// This supports multiple modal dialogs that aren't nested in the React tree:
// https://github.com/mui/base-ui/issues/1320
if (modal) {
return store.context.internalBackdropRef.current || store.context.backdropRef.current ? store.context.internalBackdropRef.current === eventTarget || store.context.backdropRef.current === eventTarget || (0, _utils.contains)(eventTarget, popupElement) && !eventTarget?.hasAttribute('data-base-ui-portal') : true;
}
return true;
}
return false;
},
escapeKey: isTopmost
});
(0, _useScrollLock.useScrollLock)(open && modal === true, popupElement);
const {
getReferenceProps,
getFloatingProps,
getTriggerProps
} = (0, _floatingUiReact.useInteractions)([role, dismiss]);
// Listen for nested open/close events on this store to maintain the count
store.useContextCallback('onNestedDialogOpen', ownChildrenCount => {
setOwnNestedOpenDialogs(ownChildrenCount + 1);
});
store.useContextCallback('onNestedDialogClose', () => {
setOwnNestedOpenDialogs(0);
});
// Notify parent of our open/close state using parent callbacks, if any
React.useEffect(() => {
if (parentContext?.onNestedDialogOpen && open) {
parentContext.onNestedDialogOpen(ownNestedOpenDialogs);
}
if (parentContext?.onNestedDialogClose && !open) {
parentContext.onNestedDialogClose();
}
return () => {
if (parentContext?.onNestedDialogClose && open) {
parentContext.onNestedDialogClose();
}
};
}, [open, parentContext, ownNestedOpenDialogs]);
const activeTriggerProps = React.useMemo(() => getReferenceProps(triggerProps), [getReferenceProps, triggerProps]);
const inactiveTriggerProps = React.useMemo(() => getTriggerProps(triggerProps), [getTriggerProps, triggerProps]);
const popupProps = React.useMemo(() => getFloatingProps(), [getFloatingProps]);
store.useSyncedValues({
openMethod,
activeTriggerProps,
inactiveTriggerProps,
popupProps,
floatingRootContext,
nestedOpenDialogCount: ownNestedOpenDialogs
});
}