UNPKG

@itwin/itwinui-react

Version:

A react component library for iTwinUI

189 lines (188 loc) 5.46 kB
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';