Skip to content

Instantly share code, notes, and snippets.

@samthor
Last active December 1, 2023 14:39
Show Gist options
  • Save samthor/babe9fad4a65625b301ba482dad284d1 to your computer and use it in GitHub Desktop.
Save samthor/babe9fad4a65625b301ba482dad284d1 to your computer and use it in GitHub Desktop.

Revisions

  1. samthor revised this gist Jun 30, 2017. 1 changed file with 1 addition and 2 deletions.
    3 changes: 1 addition & 2 deletions dialog-focus-restore.js
    Original file line number Diff line number Diff line change
    @@ -15,7 +15,7 @@ var registerFocusRestoreDialog = (function() {
    previousFocus = ev.target;
    }, true);

    return function restoreFocusDialog(dialog) {
    return function registerFocusRestoreDialog(dialog) {
    if (dialog.localName !== 'dialog') {
    throw new Error('Failed to upgrade focus on dialog: The element is not a dialog.');
    }
    @@ -36,7 +36,6 @@ var registerFocusRestoreDialog = (function() {

    // watch for 'open' change and clear saved
    var mo = new MutationObserver(function() {
    console.info('mo is called', dialog, dialog.hasAttribute('open'));
    if (!dialog.hasAttribute('open')) {
    registered.set(dialog, null);
    } else {
  2. samthor created this gist Jun 30, 2017.
    72 changes: 72 additions & 0 deletions dialog-focus-restore.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,72 @@
    /**
    * Updates the passed dialog to retain focus and restore it when the dialog is closed. Won't
    * upgrade a dialog more than once. Supports IE11+ and is a no-op otherwise.
    * @param {!HTMLDialogElement} dialog to upgrade
    */
    var registerFocusRestoreDialog = (function() {
    if (!window.WeakMap || !window.MutationObserver) {
    return function() {};
    }
    var registered = new WeakMap();

    // store previous focused node centrally
    var previousFocus = null;
    document.addEventListener('focusout', function(ev) {
    previousFocus = ev.target;
    }, true);

    return function restoreFocusDialog(dialog) {
    if (dialog.localName !== 'dialog') {
    throw new Error('Failed to upgrade focus on dialog: The element is not a dialog.');
    }
    if (registered.has(dialog)) { return; }
    registered.set(dialog, null);

    // replace showModal method directly, to save focus
    var realShowModal = dialog.showModal;
    dialog.showModal = function() {
    var savedFocus = document.activeElement;
    if (savedFocus === document || savedFocus === document.body) {
    // some browsers read activeElement as body
    savedFocus = previousFocus;
    }
    registered.set(dialog, savedFocus);
    realShowModal.call(this);
    };

    // watch for 'open' change and clear saved
    var mo = new MutationObserver(function() {
    console.info('mo is called', dialog, dialog.hasAttribute('open'));
    if (!dialog.hasAttribute('open')) {
    registered.set(dialog, null);
    } else {
    // if open was cleared/set in the same frame, then the dialog will still be a modal (Y)
    }
    });
    mo.observe(dialog, {attributes: true, attributeFilter: ['open']});

    // on close, try to focus saved, if possible
    dialog.addEventListener('close', function(ev) {
    if (dialog.hasAttribute('open')) {
    return; // in native, this fires the frame later
    }
    var savedFocus = registered.get(dialog);
    if (document.contains(savedFocus)) {
    var wasFocus = document.activeElement;
    savedFocus.focus();
    if (document.activeElement !== savedFocus) {
    wasFocus.focus(); // restore focus, we couldn't focus saved
    }
    }
    savedFocus = null;
    });

    // FIXME: If a modal dialog is readded to the page (either remove/add or .appendChild), it will
    // be a non-modal. It will still have its 'close' handler called and try to focus on the saved
    // element.
    //
    // These could basically be solved if 'close' yielded whether it was a modal or non-modal
    // being closed. But it doesn't. It could also be solved by a permanent MutationObserver, as is
    // done inside the polyfill.
    }
    }());