Skip to content

Commit

Permalink
Menus: A11y: Set the parent and order using select fields.
Browse files Browse the repository at this point in the history
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.

git-svn-id: https://develop.svn.wordpress.org/trunk@59265 602fd350-edb4-49c9-b593-d223f7449a82
  • Loading branch information
joedolson committed Oct 21, 2024
1 parent 0d9c56e commit 3be4117
Show file tree
Hide file tree
Showing 4 changed files with 276 additions and 33 deletions.
227 changes: 226 additions & 1 deletion src/js/_enqueues/lib/nav-menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@
checkboxes.prop( 'checked', false );
t.find( '.button-controls .select-all' ).prop( 'checked', false );
t.find( '.button-controls .spinner' ).removeClass( 'is-active' );
t.updateParentDropdown();
t.updateOrderDropdown();
});
});
},
Expand Down Expand Up @@ -288,6 +290,105 @@
});
});
return this;
},
updateParentDropdown : function() {
return this.each(function(){
var menuItems = $( '#menu-to-edit li' ),
parentDropdowns = $( '.edit-menu-item-parent' );

$.each( parentDropdowns, function() {
var parentDropdown = $( this ),
$html = '',
$selected = '',
currentItemID = parentDropdown.closest( 'li.menu-item' ).find( '.menu-item-data-db-id' ).val(),
currentparentID = parentDropdown.closest( 'li.menu-item' ).find( '.menu-item-data-parent-id' ).val(),
currentItem = parentDropdown.closest( 'li.menu-item' ),
currentMenuItemChild = currentItem.childMenuItems(),
excludeMenuItem = [ currentItemID ];

if ( currentMenuItemChild.length > 0 ) {
$.each( currentMenuItemChild, function(){
var childItem = $(this),
childID = childItem.find( '.menu-item-data-db-id' ).val();

excludeMenuItem.push( childID );
});
}

if ( currentparentID == 0 ) {
$selected = 'selected';
}

$html += '<option ' + $selected + ' value="0">No Parent</option>';

$.each( menuItems, function() {
var menuItem = $(this),
$selected = '',
menuID = menuItem.find( '.menu-item-data-db-id' ).val(),
menuTitle = menuItem.find( '.edit-menu-item-title' ).val();

if ( ! excludeMenuItem.includes( menuID ) ) {
if ( currentparentID == menuID ) {
$selected = 'selected';
}
$html += '<option ' + $selected + ' value="' + menuID + '">' + menuTitle + '</option>';
}
});

parentDropdown.html( $html );
});

});
},
updateOrderDropdown : function() {
return this.each( function() {
var itemPosition,
orderDropdowns = $( '.edit-menu-item-order' );

$.each( orderDropdowns, function() {
var orderDropdown = $( this ),
menuItem = orderDropdown.closest( 'li.menu-item' ).first(),
depth = menuItem.menuItemDepth(),
isPrimaryMenuItem = ( 0 === depth ),
$html = '',
$selected = '';

if ( isPrimaryMenuItem ) {
var primaryItems = $( '.menu-item-depth-0' ),
totalMenuItems = primaryItems.length;

itemPosition = primaryItems.index( menuItem ) + 1;

for ( let i = 1; i < totalMenuItems + 1; i++ ) {
$selected = '';
if ( i == itemPosition ) {
$selected = 'selected';
}
$html += '<option ' + $selected + ' value="' + i + '">' + i + ' of ' + totalMenuItems + '</option>';
}

} else {
var parentItem = menuItem.prevAll( '.menu-item-depth-' + parseInt( depth - 1, 10 ) ).first(),
parentItemId = parentItem.find( '.menu-item-data-db-id' ).val(),
subItems = $( '.menu-item .menu-item-data-parent-id[value="' + parentItemId + '"]' ),
totalSubMenuItems = subItems.length;

itemPosition = $( subItems.parents('.menu-item').get().reverse() ).index( menuItem ) + 1;

for ( let i = 1; i < totalSubMenuItems + 1; i++ ) {
$selected = '';
if ( i == itemPosition ) {
$selected = 'selected';
}
$html += '<option ' + $selected + ' value="' + i + '">' + i + ' of ' + totalSubMenuItems + '</option>';
}

}

orderDropdown.html( $html );
});

});
}
});
},
Expand All @@ -297,7 +398,6 @@
},

