UNPKG

@adaptabletools/adaptable-cjs

Version:

Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements

228 lines (227 loc) 9.82 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getConstrainRect = exports.getConstrainElement = void 0; const tslib_1 = require("tslib"); const React = tslib_1.__importStar(require("react")); const react_1 = require("react"); const debounce_1 = tslib_1.__importDefault(require("lodash/debounce")); const batchUpdate_1 = tslib_1.__importDefault(require("../utils/batchUpdate")); const selectParent_1 = tslib_1.__importDefault(require("../utils/selectParent")); const useProperty_1 = tslib_1.__importDefault(require("../utils/useProperty")); const Overlay_1 = tslib_1.__importDefault(require("./Overlay")); const join_1 = tslib_1.__importDefault(require("../utils/join")); const usePrevious_1 = tslib_1.__importDefault(require("../utils/usePrevious")); const utils_1 = require("./utils"); const useAgGridClassName_1 = tslib_1.__importDefault(require("./useAgGridClassName")); const contains_1 = tslib_1.__importDefault(require("../utils/contains")); const UIHelper_1 = require("../../View/UIHelper"); const InfiniteTable_1 = require("../InfiniteTable"); const AdaptableContext_1 = require("../../View/AdaptableContext"); const getConstrainElement = (target, constrainTo) => { let el = null; if (typeof constrainTo === 'string') { el = (0, selectParent_1.default)(constrainTo, target); } if (typeof constrainTo === 'function') { el = constrainTo(target); } return el; }; exports.getConstrainElement = getConstrainElement; const getConstrainRect = (target, constrainTo) => { let el = (0, exports.getConstrainElement)(target, constrainTo); if (el && el.tagName) { return (0, utils_1.getRect)(el); } return (0, utils_1.getDocRect)(); }; exports.getConstrainRect = getConstrainRect; let portalElement; const ensurePortalElement = () => { if (!(0, UIHelper_1.isBrowserDocumentAvailable)()) { return; } if (portalElement) { return; } portalElement = document.createElement('div'); portalElement.style.position = 'absolute'; portalElement.style.zIndex = '999999'; portalElement.style.top = '0px'; portalElement.style.left = '0px'; document.body.appendChild(portalElement); }; const defaultProps = { showEvent: 'mouseenter', hideEvent: 'mouseleave', anchor: 'vertical', targetOffset: 10, defaultZIndex: 1000000, opacityTransitionDuration: '250ms', }; const OverlayTrigger = React.forwardRef((givenProps, ref) => { const props = { ...defaultProps, ...givenProps }; const adaptable = (0, AdaptableContext_1.useAdaptable)(); let { visible: _, showTriangle, showEvent, hideEvent, render, targetOffset, preventPortalEventPropagation = false, defaultZIndex, anchor, hideDelay = 0, opacityTransitionDuration, onVisibleChange, alignPosition = [ // overlay - target ['TopLeft', 'BottomLeft'], ['TopRight', 'BottomRight'], ['TopCenter', 'BottomCenter'], ['BottomCenter', 'TopCenter'], ['TopRight', 'BottomCenter'], ['TopRight', 'BottomLeft'], ['TopRight', 'BottomLeft'], ['BottomLeft', 'TopLeft'], ['BottomRight', 'TopRight'], ['BottomRight', 'TopCenter'], ['BottomRight', 'TopLeft'], ['TopRight', 'CenterLeft'], ['TopRight', 'TopLeft'], ['TopLeft', 'TopRight'], ['CenterRight', 'CenterLeft'], ['CenterLeft', 'CenterRight'], ], constrainTo, target: targetProp, ...domProps } = props; const { showOverlay, clearAll: clearAllOverlays, hideOverlay, portal, } = (0, InfiniteTable_1.useOverlay)({ portalContainer: portalElement, }); const domRef = (0, react_1.useRef)(null); const targetRef = (0, react_1.useRef)(null); const overlayRef = (0, react_1.useRef)(null); const [visible, doSetVisible] = (0, useProperty_1.default)(props, 'visible', false); const hideTimeoutRef = (0, react_1.useRef)(null); const setVisible = React.useCallback( // visible state may quickly change from true -> false -> true // when moving the mouse cursor from the trigger to the overlay // for this case we debounce the visible change for a very small amount of time (0, debounce_1.default)((visible) => { onVisibleChange?.(visible); if (!visible) { hideTimeoutRef.current = setTimeout(() => { hideTimeoutRef.current = null; doSetVisible(false); }, hideDelay); return; } if (hideTimeoutRef.current) { clearTimeout(hideTimeoutRef.current); hideTimeoutRef.current = null; } doSetVisible(true); }, 50), []); const prevVisible = (0, usePrevious_1.default)(visible, false); ensurePortalElement(); const onShow = React.useCallback((event) => { const target = targetRef.current; if (event && preventPortalEventPropagation && !(0, contains_1.default)(target, event.target)) { // because of how React portals behave - see https://github.com/facebook/react/issues/11387 // after the portal is rendered, even though an item is in a separate dom parent, if it's in the portal, // events from it are propagated to the components in the portal and thus break some stuff return; } (0, batchUpdate_1.default)(() => { setVisible(true); }).commit(); }, [constrainTo, preventPortalEventPropagation]); const onHide = React.useCallback((_event) => { const shouldHide = props.shouldHide ? props.shouldHide(_event) : true; if (shouldHide) { setVisible(false); } }, [setVisible]); (0, react_1.useEffect)(() => { if (ref) { const api = { show: onShow, hide: onHide, }; if (typeof ref === 'function') { ref(api); } else { ref.current = api; } } }, [ref]); (0, react_1.useEffect)(() => { let target = domRef.current.previousSibling; if (targetProp) { target = targetProp(target); } if (!target) { adaptable.logger.warn('No OverlayTrigger target - make sure you render a child inside the OverlayTrigger, which will be the overlay target'); return; } targetRef.current = target; let attached = false; let onShowFn = onShow; let onHideFn = onHide; if (props.visible === undefined) { attached = true; target.addEventListener(showEvent, onShowFn); target.addEventListener(hideEvent, onHideFn); } if (props.visible && !prevVisible) { onShowFn(); } return () => { if (attached) { target.removeEventListener(showEvent, onShowFn); target.removeEventListener(hideEvent, onHideFn); } }; }, [props.visible, showEvent, hideEvent, onShow, onHide]); React.useEffect(() => { const target = targetRef.current; if (!target) { return; } const targetWidth = target.getBoundingClientRect().width; if ((prevVisible && !visible) || visible) { const overlayContent = (React.createElement(Overlay_1.default, { ...domProps, ref: (node) => { if (overlayRef.current && overlayRef.current != node) { overlayRef.current.removeEventListener(showEvent, onShow); overlayRef.current.removeEventListener(hideEvent, onHide); } overlayRef.current = node; if (node) { node.addEventListener(showEvent, onShow); node.addEventListener(hideEvent, onHide); } }, className: (0, join_1.default)('ab-Overlay', showTriangle ? 'ab-Overlay--show-triangle' : '', agGridClassName, domProps.className), style: { transition: `opacity ${opacityTransitionDuration}` }, visible: visible, onTransitionEnd: () => { if (!visible) { clearAllOverlays(); hideOverlay('overlay-trigger'); } } }, props.render({ targetWidth: targetWidth }))); let preparedConstrainTo; if (constrainTo) { preparedConstrainTo = (0, exports.getConstrainElement)(targetRef.current, constrainTo); } // show only if visible or if it was visible and now it is invisible const alignToRect = (0, utils_1.getRect)(target, targetOffset ?? 0); const showOverlayOptions = { id: 'overlay-trigger', // add id on props so component is not unmounted alignPosition, constrainTo: preparedConstrainTo?.getBoundingClientRect?.() ?? true, // at least constrain to document alignTo: alignToRect, }; showOverlay(() => overlayContent, showOverlayOptions); } else { clearAllOverlays(); } }, [visible, props.render]); const agGridClassName = (0, useAgGridClassName_1.default)([visible]); return (React.createElement(React.Fragment, null, React.Children.only(props.children), React.createElement("div", { "data-name": "OverlayTrigger", "data-visible": visible, ref: domRef, style: { visibility: 'hidden', flex: 'none', width: 0, height: 0, pointerEvents: 'none', display: 'inline-flex', } }), portal)); }); exports.default = OverlayTrigger;