mediawiki-skins-Vector/vector.js
Bartosz Dziewoński d82e4db35e Make dropdown menus keyboard-accessible without JavaScript
Instead of implementing keyboard (and mouse click) handling in
JavaScript, put an invisible <input type="checkbox"> into the
dropdown handle. It can be focused and toggled using keyboard
actions like a normal checkbox. This checkbox also takes over
the duties of handling mouse hovering and clicking.

Old JavaScript and CSS are left in place for compatibility with
cached page HTML, to be removed later in a follow-up.

Bug: T168080
Change-Id: I27532140b06c97921f1cfb64e44bff814d99a358
2018-01-03 21:58:51 +00:00

113 lines
3.4 KiB
JavaScript

/**
* Vector-specific scripts
*/
jQuery( function ( $ ) {
/**
* Collapsible tabs
*/
var $cactions = $( '#p-cactions' ),
$tabContainer = $( '#p-views ul' ),
rAF = window.requestAnimationFrame || setTimeout,
// Avoid forced style calculation during page load
initialCactionsWidth = function () {
var width = $cactions.width();
initialCactionsWidth = function () {
return width;
};
return width;
};
rAF( initialCactionsWidth );
/**
* Focus search input at the very end
*/
$( '#searchInput' ).attr( 'tabindex', $( document ).lastTabIndex() + 1 );
/**
* Dropdown menu accessibility
*/
$( 'div.vectorMenu' ).each( function () {
var $el = $( this );
if ( $el.find( '.vectorMenuCheckbox' ).length ) {
return;
}
$el.find( '> h3 > span' ).parent()
.attr( 'tabindex', '0' )
// For accessibility, show the menu when the h3 is clicked (bug 24298/46486)
.on( 'click keypress', function ( e ) {
if ( e.type === 'click' || e.which === 13 ) {
$el.toggleClass( 'menuForceShow' );
e.preventDefault();
}
} );
} );
// Bind callback functions to animate our drop down menu in and out
// and then call the collapsibleTabs function on the menu
$tabContainer
.on( 'beforeTabCollapse', function () {
// If the dropdown was hidden, show it
if ( $cactions.hasClass( 'emptyPortlet' ) ) {
$cactions.removeClass( 'emptyPortlet' );
$cactions.find( 'h3' )
.css( 'width', '1px' )
.animate( { width: initialCactionsWidth() }, 'normal' );
}
} )
.on( 'beforeTabExpand', function () {
// If we're removing the last child node right now, hide the dropdown
if ( $cactions.find( 'li' ).length === 1 ) {
$cactions.find( 'h3' ).animate( { width: '1px' }, 'normal', function () {
$( this ).attr( 'style', '' )
.parent().addClass( 'emptyPortlet' );
} );
}
} )
.collapsibleTabs( {
expandCondition: function ( eleWidth ) {
// (This looks a bit awkward because we're doing expensive queries as late as possible.)
var distance = $.collapsibleTabs.calculateTabDistance();
// If there are at least eleWidth + 1 pixels of free space, expand.
// We add 1 because .width() will truncate fractional values but .offset() will not.
if ( distance >= eleWidth + 1 ) {
return true;
} else {
// Maybe we can still expand? Account for the width of the "Actions" dropdown if the
// expansion would hide it.
if ( $cactions.find( 'li' ).length === 1 ) {
return distance >= eleWidth + 1 - initialCactionsWidth();
} else {
return false;
}
}
},
collapseCondition: function () {
var collapsibleWidth = 0;
// (This looks a bit awkward because we're doing expensive queries as late as possible.)
// TODO The dropdown itself should probably "fold" to just the down-arrow (hiding the text)
// if it can't fit on the line?
// Never collapse if there is no overlap.
if ( $.collapsibleTabs.calculateTabDistance() >= 0 ) {
return false;
}
// Always collapse if the "More" button is already shown.
if ( !$cactions.hasClass( 'emptyPortlet' ) ) {
return true;
}
$tabContainer.children( 'li.collapsible' ).each( function ( index, element ) {
collapsibleWidth += $( element ).width();
// Stop this possibly expensive loop the moment the condition is met.
return !( collapsibleWidth > initialCactionsWidth() );
} );
return collapsibleWidth > initialCactionsWidth();
}
} );
} );