diff --git a/src/wp-includes/css/media-views.css b/src/wp-includes/css/media-views.css index f4c3f21f24..7d16c309ea 100644 --- a/src/wp-includes/css/media-views.css +++ b/src/wp-includes/css/media-views.css @@ -478,8 +478,7 @@ border-right: 0; } -.media-router > a:active, -.media-router > a:focus { +.media-router > a:active { outline: none; } @@ -696,6 +695,16 @@ 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 { -webkit-box-shadow: 0 0 0 1px #fff, @@ -925,6 +934,7 @@ right: 300px; bottom: 0; overflow: auto; + outline: none; } .attachments-browser .instructions { diff --git a/src/wp-includes/js/media-views.js b/src/wp-includes/js/media-views.js index 736ceb285b..5b398292cd 100644 --- a/src/wp-includes/js/media-views.js +++ b/src/wp-includes/js/media-views.js @@ -2313,6 +2313,12 @@ } else { frame.close(); } + + // Keep focus inside media modal + // after canceling a gallery + new media.view.FocusManager({ + el: this.el + }).focus(); } }, separateCancel: new media.View({ @@ -2495,6 +2501,12 @@ }) ); 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'); + + // 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'); + + // 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, freeze: true }); + + this.focusManager = new media.view.FocusManager({ + el: this.el + }); }, /** * @returns {Object} @@ -3037,7 +3065,12 @@ 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'); // If the `freeze` option is set, restore the container's scroll position. @@ -3098,6 +3131,9 @@ if ( 27 === event.which && this.$el.is(':visible') ) { this.escape(); event.stopImmediatePropagation(); + } else { + // Keep focus inside the media modal + this.focusManager; } } }); @@ -3117,15 +3153,8 @@ }, focus: function() { - if ( _.isUndefined( this.index ) ) { - return; - } - - // Update our collection of `$tabbables`. - this.$tabbables = this.$(':tabbable'); - - // If tab is saved, focus it. - this.$tabbables.eq( this.index ).focus(); + // Reset focus on first left menu item + $('.media-menu-item').first().focus(); }, /** * @param {Object} event @@ -3136,37 +3165,23 @@ return; } - // First try to update the index. - if ( _.isUndefined( this.index ) ) { - this.updateIndex( event ); - } - - // If we still don't have an index, bail. - if ( _.isUndefined( this.index ) ) { - return; - } - - var index = this.index + ( event.shiftKey ? -1 : 1 ); - - if ( index >= 0 && index < this.$tabbables.length ) { - this.index = index; - } else { - delete this.index; + // Keep tab focus within media modal while it's open + if ( event.target === this.tabbableLast[0] && !event.shiftKey ) { + this.tabbableFirst.focus(); + return false; + } else if ( event.target === this.tabbableFirst[0] && event.shiftKey ) { + this.tabbableLast.focus(); + return false; } }, /** * @param {Object} event */ updateIndex: function( event ) { - this.$tabbables = this.$(':tabbable'); - - var index = this.$tabbables.index( event.target ); - - if ( -1 === index ) { - delete this.index; - } else { - this.index = index; - } + // Resets tabbable elements + this.tabbables = $( ':tabbable', this.$el ); + this.tabbableFirst = this.tabbables.filter( ':first' ); + this.tabbableLast = this.tabbables.filter( ':last' ); } }); @@ -4397,6 +4412,11 @@ className: 'attachment', template: media.template('attachment'), + attributes: { + tabIndex: 0, + role: 'checkbox' + }, + events: { 'click .attachment-preview': 'toggleSelectionHandler', 'change [data-setting]': 'updateSetting', @@ -4405,7 +4425,8 @@ 'change [data-setting] textarea': 'updateSetting', 'click .close': 'removeFromLibrary', 'click .check': 'removeFromSelection', - 'click a': 'preventDefault' + 'click a': 'preventDefault', + 'keydown': 'toggleSelectionHandler' }, buttons: {}, @@ -4413,6 +4434,7 @@ initialize: function() { 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:title', this._syncTitle, this ); this.model.on( 'change:caption', this._syncCaption, this ); @@ -4517,6 +4539,10 @@ toggleSelectionHandler: function( event ) { var method; + // Catch enter and space events + if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { + return; + } if ( event.shiftKey ) { method = 'between'; } else if ( event.ctrlKey || event.metaKey ) { @@ -4573,6 +4599,10 @@ return; } + // Fixes bug that loses focus when selecting a featured image + if ( !method ) { + method = 'add'; + } if ( method !== 'add' ) { method = 'reset'; } @@ -4617,7 +4647,7 @@ return; } - this.$el.addClass('selected'); + this.$el.addClass('selected').attr('aria-checked', true); }, /** * @param {Backbone.Model} model @@ -4632,7 +4662,7 @@ if ( ! selection || ( collection && collection !== selection ) ) { return; } - this.$el.removeClass('selected'); + this.$el.removeClass('selected').attr('aria-checked', false); }, /** * @param {Backbone.Model} model @@ -4865,6 +4895,10 @@ tagName: 'ul', className: 'attachments', + attributes: { + tabIndex: -1 + }, + cssTemplate: media.template('attachments-css'), events: { @@ -5579,6 +5613,12 @@ clear: function( event ) { event.preventDefault(); 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() { - /** - * @member {wp.media.view.FocusManager} - */ - this.focusManager = new media.view.FocusManager({ - el: this.el - }); /** * call 'initialize' directly on the parent class */ @@ -5922,7 +5956,6 @@ * call 'render' directly on the parent class */ media.view.Attachment.prototype.render.apply( this, arguments ); - this.focusManager.focus(); return this; }, /** @@ -5933,6 +5966,11 @@ if ( confirm( l10n.warnDelete ) ) { 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() { - /** - * @member {wp.media.view.FocusManager} - */ - this.focusManager = new media.view.FocusManager({ - el: this.el - }); - this.model.on( 'change:compat', this.render, this ); }, /** @@ -6021,8 +6052,6 @@ this.views.detach(); this.$el.html( compat.item ); this.views.render(); - - this.focusManager.focus(); return this; }, /** diff --git a/src/wp-includes/media-template.php b/src/wp-includes/media-template.php index 324ddf2ff2..59c018ca9a 100644 --- a/src/wp-includes/media-template.php +++ b/src/wp-includes/media-template.php @@ -119,6 +119,16 @@ function wp_print_media_templates() { if ( $is_IE && strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE 7') !== false ) $class .= ' ie7'; ?> +