import * as React from 'react'; import {withOverlayStack} from '../overlays/OverlayStack'; import {isContainsInUpperOverlays, uniqueId} from './utils'; import {WithStackSynchronyzer, IProps, PropsWithDefault, IMixedProps, IInputProps} from './OverlayStackSynchronizer.types'; /** * HOC-decorator for syncing the overlay with glass. * * Tracks the state of the overlay and adds/deletes / changes overlay data from the stack. */ export const withOverlayStackSynchronyzer: WithStackSynchronyzer = (Child: React.ComponentType) => { const StackSynchronyzer: React.ComponentType = class extends React.PureComponent { static defaultProps = { hasBackdrop: false }; static displayName = `WithStackSynchronyzer(${Child.displayName || Child.name || 'unknown'})`; /** * A unique ID is generated for each overlay when it is created. * * Thanks to ID, you can track the position of the overlay in the stack. * * ATTENTION! getting an id from this. props is only allowed during testing. */ id: string = this.props.id || uniqueId(); /** * Is the DOM element contained in the layers above? */ private isContainsInUpperOverlays = (element: Element) => { return isContainsInUpperOverlays(this.id, element, this.props.overlayStack.stack); }; override componentDidMount () { const {isOpen, hasBackdrop, overlayStack} = this.props as PropsWithDefault; if (isOpen) { overlayStack.add({ id: this.id, hasBackdrop }); } } override componentDidUpdate (prevProps: IProps) { const props = this.props as PropsWithDefault; const wasUpdatedBackdrop = prevProps.hasBackdrop !== props.hasBackdrop; const wasUpdatedVisibility = prevProps.isOpen !== props.isOpen; const isOpen = wasUpdatedVisibility && props.isOpen; const isClose = wasUpdatedVisibility && !props.isOpen; if (isOpen) { props.overlayStack.add({ id: this.id, hasBackdrop: props.hasBackdrop }); } else if (isClose) { props.overlayStack.remove(this.id); } else if (wasUpdatedBackdrop) { props.overlayStack.update({id: this.id, hasBackdrop: props.hasBackdrop}); } } override componentWillUnmount () { const {isOpen, overlayStack} = this.props as PropsWithDefault; if (isOpen) { overlayStack.remove(this.id); } } override render () { const {overlayStack, ...otherProps} = this.props as PropsWithDefault; let isLast = false; let isLastBackdrop = false; const stackLength = overlayStack.stack.length; if (stackLength) { isLast = overlayStack.stack[stackLength - 1].id === this.id; const backdropStack = overlayStack.stack.filter(({hasBackdrop}) => hasBackdrop); if (backdropStack.length) { isLastBackdrop = backdropStack[backdropStack.length - 1].id === this.id; } } const mixedProps = { id: this.id, isLast, isLastBackdrop, isContainsInUpperOverlays: this.isContainsInUpperOverlays }; const childProps = { ...otherProps, ...mixedProps }; return ; } }; return withOverlayStack((overlayStack) => ({overlayStack}))(StackSynchronyzer); };