Preferences: Improve accessibility of the JS tabs of Preferences
authorDerk-Jan Hartman <hartman@videolan.org>
Sun, 11 Aug 2013 07:11:40 +0000 (09:11 +0200)
committerMarius Hoch <hoo@online.de>
Tue, 10 Dec 2013 19:29:20 +0000 (20:29 +0100)
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
languages/messages/MessagesEn.php
languages/messages/MessagesQqq.php
resources/Resources.php
resources/mediawiki.special/mediawiki.special.preferences.css
resources/mediawiki.special/mediawiki.special.preferences.js

index 7b6a07f..5d7fa83 100644 (file)
@@ -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
index 4ec3ee8..a9a0b6c 100644 (file)
@@ -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',
index 483fd25..7051403 100644 (file)
@@ -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.',
index df124cc..d2a06b7 100644 (file)
@@ -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',
index 161efde..75ae5ca 100644 (file)
@@ -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;
+}
index 03d93d0..3302ec6 100644 (file)
@@ -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 = $('<ul id="preftoc"></ul>');
+       $( '#prefsubmit' ).attr( 'id', 'prefcontrol' );
+       $preftoc = $('<ul id="preftoc"></ul>')
+               .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
+       $( '<div>' ).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 = $( '<li>' )
+                       .attr( 'role', 'presentation' )
                        .addClass( i === 0 ? 'selected' : '' );
                $a = $( '<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;