diff --git a/lib/experimental/script-modules.php b/lib/experimental/script-modules.php index a113df02b9d758..f65bc1704dd890 100644 --- a/lib/experimental/script-modules.php +++ b/lib/experimental/script-modules.php @@ -201,6 +201,45 @@ function gutenberg_dequeue_module( $module_identifier ) { wp_script_modules()->dequeue( $module_identifier ); } +/** + * Prints HTML for the a11y Script Module. + * + * a11y relies on some DOM elements to use as ARIA live regions. + * Ideally, these elements are part of the initial HTML of the page + * so that accessibility tools can find them and observe updates. + */ +function gutenberg_a11y_script_module_html() { + $a11y_module_available = false; + + $get_marked_for_enqueue = new ReflectionMethod( 'WP_Script_Modules', 'get_marked_for_enqueue' ); + $get_marked_for_enqueue->setAccessible( true ); + $get_import_map = new ReflectionMethod( 'WP_Script_Modules', 'get_import_map' ); + $get_import_map->setAccessible( true ); + + foreach ( array_keys( $get_marked_for_enqueue->invoke( wp_script_modules() ) ) as $id ) { + if ( '@wordpress/a11y' === $id ) { + $a11y_module_available = true; + break; + } + } + if ( ! $a11y_module_available ) { + foreach ( array_keys( $get_import_map->invoke( wp_script_modules() )['imports'] ) as $id ) { + if ( '@wordpress/a11y' === $id ) { + $a11y_module_available = true; + break; + } + } + } + if ( ! $a11y_module_available ) { + return; + } + echo '
' + . '' + . '
' + . '
' + . '
'; +} + /** * Registers Gutenberg Script Modules. * @@ -218,12 +257,8 @@ function gutenberg_register_script_modules() { array(), $default_version ); - add_filter( - 'script_module_data_@wordpress/a11y', - function ( $data ) { - $data['i18n'] = array( 'Notifications' => __( 'Notifications', 'default' ) ); - return $data; - } - ); + + add_action( 'wp_footer', 'gutenberg_a11y_script_module_html' ); + add_action( 'admin_footer', 'gutenberg_a11y_script_module_html' ); } add_action( 'init', 'gutenberg_register_script_modules' ); diff --git a/packages/a11y/src/index.js b/packages/a11y/src/index.js index 59e93da780bd81..cdc3be10545155 100644 --- a/packages/a11y/src/index.js +++ b/packages/a11y/src/index.js @@ -2,18 +2,37 @@ * WordPress dependencies */ import domReady from '@wordpress/dom-ready'; -import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import { makeSetupFunction } from './shared/index'; +import addContainer from './script/add-container'; +import addIntroText from './script/add-intro-text'; + export { speak } from './shared/index'; /** * Create the live regions. */ -export const setup = makeSetupFunction( __( 'Notifications' ) ); +export function setup() { + const introText = document.getElementById( 'a11y-speak-intro-text' ); + const containerAssertive = document.getElementById( + 'a11y-speak-assertive' + ); + const containerPolite = document.getElementById( 'a11y-speak-polite' ); + + if ( introText === null ) { + addIntroText(); + } + + if ( containerAssertive === null ) { + addContainer( 'assertive' ); + } + + if ( containerPolite === null ) { + addContainer( 'polite' ); + } +} /** * Run setup on domReady. diff --git a/packages/a11y/src/module/index.ts b/packages/a11y/src/module/index.ts index a2c87f397f4875..a06882f068dd3a 100644 --- a/packages/a11y/src/module/index.ts +++ b/packages/a11y/src/module/index.ts @@ -1,25 +1,11 @@ /** * Internal dependencies */ -import { makeSetupFunction } from '../shared/index'; export { speak } from '../shared/index'; -// Without an i18n Script Module, "Notifications" (the only localized text used in this module) -// will be translated on the server and provided as script-module data. -let notificationsText = 'Notifications'; -try { - const textContent = document.getElementById( - 'wp-script-module-data-@wordpress/a11y' - )?.textContent; - if ( textContent ) { - const parsed = JSON.parse( textContent ); - notificationsText = parsed?.i18n?.Notifications ?? notificationsText; - } -} catch {} - /** - * Create the live regions. + * This no-op function is exported to provide compatibility with the `wp-a11y` Script. + * + * Filters should inject the relevant HTML on page load instead of requiring setup. */ -export const setup = makeSetupFunction( notificationsText ); - -setup(); +export const setup = () => {}; diff --git a/packages/a11y/src/shared/add-container.js b/packages/a11y/src/script/add-container.js similarity index 100% rename from packages/a11y/src/shared/add-container.js rename to packages/a11y/src/script/add-container.js diff --git a/packages/a11y/src/shared/add-intro-text.ts b/packages/a11y/src/script/add-intro-text.ts similarity index 83% rename from packages/a11y/src/shared/add-intro-text.ts rename to packages/a11y/src/script/add-intro-text.ts index 6bd97c887664d3..2bcf453ec44c8e 100644 --- a/packages/a11y/src/shared/add-intro-text.ts +++ b/packages/a11y/src/script/add-intro-text.ts @@ -1,18 +1,22 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + /** * Build the explanatory text to be placed before the aria live regions. * * This text is initially hidden from assistive technologies by using a `hidden` * HTML attribute which is then removed once a message fills the aria-live regions. * - * @param {string} introTextContent The translated intro text content. * @return {HTMLParagraphElement} The explanatory text HTML element. */ -export default function addIntroText( introTextContent: string ) { +export default function addIntroText() { const introText = document.createElement( 'p' ); introText.id = 'a11y-speak-intro-text'; introText.className = 'a11y-speak-intro-text'; - introText.textContent = introTextContent; + introText.textContent = __( 'Notifications' ); introText.setAttribute( 'style', diff --git a/packages/a11y/src/shared/test/add-container.test.js b/packages/a11y/src/script/test/add-container.test.js similarity index 100% rename from packages/a11y/src/shared/test/add-container.test.js rename to packages/a11y/src/script/test/add-container.test.js diff --git a/packages/a11y/src/shared/index.js b/packages/a11y/src/shared/index.js index a05f891f428561..2b6353720d0add 100644 --- a/packages/a11y/src/shared/index.js +++ b/packages/a11y/src/shared/index.js @@ -1,37 +1,9 @@ /** * Internal dependencies */ -import addContainer from './add-container'; -import addIntroText from './add-intro-text'; import clear from './clear'; import filterMessage from './filter-message'; -/** - * Create the live regions. - * @param {string} introTextContent The intro text content. - */ -export function makeSetupFunction( introTextContent ) { - return function setup() { - const introText = document.getElementById( 'a11y-speak-intro-text' ); - const containerAssertive = document.getElementById( - 'a11y-speak-assertive' - ); - const containerPolite = document.getElementById( 'a11y-speak-polite' ); - - if ( introText === null ) { - addIntroText( introTextContent ); - } - - if ( containerAssertive === null ) { - addContainer( 'assertive' ); - } - - if ( containerPolite === null ) { - addContainer( 'polite' ); - } - }; -} - /** * Allows you to easily announce dynamic interface updates to screen readers using ARIA live regions. * This module is inspired by the `speak` function in `wp-a11y.js`.