diff --git a/src/wp-admin/js/customize-controls.js b/src/wp-admin/js/customize-controls.js index 8f1b9a1268..a6ede2713e 100644 --- a/src/wp-admin/js/customize-controls.js +++ b/src/wp-admin/js/customize-controls.js @@ -74,7 +74,7 @@ * @since 4.1.0 * * @param {Object} [params] - * @param {Callback} [params.completeCallback] + * @param {Function} [params.completeCallback] */ focus = function ( params ) { var construct, completeCallback, focus; diff --git a/src/wp-admin/js/customize-nav-menus.js b/src/wp-admin/js/customize-nav-menus.js index 9db1664b00..bcebb65e1e 100644 --- a/src/wp-admin/js/customize-nav-menus.js +++ b/src/wp-admin/js/customize-nav-menus.js @@ -1363,24 +1363,38 @@ /** * Expand the menu item form control. + * + * @since 4.5.0 Added params.completeCallback. + * + * @param {Object} [params] - Optional params. + * @param {Function} [params.completeCallback] - Function to call when the form toggle has finished animating. */ - expandForm: function() { - this.toggleForm( true ); + expandForm: function( params ) { + this.toggleForm( true, params ); }, /** * Collapse the menu item form control. + * + * @since 4.5.0 Added params.completeCallback. + * + * @param {Object} [params] - Optional params. + * @param {Function} [params.completeCallback] - Function to call when the form toggle has finished animating. */ - collapseForm: function() { - this.toggleForm( false ); + collapseForm: function( params ) { + this.toggleForm( false, params ); }, /** * Expand or collapse the menu item control. * - * @param {boolean|undefined} [showOrHide] If not supplied, will be inverse of current visibility + * @since 4.5.0 Added params.completeCallback. + * + * @param {boolean} [showOrHide] - If not supplied, will be inverse of current visibility + * @param {Object} [params] - Optional params. + * @param {Function} [params.completeCallback] - Function to call when the form toggle has finished animating. */ - toggleForm: function( showOrHide ) { + toggleForm: function( showOrHide, params ) { var self = this, $menuitem, $inside, complete; $menuitem = this.container; @@ -1391,6 +1405,9 @@ // Already expanded or collapsed. if ( $inside.is( ':visible' ) === showOrHide ) { + if ( params && params.completeCallback ) { + params.completeCallback(); + } return; } @@ -1407,6 +1424,10 @@ .removeClass( 'menu-item-edit-inactive' ) .addClass( 'menu-item-edit-active' ); self.container.trigger( 'expanded' ); + + if ( params && params.completeCallback ) { + params.completeCallback(); + } }; $menuitem.find( '.item-edit' ).attr( 'aria-expanded', 'true' ); @@ -1419,6 +1440,10 @@ .addClass( 'menu-item-edit-inactive' ) .removeClass( 'menu-item-edit-active' ); self.container.trigger( 'collapsed' ); + + if ( params && params.completeCallback ) { + params.completeCallback(); + } }; self.container.trigger( 'collapse' ); @@ -1431,14 +1456,31 @@ /** * Expand the containing menu section, expand the form, and focus on * the first input in the control. + * + * @since 4.5.0 Added params.completeCallback. + * + * @param {Object} [params] - Params object. + * @param {Function} [params.completeCallback] - Optional callback function when focus has completed. */ - focus: function() { - var control = this, focusable; + focus: function( params ) { + params = params || {}; + var control = this, originalCompleteCallback = params.completeCallback; + control.expandControlSection(); - control.expandForm(); - // Note that we can't use :focusable due to a jQuery UI issue. See: https://github.com/jquery/jquery-ui/pull/1583 - focusable = control.container.find( '.menu-item-settings' ).find( 'input, select, textarea, button, object, a[href], [tabindex]' ).filter( ':visible' ); - focusable.first().focus(); + + params.completeCallback = function() { + var focusable; + + // Note that we can't use :focusable due to a jQuery UI issue. See: https://github.com/jquery/jquery-ui/pull/1583 + focusable = control.container.find( '.menu-item-settings' ).find( 'input, select, textarea, button, object, a[href], [tabindex]' ).filter( ':visible' ); + focusable.first().focus(); + + if ( originalCompleteCallback ) { + originalCompleteCallback(); + } + }; + + control.expandForm( params ); }, /** @@ -2445,6 +2487,9 @@ api.previewer.bind( 'refresh', function() { api.previewer.refresh(); }); + + // Open and focus menu control. + api.previewer.bind( 'focus-nav-menu-item-control', api.Menus.focusMenuItemControl ); } ); /** diff --git a/src/wp-includes/class-wp-customize-nav-menus.php b/src/wp-includes/class-wp-customize-nav-menus.php index 240cf3989c..2c90548c59 100644 --- a/src/wp-includes/class-wp-customize-nav-menus.php +++ b/src/wp-includes/class-wp-customize-nav-menus.php @@ -946,6 +946,9 @@ final class WP_Customize_Nav_Menus { ), 'previewCustomizeNonce' => wp_create_nonce( 'preview-customize_' . $this->manager->get_stylesheet() ), 'navMenuInstanceArgs' => $this->preview_nav_menu_instance_args, + 'l10n' => array( + 'editNavMenuItemTooltip' => __( 'Shift-click to edit this menu item.' ), + ), ); printf( '', wp_json_encode( $exports ) ); diff --git a/src/wp-includes/js/customize-preview-nav-menus.js b/src/wp-includes/js/customize-preview-nav-menus.js index 814c6f21d7..fe5f305729 100644 --- a/src/wp-includes/js/customize-preview-nav-menus.js +++ b/src/wp-includes/js/customize-preview-nav-menus.js @@ -19,7 +19,8 @@ active: false, stylesheet: '' }, - navMenuInstanceArgs: {} + navMenuInstanceArgs: {}, + l10n: {} }; api.MenusCustomizerPreview = { @@ -63,6 +64,8 @@ } } } ); + + self.highlightControls(); }, /** @@ -272,6 +275,36 @@ }, this ), refreshDebounceDelay ); + }, + + /** + * Connect nav menu items with their corresponding controls in the pane. + */ + highlightControls: function() { + var selector = '.menu-item[id^=menu-item-]', + addTooltips; + + // Open expand the menu item control when shift+clicking the menu item + $( document ).on( 'click', selector, function( e ) { + var navMenuItemParts; + if ( ! e.shiftKey ) { + return; + } + + navMenuItemParts = $( this ).attr( 'id' ).match( /^menu-item-(\d+)$/ ); + if ( navMenuItemParts ) { + e.preventDefault(); + e.stopPropagation(); // Make sure a sub-nav menu item will get focused instead of parent items. + api.preview.send( 'focus-nav-menu-item-control', parseInt( navMenuItemParts[1], 10 ) ); + } + }); + + addTooltips = function( e, params ) { + params.newContainer.find( selector ).attr( 'title', settings.l10n.editNavMenuItemTooltip ); + }; + + addTooltips( null, { newContainer: $( document.body ) } ); + $( document ).on( 'customize-preview-menu-refreshed', addTooltips ); } };