Changeset 46363
- Timestamp:
- 09/30/2019 07:36:42 PM (5 years ago)
- Location:
- trunk/src
- Files:
-
- 8 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/js/media/views/focus-manager.js
r46239 r46363 1 var $ = jQuery; 2 1 3 /** 2 4 * wp.media.view.FocusManager … … 12 14 13 15 events: { 14 'keydown': 'constrainTabbing' 16 'keydown': 'focusManagementMode' 17 }, 18 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 } 15 50 }, 16 51 … … 68 103 69 104 /** 70 * Hides from assistive technologies all the body children except the 71 * provided element and other elements that should not be hidden. 105 * Hides from assistive technologies all the body children. 106 * 107 * Sets an `aria-hidden="true"` attribute on all the body children except 108 * the provided element and other elements that should not be hidden. 72 109 * 73 110 * The reason why we use `aria-hidden` is that `aria-modal="true"` is buggy … … 112 149 113 150 /** 114 * Makes visible again to assistive technologies all body children 151 * Unhides from assistive technologies all the body children. 152 * 153 * Makes visible again to assistive technologies all the body children 115 154 * previously hidden and stored in this.ariaHiddenElements. 116 155 * … … 166 205 * @since 5.2.3 167 206 */ 168 ariaHiddenElements: [] 207 ariaHiddenElements: [], 208 209 /** 210 * Holds the jQuery collection of ARIA tabs. 211 * 212 * @since 5.3.0 213 */ 214 tabs: $(), 215 216 /** 217 * Sets up tabs in an ARIA tabbed interface. 218 * 219 * @since 5.3.0 220 * 221 * @param {object} event jQuery event object. 222 * 223 * @returns {void} 224 */ 225 setupAriaTabs: function() { 226 this.tabs = this.$( '[role="tab"]' ); 227 228 // Set up initial attributes. 229 this.tabs.attr( { 230 'aria-selected': 'false', 231 tabIndex: '-1' 232 } ); 233 234 // Set up attributes on the initially active tab. 235 this.tabs.filter( '.active' ) 236 .removeAttr( 'tabindex' ) 237 .attr( 'aria-selected', 'true' ); 238 }, 239 240 /** 241 * Enables arrows navigation within the ARIA tabbed interface. 242 * 243 * @since 5.3.0 244 * 245 * @param {object} event jQuery event object. 246 * 247 * @returns {void} 248 */ 249 tabsNavigation: function( event ) { 250 var orientation = 'horizontal', 251 keys = [ 32, 35, 36, 37, 38, 39, 40 ]; 252 253 // Return if not Spacebar, End, Home, or Arrow keys. 254 if ( keys.indexOf( event.which ) === -1 ) { 255 return; 256 } 257 258 // Determine navigation direction. 259 if ( this.$el.attr( 'aria-orientation' ) === 'vertical' ) { 260 orientation = 'vertical'; 261 } 262 263 // Make Up and Down arrow keys do nothing with horizontal tabs. 264 if ( orientation === 'horizontal' && [ 38, 40 ].indexOf( event.which ) !== -1 ) { 265 return; 266 } 267 268 // Make Left and Right arrow keys do nothing with vertical tabs. 269 if ( orientation === 'vertical' && [ 37, 39 ].indexOf( event.which ) !== -1 ) { 270 return; 271 } 272 273 this.switchTabs( event, this.tabs ); 274 }, 275 276 /** 277 * Switches tabs in the ARIA tabbed interface. 278 * 279 * @since 5.3.0 280 * 281 * @param {object} event jQuery event object. 282 * 283 * @returns {void} 284 */ 285 switchTabs: function( event ) { 286 var key = event.which, 287 index = this.tabs.index( $( event.target ) ), 288 newIndex; 289 290 switch ( key ) { 291 // Space bar: Activate current targeted tab. 292 case 32: { 293 this.activateTab( this.tabs[ index ] ); 294 break; 295 } 296 // End key: Activate last tab. 297 case 35: { 298 event.preventDefault(); 299 this.activateTab( this.tabs[ this.tabs.length - 1 ] ); 300 break; 301 } 302 // Home key: Activate first tab. 303 case 36: { 304 event.preventDefault(); 305 this.activateTab( this.tabs[ 0 ] ); 306 break; 307 } 308 // Left and up keys: Activate previous tab. 309 case 37: 310 case 38: { 311 event.preventDefault(); 312 newIndex = ( index - 1 ) < 0 ? this.tabs.length - 1 : index - 1; 313 this.activateTab( this.tabs[ newIndex ] ); 314 break; 315 } 316 // Right and down keys: Activate next tab. 317 case 39: 318 case 40: { 319 event.preventDefault(); 320 newIndex = ( index + 1 ) === this.tabs.length ? 0 : index + 1; 321 this.activateTab( this.tabs[ newIndex ] ); 322 break; 323 } 324 } 325 }, 326 327 /** 328 * Sets a single tab to be focusable and semantically selected. 329 * 330 * @since 5.3.0 331 * 332 * @param {object} tab The tab DOM element. 333 * 334 * @returns {void} 335 */ 336 activateTab: function( tab ) { 337 if ( ! tab ) { 338 return; 339 } 340 341 // The tab is a DOM element: no need for jQuery methods. 342 tab.focus(); 343 344 // Handle automatic activation. 345 if ( this.tabsAutomaticActivation ) { 346 tab.removeAttribute( 'tabindex' ); 347 tab.setAttribute( 'aria-selected', 'true' ); 348 tab.click(); 349 350 return; 351 } 352 353 // Handle manual activation. 354 $( tab ).on( 'click', function() { 355 tab.removeAttribute( 'tabindex' ); 356 tab.setAttribute( 'aria-selected', 'true' ); 357 } ); 358 } 169 359 }); 170 360 -
trunk/src/js/media/views/frame/post.js
r45531 r46363 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 }); -
trunk/src/js/media/views/media-frame.js
r45925 r46363 24 24 25 25 events: { 26 'click div.media-frame-title h1': 'toggleMenu'26 'click .media-frame-menu-toggle': 'toggleMenu' 27 27 }, 28 28 … … 76 76 this.title.mode('default'); 77 77 78 this.on( 'title:render', function( view ) {79 view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' );80 });81 82 78 // Bind default menu. 83 79 this.on( 'menu:create:default', this.createMenu, this ); 84 }, 80 81 // Set the menu ARIA tab panel attributes when the modal opens. 82 this.on( 'open', this.setMenuTabPanelAriaAttributes, this ); 83 // Set the router ARIA tab panel attributes when the modal opens. 84 this.on( 'open', this.setRouterTabPanelAriaAttributes, this ); 85 86 // Update the menu ARIA tab panel attributes when the content updates. 87 this.on( 'content:render', this.setMenuTabPanelAriaAttributes, this ); 88 // Update the router ARIA tab panel attributes when the content updates. 89 this.on( 'content:render', this.setRouterTabPanelAriaAttributes, this ); 90 }, 91 92 /** 93 * Sets the attributes to be used on the menu ARIA tab panel. 94 * 95 * @since 5.3.0 96 * 97 * @returns {void} 98 */ 99 setMenuTabPanelAriaAttributes: function() { 100 var stateId = this.state().get( 'id' ), 101 tabPanelEl = this.$el.find( '.media-frame-tab-panel' ), 102 ariaLabelledby; 103 104 tabPanelEl.removeAttr( 'role aria-labelledby tabindex' ); 105 106 if ( this.menuView && this.menuView.isVisible ) { 107 ariaLabelledby = 'menu-item-' + stateId; 108 109 // Set the tab panel attributes only if the tabs are visible. 110 tabPanelEl 111 .attr( { 112 role: 'tabpanel', 113 'aria-labelledby': ariaLabelledby, 114 tabIndex: '0' 115 } ); 116 } 117 }, 118 119 /** 120 * Sets the attributes to be used on the router ARIA tab panel. 121 * 122 * @since 5.3.0 123 * 124 * @returns {void} 125 */ 126 setRouterTabPanelAriaAttributes: function() { 127 var tabPanelEl = this.$el.find( '.media-frame-content' ), 128 ariaLabelledby; 129 130 tabPanelEl.removeAttr( 'role aria-labelledby tabindex' ); 131 132 // On the Embed view the router menu is hidden. 133 if ( 'embed' === this.content._mode ) { 134 return; 135 } 136 137 // Set the tab panel attributes only if the tabs are visible. 138 if ( this.routerView && this.routerView.isVisible && this.content._mode ) { 139 ariaLabelledby = 'menu-item-' + this.content._mode; 140 141 tabPanelEl 142 .attr( { 143 role: 'tabpanel', 144 'aria-labelledby': ariaLabelledby, 145 tabIndex: '0' 146 } ); 147 } 148 }, 149 85 150 /** 86 151 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining … … 112 177 createMenu: function( menu ) { 113 178 menu.view = new wp.media.view.Menu({ 114 controller: this 115 }); 116 }, 117 118 toggleMenu: function() { 119 this.$el.find( '.media-menu' ).toggleClass( 'visible' ); 179 controller: this, 180 181 attributes: { 182 role: 'tablist', 183 'aria-orientation': 'vertical' 184 } 185 }); 186 187 this.menuView = menu.view; 188 }, 189 190 toggleMenu: function( event ) { 191 var menu = this.$el.find( '.media-menu' ); 192 193 menu.toggleClass( 'visible' ); 194 $( event.target ).attr( 'aria-expanded', menu.hasClass( 'visible' ) ); 120 195 }, 121 196 … … 135 210 createRouter: function( router ) { 136 211 router.view = new wp.media.view.Router({ 137 controller: this 138 }); 212 controller: this, 213 214 attributes: { 215 role: 'tablist', 216 'aria-orientation': 'horizontal' 217 } 218 }); 219 220 this.routerView = router.view; 139 221 }, 140 222 /** -
trunk/src/js/media/views/menu-item.js
r45524 r46363 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 … … 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 30 if ( event ) {31 event.preventDefault();32 }33 31 34 32 if ( clickOverride ) { … … 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 */ 52 52 render: function() { 53 var options = this.options; 53 var options = this.options, 54 menuProperty = options.state || options.contentMode; 54 55 55 56 if ( options.text ) { … … 59 60 } 60 61 62 // Set the menu item ID based on the frame state associated to the menu item. 63 this.$el.attr( 'id', 'menu-item-' + menuProperty ); 64 61 65 return this; 62 66 } -
trunk/src/js/media/views/menu.js
r43309 r46363 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 } ); 43 44 // The menu is always rendered and can be visible or hidden on some frames. 45 this.isVisible = true; 30 46 }, 31 */32 47 33 48 /** … … 48 63 PriorityList.prototype.ready.apply( this, arguments ); 49 64 this.visibility(); 65 66 // Set up aria tabs initial attributes. 67 this.focusManager.setupAriaTabs(); 50 68 }, 51 69 … … 73 91 74 92 if ( this === view ) { 93 // Flag this menu as hidden or visible. 94 this.isVisible = ! hide; 95 // Set or remove a CSS class to hide the menu. 75 96 this.controller.$el.toggleClass( 'hide-' + region, hide ); 76 97 } … … 88 109 this.deselect(); 89 110 view.$el.addClass('active'); 111 112 // Set up again the aria tabs initial attributes after the menu updates. 113 this.focusManager.setupAriaTabs(); 90 114 }, 91 115 -
trunk/src/js/media/views/router.js
r43309 r46363 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 ); -
trunk/src/wp-includes/css/media-views.css
r46362 r46363 581 581 } 582 582 583 .media-menu > a { 584 display: block; 583 .media-menu .media-menu-item { 584 display: block; 585 box-sizing: border-box; 586 width: 100%; 585 587 position: relative; 588 border: 0; 589 margin: 0; 586 590 padding: 8px 20px; 587 margin: 0;591 font-size: 14px; 588 592 line-height: 1.28571428; 589 font-size: 14px;593 background: transparent; 590 594 color: #0073aa; 595 text-align: left; 591 596 text-decoration: none; 592 } 593 594 .media-menu > a:hover { 597 cursor: pointer; 598 } 599 600 .media-menu .media-menu-item:hover { 601 background: rgba(0, 0, 0, 0.04); 602 } 603 604 .media-menu .media-menu-item:active { 595 605 color: #0073aa; 596 background: rgba(0, 0, 0, 0.04);597 }598 599 .media-menu > a:active {600 606 outline: none; 601 607 } … … 605 611 color: #23282d; 606 612 font-weight: 600; 613 } 614 615 .media-menu .media-menu-item:focus { 616 box-shadow: 617 0 0 0 1px #5b9dd9, 618 0 0 2px 1px rgba(30, 140, 190, 0.8); 619 color: #124964; 620 /* Only visible in Windows High Contrast mode */ 621 outline: 1px solid transparent; 607 622 } 608 623 … … 622 637 margin: 0; 623 638 clear: both; 624 -webkit-user-select: none; 625 -moz-user-select: none; 626 -ms-user-select: none; 627 user-select: none; 628 } 629 630 .media-router a { 631 transition: none; 632 } 633 634 .media-router > a { 639 } 640 641 .media-router .media-menu-item { 635 642 position: relative; 636 643 float: left; 644 border: 0; 645 margin: 0; 637 646 padding: 8px 10px 9px; 638 margin: 0;639 647 height: 18px; 640 648 line-height: 1.28571428; 641 649 font-size: 14px; 642 650 text-decoration: none; 643 } 644 645 .media-router > a:last-child { 651 background: transparent; 652 cursor: pointer; 653 transition: none; 654 } 655 656 .media-router .media-menu-item:last-child { 646 657 border-right: 0; 647 658 } 648 659 649 .media-router > a:active { 650 outline: none; 660 .media-router .media-menu-item:hover, 661 .media-router .media-menu-item:active { 662 color: #0073aa; 651 663 } 652 664 653 665 .media-router .active, 654 666 .media-router .active:hover { 655 color: #32373c; 667 color: #23282d; 668 } 669 670 .media-router .media-menu-item:focus { 671 box-shadow: 672 0 0 0 1px #5b9dd9, 673 0 0 2px 1px rgba(30, 140, 190, 0.8); 674 color: #124964; 675 /* Only visible in Windows High Contrast mode */ 676 outline: 1px solid transparent; 656 677 } 657 678 658 679 .media-router .active, 659 .media-router > a.active:last-child {680 .media-router .media-menu-item.active:last-child { 660 681 margin: -1px -1px 0; 661 682 background: #fff; … … 753 774 } 754 775 755 .media-frame.hide-router .media-frame-title {756 border-bottom: 1px solid #ddd;757 box-shadow: 0 4px 4px -4px rgba(0, 0, 0, 0.1);758 }759 760 .media-frame-title .dashicons {761 display: none;762 }763 764 776 .media-frame-title h1 { 765 777 padding: 0 16px; … … 767 779 line-height: 2.27272727; 768 780 margin: 0; 781 } 782 783 .wp-core-ui .button.media-frame-menu-toggle { 784 display: none; 769 785 } 770 786 … … 2252 2268 */ 2253 2269 @media only screen and (max-width: 900px) { 2270 .media-modal .media-frame-title { 2271 height: 40px; 2272 } 2273 2274 .media-modal .media-frame-title h1 { 2275 line-height: 2.22222222; 2276 font-size: 18px; 2277 } 2278 2279 .media-modal-close { 2280 width: 42px; 2281 height: 42px; 2282 } 2254 2283 2255 2284 /* Drop-down menu */ 2256 .media-frame:not(.hide-menu) .media-frame-title, 2285 .media-frame .media-frame-title { 2286 position: static; 2287 padding: 0 44px; 2288 text-align: center; 2289 } 2290 2257 2291 .media-frame:not(.hide-menu) .media-frame-router, 2258 2292 .media-frame:not(.hide-menu) .media-frame-content, … … 2261 2295 } 2262 2296 2297 .media-frame:not(.hide-menu) .media-frame-router { 2298 /* 40 title + (40 - 6) menu toggle button + 6 spacing */ 2299 top: 80px; 2300 } 2301 2302 .media-frame:not(.hide-menu) .media-frame-content { 2303 /* 80 + room for the tabs */ 2304 top: 114px; 2305 } 2306 2307 .media-frame.hide-router .media-frame-content { 2308 top: 80px; 2309 } 2310 2263 2311 .media-frame:not(.hide-menu) .media-frame-menu { 2264 2312 position: static; … … 2267 2315 2268 2316 .media-frame:not(.hide-menu) .media-menu { 2317 display: none; 2269 2318 width: auto; 2270 2319 max-width: 80%; 2271 2320 overflow: auto; 2272 2321 z-index: 2000; 2273 top: 50px;2274 left: -300px;2322 top: 75px; 2323 left: 0; 2275 2324 right: auto; 2276 2325 bottom: auto; … … 2280 2329 2281 2330 .media-frame:not(.hide-menu) .media-menu.visible { 2282 left: 0;2331 display: block; 2283 2332 } 2284 2333 … … 2288 2337 } 2289 2338 2290 .media-frame:not(.hide-menu) .media-menu > a.active {2291 display: none;2292 }2293 2294 2339 .media-frame:not(.hide-menu) .media-menu .separator { 2295 2340 margin: 5px 10px; 2296 2341 } 2297 2342 2298 .media-frame:not(.hide-menu) .media-frame-title { 2299 left: 0; 2300 } 2301 2302 .media-frame:not(.hide-menu) .media-frame-title .dashicons { 2303 display: inline-block; 2304 line-height: 2.5; 2305 } 2306 2307 .media-frame:not(.hide-menu) .media-frame-title h1 { 2308 color: #0073aa; 2309 line-height: 3; 2310 font-size: 18px; 2311 float: left; 2312 cursor: pointer; 2343 .wp-core-ui .media-frame:not(.hide-menu) .button.media-frame-menu-toggle { 2344 display: inline-flex; 2345 align-items: center; 2346 vertical-align: top; 2347 min-height: 40px; 2348 margin: -6px 6px 0; 2349 padding: 0 2px 0 12px; 2350 font-size: 0.875rem; 2351 font-weight: 600; 2352 text-decoration: none; 2353 background: transparent; 2354 } 2355 2356 .wp-core-ui .button.media-frame-menu-toggle:hover, 2357 .wp-core-ui .button.media-frame-menu-toggle:active { 2358 background: transparent; 2359 transform: none; 2360 } 2361 2362 .wp-core-ui .button.media-frame-menu-toggle:focus { 2363 /* Only visible in Windows High Contrast mode */ 2364 outline: 1px solid transparent; 2313 2365 } 2314 2366 /* End drop-down menu */ … … 2562 2614 } 2563 2615 2564 /* Landscape specific header override */2565 @media screen and (max-height: 400px) {2566 .media-menu,2567 .media-frame:not(.hide-menu) .media-menu {2568 top: 44px;2569 }2570 2571 .media-frame-router {2572 top: 44px;2573 }2574 2575 .media-frame-content {2576 top: 78px;2577 }2578 2579 .attachments-browser .attachments {2580 top: 40px;2581 }2582 2583 /* Prevent unnecessary scrolling on title input */2584 .embed-link-settings {2585 overflow: visible;2586 }2587 }2588 2589 2616 @media only screen and (min-width: 901px) and (max-height: 400px) { 2590 2617 .media-menu, … … 2596 2623 2597 2624 @media only screen and (max-width: 480px) { 2598 .media-modal-close {2599 top: -5px;2600 }2601 2602 .media-modal .media-frame-title {2603 height: 40px;2604 }2605 2606 2625 .wp-core-ui.wp-customizer .media-button { 2607 2626 margin-top: 13px; 2608 }2609 2610 .media-modal .media-frame-title h1,2611 .media-frame:not(.hide-menu) .media-frame-title h1 {2612 font-size: 18px;2613 line-height: 2.22222222;2614 }2615 2616 .media-frame:not(.hide-menu) .media-frame-title .dashicons {2617 line-height: 2;2618 }2619 2620 .media-frame-router,2621 .media-frame:not(.hide-menu) .media-menu {2622 top: 40px;2623 padding-top: 0;2624 }2625 2626 .media-frame-content {2627 top: 74px;2628 2627 } 2629 2628 -
trunk/src/wp-includes/media-template.php
r46239 r46363 179 179 <script type="text/html" id="tmpl-media-frame"> 180 180 <div class="media-frame-title" id="media-frame-title"></div> 181 <button type="button" class="button button-link media-frame-menu-toggle" aria-expanded="false"> 182 <?php _e( 'Media Types' ); ?> 183 <span class="dashicons dashicons-arrow-down" aria-hidden="true"></span> 184 </button> 181 185 <div class="media-frame-menu"></div> 182 <div class="media-frame-router"></div> 183 <div class="media-frame-content"></div> 186 <div class="media-frame-tab-panel"> 187 <div class="media-frame-router"></div> 188 <div class="media-frame-content"></div> 189 </div> 184 190 <div class="media-frame-toolbar"></div> 185 191 <div class="media-frame-uploader"></div>
Note: See TracChangeset
for help on using the changeset viewer.