diff -r 7b1b88e27a20 -r 48c4eec2b7e6 wp/wp-admin/js/common.js --- a/wp/wp-admin/js/common.js Thu Sep 29 08:06:27 2022 +0200 +++ b/wp/wp-admin/js/common.js Fri Sep 05 18:40:08 2025 +0200 @@ -354,6 +354,21 @@ window.setPostThumbnailL10n = deprecateL10nObject( 'setPostThumbnailL10n', window.setPostThumbnailL10n, '5.5.0' ); /** + * Removed in 6.5.0, needed for back-compatibility. + * + * @since 4.5.0 + * @deprecated 6.5.0 + */ +window.uiAutocompleteL10n = window.uiAutocompleteL10n || { + noResults: '', + oneResult: '', + manyResults: '', + itemSelected: '' +}; + +window.uiAutocompleteL10n = deprecateL10nObject( 'uiAutocompleteL10n', window.uiAutocompleteL10n, '6.5.0' ); + +/** * Removed in 3.3.0, needed for back-compatibility. * * @since 2.7.0 @@ -617,7 +632,7 @@ * @return {void} */ panel.slideDown( 'fast', function() { - panel.trigger( 'focus' ); + panel.removeClass( 'hidden' ).trigger( 'focus' ); button.addClass( 'screen-meta-active' ).attr( 'aria-expanded', true ); }); @@ -646,6 +661,7 @@ button.removeClass( 'screen-meta-active' ).attr( 'aria-expanded', false ); $('.screen-meta-toggle').css('visibility', ''); panel.parent().hide(); + panel.addClass( 'hidden' ); }); $document.trigger( 'screen:options:close' ); @@ -751,9 +767,15 @@ selectionStart = $permalinkStructure[ 0 ].selectionStart, selectionEnd = $permalinkStructure[ 0 ].selectionEnd, textToAppend = $( this ).text().trim(), - textToAnnounce = $( this ).attr( 'data-added' ), + textToAnnounce, newSelectionStart; + if ( $( this ).hasClass( 'active' ) ) { + textToAnnounce = $( this ).attr( 'data-removed' ); + } else { + textToAnnounce = $( this ).attr( 'data-added' ); + } + // Remove structure tag if already part of the structure. if ( -1 !== permalinkStructureValue.indexOf( textToAppend ) ) { permalinkStructureValue = permalinkStructureValue.replace( textToAppend + '/', '' ); @@ -881,27 +903,6 @@ }); /** - * Handles the `aria-haspopup` attribute on the current menu item when it has a submenu. - * - * @since 4.4.0 - * - * @return {void} - */ - function currentMenuItemHasPopup() { - var $current = $( 'a.wp-has-current-submenu' ); - - if ( 'folded' === menuState ) { - // When folded or auto-folded and not responsive view, the current menu item does have a fly-out sub-menu. - $current.attr( 'aria-haspopup', 'true' ); - } else { - // When expanded or in responsive view, reset aria-haspopup. - $current.attr( 'aria-haspopup', 'false' ); - } - } - - $document.on( 'wp-menu-state-set wp-collapse-menu wp-responsive-activate wp-responsive-deactivate', currentMenuItemHasPopup ); - - /** * Ensures an admin submenu is within the visual viewport. * * @since 4.1.0 @@ -931,7 +932,7 @@ adjustment = maxtop; } - if ( adjustment > 1 ) { + if ( adjustment > 1 && $('#wp-admin-bar-menu-toggle').is(':hidden') ) { $submenu.css( 'margin-top', '-' + adjustment + 'px' ); } else { $submenu.css( 'margin-top', '' ); @@ -1147,7 +1148,7 @@ lastClicked = this; // Toggle the "Select all" checkboxes depending if the other ones are all checked or not. - var unchecked = $(this).closest('tbody').find(':checkbox').filter(':visible:enabled').not(':checked'); + var unchecked = $(this).closest('tbody').find('tr').find(':checkbox').filter(':visible:enabled').not(':checked'); /** * Determines if all checkboxes are checked. @@ -1400,8 +1401,8 @@ * @return {void} */ $('#contextual-help-link, #show-settings-link').on( 'focus.scroll-into-view', function(e){ - if ( e.target.scrollIntoView ) - e.target.scrollIntoView(false); + if ( e.target.scrollIntoViewIfNeeded ) + e.target.scrollIntoViewIfNeeded(false); }); /** @@ -1673,8 +1674,10 @@ // Modify functionality based on custom activate/deactivate event. $document.on( 'wp-responsive-activate.wp-responsive', function() { self.activate(); + self.toggleAriaHasPopup( 'add' ); }).on( 'wp-responsive-deactivate.wp-responsive', function() { self.deactivate(); + self.toggleAriaHasPopup( 'remove' ); }); $( '#wp-admin-bar-menu-toggle a' ).attr( 'aria-expanded', 'false' ); @@ -1695,13 +1698,55 @@ } } ); + // Close sidebar when target moves outside of toggle and sidebar. + $( document ).on( 'click', function( event ) { + if ( ! $wpwrap.hasClass( 'wp-responsive-open' ) || ! document.hasFocus() ) { + return; + } + + var focusIsInToggle = $.contains( $( '#wp-admin-bar-menu-toggle' )[0], event.target ); + var focusIsInSidebar = $.contains( $( '#adminmenuwrap' )[0], event.target ); + + if ( ! focusIsInToggle && ! focusIsInSidebar ) { + $( '#wp-admin-bar-menu-toggle' ).trigger( 'click.wp-responsive' ); + } + } ); + + // Close sidebar when a keypress completes outside of toggle and sidebar. + $( document ).on( 'keyup', function( event ) { + var toggleButton = $( '#wp-admin-bar-menu-toggle' )[0]; + if ( ! $wpwrap.hasClass( 'wp-responsive-open' ) ) { + return; + } + if ( 27 === event.keyCode ) { + $( toggleButton ).trigger( 'click.wp-responsive' ); + $( toggleButton ).find( 'a' ).trigger( 'focus' ); + } else { + if ( 9 === event.keyCode ) { + var sidebar = $( '#adminmenuwrap' )[0]; + var focusedElement = event.relatedTarget || document.activeElement; + // A brief delay is required to allow focus to switch to another element. + setTimeout( function() { + var focusIsInToggle = $.contains( toggleButton, focusedElement ); + var focusIsInSidebar = $.contains( sidebar, focusedElement ); + + if ( ! focusIsInToggle && ! focusIsInSidebar ) { + $( toggleButton ).trigger( 'click.wp-responsive' ); + } + }, 10 ); + } + } + }); + // Add menu events. $adminmenu.on( 'click.wp-responsive', 'li.wp-has-submenu > a', function( event ) { if ( ! $adminmenu.data('wp-responsive') ) { return; } - + let state = ( 'false' === $( this ).attr( 'aria-expanded' ) ) ? 'true' : 'false'; $( this ).parent( 'li' ).toggleClass( 'selected' ); + $( this ).attr( 'aria-expanded', state ); + $( this ).trigger( 'focus' ); event.preventDefault(); }); @@ -1775,6 +1820,34 @@ }, /** + * Toggles the aria-haspopup attribute for the responsive admin menu. + * + * The aria-haspopup attribute is only necessary for the responsive menu. + * See ticket https://core.trac.wordpress.org/ticket/43095 + * + * @since 6.6.0 + * + * @param {string} action Whether to add or remove the aria-haspopup attribute. + * + * @return {void} + */ + toggleAriaHasPopup: function( action ) { + var elements = $adminmenu.find( '[data-ariahaspopup]' ); + + if ( action === 'add' ) { + elements.each( function() { + $( this ).attr( 'aria-haspopup', 'menu' ).attr( 'aria-expanded', 'false' ); + } ); + + return; + } + + elements.each( function() { + $( this ).removeAttr( 'aria-haspopup' ).removeAttr( 'aria-expanded' ); + } ); + }, + + /** * Sets the responsiveness and enables the overlay based on the viewport width. * * @since 3.8.0 @@ -1971,7 +2044,6 @@ window.wpResponsive.init(); setPinMenu(); setMenuState(); - currentMenuItemHasPopup(); makeNoticesDismissible(); aria_button_if_js(); @@ -2077,3 +2149,123 @@ })(); }( jQuery, window )); + +/** + * Freeze animated plugin icons when reduced motion is enabled. + * + * When the user has enabled the 'prefers-reduced-motion' setting, this module + * stops animations for all GIFs on the page with the class 'plugin-icon' or + * plugin icon images in the update plugins table. + * + * @since 6.4.0 + */ +(function() { + // Private variables and methods. + var priv = {}, + pub = {}, + mediaQuery; + + // Initialize pauseAll to false; it will be set to true if reduced motion is preferred. + priv.pauseAll = false; + if ( window.matchMedia ) { + mediaQuery = window.matchMedia( '(prefers-reduced-motion: reduce)' ); + if ( ! mediaQuery || mediaQuery.matches ) { + priv.pauseAll = true; + } + } + + // Method to replace animated GIFs with a static frame. + priv.freezeAnimatedPluginIcons = function( img ) { + var coverImage = function() { + var width = img.width; + var height = img.height; + var canvas = document.createElement( 'canvas' ); + + // Set canvas dimensions. + canvas.width = width; + canvas.height = height; + + // Copy classes from the image to the canvas. + canvas.className = img.className; + + // Check if the image is inside a specific table. + var isInsideUpdateTable = img.closest( '#update-plugins-table' ); + + if ( isInsideUpdateTable ) { + // Transfer computed styles from image to canvas. + var computedStyles = window.getComputedStyle( img ), + i, max; + for ( i = 0, max = computedStyles.length; i < max; i++ ) { + var propName = computedStyles[ i ]; + var propValue = computedStyles.getPropertyValue( propName ); + canvas.style[ propName ] = propValue; + } + } + + // Draw the image onto the canvas. + canvas.getContext( '2d' ).drawImage( img, 0, 0, width, height ); + + // Set accessibility attributes on canvas. + canvas.setAttribute( 'aria-hidden', 'true' ); + canvas.setAttribute( 'role', 'presentation' ); + + // Insert canvas before the image and set the image to be near-invisible. + var parent = img.parentNode; + parent.insertBefore( canvas, img ); + img.style.opacity = 0.01; + img.style.width = '0px'; + img.style.height = '0px'; + }; + + // If the image is already loaded, apply the coverImage function. + if ( img.complete ) { + coverImage(); + } else { + // Otherwise, wait for the image to load. + img.addEventListener( 'load', coverImage, true ); + } + }; + + // Public method to freeze all relevant GIFs on the page. + pub.freezeAll = function() { + var images = document.querySelectorAll( '.plugin-icon, #update-plugins-table img' ); + for ( var x = 0; x < images.length; x++ ) { + if ( /\.gif(?:\?|$)/i.test( images[ x ].src ) ) { + priv.freezeAnimatedPluginIcons( images[ x ] ); + } + } + }; + + // Only run the freezeAll method if the user prefers reduced motion. + if ( true === priv.pauseAll ) { + pub.freezeAll(); + } + + // Listen for jQuery AJAX events. + ( function( $ ) { + if ( window.pagenow === 'plugin-install' ) { + // Only listen for ajaxComplete if this is the plugin-install.php page. + $( document ).ajaxComplete( function( event, xhr, settings ) { + + // Check if this is the 'search-install-plugins' request. + if ( settings.data && typeof settings.data === 'string' && settings.data.includes( 'action=search-install-plugins' ) ) { + // Recheck if the user prefers reduced motion. + if ( window.matchMedia ) { + var mediaQuery = window.matchMedia( '(prefers-reduced-motion: reduce)' ); + if ( mediaQuery.matches ) { + pub.freezeAll(); + } + } else { + // Fallback for browsers that don't support matchMedia. + if ( true === priv.pauseAll ) { + pub.freezeAll(); + } + } + } + } ); + } + } )( jQuery ); + + // Expose public methods. + return pub; +})();