From 8228dce519498bb9345da71da827845b94ce6f45 Mon Sep 17 00:00:00 2001 From: TallblokeUK Date: Thu, 3 Sep 2026 22:56:18 +0100 Subject: [PATCH 1/3] fix: keep the toolbar's end group intact and wrap the row before it spills --- src/css/common/list-table/_responsive.scss | 9 ++++-- tests/e2e/list-toolbar-fit.spec.ts | 34 ++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 tests/e2e/list-toolbar-fit.spec.ts diff --git a/src/css/common/list-table/_responsive.scss b/src/css/common/list-table/_responsive.scss index 236fc2ea2..64ca500e1 100644 --- a/src/css/common/list-table/_responsive.scss +++ b/src/css/common/list-table/_responsive.scss @@ -125,9 +125,12 @@ display: none; } + // The pagination and view toggle cannot shrink, so the group must not be + // asked to: letting it shrink pushed the toggle out of the toolbar (and + // off the page edge in RTL) between the tablet collapse and about 1400px. .tablenav-end-group { - flex: 0 1 auto; - min-inline-size: 0; + flex: 0 0 auto; + min-inline-size: fit-content; } // The item count is the first thing to give way when space runs short. @@ -145,7 +148,7 @@ // "Select all" at the start, the pagination + view-toggle group pinned to the // end. A forced full-width break keeps the split deterministic regardless of // content width. -@media (782px < width <= 1210px) { +@media (782px < width <= 1400px) { .snippets-list-view .tablenav.top { flex-wrap: wrap; row-gap: 8px; diff --git a/tests/e2e/list-toolbar-fit.spec.ts b/tests/e2e/list-toolbar-fit.spec.ts new file mode 100644 index 000000000..17a357caf --- /dev/null +++ b/tests/e2e/list-toolbar-fit.spec.ts @@ -0,0 +1,34 @@ +import { expect, test } from '@playwright/test' + +const SNIPPETS_URL = '/wp-admin/admin.php?page=snippets' + +// The toolbar's end group (pagination plus the view toggle) cannot shrink, so at +// widths where the row does not fit it must wrap rather than spill off the page. +test.describe('Snippets toolbar fit', () => { + for (const width of [1280, 1360, 1600]) { + test(`nothing spills out of the toolbar at ${width}px`, async ({ page }) => { + await page.setViewportSize({ width, height: 900 }) + await page.goto(SNIPPETS_URL) + await page.waitForSelector('.snippet-view-toggle') + + const fit = await page.evaluate(() => { + const nav = document.querySelector('.snippets-list-view .tablenav.top') + const toggle = document.querySelector('.snippet-view-toggle') + if (!nav || !toggle) { + return { missing: true } + } + const n = nav.getBoundingClientRect() + const t = toggle.getBoundingClientRect() + return { + missing: false, + pageOverflow: document.documentElement.scrollWidth - document.documentElement.clientWidth, + toggleInsideNav: t.left >= n.left - 1 && t.right <= n.right + 1 + } + }) + + expect(fit.missing).toBe(false) + expect(fit.pageOverflow).toBeLessThanOrEqual(0) + expect(fit.toggleInsideNav).toBe(true) + }) + } +}) From bae45f943221ac276c65b49ef32774fb24d53c3c Mon Sep 17 00:00:00 2001 From: TallblokeUK Date: Thu, 3 Sep 2026 22:56:30 +0100 Subject: [PATCH 2/3] fix: name the code editor inputs for assistive technology --- src/js/components/EditMenu/SnippetForm/fields/CodeEditor.tsx | 4 ++++ src/js/services/settings/editor-preview.ts | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/js/components/EditMenu/SnippetForm/fields/CodeEditor.tsx b/src/js/components/EditMenu/SnippetForm/fields/CodeEditor.tsx index 322e19997..32a67b904 100644 --- a/src/js/components/EditMenu/SnippetForm/fields/CodeEditor.tsx +++ b/src/js/components/EditMenu/SnippetForm/fields/CodeEditor.tsx @@ -104,6 +104,10 @@ export const CodeEditor: React.FC = ({ isExpanded, setIsExpande if (textareaRef.current && !editorInstance && window.wp.codeEditor) { editorInstance = window.wp.codeEditor.initialize(textareaRef.current) + // CodeMirror hides the labelled textarea and types into an unlabelled one + // of its own, so the name has to be put on that input directly. + editorInstance.codemirror.getInputField().setAttribute('aria-label', __('Snippet code', 'code-snippets')) + editorInstance.codemirror.on('changes', (instance, changes) => handleEditorChanges(instance, changes, setSnippet, setCurrentNotice)) } diff --git a/src/js/services/settings/editor-preview.ts b/src/js/services/settings/editor-preview.ts index 637b30fb6..6214e6909 100644 --- a/src/js/services/settings/editor-preview.ts +++ b/src/js/services/settings/editor-preview.ts @@ -1,3 +1,4 @@ +import { __ } from '@wordpress/i18n' import '../../entries/editor' const parseSelect = (select: HTMLSelectElement) => select.options[select.selectedIndex].value @@ -10,6 +11,10 @@ const initialiseCodeMirror = () => { if (textarea && codeEditor) { window.code_snippets_editor_preview = codeEditor.initialize(textarea) + + // CodeMirror's own input is unlabelled; name it so the preview reads as what it is. + window.code_snippets_editor_preview.codemirror.getInputField().setAttribute('aria-label', __('Code editor preview', 'code-snippets')) + return window.code_snippets_editor_preview.codemirror } From d85e0f3e521918c84039e80b28f0ac66fd50fd55 Mon Sep 17 00:00:00 2001 From: TallblokeUK Date: Thu, 3 Sep 2026 22:56:41 +0100 Subject: [PATCH 3/3] fix: span untitled settings rows and lift the tags help text contrast --- src/css/edit/_sidebar.scss | 6 ++++++ src/php/Settings/settings.php | 9 +++++++++ tests/unit/Settings/Settings_Layout_Test.php | 3 +++ 3 files changed, 18 insertions(+) diff --git a/src/css/edit/_sidebar.scss b/src/css/edit/_sidebar.scss index fbb9f783c..01df0e161 100644 --- a/src/css/edit/_sidebar.scss +++ b/src/css/edit/_sidebar.scss @@ -179,6 +179,12 @@ .components-form-token-field { inline-size: 100%; + + // The component's own grey is 4.05:1 on the sidebar surface; the muted + // text token clears the 4.5:1 floor. + .components-form-token-field__help { + color: var(--cs-color-text-muted); + } } .generate-button { diff --git a/src/php/Settings/settings.php b/src/php/Settings/settings.php index fcb3272f5..6e2cee280 100644 --- a/src/php/Settings/settings.php +++ b/src/php/Settings/settings.php @@ -136,6 +136,15 @@ function do_settings_fields_with_headings( string $page, string $section ): void echo ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped above. + // A field with no title, such as a notice rendered by a callback, spans + // the row rather than leaving an empty header cell for a screen reader. + if ( '' === (string) $field['title'] ) { + echo ''; + call_user_func( $field['callback'], $field['args'] ); + echo ''; + continue; + } + if ( ! empty( $field['args']['label_for'] ) ) { printf( '', diff --git a/tests/unit/Settings/Settings_Layout_Test.php b/tests/unit/Settings/Settings_Layout_Test.php index 3d9f9547f..812d57eb9 100644 --- a/tests/unit/Settings/Settings_Layout_Test.php +++ b/tests/unit/Settings/Settings_Layout_Test.php @@ -102,6 +102,7 @@ public function test_group_headings_render_once_and_escaped(): void { ] ); add_settings_field( 'third', 'Third', '__return_null', Settings_Menu::SETTINGS_PAGE, 'layout-test', [ 'group_heading' => 'Group two' ] ); + add_settings_field( 'notice', '', static fn() => print( 'Notice text' ), Settings_Menu::SETTINGS_PAGE, 'layout-test' ); ob_start(); do_settings_fields_with_headings( Settings_Menu::SETTINGS_PAGE, 'layout-test' ); @@ -112,6 +113,8 @@ public function test_group_headings_render_once_and_escaped(): void { $this->assertStringContainsString( 'Group two', $html ); $this->assertStringContainsString( '', $html ); $this->assertStringContainsString( 'First', $html ); + $this->assertStringContainsString( 'Notice text', $html, 'a field with no title spans the row' ); + $this->assertStringNotContainsString( '', $html, 'no empty header cell is left behind' ); } /**