Skip to content

Commit

Permalink
Keyboard accessibility improvements (#16613)
Browse files Browse the repository at this point in the history
### What does it do?
Make the MODX manager interface more accessible when using the keyboard

### How to test
Minify CSS and compress JS via grunt first. Use the `Tab` key to
navigate through the Manager UI. Use the `Esc` key to close the
subnavigation. Use the tab key to navigate through the tabs.

### Related issue(s)/PR(s)
See #16612

---------

Co-authored-by: Jason Coward <jason@opengeek.com>
  • Loading branch information
jenswittmann and opengeek authored Sep 23, 2024
1 parent 1755b98 commit b207371
Show file tree
Hide file tree
Showing 15 changed files with 238 additions and 58 deletions.
44 changes: 44 additions & 0 deletions _build/templates/default/sass/_a11y.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
.ext-webkit {

* {

&:focus-visible {
outline: auto !important;
outline-offset: .1em;

&.x-form-focus {
outline: none !important;
}
}
}

.x-form-check-wrap:focus-within:has(:focus-visible) {

label:before {
outline: auto !important;
outline-offset: .1em;
}
}
}

// skiplinks
.skiplinks {
position: fixed;
top: -100rem;
left: -100rem;
z-index: 99999;
width: 0;
height: 0;

a {
&:focus,
&:active {
position: fixed;
top: 0;
left: 0;
padding: 1rem;
margin: 1rem;
background-color: white;
}
}
}
3 changes: 2 additions & 1 deletion _build/templates/default/sass/_dashboard.scss
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@
}
}

