UNPKG

@uiw/react-overlay-trigger

Version:

OverlayTrigger component

245 lines (244 loc) 8.09 kB
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() }))) }))] }); });