WordPress.org

Make WordPress Core

Changeset 48373


Ignore:
Timestamp:
07/07/2020 12:58:10 PM (5 months 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.