Skip to content

Commit 807cac1

Browse files
authored
Fix button block focus trap after a URL has been added (WordPress#34314)
* Rework button block link UI to match RichText format implementation * Refine some more, determine visibility by selection and url state * Add e2e test * Also focus rich text when unlinking using a keyboard shortcut
1 parent d75d953 commit 807cac1

File tree

2 files changed

+74
-17
lines changed

2 files changed

+74
-17
lines changed

packages/block-library/src/button/edit.js

+44-17
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import classnames from 'classnames';
77
* WordPress dependencies
88
*/
99
import { __ } from '@wordpress/i18n';
10-
import { useCallback, useState, useRef } from '@wordpress/element';
10+
import { useCallback, useEffect, useState, useRef } from '@wordpress/element';
1111
import {
1212
Button,
1313
ButtonGroup,
@@ -73,27 +73,42 @@ function URLPicker( {
7373
opensInNewTab,
7474
onToggleOpenInNewTab,
7575
anchorRef,
76+
richTextRef,
7677
} ) {
77-
const [ isURLPickerOpen, setIsURLPickerOpen ] = useState( false );
78-
const urlIsSet = !! url;
79-
const urlIsSetandSelected = urlIsSet && isSelected;
80-
const openLinkControl = () => {
81-
setIsURLPickerOpen( true );
82-
return false; // prevents default behaviour for event
78+
const [ isEditingURL, setIsEditingURL ] = useState( false );
79+
const isURLSet = !! url;
80+
81+
const startEditing = ( event ) => {
82+
event.preventDefault();
83+
setIsEditingURL( true );
8384
};
84-
const unlinkButton = () => {
85+
86+
const unlink = () => {
8587
setAttributes( {
8688
url: undefined,
8789
linkTarget: undefined,
8890
rel: undefined,
8991
} );
90-
setIsURLPickerOpen( false );
92+
setIsEditingURL( false );
9193
};
92-
const linkControl = ( isURLPickerOpen || urlIsSetandSelected ) && (
94+
95+
useEffect( () => {
96+
if ( ! isSelected ) {
97+
setIsEditingURL( false );
98+
}
99+
}, [ isSelected ] );
100+
101+
const isLinkControlVisible = isSelected && ( isEditingURL || isURLSet );
102+
103+
const linkControl = isLinkControlVisible && (
93104
<Popover
94105
position="bottom center"
95-
onClose={ () => setIsURLPickerOpen( false ) }
106+
onClose={ () => {
107+
setIsEditingURL( false );
108+
richTextRef.current?.focus();
109+
} }
96110
anchorRef={ anchorRef?.current }
111+
focusOnMount={ isEditingURL ? 'firstElement' : false }
97112
>
98113
<LinkControl
99114
className="wp-block-navigation-link__inline-link-input"
@@ -108,28 +123,34 @@ function URLPicker( {
108123
onToggleOpenInNewTab( newOpensInNewTab );
109124
}
110125
} }
126+
onRemove={ () => {
127+
unlink();
128+
richTextRef.current?.focus();
129+
} }
130+
forceIsEditingLink={ isEditingURL }
111131
/>
112132
</Popover>
113133
);
134+
114135
return (
115136
<>
116137
<BlockControls group="block">
117-
{ ! urlIsSet && (
138+
{ ! isURLSet && (
118139
<ToolbarButton
119140
name="link"
120141
icon={ link }
121142
title={ __( 'Link' ) }
122143
shortcut={ displayShortcut.primary( 'k' ) }
123-
onClick={ openLinkControl }
144+
onClick={ startEditing }
124145
/>
125146
) }
126-
{ urlIsSetandSelected && (
147+
{ isURLSet && (
127148
<ToolbarButton
128149
name="link"
129150
icon={ linkOff }
130151
title={ __( 'Unlink' ) }
131152
shortcut={ displayShortcut.primaryShift( 'k' ) }
132-
onClick={ unlinkButton }
153+
onClick={ unlink }
133154
isActive={ true }
134155
/>
135156
) }
@@ -138,8 +159,11 @@ function URLPicker( {
138159
<KeyboardShortcuts
139160
bindGlobal
140161
shortcuts={ {
141-
[ rawShortcut.primary( 'k' ) ]: openLinkControl,
142-
[ rawShortcut.primaryShift( 'k' ) ]: unlinkButton,
162+
[ rawShortcut.primary( 'k' ) ]: startEditing,
163+
[ rawShortcut.primaryShift( 'k' ) ]: () => {
164+
unlink();
165+
richTextRef.current?.focus();
166+
},
143167
} }
144168
/>
145169
) }
@@ -201,6 +225,7 @@ function ButtonEdit( props ) {
201225
const colorProps = useColorProps( attributes );
202226
const spacingProps = useSpacingProps( attributes );
203227
const ref = useRef();
228+
const richTextRef = useRef();
204229
const blockProps = useBlockProps( { ref } );
205230

206231
return (
@@ -213,6 +238,7 @@ function ButtonEdit( props ) {
213238
} ) }
214239
>
215240
<RichText
241+
ref={ richTextRef }
216242
aria-label={ __( 'Button text' ) }
217243
placeholder={ placeholder || __( 'Add text…' ) }
218244
value={ text }
@@ -252,6 +278,7 @@ function ButtonEdit( props ) {
252278
opensInNewTab={ linkTarget === '_blank' }
253279
onToggleOpenInNewTab={ onToggleOpenInNewTab }
254280
anchorRef={ ref }
281+
richTextRef={ richTextRef }
255282
/>
256283
<InspectorControls>
257284
<WidthPanel

packages/e2e-tests/specs/editor/blocks/buttons.test.js

+30
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,36 @@ describe( 'Buttons', () => {
3838
expect( await getEditedPostContent() ).toMatchSnapshot();
3939
} );
4040

41+
it( 'moves focus from the link editor back to the button when escape is pressed after the URL has been submitted', async () => {
42+
// Regression: https://github.com/WordPress/gutenberg/issues/34307
43+
await insertBlock( 'Buttons' );
44+
await pressKeyWithModifier( 'primary', 'k' );
45+
await page.waitForFunction(
46+
() => !! document.activeElement.closest( '.block-editor-url-input' )
47+
);
48+
await page.keyboard.type( 'https://example.com' );
49+
await page.keyboard.press( 'Enter' );
50+
await page.waitForFunction(
51+
() =>
52+
document.activeElement ===
53+
document.querySelector(
54+
'.block-editor-link-control a[href="https://example.com"]'
55+
)
56+
);
57+
await page.keyboard.press( 'Escape' );
58+
59+
// Focus should move from the link control to the button block's text.
60+
await page.waitForFunction(
61+
() =>
62+
document.activeElement ===
63+
document.querySelector( '[aria-label="Button text"]' )
64+
);
65+
66+
// The link control should still be visible when a URL is set.
67+
const linkControl = await page.$( '.block-editor-link-control' );
68+
expect( linkControl ).toBeTruthy();
69+
} );
70+
4171
it( 'can jump to the link editor using the keyboard shortcut', async () => {
4272
await insertBlock( 'Buttons' );
4373
await page.keyboard.type( 'WordPress' );

0 commit comments

Comments
 (0)