From cc73b8b920f22ea745ba2a963a8505b1d21e6116 Mon Sep 17 00:00:00 2001 From: joedolson Date: Mon, 1 Sep 2025 21:22:31 +0000 Subject: [PATCH] Accessibility: Feedback & focus on deleting terms via AJAX. When deleting a term using AJAX, notify screen reader users of the deletion using `wp.a11y.speak()`, set the active row to be unfocusable, then explicitly set new focus after the deletion is completed. Props jeremyfelt, afercia, wido, nikunj8866, SirLouen, pmbaldha, joedolson. Fixes #47101. Built from https://develop.svn.wordpress.org/trunk@60700 git-svn-id: http://core.svn.wordpress.org/trunk@60036 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-admin/js/tags.js | 47 +++++++++++++++++++++++++++++++++++------ wp-admin/js/tags.min.js | 2 +- wp-includes/version.php | 2 +- 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/wp-admin/js/tags.js b/wp-admin/js/tags.js index 3b6cf2b489..ff7761adb8 100644 --- a/wp-admin/js/tags.js +++ b/wp-admin/js/tags.js @@ -31,6 +31,12 @@ jQuery( function($) { if ( r ) { data = t.attr('href').replace(/[^?]*\?/, '').replace(/action=delete/, 'action=delete-tag'); + tr.children().css('backgroundColor', '#faafaa'); + + // Disable pointer events and all form controls/links in the row + tr.css('pointer-events', 'none'); + tr.find(':input, a').prop('disabled', true).attr('tabindex', -1); + /** * Makes a request to the server to delete the term that corresponds to the * delete term button. @@ -40,8 +46,20 @@ jQuery( function($) { * @return {void} */ $.post(ajaxurl, data, function(r){ + var message; if ( '1' == r ) { $('#ajax-response').empty(); + let nextFocus = tr.next( 'tr' ).find( 'a.row-title' ); + let prevFocus = tr.prev( 'tr' ).find( 'a.row-title' ); + // If there is neither a next row or a previous row, focus the tag input field. + if ( nextFocus.length < 1 && prevFocus.length < 1 ) { + nextFocus = $( '#tag-name' ).trigger( 'focus' ); + } else { + if ( nextFocus.length < 1 ) { + nextFocus = prevFocus; + } + } + tr.fadeOut('normal', function(){ tr.remove(); }); /** @@ -53,23 +71,38 @@ jQuery( function($) { */ $('select#parent option[value="' + data.match(/tag_ID=(\d+)/)[1] + '"]').remove(); $('a.tag-link-' + data.match(/tag_ID=(\d+)/)[1]).remove(); - + nextFocus.trigger( 'focus' ); + message = wp.i18n.__( 'The selected tag has been deleted.' ); + } else if ( '-1' == r ) { - $('#ajax-response').empty().append('

' + wp.i18n.__( 'Sorry, you are not allowed to do that.' ) + '

'); - tr.children().css('backgroundColor', ''); + message = wp.i18n.__( 'Sorry, you are not allowed to do that.' ); + $('#ajax-response').empty().append('

' + message + '

'); + resetRowAfterFailure( tr ); } else { - $('#ajax-response').empty().append('

' + wp.i18n.__( 'An error occurred while processing your request. Please try again later.' ) + '

'); - tr.children().css('backgroundColor', ''); + message = wp.i18n.__( 'An error occurred while processing your request. Please try again later.' ); + $('#ajax-response').empty().append('

' + message + '

'); + resetRowAfterFailure( tr ); } + wp.a11y.speak( message, 'assertive' ); }); - - tr.children().css('backgroundColor', '#f33'); } return false; }); + /** + * Restores the original UI state of a table row after an AJAX failure. + * + * @param {jQuery} tr The table row to reset. + * @return {void} + */ + function resetRowAfterFailure( tr ) { + tr.children().css( 'backgroundColor', '' ); + tr.css( 'pointer-events', '' ); + tr.find( ':input, a' ).prop( 'disabled', false ).removeAttr( 'tabindex' ); + } + /** * Adds a deletion confirmation when removing a tag. * diff --git a/wp-admin/js/tags.min.js b/wp-admin/js/tags.min.js index 4802ae12e7..97d43363f3 100644 --- a/wp-admin/js/tags.min.js +++ b/wp-admin/js/tags.min.js @@ -1,2 +1,2 @@ /*! This file is auto-generated */ -jQuery(function(o){var s=!1;o("#the-list").on("click",".delete-tag",function(){var t,e=o(this),n=e.parents("tr"),r=!0;return(r="undefined"!=showNotice?showNotice.warn():r)&&(t=e.attr("href").replace(/[^?]*\?/,"").replace(/action=delete/,"action=delete-tag"),o.post(ajaxurl,t,function(e){"1"==e?(o("#ajax-response").empty(),n.fadeOut("normal",function(){n.remove()}),o('select#parent option[value="'+t.match(/tag_ID=(\d+)/)[1]+'"]').remove(),o("a.tag-link-"+t.match(/tag_ID=(\d+)/)[1]).remove()):("-1"==e?o("#ajax-response").empty().append('

