Make WordPress Core

Changeset 45866


Ignore:
Timestamp:
08/20/2019 10:41:02 PM (7 years ago)
Author:
SergeyBiryukov
Message:

Accessibility: Make the Media modal an ARIA modal dialog.

For a number of years, the Media modal missed an explicit ARIA role and the required attributes for modal dialogs.

This was confusing for assistive technology users, since they may not realize they're inside a dialog, and that consequently the keyboard interactions may be different from the rest of the page. Lack of an explicit label for the dialog was confusing as well, since assistive technology users didn't have an immediate sense of what the dialog is for.

This change makes the Media modal meet the ARIA Authoring Practices recommendations, helping users better understand the purpose and interactions with the modal. Also, it makes sure to hide the rest of the page content from assistive technologies, until support for aria-modal="true" improves.

Additionally:

  • moves the modal H1 heading to the beginning of the modal content
  • changes the modal left menu position to make visual and DOM order match
  • improves the wp.media.view.FocusManager documentation

Props afercia.
Merges [45572] to the 5.2 branch.
Fixes #47145.

Location:
branches/5.2
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • branches/5.2

  • branches/5.2/src/js/media/views/focus-manager.js

    r45840 r45866  
    1717        /**
    1818         * Moves focus to the first visible menu item in the modal.
     19         *
     20         * @since 3.5.0
     21         *
     22         * @returns {void}
    1923         */
    2024        focus: function() {
     
    2226        },
    2327        /**
    24          * @param {Object} event
     28         * Constrains navigation with the Tab key within the media view element.
     29         *
     30         * @since 4.0.0
     31         *
     32         * @param {Object} event A keydown jQuery event.
     33         *
     34         * @returns {void}
    2535         */
    2636        constrainTabbing: function( event ) {
     
    4353                        return false;
    4454                }
    45         }
     55        },
    4656
     57        /**
     58         * Hides from assistive technologies all the body children except the
     59         * provided element and other elements that should not be hidden.
     60         *
     61         * The reason why we use `aria-hidden` is that `aria-modal="true"` is buggy
     62         * in Safari 11.1 and support is spotty in other browsers. In the future we
     63         * should consider to remove this helper function and only use `aria-modal="true"`.
     64         *
     65         * @since 5.3.0
     66         *
     67         * @param {object} visibleElement The jQuery object representing the element that should not be hidden.
     68         *
     69         * @returns {void}
     70         */
     71        setAriaHiddenOnBodyChildren: function( visibleElement ) {
     72                var bodyChildren,
     73                        self = this;
     74
     75                if ( this.isBodyAriaHidden ) {
     76                        return;
     77                }
     78
     79                // Get all the body children.
     80                bodyChildren = document.body.children;
     81
     82                // Loop through the body children and hide the ones that should be hidden.
     83                _.each( bodyChildren, function( element ) {
     84                        // Don't hide the modal element.
     85                        if ( element === visibleElement[0] ) {
     86                                return;
     87                        }
     88
     89                        // Determine the body children to hide.
     90                        if ( self.elementShouldBeHidden( element ) ) {
     91                                element.setAttribute( 'aria-hidden', 'true' );
     92                                // Store the hidden elements.
     93                                self.ariaHiddenElements.push( element );
     94                        }
     95                } );
     96
     97                this.isBodyAriaHidden = true;
     98        },
     99
     100        /**
     101         * Makes visible again to assistive technologies all body children
     102         * previously hidden and stored in this.ariaHiddenElements.
     103         *
     104         * @since 5.3.0
     105         *
     106         * @returns {void}
     107         */
     108        removeAriaHiddenFromBodyChildren: function() {
     109                _.each( this.ariaHiddenElements, function( element ) {
     110                        element.removeAttribute( 'aria-hidden' );
     111                } );
     112
     113                this.ariaHiddenElements = [];
     114                this.isBodyAriaHidden   = false;
     115        },
     116
     117        /**
     118         * Determines if the passed element should not be hidden from assistive technologies.
     119         *
     120         * @since 5.3.0
     121         *
     122         * @param {object} element The DOM element that should be checked.
     123         *
     124         * @returns {boolean} Whether the element should not be hidden from assistive technologies.
     125         */
     126        elementShouldBeHidden: function( element ) {
     127                var role = element.getAttribute( 'role' ),
     128                        liveRegionsRoles = [ 'alert', 'status', 'log', 'marquee', 'timer' ];
     129
     130                /*
     131                 * Don't hide scripts, elements that already have `aria-hidden`, and
     132                 * ARIA live regions.
     133                 */
     134                return ! (
     135                        element.tagName === 'SCRIPT' ||
     136                        element.hasAttribute( 'aria-hidden' ) ||
     137                        element.hasAttribute( 'aria-live' ) ||
     138                        liveRegionsRoles.indexOf( role ) !== -1
     139                );
     140        },
     141
     142        /**
     143         * Whether the body children are hidden from assistive technologies.
     144         *
     145         * @since 5.3.0
     146         */
     147        isBodyAriaHidden: false,
     148
     149        /**
     150         * Stores an array of DOM elements that should be hidden from assistive
     151         * technologies, for example when the media modal dialog opens.
     152         *
     153         * @since 5.3.0
     154         */
     155        ariaHiddenElements: []
    47156});
    48157
  • branches/5.2/src/js/media/views/modal.js

    r45849 r45866  
    118118                this.$( '.media-modal' ).focus();
    119119
     120                // Hide the page content from assistive technologies.
     121                this.focusManager.setAriaHiddenOnBodyChildren( $el );
     122
    120123                return this.propagate('open');
    121124        },
     
    135138                // Hide modal and remove restricted media modal tab focus once it's closed
    136139                this.$el.hide().undelegate( 'keydown' );
     140
     141                /*
     142                 * Make visible again to assistive technologies all body children that
     143                 * have been made hidden when the modal opened.
     144                 */
     145                this.focusManager.removeAriaHiddenFromBodyChildren();
    137146
    138147                // Put focus back in useful location once modal is closed.
  • branches/5.2/src/wp-includes/css/media-views.css

    r45840 r45866  
    540540        bottom: 0;
    541541        margin: 0;
    542         padding: 10px 0;
     542        padding: 50px 0 10px;
    543543        background: #f3f3f3;
    544544        border-right-width: 1px;
     
    25622562/* Landscape specific header override */
    25632563@media screen and (max-height: 400px) {
    2564         .media-menu {
    2565                 padding: 0;
     2564        .media-menu,
     2565        .media-frame:not(.hide-menu) .media-menu {
     2566                top: 44px;
    25662567        }
    25672568
     
    25812582        .embed-link-settings {
    25822583                overflow: visible;
     2584        }
     2585}
     2586
     2587@media only screen and (min-width: 901px) and (max-height: 400px) {
     2588        .media-menu,
     2589        .media-frame:not(.hide-menu) .media-menu {
     2590                top: 0;
     2591                padding-top: 44px;
    25832592        }
    25842593}
     
    26102619        .media-frame:not(.hide-menu) .media-menu {
    26112620                top: 40px;
     2621                padding-top: 0;
    26122622        }
    26132623
  • branches/5.2/src/wp-includes/media-template.php

    r45865 r45866  
    186186        <![endif]-->
    187187        <script type="text/html" id="tmpl-media-frame">
     188                <div class="media-frame-title" id="media-frame-title"></div>
    188189                <div class="media-frame-menu"></div>
    189                 <div class="media-frame-title"></div>
    190190                <div class="media-frame-router"></div>
    191191                <div class="media-frame-content"></div>
     
    195195
    196196        <script type="text/html" id="tmpl-media-modal">
    197                 <div tabindex="0" class="<?php echo $class; ?>">
     197                <div tabindex="0" class="<?php echo $class; ?>" role="dialog" aria-modal="true" aria-labelledby="media-frame-title">
    198198                        <# if ( data.hasCloseButton ) { #>
    199199                                <button type="button" class="media-modal-close"><span class="media-modal-icon"><span class="screen-reader-text"><?php _e( 'Close dialog' ); ?></span></span></button>
    200200                        <# } #>
    201                         <div class="media-modal-content"></div>
     201                        <div class="media-modal-content" role="document"></div>
    202202                </div>
    203203                <div class="media-modal-backdrop"></div>
Note: See TracChangeset for help on using the changeset viewer.