Ticket #47149: 47149.diff
File 47149.diff, 10.6 KB (added by , 6 years ago) |
---|
-
src/js/media/views/focus-manager.js
1 var $ = jQuery; 2 1 3 /** 2 4 * wp.media.view.FocusManager 3 5 * … … 11 13 var FocusManager = wp.media.View.extend(/** @lends wp.media.view.FocusManager.prototype */{ 12 14 13 15 events: { 14 'keydown': ' constrainTabbing'16 'keydown': 'focusManagementMode' 15 17 }, 16 18 19 initialize: function( options ) { 20 this.mode = options.mode || 'constrainTabbing'; 21 this.tabsAutomaticActivation = options.tabsAutomaticActivation || false; 22 }, 23 17 24 /** 25 * Determines which focus management mode to use. 26 * 27 * @param {object} event jQuery event object. 28 * 29 * @returns {void} 30 */ 31 focusManagementMode: function( event ) { 32 if ( this.mode === 'constrainTabbing' ) { 33 this.constrainTabbing( event ); 34 } 35 36 if ( this.mode === 'tabsNavigation' ) { 37 this.tabsNavigation( event ); 38 } 39 }, 40 41 /** 18 42 * Gets all the tabbable elements. 43 * 44 * @returns {object} jQuery collection of tabbable elements. 19 45 */ 20 46 getTabbables: function() { 21 47 // Skip the file input added by Plupload. … … 24 50 25 51 /** 26 52 * Moves focus to the modal dialog. 53 * 54 * @returns {void} 27 55 */ 28 56 focus: function() { 29 57 this.$( '.media-modal' ).focus(); … … 30 58 }, 31 59 32 60 /** 33 * @param {Object} event 61 * Constrains navigation with the Tab key within the modal. 62 * 63 * @param {object} event jQuery event object. 64 * 65 * @returns {void} 34 66 */ 35 67 constrainTabbing: function( event ) { 36 68 var tabbables; … … 50 82 tabbables.last().focus(); 51 83 return false; 52 84 } 85 }, 86 87 // Holds the collection of tabs. 88 tabs: $(), 89 90 /** 91 * Initializes tabs in an ARIA tabbed interface. 92 * 93 * @param {object} event jQuery event object. 94 * 95 * @returns {void} 96 */ 97 initializeAriaTabs: function() { 98 this.tabs = this.$( '[role="tab"]' ); 99 100 // Set up initial attributes. 101 this.tabs.attr( { 102 'aria-selected': false, 103 tabIndex: '-1' 104 } ); 105 106 // Set up attributes on the initially active tab. 107 this.tabs.filter( '.active' ) 108 .removeAttr( 'tabindex' ) 109 .attr( 'aria-selected', 'true' ); 110 }, 111 112 /** 113 * Enables arrows navigation within the ARIA tabbed interface. 114 * 115 * @param {object} event jQuery event object. 116 * 117 * @returns {void} 118 */ 119 tabsNavigation: function( event ) { 120 var orientation = 'horizontal', 121 keys = [ 32, 35, 36, 37, 38, 39, 40 ]; 122 123 // Return if not Spacebar, End, Home, or Arrow keys. 124 if ( keys.indexOf( event.which ) === -1 ) { 125 return; 126 } 127 128 // Determine navigation direction. 129 if ( this.$el.attr( 'aria-orientation' ) === 'vertical' ) { 130 orientation = 'vertical'; 131 } 132 133 // Make Up and Down arrow keys do nothing with horizontal tabs. 134 if ( orientation === 'horizontal' && [ 38, 40 ].indexOf( event.which ) !== -1 ) { 135 return; 136 } 137 138 // Make Left and Right arrow keys do nothing with vertical tabs. 139 if ( orientation === 'vertical' && [ 37, 39 ].indexOf( event.which ) !== -1 ) { 140 return; 141 } 142 143 this.switchTabs( event, this.tabs ); 144 }, 145 146 /** 147 * Switch tabs in the ARIA tabbed interface. 148 * 149 * @param {object} event jQuery event object. 150 * 151 * @returns {void} 152 */ 153 switchTabs: function( event ) { 154 var key = event.which, 155 index = this.tabs.index( jQuery( event.target ) ), 156 newIndex; 157 158 switch ( key ) { 159 // Space bar: Activate current targeted tab. 160 case 32: { 161 this.activateTab( this.tabs[ index ] ); 162 break; 163 } 164 // End key: Activate last tab. 165 case 35: { 166 event.preventDefault(); 167 this.activateTab( this.tabs[ this.tabs.length - 1 ] ); 168 break; 169 } 170 // Home key: Activate first tab. 171 case 36: { 172 event.preventDefault(); 173 this.activateTab( this.tabs[ 0 ] ); 174 break; 175 } 176 // Left and up keys: Activate previous tab. 177 case 37: 178 case 38: { 179 event.preventDefault(); 180 newIndex = ( index - 1 ) < 0 ? this.tabs.length - 1 : index - 1; 181 this.activateTab( this.tabs[ newIndex ] ); 182 break; 183 } 184 // Right and down keys: Activate next tab. 185 case 39: 186 case 40: { 187 event.preventDefault(); 188 newIndex = ( index + 1 ) === this.tabs.length ? 0 : index + 1; 189 this.activateTab( this.tabs[ newIndex ] ); 190 break; 191 } 192 } 193 }, 194 195 /** 196 * Sets all the tabs to be not focusable and semantically not selected. 197 * 198 * @param {object} tabs The tabs jQuery collection. 199 * 200 * @returns {void} 201 */ 202 deactivateTabs: function( tabs ) { 203 tabs 204 .attr( { 205 'aria-selected': 'false', 206 tabIndex: '-1' 207 } ); 208 }, 209 210 /** 211 * Sets a single tab to be focusable and semantically selected. 212 * 213 * @param {object} tab The tab DOM element. 214 * 215 * @returns {void} 216 */ 217 activateTab: function( tab ) { 218 if ( ! tab ) { 219 return; 220 } 221 222 this.deactivateTabs( this.tabs ); 223 224 // The tab is a DOM element: no need for jQuery methods. 225 tab.focus(); 226 227 // Handle manual activation. 228 if ( ! this.tabsAutomaticActivation ) { 229 tab.removeAttribute( 'tabindex' ); 230 tab.setAttribute( 'aria-selected', 'true' ); 231 return; 232 } 233 234 // Handle automatic activation. 235 $( tab ).on( 'click', function() { 236 tab.removeAttribute( 'tabindex' ); 237 tab.setAttribute( 'aria-selected', 'true' ); 238 } ); 239 240 tab.click(); 53 241 } 54 55 242 }); 56 243 57 244 module.exports = FocusManager; -
src/js/media/views/frame/post.js
257 257 mainMenu: function( view ) { 258 258 view.set({ 259 259 'library-separator': new wp.media.View({ 260 className: 'separator', 261 priority: 100 260 className: 'separator', 261 priority: 100, 262 attributes: { 263 role: 'presentation' 264 } 262 265 }) 263 266 }); 264 267 }, -
src/js/media/views/media-frame.js
110 110 */ 111 111 createMenu: function( menu ) { 112 112 menu.view = new wp.media.view.Menu({ 113 controller: this 113 controller: this, 114 115 attributes: { 116 role: 'tablist', 117 'aria-orientation': 'vertical' 118 } 114 119 }); 115 120 }, 116 121 -
src/js/media/views/menu-item.js
11 11 * @augments Backbone.View 12 12 */ 13 13 MenuItem = wp.media.View.extend(/** @lends wp.media.view.MenuItem.prototype */{ 14 tagName: ' a',14 tagName: 'button', 15 15 className: 'media-menu-item', 16 16 17 17 attributes: { 18 href: '#' 18 type: 'button', 19 role: 'tab' 19 20 }, 20 21 21 22 events: { 22 23 'click': '_click' 23 24 }, 25 24 26 /** 25 * @param {Object} event27 * Allows to override the click event. 26 28 */ 27 _click: function( event) {29 _click: function() { 28 30 var clickOverride = this.options.click; 29 31 30 if ( event ) {31 event.preventDefault();32 }33 34 32 if ( clickOverride ) { 35 33 clickOverride.call( this ); 36 34 } else { … … 43 41 44 42 if ( state ) { 45 43 this.controller.setState( state ); 44 // Toggle the menu visibility in the responsive view. 46 45 this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below 47 46 } 48 47 }, 48 49 49 /** 50 50 * @returns {wp.media.view.MenuItem} returns itself to allow chaining 51 51 */ -
src/js/media/views/menu.js
20 20 ItemView: MenuItem, 21 21 region: 'menu', 22 22 23 /* TODO: alternatively hide on any click anywhere24 events: {25 ' click': 'click'23 attributes: { 24 role: 'tablist', 25 'aria-orientation': 'horizontal' 26 26 }, 27 27 28 click: function() { 29 this.$el.removeClass( 'visible' ); 28 initialize: function() { 29 this._views = {}; 30 31 this.set( _.extend( {}, this._views, this.options.views ), { silent: true }); 32 delete this.options.views; 33 34 if ( ! this.options.silent ) { 35 this.render(); 36 } 37 38 // Initialize the Focus Manager. 39 this.focusManager = new wp.media.view.FocusManager( { 40 el: this.el, 41 mode: 'tabsNavigation' 42 } ); 30 43 }, 31 */32 44 33 45 /** 34 46 * @param {Object} options … … 47 59 */ 48 60 PriorityList.prototype.ready.apply( this, arguments ); 49 61 this.visibility(); 62 63 // Set up aria tabs initial attributes. 64 this.focusManager.initializeAriaTabs(); 50 65 }, 51 66 52 67 set: function() { … … 87 102 88 103 this.deselect(); 89 104 view.$el.addClass('active'); 105 106 // Set up again the aria tabs initial attributes after the menu updates. 107 this.focusManager.initializeAriaTabs(); 90 108 }, 91 109 92 110 deselect: function() { -
src/js/media/views/router.js
20 20 ItemView: wp.media.view.RouterItem, 21 21 region: 'router', 22 22 23 attributes: { 24 role: 'tablist', 25 'aria-orientation': 'horizontal' 26 }, 27 23 28 initialize: function() { 24 29 this.controller.on( 'content:render', this.update, this ); 25 30 // Call 'initialize' directly on the parent class. -
src/wp-includes/css/media-views.css
553 553 user-select: none; 554 554 } 555 555 556 .media-menu > a{556 .media-menu .media-menu-item { 557 557 display: block; 558 558 position: relative; 559 559 padding: 8px 20px; … … 564 564 text-decoration: none; 565 565 } 566 566 567 .media-menu > a:hover {567 .media-menu .media-menu-item:hover { 568 568 color: #0073aa; 569 569 background: rgba(0, 0, 0, 0.04); 570 570 } 571 571 572 .media-menu > a:active {572 .media-menu .media-menu-item:active { 573 573 outline: none; 574 574 } 575 575 … … 600 600 user-select: none; 601 601 } 602 602 603 .media-router a{603 .media-router .media-menu-item { 604 604 transition: none; 605 605 } 606 606 607 .media-router > a{607 .media-router .media-menu-item { 608 608 position: relative; 609 609 float: left; 610 610 padding: 8px 10px 9px; … … 615 615 text-decoration: none; 616 616 } 617 617 618 .media-router > a:last-child {618 .media-router .media-menu-item:last-child { 619 619 border-right: 0; 620 620 } 621 621 622 .media-router > a:active {622 .media-router .media-menu-item:active { 623 623 outline: none; 624 624 } 625 625 … … 629 629 } 630 630 631 631 .media-router .active, 632 .media-router > a.active:last-child {632 .media-router .media-menu-item.active:last-child { 633 633 margin: -1px -1px 0; 634 634 background: #fff; 635 635 border: 1px solid #ddd;