News:

SMF 2.1.4 has been released! Take it for a spin! Read more.

Main Menu

Trap focus within the login popup

Started by live627, October 30, 2023, 12:41:24 AM

Previous topic - Next topic

live627

This is a bunch of JavaScript that modifies the built-in popup to make focus stay within the popup, so that hitting the tab button on the keyboard does not select any document elements behind the popup.

Find in ./Themes/default/scripts/script.js
smc_Popup.prototype.show = function ()
{
    popup_class = 'popup_window ' + (this.opt.custom_class ? this.opt.custom_class : 'description');
    if (this.opt.icon_class)
        icon = '<span class="' + this.opt.icon_class + '"></span> ';
    else
        icon = this.opt.icon ? '<img src="' + this.opt.icon + '" class="icon" alt=""> ' : '';

    // Create the div that will be shown
    $('body').append('<div id="' + this.popup_id + '" class="popup_container"><div class="' + popup_class + '"><div class="catbg popup_heading"><a href="javascript:void(0);" class="main_icons hide_popup"></a>' + icon + this.opt.heading + '</div><div class="popup_content">' + this.opt.content + '</div></div></div>');

    // Show it
    this.popup_body = $('#' + this.popup_id).children('.popup_window');
    this.popup_body.parent().fadeIn(300);

    // Trigger hide on escape or mouse click
    var popup_instance = this;
    $(document).mouseup(function (e) {
        if ($('#' + popup_instance.popup_id).has(e.target).length === 0)
            popup_instance.hide();
    }).keyup(function(e){
        if (e.keyCode == 27)
            popup_instance.hide();
    });
    $('#' + this.popup_id).find('.hide_popup').click(function (){ return popup_instance.hide(); });

    return false;
}

smc_Popup.prototype.hide = function ()
{
    $('#' + this.popup_id).fadeOut(300, function(){ $(this).remove(); });

    return false;
}

Replace with

smc_Popup.prototype.show = function ()
{
    popup_class = 'popup_window ' + (this.opt.custom_class ? this.opt.custom_class : 'description');
    if (this.opt.icon_class)
        icon = '<span class="' + this.opt.icon_class + '"></span> ';
    else
        icon = this.opt.icon ? '<img src="' + this.opt.icon + '" class="icon" alt=""> ' : '';

    // Create the div that will be shown
    this.cover = document.createElement("div");
    this.cover.id = this.popup_id;
    this.cover.className = 'popup_container';
    const root = document.createElement("div");
    const heading = document.createElement("div");
    const content = document.createElement("div");
    const a = document.createElement("a");
    heading.append(a);
    heading.insertAdjacentHTML('beforeend', icon);
    heading.append(this.opt.heading);
    root.append(heading, content);
    this.cover.appendChild(root);
    root.className = popup_class;
    content.className = 'popup_content';
    content.innerHTML = this.opt.content;
    heading.className = 'popup_heading';
    a.className = 'main_icons hide_popup';
    a.setAttribute('role', 'button');
    a.setAttribute('tabindex', '0');
    a.addEventListener("click", this.hide.bind(this));
    this.cover.addEventListener('click', function(e)
    {
        if (e.target === this.cover)
            this.hide();
    }.bind(this));
    document.body.appendChild(this.cover);

    // Show it
    $(this.cover).fadeIn(300, function()
    {
        var focusableEls = root.querySelectorAll('a[href]:not([href="javascript:self.close();"]), area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex^="-"])');
        this.focusableEls = Array.prototype.slice.call(focusableEls);
        this.firstFocusableEl = focusableEls[0];
        this.lastFocusableEl = focusableEls[focusableEls.length - 1];

        this.focusedElBeforeOpen = document.activeElement;
        if (focusableEls[1])
            focusableEls[1].focus();
    }.bind(this));

    root.addEventListener('keydown', function(e)
    {
        switch (e.keyCode)
        {
            case 9:
                if (this.focusableEls.length == 1)
                {
                    e.preventDefault();
                    break;
                }

                if (e.shiftKey && document.activeElement === this.firstFocusableEl)
                {
                    e.preventDefault();
                    this.lastFocusableEl.focus();
                }
                else if (!e.shiftKey && document.activeElement === this.lastFocusableEl)
                {
                    e.preventDefault();
                    this.firstFocusableEl.focus();
                }
                break;
            case 27:
                this.hide();
                break;
        }
    }.bind(this));

    // Disable document scrolling..
    document.body.style.overflow = 'hidden';

    return false;
}

smc_Popup.prototype.hide = function ()
{
    if (this.focusedElBeforeOpen)
        this.focusedElBeforeOpen.focus();

    document.body.style.overflow = '';
    $(this.cover).fadeOut(300, function() { this.remove(); });

    return false;
}

Most of this code does the same stuff, only rewritten from jquery.

Now some CSS to spice up the background by adding slanted pinstripes. Also a light blur.

Find in ./Themes/default/css/index.css

.popup_container {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(40, 64, 80, 0.5);
    z-index: 6;
}

Replace with

.popup_container {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: repeating-linear-gradient(
        45deg,
        rgba(0, 0, 0, 0.2),
        rgba(0, 0, 0, 0.2) 1px,
        rgba(0, 0, 0, 0.3) 1px,
        rgba(0, 0, 0, 0.3) 20px
    );
    backdrop-filter: blur(3px);
    z-index: 6;
}

Before
You cannot view this attachment.

After
You cannot view this attachment.

Antechinus

The eye candy is neither here nor there (personal preference) but the trapped focus is a good idea. Bung it in 2.1.5. Kthnx.

Advertisement: