import addClass from 'dom-helpers/addClass' ; import removeClass from 'dom-helpers/removeClass' ; import css from 'dom-helpers/css' ; import getScrollbarSize from 'dom-helpers/scrollbarSize' ; import isOverflowing from './isOverflowing' ; import { ariaHidden , hideSiblings , showSiblings } from './manageAriaHidden' ; function findIndexOf < T > ( arr , cb ) { let idx = - 1 ; arr . some ( ( d , i ) => { if ( cb ( d , i ) ) { idx = i ; return true ; } return false ; } ) ; return idx ; } export interface ModalInstance { dialog : Element ; backdrop : Element ; } export type ContainerState = Record < string , any > & { isOverflowing ? : boolean ; style ? : Partial < CSSStyleDeclaration > ; modals : ModalInstance [ ] ; } ; internal class ModalManager { readonly hideSiblingNodes : boolean ; readonly handleContainerOverflow : boolean ; readonly modals : ModalInstance [ ] ; readonly containers : HTMLElement [ ] ; readonly data : ContainerState [ ] ; readonly scrollbarSize : number ; constructor ( { hideSiblingNodes = true , handleContainerOverflow = true , } = { } ) { this . hideSiblingNodes = hideSiblingNodes ; this . handleContainerOverflow = handleContainerOverflow ; this . modals = [ ] ; this . containers = [ ] ; this . data = [ ] ; this . scrollbarSize = getScrollbarSize ( ) ; } isContainerOverflowing ( modal ) { const data = this . data [ this . containerIndexFromModal ( modal ) ] ; return data && data . overflowing ; } containerIndexFromModal ( modal ) { return findIndexOf ( this . data , ( d ) => d . modals . indexOf ( modal ) !== - 1 ) ; } setContainerStyle ( containerState , container ) { const style = { overflow : 'hidden' } ; containerState . style = { overflow : container . style . overflow , paddingRight : container . style . paddingRight , } ; if ( containerState . overflowing ) { style . paddingRight = `${ parseInt ( css ( container , 'paddingRight' ) || '0' , 10 ) + this . scrollbarSize }px` ; } css ( container , style as any ) ; } removeContainerStyle ( containerState , container ) { Object . assign ( container . style , containerState . style ) ; } add ( modal , container , className ? ) { let modalIdx = this . modals . indexOf ( modal ) ; const containerIdx = this . containers . indexOf ( container ) ; if ( modalIdx !== - 1 ) { return modalIdx ; } modalIdx = this . modals . length ; this . modals . push ( modal ) ; if ( this . hideSiblingNodes ) { hideSiblings ( container , modal ) ; } if ( containerIdx !== - 1 ) { this . data [ containerIdx ] . modals . push ( modal ) ; return modalIdx ; } const data = { modals : [ modal ] , classes : className ? className . split ( /\s+/ ) : [ ] , overflowing : isOverflowing ( container ) , } ; if ( this . handleContainerOverflow ) { this . setContainerStyle ( data , container ) ; } data . classes . forEach ( addClass . bind ( null , container ) ) ; this . containers . push ( container ) ; this . data . push ( data ) ; return modalIdx ; } remove ( modal ) { const modalIdx = this . modals . indexOf ( modal ) ; if ( modalIdx === - 1 ) { return ; } const containerIdx = this . containerIndexFromModal ( modal ) ; const data = this . data [ containerIdx ] ; const container = this . containers [ containerIdx ] ; data . modals . splice ( data . modals . indexOf ( modal ) , 1 ) ; this . modals . splice ( modalIdx , 1 ) ; if ( data . modals . length === 0 ) { data . classes . forEach ( removeClass . bind ( null , container ) ) ; if ( this . handleContainerOverflow ) { this . removeContainerStyle ( data , container ) ; } if ( this . hideSiblingNodes ) { showSiblings ( container , modal ) ; } this . containers . splice ( containerIdx , 1 ) ; this . data . splice ( containerIdx , 1 ) ; } else if ( this . hideSiblingNodes ) { const { backdrop , dialog } = data . modals [ data . modals . length - 1 ] ; ariaHidden ( false , dialog ) ; ariaHidden ( false , backdrop ) ; } } isTopModal ( modal ) { return ( ! ! this . modals . length && this . modals [ this . modals . length - 1 ] === modal ) ; } } export default ModalManager ;