import * as React from 'react'; import { Component, ComponentClass } from 'react'; import targetIsDescendant from '../../utils/targetIsDescendant'; const DEFAULT_ATTACH_DELAY = 200; export interface Options { attachDelay: number; shouldAttach(props, instance): boolean; } export interface WithClickOutsideProps { onClickOutside(): void; onClickOutsideActive?: boolean; } export type WithClickOutsideHOC = ( decorated: ComponentClass, ) => ComponentClass; export default function withClickOutside({ attachDelay, shouldAttach, }: Options): WithClickOutsideHOC { return function(DecoratedComponent) { return class OutsideClickable extends Component< WithClickOutsideProps & T, {} > { attachTimeout; isAttached = false; rootNode: HTMLElement | null = null; componentDidMount() { if (this.shouldAttach()) { this.addEventListener(); } } componentDidUpdate() { if (this.shouldAttach()) { this.addEventListener(); } else { this.removeEventListener(); } } componentWillUnmount() { this.clearAttachTimeout(); this.removeEventListener(); } addEventListener() { this.clearAttachTimeout(); this.attachTimeout = setTimeout(() => { if (!this.isAttached) { document.addEventListener('click', this.handleOutsideClick, false); this.isAttached = true; } }, attachDelay || DEFAULT_ATTACH_DELAY); } removeEventListener() { this.clearAttachTimeout(); if (this.isAttached) { document.removeEventListener('click', this.handleOutsideClick); this.isAttached = false; } } clearAttachTimeout() { if (this.attachTimeout) { clearTimeout(this.attachTimeout); } } handleOutsideClick = event => { if (this.props.onClickOutside) { let isDescendant: undefined | boolean; try { if (this.rootNode) { isDescendant = targetIsDescendant( event.target as HTMLElement, this.rootNode, ); } } catch (error) { // Prevent againt unmounting self (May happen with tooltips) } if (!isDescendant) { this.props.onClickOutside(); } } }; handleRef = node => { this.rootNode = node; }; shouldAttach = () => this.props.onClickOutsideActive || (shouldAttach && shouldAttach(this.props, this.rootNode)); render() { const { onClickOutside, onClickOutsideActive, ...other } = this .props as any; return ; } }; }; }