Skip to content

Commit

Permalink
Image: Improve focus management in lightbox (#55428)
Browse files Browse the repository at this point in the history
* Improve focus management

This commit removes the logic to set focus differently
based on event.pointerType and instead sets focus on the
dialog itself when the lightbox opens, and on the lightbox
trigger when the lightbox closes.

* Add delay before focusing when closing lightbox

* Put focus back on close button when opening lightbox

It turns out that placing focus on the modal was causing
inconsistent behavior in Safari, so I've put the focus back
on the close button when the lightbox opens, which
performs predictably.

I've also added a tabindex to the image, which prevents
the focus ring from erroneously showing when opening the lightbox
with a mouse in Chrome and Firefox.

* Move focus to the dialog when opening the lightbox.

* Fix SVG markup.

* Consistent indentation with spaces.

* Remove unnecessary tabindex

---------

Co-authored-by: Andrea Fercia <[email protected]>
  • Loading branch information
artemiomorales and afercia authored Oct 20, 2023
1 parent 145ee70 commit fd8bdfe
Show file tree
Hide file tree
Showing 2 changed files with 17 additions and 21 deletions.
6 changes: 4 additions & 2 deletions packages/block-library/src/image/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ function block_core_image_render_lightbox( $block_content, $block ) {
$button =
$img[0]
. '<button
class="lightbox-trigger"
type="button"
aria-haspopup="dialog"
aria-label="' . esc_attr( $aria_label ) . '"
Expand All @@ -243,7 +244,7 @@ function block_core_image_render_lightbox( $block_content, $block ) {
data-wp-style--top="context.core.image.imageButtonTop"
style="background: #000"
>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#FFFFFF">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false">
<Path stroke="#FFFFFF" d="M6 4a2 2 0 0 0-2 2v3h1.5V6a.5.5 0 0 1 .5-.5h3V4H6Zm3 14.5H6a.5.5 0 0 1-.5-.5v-3H4v3a2 2 0 0 0 2 2h3v-1.5Zm6 1.5v-1.5h3a.5.5 0 0 0 .5-.5v-3H20v3a2 2 0 0 1-2 2h-3Zm3-16a2 2 0 0 1 2 2v3h-1.5V6a.5.5 0 0 0-.5-.5h-3V4h3Z" />
</svg>
</button>';
Expand Down Expand Up @@ -319,12 +320,13 @@ function block_core_image_render_lightbox( $block_content, $block ) {
data-wp-on--touchmove="actions.core.image.handleTouchMove"
data-wp-on--touchend="actions.core.image.handleTouchEnd"
data-wp-on--click="actions.core.image.hideLightbox"
tabindex="-1"
>
<button type="button" aria-label="$close_button_label" style="fill: $close_button_color" class="close-button" data-wp-on--click="actions.core.image.hideLightbox">
$close_button_icon
</button>
<div class="lightbox-image-container">$initial_image_content</div>
<div class="lightbox-image-container">$enlarged_image_content</div>
<div class="lightbox-image-container">$enlarged_image_content</div>
<div class="scrim" style="background-color: $background_color" aria-hidden="true"></div>
</div>
HTML;
Expand Down
32 changes: 13 additions & 19 deletions packages/block-library/src/image/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ store(
false
);
},
hideLightbox: async ( { context, event } ) => {
hideLightbox: async ( { context } ) => {
context.core.image.hideAnimationEnabled = true;
if ( context.core.image.lightboxEnabled ) {
// We want to wait until the close animation is completed
Expand All @@ -149,19 +149,15 @@ store(
'scroll',
scrollCallback
);
// If we don't delay before changing the focus,
// the focus ring will appear on Firefox before
// the image has finished animating, which looks broken.
context.core.image.lightboxTriggerRef.focus( {
preventScroll: true,
} );
}, 450 );

context.core.image.lightboxEnabled = false;

// We want to avoid drawing attention to the button
// after the lightbox closes for mouse and touch users.
// Note that the `event.pointerType` property returns
// as an empty string if a keyboard fired the event.
if ( event.pointerType === '' ) {
context.core.image.lastFocusedElement.focus( {
preventScroll: true,
} );
}
}
},
handleKeydown: ( { context, actions, event } ) => {
Expand Down Expand Up @@ -266,6 +262,10 @@ store(
image: {
initOriginImage: ( { context, ref } ) => {
context.core.image.imageRef = ref;
context.core.image.lightboxTriggerRef =
ref.parentElement.querySelector(
'.lightbox-trigger'
);
if ( ref.complete ) {
context.core.image.imageLoaded = true;
context.core.image.imageCurrentSrc = ref.currentSrc;
Expand All @@ -282,14 +282,8 @@ store(
focusableElements.length - 1
];

// We want to avoid drawing unnecessary attention to the close
// button for mouse and touch users. Note that even if opening
// the lightbox via keyboard, the event fired is of type
// `pointerEvent`, so we need to rely on the `event.pointerType`
// property, which returns an empty string for keyboard events.
if ( context.core.image.pointerType === '' ) {
ref.querySelector( '.close-button' ).focus();
}
// Move focus to the dialog when opening it.
ref.focus();
}
},
setButtonStyles: ( { context, ref } ) => {
Expand Down

1 comment on commit fd8bdfe

@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 fd8bdfe.
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/6590046802
📝 Reported issues:

Please sign in to comment.