/** * @file Overlay * @description * @author fex */ import { Position as BasePosition, Portal, RootCloseWrapper } from 'react-overlays'; import {findDOMNode} from 'react-dom'; import React from 'react'; import {calculatePosition, getContainer, ownerDocument} from '../utils/dom'; import {autobind, noop} from '../utils/helper'; import {resizeSensor, getComputedStyle} from '../utils/resize-sensor'; // @ts-ignore BasePosition.propTypes.placement = () => null; // @ts-ignore class Position extends BasePosition { props: any; _lastTarget: any; resizeDispose: Array<() => void>; watchedTarget: any; setState: (state: any) => void; updatePosition(target: any) { this._lastTarget = target; if (!target) { return this.setState({ positionLeft: 0, positionTop: 0, arrowOffsetLeft: null, arrowOffsetTop: null }); } const watchTargetSizeChange = this.props.watchTargetSizeChange; const overlay = findDOMNode(this as any) as HTMLElement; const container = getContainer( this.props.container, ownerDocument(this).body ); if ( (!this.watchedTarget || this.watchedTarget !== target) && getComputedStyle(target, 'position') !== 'static' ) { this.resizeDispose?.forEach(fn => fn()); this.watchedTarget = target; this.resizeDispose = [ watchTargetSizeChange !== false ? resizeSensor(target, () => this.updatePosition(target)) : noop, resizeSensor(overlay, () => this.updatePosition(target)) ]; } this.setState( calculatePosition( this.props.placement, overlay, target, container, this.props.containerPadding ) ); } componentWillUnmount() { this.resizeDispose?.forEach(fn => fn()); } } interface OverlayProps { placement?: string; show?: boolean; transition?: React.ReactType; containerPadding?: number; shouldUpdatePosition?: boolean; rootClose?: boolean; onHide?(props: any, ...args: any[]): any; container?: React.ReactNode | Function; target?: React.ReactNode | Function; watchTargetSizeChange?: boolean; onEnter?(node: HTMLElement): any; onEntering?(node: HTMLElement): any; onEntered?(node: HTMLElement): any; onExit?(node: HTMLElement): any; onExiting?(node: HTMLElement): any; onExited?(node: HTMLElement): any; } interface OverlayState { exited: boolean; } export default class Overlay extends React.Component< OverlayProps, OverlayState > { static defaultProps = { placement: 'auto' }; constructor(props: OverlayProps) { super(props as any); this.state = { exited: !props.show }; } componentWillReceiveProps(nextProps: OverlayProps) { if (nextProps.show) { this.setState({exited: false}); } else if (!nextProps.transition) { // Otherwise let handleHidden take care of marking exited. this.setState({exited: true}); } } @autobind onHiddenListener(node: HTMLElement) { this.setState({exited: true}); if (this.props.onExited) { this.props.onExited(node); } } render() { const { container, containerPadding, target, placement, shouldUpdatePosition, rootClose, children, watchTargetSizeChange, transition: Transition, ...props } = this.props; const mountOverlay = props.show || (Transition && !this.state.exited); if (!mountOverlay) { // Don't bother showing anything if we don't have to. return null; } let child = children; // Position is be inner-most because it adds inline styles into the child, // which the other wrappers don't forward correctly. child = ( // @ts-ignore {child} ); if (Transition) { let {onExit, onExiting, onEnter, onEntering, onEntered} = props; // This animates the child node by injecting props, so it must precede // anything that adds a wrapping div. child = ( {child} ); } // This goes after everything else because it adds a wrapping div. if (rootClose) { child = ( // @ts-ignore {child} ); } // @ts-ignore return {child}; } }