Skip to content

Background Image Panel: fix focus loss #69813

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

Mayank-Tripathi32
Copy link
Contributor

What?

Closes #69745

Fixes focus management in the Background Image Control component by improving ref handling and focus timing.

Why?

The focus was not being properly maintained after image operations in the Background Image Control because:

  1. The ref was being created but not properly attached to the parent container as it unmounted.
  2. Focus timing issues occurred during DOM updates and state changes
  3. The component structure made it difficult to reliably find and focus the toggle button

How?

  • Moved the containerRef attachment to the parent container div in BackgroundImagePanel
  • Ensured proper ref propagation through component hierarchy
  • Modified the closeAndFocus function to use requestAnimationFrame for reliable timing
  • Removed the immediate click operation after focus (as that didn't feel consistent with other settings)
  • Added proper null checks for the container ref
  • Added closeAndFocus handler call to onSelectMedia flow to ensure that focus returns correctly.

The architectural change of moving the ref to the parent container provides a more reliable way to find focusable elements within the component's DOM structure.

Testing Instructions (keyboard)

  1. Open any post, add "group block"
  2. Open the Inspector and navigate to "background image"
  3. Click "Add background image" and select an image, complete the flow for adding image
  4. Verify that focus returns to the toggle button (preview button) after image selection
  5. Click the toggle button to open the dropdown
  6. Click "Reset" via modal
  7. Verify that focus returns to the toggle button
  8. Repeat steps 4-8 multiple times to ensure consistent behavior

The focus should consistently return to the toggle button after any image operation (select/reset/remove) and the dropdown should close properly.

ScreenCast

https://q7utzrengv.ufs.sh/f/Wgl9eBAmTj29aD9Oh8fFRjF31d07GtqsWNpK4IXH6Z9OnbyQ

…d focus handling

- Added containerRef to BackgroundImageControls and BackgroundImagePanel for better focus management.
- Updated closeAndFocus function to utilize requestAnimationFrame for ensuring DOM updates are complete before focusing the toggle button.
- Removed unused replaceContainerRef as we couldn't focus due to unmounted state.
@Mayank-Tripathi32 Mayank-Tripathi32 marked this pull request as ready for review April 3, 2025 22:04
Copy link

github-actions bot commented Apr 3, 2025

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: Mayank-Tripathi32 <mayanktripathi32@git.wordpress.org>
Co-authored-by: Mamaduka <mamaduka@git.wordpress.org>
Co-authored-by: t-hamano <wildworks@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@Mamaduka Mamaduka added [Type] Enhancement A suggestion for improvement. [Focus] Accessibility (a11y) Changes that impact accessibility and need corresponding review (e.g. markup changes). Global Styles Anything related to the broader Global Styles efforts, including Styles Engine and theme.json labels Apr 4, 2025
Copy link
Member

@Mamaduka Mamaduka left a comment

Choose a reason for hiding this comment

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

I think there are a couple of other tricks we might use, which, in theory, should reduce the complexity of this component.

  1. Refactor/re-arrange components so that the Media modal returns focus "naturally." It is usually my preferred method. Example: #68621.
  2. Use flag and ref callback. Example: #67236.

Comment on lines +347 to +348
// Use requestAnimationFrame to ensure DOM updates are complete
window.requestAnimationFrame( () => {
Copy link
Member

Choose a reason for hiding this comment

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

In my experience, rAFs or setTimeout tasks aren't entirely reliable for focus management.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will check out the other implementations in that case, should we also look into updating existing uses of rAFs in that case?

@t-hamano
Copy link
Contributor

t-hamano commented Apr 7, 2025

Thanks for the PR!

This problem might be more difficult than I thought.

I was wondering why this block needs special focus handling because the MediaRplaceFlow component should automatically return focus to the proper element.

From what I've researched, different components are rendered depending on whether media is set or not. If media is not set, the MediaReplaceFlow component is rendered. If media is set, a Dropdown component is rendered and the MediaReplaceFlow component is rendered in a popover.

Although they look the same button at first glance, different components are rendered, so I think special focus handling is required.

Considering that, we may not be able to use the same approach as the site logo.

Is my understanding correct?

@Mayank-Tripathi32
Copy link
Contributor Author

Although they look the same button at first glance, different components are rendered, so I think special focus handling is required.

Considering that, we may not be able to use the same approach as the site logo.

Yes, its using different buttons, So while the component should return focus but it cannot as original button might be unmounted.

We can probably refactor the component.

		{ shouldShowBackgroundImageControls ? (
				<BackgroundControlsPanel
					label={ title }
					filename={ title }
					url={ url }
					onToggle={ setIsDropDownOpen }
					hasImageValue={ hasImageValue }
				>
					<VStack spacing={ 3 } className="single-column">
						<BackgroundImageControls
							onChange={ onChange }
							style={ value }
							inheritedValue={ resolvedInheritedValue }
							displayInPanel
							onResetImage={ () => {
								setIsDropDownOpen( false );
								resetBackground();
							} }
							onRemoveImage={ () => setIsDropDownOpen( false ) }
							defaultValues={ defaultValues }
							containerRef={ containerRef }
						/>
						<BackgroundSizeControls
							onChange={ onChange }
							style={ value }
							defaultValues={ defaultValues }
							inheritedValue={ resolvedInheritedValue }
						/>
					</VStack>
				</BackgroundControlsPanel>
			) : (
				<BackgroundImageControls
					onChange={ onChange }
					style={ value }
					inheritedValue={ resolvedInheritedValue }
					defaultValues={ defaultValues }
					onResetImage={ () => {
						setIsDropDownOpen( false );
						resetBackground();
					} }
					onRemoveImage={ () => setIsDropDownOpen( false ) }
					containerRef={ containerRef }
				/>
			) }

I think following code might be causing it.

@Mayank-Tripathi32
Copy link
Contributor Author

@t-hamano Should I go ahead with changing the structure a little bit so that focus returns automatically?

@t-hamano
Copy link
Contributor

Should I go ahead with changing the structure a little bit so that focus returns automatically?

Yes, as long as there are no visual changes, I think refactoring to achieve our purpose is acceptable.

@Mayank-Tripathi32
Copy link
Contributor Author

Should I go ahead with changing the structure a little bit so that focus returns automatically?

Yes, as long as there are no visual changes, I think refactoring to achieve our purpose is acceptable.

I tried to refactor the component to remove then "unmounting" of toggle button but it wont look the same as internally its using two different popovers.
image
or we can refactor "MediaReplaceFlow" to use Dropdown component as well.


Also,

image

This should be using dropdown component too to match other components where it opens on the side?
image

@t-hamano
Copy link
Contributor

t-hamano commented May 4, 2025

@Mayank-Tripathi32 Thanks for investigating!

Yes, as long as there are no visual changes, I think refactoring to achieve our purpose is acceptable.

I have tried to find an ideal approach, but I haven't found it yet 😅 For now, I can think of three approaches:

The first approach: Don't unmount the components, but visually hide the components that shouldn't be displayed with visibility: hidden. That way, we won't lose the ref and we should be able to focus on the button when the image is uploaded or reset.

The second approach: create a separate toggle button for the popover, which may not be acceptable since it introduces a visual change. Two examples:

image

image

The third approach: As already tried, use window.requestAnimationFrame. This seems like it might be unreliable for focus management, but as far as I've tested, this approach seems to work stably.

@Mamaduka @Mayank-Tripathi32

I think it might be a good idea to try the third approach, what do you think?

@Mamaduka
Copy link
Member

Mamaduka commented May 5, 2025

Agreed. Let's try the third approach for now.

Copy link
Contributor

@t-hamano t-hamano left a comment

Choose a reason for hiding this comment

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

This PR works fine for me.

@Mamaduka Are there any further improvements regarding this approach using requestAnimationFrame ?

@t-hamano
Copy link
Contributor

Let's go ahead with this PR for now, and if the rAF approach is unstable, consider a different approach in follow-up.

@t-hamano t-hamano merged commit ef45497 into WordPress:trunk May 21, 2025
70 of 73 checks passed
@github-actions github-actions bot added this to the Gutenberg 20.9 milestone May 21, 2025
@Mamaduka
Copy link
Member

Sorry, somehow I missed the latest pings. No further feedback on rAF, besides the initial comment.

I think this is a good hotfix. When I have time, I might look into refactoring the whole component, but it's not a priority at the moment.

chriszarate pushed a commit to chriszarate/gutenberg that referenced this pull request Jul 1, 2025
…d focus handling (WordPress#69813)

- Added containerRef to BackgroundImageControls and BackgroundImagePanel for better focus management.
- Updated closeAndFocus function to utilize requestAnimationFrame for ensuring DOM updates are complete before focusing the toggle button.
- Removed unused replaceContainerRef as we couldn't focus due to unmounted state.

Co-authored-by: Mayank-Tripathi32 <mayanktripathi32@git.wordpress.org>
Co-authored-by: Mamaduka <mamaduka@git.wordpress.org>
Co-authored-by: t-hamano <wildworks@git.wordpress.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Focus] Accessibility (a11y) Changes that impact accessibility and need corresponding review (e.g. markup changes). Global Styles Anything related to the broader Global Styles efforts, including Styles Engine and theme.json [Type] Enhancement A suggestion for improvement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Background Image Panel: fix focus loss
3 participants