WordPress.org

Make WordPress Core

Ticket #47149: 47149.3.diff

File 47149.3.diff, 13.0 KB (added by afercia, 5 weeks ago)
  • src/js/media/views/focus-manager.js

     
     1var $ = jQuery;
     2
    13/**
    24 * wp.media.view.FocusManager
    35 *
     
    1113var FocusManager = wp.media.View.extend(/** @lends wp.media.view.FocusManager.prototype */{
    1214
    1315        events: {
    14                 'keydown': 'constrainTabbing'
     16                'keydown': 'focusManagementMode'
    1517        },
    1618
    1719        /**
     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        /**
    1853         * Gets all the tabbable elements.
    1954         *
    2055         * @since 5.3.0
     
    164199         *
    165200         * @since 5.3.0
    166201         */
    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        }
    168354});
    169355
    170356module.exports = FocusManager;
  • src/js/media/views/frame/post.js

     
    257257        mainMenu: function( view ) {
    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                });
    264267        },
  • src/js/media/views/media-frame.js

     
    8080
    8181                // Bind default menu.
    8282                this.on( 'menu:create:default', this.createMenu, this );
     83
     84                this.on( 'content:render', this.updateContentTabPanelAriaLabelledBy, this );
    8385        },
     86
    8487        /**
     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        /**
    85117         * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
    86118         */
    87119        render: function() {
     
    110142         */
    111143        createMenu: function( menu ) {
    112144                menu.view = new wp.media.view.Menu({
    113                         controller: this
     145                        controller: this,
     146
     147                        attributes: {
     148                                role:               'tablist',
     149                                'aria-orientation': 'vertical'
     150                        }
    114151                });
    115152        },
    116153
  • src/js/media/views/menu-item.js

     
    1111 * @augments Backbone.View
    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
    2122        events: {
    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;
    2931
    30                 if ( event ) {
    31                         event.preventDefault();
    32                 }
    33 
    3432                if ( clickOverride ) {
    3533                        clickOverride.call( this );
    3634                } else {
     
    4341
    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         */
     
    5858                        this.$el.html( options.html );
    5959                }
    6060
     61                this.$el.attr( 'id', 'menu-item-' + this.options.state );
     62
    6163                return this;
    6264        }
    6365});
  • src/js/media/views/menu.js

     
    2020        ItemView:  MenuItem,
    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                } );
    3043        },
    31         */
    3244
    3345        /**
    3446         * @param {Object} options
     
    4759                 */
    4860                PriorityList.prototype.ready.apply( this, arguments );
    4961                this.visibility();
     62
     63                // Set up aria tabs initial attributes.
     64                this.focusManager.setupAriaTabs();
    5065        },
    5166
    5267        set: function() {
     
    87102
    88103                this.deselect();
    89104                view.$el.addClass('active');
     105
     106                // Set up again the aria tabs initial attributes after the menu updates.
     107                this.focusManager.setupAriaTabs();
    90108        },
    91109
    92110        deselect: function() {
  • src/js/media/views/router.js

     
    2020        ItemView:  wp.media.view.RouterItem,
    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 );
    2530                // Call 'initialize' directly on the parent class.
  • src/wp-includes/css/media-views.css

     
    553553        user-select: none;
    554554}
    555555
    556 .media-menu > a {
     556.media-menu .media-menu-item {
    557557        display: block;
     558        box-sizing: border-box;
     559        width: 100%;
    558560        position: relative;
     561        border: 0;
     562        margin: 0;
    559563        padding: 8px 20px;
    560         margin: 0;
     564        font-size: 14px;
    561565        line-height: 1.28571428;
    562         font-size: 14px;
     566        background: transparent;
    563567        color: #0073aa;
     568        text-align: left;
    564569        text-decoration: none;
     570        cursor: pointer;
    565571}
    566572
    567 .media-menu > a:hover {
    568         color: #0073aa;
     573.media-menu .media-menu-item:hover {
    569574        background: rgba(0, 0, 0, 0.04);
    570575}
    571576
    572 .media-menu > a:active {
     577.media-menu .media-menu-item:active {
     578        color: #0073aa;
    573579        outline: none;
    574580}
    575581
     
    579585        font-weight: 600;
    580586}
    581587
     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
    582597.media-menu .separator {
    583598        height: 0;
    584599        margin: 12px 20px;
     
    594609        padding: 0 6px;
    595610        margin: 0;
    596611        clear: both;
    597         -webkit-user-select: none;
    598         -moz-user-select: none;
    599         -ms-user-select: none;
    600         user-select: none;
    601612}
    602613
    603 .media-router a {
    604         transition: none;
    605 }
    606 
    607 .media-router > a {
     614.media-router .media-menu-item {
    608615        position: relative;
    609616        float: left;
     617        border: 0;
     618        margin: 0;
    610619        padding: 8px 10px 9px;
    611         margin: 0;
    612620        height: 18px;
    613621        line-height: 1.28571428;
    614622        font-size: 14px;
    615623        text-decoration: none;
     624        background: transparent;
     625        cursor: pointer;
     626        transition: none;
    616627}
    617628
    618 .media-router > a:last-child {
     629.media-router .media-menu-item:last-child {
    619630        border-right: 0;
    620631}
    621632
    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;
    624636}
    625637
    626638.media-router .active,
    627639.media-router .active:hover {
    628         color: #32373c;
     640        color: #23282d;
    629641}
    630642
     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
    631652.media-router .active,
    632 .media-router > a.active:last-child {
     653.media-router .media-menu-item.active:last-child {
    633654        margin: -1px -1px 0;
    634655        background: #fff;
    635656        border: 1px solid #ddd;
  • src/wp-includes/media-template.php

     
    179179        <script type="text/html" id="tmpl-media-frame">
    180180                <div class="media-frame-title" id="media-frame-title"></div>
    181181                <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>
    184186                <div class="media-frame-toolbar"></div>
    185187                <div class="media-frame-uploader"></div>
    186188        </script>