News:

Bored?  Looking to kill some time?  Want to chat with other SMF users?  Join us in IRC chat or Discord

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: