From 460ad0e05ac41eee818685327683ffc5b4d336d2 Mon Sep 17 00:00:00 2001 From: Derk-Jan Hartman Date: Sun, 11 Aug 2013 09:11:40 +0200 Subject: [PATCH] Preferences: Improve accessibility of the JS tabs of Preferences This enables keyboard accessibility of the tabs, by only allowing focus on one tab element using tabindex=0 and tabindex=-1 for the other tabs. Navigation between tabs is then handled by using the left and right arrow keys. This is the advised methodology. We also add tab, tabpanel and tablist roles to improve accessibility for assistive technology, while overriding the implicit tablist role of the li element with 'presentation' to make sure we don't have mixed semantics of lists and tabs. We keep track of: aria-selected: If this tab is currently selected aria-controls: Which tabpanel is controlled by this tab aria-labelledby: Which tab is the label for this tabpanel aria-hidden: If this tabpanel is (not) visible Tested using VoiceOver. Should also work with JAWS 14. Change-Id: Ica447a3b6f08422fd3c7452a5bd87d509dad9870 --- RELEASE-NOTES-1.23 | 1 + languages/messages/MessagesEn.php | 1 + languages/messages/MessagesQqq.php | 1 + resources/Resources.php | 3 + .../mediawiki.special.preferences.css | 10 +++ .../mediawiki.special.preferences.js | 73 +++++++++++++++++-- 6 files changed, 81 insertions(+), 8 deletions(-) diff --git a/RELEASE-NOTES-1.23 b/RELEASE-NOTES-1.23 index 7b6a07fab7..5d7fa83ad1 100644 --- a/RELEASE-NOTES-1.23 +++ b/RELEASE-NOTES-1.23 @@ -53,6 +53,7 @@ production. custom CSS or JavaScript enabled only for registered users. * (bug 52005) Special pages RecentChanges, RecentChangesLinked and Watchlist now include a legend describing the symbols used in lists of changes. +* Improved the accessibility of the tabs in Special:Preferences. === Bug fixes in 1.23 === * (bug 41759) The "updated since last visit" markers (on history pages, recent diff --git a/languages/messages/MessagesEn.php b/languages/messages/MessagesEn.php index 4ec3ee8480..a9a0b6ca2b 100644 --- a/languages/messages/MessagesEn.php +++ b/languages/messages/MessagesEn.php @@ -1995,6 +1995,7 @@ Your email address is not revealed when other users contact you.', 'prefs-tokenwatchlist' => 'Token', 'prefs-diffs' => 'Diffs', 'prefs-help-prefershttps' => 'This preference will take effect on your next login.', +'prefs-tabs-navigation-hint' => 'Tip: You can use the left and right arrow keys to navigate between the tabs in the tabs list.', # User preference: email validation using jQuery 'email-address-validity-valid' => 'Email address appears valid', diff --git a/languages/messages/MessagesQqq.php b/languages/messages/MessagesQqq.php index 483fd25bab..7051403125 100644 --- a/languages/messages/MessagesQqq.php +++ b/languages/messages/MessagesQqq.php @@ -3213,6 +3213,7 @@ Used in [[Special:Preferences]], tab "Watchlist". The checkbox has the label {{msg-mw|Tog-prefershttps}}. See example: [[mw:Special:Preferences]].', +'prefs-tabs-navigation-hint' => 'Hint message that explains the arrow key navigation for the tabs on Special:Preferences to screenreader users.', # User preference: email validation using jQuery 'email-address-validity-valid' => 'Used as hint for {{msg-mw|changeemail-newemail}} field in [[Special:ChangeEmail]], when the provided E-mail address is valid.', diff --git a/resources/Resources.php b/resources/Resources.php index df124cc180..d2a06b782e 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -1030,6 +1030,9 @@ return array( 'skinStyles' => array( 'vector' => 'skins/vector/special.preferences.less', ), + 'messages' => array( + 'prefs-tabs-navigation-hint', + ), ), 'mediawiki.special.recentchanges' => array( 'scripts' => 'resources/mediawiki.special/mediawiki.special.recentchanges.js', diff --git a/resources/mediawiki.special/mediawiki.special.preferences.css b/resources/mediawiki.special/mediawiki.special.preferences.css index 161efde30d..75ae5ca245 100644 --- a/resources/mediawiki.special/mediawiki.special.preferences.css +++ b/resources/mediawiki.special/mediawiki.special.preferences.css @@ -9,3 +9,13 @@ /* .mw-email-authenticated .mw-input { } */ + +/** + * Hide, but keep accessible for screen-readers. + * Like .mw-jump, #jump-to-nav from skins/common/shared.css + */ +.mw-navigation-hint { + overflow: hidden; + height: 0; + zoom: 1; +} diff --git a/resources/mediawiki.special/mediawiki.special.preferences.js b/resources/mediawiki.special/mediawiki.special.preferences.js index 03d93d00e6..3302ec636b 100644 --- a/resources/mediawiki.special/mediawiki.special.preferences.js +++ b/resources/mediawiki.special/mediawiki.special.preferences.js @@ -3,22 +3,45 @@ */ jQuery( function ( $ ) { var $preftoc, $preferences, $fieldsets, $legends, - hash, + hash, labelFunc, $tzSelect, $tzTextbox, $localtimeHolder, servertime; - $( '#prefsubmit' ).attr( 'id', 'prefcontrol' ); + labelFunc = function () { + return this.id.replace( /^mw-prefsection/g, 'preftab' ); + }; - $preftoc = $(''); + $( '#prefsubmit' ).attr( 'id', 'prefcontrol' ); + $preftoc = $('') + .attr( 'role', 'tablist' ); $preferences = $( '#preferences' ) .addClass( 'jsprefs' ) .before( $preftoc ); $fieldsets = $preferences.children( 'fieldset' ) .hide() + .attr( { + role: 'tabpanel', + 'aria-hidden': 'true', + 'aria-labelledby': labelFunc + } ) .addClass( 'prefsection' ); $legends = $fieldsets .children( 'legend' ) .addClass( 'mainLegend' ); + // Make sure the accessibility tip is selectable so that screen reader users take notice, + // but hide it per default to reduce interface clutter. Also make sure it becomes visible + // when selected. Similar to jquery.mw-jump + $( '
' ).addClass( 'mw-navigation-hint' ) + .text( mediaWiki.msg( 'prefs-tabs-navigation-hint' ) ) + .attr( 'tabIndex', 0 ) + .on( 'focus blur', function( e ) { + if ( e.type === 'blur' || e.type === 'focusout' ) { + $( this ).css( 'height', '0' ); + } else { + $( this ).css( 'height', 'auto' ); + } + } ).insertBefore( $preftoc ); + /** * It uses document.getElementById for security reasons (HTML injections in $()). * @@ -36,12 +59,23 @@ jQuery( function ( $ ) { } $( window ).scrollTop( scrollTop ); - $preftoc.find( 'li' ).removeClass( 'selected' ); + $preftoc.find( 'li' ).removeClass( 'selected' ) + .find( 'a' ).attr( { + tabIndex: -1, + 'aria-selected': 'false' + } ); + $tab = $( document.getElementById( 'preftab-' + name ) ); if ( $tab.length ) { - $tab.parent().addClass( 'selected' ); - $preferences.children( 'fieldset' ).hide(); - $( document.getElementById( 'mw-prefsection-' + name ) ).show(); + $tab.attr( { + tabIndex: 0, + 'aria-selected': 'true' + } ) + .focus() + .parent().addClass( 'selected' ); + + $preferences.children( 'fieldset' ).hide().attr( 'aria-hidden', 'true' ); + $( document.getElementById( 'mw-prefsection-' + name ) ).show().attr( 'aria-hidden', 'false' ); } } @@ -55,17 +89,40 @@ jQuery( function ( $ ) { ident = $legend.parent().attr( 'id' ); $li = $( '
  • ' ) + .attr( 'role', 'presentation' ) .addClass( i === 0 ? 'selected' : '' ); $a = $( '' ) .attr( { id: ident.replace( 'mw-prefsection', 'preftab' ), - href: '#' + ident + href: '#' + ident, + role: 'tab', + tabIndex: i === 0 ? 0 : -1, + 'aria-selected': i === 0 ? 'true' : 'false', + 'aria-controls': ident } ) .text( $legend.text() ); $li.append( $a ); $preftoc.append( $li ); } ); + // Enable keyboard users to use left and right keys to switch tabs + $preftoc.on( 'keydown', function( event ) { + var keyLeft = 37, + keyRight = 39, + $el; + + if( event.keyCode === keyLeft ) { + $el = $( '#preftoc li.selected' ).prev().find( 'a' ); + } else if ( event.keyCode === keyRight ) { + $el = $( '#preftoc li.selected' ).next().find( 'a' ); + } else { + return; + } + if ( $el.length > 0 ) { + switchPrefTab( $el.attr( 'href' ).replace( '#mw-prefsection-', '' ) ); + } + } ); + // If we've reloaded the page or followed an open-in-new-window, // make the selected tab visible. hash = window.location.hash; -- 2.20.1