license import { MDCComponent } from '@material/base/component' ; import { SpecificEventListener } from '@material/base/types' ; import { FocusTrap } from '@material/dom/focus-trap' ; import { closest , matches } from '@material/dom/ponyfill' ; import { MDCRipple } from '@material/ripple/component' ; import { MDCDialogAdapter } from './adapter' ; import { MDCDialogFoundation } from './foundation' ; import { MDCDialogCloseEventDetail } from './types' ; import * as util from './util' ; import { MDCDialogFocusTrapFactory } from './util' ; const { strings } = MDCDialogFoundation ; export class MDCDialog extends MDCComponent < MDCDialogFoundation > { get isOpen ( ) { return this . foundation . isOpen ( ) ; } get escapeKeyAction ( ) { return this . foundation . getEscapeKeyAction ( ) ; } set escapeKeyAction ( action ) { this . foundation . setEscapeKeyAction ( action ) ; } get scrimClickAction ( ) { return this . foundation . getScrimClickAction ( ) ; } set scrimClickAction ( action ) { this . foundation . setScrimClickAction ( action ) ; } get autoStackButtons ( ) { return this . foundation . getAutoStackButtons ( ) ; } set autoStackButtons ( autoStack ) { this . foundation . setAutoStackButtons ( autoStack ) ; } static override attachTo ( root ) { return new MDCDialog ( root ) ; } private buttonRipples ! : MDCRipple [ ] ; private buttons ! : HTMLButtonElement [ ] ; private container ! : HTMLElement ; private content ! : HTMLElement | null ; private defaultButton ! : HTMLButtonElement | null ; private focusTrap ! : FocusTrap ; private focusTrapFactory ! : MDCDialogFocusTrapFactory ; private handleClick ! : SpecificEventListener < 'click' > ; private handleKeydown ! : SpecificEventListener < 'keydown' > ; private handleDocumentKeydown ! : SpecificEventListener < 'keydown' > ; private handleOpening ! : EventListener ; private handleClosing ! : ( ) => void ; override initialize ( focusTrapFactory = ( el , focusOptions ) => new FocusTrap ( el , focusOptions ) ) { const container = this . root . querySelector < HTMLElement > ( strings . CONTAINER_SELECTOR ) ; if ( ! container ) { throw new Error ( `Dialog component requires a ${ strings . CONTAINER_SELECTOR } container element` ) ; } this . container = container ; this . content = this . root . querySelector < HTMLElement > ( strings . CONTENT_SELECTOR ) ; this . buttons = [ ] . slice . call ( this . root . querySelectorAll < HTMLButtonElement > ( strings . BUTTON_SELECTOR ) ) ; this . defaultButton = this . root . querySelector < HTMLButtonElement > ( `[${ strings . BUTTON_DEFAULT_ATTRIBUTE }]` ) ; this . focusTrapFactory = focusTrapFactory ; this . buttonRipples = [ ] ; for ( const buttonEl of this . buttons ) { this . buttonRipples . push ( new MDCRipple ( buttonEl ) ) ; } } override initialSyncWithDOM ( ) { this . focusTrap = util . createFocusTrapInstance ( this . container , this . focusTrapFactory , this . getInitialFocusEl ( ) || undefined ) ; this . handleClick = this . foundation . handleClick . bind ( this . foundation ) ; this . handleKeydown = this . foundation . handleKeydown . bind ( this . foundation ) ; this . handleDocumentKeydown = this . foundation . handleDocumentKeydown . bind ( this . foundation ) ; this . handleOpening = ( ) => { document . addEventListener ( 'keydown' , this . handleDocumentKeydown ) ; } ; this . handleClosing = ( ) => { document . removeEventListener ( 'keydown' , this . handleDocumentKeydown ) ; } ; this . listen ( 'click' , this . handleClick ) ; this . listen ( 'keydown' , this . handleKeydown ) ; this . listen ( strings . OPENING_EVENT , this . handleOpening ) ; this . listen ( strings . CLOSING_EVENT , this . handleClosing ) ; } override destroy ( ) { this . unlisten ( 'click' , this . handleClick ) ; this . unlisten ( 'keydown' , this . handleKeydown ) ; this . unlisten ( strings . OPENING_EVENT , this . handleOpening ) ; this . unlisten ( strings . CLOSING_EVENT , this . handleClosing ) ; this . handleClosing ( ) ; this . buttonRipples . forEach ( ( ripple ) => { ripple . destroy ( ) ; } ) ; super . destroy ( ) ; } layout ( ) { this . foundation . layout ( ) ; } open ( ) { this . foundation . open ( ) ; } close ( action = '' ) { this . foundation . close ( action ) ; } override getDefaultFoundation ( ) { const adapter = { addBodyClass : ( className ) => document . body . classList . add ( className ) , addClass : ( className ) => this . root . classList . add ( className ) , areButtonsStacked : ( ) => util . areTopsMisaligned ( this . buttons ) , clickDefaultButton : ( ) => { if ( this . defaultButton && ! this . defaultButton . disabled ) { this . defaultButton . click ( ) ; } } , eventTargetMatches : ( target , selector ) => target ? matches ( target as Element , selector ) : false , getActionFromEvent : ( evt ) => { if ( ! evt . target ) { return '' ; } const element = closest ( evt . target as Element , `[${ strings . ACTION_ATTRIBUTE }]` ) ; return element && element . getAttribute ( strings . ACTION_ATTRIBUTE ) ; } , getInitialFocusEl : ( ) => this . getInitialFocusEl ( ) , hasClass : ( className ) => this . root . classList . contains ( className ) , isContentScrollable : ( ) => util . isScrollable ( this . content ) , notifyClosed : ( action ) => this . emit < MDCDialogCloseEventDetail > ( strings . CLOSED_EVENT , action ? { action } : { } ) , notifyClosing : ( action ) => this . emit < MDCDialogCloseEventDetail > ( strings . CLOSING_EVENT , action ? { action } : { } ) , notifyOpened : ( ) => this . emit ( strings . OPENED_EVENT , { } ) , notifyOpening : ( ) => this . emit ( strings . OPENING_EVENT , { } ) , releaseFocus : ( ) => { this . focusTrap . releaseFocus ( ) ; } , removeBodyClass : ( className ) => document . body . classList . remove ( className ) , removeClass : ( className ) => this . root . classList . remove ( className ) , reverseButtons : ( ) => { this . buttons . reverse ( ) ; this . buttons . forEach ( ( button ) => { button . parentElement ! . appendChild ( button ) ; } ) ; } , trapFocus : ( ) => { this . focusTrap . trapFocus ( ) ; } , registerContentEventHandler : ( evt , handler ) => { if ( this . content instanceof HTMLElement ) { this . content . addEventListener ( evt , handler ) ; } } , deregisterContentEventHandler : ( evt , handler ) => { if ( this . content instanceof HTMLElement ) { this . content . removeEventListener ( evt , handler ) ; } } , isScrollableContentAtTop : ( ) => { return util . isScrollAtTop ( this . content ) ; } , isScrollableContentAtBottom : ( ) => { return util . isScrollAtBottom ( this . content ) ; } , registerWindowEventHandler : ( evt , handler ) => { window . addEventListener ( evt , handler ) ; } , deregisterWindowEventHandler : ( evt , handler ) => { window . removeEventListener ( evt , handler ) ; } , } ; return new MDCDialogFoundation ( adapter ) ; } private getInitialFocusEl ( ) { return this . root . querySelector ( `[${ strings . INITIAL_FOCUS_ATTRIBUTE }]` ) ; } }