moveMenuItem : function( $this, dir ) {

var items, newItemPosition, newDepth,
menuItems = $( '#menu-to-edit li' ),
menuItemsCount = menuItems.length,
Expand Down Expand Up @@ -400,6 +500,8 @@
api.registerChange();
api.refreshKeyboardAccessibility();
api.refreshAdvancedAccessibility();
thisItem.updateParentDropdown();
thisItem.updateOrderDropdown();

if ( a11ySpeech ) {
wp.a11y.speak( a11ySpeech );
Expand Down Expand Up @@ -431,6 +533,123 @@
api.moveMenuItem( $( this ).parents( 'li.menu-item' ).find( 'a.item-edit' ), dir );
}
});

// Set menu parents data for all menu items.
menu.updateParentDropdown();

// Set menu order data for all menu items.
menu.updateOrderDropdown();

// Update menu item parent when value is changed.
menu.on( 'change', '.edit-menu-item-parent', function() {
api.changeMenuParent( $( this ) );
});

// Update menu item order when value is changed.
menu.on( 'change', '.edit-menu-item-order', function() {
api.changeMenuOrder( $( this ) );
});
},

/**
* changeMenuParent( [parentDropdown] )
*
* @since 6.7.0
*
* @param {object} parentDropdown select field
*/
changeMenuParent : function( parentDropdown ) {
var menuItemNewPosition,
menuItems = $( '#menu-to-edit li' ),
$this = $( parentDropdown ),
newParentID = $this.val(),
menuItem = $this.closest( 'li.menu-item' ).first(),
menuItemOldDepth = menuItem.menuItemDepth(),
menuItemChildren = menuItem.childMenuItems(),
menuItemNoChildren = parseInt( menuItem.childMenuItems().length, 10 ),
parentItem = $( '#menu-item-' + newParentID ),
parentItemDepth = parentItem.menuItemDepth(),
menuItemNewDepth = parseInt( parentItemDepth ) + 1;

if ( newParentID == 0 ) {
menuItemNewDepth = 0;
}

menuItem.find( '.menu-item-data-parent-id' ).val( newParentID );
menuItem.moveHorizontally( menuItemNewDepth, menuItemOldDepth );

if ( menuItemNoChildren > 0 ) {
menuItem = menuItem.add( menuItemChildren );
}
menuItem.detach();

menuItems = $( '#menu-to-edit li' );

var parentItemPosition = parseInt( parentItem.index(), 10 ),
parentItemNoChild = parseInt( parentItem.childMenuItems().length, 10 );

if ( parentItemNoChild > 0 ){
menuItemNewPosition = parentItemPosition + parentItemNoChild;
} else {
menuItemNewPosition = parentItemPosition;
}

if ( newParentID == 0 ) {
menuItemNewPosition = menuItems.length - 1;
}

menuItem.insertAfter( menuItems.eq( menuItemNewPosition ) ).updateParentMenuItemDBId().updateParentDropdown().updateOrderDropdown();

api.registerChange();
api.refreshKeyboardAccessibility();
api.refreshAdvancedAccessibility();
$this.trigger( 'focus' );
wp.a11y.speak( menus.parentUpdated, 'polite' );
},

