pinafore/routes/_utils/dialogs.js

55 lines
1.7 KiB
JavaScript

// From https://gist.github.com/samthor/babe9fad4a65625b301ba482dad284d1
// Via https://github.com/GoogleChrome/dialog-polyfill/issues/139
let registered = new WeakMap()
// store previous focused node centrally
let previousFocus = null
document.addEventListener('focusout', (e) => {
previousFocus = e.target
}, true)
/**
* Updates the passed dialog to retain focus and restore it when the dialog is closed.
* @param {!HTMLDialogElement} dialog to upgrade
*/
export function registerFocusRestoreDialog(dialog) {
// replace showModal method directly, to save focus
let realShowModal = dialog.showModal
dialog.showModal = function () {
let savedFocus = document.activeElement
if (savedFocus === document || savedFocus === document.body) {
// some browsers read activeElement as body
savedFocus = previousFocus
}
registered.set(dialog, savedFocus)
realShowModal.call(this)
}
// on close, try to focus saved, if possible
dialog.addEventListener('close', function () {
if (dialog.hasAttribute('open')) {
return // in native, this fires the frame later
}
let savedFocus = registered.get(dialog)
if (document.contains(savedFocus)) {
let wasFocus = document.activeElement
savedFocus.focus()
if (document.activeElement !== savedFocus) {
wasFocus.focus() // restore focus, we couldn't focus saved
}
}
savedFocus = null
})
}
export function createDialogElement(label) {
if (!label) {
throw new Error('the modal must have a label')
}
let dialogElement = document.createElement('dialog')
dialogElement.classList.add('modal-dialog')
dialogElement.setAttribute('aria-label', label)
document.body.appendChild(dialogElement)
return dialogElement
}