@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
JavaScript
;
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;