import React, { useEffect, useRef } from 'react'; import { ChildrenSibling } from './style'; import { getSiblingLocation, mountContainer } from './helper'; import { genUUID } from 'utils'; import { setPopupLocation } from './popup'; import { IPopover } from './types'; const ref: any = React.createRef(); mountContainer(ref); const Popover = function(props: IPopover) { const { children, trigger, onVisibleChange, placement, delay = 0, ...rest } = props; const controlledVisible = 'visible' in props; // function component 不太好获得children的ref,只能添加一个隐藏sibling 获取children的dom引用 // https://github.com/facebook/react/issues/4244 const siblingRef: any = useRef(); const uid: any = useRef(); const openTimeoutId = useRef(); useEffect(() => { uid.current = genUUID(); }, []); // insert popup dom useEffect(() => { if (!ref) { return; } ref.current.setPopups((p: any) => { const find = p.findIndex((s: any) => s.uid === uid.current); if (~find) { // modify absolute position in case dom layout change const loc = getSiblingLocation(siblingRef); setTimeout(() => { //p[find].ref.current.setPos({ // left: loc.left, // top: loc.top - p[find].ref.current.dom.offsetHeight - 10, //}); p[find].ref.current.setPos(setPopupLocation(placement, loc as DOMRect, p[find].ref.current.dom)); }, 0); p[find] = { ...p[find], trigger, ...rest, }; } else { p.push({ uid: uid.current, trigger, ref: React.createRef(), siblingRef, onVisibleChange, placement, ...rest, }); } return [...p]; }); }, [trigger, rest.content, rest.themeColor, rest.visible, placement]); useEffect(() => { var node: any, openHandle: any, closeHandle: any, toggleHandle: any, popupRef: any; setTimeout(() => { node = siblingRef.current && siblingRef.current.previousElementSibling; if (!ref || !node) { return; } const find = ref.current.popups.find((p: any) => p.uid === uid.current); if (!find) { return; } popupRef = find.ref; openHandle = () => { const loc = getSiblingLocation(siblingRef); //popupRef.current.setPos({ left: loc.left, top: loc.top - popupRef.current.dom.offsetHeight - 10 }); if (openTimeoutId.current) { clearTimeout(openTimeoutId.current); } openTimeoutId.current = setTimeout(() => { popupRef.current.setPos(setPopupLocation(placement, loc as DOMRect, popupRef.current.dom)); if (!controlledVisible) { popupRef.current.setVisible(true); } else { onVisibleChange && onVisibleChange(true); } }, delay); }; closeHandle = (e: any) => { clearTimeout(openTimeoutId.current); setTimeout(() => { if (!popupRef.current.cursorIn) { if (!controlledVisible) { popupRef.current.setVisible(false); } else { onVisibleChange && onVisibleChange(false); } } }, 150); }; toggleHandle = () => { const loc = getSiblingLocation(siblingRef); //popupRef.current.setPos({ left: loc.left, top: loc.top - popupRef.current.dom.offsetHeight - 10 }); popupRef.current.setPos(setPopupLocation(placement, loc as DOMRect, popupRef.current.dom)); if (!controlledVisible) { popupRef.current.setVisible((s: boolean) => !s); } else { onVisibleChange && onVisibleChange(!popupRef.current.visible); } }; if (trigger === 'hover') { node.addEventListener('mouseenter', openHandle); node.addEventListener('mouseleave', closeHandle); } else { node.addEventListener('mousedown', toggleHandle); } }, 0); return () => { if (popupRef && popupRef.current) { popupRef.current.setVisible(false); } if (trigger === 'hover') { node && node.removeEventListener('mouseenter', openHandle); node && node.removeEventListener('mouseleave', closeHandle); } else { node && node.removeEventListener('mousedown', toggleHandle); } }; }, [trigger, controlledVisible, placement]); return ( <> {children} ); }; Popover.defaultProps = { themeColor: 'white', trigger: 'hover', placement: 'topLeft', delay: 0, }; export default Popover;