UNPKG

@ozen-ui/kit

Version:

React component library

165 lines (164 loc) 10.8 kB
import { __assign, __read, __rest, __spreadArray } from "tslib"; import React, { useCallback, useMemo, useEffect, useRef, useState, forwardRef, } from 'react'; import { usePopper } from 'react-popper'; import { CSSTransition } from 'react-transition-group'; import './Popover.css'; import { isServer } from '../../constants/environment'; import { useClickOutside } from '../../hooks/useClickOutside'; import { useEventListener } from '../../hooks/useEventListener'; import { useFocusTrap } from '../../hooks/useFocusTrap'; import { useMultiRef } from '../../hooks/useMultiRef'; import { usePortalContainer } from '../../hooks/usePortalContainer'; import { useThemeProps } from '../../hooks/useThemeProps'; import { cn } from '../../utils/classname'; import { isKey } from '../../utils/isKey'; import { polymorphicComponentWithRef } from '../../utils/polymorphicComponentWithRef'; import { Paper } from '../Paper'; import { PortalBase } from '../PortalBase'; import { PopoverArrow } from './components'; import { POPOVER_DEFAULT_TAG, POPOVER_DEFAULT_ARROW, POPOVER_DEFAULT_DISABLE_CLICK_OUTSIDE, POPOVER_DEFAULT_DISABLE_ENFORCE_FOCUS, POPOVER_DEFAULT_DISABLE_INTERACTIVE, POPOVER_DEFAULT_DISABLE_RETURN_FOCUS, POPOVER_DEFAULT_OPEN, POPOVER_DEFAULT_PLACEMENT, POPOVER_DEFAULT_STRATEGY, POPOVER_DEFAULT_DISABLE_PREVENT_OVERFLOW, POPOVER_DEFAULT_PORTAL_CONTAINER, } from './constants'; import { usePopoverManager } from './index'; import { PopoverContext } from './PopoverContext'; export var cnPopover = cn('Popover'); export var Popover = polymorphicComponentWithRef(function (inProps, ref) { var props = useThemeProps({ props: inProps, name: 'Popover' }); var _a = props.arrow, arrow = _a === void 0 ? POPOVER_DEFAULT_ARROW : _a, _b = props.open, open = _b === void 0 ? POPOVER_DEFAULT_OPEN : _b, _c = props.disableInteractive, disableInteractive = _c === void 0 ? POPOVER_DEFAULT_DISABLE_INTERACTIVE : _c, _d = props.disableEnforceFocus, disableEnforceFocus = _d === void 0 ? POPOVER_DEFAULT_DISABLE_ENFORCE_FOCUS : _d, _e = props.disableReturnFocus, disableReturnFocus = _e === void 0 ? POPOVER_DEFAULT_DISABLE_RETURN_FOCUS : _e, _f = props.disableClickOutside, disableClickOutside = _f === void 0 ? POPOVER_DEFAULT_DISABLE_CLICK_OUTSIDE : _f, _g = props.placement, placement = _g === void 0 ? POPOVER_DEFAULT_PLACEMENT : _g, _h = props.strategy, strategy = _h === void 0 ? POPOVER_DEFAULT_STRATEGY : _h, _j = props.disablePreventOverflow, disablePreventOverflow = _j === void 0 ? POPOVER_DEFAULT_DISABLE_PREVENT_OVERFLOW : _j, _k = props.container, containerProp = _k === void 0 ? POPOVER_DEFAULT_PORTAL_CONTAINER : _k, ignoreClickOutsideRefs = props.ignoreClickOutsideRefs, disableEscapeKeyDown = props.disableEscapeKeyDown, arrowProps = props.arrowProps, anchorRef = props.anchorRef, equalAnchorWidth = props.equalAnchorWidth, offset = props.offset, children = props.children, transitionProps = props.transitionProps, onClose = props.onClose, onEnter = props.onEnter, onEntered = props.onEntered, onExit = props.onExit, onExitedProp = props.onExited, className = props.className, position = props.position, style = props.style, setUpdate = props.setUpdate, modifiersProp = props.modifiers, _l = props.as, as = _l === void 0 ? POPOVER_DEFAULT_TAG : _l, other = __rest(props, ["arrow", "open", "disableInteractive", "disableEnforceFocus", "disableReturnFocus", "disableClickOutside", "placement", "strategy", "disablePreventOverflow", "container", "ignoreClickOutsideRefs", "disableEscapeKeyDown", "arrowProps", "anchorRef", "equalAnchorWidth", "offset", "children", "transitionProps", "onClose", "onEnter", "onEntered", "onExit", "onExited", "className", "position", "style", "setUpdate", "modifiers", "as"]); var Tag = useMemo(function () { // eslint-disable-next-line react/display-name return forwardRef(function (props, ref) { return (React.createElement(Paper, __assign({ radius: "l", shadow: "m", background: "main" }, props, { as: as, ref: ref }))); }); }, [as]); var _m = __read(useState(false), 2), openState = _m[0], setOpenState = _m[1]; var focusedElement = useRef(null); var popoverRef = useRef(null); var _o = usePopoverManager(popoverRef, 1000, openState), refsClickOutside = _o.refsClickOutside, isTop = _o.isTop; // Ловушка фокуса var trapRef = useFocusTrap({ active: !disableEnforceFocus && isTop, focusinTrap: false, }); var _p = __read(useState(null), 2), popperElement = _p[0], setPopperElement = _p[1]; var portalRef = useMultiRef([ref, popoverRef, trapRef, setPopperElement]); // Нажатие клавиши {ESC} useEventListener({ eventName: 'keydown', handler: function (event) { if (!isKey(event, 'Escape')) return; onClose === null || onClose === void 0 ? void 0 : onClose(); }, active: isTop && !disableEscapeKeyDown, }); useClickOutside({ refs: __spreadArray(__spreadArray(__spreadArray([ popoverRef ], __read(refsClickOutside), false), __read((anchorRef ? [anchorRef] : [])), false), __read((ignoreClickOutsideRefs || [])), false), handler: onClose, active: openState && !disableClickOutside, }); var onExited = useCallback(function () { var _a, _b; if (isServer) { return; } // Возвращаем фокус активного элемента if (!disableReturnFocus && (((_a = popoverRef.current) === null || _a === void 0 ? void 0 : _a.contains(document.activeElement)) || document.activeElement === document.body)) { (_b = focusedElement.current) === null || _b === void 0 ? void 0 : _b.focus({ preventScroll: true }); } onExitedProp === null || onExitedProp === void 0 ? void 0 : onExitedProp(); }, [onExitedProp]); var modifiers = useMemo(function () { return __spreadArray([ // Вычисление стилей позиционирования { name: 'computeStyles', options: { // Позиционирование через position: absolute // https://popper.js.org/docs/v2/modifiers/compute-styles/#gpuacceleration gpuAcceleration: false, }, }, { name: 'preventOverflow', enabled: disablePreventOverflow }, // Ширина всплывающего элемента { name: 'sameWidth', enabled: !!equalAnchorWidth, fn: function (_a) { var state = _a.state; if ('popper' in state.styles) { // eslint-disable-next-line no-param-reassign state.styles.popper.width = "".concat(state.rects.reference.width, "px"); } }, effect: function (_a) { var _b; var state = _a.state; // eslint-disable-next-line no-param-reassign state.elements.popper.style.width = "".concat((_b = state.elements.reference) === null || _b === void 0 ? void 0 : _b.offsetWidth, "px"); }, phase: 'beforeWrite', requires: ['computeStyles'], }, { name: 'arrow', options: { padding: 12, }, }, // Отступы от якорного элемента { name: 'offset', options: { offset: offset, }, } ], __read((modifiersProp || [])), false); }, [offset, equalAnchorWidth, modifiersProp]); // Якорный элемент или координаты var resolveReferenceElement = useMemo(function () { if (anchorRef === null || anchorRef === void 0 ? void 0 : anchorRef.current) return anchorRef === null || anchorRef === void 0 ? void 0 : anchorRef.current; if (position) return { getBoundingClientRect: function () { return { top: (position === null || position === void 0 ? void 0 : position.y) || 0, left: (position === null || position === void 0 ? void 0 : position.x) || 0, bottom: (position === null || position === void 0 ? void 0 : position.y) || 0, right: (position === null || position === void 0 ? void 0 : position.x) || 0, width: 0, height: 0, }; }, }; return undefined; }, [anchorRef === null || anchorRef === void 0 ? void 0 : anchorRef.current, position]); var _q = usePopper(resolveReferenceElement, popperElement, { placement: placement, strategy: strategy, modifiers: modifiers, }), styles = _q.styles, attributes = _q.attributes, update = _q.update; // Передача метода по перерасчету расположения компонента Popover в родительский компонент useEffect(function () { setUpdate === null || setUpdate === void 0 ? void 0 : setUpdate(update); }, [update]); useEffect(function () { var _a; // Сохраняем фокус активного элемента до момента открытия всплывающего окна if (open && !disableReturnFocus) { focusedElement.current = document.activeElement; (_a = focusedElement.current) === null || _a === void 0 ? void 0 : _a.blur(); } setOpenState(open); }, [open]); var container = usePortalContainer(containerProp); if (!container) { return null; } return (React.createElement(PopoverContext.Provider, { value: { isTop: isTop } }, React.createElement(CSSTransition, __assign({ nodeRef: popoverRef, classNames: cnPopover({ animation: true }), timeout: 120 }, transitionProps, { in: openState, onEnter: onEnter, onEntered: onEntered, onExit: onExit, onExited: onExited, unmountOnExit: true }), React.createElement(PortalBase, __assign({ as: Tag }, other, { style: __assign(__assign({}, style), styles.popper), ref: portalRef, container: container, className: cnPopover({ disableInteractive: disableInteractive }, [className]) }, attributes.popper), children, arrow && (React.createElement(PopoverArrow, __assign({}, arrowProps, { style: __assign(__assign({}, arrowProps === null || arrowProps === void 0 ? void 0 : arrowProps.style), styles.arrow), "data-popper-arrow": true }))))))); }); Popover.displayName = 'Popover';