@zohodesk/components
Version:
In this Package, we Provide Some Basic Components to Build Web App
644 lines (573 loc) • 20.3 kB
JavaScript
/**** Libraries ****/
import React from 'react';
import PropTypes from 'prop-types';
import hoistStatics from 'hoist-non-react-statics';
/**** Methods ****/
import { debounce, isDescendant, isTextSelected, cancelBubblingEffect } from "../../utils/Common.js";
import viewPort from "../../Popup/viewPort";
import { absolutePositionMapping, rtlAbsolutePositionMapping, rtlFixedPositionMapping } from "../../Popup/PositionMapping.js";
import ResizeObserver from '@zohodesk/virtualizer/lib/commons/ResizeObserver.js';
let lastOpenedGroup = [];
let popups = {};
global.closeGroupPopups = function (groupName) {
const groupPopups = popups[groupName] || [];
groupPopups.forEach(popup => {
popup.state.isPopupOpen && popup.setState({
isPopupOpen: false,
isPopupReady: false
});
});
};
const defaultState = {
position: 'bottomCenter',
height: '0px',
positions: {},
//{bottomCenter: true, bottomLeft: false, ...}
positionsOffset: {},
//{bottomCenter: {left: ‘’,top: ‘’ }, .....}
targetOffset: {},
//{height: ‘’, width: ‘’,}
popupOffset: {},
//{height: ‘’, width: ‘’,}
isAbsolutePositioningNeeded: true
};
/* eslint-disable react/no-deprecated */
/* eslint-disable react/prop-types */
const Popup = function (Component) {
let group = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'global';
let needResizeHandling = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
let {
isAbsolutePositioningNeeded: isAbsolutePopup = true,
isArrow: needPopupArrow = false,
customOrder: customPositionOrder = [],
scrollDebounceTime: popupScrollDebounceTime = 0,
closeOnScroll: closeOnScrollPopup = false
} = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
class Popup extends React.Component {
constructor(props) {
super(props);
this.state = {
isPopupOpen: props.isPopupOpen || false,
position: 'bottomCenter',
height: '0px',
isPopupReady: props.isPopupOpen || false,
positions: {},
//{bottomCenter: true, bottomLeft: false, ...}
positionsOffset: {},
//{bottomCenter: {left: ‘’,top: ‘’ }, .....}
targetOffset: {},
//{height: ‘’, width: ‘’,}
popupOffset: {},
//{height: ‘’, width: ‘’,}
isAbsolutePositioningNeeded: true
};
this.togglePopup = this.togglePopup.bind(this);
this.documentClickHandler = this.documentClickHandler.bind(this);
this.documentClickHandler1 = this.documentClickHandler1.bind(this);
this.removeClose = this.removeClose.bind(this);
this.documentKeyupHandler = this.documentKeyupHandler.bind(this);
this.openPopupOnly = this.openPopupOnly.bind(this);
this.closePopupOnly = this.closePopupOnly.bind(this);
this.getTargetRef = this.getTargetRef.bind(this);
this.getContainerRef = this.getContainerRef.bind(this);
this.handlePopupPosition = this.handlePopupPosition.bind(this);
this.handleResize = debounce(this.handleResize.bind(this), 300);
this.handlePopupResize = this.handlePopupResize.bind(this);
this.getIsAbsolutePopup = this.getIsAbsolutePopup.bind(this);
this.getNeedArrow = this.getNeedArrow.bind(this);
this.getCustomPositionOrder = this.getCustomPositionOrder.bind(this);
this.handleOpenPopupPositionChange = this.handleOpenPopupPositionChange.bind(this);
this.getScrollDebounceTime = this.getScrollDebounceTime.bind(this);
this.getCloseOnScrollPopup = this.getCloseOnScrollPopup.bind(this);
this.handleCloseLastOpenedGroup = this.handleCloseLastOpenedGroup.bind(this);
this.handleDocumentMouseDown = this.handleDocumentMouseDown.bind(this);
this.handleDocumentFocus = this.handleDocumentFocus.bind(this);
this.handleGetNeedPrevent = this.handleGetNeedPrevent.bind(this);
this.popupObserver = new ResizeObserver(this.handlePopupResize); //dropBoxSize
this.size = null;
this.isAbsolutePopup = isAbsolutePopup;
this.needPopupArrow = needPopupArrow;
this.customPositionOrder = customPositionOrder;
this.scrollDebounceTime = popupScrollDebounceTime;
this.closeOnScroll = closeOnScrollPopup;
const {
scrollDebounceTime
} = this.getScrollDebounceTime(this);
this.handleScroll = debounce(this.handleScroll.bind(this), scrollDebounceTime);
}
componentDidMount() {
const group = this.getGroup();
const groupPopups = popups[group] || [];
groupPopups.push(this);
popups[group] = groupPopups;
if (Object.keys(popups).length === 1 && groupPopups.length === 1) {
document.addEventListener('click', this.documentClickHandler, false);
document.addEventListener('keyup', this.documentKeyupHandler, false); // document.addEventListener('scroll', this.handleScroll, true);
window.addEventListener('resize', this.handleResize);
document.addEventListener('click', this.documentClickHandler1, true);
document.addEventListener('mousedown', this.handleDocumentMouseDown, true);
document.addEventListener('focus', this.handleDocumentFocus, true);
}
}
componentWillReceiveProps(nextProps) {
const {
isPopupOpen
} = this.state;
if (typeof nextProps.isPopupOpen !== 'undefined' && nextProps.isPopupOpen !== isPopupOpen) {
this.setState({
isPopupOpen: nextProps.isPopupOpen,
isPopupReady: nextProps.isPopupOpen
});
}
}
componentDidUpdate(prevProps, prevState) {
const {
isPopupReady
} = this.state;
const {
isPopupReady: oldStateOpen = false
} = prevState || {};
const {
dropElement
} = this;
const {
needResizeHandling: propResizeHandling
} = this.props;
if (oldStateOpen !== isPopupReady) {
if (isPopupReady && dropElement && (propResizeHandling !== undefined ? propResizeHandling : needResizeHandling)) {
this.popupObserver.replaceObservationElement(dropElement);
} else if (!isPopupReady) {
this.size = null;
this.popupObserver.disconnect();
}
}
}
componentWillUnmount() {
const group = this.getGroup();
popups = Object.keys(popups).reduce((res, groupName) => {
if (groupName === group) {
const groupPopups = popups[group];
const newGroupPopups = groupPopups.filter(popup => popup !== this);
newGroupPopups.length === 0 && lastOpenedGroup.indexOf(group) >= 0 && lastOpenedGroup.splice(lastOpenedGroup.indexOf(group), 1);
res[group] = newGroupPopups;
}
return res;
}, popups);
let noPopups = true;
for (const i in popups) {
if (popups[i].length >= 1) {
noPopups = false;
break;
}
}
if (this.popupObserver) {
this.popupObserver.disconnect();
}
if (noPopups) {
document.removeEventListener('click', this.documentClickHandler);
document.removeEventListener('keyup', this.documentKeyupHandler); // document.removeEventListener('scroll', this.handleScroll);
window.removeEventListener('resize', this.handleResize);
document.removeEventListener('click', this.documentClickHandler1, true);
document.removeEventListener('mousedown', this.handleDocumentMouseDown, true);
document.removeEventListener('focus', this.handleDocumentFocus, true);
}
}
getGroup() {
const {
popupGroup
} = this.props;
return popupGroup || group;
}
getNeedArrow(popup) {
const {
isArrow
} = popup.props;
const {
needPopupArrow
} = popup;
return isArrow !== undefined ? isArrow : needPopupArrow;
}
getScrollDebounceTime(popup) {
const {
scrollDebounceTime
} = popup.props;
const {
scrollDebounceTime: popupScrollDebounceTime
} = popup;
return scrollDebounceTime !== undefined ? scrollDebounceTime : popupScrollDebounceTime;
}
getCloseOnScrollPopup(popup) {
const {
closeOnScroll
} = popup.props;
const {
closeOnScroll: closeOnScrollPopup
} = popup;
return closeOnScroll !== undefined ? closeOnScroll : closeOnScrollPopup;
}
getIsAbsolutePopup(popup) {
const {
isAbsolutePositioningNeeded
} = popup.props;
const {
isAbsolutePopup
} = popup;
return isAbsolutePositioningNeeded !== undefined ? isAbsolutePositioningNeeded : isAbsolutePopup;
}
getCustomPositionOrder(popup) {
const {
customOrder = []
} = popup.props;
const {
customPositionOrder
} = popup;
return customOrder.length !== 0 ? customOrder : customPositionOrder;
}
togglePopup(e, defaultPosition) {
const group = this.getGroup();
this.removeClose(e);
const {
isPopupOpen
} = this.state;
const groupPopups = popups[group] || [];
lastOpenedGroup = !isPopupOpen && lastOpenedGroup.indexOf(group) === -1 ? [group, ...lastOpenedGroup] : lastOpenedGroup;
isPopupOpen && lastOpenedGroup.splice(0, 1);
groupPopups.forEach(popup => {
if (popup !== this && popup.state.isPopupOpen) {
popup.setState({
isPopupOpen: false,
isPopupReady: false
});
}
});
if (isPopupOpen) {
this.setState({
isPopupOpen: false,
isPopupReady: false
});
} else {
this.handlePopupPosition(defaultPosition);
}
}
openPopupOnly(e, defaultPosition) {
const group = this.getGroup();
this.removeClose(e);
lastOpenedGroup = lastOpenedGroup.indexOf(group) === -1 ? [group, ...lastOpenedGroup] : lastOpenedGroup;
this.handlePopupPosition(defaultPosition);
}
closePopupOnly(e) {
this.removeClose(e);
lastOpenedGroup.splice(0, 1);
const {
isPopupOpen
} = this.state;
if (isPopupOpen) {
this.setState({
isPopupOpen: false,
isPopupReady: false
});
}
}
handleCloseLastOpenedGroup() {
const groupPopups = lastOpenedGroup.length ? popups[lastOpenedGroup[0]] || [] : [];
lastOpenedGroup.splice(0, 1);
groupPopups.forEach(popup => {
popup.state.isPopupOpen && popup.setState({
isPopupOpen: false,
isPopupReady: false
});
});
}
handleDocumentMouseDown(e) {
const needPrevent = this.handleGetNeedPrevent(e);
if (needPrevent) {
this.removeClose(e);
}
}
handleDocumentFocus(e) {
const needPrevent = this.handleGetNeedPrevent(e);
if (needPrevent) {
this.removeClose(e);
}
}
handleGetNeedPrevent(e) {
let needPrevent = false;
if (lastOpenedGroup.length > 1) {
const {
target
} = e;
const groupPopups = lastOpenedGroup.length ? popups[lastOpenedGroup[0]] : [];
let openedPopup = null; // eslint-disable-next-line guard-for-in
for (const i in groupPopups) {
const {
isPopupOpen
} = groupPopups[i].state;
if (isPopupOpen) {
openedPopup = groupPopups[i];
break;
}
}
if (openedPopup) {
const {
dropElement,
placeHolderElement
} = openedPopup;
const isDropBoxChild = isDescendant(dropElement, target);
const isTargetChild = isDescendant(placeHolderElement, target); // const isPopupMassUpdateChild = isDescendant(
// massUpdateParent,
// dropElement
// );
if (!isDropBoxChild && !isTargetChild // && isPopupMassUpdateChild
) {
needPrevent = true;
}
}
}
return needPrevent;
}
documentClickHandler1(e) {
const needPrevent = this.handleGetNeedPrevent(e);
if (needPrevent) {
this.removeClose(e);
this.handleCloseLastOpenedGroup();
}
}
documentClickHandler() {
try {
Object.keys(popups).forEach(groupName => {
const groupPopups = popups[groupName] || [];
groupPopups.forEach(popup => {
popup.state.isPopupOpen && (!popup.props.checkBeforeClose || popup.props.checkBeforeClose && popup.props.checkBeforeClose()) && !isTextSelected() && popup.setState({
isPopupOpen: false,
isPopupReady: false
});
});
});
lastOpenedGroup = [];
} catch (e) {// eslint-disable-next-line no-console
//console.error('popup component not unmounted properly', e);
}
}
documentKeyupHandler(e) {
try {
if (e.keyCode === 27) {
this.handleCloseLastOpenedGroup();
}
} catch (e) {// eslint-disable-next-line no-console
//console.log('error', e);
}
}
removeClose(e) {
// e && e.preventDefault && e.preventDefault();
cancelBubblingEffect(e);
}
handlePopupPosition() {
let defaultPosition = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'bottomCenter';
let isResizeHandling = arguments.length > 1 ? arguments[1] : undefined;
// isResizeHandling --->>> Window resize and dropBox resize
const {
direction
} = this.context || {};
const {
placeHolderElement,
dropElement
} = this;
const needArrow = this.getNeedArrow(this);
const isAbsolute = this.getIsAbsolutePopup(this);
const customOrder = this.getCustomPositionOrder(this);
if (direction === 'rtl') {
defaultPosition = isAbsolute ? rtlAbsolutePositionMapping[defaultPosition] : rtlFixedPositionMapping[defaultPosition];
} else {
defaultPosition = isAbsolute ? absolutePositionMapping[defaultPosition] : defaultPosition;
}
if (!placeHolderElement && !dropElement) {
this.setState({
isPopupOpen: true,
isPopupReady: true
});
return;
}
const setPosition = () => {
requestAnimationFrame(() => {
const {
placeHolderElement,
dropElement
} = this;
const {
position,
isPopupReady
} = this.state;
const scrollContainer = placeHolderElement.closest('[data-scroll=true]') || document.body;
const betterPosition = viewPort.betterView(dropElement, placeHolderElement, defaultPosition, scrollContainer, {
needArrow,
isAbsolute,
customOrder
});
const {
view,
views,
viewsOffset,
targetOffset,
popupOffset
} = betterPosition || {};
if (position !== view || !isPopupReady) {
this.setState({
isPopupReady: true,
position: view,
positions: views,
positionsOffset: viewsOffset,
targetOffset,
popupOffset,
isAbsolutePositioningNeeded: isAbsolute
});
}
});
};
if (isResizeHandling) {
setPosition();
} else {
this.defaultPosition = defaultPosition;
this.setState({
isPopupOpen: true,
isPopupReady: false
}, setPosition);
}
}
handleOpenPopupPositionChange() {
Object.keys(popups).forEach(groupName => {
const groupPopups = popups[groupName] || [];
groupPopups.forEach(popup => {
if (popup.state.isPopupOpen) {
const {
placeHolderElement,
dropElement,
defaultPosition
} = popup;
const {
position,
positionsOffset = {}
} = popup.state;
if (placeHolderElement && dropElement) {
const scrollContainer = placeHolderElement.closest('[data-scroll=true]') || document.body;
requestAnimationFrame(() => {
const needArrow = this.getNeedArrow(popup);
const isAbsolute = this.getIsAbsolutePopup(popup);
const customOrder = this.getCustomPositionOrder(popup);
const betterPosition = viewPort.betterView(dropElement, placeHolderElement, defaultPosition, scrollContainer, {
needArrow,
isAbsolute,
customOrder
});
const {
view,
views,
viewsOffset = {},
targetOffset,
popupOffset
} = betterPosition || {};
const {
left: oldLeft = '',
top: oldTop = ''
} = positionsOffset[position] || {};
const {
left = '',
top = ''
} = viewsOffset[view] || {};
const changeState = isAbsolute ? position !== view : oldLeft !== left || oldTop !== top; // let isInViewPort = viewPort.isInViewPort(
// placeHolderElement,
// scrollContainer
// );
if (changeState) {
popup.setState({
position: view,
positions: views,
positionsOffset: viewsOffset,
targetOffset,
popupOffset,
isAbsolutePositioningNeeded: isAbsolute
});
} // if (!isInViewPort && !isAbsolute) {
// popup.setState({ isPopupOpen: false, isPopupReady: false });
// } else if (view && changeState) {
// popup.setState({
// position: view,
// positions: views,
// positionsOffset: viewsOffset,
// targetOffset,
// popupOffset,
// isAbsolutePositioningNeeded: isAbsolute
// });
// }
});
}
}
});
});
}
handleResize() {
this.handleOpenPopupPositionChange();
}
handleScroll(e) {
// this.handleOpenPopupPositionChange();
const {
closeOnScroll
} = this.getCloseOnScrollPopup(this);
const {
isPopupReady
} = this.state;
if (isPopupReady) {
console.log('onscrollPopupREady');
}
if (isPopupReady && closeOnScroll) {
console.log(this, 'handle Scroll');
this.togglePopup(e);
}
}
handlePopupResize(popupSize) {
const {
height,
width
} = popupSize;
const {
height: oldHeight = 0,
width: oldWidth = 0
} = this.size || {};
const {
isPopupReady,
position
} = this.state;
if (isPopupReady && this.size && (oldHeight !== height || width !== oldWidth)) {
this.handlePopupPosition(position, true);
}
this.size = popupSize;
}
getTargetRef(el) {
this.placeHolderElement = el;
}
getContainerRef(el) {
this.dropElement = el;
}
render() {
const {
isPopupReady,
isPopupOpen
} = this.state;
const localState = isPopupReady ? this.state : {};
return /*#__PURE__*/React.createElement(Component, { ...this.props,
...this.state,
openPopupOnly: this.openPopupOnly,
closePopupOnly: this.closePopupOnly,
togglePopup: this.togglePopup,
removeClose: this.removeClose,
getTargetRef: this.getTargetRef,
getContainerRef: this.getContainerRef
});
}
}
Popup.displayName = Component.displayName || Component.name || Popup.name;
Popup.contextTypes = {
direction: PropTypes.string
};
return hoistStatics(Popup, Component);
};
export default Popup;