react-widget-trigger
Version:
511 lines (510 loc) • 21.2 kB
JavaScript
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;