@ozen-ui/kit
Version:
React component library
165 lines (164 loc) • 10.8 kB
JavaScript
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';