@uiw/react-overlay-trigger
Version:
OverlayTrigger component
245 lines (244 loc) • 8.09 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/objectWithoutPropertiesLoose";
var _excluded = ["className", "prefixCls", "usePortal", "isOutside", "isClickOutside", "disabled", "isOpen", "trigger", "placement", "autoAdjustOverflow", "transitionName", "children", "overlay", "onVisibleChange", "onEnter"];
import React, { cloneElement, useEffect, useRef, useState, useImperativeHandle } from 'react';
import { noop } from '@uiw/utils';
import Overlay from '@uiw/react-overlay';
import contains from './utils';
import { getStyle } from './getStyle';
import "./style/index.css";
import { jsx as _jsx } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime";
var normalizeDelay = delay => delay && typeof delay === 'object' ? delay : {
show: delay,
hide: delay
};
export default /*#__PURE__*/React.forwardRef((props, ref) => {
var _child$props;
var {
className,
prefixCls = 'w-overlay-trigger',
usePortal = true,
isOutside = false,
isClickOutside = true,
disabled = false,
isOpen: _ = false,
trigger = 'hover',
placement = 'top',
autoAdjustOverflow,
transitionName,
children,
overlay,
onVisibleChange = noop,
onEnter = noop
} = props,
other = _objectWithoutPropertiesLoose(props, _excluded);
var zIndex = useRef(999);
var triggerRef = useRef();
var popupRef = useRef();
var timeoutRef = useRef([]);
var hoverStateRef = useRef(null);
var [isOpen, setIsOpen] = useState(!!props.isOpen);
var [overlayStyl, setOverlayStyl] = useState({
placement,
top: 0,
bottom: 0,
left: 0,
right: 0,
zIndex: zIndex.current
});
useImperativeHandle(ref, () => ({
hide: () => _hide(),
show: () => _show(),
overlayDom: popupRef
}));
var child = React.Children.only(children);
var overlayProps = _extends({}, other, {
placement,
isOpen,
dialogProps: {}
});
var triggerProps = {};
function getChildProps() {
if (child && /*#__PURE__*/React.isValidElement(child)) {
return child.props;
}
return {};
}
useEffect(() => {
if (isClickOutside && isOpen) {
document && document.addEventListener('mousedown', handleClickOutside);
window.addEventListener('resize', handleResize);
}
return () => {
document && isClickOutside && document.removeEventListener('mousedown', handleClickOutside);
window.removeEventListener('resize', handleResize);
};
}, [isOpen]);
useEffect(() => {
if (props.isOpen !== isOpen) {
setIsOpen(!!props.isOpen);
}
}, [props.isOpen]);
useEffect(() => {
var styls = getStyle({
placement: overlayStyl.placement || placement,
trigger: triggerRef.current,
popup: popupRef.current,
usePortal,
autoAdjustOverflow
});
setOverlayStyl(_extends({}, styls, {
zIndex: zIndex.current
}));
onVisibleChange(isOpen);
}, [isOpen]);
var handleResize = () => {
if (isOpen) {
zIndex.current -= 1;
setIsOpen(false);
onVisibleChange && onVisibleChange(false);
}
};
var handleClickOutside = e => {
var popNode = popupRef.current;
var childNode = triggerRef.current;
if (popNode && childNode && e.target && !contains(popNode, e.target) && !contains(childNode, e.target)) {
zIndex.current -= 1;
setIsOpen(false);
onVisibleChange && onVisibleChange(false);
}
};
function clearTimeouts() {
if (timeoutRef.current.length > 0) {
for (var timeoutId of timeoutRef.current) {
window.clearTimeout(timeoutId);
}
timeoutRef.current = [];
}
}
function handleShow() {
var _props$children;
clearTimeouts();
hoverStateRef.current = 'show';
var delay = normalizeDelay(props.delay);
if (!delay.show && !((_props$children = props.children) != null && _props$children.props.disabled)) {
_show();
return;
}
var handle = window.setTimeout(() => {
if (hoverStateRef.current === 'show') _show();
}, delay.show);
clearTimeout(handle);
timeoutRef.current.push(handle);
}
function handleHide(isOutside) {
clearTimeouts();
if (!isOutside && props.isOutside) return;
hoverStateRef.current = 'hide';
var delay = normalizeDelay(props.delay);
if (!delay.hide) {
_hide();
return;
}
var handle = window.setTimeout(() => {
if (hoverStateRef.current === 'hide') _hide();
}, delay.hide);
timeoutRef.current.push(handle);
}
// Simple implementation of mouseEnter and mouseLeave.
// React's built version is broken: https://github.com/facebook/react/issues/4251
// for cases when the trigger is disabled and mouseOut/Over can cause flicker
// moving from one child element to another.
function handleMouseOverOut(handler, e, relatedNative) {
var target = e.currentTarget;
var related = e.relatedTarget || e.nativeEvent[relatedNative];
var isOutside = true;
if (popupRef.current && contains(popupRef.current, related) || triggerRef.current && contains(triggerRef.current, related)) {
isOutside = false;
}
if ((!related || related !== target) && !contains(target, related)) {
handler(isOutside, e);
}
}
function _hide() {
if (!isOpen) return;
if (zIndex.current <= 999) {
zIndex.current = 999;
} else {
zIndex.current -= 1;
}
setIsOpen(false);
}
function _show() {
var _triggerRef$current;
if (isOpen) return;
var nodeIndex = (_triggerRef$current = triggerRef.current) == null ? void 0 : _triggerRef$current.style.zIndex;
if (nodeIndex) {
zIndex.current = Number(nodeIndex) + 1;
} else {
zIndex.current += 1;
}
setIsOpen(true);
}
function handleEnter(node, isAppearing) {
onEnter && onEnter(node, isAppearing);
var styls = getStyle({
placement: overlayStyl.placement || placement,
trigger: triggerRef.current,
popup: popupRef.current,
usePortal,
autoAdjustOverflow
});
setOverlayStyl(_extends({}, styls, {
zIndex: zIndex.current
}));
}
if (trigger === 'click' && !disabled) {
triggerProps.onClick = e => {
var {
onClick
} = getChildProps();
isOpen ? _hide() : _show();
if (onClick) onClick(e, !isOpen);
};
}
if (trigger === 'focus' && !disabled) {
triggerProps.onFocus = () => handleShow();
}
if (trigger === 'hover' && !disabled) {
triggerProps.onMouseOver = triggerProps.onMouseEnter = e => {
handleMouseOverOut(handleShow, e, 'fromElement');
};
triggerProps.onMouseOut = triggerProps.onMouseLeave = e => {
handleMouseOverOut(handleHide, e, 'toElement');
};
if (overlayProps.dialogProps) {
overlayProps.dialogProps.onMouseLeave = e => {
handleMouseOverOut(handleHide, e, 'toElement');
};
}
}
overlayProps.style = _extends({}, overlayProps.style, overlayStyl);
return /*#__PURE__*/_jsxs(React.Fragment, {
children: [/*#__PURE__*/cloneElement(child, Object.assign({}, child.props, _extends({}, triggerProps, {
ref: triggerRef,
style: _extends({}, (_child$props = child.props) == null ? void 0 : _child$props.style, {
zIndex: zIndex.current
}),
className: [child.props.className, disabled ? prefixCls + "-disabled" : null].filter(Boolean).join(' ').trim()
}))), /*#__PURE__*/_jsx(Overlay, _extends({}, overlayProps, {
style: _extends({}, overlayProps.style, overlayStyl),
onEnter: handleEnter,
className: [prefixCls, className, overlayStyl.placement].filter(Boolean).join(' ').trim(),
usePortal: usePortal,
transitionName: transitionName,
isOpen: isOpen,
hasBackdrop: false,
children: /*#__PURE__*/cloneElement(overlay, Object.assign(_extends({}, overlay.props, {
ref: popupRef,
className: [overlay.props && overlay.props.className, placement].filter(Boolean).join(' ').trim()
})))
}))]
});
});