Make WordPress Core

Changeset 46363


Ignore:
Timestamp:
09/30/2019 07:36:42 PM (5 years ago)
Author:
afercia
Message:

Accessibility: Media: Use the ARIA tabs pattern for the media modal menus.

The ARIA tabs pattern improves interaction for keyboard and assistive technologies users.
It gives the menu items proper roles, and aria-selected allows users of assistive technologies to know which tab is currently selected.

Props audrasjb, afercia, joedolson, karmatosed, melchoyce.
See #47149.

Location:
trunk/src
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/js/media/views/focus-manager.js

    r46239 r46363  
     1var $ = jQuery;
     2
    13/**
    24 * wp.media.view.FocusManager
     
    1214
    1315    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        }
    1550    },
    1651
     
    68103
    69104    /**
    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.
    72109     *
    73110     * The reason why we use `aria-hidden` is that `aria-modal="true"` is buggy
     
    112149
    113150    /**
    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
    115154     * previously hidden and stored in this.ariaHiddenElements.
    116155     *
     
    166205     * @since 5.2.3
    167206     */
    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    }
    169359});
    170360
  • trunk/src/js/media/views/frame/post.js

    r45531 r46363  
    258258        view.set({
    259259            'library-separator': new wp.media.View({
    260                 className: 'separator',
    261                 priority: 100
     260                className:  'separator',
     261                priority:   100,
     262                attributes: {
     263                    role: 'presentation'
     264                }
    262265            })
    263266        });
  • trunk/src/js/media/views/media-frame.js

    r45925 r46363  
    2424
    2525    events: {
    26         'click div.media-frame-title h1': 'toggleMenu'
     26        'click .media-frame-menu-toggle': 'toggleMenu'
    2727    },
    2828
     
    7676        this.title.mode('default');
    7777
    78         this.on( 'title:render', function( view ) {
    79             view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' );
    80         });
    81 
    8278        // Bind default menu.
    8379        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
    85150    /**
    86151     * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
     
    112177    createMenu: function( menu ) {
    113178        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' ) );
    120195    },
    121196
     
    135210    createRouter: function( router ) {
    136211        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;
    139221    },
    140222    /**
  • trunk/src/js/media/views/menu-item.js

    r45524 r46363  
    1212 */
    1313MenuItem = wp.media.View.extend(/** @lends wp.media.view.MenuItem.prototype */{
    14     tagName:   'a',
     14    tagName:   'button',
    1515    className: 'media-menu-item',
    1616
    1717    attributes: {
    18         href: '#'
     18        type: 'button',
     19        role: 'tab'
    1920    },
    2021
     
    2223        'click': '_click'
    2324    },
     25
    2426    /**
    25      * @param {Object} event
     27     * Allows to override the click event.
    2628     */
    27     _click: function( event ) {
     29    _click: function() {
    2830        var clickOverride = this.options.click;
    29 
    30         if ( event ) {
    31             event.preventDefault();
    32         }
    3331
    3432        if ( clickOverride ) {
     
    4442        if ( state ) {
    4543            this.controller.setState( state );
     44            // Toggle the menu visibility in the responsive view.
    4645            this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below
    4746        }
    4847    },
     48
    4949    /**
    5050     * @returns {wp.media.view.MenuItem} returns itself to allow chaining
    5151     */
    5252    render: function() {
    53         var options = this.options;
     53        var options = this.options,
     54            menuProperty = options.state || options.contentMode;
    5455
    5556        if ( options.text ) {
     
    5960        }
    6061
     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
    6165        return this;
    6266    }
  • trunk/src/js/media/views/menu.js

    r43309 r46363  
    2121    region:    'menu',
    2222
    23     /* TODO: alternatively hide on any click anywhere
    24     events: {
    25         'click': 'click'
     23    attributes: {
     24        role:               'tablist',
     25        'aria-orientation': 'horizontal'
    2626    },
    2727
    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;
    3046    },
    31     */
    3247
    3348    /**
     
    4863        PriorityList.prototype.ready.apply( this, arguments );
    4964        this.visibility();
     65
     66        // Set up aria tabs initial attributes.
     67        this.focusManager.setupAriaTabs();
    5068    },
    5169
     
    7391
    7492        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.
    7596            this.controller.$el.toggleClass( 'hide-' + region, hide );
    7697        }
     
    88109        this.deselect();
    89110        view.$el.addClass('active');
     111
     112        // Set up again the aria tabs initial attributes after the menu updates.
     113        this.focusManager.setupAriaTabs();
    90114    },
    91115
  • trunk/src/js/media/views/router.js

    r43309 r46363  
    2121    region:    'router',
    2222
     23    attributes: {
     24        role:               'tablist',
     25        'aria-orientation': 'horizontal'
     26    },
     27
    2328    initialize: function() {
    2429        this.controller.on( 'content:render', this.update, this );
  • trunk/src/wp-includes/css/media-views.css

    r46362 r46363  
    581581}
    582582
    583 .media-menu > a {
    584     display: block;
     583.media-menu .media-menu-item {
     584    display: block;
     585    box-sizing: border-box;
     586    width: 100%;
    585587    position: relative;
     588    border: 0;
     589    margin: 0;
    586590    padding: 8px 20px;
    587     margin: 0;
     591    font-size: 14px;
    588592    line-height: 1.28571428;
    589     font-size: 14px;
     593    background: transparent;
    590594    color: #0073aa;
     595    text-align: left;
    591596    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 {
    595605    color: #0073aa;
    596     background: rgba(0, 0, 0, 0.04);
    597 }
    598 
    599 .media-menu > a:active {
    600606    outline: none;
    601607}
     
    605611    color: #23282d;
    606612    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;
    607622}
    608623
     
    622637    margin: 0;
    623638    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 {
    635642    position: relative;
    636643    float: left;
     644    border: 0;
     645    margin: 0;
    637646    padding: 8px 10px 9px;
    638     margin: 0;
    639647    height: 18px;
    640648    line-height: 1.28571428;
    641649    font-size: 14px;
    642650    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 {
    646657    border-right: 0;
    647658}
    648659
    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;
    651663}
    652664
    653665.media-router .active,
    654666.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;
    656677}
    657678
    658679.media-router .active,
    659 .media-router > a.active:last-child {
     680.media-router .media-menu-item.active:last-child {
    660681    margin: -1px -1px 0;
    661682    background: #fff;
     
    753774}
    754775
    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 
    764776.media-frame-title h1 {
    765777    padding: 0 16px;
     
    767779    line-height: 2.27272727;
    768780    margin: 0;
     781}
     782
     783.wp-core-ui .button.media-frame-menu-toggle {
     784    display: none;
    769785}
    770786
     
    22522268 */
    22532269@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    }
    22542283
    22552284    /* 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
    22572291    .media-frame:not(.hide-menu) .media-frame-router,
    22582292    .media-frame:not(.hide-menu) .media-frame-content,
     
    22612295    }
    22622296
     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
    22632311    .media-frame:not(.hide-menu) .media-frame-menu {
    22642312        position: static;
     
    22672315
    22682316    .media-frame:not(.hide-menu) .media-menu {
     2317        display: none;
    22692318        width: auto;
    22702319        max-width: 80%;
    22712320        overflow: auto;
    22722321        z-index: 2000;
    2273         top: 50px;
    2274         left: -300px;
     2322        top: 75px;
     2323        left: 0;
    22752324        right: auto;
    22762325        bottom: auto;
     
    22802329
    22812330    .media-frame:not(.hide-menu) .media-menu.visible {
    2282         left: 0;
     2331        display: block;
    22832332    }
    22842333
     
    22882337    }
    22892338
    2290     .media-frame:not(.hide-menu) .media-menu > a.active {
    2291         display: none;
    2292     }
    2293 
    22942339    .media-frame:not(.hide-menu) .media-menu .separator {
    22952340        margin: 5px 10px;
    22962341    }
    22972342
    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;
    23132365    }
    23142366    /* End drop-down menu */
     
    25622614}
    25632615
    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 
    25892616@media only screen and (min-width: 901px) and (max-height: 400px) {
    25902617    .media-menu,
     
    25962623
    25972624@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 
    26062625    .wp-core-ui.wp-customizer .media-button {
    26072626        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;
    26282627    }
    26292628
  • trunk/src/wp-includes/media-template.php

    r46239 r46363  
    179179    <script type="text/html" id="tmpl-media-frame">
    180180        <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>
    181185        <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>
    184190        <div class="media-frame-toolbar"></div>
    185191        <div class="media-frame-uploader"></div>
Note: See TracChangeset for help on using the changeset viewer.