&:hover {
&:hover,
&:focus-within {
.action-buttons {
button {
opacity: 1;
Expand Down
9 changes: 7 additions & 2 deletions _build/templates/default/sass/_navbar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
background: $colorSplash;
max-width: $navbarWidth;
position: absolute;
z-index: 2;
z-index: 9999;
height: 100%;
}

Expand Down Expand Up @@ -184,6 +184,7 @@
}
}

#modx-header,
#modx-footer {
.modx-subnav {
border: 1px solid $navbarBorder;
Expand All @@ -194,7 +195,7 @@
box-sizing: border-box;
list-style: none;
position: absolute;
z-index: 10000;
z-index: 99999999999;
opacity: 0;
visibility: hidden;
transition: all .15s ease;
Expand Down Expand Up @@ -235,6 +236,7 @@
border-radius: $borderRadius;
background-color: $subnavBg;
color: $subnavTitleColor;
font-size: 13px;
font-weight: bold;
line-height: 1.5;
margin: 0;
Expand All @@ -244,6 +246,7 @@
display: block;
text-decoration: none;
cursor: pointer;
text-align: left;

.icon {
display: inline-block;
Expand Down Expand Up @@ -409,6 +412,7 @@
}
}

#modx-header,
#modx-footer {
.modx-subnav {
min-width: 300px;
Expand Down Expand Up @@ -458,6 +462,7 @@
}

@media (max-height: 520px) {
#modx-header,
#modx-footer {
.modx-subnav {
.description {
Expand Down
3 changes: 2 additions & 1 deletion _build/templates/default/sass/_tree.scss
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,8 @@
}

&:hover,
&:focus {
&:focus,
&:focus-within {
.modx-tree-node-btn-create {
opacity: 1.0;
}
Expand Down
1 change: 1 addition & 0 deletions _build/templates/default/sass/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ $fa-css-prefix: fa;
@import "help";
@import "trash";
@import "package-management";
@import "a11y";

.icon {
@extend %pseudo-font;
Expand Down
90 changes: 71 additions & 19 deletions manager/assets/modext/core/modx.layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ Ext.extend(MODx.Layout, Ext.Viewport, {
*/
,splitBarMargin: 8

/**
* @property {Array} focusRestoreEls - Set Focus back on the last Element in array on close
*/
,focusRestoreEls: []

/**
* @property {bool} subNavOpen - Check if Subnav is opened
*/
,subNavOpen: false

/**
* @property {Function} getSplitBarMargin - Utility getter for splitBarMargin
* @returns {Number}
Expand Down Expand Up @@ -141,7 +151,7 @@ Ext.extend(MODx.Layout, Ext.Viewport, {
,xtype: 'box'
,id: 'modx-header'
,applyTo: 'modx-header'
,autoScroll: true
//,autoScroll: true
,width: this.menuBarWidth
,listeners: {
afterrender: { fn: this.initPopper, scope: this }
Expand Down Expand Up @@ -465,10 +475,11 @@ Ext.extend(MODx.Layout, Ext.Viewport, {
}
}
});
buttons[i].onclick = function(e) {
buttons[i].addEventListener('click', function(e) {
e.stopPropagation();
el.focusRestoreEls.push(this.querySelectorAll('a')[0]);
el.showMenu(this);
};
});
}
window.addEventListener('click', function() {
el.hideMenu();
Expand All @@ -485,6 +496,31 @@ Ext.extend(MODx.Layout, Ext.Viewport, {
} else {
this.hideMenu();
submenu.classList.add('active');
setTimeout(() => {
submenu.querySelectorAll('a')[0].focus();
}, 50);
var focusRestore = (e) => {
setTimeout(() => {
if (this.subNavOpen) {
return;
}
if (!submenu.contains(document.activeElement)) {
this.focusRestoreEls?.pop()?.focus();
this.hideMenu();
window.removeEventListener('focusout', focusRestore);
}
}, 1);
};
var menuArrowKeysNavigation = (e) => {
if (e.code == 'Escape') {
this.hideMenu();
this.focusRestoreEls[0]?.focus();
this.focusRestoreEls = [];
window.removeEventListener('keyup', menuArrowKeysNavigation);
}
};
window.addEventListener('focusout', focusRestore);
window.addEventListener('keyup', menuArrowKeysNavigation);
}
this.hideSubMenu();
}
Expand All @@ -494,10 +530,10 @@ Ext.extend(MODx.Layout, Ext.Viewport, {
submenus[i].classList.remove('active');
}
}

,initSubPopper: function () {
var buttons = document.getElementById('modx-footer').querySelectorAll('.sub');
var buttons = document.querySelectorAll('#modx-header .sub, #modx-footer .sub');
var position = window.innerWidth <= 960 ? 'bottom' : 'right';
var _this = this;
for (var i = 0; i < buttons.length; i++) {
let popperInstance = null;

Expand Down Expand Up @@ -539,34 +575,50 @@ Ext.extend(MODx.Layout, Ext.Viewport, {
}
}

function show(button, menu) {
function show(button) {
var menu = button.getElementsByTagName('ul')[0];
button.classList.add('active');
menu.classList.add('active');
_this.focusRestoreEls.push(button.querySelectorAll('a')[0]);
_this.subNavOpen = true;
create(button, menu);
var focusRestore = (e) => {
requestAnimationFrame(() => {
if (!menu.contains(document.activeElement)) {
_this.focusRestoreEls?.pop()?.parentNode?.nextSibling?.focus();
hide(button);
window.removeEventListener('focusout', focusRestore);
}
});
};
window.addEventListener('focusout', focusRestore);
}

function hide(menu) {
var buttons = menu.querySelectorAll('.sub');
function hide(button) {
var parentmenu = button.closest('ul');
button.classList.remove('active');
var buttons = parentmenu.querySelectorAll('.sub');
for (var i = 0; i < buttons.length; i++) {
var submenu = buttons[i].getElementsByTagName('ul')[0];
submenu.classList.remove('active');
submenu.removeAttribute('style');
buttons[i].classList.remove('active');
}
_this.subNavOpen = false;
destroy();
}

buttons[i].onmouseenter = function (e) {
buttons[i].addEventListener('mouseenter', function (e) {
e.stopPropagation();
var submenu = this.getElementsByTagName('ul')[0];
this.classList.add('active');
show(this, submenu);
};
buttons[i].onmouseleave = function (e) {
show(this);
});
buttons[i].querySelectorAll('a')[0].addEventListener('focus', function (e) {
e.stopPropagation();
var parentmenu = this.closest('ul');
this.classList.remove('active');
hide(parentmenu);
};
show(this.parentNode);
});
buttons[i].addEventListener('mouseleave', function (e) {
e.stopPropagation();
hide(this);
});
}
}

Expand Down
6 changes: 3 additions & 3 deletions manager/assets/modext/modx.jsgrps-min.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions manager/assets/modext/util/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ Ext.override(Ext.tree.TreeNodeUI,{
elbowMarkup,
iconMarkup,
cb ? ('<input class="x-tree-node-cb" type="checkbox" ' + (a.checked ? 'checked="checked" />' : '/>')) : '',
'<a hidefocus="on" class="x-tree-node-anchor" href="',href,'" tabIndex="1" ',
'<a hidefocus="on" class="x-tree-node-anchor" href="',href,'" tabIndex="0" ',
a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", '><span unselectable="on">',renderer(a),"</span></a></div>",
'<ul class="x-tree-node-ct" style="display:none;"></ul>',
"</li>"].join('');
Expand Down Expand Up @@ -493,8 +493,8 @@ MODx.util.Format = {
},
/**
* Trim a set of characters from the beginning and/or ending of a string
* @param {String} string
* @param {String} charList
* @param {String} string
* @param {String} charList
*/
trimCharacters: function(string, charList = '', direction = 'both') {
if (charList.length) {
Expand Down
4 changes: 2 additions & 2 deletions manager/assets/modext/widgets/core/modx.button.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ MODx.Button = function(config) {
config.iconCls = ''
}
Ext.applyIf(config,{
template: new Ext.XTemplate('<span id="{4}" class="x-btn icon {1} {3}" unselectable="on">'+
template: new Ext.XTemplate('<a href="javascrpt:;" id="{4}" class="x-btn icon {1} {3}">'+

This comment has been minimized.

Copy link
@Jako

Jako Sep 28, 2024

Collaborator

Is this a typo and should be javascript:;

' <i class="{2}">'+
' </i>'+
'</span>').compile()
'</a>').compile()
});

MODx.Button.superclass.constructor.call(this,config);
Expand Down
10 changes: 9 additions & 1 deletion manager/assets/modext/widgets/core/modx.tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,15 @@ MODx.Tabs = function(config = {}) {
border: false,
autoScroll: true,
autoHeight: true,
cls: 'modx-tabs'
cls: 'modx-tabs',
itemTpl: new Ext.XTemplate(
'<li class="{cls}" id="{id}">',
'<a class="x-tab-strip-close"></a>',
'<a href="#">',
'<span class="x-tab-strip-text">{text}</span>',
'</a>',
'</li>'
)
});
MODx.Tabs.superclass.constructor.call(this, config);
this.config = config;
Expand Down
4 changes: 2 additions & 2 deletions manager/controllers/default/header.php
Original file line number Diff line number Diff line change
Expand Up @@ -329,10 +329,10 @@ public function processSubMenus(&$output, array $menus = [])
$attributes = ' href="?a='.$menu['action'].$menu['params'].'"';
}
if (!empty($menu['handler'])) {
$attributes .= ' onclick="{literal} '.str_replace('"','\'',$menu['handler']).'{/literal} "';
$attributes .= ' href="javascript:;" onclick="{literal} '.str_replace('"','\'',$menu['handler']).'{/literal} "';
}
$menu['icon'] = $menu['icon'] ?? '';
$smTpl .= '<a'.$attributes.'>'.$menu['text'].$menu['icon'].$description.'</a>'."\n";
$smTpl .= '<a'.$attributes.' tabindex="0">'.$menu['text'].$menu['icon'].$description.'</a>'."\n";

if (!empty($menu['children'])) {
$smTpl .= '<ul class="modx-subsubnav">'."\n";
Expand Down
4 changes: 2 additions & 2 deletions manager/templates/default/css/index-min.css

Large diffs are not rendered by default.

Loading

0 comments on commit b207371

Please sign in to comment.