Make WordPress Core

Changeset 59265


Ignore:
Timestamp:
10/21/2024 07:53:10 PM (13 months ago)
Author:
joedolson
Message:

Menus: A11y: Set the parent and order using select fields.

Add select inputs to allow users to set the parent and position of items in the menu settings. Fixes a significant problem for screen reader users that makes updating menus extremely tedious, since the options for moving items do not explicitly set a position. This is also a significant improvement for all users manipulating large menus.

This could easily be considered an enhancement, but while it is a minor enhancement for most users, it is transformative for screen reader users in managing menus, moving that interface from nearly unusable to very manageable.

Props javad2000, audrasjb, juliemoynat, williamalexander, rcreators, milamj, joedolson.
Fixes #43305.

Location:
trunk/src
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/js/_enqueues/lib/nav-menu.js

    r58306 r59265  
    217217                            t.find( '.button-controls .select-all' ).prop( 'checked', false );
    218218                            t.find( '.button-controls .spinner' ).removeClass( 'is-active' );
     219                            t.updateParentDropdown();
     220                            t.updateOrderDropdown();
    219221                        });
    220222                    });
     
    289291                    });
    290292                    return this;
     293                },
     294                updateParentDropdown : function() {
     295                    return this.each(function(){
     296                        var menuItems = $( '#menu-to-edit li' ),
     297                            parentDropdowns = $( '.edit-menu-item-parent' );
     298
     299                        $.each( parentDropdowns, function() {
     300                            var parentDropdown = $( this ),
     301                                $html = '',
     302                                $selected = '',
     303                                currentItemID = parentDropdown.closest( 'li.menu-item' ).find( '.menu-item-data-db-id' ).val(),
     304                                currentparentID = parentDropdown.closest( 'li.menu-item' ).find( '.menu-item-data-parent-id' ).val(),
     305                                currentItem = parentDropdown.closest( 'li.menu-item' ),
     306                                currentMenuItemChild = currentItem.childMenuItems(),
     307                                excludeMenuItem = [ currentItemID ];
     308
     309                            if ( currentMenuItemChild.length > 0 ) {
     310                                $.each( currentMenuItemChild, function(){
     311                                    var childItem = $(this),
     312                                        childID = childItem.find( '.menu-item-data-db-id' ).val();
     313
     314                                    excludeMenuItem.push( childID );
     315                                });
     316                            }
     317
     318                            if ( currentparentID == 0 ) {
     319                                $selected = 'selected';
     320                            }
     321
     322                            $html += '<option ' + $selected + ' value="0">No Parent</option>';
     323
     324                            $.each( menuItems, function() {
     325                                var menuItem = $(this),
     326                                $selected = '',
     327                                menuID = menuItem.find( '.menu-item-data-db-id' ).val(),
     328                                menuTitle = menuItem.find( '.edit-menu-item-title' ).val();
     329
     330                                if ( ! excludeMenuItem.includes( menuID ) ) {
     331                                    if ( currentparentID == menuID ) {
     332                                        $selected = 'selected';
     333                                    }
     334                                    $html += '<option ' + $selected + ' value="' + menuID + '">' + menuTitle + '</option>';
     335                                }
     336                            });
     337
     338                            parentDropdown.html( $html );
     339                        });
     340                       
     341                    });
     342                },
     343                updateOrderDropdown : function() {
     344                    return this.each( function() {
     345                        var itemPosition,
     346                            orderDropdowns = $( '.edit-menu-item-order' );
     347
     348                        $.each( orderDropdowns, function() {
     349                            var orderDropdown = $( this ),
     350                                menuItem = orderDropdown.closest( 'li.menu-item' ).first(),
     351                                depth = menuItem.menuItemDepth(),
     352                                isPrimaryMenuItem = ( 0 === depth ),
     353                                $html = '',
     354                                $selected = '';
     355
     356                            if ( isPrimaryMenuItem ) {
     357                                var primaryItems = $( '.menu-item-depth-0' ),
     358                                    totalMenuItems = primaryItems.length;
     359
     360                                itemPosition = primaryItems.index( menuItem ) + 1;
     361
     362                                for ( let i = 1; i < totalMenuItems + 1; i++ ) {
     363                                    $selected = '';
     364                                    if ( i == itemPosition ) {
     365                                        $selected = 'selected';
     366                                    }
     367                                    $html += '<option ' + $selected + ' value="' + i + '">' + i + ' of ' + totalMenuItems + '</option>';
     368                                }
     369
     370                            } else {
     371                                var parentItem = menuItem.prevAll( '.menu-item-depth-' + parseInt( depth - 1, 10 ) ).first(),
     372                                    parentItemId = parentItem.find( '.menu-item-data-db-id' ).val(),
     373                                    subItems = $( '.menu-item .menu-item-data-parent-id[value="' + parentItemId + '"]' ),
     374                                    totalSubMenuItems = subItems.length;
     375
     376                                itemPosition = $( subItems.parents('.menu-item').get().reverse() ).index( menuItem ) + 1;
     377
     378                                for ( let i = 1; i < totalSubMenuItems + 1; i++ ) {
     379                                    $selected = '';
     380                                    if ( i == itemPosition ) {
     381                                        $selected = 'selected';
     382                                    }
     383                                    $html += '<option ' + $selected + ' value="' + i + '">' + i + ' of ' + totalSubMenuItems + '</option>';
     384                                }
     385
     386                            }
     387
     388                            orderDropdown.html( $html );
     389                        });
     390                       
     391                    });
    291392                }
    292393            });
     
    298399
    299400        moveMenuItem : function( $this, dir ) {
    300 
    301401            var items, newItemPosition, newDepth,
    302402                menuItems = $( '#menu-to-edit li' ),
     
    401501            api.refreshKeyboardAccessibility();
    402502            api.refreshAdvancedAccessibility();
     503            thisItem.updateParentDropdown();
     504            thisItem.updateOrderDropdown();
    403505
    404506            if ( a11ySpeech ) {
     
    432534                }
    433535            });
     536
     537            // Set menu parents data for all menu items.
     538            menu.updateParentDropdown();
     539
     540            // Set menu order data for all menu items.
     541            menu.updateOrderDropdown();
     542
     543            // Update menu item parent when value is changed.
     544            menu.on( 'change', '.edit-menu-item-parent', function() {
     545                api.changeMenuParent( $( this ) );
     546            });
     547           
     548            // Update menu item order when value is changed.
     549            menu.on( 'change', '.edit-menu-item-order', function() {
     550                api.changeMenuOrder( $( this ) );
     551            });
     552        },
     553
     554        /**
     555         * changeMenuParent( [parentDropdown] )
     556         *
     557         * @since 6.7.0
     558         *
     559         * @param {object} parentDropdown select field
     560         */
     561        changeMenuParent : function( parentDropdown ) {
     562            var menuItemNewPosition,
     563                menuItems = $( '#menu-to-edit li' ),
     564                $this = $( parentDropdown ),
     565                newParentID = $this.val(),
     566                menuItem = $this.closest( 'li.menu-item' ).first(),
     567                menuItemOldDepth = menuItem.menuItemDepth(),
     568                menuItemChildren = menuItem.childMenuItems(),
     569                menuItemNoChildren = parseInt( menuItem.childMenuItems().length, 10 ),
     570                parentItem = $( '#menu-item-' + newParentID ),
     571                parentItemDepth = parentItem.menuItemDepth(),
     572                menuItemNewDepth = parseInt( parentItemDepth ) + 1;
     573
     574            if ( newParentID == 0 ) {
     575                menuItemNewDepth = 0;
     576            }
     577
     578            menuItem.find( '.menu-item-data-parent-id' ).val( newParentID );
     579            menuItem.moveHorizontally( menuItemNewDepth, menuItemOldDepth );
     580
     581            if ( menuItemNoChildren > 0 ) {
     582                menuItem = menuItem.add( menuItemChildren );
     583            }
     584            menuItem.detach();
     585
     586            menuItems = $( '#menu-to-edit li' );
     587
     588            var parentItemPosition = parseInt( parentItem.index(), 10 ),
     589                parentItemNoChild = parseInt( parentItem.childMenuItems().length, 10 );
     590
     591            if ( parentItemNoChild > 0 ){
     592                menuItemNewPosition = parentItemPosition + parentItemNoChild;
     593            } else {
     594                menuItemNewPosition = parentItemPosition;
     595            }
     596
     597            if ( newParentID == 0 ) {
     598                menuItemNewPosition = menuItems.length - 1;
     599            }
     600
     601            menuItem.insertAfter( menuItems.eq( menuItemNewPosition ) ).updateParentMenuItemDBId().updateParentDropdown().updateOrderDropdown();
     602
     603            api.registerChange();
     604            api.refreshKeyboardAccessibility();
     605            api.refreshAdvancedAccessibility();
     606            $this.trigger( 'focus' );
     607            wp.a11y.speak( menus.parentUpdated, 'polite' );
     608        },
     609
     610        /**
     611         * changeMenuOrder( [OrderDropdown] )
     612         *
     613         * @since 6.7.0
     614         *
     615         * @param {object} orderDropdown select field
     616         */
     617        changeMenuOrder : function( orderDropdown ) {
     618            var menuItems = $( '#menu-to-edit li' ),
     619                $this = $( orderDropdown ),
     620                newOrderID = parseInt( $this.val(), 10),
     621                menuItem = $this.closest( 'li.menu-item' ).first(),
     622                menuItemChildren = menuItem.childMenuItems(),
     623                menuItemNoChildren = menuItemChildren.length,
     624                menuItemCurrentPosition = parseInt( menuItem.index(), 10 ),
     625                parentItemID = menuItem.find( '.menu-item-data-parent-id' ).val(),
     626                subItems = $( '.menu-item .menu-item-data-parent-id[value="' + parentItemID + '"]' ),
     627                currentItemAtPosition = $(subItems[newOrderID - 1]).closest( 'li.menu-item' );
     628
     629            if ( menuItemNoChildren > 0 ) {
     630                menuItem = menuItem.add( menuItemChildren );
     631            }
     632
     633            var currentItemNoChildren = currentItemAtPosition.childMenuItems().length,
     634                currentItemPosition = parseInt( currentItemAtPosition.index(), 10 );
     635
     636            menuItems = $( '#menu-to-edit li' );
     637
     638            var menuItemNewPosition = currentItemPosition;
     639
     640            if(menuItemCurrentPosition > menuItemNewPosition){
     641                menuItemNewPosition = currentItemPosition;
     642                menuItem.detach().insertBefore( menuItems.eq( menuItemNewPosition ) ).updateOrderDropdown();
     643            } else {
     644                menuItemNewPosition = menuItemNewPosition + currentItemNoChildren;
     645                menuItem.detach().insertAfter( menuItems.eq( menuItemNewPosition ) ).updateOrderDropdown();
     646            }
     647
     648            api.registerChange();
     649            api.refreshKeyboardAccessibility();
     650            api.refreshAdvancedAccessibility();
     651            $this.trigger( 'focus' );
     652            wp.a11y.speak( menus.orderUpdated, 'polite' );
    434653        },
    435654
     
    738957                    api.refreshKeyboardAccessibility();
    739958                    api.refreshAdvancedAccessibility();
     959                    ui.item.updateParentDropdown();
     960                    ui.item.updateOrderDropdown();
    740961                    api.refreshAdvancedAccessibilityOfItem( ui.item.find( 'a.item-edit' ) );
    741962                },
     
    9891210                    wp.a11y.speak( deletionSpeech, 'polite' );
    9901211                    that.disableBulkSelection();
     1212                    menus.updateParentDropdown();
     1213                    menus.updateOrderDropdown();
    9911214                }
    9921215            });
     
    15281751                    api.refreshAdvancedAccessibility();
    15291752                    wp.a11y.speak( menus.itemRemoved );
     1753                    menus.updateParentDropdown();
     1754                    menus.updateOrderDropdown();
    15301755                });
    15311756        },
  • trunk/src/wp-admin/css/nav-menus.css

    r58747 r59265  
    828828}
    829829
    830 .menu-item-settings .description-thin,
    831 .menu-item-settings .description-wide {
    832     margin-right: 10px;
    833     float: left;
    834 }
    835 
    836 .description-thin {
    837     width: calc(50% - 5px);
    838 }
    839 
    840 .menu-item-settings .description-thin + .description-thin {
    841     margin-right: 0;
    842 }
    843 
    844 .description-wide {
    845     width: 100%;
     830.description-group {
     831    display: flex;
     832    column-gap: 10px;
     833}
     834
     835.description-group > * {
     836    flex-grow: 1;
    846837}
    847838
     
    952943
    953944    .menu-item-bar .menu-item-handle,
    954     .menu-item-settings,
    955     .description-wide {
     945    .menu-item-settings {
    956946        width: auto;
    957947    }
     
    961951    }
    962952
    963     .menu-item-settings .description-thin,
    964     .menu-item-settings .description-wide {
    965         width: 100%;
     953    .menu-item-settings .description-group {
     954        display: block;
    966955    }
    967956
  • trunk/src/wp-admin/includes/class-walker-nav-menu-edit.php

    r56586 r59265  
    219219                    </label>
    220220                </p>
    221                 <p class="field-css-classes description description-thin">
    222                     <label for="edit-menu-item-classes-<?php echo $item_id; ?>">
    223                         <?php _e( 'CSS Classes (optional)' ); ?><br />
    224                         <input type="text" id="edit-menu-item-classes-<?php echo $item_id; ?>" class="widefat code edit-menu-item-classes" name="menu-item-classes[<?php echo $item_id; ?>]" value="<?php echo esc_attr( implode( ' ', $menu_item->classes ) ); ?>" />
    225                     </label>
    226                 </p>
    227                 <p class="field-xfn description description-thin">
    228                     <label for="edit-menu-item-xfn-<?php echo $item_id; ?>">
    229                         <?php _e( 'Link Relationship (XFN)' ); ?><br />
    230                         <input type="text" id="edit-menu-item-xfn-<?php echo $item_id; ?>" class="widefat code edit-menu-item-xfn" name="menu-item-xfn[<?php echo $item_id; ?>]" value="<?php echo esc_attr( $menu_item->xfn ); ?>" />
    231                     </label>
    232                 </p>
     221                <div class="description-group">
     222                    <p class="field-css-classes description description-thin">
     223                        <label for="edit-menu-item-classes-<?php echo $item_id; ?>">
     224                            <?php _e( 'CSS Classes (optional)' ); ?><br />
     225                            <input type="text" id="edit-menu-item-classes-<?php echo $item_id; ?>" class="widefat code edit-menu-item-classes" name="menu-item-classes[<?php echo $item_id; ?>]" value="<?php echo esc_attr( implode( ' ', $menu_item->classes ) ); ?>" />
     226                        </label>
     227                    </p>
     228                    <p class="field-xfn description description-thin">
     229                        <label for="edit-menu-item-xfn-<?php echo $item_id; ?>">
     230                            <?php _e( 'Link Relationship (XFN)' ); ?><br />
     231                            <input type="text" id="edit-menu-item-xfn-<?php echo $item_id; ?>" class="widefat code edit-menu-item-xfn" name="menu-item-xfn[<?php echo $item_id; ?>]" value="<?php echo esc_attr( $menu_item->xfn ); ?>" />
     232                        </label>
     233                    </p>
     234                </div>
    233235                <p class="field-description description description-wide">
    234236                    <label for="edit-menu-item-description-<?php echo $item_id; ?>">
     
    238240                    </label>
    239241                </p>
     242
     243                <?php
     244                /**
     245                 * Update parent and order of menu item using select inputs.
     246                 *
     247                 * @since 6.7.0
     248                 */
     249                ?>
     250   
     251                <div class="field-move-combo description-group">
     252                    <p class="description description-wide">
     253                        <label for="edit-menu-item-parent-<?php echo $item_id; ?>">
     254                            <?php _e( 'Menu Parent' ); ?>
     255                        </label>
     256                        <select class="edit-menu-item-parent widefat" id="edit-menu-item-parent-<?php echo $item_id; ?>" name="menu-item-parent[<?php echo $item_id; ?>]">
     257                        </select>
     258                    </p>
     259                    <p class="description description-wide">
     260                        <label for="edit-menu-item-order-<?php echo $item_id; ?>">
     261                            <?php _e( 'Menu Order' ); ?>
     262                        </label>
     263                        <select class="edit-menu-item-order widefat" id="edit-menu-item-order-<?php echo $item_id; ?>" name="menu-item-order[<?php echo $item_id; ?>]">
     264                        </select>
     265                    </p>
     266                </div>
    240267
    241268                <?php
  • trunk/src/wp-admin/nav-menus.php

    r58306 r59265  
    598598    'movedLeft'               => __( 'Menu item moved out of submenu' ),
    599599    'movedRight'              => __( 'Menu item is now a sub-item' ),
     600    'parentUpdated'           => __( 'Menu parent updated' ),
     601    'orderUpdated'            => __( 'Menu order updated' ),
    600602);
    601603wp_localize_script( 'nav-menu', 'menus', $nav_menus_l10n );
Note: See TracChangeset for help on using the changeset viewer.