UNPKG

@zohodesk/components

Version:

In this Package, we Provide Some Basic Components to Build Web App

644 lines (573 loc) 20.3 kB
/**** 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;