UNPKG

@gravity-ui/uikit

Version:

Gravity UI base styling and components

118 lines (117 loc) 6.46 kB
'use client'; "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Modal = Modal; const tslib_1 = require("tslib"); const jsx_runtime_1 = require("react/jsx-runtime"); const React = tslib_1.__importStar(require("react")); const react_1 = require("@floating-ui/react"); const tabbable_1 = require("tabbable"); const constants_1 = require("../../constants.js"); const hooks_1 = require("../../hooks/index.js"); const private_1 = require("../../hooks/private/index.js"); const useFloatingTransition_1 = require("../../hooks/private/useFloatingTransition/index.js"); const Portal_1 = require("../Portal/index.js"); const cn_1 = require("../utils/cn.js"); const filterDOMProps_1 = require("../utils/filterDOMProps.js"); const layer_manager_1 = require("../utils/layer-manager/index.js"); const i18n_1 = tslib_1.__importDefault(require("./i18n/index.js")); require("./Modal.css"); const b = (0, cn_1.block)('modal'); const TRANSITION_DURATION = 150; function ModalComponent({ open = false, onOpenChange, keepMounted = false, disableBodyScrollLock = false, disableEscapeKeyDown, disableOutsideClick, initialFocus, returnFocus, disableVisuallyHiddenDismiss, onEscapeKeyDown, onOutsideClick, onClose, onEnterKeyDown, onTransitionIn, onTransitionInComplete, onTransitionOut, onTransitionOutComplete, children, style, contentOverflow = 'visible', className, contentClassName, container, disablePortal, qa, floatingRef, disableHeightTransition = false, ...restProps }) { (0, layer_manager_1.useLayer)({ open, type: 'modal' }); const overlayRef = React.useRef(null); const handleOpenChange = React.useCallback((isOpen, event, reason) => { onOpenChange?.(isOpen, event, reason); if (isOpen || !event) { return; } let closeReason; if (reason === 'escape-key') { closeReason = 'escapeKeyDown'; } else if (reason === 'outside-press') { closeReason = 'outsideClick'; } else { closeReason = reason; } if (closeReason === 'escapeKeyDown' && onEscapeKeyDown) { onEscapeKeyDown(event); } if (closeReason === 'outsideClick' && onOutsideClick) { onOutsideClick(event); } onClose?.(event, closeReason); }, [onOpenChange, onEscapeKeyDown, onOutsideClick, onClose]); const floatingNodeId = (0, react_1.useFloatingNodeId)(); const { refs, elements, context } = (0, react_1.useFloating)({ nodeId: floatingNodeId, open, onOpenChange: handleOpenChange, }); const handleFloatingRef = (0, hooks_1.useForkRef)(refs.setFloating, floatingRef); const dismiss = (0, react_1.useDismiss)(context, { enabled: !disableOutsideClick || !disableEscapeKeyDown, outsidePress: (event) => { if (disableOutsideClick) { return false; } // Prevent closing parent modals if they aren't nested in the React tree if (event.target.closest(`.${b()}`) !== overlayRef.current) { return false; } return true; }, escapeKey: !disableEscapeKeyDown, }); const role = (0, react_1.useRole)(context, { role: 'dialog' }); const { getFloatingProps } = (0, react_1.useInteractions)([dismiss, role]); const { isMounted, status } = (0, useFloatingTransition_1.useFloatingTransition)({ context, duration: TRANSITION_DURATION, onTransitionIn, onTransitionInComplete, onTransitionOut, onTransitionOutComplete, }); (0, private_1.useAnimateHeight)({ ref: refs.floating, enabled: status === 'open' && !disableHeightTransition, }); const handleKeyDown = React.useCallback((event) => { if (!onEnterKeyDown || event.key !== constants_1.KeyCode.ENTER || event.defaultPrevented) { return; } const floatingElement = elements.floating; if (!floatingElement) { return; } const pathElements = event.nativeEvent.composedPath(); const index = pathElements.indexOf(floatingElement); const nestedElements = index < 0 ? pathElements : pathElements.slice(0, index); const nestedFloatingElementIndex = nestedElements.findIndex((el) => el?.hasAttribute('data-floating-ui-focusable')); if (nestedFloatingElementIndex < 0) { onEnterKeyDown(event.nativeEvent); return; } const hasInnerTabbableElements = nestedElements .slice(0, nestedFloatingElementIndex) .some((el) => (0, tabbable_1.isTabbable)(el)); if (!hasInnerTabbableElements) { onEnterKeyDown(event.nativeEvent); } }, [elements.floating, onEnterKeyDown]); return ((0, jsx_runtime_1.jsx)(react_1.FloatingNode, { id: floatingNodeId, children: isMounted || keepMounted ? ((0, jsx_runtime_1.jsx)(Portal_1.Portal, { container: container, disablePortal: disablePortal, children: (0, jsx_runtime_1.jsx)(react_1.FloatingOverlay, { ref: overlayRef, style: style, className: b({ open }, className), "data-qa": qa, "data-floating-ui-status": status, lockScroll: !disableBodyScrollLock, children: (0, jsx_runtime_1.jsx)("div", { className: b('content-aligner'), children: (0, jsx_runtime_1.jsx)("div", { className: b('content-wrapper'), children: (0, jsx_runtime_1.jsx)(react_1.FloatingFocusManager, { context: context, disabled: !isMounted, modal: isMounted, initialFocus: initialFocus ?? refs.floating, returnFocus: returnFocus, visuallyHiddenDismiss: disableVisuallyHiddenDismiss ? false : (0, i18n_1.default)('close'), restoreFocus: true, children: (0, jsx_runtime_1.jsx)("div", { ...(0, filterDOMProps_1.filterDOMProps)(restProps, { labelable: true }), className: b('content', { 'has-scroll': contentOverflow === 'auto' }, contentClassName), ref: handleFloatingRef, ...getFloatingProps({ onKeyDown: handleKeyDown, }), children: children }) }) }) }) }) })) : null })); } function Modal(props) { const parentId = (0, react_1.useFloatingParentNodeId)(); if (parentId === null) { return ((0, jsx_runtime_1.jsx)(react_1.FloatingTree, { children: (0, jsx_runtime_1.jsx)(ModalComponent, { ...props }) })); } return (0, jsx_runtime_1.jsx)(ModalComponent, { ...props }); } //# sourceMappingURL=Modal.js.map