UNPKG

react-widget-trigger

Version:
511 lines (510 loc) 21.2 kB
import React from "react"; import { findDOMNode } from "react-dom"; import Popup from "react-widget-popup"; import listen from "dom-helpers/listen"; import contains from "dom-helpers/contains"; import removeClass from "dom-helpers/removeClass"; import addClass from "dom-helpers/addClass"; import position from "jq-position"; import Portal from "react-widget-portal"; import getPlacement from "./getPlacement"; export const version = "%VERSION%"; export function feedbackToPlacement(feedback) { const map = { center_top_center_bottom: "top", left_top_left_bottom: "top-left", right_top_right_bottom: "top-right", center_bottom_center_top: "bottom", left_bottom_left_top: "bottom-left", right_bottom_right_top: "bottom-right", left_center_right_center: "left", left_top_right_top: "left-top", left_bottom_right_bottom: "left-bottom", right_center_left_center: "right", right_top_left_top: "right-top", right_bottom_left_bottom: "right-bottom", }; return map[feedback.at.join("_") + "_" + feedback.my.join("_")]; } let Trigger = /** @class */ (() => { class Trigger extends React.Component { constructor() { super(...arguments); this.state = { popupVisible: this.props.defaultPopupVisible, }; this.delayTimer = null; this.refHandlers = { popup: (node) => (this.popupInstance = node), trigger: (node) => (this.triggerInstance = node), }; this.onOutsideClickToHide = (event) => { const target = event.target; const triggerNode = this.getTriggerNode(); const popupRootNode = this.popupInstance.getRootDOM(); if (!contains(triggerNode, target) && !contains(popupRootNode, target)) { this.hide(); } }; this.isFocusToShow = () => { return this.checkToShow(["focus"]); }; this.isBlurToHide = () => { return this.checkToHide(["focus", "blur"]); }; this.isWindowResizeToHide = () => { return this.checkToHide(["resize"]); }; this.onMouseEnter = (e) => { this.delaySetPopupVisible(true); }; this.onMouseLeave = (e) => { this.delaySetPopupVisible(false); }; this.onFocus = (e) => { this.delaySetPopupVisible(true); }; this.onBlur = (e) => { this.delaySetPopupVisible(false); }; this.onContextMenuClose = () => { this.hide(); }; } static getDerivedStateFromProps(nextProps, state) { return { popupVisible: nextProps.disabled ? false : nextProps.popupVisible === undefined ? state.popupVisible : nextProps.popupVisible, }; } componentDidMount() { this.togglePopupCloseEvents(); } componentDidUpdate() { this.togglePopupCloseEvents(); } componentWillUnmount() { this.clearDelayTimer(); this.clearOutsideHandler(); } togglePopupCloseEvents() { const { getDocument, outsideHideEventName } = this.props; const { popupVisible } = this.state; if (popupVisible) { const currentDocument = getDocument(); if (!this.clickOutsideHandler && (this.isMouseDownToHide() || this.isClickToHide() || this.isContextMenuToShow())) { if (Array.isArray(outsideHideEventName)) { const cancelHandlers = []; outsideHideEventName.forEach((name) => { cancelHandlers.push(listen(currentDocument, name, this.onOutsideClickToHide)); }); this.clickOutsideHandler = () => { cancelHandlers.forEach((cancel) => cancel()); }; } else { this.clickOutsideHandler = listen(currentDocument, outsideHideEventName, this.onOutsideClickToHide); } } // if (!this.touchOutsideHandler && isMobile) { // this.touchOutsideHandler = listen(currentDocument, "click", this.onOutsideClick); // } // close popup when trigger type contains 'onContextMenu' and document is scrolling. if (!this.contextMenuOutsideHandler1 && this.isContextMenuToShow()) { this.contextMenuOutsideHandler1 = listen(currentDocument, "scroll", this.onContextMenuClose); } // close popup when trigger type contains 'onContextMenu' and window is blur. if (!this.contextMenuOutsideHandler2 && this.isContextMenuToShow()) { //@ts-ignore this.contextMenuOutsideHandler2 = listen(window, "blur", this.onContextMenuClose); } // if (!this.windowScrollHandler && this.isWindowScrollToHide()) { // this.windowScrollHandler = listen( // currentDocument, // "scroll", // this.onOutsideClickToHide // ); // } if (!this.windowResizeHandler && this.isWindowResizeToHide()) { //@ts-ignore this.windowResizeHandler = listen(window, "resize", this.hide.bind(this)); } } else { this.clearOutsideHandler(); } } getTriggerNode() { return findDOMNode(this); } getPopupNode() { return this.popupInstance.getPopupDOM(); } getComponentNode() { return findDOMNode(this); } clearOutsideHandler() { if (this.clickOutsideHandler) { this.clickOutsideHandler(); this.clickOutsideHandler = null; } if (this.contextMenuOutsideHandler1) { this.contextMenuOutsideHandler1(); this.contextMenuOutsideHandler1 = null; } if (this.contextMenuOutsideHandler2) { this.contextMenuOutsideHandler2(); this.contextMenuOutsideHandler2 = null; } if (this.touchOutsideHandler) { this.touchOutsideHandler(); this.touchOutsideHandler = null; } if (this.windowScrollHandler) { this.windowScrollHandler(); this.windowScrollHandler = null; } if (this.windowResizeHandler) { this.windowResizeHandler(); this.windowResizeHandler = null; } } _setPopupVisible(popupVisible) { if (this.props.popupVisible === undefined) { this.setState({ popupVisible, }); } this.props.onPopupVisibleChange?.(popupVisible); } show() { this.delaySetPopupVisible(true); } hide() { this.delaySetPopupVisible(false); } clearDelayTimer() { if (this.delayTimer) { clearTimeout(this.delayTimer); this.delayTimer = null; } } getDelayTime(action = "show") { const { delay } = this.props; if (delay && typeof delay !== "number") { return Math.abs(delay[action]); } return Math.abs(delay); } delaySetPopupVisible(visible) { if (this.state.popupVisible === visible) { return; } this.clearDelayTimer(); this.clearOutsideHandler(); const delay = this.getDelayTime(visible ? "show" : "hide"); if (delay) { this.delayTimer = setTimeout(() => { this.delayTimer = null; this._setPopupVisible(visible); }, delay); } else { this._setPopupVisible(visible); } } checkToShow(actions) { const { action, showAction } = this.props; const action1 = Array.isArray(action) ? action : [action]; const showAction1 = Array.isArray(showAction) ? showAction : [showAction]; const s = [ ...action1, ...showAction1, ]; for (let i = 0; i < actions.length; i++) { if (s.indexOf(actions[i]) !== -1) return true; } return false; } checkToHide(actions) { const { action, hideAction } = this.props; const action1 = Array.isArray(action) ? action : [action]; const hideAction1 = Array.isArray(hideAction) ? hideAction : [hideAction]; const s = [ ...action1, ...hideAction1, ]; for (let i = 0; i < actions.length; i++) { if (s.indexOf(actions[i]) !== -1) return true; } return false; } isContextMenuToShow() { return this.checkToShow(["contextMenu"]); } isMouseDownToShow() { return this.checkToShow(["mouseDown"]); } isMouseDownToHide() { return this.checkToHide(["mouseDown"]); } isClickToShow() { return this.checkToShow(["click"]); } isClickToHide() { return this.checkToHide(["click"]); } isMouseEnterToShow() { return this.checkToShow(["hover", "mouseEnter"]); } isMouseLeaveToHide() { return this.checkToHide(["hover", "mouseLeave"]); } // protected isWindowScrollToHide = () => { // return this.checkToHide(["scroll"]); // }; onContextMenu(e) { e.preventDefault(); this.delaySetPopupVisible(true); } onTriggerClick(e) { const nextVisible = !this.state.popupVisible; if ((this.isClickToHide() && !nextVisible) || (nextVisible && this.isClickToShow())) { this.delaySetPopupVisible(nextVisible); } } onTriggerMouseDown(e) { const nextVisible = !this.state.popupVisible; if ((this.isMouseDownToHide() && !nextVisible) || (nextVisible && this.isMouseDownToShow())) { this.delaySetPopupVisible(nextVisible); } } removeClassNames(popupNode) { const { prefixCls } = this.props; [ `${prefixCls}-placement-top`, `${prefixCls}-placement-topLeft`, `${prefixCls}-placement-topRight`, `${prefixCls}-placement-bottom`, `${prefixCls}-placement-bottomLeft`, `${prefixCls}-placement-bottomRight`, `${prefixCls}-placement-left`, `${prefixCls}-placement-leftTop`, `${prefixCls}-placement-leftBottom`, `${prefixCls}-placement-right`, `${prefixCls}-placement-rightTop`, `${prefixCls}-placement-rightBottom`, ].forEach(removeClass.bind(null, popupNode)); } addPlacementClassName(popupNode, feedback) { const { prefixCls } = this.props; this.removeClassNames(popupNode); addClass(popupNode, `${prefixCls}-placement-${feedbackToPlacement(feedback)}`); } setPopupPosition(dom) { const { placement, offset, position: positionOpts, adjustPosition } = this.props; position(dom, { ...getPlacement(placement, offset), collision: "flipfit", of: this.getTriggerNode(), ...positionOpts, using: (pos, feedback) => { this.addPlacementClassName(dom, feedback); if (positionOpts && positionOpts.using) { positionOpts.using(pos, feedback); } else { dom.style.left = pos.left + "px"; dom.style.top = pos.top + "px"; } if (adjustPosition) { adjustPosition(dom, pos, feedback); } }, }); } updatePopupPosition() { const node = this.getPopupNode(); node && this.setPopupPosition(node); } getPopupComponent() { const { popup, prefixCls, popupClassName, popupMaskClassName, popupProps, popupMaskProps, popupTransition, popupMaskTransition, forceRender, mask, disableMask, maskClosable, popupStyle, popupMaskStyle, destroyPopupOnHide, zIndex, popupRootClassName, popupRootStyle, onBeforeShow, onAfterShow, onBeforeHide, onAfterHide, } = this.props; const { popupVisible } = this.state; const newPopupStyle = { ...popupStyle }; const newPopupMaskStyle = { ...popupMaskStyle }; if (zIndex != null) { newPopupStyle.zIndex = zIndex; newPopupMaskStyle.zIndex = zIndex; } return (React.createElement(Popup, Object.assign({ ref: this.refHandlers.popup, prefixCls: prefixCls, destroyOnClose: destroyPopupOnHide, style: newPopupStyle, className: popupClassName, maskClassName: popupMaskClassName, maskStyle: newPopupMaskStyle, mask: mask, disableMask: disableMask, rootClassName: popupRootClassName, rootStyle: popupRootStyle, forceRender: forceRender }, popupProps, { fixed: false, visible: popupVisible, transition: { ...popupTransition, onEnter: (dom, appearing) => { onBeforeShow?.(dom); this.setPopupPosition(dom); popupTransition?.onEnter?.(dom, appearing); }, onEntered: (dom, appearing) => { // this.setPopupPosition(dom); popupTransition?.onEntered?.(dom, appearing); onAfterShow?.(dom); }, onExit: (dom) => { onBeforeHide?.(dom); popupTransition?.onExit?.(dom); }, onExited: (dom) => { onAfterHide?.(dom); popupTransition?.onExit?.(dom); }, }, onMouseEnter: (e) => { this.clearDelayTimer(); popupProps?.onMouseEnter?.(e); }, onMouseLeave: (e) => { if (this.isMouseLeaveToHide()) { this.onMouseLeave(e); } popupProps?.onMouseLeave?.(e); }, maskTransition: popupMaskTransition, maskProps: { ...popupMaskProps, onClick: (e) => { if (maskClosable) { this.hide(); } popupMaskProps?.onClick?.(e); }, onMouseEnter: (e) => { this.clearDelayTimer(); popupMaskProps?.onMouseEnter?.(e); }, } }), typeof popup === "function" ? popup(this) : popup)); } genNewChildProps(child) { const { checkDefaultPrevented, disabled } = this.props; const newChildProps = {}; if (disabled) { return newChildProps; } if (this.isContextMenuToShow()) { newChildProps.onContextMenu = (e) => { if (child.props.onContextMenu) { child.props.onContextMenu(e); } if (checkDefaultPrevented && e.defaultPrevented) return; this.clearDelayTimer(); this.onContextMenu(e); }; } if (this.isMouseDownToShow() || this.isMouseDownToHide()) { newChildProps.onMouseDown = (e) => { if (child.props.onMouseDown) { child.props.onMouseDown(e); } if (checkDefaultPrevented && e.defaultPrevented) return; this.clearDelayTimer(); this.onTriggerMouseDown(e); }; } if (this.isClickToHide() || this.isClickToShow()) { newChildProps.onClick = (e) => { if (child.props.onClick) { child.props.onClick(e); } if (checkDefaultPrevented && e.defaultPrevented) return; this.clearDelayTimer(); this.onTriggerClick(e); }; } if (this.isMouseEnterToShow()) { newChildProps.onMouseEnter = (e) => { if (child.props.onMouseEnter) { child.props.onMouseEnter(e); } if (checkDefaultPrevented && e.defaultPrevented) return; this.clearDelayTimer(); this.onMouseEnter(e); }; } if (this.isMouseLeaveToHide()) { newChildProps.onMouseLeave = (e) => { if (child.props.onMouseLeave) { child.props.onMouseLeave(e); } if (checkDefaultPrevented && e.defaultPrevented) return; this.clearDelayTimer(); this.onMouseLeave(e); }; } if (this.isFocusToShow()) { newChildProps.onFocus = (e) => { if (child.props.onFocus) { child.props.onFocus(e); } if (checkDefaultPrevented && e.defaultPrevented) return; this.clearDelayTimer(); this.onFocus(e); }; } if (this.isBlurToHide()) { newChildProps.onBlur = (e) => { if (child.props.onBlur) { child.props.onBlur(e); } if (checkDefaultPrevented && e.defaultPrevented) return; this.clearDelayTimer(); this.onBlur(e); }; } return newChildProps; } render() { const { usePortal, container } = this.props; const child = React.Children.only(this.props.children); const trigger = React.cloneElement(child, this.genNewChildProps(child)); let popup = this.getPopupComponent(); if (usePortal) { popup = React.createElement(Portal, { container: container }, popup); } return (React.createElement(React.Fragment, null, trigger, popup)); } } Trigger.defaultProps = { prefixCls: "rw-trigger", placement: "bottomLeft", offset: 0, defaultPopupVisible: false, action: ["click"], showAction: [], hideAction: [], outsideHideEventName: ["mousedown", "click"], delay: 0, getDocument: () => window.document, container: document.body, mask: false, maskClosable: true, destroyPopupOnHide: true, popupProps: {}, popupStyle: {}, popupMaskStyle: {}, checkDefaultPrevented: false, usePortal: true, }; return Trigger; })(); export { Trigger }; export default Trigger;