At long last, improved keyboard accessibility for the media modal.

props lessbloat, grahamarmfield, sharonaustin, bramd.
see #23560.

Built from https://develop.svn.wordpress.org/trunk@28607


git-svn-id: http://core.svn.wordpress.org/trunk@28431 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Helen Hou-Sandí 2014-05-29 03:39:15 +00:00
parent c4243e53d0
commit 78d90bd443
7 changed files with 124 additions and 65 deletions

View File

@ -478,8 +478,7 @@
border-left: 0; border-left: 0;
} }
.media-router > a:active, .media-router > a:active {
.media-router > a:focus {
outline: none; outline: none;
} }
@ -696,6 +695,16 @@
user-select: none; user-select: none;
} }
.attachment:focus {
-webkit-box-shadow:
0 0 0 1px #5b9dd9,
0 0 2px 2px #5b9dd9;
box-shadow:
0 0 0 1px #5b9dd9,
0 0 2px 2px #5b9dd9;
outline: none;
}
.selected.attachment { .selected.attachment {
-webkit-box-shadow: -webkit-box-shadow:
0 0 0 1px #fff, 0 0 0 1px #fff,
@ -925,6 +934,7 @@
left: 300px; left: 300px;
bottom: 0; bottom: 0;
overflow: auto; overflow: auto;
outline: none;
} }
.attachments-browser .instructions { .attachments-browser .instructions {

File diff suppressed because one or more lines are too long

View File

@ -478,8 +478,7 @@
border-right: 0; border-right: 0;
} }
.media-router > a:active, .media-router > a:active {
.media-router > a:focus {
outline: none; outline: none;
} }
@ -696,6 +695,16 @@
user-select: none; user-select: none;
} }
.attachment:focus {
-webkit-box-shadow:
0 0 0 1px #5b9dd9,
0 0 2px 2px #5b9dd9;
box-shadow:
0 0 0 1px #5b9dd9,
0 0 2px 2px #5b9dd9;
outline: none;
}
.selected.attachment { .selected.attachment {
-webkit-box-shadow: -webkit-box-shadow:
0 0 0 1px #fff, 0 0 0 1px #fff,
@ -925,6 +934,7 @@
right: 300px; right: 300px;
bottom: 0; bottom: 0;
overflow: auto; overflow: auto;
outline: none;
} }
.attachments-browser .instructions { .attachments-browser .instructions {

File diff suppressed because one or more lines are too long

View File

@ -2313,6 +2313,12 @@
} else { } else {
frame.close(); frame.close();
} }
// Keep focus inside media modal
// after canceling a gallery
new media.view.FocusManager({
el: this.el
}).focus();
} }
}, },
separateCancel: new media.View({ separateCancel: new media.View({
@ -2495,6 +2501,12 @@
}) ); }) );
this.controller.setState('gallery-edit'); this.controller.setState('gallery-edit');
// Keep focus inside media modal
// after jumping to gallery view
new media.view.FocusManager({
el: this.el
}).focus();
} }
}); });
}, },
@ -2521,6 +2533,12 @@
}) ); }) );
this.controller.setState('playlist-edit'); this.controller.setState('playlist-edit');
// Keep focus inside media modal
// after jumping to playlist view
new media.view.FocusManager({
el: this.el
}).focus();
} }
}); });
}, },
@ -2547,6 +2565,12 @@
}) ); }) );
this.controller.setState('video-playlist-edit'); this.controller.setState('video-playlist-edit');
// Keep focus inside media modal
// after jumping to video playlist view
new media.view.FocusManager({
el: this.el
}).focus();
} }
}); });
}, },
@ -2956,6 +2980,10 @@
propagate: true, propagate: true,
freeze: true freeze: true
}); });
this.focusManager = new media.view.FocusManager({
el: this.el
});
}, },
/** /**
* @returns {Object} * @returns {Object}
@ -3037,7 +3065,12 @@
return this; return this;
} }
this.$el.hide(); // Hide modal and remove restricted media modal tab focus once it's closed
this.$el.hide().undelegate( 'keydown' );
// Put focus back in useful location once modal is closed
$('#wpbody-content').focus();
this.propagate('close'); this.propagate('close');
// If the `freeze` option is set, restore the container's scroll position. // If the `freeze` option is set, restore the container's scroll position.
@ -3098,6 +3131,9 @@
if ( 27 === event.which && this.$el.is(':visible') ) { if ( 27 === event.which && this.$el.is(':visible') ) {
this.escape(); this.escape();
event.stopImmediatePropagation(); event.stopImmediatePropagation();
} else {
// Keep focus inside the media modal
this.focusManager;
} }
} }
}); });
@ -3117,15 +3153,8 @@
}, },
focus: function() { focus: function() {
if ( _.isUndefined( this.index ) ) { // Reset focus on first left menu item
return; $('.media-menu-item').first().focus();
}
// Update our collection of `$tabbables`.
this.$tabbables = this.$(':tabbable');
// If tab is saved, focus it.
this.$tabbables.eq( this.index ).focus();
}, },
/** /**
* @param {Object} event * @param {Object} event
@ -3136,37 +3165,23 @@
return; return;
} }
// First try to update the index. // Keep tab focus within media modal while it's open
if ( _.isUndefined( this.index ) ) { if ( event.target === this.tabbableLast[0] && !event.shiftKey ) {
this.updateIndex( event ); this.tabbableFirst.focus();
} return false;
} else if ( event.target === this.tabbableFirst[0] && event.shiftKey ) {
// If we still don't have an index, bail. this.tabbableLast.focus();
if ( _.isUndefined( this.index ) ) { return false;
return;
}
var index = this.index + ( event.shiftKey ? -1 : 1 );
if ( index >= 0 && index < this.$tabbables.length ) {
this.index = index;
} else {
delete this.index;
} }
}, },
/** /**
* @param {Object} event * @param {Object} event
*/ */
updateIndex: function( event ) { updateIndex: function( event ) {
this.$tabbables = this.$(':tabbable'); // Resets tabbable elements
this.tabbables = $( ':tabbable', this.$el );
var index = this.$tabbables.index( event.target ); this.tabbableFirst = this.tabbables.filter( ':first' );
this.tabbableLast = this.tabbables.filter( ':last' );
if ( -1 === index ) {
delete this.index;
} else {
this.index = index;
}
} }
}); });
@ -4397,6 +4412,11 @@
className: 'attachment', className: 'attachment',
template: media.template('attachment'), template: media.template('attachment'),
attributes: {
tabIndex: 0,
role: 'checkbox'
},
events: { events: {
'click .attachment-preview': 'toggleSelectionHandler', 'click .attachment-preview': 'toggleSelectionHandler',
'change [data-setting]': 'updateSetting', 'change [data-setting]': 'updateSetting',
@ -4405,7 +4425,8 @@
'change [data-setting] textarea': 'updateSetting', 'change [data-setting] textarea': 'updateSetting',
'click .close': 'removeFromLibrary', 'click .close': 'removeFromLibrary',
'click .check': 'removeFromSelection', 'click .check': 'removeFromSelection',
'click a': 'preventDefault' 'click a': 'preventDefault',
'keydown': 'toggleSelectionHandler'
}, },
buttons: {}, buttons: {},
@ -4413,6 +4434,7 @@
initialize: function() { initialize: function() {
var selection = this.options.selection; var selection = this.options.selection;
this.$el.attr('aria-label', this.model.attributes.title).attr('aria-checked', false);
this.model.on( 'change:sizes change:uploading', this.render, this ); this.model.on( 'change:sizes change:uploading', this.render, this );
this.model.on( 'change:title', this._syncTitle, this ); this.model.on( 'change:title', this._syncTitle, this );
this.model.on( 'change:caption', this._syncCaption, this ); this.model.on( 'change:caption', this._syncCaption, this );
@ -4517,6 +4539,10 @@
toggleSelectionHandler: function( event ) { toggleSelectionHandler: function( event ) {
var method; var method;
// Catch enter and space events
if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
return;
}
if ( event.shiftKey ) { if ( event.shiftKey ) {
method = 'between'; method = 'between';
} else if ( event.ctrlKey || event.metaKey ) { } else if ( event.ctrlKey || event.metaKey ) {
@ -4573,6 +4599,10 @@
return; return;
} }
// Fixes bug that loses focus when selecting a featured image
if ( !method ) {
method = 'add';
}
if ( method !== 'add' ) { if ( method !== 'add' ) {
method = 'reset'; method = 'reset';
} }
@ -4617,7 +4647,7 @@
return; return;
} }
this.$el.addClass('selected'); this.$el.addClass('selected').attr('aria-checked', true);
}, },
/** /**
* @param {Backbone.Model} model * @param {Backbone.Model} model
@ -4632,7 +4662,7 @@
if ( ! selection || ( collection && collection !== selection ) ) { if ( ! selection || ( collection && collection !== selection ) ) {
return; return;
} }
this.$el.removeClass('selected'); this.$el.removeClass('selected').attr('aria-checked', false);
}, },
/** /**
* @param {Backbone.Model} model * @param {Backbone.Model} model
@ -4865,6 +4895,10 @@
tagName: 'ul', tagName: 'ul',
className: 'attachments', className: 'attachments',
attributes: {
tabIndex: -1
},
cssTemplate: media.template('attachments-css'), cssTemplate: media.template('attachments-css'),
events: { events: {
@ -5579,6 +5613,12 @@
clear: function( event ) { clear: function( event ) {
event.preventDefault(); event.preventDefault();
this.collection.reset(); this.collection.reset();
// Keep focus inside media modal
// after clear link is selected
new media.view.FocusManager({
el: this.el
}).focus();
} }
}); });
@ -5903,12 +5943,6 @@
}, },
initialize: function() { initialize: function() {
/**
* @member {wp.media.view.FocusManager}
*/
this.focusManager = new media.view.FocusManager({
el: this.el
});
/** /**
* call 'initialize' directly on the parent class * call 'initialize' directly on the parent class
*/ */
@ -5922,7 +5956,6 @@
* call 'render' directly on the parent class * call 'render' directly on the parent class
*/ */
media.view.Attachment.prototype.render.apply( this, arguments ); media.view.Attachment.prototype.render.apply( this, arguments );
this.focusManager.focus();
return this; return this;
}, },
/** /**
@ -5933,6 +5966,11 @@
if ( confirm( l10n.warnDelete ) ) { if ( confirm( l10n.warnDelete ) ) {
this.model.destroy(); this.model.destroy();
// Keep focus inside media modal
// after image is deleted
new media.view.FocusManager({
el: this.el
}).focus();
} }
}, },
/** /**
@ -5988,13 +6026,6 @@
}, },
initialize: function() { initialize: function() {
/**
* @member {wp.media.view.FocusManager}
*/
this.focusManager = new media.view.FocusManager({
el: this.el
});
this.model.on( 'change:compat', this.render, this ); this.model.on( 'change:compat', this.render, this );
}, },
/** /**
@ -6021,8 +6052,6 @@
this.views.detach(); this.views.detach();
this.$el.html( compat.item ); this.$el.html( compat.item );
this.views.render(); this.views.render();
this.focusManager.focus();
return this; return this;
}, },
/** /**

File diff suppressed because one or more lines are too long

View File

@ -119,6 +119,16 @@ function wp_print_media_templates() {
if ( $is_IE && strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE 7') !== false ) if ( $is_IE && strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE 7') !== false )
$class .= ' ie7'; $class .= ' ie7';
?> ?>
<!--[if lte IE 8]>
<style>
.attachment:focus {
outline: #1e8cbe solid;
}
.selected.attachment {
outline: #1e8cbe solid;
}
</style>
<![endif]-->
<script type="text/html" id="tmpl-media-frame"> <script type="text/html" id="tmpl-media-frame">
<div class="media-frame-menu"></div> <div class="media-frame-menu"></div>
<div class="media-frame-title"></div> <div class="media-frame-title"></div>
@ -238,7 +248,7 @@ function wp_print_media_templates() {
<# } else if ( 'image' === data.type ) { #> <# } else if ( 'image' === data.type ) { #>
<div class="thumbnail"> <div class="thumbnail">
<div class="centered"> <div class="centered">
<img src="{{ data.size.url }}" draggable="false" /> <img src="{{ data.size.url }}" draggable="false" alt="" />
</div> </div>
</div> </div>
<# } else { #> <# } else { #>
@ -253,7 +263,7 @@ function wp_print_media_templates() {
<# } #> <# } #>
<# if ( data.buttons.check ) { #> <# if ( data.buttons.check ) { #>
<a class="check" href="#" title="<?php esc_attr_e('Deselect'); ?>"><div class="media-modal-icon"></div></a> <a class="check" href="#" title="<?php esc_attr_e('Deselect'); ?>" tabindex="-1"><div class="media-modal-icon"></div></a>
<# } #> <# } #>
</div> </div>
<# <#