/**
* changeMenuOrder( [OrderDropdown] )
*
* @since 6.7.0
*
* @param {object} orderDropdown select field
*/
changeMenuOrder : function( orderDropdown ) {
var menuItems = $( '#menu-to-edit li' ),
$this = $( orderDropdown ),
newOrderID = parseInt( $this.val(), 10),
menuItem = $this.closest( 'li.menu-item' ).first(),
menuItemChildren = menuItem.childMenuItems(),
menuItemNoChildren = menuItemChildren.length,
menuItemCurrentPosition = parseInt( menuItem.index(), 10 ),
parentItemID = menuItem.find( '.menu-item-data-parent-id' ).val(),
subItems = $( '.menu-item .menu-item-data-parent-id[value="' + parentItemID + '"]' ),
currentItemAtPosition = $(subItems[newOrderID - 1]).closest( 'li.menu-item' );

if ( menuItemNoChildren > 0 ) {
menuItem = menuItem.add( menuItemChildren );
}

var currentItemNoChildren = currentItemAtPosition.childMenuItems().length,
currentItemPosition = parseInt( currentItemAtPosition.index(), 10 );

menuItems = $( '#menu-to-edit li' );

var menuItemNewPosition = currentItemPosition;

if(menuItemCurrentPosition > menuItemNewPosition){
menuItemNewPosition = currentItemPosition;
menuItem.detach().insertBefore( menuItems.eq( menuItemNewPosition ) ).updateOrderDropdown();
} else {
menuItemNewPosition = menuItemNewPosition + currentItemNoChildren;
menuItem.detach().insertAfter( menuItems.eq( menuItemNewPosition ) ).updateOrderDropdown();
}

api.registerChange();
api.refreshKeyboardAccessibility();
api.refreshAdvancedAccessibility();
$this.trigger( 'focus' );
wp.a11y.speak( menus.orderUpdated, 'polite' );
},

/**
Expand Down Expand Up @@ -737,6 +956,8 @@

api.refreshKeyboardAccessibility();
api.refreshAdvancedAccessibility();
ui.item.updateParentDropdown();
ui.item.updateOrderDropdown();
api.refreshAdvancedAccessibilityOfItem( ui.item.find( 'a.item-edit' ) );
},
change: function(e, ui) {
Expand Down Expand Up @@ -988,6 +1209,8 @@
deletionSpeech = menus.itemsDeleted.replace( '%s', itemsPendingDeletion );
wp.a11y.speak( deletionSpeech, 'polite' );
that.disableBulkSelection();
menus.updateParentDropdown();
menus.updateOrderDropdown();
}
});
},
Expand Down Expand Up @@ -1527,6 +1750,8 @@
}
api.refreshAdvancedAccessibility();
wp.a11y.speak( menus.itemRemoved );
menus.updateParentDropdown();
menus.updateOrderDropdown();
});
},

Expand Down
27 changes: 8 additions & 19 deletions src/wp-admin/css/nav-menus.css
Original file line number Diff line number Diff line change
Expand Up @@ -827,22 +827,13 @@ body.menu-max-depth-11 { min-width: 1280px !important; }
display: none;
}

.menu-item-settings .description-thin,
.menu-item-settings .description-wide {
margin-right: 10px;
float: left;
}

.description-thin {
width: calc(50% - 5px);
}

.menu-item-settings .description-thin + .description-thin {
margin-right: 0;
.description-group {
display: flex;
column-gap: 10px;
}

.description-wide {
width: 100%;
.description-group > * {
flex-grow: 1;
}

.menu-item-actions {
Expand Down Expand Up @@ -951,18 +942,16 @@ body.menu-max-depth-11 { min-width: 1280px !important; }
}

.menu-item-bar .menu-item-handle,
.menu-item-settings,
.description-wide {
.menu-item-settings {
width: auto;
}

.menu-item-settings {
padding: 10px;
}

.menu-item-settings .description-thin,
.menu-item-settings .description-wide {
width: 100%;
.menu-item-settings .description-group {
display: block;
}

.menu-item-settings input {
Expand Down
Loading

0 comments on commit 3be4117

Please sign in to comment.