mirror of
https://github.com/jquery/jquery-ui.git
synced 2025-01-07 20:34:24 +00:00
Accordion: Fixed ARIA support and added proper keyboard support.
This commit is contained in:
parent
adbc2733bb
commit
0370170b2f
@ -25,24 +25,50 @@ test( "handle click on header-descendant", function() {
|
||||
});
|
||||
|
||||
test( "accessibility", function () {
|
||||
expect( 13 );
|
||||
var element = $( "#list1" ).accordion().accordion( "option", "active", 1 );
|
||||
expect( 37 );
|
||||
var element = $( "#list1" ).accordion({
|
||||
active: 1
|
||||
});
|
||||
var headers = element.find( ".ui-accordion-header" );
|
||||
|
||||
equal( headers.eq( 1 ).attr( "tabindex" ), 0, "active header should have tabindex=0" );
|
||||
equal( headers.eq( 0 ).attr( "tabindex" ), -1, "inactive header should have tabindex=-1" );
|
||||
equal( element.attr( "role" ), "tablist", "main role" );
|
||||
equal( headers.attr( "role" ), "tab", "tab roles" );
|
||||
equal( headers.next().attr( "role" ), "tabpanel", "tabpanel roles" );
|
||||
equal( headers.eq( 1 ).attr( "aria-expanded" ), "true", "active tab has aria-expanded" );
|
||||
equal( headers.eq( 0 ).attr( "aria-expanded" ), "false", "inactive tab has aria-expanded" );
|
||||
equal( headers.eq( 1 ).attr( "aria-selected" ), "true", "active tab has aria-selected" );
|
||||
equal( headers.eq( 0 ).attr( "aria-selected" ), "false", "inactive tab has aria-selected" );
|
||||
equal( element.attr( "role" ), "tablist", "element role" );
|
||||
headers.each(function( i ) {
|
||||
var header = headers.eq( i ),
|
||||
panel = header.next();
|
||||
equal( header.attr( "role" ), "tab", "header " + i + " role" );
|
||||
equal( header.attr( "aria-controls" ), panel.attr( "id" ), "header " + i + " aria-controls" );
|
||||
equal( panel.attr( "role" ), "tabpanel", "panel " + i + " role" );
|
||||
equal( panel.attr( "aria-labelledby" ), header.attr( "id" ), "panel " + i + " aria-labelledby" );
|
||||
});
|
||||
|
||||
equal( headers.eq( 1 ).attr( "tabindex" ), 0, "active header has tabindex=0" );
|
||||
equal( headers.eq( 1 ).attr( "aria-selected" ), "true", "active tab has aria-selected=true" );
|
||||
equal( headers.eq( 1 ).next().attr( "aria-expanded" ), "true", "active tabpanel has aria-expanded=true" );
|
||||
equal( headers.eq( 1 ).next().attr( "aria-hidden" ), "false", "active tabpanel has aria-hidden=false" );
|
||||
equal( headers.eq( 0 ).attr( "tabindex" ), -1, "active header has tabindex=-1" );
|
||||
equal( headers.eq( 0 ).attr( "aria-selected" ), "false", "active tab has aria-selected=false" );
|
||||
equal( headers.eq( 0 ).next().attr( "aria-expanded" ), "false", "active tabpanel has aria-expanded=false" );
|
||||
equal( headers.eq( 0 ).next().attr( "aria-hidden" ), "true", "active tabpanel has aria-hidden=true" );
|
||||
equal( headers.eq( 2 ).attr( "tabindex" ), -1, "active header has tabindex=-1" );
|
||||
equal( headers.eq( 2 ).attr( "aria-selected" ), "false", "active tab has aria-selected=false" );
|
||||
equal( headers.eq( 2 ).next().attr( "aria-expanded" ), "false", "active tabpanel has aria-expanded=false" );
|
||||
equal( headers.eq( 2 ).next().attr( "aria-hidden" ), "true", "active tabpanel has aria-hidden=true" );
|
||||
|
||||
element.accordion( "option", "active", 0 );
|
||||
equal( headers.eq( 0 ).attr( "aria-expanded" ), "true", "newly active tab has aria-expanded" );
|
||||
equal( headers.eq( 1 ).attr( "aria-expanded" ), "false", "newly inactive tab has aria-expanded" );
|
||||
equal( headers.eq( 0 ).attr( "aria-selected" ), "true", "active tab has aria-selected" );
|
||||
equal( headers.eq( 1 ).attr( "aria-selected" ), "false", "inactive tab has aria-selected" );
|
||||
equal( headers.eq( 0 ).attr( "tabindex" ), 0, "active header has tabindex=0" );
|
||||
equal( headers.eq( 0 ).attr( "aria-selected" ), "true", "active tab has aria-selected=true" );
|
||||
equal( headers.eq( 0 ).next().attr( "aria-expanded" ), "true", "active tabpanel has aria-expanded=true" );
|
||||
equal( headers.eq( 0 ).next().attr( "aria-hidden" ), "false", "active tabpanel has aria-hidden=false" );
|
||||
equal( headers.eq( 1 ).attr( "tabindex" ), -1, "active header has tabindex=-1" );
|
||||
equal( headers.eq( 1 ).attr( "aria-selected" ), "false", "active tab has aria-selected=false" );
|
||||
equal( headers.eq( 1 ).next().attr( "aria-expanded" ), "false", "active tabpanel has aria-expanded=false" );
|
||||
equal( headers.eq( 1 ).next().attr( "aria-hidden" ), "true", "active tabpanel has aria-hidden=true" );
|
||||
equal( headers.eq( 2 ).attr( "tabindex" ), -1, "active header has tabindex=-1" );
|
||||
equal( headers.eq( 2 ).attr( "aria-selected" ), "false", "active tab has aria-selected=false" );
|
||||
equal( headers.eq( 2 ).next().attr( "aria-expanded" ), "false", "active tabpanel has aria-expanded=false" );
|
||||
equal( headers.eq( 2 ).next().attr( "aria-hidden" ), "true", "active tabpanel has aria-hidden=true" );
|
||||
});
|
||||
|
||||
// TODO: keyboard support
|
||||
|
||||
}( jQuery ) );
|
||||
|
96
ui/jquery.ui.accordion.js
vendored
96
ui/jquery.ui.accordion.js
vendored
@ -12,6 +12,7 @@
|
||||
* jquery.ui.widget.js
|
||||
*/
|
||||
(function( $, undefined ) {
|
||||
var uid = 0;
|
||||
|
||||
$.widget( "ui.accordion", {
|
||||
version: "@VERSION",
|
||||
@ -33,7 +34,9 @@ $.widget( "ui.accordion", {
|
||||
},
|
||||
|
||||
_create: function() {
|
||||
var options = this.options;
|
||||
var accordionId = this.accordionId = "ui-accordion-" +
|
||||
(this.element.attr( "id" ) || ++uid),
|
||||
options = this.options;
|
||||
|
||||
this.prevShow = this.prevHide = $();
|
||||
this.element.addClass( "ui-accordion ui-widget ui-helper-reset" );
|
||||
@ -68,18 +71,36 @@ $.widget( "ui.accordion", {
|
||||
|
||||
this.headers
|
||||
.attr( "role", "tab" )
|
||||
.each(function( i ) {
|
||||
var header = $( this ),
|
||||
headerId = header.attr( "id" ),
|
||||
panel = header.next(),
|
||||
panelId = panel.attr( "id" );
|
||||
if ( !headerId ) {
|
||||
headerId = accordionId + "-header-" + i;
|
||||
header.attr( "id", headerId );
|
||||
}
|
||||
if ( !panelId ) {
|
||||
panelId = accordionId + "-panel-" + i;
|
||||
panel.attr( "id", panelId );
|
||||
}
|
||||
header.attr( "aria-controls", panelId );
|
||||
panel.attr( "aria-labelledby", headerId );
|
||||
})
|
||||
.next()
|
||||
.attr( "role", "tabpanel" );
|
||||
|
||||
this.headers
|
||||
.not( this.active )
|
||||
.attr({
|
||||
// TODO: document support for each of these
|
||||
"aria-expanded": "false",
|
||||
"aria-selected": "false",
|
||||
tabIndex: -1
|
||||
})
|
||||
.next()
|
||||
.attr({
|
||||
"aria-expanded": "false",
|
||||
"aria-hidden": "true"
|
||||
})
|
||||
.hide();
|
||||
|
||||
// make sure at least one header is in the tab order
|
||||
@ -87,10 +108,14 @@ $.widget( "ui.accordion", {
|
||||
this.headers.eq( 0 ).attr( "tabIndex", 0 );
|
||||
} else {
|
||||
this.active.attr({
|
||||
"aria-expanded": "true",
|
||||
"aria-selected": "true",
|
||||
tabIndex: 0
|
||||
});
|
||||
})
|
||||
.next()
|
||||
.attr({
|
||||
"aria-expanded": "true",
|
||||
"aria-hidden": "false"
|
||||
});
|
||||
}
|
||||
|
||||
this._setupEvents( options.event );
|
||||
@ -124,6 +149,8 @@ $.widget( "ui.accordion", {
|
||||
},
|
||||
|
||||
_destroy: function() {
|
||||
var accordionId = this.accordionId;
|
||||
|
||||
// clean up main element
|
||||
this.element
|
||||
.removeClass( "ui-accordion ui-widget ui-helper-reset" )
|
||||
@ -131,19 +158,31 @@ $.widget( "ui.accordion", {
|
||||
|
||||
// clean up headers
|
||||
this.headers
|
||||
.unbind( ".accordion" )
|
||||
.removeClass( "ui-accordion-header ui-accordion-header-active ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top" )
|
||||
.removeAttr( "role" )
|
||||
.removeAttr( "aria-expanded" )
|
||||
.removeAttr( "aria-selected" )
|
||||
.removeAttr( "tabIndex" );
|
||||
.removeAttr( "aria-controls" )
|
||||
.removeAttr( "tabIndex" )
|
||||
.each(function() {
|
||||
if ( /^ui-accordion/.test( this.id ) ) {
|
||||
this.removeAttribute( "id" );
|
||||
}
|
||||
});
|
||||
this._destroyIcons();
|
||||
|
||||
// clean up content panels
|
||||
var contents = this.headers.next()
|
||||
.css( "display", "" )
|
||||
.removeAttr( "role" )
|
||||
.removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled" );
|
||||
.removeAttr( "aria-expanded" )
|
||||
.removeAttr( "aria-hidden" )
|
||||
.removeAttr( "aria-labelledby" )
|
||||
.removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled" )
|
||||
.each(function() {
|
||||
if ( /^ui-accordion/.test( this.id ) ) {
|
||||
this.removeAttribute( "id" );
|
||||
}
|
||||
});
|
||||
if ( this.options.heightStyle !== "content" ) {
|
||||
this.element.css( "height", this.originalHeight );
|
||||
contents.css( "height", "" );
|
||||
@ -208,6 +247,13 @@ $.widget( "ui.accordion", {
|
||||
case keyCode.SPACE:
|
||||
case keyCode.ENTER:
|
||||
this._eventHandler( event );
|
||||
break;
|
||||
case keyCode.HOME:
|
||||
toFocus = this.headers[ 0 ];
|
||||
break;
|
||||
case keyCode.END:
|
||||
toFocus = this.headers[ length - 1 ];
|
||||
break;
|
||||
}
|
||||
|
||||
if ( toFocus ) {
|
||||
@ -218,6 +264,12 @@ $.widget( "ui.accordion", {
|
||||
}
|
||||
},
|
||||
|
||||
_panelKeyDown : function( event ) {
|
||||
if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) {
|
||||
$( event.currentTarget ).prev().focus();
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function() {
|
||||
var heightStyle = this.options.heightStyle,
|
||||
parent = this.element.parent(),
|
||||
@ -305,6 +357,9 @@ $.widget( "ui.accordion", {
|
||||
});
|
||||
}
|
||||
this._bind( this.headers, events );
|
||||
this._bind( this.headers.next(), {
|
||||
keydown: "_panelKeyDown"
|
||||
});
|
||||
},
|
||||
|
||||
_eventHandler: function( event ) {
|
||||
@ -382,21 +437,26 @@ $.widget( "ui.accordion", {
|
||||
this._toggleComplete( data );
|
||||
}
|
||||
|
||||
// TODO assert that the blur and focus triggers are really necessary, remove otherwise
|
||||
toHide.prev()
|
||||
toHide
|
||||
.attr({
|
||||
"aria-expanded": "false",
|
||||
"aria-selected": "false",
|
||||
tabIndex: -1
|
||||
"aria-hidden": "true"
|
||||
})
|
||||
.blur();
|
||||
toShow.prev()
|
||||
.prev()
|
||||
.attr({
|
||||
"aria-selected": "false",
|
||||
tabIndex: -1
|
||||
});
|
||||
toShow
|
||||
.attr({
|
||||
"aria-expanded": "true",
|
||||
"aria-selected": "true",
|
||||
tabIndex: 0
|
||||
"aria-hidden": "false"
|
||||
})
|
||||
.focus();
|
||||
.prev()
|
||||
.attr({
|
||||
"aria-selected": "true",
|
||||
tabIndex: 0
|
||||
});
|
||||
},
|
||||
|
||||
_animate: function( toShow, toHide, data ) {
|
||||
|
Loading…
Reference in New Issue
Block a user