Ticket #47149: 47149.3.diff
File 47149.3.diff, 13.0 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 17 19 /** 20 * Initializes the Focus Manager. 21 * 22 * @param {object} options The Focus Manager options. 23 * 24 * @since 5.3.0 25 * 26 * @return {void} 27 */ 28 initialize: function( options ) { 29 this.mode = options.mode || 'constrainTabbing'; 30 this.tabsAutomaticActivation = options.tabsAutomaticActivation || false; 31 }, 32 33 /** 34 * Determines which focus management mode to use. 35 * 36 * @since 5.3.0 37 * 38 * @param {object} event jQuery event object. 39 * 40 * @returns {void} 41 */ 42 focusManagementMode: function( event ) { 43 if ( this.mode === 'constrainTabbing' ) { 44 this.constrainTabbing( event ); 45 } 46 47 if ( this.mode === 'tabsNavigation' ) { 48 this.tabsNavigation( event ); 49 } 50 }, 51 52 /** 18 53 * Gets all the tabbable elements. 19 54 * 20 55 * @since 5.3.0 … … 164 199 * 165 200 * @since 5.3.0 166 201 */ 167 ariaHiddenElements: [] 202 ariaHiddenElements: [], 203 204 /** 205 * Holds the jQuery collection of ARIA tabs. 206 * 207 * @since 5.3.0 208 */ 209 tabs: $(), 210 211 /** 212 * Sets up tabs in an ARIA tabbed interface. 213 * 214 * @since 5.3.0 215 * 216 * @param {object} event jQuery event object. 217 * 218 * @returns {void} 219 */ 220 setupAriaTabs: function() { 221 this.tabs = this.$( '[role="tab"]' ); 222 223 // Set up initial attributes. 224 this.tabs.attr( { 225 'aria-selected': null, 226 tabIndex: '-1' 227 } ); 228 229 // Set up attributes on the initially active tab. 230 this.tabs.filter( '.active' ) 231 .removeAttr( 'tabindex' ) 232 .attr( 'aria-selected', 'true' ); 233 }, 234 235 /** 236 * Enables arrows navigation within the ARIA tabbed interface. 237 * 238 * @since 5.3.0 239 * 240 * @param {object} event jQuery event object. 241 * 242 * @returns {void} 243 */ 244 tabsNavigation: function( event ) { 245 var orientation = 'horizontal', 246 keys = [ 32, 35, 36, 37, 38, 39, 40 ]; 247 248 // Return if not Spacebar, End, Home, or Arrow keys. 249 if ( keys.indexOf( event.which ) === -1 ) { 250 return; 251 } 252 253 // Determine navigation direction. 254 if ( this.$el.attr( 'aria-orientation' ) === 'vertical' ) { 255 orientation = 'vertical'; 256 } 257 258 // Make Up and Down arrow keys do nothing with horizontal tabs. 259 if ( orientation === 'horizontal' && [ 38, 40 ].indexOf( event.which ) !== -1 ) { 260 return; 261 } 262 263 // Make Left and Right arrow keys do nothing with vertical tabs. 264 if ( orientation === 'vertical' && [ 37, 39 ].indexOf( event.which ) !== -1 ) { 265 return; 266 } 267 268 this.switchTabs( event, this.tabs ); 269 }, 270 271 /** 272 * Switches tabs in the ARIA tabbed interface. 273 * 274 * @since 5.3.0 275 * 276 * @param {object} event jQuery event object. 277 * 278 * @returns {void} 279 */ 280 switchTabs: function( event ) { 281 var key = event.which, 282 index = this.tabs.index( jQuery( event.target ) ), 283 newIndex; 284 285 switch ( key ) { 286 // Space bar: Activate current targeted tab. 287 case 32: { 288 this.activateTab( this.tabs[ index ] ); 289 break; 290 } 291 // End key: Activate last tab. 292 case 35: { 293 event.preventDefault(); 294 this.activateTab( this.tabs[ this.tabs.length - 1 ] ); 295 break; 296 } 297 // Home key: Activate first tab. 298 case 36: { 299 event.preventDefault(); 300 this.activateTab( this.tabs[ 0 ] ); 301 break; 302 } 303 // Left and up keys: Activate previous tab. 304 case 37: 305 case 38: { 306 event.preventDefault(); 307 newIndex = ( index - 1 ) < 0 ? this.tabs.length - 1 : index - 1; 308 this.activateTab( this.tabs[ newIndex ] ); 309 break; 310 } 311 // Right and down keys: Activate next tab. 312 case 39: 313 case 40: { 314 event.preventDefault(); 315 newIndex = ( index + 1 ) === this.tabs.length ? 0 : index + 1; 316 this.activateTab( this.tabs[ newIndex ] ); 317 break; 318 } 319 } 320 }, 321 322 /** 323 * Sets a single tab to be focusable and semantically selected. 324 * 325 * @since 5.3.0 326 * 327 * @param {object} tab The tab DOM element. 328 * 329 * @returns {void} 330 */ 331 activateTab: function( tab ) { 332 if ( ! tab ) { 333 return; 334 } 335 336 // The tab is a DOM element: no need for jQuery methods. 337 tab.focus(); 338 339 // Handle automatic activation. 340 if ( this.tabsAutomaticActivation ) { 341 tab.removeAttribute( 'tabindex' ); 342 tab.setAttribute( 'aria-selected', 'true' ); 343 tab.click(); 344 345 return; 346 } 347 348 // Handle manual activation. 349 $( tab ).on( 'click', function() { 350 tab.removeAttribute( 'tabindex' ); 351 tab.setAttribute( 'aria-selected', 'true' ); 352 } ); 353 } 168 354 }); 169 355 170 356 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
80 80 81 81 // Bind default menu. 82 82 this.on( 'menu:create:default', this.createMenu, this ); 83 84 this.on( 'content:render', this.updateContentTabPanelAriaLabelledBy, this ); 83 85 }, 86 84 87 /** 88 * Updates the aria-labelledby to be used on an ARIA tab pabel. 89 * 90 * @since 5.3.0 91 * 92 * @returns {string} The aria-labelledby value to use. 93 */ 94 updateContentTabPanelAriaLabelledBy: function() { 95 var stateId = this.state().get( 'id' ), 96 contentTabPanelAriaLabelledBy = 'menu-item-' + stateId; 97 98 this.$el.find( '.media-frame-tab-panel' ).attr( 'aria-labelledby', contentTabPanelAriaLabelledBy ); 99 100 return contentTabPanelAriaLabelledBy; 101 }, 102 103 /** 104 * Prepares view for display. 105 * 106 * @since 5.3.0 107 * 108 * @returns {void} 109 */ 110 prepare: function() { 111 return _.defaults({ 112 tabPanelAriaLabelledBy: this.updateContentTabPanelAriaLabelledBy() 113 }, this.options ); 114 }, 115 116 /** 85 117 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 86 118 */ 87 119 render: function() { … … 110 142 */ 111 143 createMenu: function( menu ) { 112 144 menu.view = new wp.media.view.Menu({ 113 controller: this 145 controller: this, 146 147 attributes: { 148 role: 'tablist', 149 'aria-orientation': 'vertical' 150 } 114 151 }); 115 152 }, 116 153 -
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 */ … … 58 58 this.$el.html( options.html ); 59 59 } 60 60 61 this.$el.attr( 'id', 'menu-item-' + this.options.state ); 62 61 63 return this; 62 64 } 63 65 }); -
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.setupAriaTabs(); 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.setupAriaTabs(); 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 box-sizing: border-box; 559 width: 100%; 558 560 position: relative; 561 border: 0; 562 margin: 0; 559 563 padding: 8px 20px; 560 margin: 0;564 font-size: 14px; 561 565 line-height: 1.28571428; 562 font-size: 14px;566 background: transparent; 563 567 color: #0073aa; 568 text-align: left; 564 569 text-decoration: none; 570 cursor: pointer; 565 571 } 566 572 567 .media-menu > a:hover { 568 color: #0073aa; 573 .media-menu .media-menu-item:hover { 569 574 background: rgba(0, 0, 0, 0.04); 570 575 } 571 576 572 .media-menu > a:active { 577 .media-menu .media-menu-item:active { 578 color: #0073aa; 573 579 outline: none; 574 580 } 575 581 … … 579 585 font-weight: 600; 580 586 } 581 587 588 .media-menu .media-menu-item:focus { 589 box-shadow: 590 0 0 0 1px #5b9dd9, 591 0 0 2px 1px rgba(30, 140, 190, 0.8); 592 color: #124964; 593 /* Only visible in Windows High Contrast mode */ 594 outline: 1px solid transparent; 595 } 596 582 597 .media-menu .separator { 583 598 height: 0; 584 599 margin: 12px 20px; … … 594 609 padding: 0 6px; 595 610 margin: 0; 596 611 clear: both; 597 -webkit-user-select: none;598 -moz-user-select: none;599 -ms-user-select: none;600 user-select: none;601 612 } 602 613 603 .media-router a { 604 transition: none; 605 } 606 607 .media-router > a { 614 .media-router .media-menu-item { 608 615 position: relative; 609 616 float: left; 617 border: 0; 618 margin: 0; 610 619 padding: 8px 10px 9px; 611 margin: 0;612 620 height: 18px; 613 621 line-height: 1.28571428; 614 622 font-size: 14px; 615 623 text-decoration: none; 624 background: transparent; 625 cursor: pointer; 626 transition: none; 616 627 } 617 628 618 .media-router > a:last-child {629 .media-router .media-menu-item:last-child { 619 630 border-right: 0; 620 631 } 621 632 622 .media-router > a:active { 623 outline: none; 633 .media-router .media-menu-item:hover, 634 .media-router .media-menu-item:active { 635 color: #0073aa; 624 636 } 625 637 626 638 .media-router .active, 627 639 .media-router .active:hover { 628 color: # 32373c;640 color: #23282d; 629 641 } 630 642 643 .media-router .media-menu-item:focus { 644 box-shadow: 645 0 0 0 1px #5b9dd9, 646 0 0 2px 1px rgba(30, 140, 190, 0.8); 647 color: #124964; 648 /* Only visible in Windows High Contrast mode */ 649 outline: 1px solid transparent; 650 } 651 631 652 .media-router .active, 632 .media-router > a.active:last-child {653 .media-router .media-menu-item.active:last-child { 633 654 margin: -1px -1px 0; 634 655 background: #fff; 635 656 border: 1px solid #ddd; -
src/wp-includes/media-template.php
179 179 <script type="text/html" id="tmpl-media-frame"> 180 180 <div class="media-frame-title" id="media-frame-title"></div> 181 181 <div class="media-frame-menu"></div> 182 <div class="media-frame-router"></div> 183 <div class="media-frame-content"></div> 182 <div class="media-frame-tab-panel" role="tabpanel" aria-labelledby="{{ data.tabPanelAriaLabelledBy }}" tabindex="0"> 183 <div class="media-frame-router"></div> 184 <div class="media-frame-content"></div> 185 </div> 184 186 <div class="media-frame-toolbar"></div> 185 187 <div class="media-frame-uploader"></div> 186 188 </script>