Skip to content

Commit b9e438d

Browse files
dylanbscottgonzalez
authored andcommitted
Tooltip: Improve accessibility by adding content to an aria-live div
Fixes #9610 Closes jquerygh-1118
1 parent c9042b9 commit b9e438d

File tree

3 files changed

+54
-8
lines changed

3 files changed

+54
-8
lines changed

tests/unit/tooltip/tooltip_core.js

+24-4
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,47 @@ test( "markup structure", function() {
2121
});
2222

2323
test( "accessibility", function() {
24-
expect( 5 );
24+
expect( 15 );
2525

26-
var tooltipId,
27-
tooltip,
28-
element = $( "#multiple-describedby" ).tooltip();
26+
var tooltipId, tooltip,
27+
element = $( "#multiple-describedby" ).tooltip(),
28+
liveRegion = element.tooltip( "instance" ).liveRegion;
2929

30+
equal( liveRegion.find( ">div" ).length, 0 );
31+
equal( liveRegion.attr( "role" ), "log" );
32+
equal( liveRegion.attr( "aria-live" ), "assertive" );
33+
equal( liveRegion.attr( "aria-relevant" ), "additions" );
3034
element.tooltip( "open" );
3135
tooltipId = element.data( "ui-tooltip-id" );
3236
tooltip = $( "#" + tooltipId );
3337
equal( tooltip.attr( "role" ), "tooltip", "role" );
3438
equal( element.attr( "aria-describedby" ), "fixture-span " + tooltipId,
3539
"multiple describedby when open" );
40+
3641
// strictEqual to distinguish between .removeAttr( "title" ) and .attr( "title", "" )
3742
// support: jQuery <1.6.2
3843
// support: IE <8
3944
// We should use strictEqual( ..., undefined ) when dropping jQuery 1.6.1 support (or IE6/7)
4045
ok( !element.attr( "title" ), "no title when open" );
46+
equal( liveRegion.children().length, 1 );
47+
equal( liveRegion.children().last().html(), "..." );
4148
element.tooltip( "close" );
4249
equal( element.attr( "aria-describedby" ), "fixture-span",
4350
"correct describedby when closed" );
4451
equal( element.attr( "title" ), "...", "title restored when closed" );
52+
53+
element.tooltip( "open" );
54+
equal( liveRegion.children().length, 2,
55+
"After the second tooltip show, there should be two children" );
56+
equal( liveRegion.children().filter( ":visible" ).length, 1,
57+
"Only one of the children should be visible" );
58+
ok( liveRegion.children().last().is( ":visible" ),
59+
"Only the last child should be visible" );
60+
element.tooltip( "close" );
61+
62+
element.tooltip( "destroy" );
63+
equal( liveRegion.parent().length, 0,
64+
"Tooltip liveregion element should be removed" );
4565
});
4666

4767
test( "delegated removal", function() {

tests/unit/tooltip/tooltip_options.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,16 @@ test( "content: return string", function() {
4141
});
4242

4343
test( "content: return jQuery", function() {
44-
expect( 1 );
44+
expect( 2 );
4545
var element = $( "#tooltipped1" ).tooltip({
4646
content: function() {
47-
return $( "<div>" ).html( "cu<b>s</b>tomstring" );
47+
return $( "<div id='unique'>" ).html( "cu<b id='bold'>s</b>tomstring" );
4848
}
49-
}).tooltip( "open" );
49+
}).tooltip( "open" ),
50+
liveRegion = element.tooltip( "instance" ).liveRegion;
5051
deepEqual( $( "#" + element.data( "ui-tooltip-id" ) ).text(), "customstring" );
52+
equal( liveRegion.children().last().html(), "<div>cu<b>s</b>tomstring</div>",
53+
"The accessibility live region will strip the ids but keep the structure" );
5154
});
5255

5356
asyncTest( "content: sync + async callback", function() {

ui/jquery.ui.tooltip.js

+24-1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,16 @@ $.widget( "ui.tooltip", {
8282
if ( this.options.disabled ) {
8383
this._disable();
8484
}
85+
86+
// Append the aria-live region so tooltips announce correctly
87+
this.liveRegion = $( "<div>" )
88+
.attr({
89+
role: "log",
90+
"aria-live": "assertive",
91+
"aria-relevant": "additions"
92+
})
93+
.addClass( "ui-helper-hidden-accessible" )
94+
.appendTo( this.document[ 0 ].body );
8595
},
8696

8797
_setOption: function( key, value ) {
@@ -211,7 +221,7 @@ $.widget( "ui.tooltip", {
211221
},
212222

213223
_open: function( event, target, content ) {
214-
var tooltip, events, delayedShow,
224+
var tooltip, events, delayedShow, a11yContent,
215225
positionOption = $.extend( {}, this.options.position );
216226

217227
if ( !content ) {
@@ -245,6 +255,18 @@ $.widget( "ui.tooltip", {
245255
this._addDescribedBy( target, tooltip.attr( "id" ) );
246256
tooltip.find( ".ui-tooltip-content" ).html( content );
247257

258+
// Support: Voiceover on OS X, JAWS on IE <= 9
259+
// JAWS announces deletions even when aria-relevant="additions"
260+
// Voiceover will sometimes re-read the entire log region's contents from the beginning
261+
this.liveRegion.children().hide();
262+
if ( content.clone ) {
263+
a11yContent = content.clone();
264+
a11yContent.removeAttr( "id" ).find( "[id]" ).removeAttr( "id" );
265+
} else {
266+
a11yContent = content;
267+
}
268+
$( "<div>" ).html( a11yContent ).appendTo( this.liveRegion );
269+
248270
function position( event ) {
249271
positionOption.of = event;
250272
if ( tooltip.is( ":hidden" ) ) {
@@ -394,6 +416,7 @@ $.widget( "ui.tooltip", {
394416
element.removeData( "ui-tooltip-title" );
395417
}
396418
});
419+
this.liveRegion.remove();
397420
}
398421
});
399422

0 commit comments

Comments
 (0)