Skip to content

Commit 9fa6ea7

Browse files
Merge pull request #1606 from equalizedigital/codex/fix-editor-iframe-styles
feat: add screen reader only format support in block editor and frontend
2 parents f3a0d17 + e5e40e3 commit 9fa6ea7

16 files changed

Lines changed: 1072 additions & 3 deletions

admin/class-admin.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ public function init(): void {
5353
$update_database->init_hooks();
5454

5555
add_action( 'admin_enqueue_scripts', [ 'EDAC\Admin\Enqueue_Admin', 'enqueue' ] );
56+
add_action( 'enqueue_block_editor_assets', [ 'EDAC\Admin\Enqueue_Admin', 'maybe_enqueue_sr_only_format' ] );
57+
add_filter( 'block_editor_settings_all', [ 'EDAC\Admin\Enqueue_Admin', 'maybe_inject_sr_only_editor_styles' ] );
5658
add_action( 'wp_trash_post', [ Purge_Post_Data::class, 'delete_post' ] );
5759
add_action( 'save_post', [ Post_Save::class, 'delete_issue_data_on_post_trashing' ], 10, 3 );
5860
add_filter( 'edac_filter_generate_link_type_ref', [ $this, 'add_ref_param_to_links' ], 5, 1 );
@@ -87,6 +89,27 @@ public function init(): void {
8789
$this->init_ajax();
8890

8991
$this->meta_boxes->init_hooks();
92+
93+
add_filter( 'admin_body_class', [ $this, 'sr_only_admin_body_class' ] );
94+
}
95+
96+
/**
97+
* Adds the sr-only-show-always body class when the user has enabled always-show.
98+
*
99+
* @param string $classes The current admin body classes.
100+
* @return string
101+
*/
102+
public function sr_only_admin_body_class( string $classes ): string {
103+
if ( ! get_user_meta( get_current_user_id(), 'show_sr_text_in_editor', true ) ) {
104+
return $classes;
105+
}
106+
107+
$classes = trim( $classes );
108+
if ( false !== strpos( " {$classes} ", ' sr-only-show-always ' ) ) {
109+
return $classes;
110+
}
111+
112+
return trim( $classes . ' sr-only-show-always' );
90113
}
91114

92115
/**

admin/class-enqueue-admin.php

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,145 @@ public static function maybe_enqueue_email_opt_in_script() {
300300
}
301301

302302

303+
/**
304+
* Enqueue the screen reader only format script and styles for the block editor.
305+
*
306+
* Loads on post edit screens for scannable post types, and also on the
307+
* Full Site Editor (site-editor.php) where there is no post type context.
308+
*
309+
* @return void
310+
*/
311+
public static function maybe_enqueue_sr_only_format(): void {
312+
if ( ! self::should_load_sr_only_format() ) {
313+
return;
314+
}
315+
316+
global $pagenow;
317+
$is_fse = 'site-editor.php' === $pagenow;
318+
319+
wp_enqueue_script(
320+
'edac-sr-only-format',
321+
plugin_dir_url( EDAC_PLUGIN_FILE ) . 'build/srOnlyFormat.bundle.js',
322+
[ 'wp-rich-text', 'wp-block-editor', 'wp-element', 'wp-i18n', 'wp-plugins', 'wp-editor', 'wp-api-fetch' ],
323+
EDAC_VERSION,
324+
false
325+
);
326+
327+
wp_set_script_translations( 'edac-sr-only-format', 'accessibility-checker', plugin_dir_path( EDAC_PLUGIN_FILE ) . 'languages' );
328+
329+
wp_localize_script(
330+
'edac-sr-only-format',
331+
'edacSrOnlyFormat',
332+
[
333+
'showSrTextInEditor' => (bool) get_user_meta( get_current_user_id(), 'show_sr_text_in_editor', true ),
334+
'isFSE' => $is_fse,
335+
]
336+
);
337+
}
338+
339+
/**
340+
* Inject screen reader only format styles into the block editor iframe.
341+
*
342+
* @param array $editor_settings Default editor settings.
343+
* @return array
344+
*/
345+
public static function maybe_inject_sr_only_editor_styles( array $editor_settings ): array {
346+
if ( ! self::should_load_sr_only_format() ) {
347+
return $editor_settings;
348+
}
349+
350+
$css = self::get_sr_only_editor_styles();
351+
if ( '' === $css ) {
352+
return $editor_settings;
353+
}
354+
355+
if ( ! isset( $editor_settings['styles'] ) || ! is_array( $editor_settings['styles'] ) ) {
356+
$editor_settings['styles'] = [];
357+
}
358+
359+
if ( get_user_meta( get_current_user_id(), 'show_sr_text_in_editor', true ) ) {
360+
$body_classes = trim( (string) ( $editor_settings['bodyClassName'] ?? '' ) );
361+
if ( false === strpos( " {$body_classes} ", ' sr-only-show-always ' ) ) {
362+
$editor_settings['bodyClassName'] = trim( $body_classes . ' sr-only-show-always' );
363+
}
364+
}
365+
366+
$editor_settings['styles'][] = [
367+
'css' => $css,
368+
];
369+
370+
return $editor_settings;
371+
}
372+
373+
/**
374+
* Determine whether the screen reader only format assets should load.
375+
*
376+
* Returns true for the post editor on scannable post types, and also
377+
* for the Full Site Editor where there is no post type context.
378+
*
379+
* @return bool
380+
*/
381+
private static function should_load_sr_only_format(): bool {
382+
global $pagenow;
383+
384+
$is_post_editor = 'post.php' === $pagenow || 'post-new.php' === $pagenow;
385+
$is_fse = 'site-editor.php' === $pagenow;
386+
387+
if ( ! $is_post_editor && ! $is_fse ) {
388+
return false;
389+
}
390+
391+
if ( ! Helpers::is_block_editor() ) {
392+
return false;
393+
}
394+
395+
// The FSE has no post type context, so always load there.
396+
if ( $is_fse ) {
397+
return true;
398+
}
399+
400+
$post_types = Settings::get_scannable_post_types();
401+
402+
return Helpers::is_current_post_type_scannable( $post_types );
403+
}
404+
405+
/**
406+
* Build the CSS that should be injected into the block editor iframe.
407+
*
408+
* @return string
409+
*/
410+
private static function get_sr_only_editor_styles(): string {
411+
static $cached_css = null;
412+
413+
if ( null !== $cached_css ) {
414+
return $cached_css;
415+
}
416+
417+
$css_file = plugin_dir_path( EDAC_PLUGIN_FILE ) . 'build/css/srOnlyFormat.css';
418+
if ( ! file_exists( $css_file ) || ! is_readable( $css_file ) ) {
419+
$cached_css = '';
420+
return $cached_css;
421+
}
422+
423+
$css = file_get_contents( $css_file ); // phpcs:ignore WordPressVIPMinimum.Performance.FetchingRemoteData.FileGetContentsUnknown -- Reads a local built CSS asset from the plugin directory.
424+
if ( false === $css ) {
425+
$cached_css = '';
426+
return $cached_css;
427+
}
428+
429+
$css .= sprintf(
430+
'
431+
.is-selected .text-format-sr-only:hover:after,
432+
.is-selected .text-format-sr-only:focus:after {
433+
content: %s;
434+
}',
435+
wp_json_encode( __( '* Screen Reader Text', 'accessibility-checker' ) )
436+
);
437+
438+
$cached_css = $css;
439+
return $cached_css;
440+
}
441+
303442
/**
304443
* Gets the current admin page slug.
305444
*

includes/classes/class-enqueue-frontend.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,29 @@ public function __construct() {
2727
*/
2828
public static function enqueue() {
2929
self::maybe_enqueue_frontend_highlighter();
30+
self::enqueue_sr_only_styles();
31+
}
32+
33+
/**
34+
* Enqueue the screen reader only format styles on the frontend.
35+
*
36+
* Loaded on all frontend pages so that text wrapped in .text-format-sr-only
37+
* is visually hidden for sighted users while remaining accessible to screen readers.
38+
*
39+
* @return void
40+
*/
41+
public static function enqueue_sr_only_styles(): void {
42+
if ( is_admin() ) {
43+
return;
44+
}
45+
46+
wp_enqueue_style(
47+
'edac-sr-only-format',
48+
plugin_dir_url( EDAC_PLUGIN_FILE ) . 'build/css/srOnlyFormat.css',
49+
[],
50+
EDAC_VERSION,
51+
'all'
52+
);
3053
}
3154

3255
/**

includes/classes/class-plugin.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public function __construct() {
4646
$cleanup->init_hooks();
4747

4848
$this->register_fixes_manager();
49+
$this->register_sr_only_meta_hooks();
4950

5051
// When WP CLI is enabled, load the CLI commands.
5152
if ( defined( 'WP_CLI' ) && WP_CLI ) {
@@ -78,6 +79,32 @@ private function init() {
7879
$lazyload_filter->init_hooks();
7980
}
8081

82+
/**
83+
* Register hooks for the screen reader only user meta.
84+
*
85+
* @return void
86+
*/
87+
public function register_sr_only_meta_hooks(): void {
88+
add_action( 'init', [ $this, 'register_sr_only_user_meta' ] );
89+
}
90+
91+
/**
92+
* Register user meta for the screen reader text always-show preference.
93+
*
94+
* @return void
95+
*/
96+
public function register_sr_only_user_meta(): void {
97+
register_meta(
98+
'user',
99+
'show_sr_text_in_editor',
100+
[
101+
'type' => 'boolean',
102+
'single' => true,
103+
'show_in_rest' => true,
104+
]
105+
);
106+
}
107+
81108
/**
82109
* Register the FixesManager.
83110
*
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* Screen Reader Text Format panel.
3+
*/
4+
5+
import { __ } from '@wordpress/i18n';
6+
import { PanelBody, PanelRow } from '@wordpress/components';
7+
import { useSelect, useDispatch } from '@wordpress/data';
8+
import UserMetaCheckboxControl from '../../../srOnlyFormat/components/UserMetaCheckboxControl';
9+
import { STORE_NAME } from '../../store/accessibility-checker-store';
10+
import { renderPanelTitleWithIcon } from '../../utils/panelHelpers';
11+
import '../../sass/components/screen-reader-text-format.scss';
12+
13+
/**
14+
* Panel for screen reader text format settings.
15+
*
16+
* @return {JSX.Element} Sidebar panel.
17+
*/
18+
const ScreenReaderTextFormat = () => {
19+
const panelId = 'screen-reader-text-format';
20+
const isPanelExpanded = useSelect(
21+
( select ) => select( STORE_NAME ).isExpandedPanel( panelId ),
22+
[ panelId ],
23+
);
24+
const { setExpandedPanel } = useDispatch( STORE_NAME );
25+
26+
const handlePanelToggle = () => {
27+
setExpandedPanel( panelId, ! isPanelExpanded );
28+
};
29+
30+
return (
31+
<PanelBody
32+
title={ renderPanelTitleWithIcon(
33+
'info',
34+
__( 'Screen Reader Text Format', 'accessibility-checker' ),
35+
) }
36+
className="edac-panel-body edac-screen-reader-text-format"
37+
initialOpen={ false }
38+
opened={ isPanelExpanded }
39+
onToggle={ handlePanelToggle }
40+
>
41+
<PanelRow className="edac-panel-row">
42+
<div className="edac-panel-section">
43+
<p className="edac-panel-section__message">
44+
{ __( 'Control whether screen reader text stays visible while you edit.', 'accessibility-checker' ) }
45+
</p>
46+
<div className="edac-panel-section__subsection">
47+
<UserMetaCheckboxControl
48+
label={ __( 'Always show screen reader text?', 'accessibility-checker' ) }
49+
metaKey="show_sr_text_in_editor"
50+
/>
51+
</div>
52+
</div>
53+
</PanelRow>
54+
</PanelBody>
55+
);
56+
};
57+
58+
export default ScreenReaderTextFormat;

src/sidebar/components/SidebarContent.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import AccessibilityStatus from './Panels/AccessibilityStatus';
88
import AccessibilityAnalysis from './Panels/AccessibilityAnalysis';
99
import DismissedIssues from './Panels/DismissedIssues';
1010
import ReadabilityAnalysis from './Panels/ReadabilityAnalysis';
11+
import ScreenReaderTextFormat from './Panels/ScreenReaderTextFormat';
1112
import '../sass/components/sidebar-content.scss';
1213

1314
/**
@@ -41,14 +42,19 @@ const SidebarContent = () => {
4142
<div className="edac-sidebar__content">
4243
<AccessibilityStatus />
4344
<AccessibilityAnalysis />
44-
<DismissedIssues />
4545
<ReadabilityAnalysis />
46+
<DismissedIssues />
47+
<ScreenReaderTextFormat />
4648
</div>
4749
);
4850
}
4951

50-
// No data yet and not loading
51-
return null;
52+
// If there is a failure the sr option should still be available to use.
53+
return (
54+
<div className="edac-sidebar__content">
55+
<ScreenReaderTextFormat />
56+
</div>
57+
);
5258
};
5359

5460
export default SidebarContent;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.edac-screen-reader-text-format {
2+
.components-base-control {
3+
width: 100%;
4+
}
5+
6+
.components-base-control__help {
7+
margin-bottom: 0;
8+
}
9+
}

0 commit comments

Comments
 (0)