diff --git a/lib/experimental/script-modules.php b/lib/experimental/script-modules.php index 5a14e1418ed6d..a113df02b9d75 100644 --- a/lib/experimental/script-modules.php +++ b/lib/experimental/script-modules.php @@ -200,3 +200,30 @@ function gutenberg_dequeue_module( $module_identifier ) { _deprecated_function( __FUNCTION__, 'Gutenberg 17.6.0', 'wp_dequeue_script_module' ); wp_script_modules()->dequeue( $module_identifier ); } + +/** + * Registers Gutenberg Script Modules. + * + * @since 19.3 + */ +function gutenberg_register_script_modules() { + // When in production, use the plugin's version as the default asset version; + // else (for development or test) default to use the current time. + $default_version = defined( 'GUTENBERG_VERSION' ) && ! SCRIPT_DEBUG ? GUTENBERG_VERSION : time(); + + wp_deregister_script_module( '@wordpress/a11y' ); + wp_register_script_module( + '@wordpress/a11y', + gutenberg_url( 'build-module/a11y/index.min.js' ), + array(), + $default_version + ); + add_filter( + 'script_module_data_@wordpress/a11y', + function ( $data ) { + $data['i18n'] = array( 'Notifications' => __( 'Notifications', 'default' ) ); + return $data; + } + ); +} +add_action( 'init', 'gutenberg_register_script_modules' ); diff --git a/packages/a11y/README.md b/packages/a11y/README.md index 0f40d9edd010e..755396d2bb8f0 100644 --- a/packages/a11y/README.md +++ b/packages/a11y/README.md @@ -39,7 +39,7 @@ speak( 'The message you want to send to the ARIA live region', 'assertive' ); _Parameters_ - _message_ `string`: The message to be announced by assistive technologies. -- _ariaLive_ `[string]`: The politeness level for aria-live; default: 'polite'. +- _ariaLive_ `['polite'|'assertive']`: The politeness level for aria-live; default: 'polite'. diff --git a/packages/a11y/package.json b/packages/a11y/package.json index 88123b3c6c712..327d6b9ff0716 100644 --- a/packages/a11y/package.json +++ b/packages/a11y/package.json @@ -28,6 +28,7 @@ "module": "build-module/index.js", "react-native": "src/index", "types": "build-types", + "wpScriptModuleExports": "./build-module/module/index.js", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/dom-ready": "file:../dom-ready", diff --git a/packages/a11y/src/index.js b/packages/a11y/src/index.js index 957c76500c434..59e93da780bd8 100644 --- a/packages/a11y/src/index.js +++ b/packages/a11y/src/index.js @@ -2,87 +2,20 @@ * WordPress dependencies */ import domReady from '@wordpress/dom-ready'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import addIntroText from './add-intro-text'; -import addContainer from './add-container'; -import clear from './clear'; -import filterMessage from './filter-message'; +import { makeSetupFunction } from './shared/index'; +export { speak } from './shared/index'; /** * Create the live regions. */ -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' ); - } -} +export const setup = makeSetupFunction( __( 'Notifications' ) ); /** * Run setup on domReady. */ domReady( setup ); - -/** - * 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`. - * - * @param {string} message The message to be announced by assistive technologies. - * @param {string} [ariaLive] The politeness level for aria-live; default: 'polite'. - * - * @example - * ```js - * import { speak } from '@wordpress/a11y'; - * - * // For polite messages that shouldn't interrupt what screen readers are currently announcing. - * speak( 'The message you want to send to the ARIA live region' ); - * - * // For assertive messages that should interrupt what screen readers are currently announcing. - * speak( 'The message you want to send to the ARIA live region', 'assertive' ); - * ``` - */ -export function speak( message, ariaLive ) { - /* - * Clear previous messages to allow repeated strings being read out and hide - * the explanatory text from assistive technologies. - */ - clear(); - - message = filterMessage( message ); - - const introText = document.getElementById( 'a11y-speak-intro-text' ); - const containerAssertive = document.getElementById( - 'a11y-speak-assertive' - ); - const containerPolite = document.getElementById( 'a11y-speak-polite' ); - - if ( containerAssertive && ariaLive === 'assertive' ) { - containerAssertive.textContent = message; - } else if ( containerPolite ) { - containerPolite.textContent = message; - } - - /* - * Make the explanatory text available to assistive technologies by removing - * the 'hidden' HTML attribute. - */ - if ( introText ) { - introText.removeAttribute( 'hidden' ); - } -} diff --git a/packages/a11y/src/index.native.js b/packages/a11y/src/index.native.js index f6f53b6343adb..e17597a8b2747 100644 --- a/packages/a11y/src/index.native.js +++ b/packages/a11y/src/index.native.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import filterMessage from './filter-message'; +import filterMessage from './shared/filter-message'; /** * Update the ARIA live notification area text node. diff --git a/packages/a11y/src/module/index.ts b/packages/a11y/src/module/index.ts new file mode 100644 index 0000000000000..a2c87f397f487 --- /dev/null +++ b/packages/a11y/src/module/index.ts @@ -0,0 +1,25 @@ +/** + * 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. + */ +export const setup = makeSetupFunction( notificationsText ); + +setup(); diff --git a/packages/a11y/src/add-container.js b/packages/a11y/src/shared/add-container.js similarity index 100% rename from packages/a11y/src/add-container.js rename to packages/a11y/src/shared/add-container.js diff --git a/packages/a11y/src/add-intro-text.js b/packages/a11y/src/shared/add-intro-text.ts similarity index 83% rename from packages/a11y/src/add-intro-text.js rename to packages/a11y/src/shared/add-intro-text.ts index 2bcf453ec44c8..6bd97c887664d 100644 --- a/packages/a11y/src/add-intro-text.js +++ b/packages/a11y/src/shared/add-intro-text.ts @@ -1,22 +1,18 @@ -/** - * 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() { +export default function addIntroText( introTextContent: string ) { const introText = document.createElement( 'p' ); introText.id = 'a11y-speak-intro-text'; introText.className = 'a11y-speak-intro-text'; - introText.textContent = __( 'Notifications' ); + introText.textContent = introTextContent; introText.setAttribute( 'style', diff --git a/packages/a11y/src/clear.js b/packages/a11y/src/shared/clear.js similarity index 100% rename from packages/a11y/src/clear.js rename to packages/a11y/src/shared/clear.js diff --git a/packages/a11y/src/filter-message.js b/packages/a11y/src/shared/filter-message.js similarity index 100% rename from packages/a11y/src/filter-message.js rename to packages/a11y/src/shared/filter-message.js diff --git a/packages/a11y/src/shared/index.js b/packages/a11y/src/shared/index.js new file mode 100644 index 0000000000000..a05f891f42856 --- /dev/null +++ b/packages/a11y/src/shared/index.js @@ -0,0 +1,81 @@ +/** + * 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`. + * + * @param {string} message The message to be announced by assistive technologies. + * @param {'polite'|'assertive'} [ariaLive] The politeness level for aria-live; default: 'polite'. + * + * @example + * ```js + * import { speak } from '@wordpress/a11y'; + * + * // For polite messages that shouldn't interrupt what screen readers are currently announcing. + * speak( 'The message you want to send to the ARIA live region' ); + * + * // For assertive messages that should interrupt what screen readers are currently announcing. + * speak( 'The message you want to send to the ARIA live region', 'assertive' ); + * ``` + */ +export function speak( message, ariaLive ) { + /* + * Clear previous messages to allow repeated strings being read out and hide + * the explanatory text from assistive technologies. + */ + clear(); + + message = filterMessage( message ); + + const introText = document.getElementById( 'a11y-speak-intro-text' ); + const containerAssertive = document.getElementById( + 'a11y-speak-assertive' + ); + const containerPolite = document.getElementById( 'a11y-speak-polite' ); + + if ( containerAssertive && ariaLive === 'assertive' ) { + containerAssertive.textContent = message; + } else if ( containerPolite ) { + containerPolite.textContent = message; + } + + /* + * Make the explanatory text available to assistive technologies by removing + * the 'hidden' HTML attribute. + */ + if ( introText ) { + introText.removeAttribute( 'hidden' ); + } +} diff --git a/packages/a11y/src/test/add-container.test.js b/packages/a11y/src/shared/test/add-container.test.js similarity index 100% rename from packages/a11y/src/test/add-container.test.js rename to packages/a11y/src/shared/test/add-container.test.js diff --git a/packages/a11y/src/test/clear.test.js b/packages/a11y/src/shared/test/clear.test.js similarity index 100% rename from packages/a11y/src/test/clear.test.js rename to packages/a11y/src/shared/test/clear.test.js diff --git a/packages/a11y/src/test/filter-message.test.js b/packages/a11y/src/shared/test/filter-message.test.js similarity index 100% rename from packages/a11y/src/test/filter-message.test.js rename to packages/a11y/src/shared/test/filter-message.test.js diff --git a/packages/a11y/src/test/index.test.js b/packages/a11y/src/test/index.test.js index 4815baa220504..0f6b9d0bd572e 100644 --- a/packages/a11y/src/test/index.test.js +++ b/packages/a11y/src/test/index.test.js @@ -7,10 +7,10 @@ import domReady from '@wordpress/dom-ready'; * Internal dependencies */ import { setup, speak } from '../'; -import clear from '../clear'; -import filterMessage from '../filter-message'; +import clear from '../shared/clear'; +import filterMessage from '../shared/filter-message'; -jest.mock( '../clear', () => { +jest.mock( '../shared/clear', () => { return jest.fn(); } ); jest.mock( '@wordpress/dom-ready', () => { @@ -18,7 +18,7 @@ jest.mock( '@wordpress/dom-ready', () => { callback(); } ); } ); -jest.mock( '../filter-message', () => { +jest.mock( '../shared/filter-message', () => { return jest.fn( ( message ) => { return message; } ); diff --git a/packages/dependency-extraction-webpack-plugin/lib/util.js b/packages/dependency-extraction-webpack-plugin/lib/util.js index fcc6e5a0ed173..cc99986024476 100644 --- a/packages/dependency-extraction-webpack-plugin/lib/util.js +++ b/packages/dependency-extraction-webpack-plugin/lib/util.js @@ -90,9 +90,10 @@ function defaultRequestToExternalModule( request ) { return `module ${ request }`; } - if ( request === '@wordpress/interactivity-router' ) { - // Assumes this is usually going to be used as a dynamic import. - return `import ${ request }`; + switch ( request ) { + case '@wordpress/interactivity-router': + case '@wordpress/a11y': + return `import ${ request }`; } const isWordPressScript = Boolean( defaultRequestToExternal( request ) );