Skip to content

Commit

Permalink
Interactivity API: Use a11y Script Module in Gutenberg (#65123)
Browse files Browse the repository at this point in the history
Fix an issue where accessibility texts were always "" (empty string) in the
Interactivity Router.

Use the new `@wordpress/a11y` script module for accessibility in the
Interactivity Router.

The change to use the new script module is behind a Gutenberg-only
feature flag.

Pass static localization texts via script module data passing instead of
through the Interactivity API store.

---

Co-authored-by: sirreal <jonsurrell@git.wordpress.org>
Co-authored-by: swissspidy <swissspidy@git.wordpress.org>
Co-authored-by: gziolo <gziolo@git.wordpress.org>
Co-authored-by: michalczaplinski <czapla@git.wordpress.org>
  • Loading branch information
5 people authored Sep 17, 2024
1 parent 6fa0bc1 commit 256fa3f
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 17 deletions.
36 changes: 34 additions & 2 deletions lib/interactivity-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,41 @@ function gutenberg_reregister_interactivity_script_modules() {
wp_register_script_module(
'@wordpress/interactivity-router',
gutenberg_url( '/build-module/interactivity-router/index.min.js' ),
array( '@wordpress/interactivity' ),
array(
array(
'id' => '@wordpress/a11y',
'import' => 'dynamic',
),
'@wordpress/interactivity',
),
$default_version
);
}

add_action( 'init', 'gutenberg_reregister_interactivity_script_modules' );

/**
* Adds script data to the interactivity-router script module.
*
* This filter is registered conditionally anticipating a WordPress Core change to add the script module data.
* The filter runs on 'after_setup_theme' (when Core registers Interactivity and Script Modules hooks)
* to ensure that the conditional registration happens after Core and correctly determine whether
* the filter should be added.
*
* @see https://github.com/WordPress/wordpress-develop/pull/7304
*/
function gutenberg_register_interactivity_script_module_data_hooks() {
if ( ! has_filter( 'script_module_data_@wordpress/interactivity-router', array( wp_interactivity(), 'filter_script_module_interactivity_router_data' ) ) ) {
add_filter(
'script_module_data_@wordpress/interactivity-router',
function ( $data ) {
if ( ! isset( $data['i18n'] ) ) {
$data['i18n'] = array();
}
$data['i18n']['loading'] = __( 'Loading page, please wait.', 'default' );
$data['i18n']['loaded'] = __( 'Page Loaded.', 'default' );
return $data;
}
);
}
}
add_action( 'after_setup_theme', 'gutenberg_register_interactivity_script_module_data_hooks', 20 );
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/interactivity-router/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"types": "build-types",
"wpScriptModuleExports": "./build-module/index.js",
"dependencies": {
"@wordpress/a11y": "file:../a11y",
"@wordpress/interactivity": "file:../interactivity"
},
"publishConfig": {
Expand Down
94 changes: 80 additions & 14 deletions packages/interactivity-router/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,16 +209,37 @@ const isValidEvent = ( event: MouseEvent ) =>
// Variable to store the current navigation.
let navigatingTo = '';

export const { state, actions } = store( 'core/router', {
let hasLoadedNavigationTextsData = false;
const navigationTexts = {
loading: 'Loading page, please wait.',
loaded: 'Page Loaded.',
};

interface Store {
state: {
url: string;
navigation: {
hasStarted: boolean;
hasFinished: boolean;
message: string;
texts?: {
loading?: string;
loaded?: string;
};
};
};
actions: {
navigate: ( href: string, options?: NavigateOptions ) => void;
prefetch: ( url: string, options?: PrefetchOptions ) => void;
};
}

export const { state, actions } = store< Store >( 'core/router', {
state: {
url: window.location.href,
navigation: {
hasStarted: false,
hasFinished: false,
texts: {
loading: '',
loaded: '',
},
message: '',
},
},
Expand Down Expand Up @@ -275,7 +296,7 @@ export const { state, actions } = store( 'core/router', {
navigation.hasFinished = false;
}
if ( screenReaderAnnouncement ) {
navigation.message = navigation.texts.loading;
a11ySpeak( 'loading' );
}
}, 400 );

Expand Down Expand Up @@ -315,14 +336,7 @@ export const { state, actions } = store( 'core/router', {
}

if ( screenReaderAnnouncement ) {
// Announce that the page has been loaded. If the message is the
// same, we use a no-break space similar to the @wordpress/a11y
// package: https://github.com/WordPress/gutenberg/blob/c395242b8e6ee20f8b06c199e4fc2920d7018af1/packages/a11y/src/filter-message.js#L20-L26
navigation.message =
navigation.texts.loaded +
( navigation.message === navigation.texts.loaded
? '\u00A0'
: '' );
a11ySpeak( 'loaded' );
}

// Scroll to the anchor if exits in the link.
Expand Down Expand Up @@ -363,6 +377,58 @@ export const { state, actions } = store( 'core/router', {
},
} );

/**
* Announces a message to screen readers.
*
* This is a wrapper around the `@wordpress/a11y` package's `speak` function. It handles importing
* the package on demand and should be used instead of calling `ally.speak` direacly.
*
* @param messageKey The message to be announced by assistive technologies.
*/
function a11ySpeak( messageKey: keyof typeof navigationTexts ) {
if ( ! hasLoadedNavigationTextsData ) {
hasLoadedNavigationTextsData = true;
const content = document.getElementById(
'wp-script-module-data-@wordpress/interactivity-router'
)?.textContent;
if ( content ) {
try {
const parsed = JSON.parse( content );
if ( typeof parsed?.i18n?.loading === 'string' ) {
navigationTexts.loading = parsed.i18n.loading;
}
if ( typeof parsed?.i18n?.loaded === 'string' ) {
navigationTexts.loaded = parsed.i18n.loaded;
}
} catch {}
} else {
// Fallback to localized strings from Interactivity API state.
if ( state.navigation.texts?.loading ) {
navigationTexts.loading = state.navigation.texts.loading;
}
if ( state.navigation.texts?.loaded ) {
navigationTexts.loaded = state.navigation.texts.loaded;
}
}
}

const message = navigationTexts[ messageKey ];

if ( globalThis.IS_GUTENBERG_PLUGIN ) {
import( '@wordpress/a11y' ).then(
( { speak } ) => speak( message ),
// Ignore failures to load the a11y module.
() => {}
);
} else {
state.navigation.message =
// Announce that the page has been loaded. If the message is the
// same, we use a no-break space similar to the @wordpress/a11y
// package: https://github.com/WordPress/gutenberg/blob/c395242b8e6ee20f8b06c199e4fc2920d7018af1/packages/a11y/src/filter-message.js#L20-L26
message + ( state.navigation.message === message ? '\u00A0' : '' );
}
}

// Add click and prefetch to all links.
if ( globalThis.IS_GUTENBERG_PLUGIN ) {
if ( navigationMode === 'fullPage' ) {
Expand Down
2 changes: 1 addition & 1 deletion packages/interactivity-router/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
"checkJs": false,
"strict": false
},
"references": [ { "path": "../interactivity" } ],
"references": [ { "path": "../a11y" }, { "path": "../interactivity" } ],
"include": [ "src/**/*" ]
}

1 comment on commit 256fa3f

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flaky tests detected in 256fa3f.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/10906314489
📝 Reported issues:

Please sign in to comment.