Make WordPress Core

Changeset 48373


Ignore:
Timestamp:
07/07/2020 12:58:10 PM (6 years ago)
Author:
afercia
Message:

Accessibility: Allow post boxes on the Dashboard and Classic Editor pages to be reordered by using the keyboard.

So far, it has been possible to rearrange into a new order the post boxes (also known as "widgets" on the Dashboard and "meta boxes" on the Edit post page) only by using a pointing device, for example a mouse.

This change adds new controls and functionality to allow the boxes to be rearranged also with the keyboard. Additionally, audible messages are sent to the admin ARIA live region to notify screen reader users of the reorder action result.

Props joedolson, anevins, antpb, audrasjb, xkon, MarcoZ, karmatosed, afercia.
Fixes #39074.

Location:
trunk/src
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/js/_enqueues/admin/postbox.js

    r48340 r48373  
    4343                handle_click : function () {
    4444                        var $el = $( this ),
    45                                 p = $el.parent( '.postbox' ),
     45                                p = $el.closest( '.postbox' ),
    4646                                id = p.attr( 'id' ),
    4747                                ariaExpandedValue;
    … …  
    5252
    5353                        p.toggleClass( 'closed' );
    54 
    5554                        ariaExpandedValue = ! p.hasClass( 'closed' );
    5655
    … …  
    9190
    9291                /**
     92                 * Handles clicks on the move up/down buttons.
     93                 *
     94                 * @since 5.5.0
     95                 *
     96                 * @return {void}
     97                 */
     98                handleOrder: function() {
     99                        var button = $( this ),
     100                                postbox = button.closest( '.postbox' ),
     101                                postboxId = postbox.attr( 'id' ),
     102                                postboxesWithinSortables = postbox.closest( '.meta-box-sortables' ).find( '.postbox:visible' ),
     103                                postboxesWithinSortablesCount = postboxesWithinSortables.length,
     104                                postboxWithinSortablesIndex = postboxesWithinSortables.index( postbox ),
     105                                firstOrLastPositionMessage;
     106
     107                        if ( 'dashboard_browser_nag' === postboxId ) {
     108                                return;
     109                        }
     110
     111                        // If on the first or last position, do nothing and send an audible message to screen reader users.
     112                        if ( 'true' === button.attr( 'aria-disabled' ) ) {
     113                                firstOrLastPositionMessage = button.hasClass( 'handle-order-higher' ) ?
     114                                        __( 'The box is on the first position' ) :
     115                                        __( 'The box is on the last position' );
     116
     117                                wp.a11y.speak( firstOrLastPositionMessage );
     118                                return;
     119                        }
     120
     121                        // Move a postbox up.
     122                        if ( button.hasClass( 'handle-order-higher' ) ) {
     123                                // If the box is first within a sortable area, move it to the previous sortable area.
     124                                if ( 0 === postboxWithinSortablesIndex ) {
     125                                        postboxes.handleOrderBetweenSortables( 'previous', button, postbox );
     126                                        return;
     127                                }
     128
     129                                postbox.prevAll( '.postbox:visible' ).eq( 0 ).before( postbox );
     130                                button.focus();
     131                                postboxes.updateOrderButtonsProperties();
     132                                postboxes.save_order( postboxes.page );
     133                        }
     134
     135                        // Move a postbox down.
     136                        if ( button.hasClass( 'handle-order-lower' ) ) {
     137                                // If the box is last within a sortable area, move it to the next sortable area.
     138                                if ( postboxWithinSortablesIndex + 1 === postboxesWithinSortablesCount ) {
     139                                        postboxes.handleOrderBetweenSortables( 'next', button, postbox );
     140                                        return;
     141                                }
     142
     143                                postbox.nextAll( '.postbox:visible' ).eq( 0 ).after( postbox );
     144                                button.focus();
     145                                postboxes.updateOrderButtonsProperties();
     146                                postboxes.save_order( postboxes.page );
     147                        }
     148
     149                },
     150
     151                /**
     152                 * Moves postboxes between the sortables areas.
     153                 *
     154                 * @since 5.5.0
     155                 *
     156                 * @param {string} position The "previous" or "next" sortables area.
     157                 * @param {object} button   The jQuery object representing the button that was clicked.
     158                 * @param {object} postbox  The jQuery object representing the postbox to be moved.
     159                 *
     160                 * @return {void}
     161                 */
     162                handleOrderBetweenSortables: function( position, button, postbox ) {
     163                        var closestSortablesId = button.closest( '.meta-box-sortables' ).attr( 'id' ),
     164                                sortablesIds = [],
     165                                sortablesIndex,
     166                                detachedPostbox;
     167
     168                        // Get the list of sortables within the page.
     169                        $( '.meta-box-sortables:visible' ).each( function() {
     170                                sortablesIds.push( $( this ).attr( 'id' ) );
     171                        });
     172
     173                        // Return if there's only one visible sortables area, e.g. in the block editor page.
     174                        if ( 1 === sortablesIds.length ) {
     175                                return;
     176                        }
     177
     178                        // Find the index of the current sortables area within all the sortable areas.
     179                        sortablesIndex = $.inArray( closestSortablesId, sortablesIds );
     180                        // Detach the postbox to be moved.
     181                        detachedPostbox = postbox.detach();
     182
     183                        // Move the detached postbox to its new position.
     184                        if ( 'previous' === position ) {
     185                                $( detachedPostbox ).appendTo( '#' + sortablesIds[ sortablesIndex - 1 ] );
     186                        }
     187
     188                        if ( 'next' === position ) {
     189                                $( detachedPostbox ).prependTo( '#' + sortablesIds[ sortablesIndex + 1 ] );
     190                        }
     191
     192                        postboxes._mark_area();
     193                        button.focus();
     194                        postboxes.updateOrderButtonsProperties();
     195                        postboxes.save_order( postboxes.page );
     196                },
     197
     198                /**
     199                 * Update the move buttons properties depending on the postbox position.
     200                 *
     201                 * @since 5.5.0
     202                 *
     203                 * @return {void}
     204                 */
     205                updateOrderButtonsProperties: function() {
     206                        var firstSortablesId = $( '.meta-box-sortables:first' ).attr( 'id' ),
     207                                lastSortablesId = $( '.meta-box-sortables:last' ).attr( 'id' ),
     208                                firstPostbox = $( '.postbox:visible:first' ),
     209                                lastPostbox = $( '.postbox:visible:last' ),
     210                                firstPostboxSortablesId = firstPostbox.closest( '.meta-box-sortables' ).attr( 'id' ),
     211                                lastPostboxSortablesId = lastPostbox.closest( '.meta-box-sortables' ).attr( 'id' );
     212
     213                        // Enable all buttons as a reset first.
     214                        $( '.handle-order-higher' ).attr( 'aria-disabled', 'false' );
     215                        $( '.handle-order-lower' ).attr( 'aria-disabled', 'false' );
     216
     217                        // Set an aria-disabled=true attribute on the first visible "move" buttons.
     218                        if ( firstSortablesId === firstPostboxSortablesId ) {
     219                                $( firstPostbox ).find( '.handle-order-higher' ).attr( 'aria-disabled', 'true' );
     220                        }
     221
     222                        // Set an aria-disabled=true attribute on the last visible "move" buttons.
     223                        if ( lastSortablesId === lastPostboxSortablesId ) {
     224                                $( '.postbox:visible .handle-order-lower' ).last().attr( 'aria-disabled', 'true' );
     225                        }
     226                },
     227
     228                /**
    93229                 * Adds event handlers to all postboxes and screen option on the current page.
    94230                 *
    … …  
    104240                 */
    105241                add_postbox_toggles : function (page, args) {
    106                         var $handles = $( '.postbox .hndle, .postbox .handlediv' );
     242                        var $handles = $( '.postbox .hndle, .postbox .handlediv' ),
     243                                $orderButtons = $( '.postbox .handle-order-higher, .postbox .handle-order-lower' );
    107244
    108245                        this.page = page;
    … …  
    110247
    111248                        $handles.on( 'click.postboxes', this.handle_click );
     249
     250                        // Handle the order of the postboxes.
     251                        $orderButtons.on( 'click.postboxes', this.handleOrder );
    112252
    113253                        /**
    … …  
    123263                         * Event handler for the postbox dismiss button. After clicking the button
    124264                         * the postbox will be hidden.
     265                         *
     266                         * As of WordPress 5.5, this is only used for the browser update nag.
    125267                         *
    126268                         * @since 3.2.0
    … …  
    249391                                                return;
    250392                                        }
     393
     394                                        postboxes.updateOrderButtonsProperties();
    251395                                        postboxes.save_order(page);
    252396                                },
    … …  
    267411                        this._mark_area();
    268412
     413                        // Update the "move" buttons properties.
     414                        this.updateOrderButtonsProperties();
     415                        $document.on( 'postbox-toggled', this.updateOrderButtonsProperties );
     416
    269417                        // Set the handle buttons `aria-expanded` attribute initial value on page load.
    270418                        $handleButtons.each( function () {
    271419                                var $el = $( this );
    272                                 $el.attr( 'aria-expanded', ! $el.parent( '.postbox' ).hasClass( 'closed' ) );
     420                                $el.attr( 'aria-expanded', ! $el.closest( '.postbox' ).hasClass( 'closed' ) );
    273421                        });
    274422                },
    … …  
    333481                        } );
    334482
    335                         $.post( ajaxurl, postVars );
     483                        $.post(
     484                                ajaxurl,
     485                                postVars,
     486                                function( response ) {
     487                                        if ( response.success ) {
     488                                                wp.a11y.speak( __( 'The boxes order has been saved.' ) );
     489                                        }
     490                                }
     491                        );
    336492                },
    337493
  • trunk/src/wp-admin/css/common.css

    r48368 r48373  
    728728}
    729729
    730 .postbox .hndle,
    731730.stuffbox .hndle {
    732731        border-bottom: 1px solid #ccd0d4;
    … …  
    19841983}
    19851984
     1985/* Configurable dashboard widgets "Configure" edit-box link. */
    19861986.hndle a {
    1987         font-size: 11px;
     1987        font-size: 12px;
    19881988        font-weight: 400;
    19891989}
    19901990
     1991.postbox-header {
     1992        display: flex;
     1993        align-items: center;
     1994        justify-content: space-between;
     1995        border-bottom: 1px solid #ccd0d4;
     1996}
     1997
     1998.postbox-header .hndle {
     1999        flex-grow: 1;
     2000        /* Handle the alignment for the configurable dashboard widgets "Configure" edit-box link. */
     2001        display: flex;
     2002        justify-content: space-between;
     2003        align-items: center;
     2004}
     2005
     2006.postbox-header .handle-actions {
     2007        flex-shrink: 0;
     2008}
     2009
     2010.postbox .handle-order-higher,
     2011.postbox .handle-order-lower,
    19912012.postbox .handlediv {
    1992         display: none;
    1993         float: right;
    19942013        width: 36px;
    19952014        height: 36px;
    … …  
    20012020}
    20022021
    2003 .js .postbox .handlediv {
    2004         display: block;
     2022.postbox .handle-order-higher,
     2023.postbox .handle-order-lower {
     2024        color: #72777c;
     2025}
     2026
     2027.postbox .handle-order-higher[aria-disabled="true"],
     2028.postbox .handle-order-lower[aria-disabled="true"] {
     2029        cursor: default;
     2030        color: #a0a5aa;
    20052031}
    20062032
    … …  
    29502976
    29512977/* Metabox collapse arrow indicators */
    2952 .sidebar-name .toggle-indicator:before,
    2953 .js .meta-box-sortables .postbox .toggle-indicator:before,
    2954 .bulk-action-notice .toggle-indicator:before,
    2955 .privacy-text-box .toggle-indicator:before {
     2978.sidebar-name .toggle-indicator::before,
     2979.meta-box-sortables .postbox .toggle-indicator::before,
     2980.meta-box-sortables .postbox .order-higher-indicator::before,
     2981.meta-box-sortables .postbox .order-lower-indicator::before,
     2982.bulk-action-notice .toggle-indicator::before,
     2983.privacy-text-box .toggle-indicator::before {
    29562984        content: "\f142";
    29572985        display: inline-block;
    … …  
    29632991}
    29642992
    2965 .js .widgets-holder-wrap.closed .toggle-indicator:before,
    2966 .js .meta-box-sortables .postbox.closed .handlediv .toggle-indicator:before,
    2967 .bulk-action-notice .bulk-action-errors-collapsed .toggle-indicator:before,
    2968 .privacy-text-box.closed .toggle-indicator:before {
     2993.js .widgets-holder-wrap.closed .toggle-indicator::before,
     2994.meta-box-sortables .postbox.closed .handlediv .toggle-indicator::before,
     2995.bulk-action-notice .bulk-action-errors-collapsed .toggle-indicator::before,
     2996.privacy-text-box.closed .toggle-indicator::before {
    29692997        content: "\f140";
    29702998}
    29712999
    2972 .js .postbox .handlediv .toggle-indicator:before {
    2973         margin-top: 4px;
     3000.postbox .handle-order-higher .order-higher-indicator::before {
     3001        content: "\f343";
     3002        color: inherit;
     3003}
     3004
     3005.postbox .handle-order-lower .order-lower-indicator::before {
     3006        content: "\f347";
     3007        color: inherit;
     3008}
     3009
     3010.postbox .handle-order-higher .order-higher-indicator::before,
     3011.postbox .handle-order-lower .order-lower-indicator::before,
     3012.postbox .handlediv .toggle-indicator::before {
    29743013        width: 20px;
    29753014        border-radius: 50%;
    2976         text-indent: -1px; /* account for the dashicon alignment */
    2977 }
    2978 
    2979 .rtl.js .postbox .handlediv .toggle-indicator:before {
    2980         text-indent: 1px; /* account for the dashicon alignment */
    2981 }
    2982 
    2983 .bulk-action-notice .toggle-indicator:before {
     3015}
     3016
     3017.postbox .handlediv .toggle-indicator::before {
     3018        text-indent: -1px; /* account for the dashicon glyph uneven horizontal alignment */
     3019}
     3020
     3021.rtl .postbox .handlediv .toggle-indicator::before {
     3022        text-indent: 1px; /* account for the dashicon glyph uneven horizontal alignment */
     3023}
     3024
     3025.bulk-action-notice .toggle-indicator::before {
    29843026        line-height: 16px;
    29853027        vertical-align: top;
    … …  
    29873029}
    29883030
    2989 .js .postbox .handlediv:focus {
     3031.postbox .handle-order-higher:focus,
     3032.postbox .handle-order-lower:focus,
     3033.postbox .handlediv:focus {
    29903034        box-shadow: none;
    29913035        /* Only visible in Windows High Contrast mode */
    … …  
    29933037}
    29943038
    2995 .js .postbox .handlediv:focus .toggle-indicator:before {
     3039.postbox .handle-order-higher:focus .order-higher-indicator::before,
     3040.postbox .handle-order-lower:focus .order-lower-indicator::before,
     3041.postbox .handlediv:focus .toggle-indicator::before {
    29963042        box-shadow:
    29973043                0 0 0 1px #5b9dd9,
  • trunk/src/wp-admin/css/dashboard.css

    r48340 r48373  
    4949
    5050#dashboard-widgets .meta-box-sortables {
     51        display: flow-root; /* avoid margin collapsing between parent and first/last child elements */
    5152        /* Required min-height to make the jQuery UI Sortable drop zone work. */
    5253        min-height: 100px;
  • trunk/src/wp-admin/includes/ajax-actions.php

    r48200 r48373  
    19281928        }
    19291929
    1930         wp_die( 1 );
     1930        wp_send_json_success();
    19311931}
    19321932
  • trunk/src/wp-admin/includes/template.php

    r48199 r48373  
    13151315                                        $hidden_class = ( ! $screen->is_block_editor() && in_array( $box['id'], $hidden, true ) ) ? ' hide-if-js' : '';
    13161316                                        echo '<div id="' . $box['id'] . '" class="postbox ' . postbox_classes( $box['id'], $page ) . $hidden_class . '" ' . '>' . "\n";
     1317
     1318                                        echo '<div class="postbox-header">';
     1319                                        echo '<h2 class="hndle">';
     1320                                        if ( 'dashboard_php_nag' === $box['id'] ) {
     1321                                                echo '<span aria-hidden="true" class="dashicons dashicons-warning"></span>';
     1322                                                echo '<span class="screen-reader-text">' . __( 'Warning:' ) . ' </span>';
     1323                                        }
     1324                                        echo "{$box['title']}";
     1325                                        echo "</h2>\n";
     1326
    13171327                                        if ( 'dashboard_browser_nag' !== $box['id'] ) {
    13181328                                                $widget_title = $box['title'];
    … …  
    13231333                                                        unset( $box['args']['__widget_basename'] );
    13241334                                                }
     1335
     1336                                                echo '<div class="handle-actions hide-if-no-js">';
     1337
     1338                                                echo '<button type="button" class="handle-order-higher" aria-disabled="false" aria-describedby="' . $box['id'] . '-handle-order-higher-description">';
     1339                                                echo '<span class="screen-reader-text">' . __( 'Move up' ) . '</span>';
     1340                                                echo '<span class="order-higher-indicator" aria-hidden="true"></span>';
     1341                                                echo '</button>';
     1342                                                echo '<span class="hidden" id="' . $box['id'] . '-handle-order-higher-description">' . sprintf(
     1343                                                        /* translators: %s: Meta box title. */
     1344                                                        __( 'Move %s box up' ),
     1345                                                        $widget_title
     1346                                                ) . '</span>';
     1347
     1348                                                echo '<button type="button" class="handle-order-lower" aria-disabled="false" aria-describedby="' . $box['id'] . '-handle-order-lower-description">';
     1349                                                echo '<span class="screen-reader-text">' . __( 'Move down' ) . '</span>';
     1350                                                echo '<span class="order-lower-indicator" aria-hidden="true"></span>';
     1351                                                echo '</button>';
     1352                                                echo '<span class="hidden" id="' . $box['id'] . '-handle-order-lower-description">' . sprintf(
     1353                                                        /* translators: %s: Meta box title. */
     1354                                                        __( 'Move %s box down' ),
     1355                                                        $widget_title
     1356                                                ) . '</span>';
    13251357
    13261358                                                echo '<button type="button" class="handlediv" aria-expanded="true">';
    … …  
    13321364                                                echo '<span class="toggle-indicator" aria-hidden="true"></span>';
    13331365                                                echo '</button>';
     1366
     1367                                                echo '</div>';
    13341368                                        }
    1335                                         echo '<h2 class="hndle">';
    1336                                         if ( 'dashboard_php_nag' === $box['id'] ) {
    1337                                                 echo '<span aria-hidden="true" class="dashicons dashicons-warning"></span>';
    1338                                                 echo '<span class="screen-reader-text">' . __( 'Warning:' ) . ' </span>';
    1339                                         }
    1340                                         echo "<span>{$box['title']}</span>";
    1341                                         echo "</h2>\n";
     1369                                        echo '</div>';
     1370
    13421371                                        echo '<div class="inside">' . "\n";
    13431372
  • trunk/src/wp-includes/script-loader.php

    r48350 r48373  
    12091209                $scripts->add( 'xfn', "/wp-admin/js/xfn$suffix.js", array( 'jquery' ), false, 1 );
    12101210
    1211                 $scripts->add( 'postbox', "/wp-admin/js/postbox$suffix.js", array( 'jquery-ui-sortable' ), false, 1 );
     1211                $scripts->add( 'postbox', "/wp-admin/js/postbox$suffix.js", array( 'jquery-ui-sortable', 'wp-a11y' ), false, 1 );
    12121212                $scripts->set_translations( 'postbox' );
    12131213
Note: See TracChangeset for help on using the changeset viewer.