2018-07-06 01:18:51 -04:00
class ModalHandler {
static exists ( ) {
return document . getElementById ( "modal-container" ) !== null ;
}
2023-07-02 16:28:02 -04:00
static getModalContainer ( ) {
2023-07-03 09:32:31 -04:00
return document . getElementById ( "modal-container" ) ;
2023-07-02 16:28:02 -04:00
}
static getFocusableElements ( ) {
let container = this . getModalContainer ( ) ;
2023-07-03 09:32:31 -04:00
if ( container === null ) {
return null ;
2023-07-02 16:28:02 -04:00
}
return container . querySelectorAll ( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ) ;
}
static setupFocusTrap ( ) {
let focusableElements = this . getFocusableElements ( ) ;
2023-07-03 09:32:31 -04:00
if ( focusableElements === null ) {
2023-07-02 16:28:02 -04:00
return ;
}
let firstFocusableElement = focusableElements [ 0 ] ;
let lastFocusableElement = focusableElements [ focusableElements . length - 1 ] ;
this . getModalContainer ( ) . onkeydown = ( e ) => {
if ( e . key !== 'Tab' ) {
return ;
}
// If there is only one focusable element in the dialog we always want to focus that one with the tab key.
// This handles the special case of having just one focusable element in a dialog where keyboard focus is placed on an element that is not in the tab order.
if ( focusableElements . length === 1 ) {
firstFocusableElement . focus ( ) ;
e . preventDefault ( ) ;
return ;
}
if ( e . shiftKey && document . activeElement === firstFocusableElement ) {
lastFocusableElement . focus ( ) ;
e . preventDefault ( ) ;
} else if ( ! e . shiftKey && document . activeElement === lastFocusableElement ) {
firstFocusableElement . focus ( ) ;
e . preventDefault ( ) ;
}
2023-07-03 06:22:34 -04:00
} ;
2023-07-02 16:28:02 -04:00
}
static open ( fragment , initialFocusElementId ) {
2018-07-06 01:18:51 -04:00
if ( ModalHandler . exists ( ) ) {
return ;
}
2023-07-02 16:28:02 -04:00
this . activeElement = document . activeElement ;
2018-07-06 01:18:51 -04:00
let container = document . createElement ( "div" ) ;
container . id = "modal-container" ;
2023-07-02 16:28:02 -04:00
container . setAttribute ( "role" , "dialog" ) ;
2018-07-06 01:18:51 -04:00
container . appendChild ( document . importNode ( fragment , true ) ) ;
document . body . appendChild ( container ) ;
2023-07-02 16:28:02 -04:00
let closeButton = document . querySelector ( "button.btn-close-modal" ) ;
2018-07-06 01:18:51 -04:00
if ( closeButton !== null ) {
closeButton . onclick = ( event ) => {
event . preventDefault ( ) ;
ModalHandler . close ( ) ;
} ;
}
2023-07-02 16:28:02 -04:00
let initialFocusElement ;
if ( initialFocusElementId !== undefined ) {
initialFocusElement = document . getElementById ( initialFocusElementId ) ;
} else {
2023-07-03 09:32:31 -04:00
let focusableElements = this . getFocusableElements ( ) ;
if ( focusableElements !== null ) {
initialFocusElement = focusableElements [ 0 ] ;
}
2023-07-02 16:28:02 -04:00
}
2023-07-03 09:32:31 -04:00
if ( initialFocusElement !== undefined ) {
initialFocusElement . focus ( ) ;
}
2023-07-02 16:28:02 -04:00
this . setupFocusTrap ( ) ;
2018-07-06 01:18:51 -04:00
}
static close ( ) {
2023-07-02 16:28:02 -04:00
let container = this . getModalContainer ( ) ;
2018-07-06 01:18:51 -04:00
if ( container !== null ) {
container . parentNode . removeChild ( container ) ;
}
2023-07-02 16:28:02 -04:00
2023-07-03 09:32:31 -04:00
if ( this . activeElement !== undefined && this . activeElement !== null ) {
2023-07-02 16:28:02 -04:00
this . activeElement . focus ( ) ;
}
2018-07-06 01:18:51 -04:00
}
}