@itwin/itwinui-react
Version:
A react component library for iTwinUI
189 lines (188 loc) • 5.46 kB
JavaScript
import * as React from 'react';
import cx from 'classnames';
import {
FocusTrap,
getTranslateValuesFromElement,
Resizer,
useMergedRefs,
useLayoutEffect,
Box,
ShadowRoot,
} from '../../utils/index.js';
import { useDialogContext } from './DialogContext.js';
import { DialogDragContext } from './DialogDragContext.js';
import { useDragAndDrop } from '../../utils/hooks/useDragAndDrop.js';
import { DialogMainContext } from './DialogMainContext.js';
export const DialogMain = React.forwardRef((props, forwardedRef) => {
let dialogContext = useDialogContext();
let {
className,
children,
styleType = 'default',
isOpen = dialogContext?.isOpen,
isDismissible = dialogContext?.isDismissible,
onClose = dialogContext?.onClose,
closeOnEsc = dialogContext?.closeOnEsc,
trapFocus = dialogContext?.trapFocus,
setFocus = dialogContext?.setFocus,
preventDocumentScroll = dialogContext?.preventDocumentScroll,
onKeyDown,
isDraggable = dialogContext?.isDraggable,
isResizable = dialogContext?.isResizable,
style: propStyle,
placement = dialogContext?.placement,
...rest
} = props;
let { dialogRootRef, setDialogElement } = dialogContext || {};
let dialogRef = React.useRef(null);
let previousFocusedElement = React.useRef(null);
let [style, setStyle] = React.useState();
let hasBeenResized = React.useRef(false);
let originalBodyOverflow = React.useRef('');
useLayoutEffect(() => {
if (isOpen) originalBodyOverflow.current = document.body.style.overflow;
}, [isOpen]);
React.useEffect(() => {
let ownerDocument = dialogRef.current?.ownerDocument;
if (
!ownerDocument ||
!preventDocumentScroll ||
'hidden' === originalBodyOverflow.current
)
return;
if (isOpen) ownerDocument.body.style.overflow = 'hidden';
else ownerDocument.body.style.overflow = originalBodyOverflow.current;
return () => {
ownerDocument.body.style.overflow = originalBodyOverflow.current;
};
}, [dialogRef, isOpen, preventDocumentScroll]);
let handleKeyDown = (event) => {
if (event.altKey) return;
event.persist();
if (isDismissible && closeOnEsc && 'Escape' === event.key && onClose) {
beforeClose();
onClose(event);
}
onKeyDown?.(event);
};
let { onPointerDown, transform } = useDragAndDrop(
dialogRef,
dialogRootRef,
isDraggable,
);
let handlePointerDown = React.useCallback(
(event) => {
if (isDraggable) onPointerDown(event);
},
[isDraggable, onPointerDown],
);
useLayoutEffect(() => {
if (!isDraggable || !isOpen) return;
let [translateX, translateY] = getTranslateValuesFromElement(
dialogRef.current,
);
setStyle((oldStyle) => ({
...oldStyle,
insetInlineStart: dialogRef.current?.offsetLeft,
insetBlockStart: dialogRef.current?.offsetTop,
transform: `translate(${translateX}px,${translateY}px)`,
}));
}, [dialogRef, isDraggable, isOpen]);
let setResizeStyle = React.useCallback((newStyle) => {
setStyle((oldStyle) => ({
...oldStyle,
...newStyle,
}));
}, []);
let onEnter = React.useCallback(() => {
previousFocusedElement.current =
dialogRef.current?.ownerDocument.activeElement;
if (setFocus)
dialogRef.current?.focus({
preventScroll: true,
});
}, [setFocus]);
let beforeClose = React.useCallback(() => {
if (
dialogRef.current?.contains(
dialogRef.current?.ownerDocument.activeElement,
)
)
previousFocusedElement.current?.focus();
}, [dialogRef, previousFocusedElement]);
let mountRef = React.useCallback(
(element) => {
if (element) onEnter();
},
[onEnter],
);
let content = React.createElement(
Box,
{
className: cx(
'iui-dialog',
{
'iui-dialog-default': 'default' === styleType,
'iui-dialog-full-page': 'fullPage' === styleType,
'iui-dialog-visible': isOpen,
'iui-dialog-draggable': isDraggable,
},
className,
),
role: 'dialog',
ref: useMergedRefs(dialogRef, mountRef, setDialogElement, forwardedRef),
onKeyDown: handleKeyDown,
tabIndex: -1,
'data-iui-placement': placement,
style: {
transform,
...style,
...propStyle,
},
...rest,
},
React.createElement(
ShadowRoot,
null,
React.createElement('slot', null),
isResizable &&
React.createElement(Resizer, {
elementRef: dialogRef,
containerRef: dialogRootRef,
onResizeStart: () => {
if (!hasBeenResized.current) {
hasBeenResized.current = true;
setResizeStyle({
maxInlineSize: '100%',
});
}
},
onResizeEnd: setResizeStyle,
}),
),
children,
);
return React.createElement(
DialogMainContext.Provider,
{
value: React.useMemo(
() => ({
beforeClose,
}),
[beforeClose],
),
},
React.createElement(
DialogDragContext.Provider,
{
value: {
onPointerDown: handlePointerDown,
},
},
trapFocus && React.createElement(FocusTrap, null, content),
!trapFocus && content,
),
);
});
if ('development' === process.env.NODE_ENV)
DialogMain.displayName = 'Dialog.Main';