Files
2025-03-07 19:22:02 +01:00

97 lines
3.6 KiB
JavaScript

import { useHasParentContext } from '@fluentui/react-context-selector';
import { useModalAttributes } from '@fluentui/react-tabster';
import { presenceMotionSlot } from '@fluentui/react-motion';
import { useControllableState, useEventCallback, useId } from '@fluentui/react-utilities';
import * as React from 'react';
import { useFocusFirstElement } from '../../utils';
import { DialogContext } from '../../contexts';
import { DialogSurfaceMotion } from '../DialogSurfaceMotion';
/**
* Create the state required to render Dialog.
*
* The returned state can be modified with hooks such as useDialogStyles_unstable,
* before being passed to renderDialog_unstable.
*
* @param props - props from this instance of Dialog
*/ export const useDialog_unstable = (props)=>{
const { children, modalType = 'modal', onOpenChange, inertTrapFocus = false } = props;
const [trigger, content] = childrenToTriggerAndContent(children);
const [open, setOpen] = useControllableState({
state: props.open,
defaultState: props.defaultOpen,
initialState: false
});
const requestOpenChange = useEventCallback((data)=>{
onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(data.event, data);
// if user prevents default then do not change state value
// otherwise updates state value and trigger reference to the element that caused the opening
if (!data.event.isDefaultPrevented()) {
setOpen(data.open);
}
});
const focusRef = useFocusFirstElement(open, modalType);
const { modalAttributes, triggerAttributes } = useModalAttributes({
trapFocus: modalType !== 'non-modal',
legacyTrapFocus: !inertTrapFocus
});
const isNestedDialog = useHasParentContext(DialogContext);
return {
components: {
// TODO: remove once React v18 slot API is modified
// This is a problem at the moment due to UnknownSlotProps assumption
// that `children` property is `ReactNode`, which in this case is not valid
// as PresenceComponentProps['children'] is `ReactElement`
surfaceMotion: DialogSurfaceMotion
},
inertTrapFocus,
open,
modalType,
content,
trigger,
requestOpenChange,
dialogTitleId: useId('dialog-title-'),
isNestedDialog,
dialogRef: focusRef,
modalAttributes,
triggerAttributes,
surfaceMotion: presenceMotionSlot(props.surfaceMotion, {
elementType: DialogSurfaceMotion,
defaultProps: {
appear: true,
visible: open,
unmountOnExit: true
}
})
};
};
/**
* Extracts trigger and content from children
*/ function childrenToTriggerAndContent(children) {
const childrenArray = React.Children.toArray(children);
if (process.env.NODE_ENV !== 'production') {
if (childrenArray.length !== 1 && childrenArray.length !== 2) {
// eslint-disable-next-line no-console
console.warn(`@fluentui/react-dialog [useDialog]:
Dialog must contain at least one child <DialogSurface/>,
and at most two children <DialogTrigger/> <DialogSurface/> (in this order).`);
}
}
switch(childrenArray.length){
// case where there's a trigger followed by content
case 2:
return childrenArray;
// case where there's only content
case 1:
return [
undefined,
childrenArray[0]
];
// unknown case
default:
return [
undefined,
undefined
];
}
}