WordPress.org

Make WordPress Core

Ticket #47149: 47149.diff

File 47149.diff, 10.6 KB (added by afercia, 5 months 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
     19        initialize: function( options ) {
     20                this.mode                    = options.mode || 'constrainTabbing';
     21                this.tabsAutomaticActivation = options.tabsAutomaticActivation || false;
     22        },
     23
    1724        /**
     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        /**
    1842         * Gets all the tabbable elements.
     43         *
     44         * @returns {object} jQuery collection of tabbable elements.
    1945         */
    2046        getTabbables: function() {
    2147                // Skip the file input added by Plupload.
     
    2450
    2551        /**
    2652         * Moves focus to the modal dialog.
     53         *
     54         * @returns {void}
    2755         */
    2856        focus: function() {
    2957                this.$( '.media-modal' ).focus();
     
    3058        },
    3159
    3260        /**
    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}
    3466         */
    3567        constrainTabbing: function( event ) {
    3668                var tabbables;
     
    5082                        tabbables.last().focus();
    5183                        return false;
    5284                }
     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();
    53241        }
    54 
    55242});
    56243
    57244module.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

     
    110110         */
    111111        createMenu: function( menu ) {
    112112                menu.view = new wp.media.view.Menu({
    113                         controller: this
     113                        controller: this,
     114
     115                        attributes: {
     116                                role:               'tablist',
     117                                'aria-orientation': 'vertical'
     118                        }
    114119                });
    115120        },
    116121
  • 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         */
  • 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.initializeAriaTabs();
    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.initializeAriaTabs();
    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;
    558558        position: relative;
    559559        padding: 8px 20px;
     
    564564        text-decoration: none;
    565565}
    566566
    567 .media-menu > a:hover {
     567.media-menu .media-menu-item:hover {
    568568        color: #0073aa;
    569569        background: rgba(0, 0, 0, 0.04);
    570570}
    571571
    572 .media-menu > a:active {
     572.media-menu .media-menu-item:active {
    573573        outline: none;
    574574}
    575575
     
    600600        user-select: none;
    601601}
    602602
    603 .media-router a {
     603.media-router .media-menu-item {
    604604        transition: none;
    605605}
    606606
    607 .media-router > a {
     607.media-router .media-menu-item {
    608608        position: relative;
    609609        float: left;
    610610        padding: 8px 10px 9px;
     
    615615        text-decoration: none;
    616616}
    617617
    618 .media-router > a:last-child {
     618.media-router .media-menu-item:last-child {
    619619        border-right: 0;
    620620}
    621621
    622 .media-router > a:active {
     622.media-router .media-menu-item:active {
    623623        outline: none;
    624624}
    625625
     
    629629}
    630630
    631631.media-router .active,
    632 .media-router > a.active:last-child {
     632.media-router .media-menu-item.active:last-child {
    633633        margin: -1px -1px 0;
    634634        background: #fff;
    635635        border: 1px solid #ddd;