'+wp.i18n.__("Sorry, you are not allowed to do that.")+"

"):o("#ajax-response").empty().append('

'+wp.i18n.__("An error occurred while processing your request. Please try again later.")+"

"),n.children().css("backgroundColor",""))}),n.children().css("backgroundColor","#f33")),!1}),o("#edittag").on("click",".delete",function(e){if("undefined"==typeof showNotice)return!0;showNotice.warn()||e.preventDefault()}),o("#submit").on("click",function(){var a=o(this).parents("form");return s||(s=!0,a.find(".submit .spinner").addClass("is-active"),o.post(ajaxurl,o("#addtag").serialize(),function(e){var t,n,r;if(s=!1,a.find(".submit .spinner").removeClass("is-active"),o("#ajax-response").empty(),(t=wpAjax.parseAjaxResponse(e,"ajax-response")).errors&&"empty_term_name"===t.responses[0].errors[0].code&&validateForm(a),t&&!t.errors){if(0<(e=a.find("select#parent").val())&&0'+n+e.name+"")}o('input:not([type="checkbox"]):not([type="radio"]):not([type="button"]):not([type="submit"]):not([type="reset"]):visible, textarea:visible',a).val("")}})),!1})}); \ No newline at end of file +jQuery(function(s){var o=!1;function a(e){e.children().css("backgroundColor",""),e.css("pointer-events",""),e.find(":input, a").prop("disabled",!1).removeAttr("tabindex")}s("#the-list").on("click",".delete-tag",function(){var n,e=s(this),r=e.parents("tr"),t=!0;return(t="undefined"!=showNotice?showNotice.warn():t)&&(n=e.attr("href").replace(/[^?]*\?/,"").replace(/action=delete/,"action=delete-tag"),r.children().css("backgroundColor","#faafaa"),r.css("pointer-events","none"),r.find(":input, a").prop("disabled",!0).attr("tabindex",-1),s.post(ajaxurl,n,function(e){if("1"==e){s("#ajax-response").empty();let e=r.next("tr").find("a.row-title");var t=r.prev("tr").find("a.row-title");e.length<1&&t.length<1?e=s("#tag-name").trigger("focus"):e.length<1&&(e=t),r.fadeOut("normal",function(){r.remove()}),s('select#parent option[value="'+n.match(/tag_ID=(\d+)/)[1]+'"]').remove(),s("a.tag-link-"+n.match(/tag_ID=(\d+)/)[1]).remove(),e.trigger("focus"),t=wp.i18n.__("The selected tag has been deleted.")}else t="-1"==e?wp.i18n.__("Sorry, you are not allowed to do that."):wp.i18n.__("An error occurred while processing your request. Please try again later."),s("#ajax-response").empty().append('

'+t+"

"),a(r);wp.a11y.speak(t,"assertive")})),!1}),s("#edittag").on("click",".delete",function(e){if("undefined"==typeof showNotice)return!0;showNotice.warn()||e.preventDefault()}),s("#submit").on("click",function(){var a=s(this).parents("form");return o||(o=!0,a.find(".submit .spinner").addClass("is-active"),s.post(ajaxurl,s("#addtag").serialize(),function(e){var t,n,r;if(o=!1,a.find(".submit .spinner").removeClass("is-active"),s("#ajax-response").empty(),(t=wpAjax.parseAjaxResponse(e,"ajax-response")).errors&&"empty_term_name"===t.responses[0].errors[0].code&&validateForm(a),t&&!t.errors){if(0<(e=a.find("select#parent").val())&&0'+n+e.name+"")}s('input:not([type="checkbox"]):not([type="radio"]):not([type="button"]):not([type="submit"]):not([type="reset"]):visible, textarea:visible',a).val("")}})),!1})}); \ No newline at end of file diff --git a/wp-includes/version.php b/wp-includes/version.php index 52d93e4052..c855bc32b9 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '6.9-alpha-60699'; +$wp_version = '6.9-alpha-60700'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.