import m from 'mithril' ; import classnames from 'classnames' ; import { Transition , TransitionState } from 'mithril-transition-group' ; import { IAttrs , Classes } from '../../_shared' ; export interface ICollapseAttrs extends IAttrs { /** Toggles visibility */ isOpen ? : boolean ; default duration ? : number ; } export class Collapse implements m . Component < ICollapseAttrs > { private height : number = 0 ; private duration : number ; private contentEl : HTMLElement ; private containerStyles = { height : 0 , overflow : 'hidden' , transition : '' } ; public oninit ( { attrs } ) { this . duration = attrs . duration || 300 ; this . containerStyles . transition = `height ${ this . duration }ms ease-out` ; } public onbeforeupdate ( ) { if ( this . contentEl ) { this . height = this . getContentHeight ( this . contentEl ) ; } } public view ( { attrs , children } ) { const classes = classnames ( Classes . COLLAPSE , attrs . class ) ; return m ( Transition , { isVisible : attrs . isOpen , onEnter : this . handleEnter , onExit : this . handleExit , content : ( state ) => { const containerTransitionStyles = { entering : { height : this . height + 'px' } , entered : { height : 'auto' , transition : 'none' } , exiting : { height : '0px' } } ; const bodyTransitionStyles = { entering : { transform : 'translateY(0px)' , transition : `transform ${ this . duration }ms ease-out` } , exiting : { transform : `translateY(${ - this . height }px)` , transition : `transform ${ this . duration }ms ease-out` } } ; const body = m ( '' , { class : Classes . COLLAPSE_BODY , style : { ... bodyTransitionStyles [ state ] } } , children ) ; const container = m ( '' , { class : classes , style : { ... this . containerStyles , ... containerTransitionStyles [ state ] , ... attrs . style as CSSStyleDeclaration } } , body ) ; return container ; } , timeout : this . duration } ) ; } private handleEnter = ( node ) => { const body = node . querySelector ( `.${ Classes . COLLAPSE_BODY }` ) as HTMLElement ; this . contentEl = body . children [ 0 ] as HTMLElement ; this . height = this . getContentHeight ( this . contentEl ) ; body . style . transform = `translateY(${ - this . height }px)` ; } ; private handleExit = ( node ) => { node . style . height = `${ this . height }px` ; } ; private getContentHeight ( element ) { if ( ! element ) return 0 ; const styles = window . getComputedStyle ( element ) ; const margin = parseFloat ( styles . marginTop ! ) + parseFloat ( styles . marginBottom ! ) ; return Math . ceil ( element . offsetHeight + margin ) ; } }