Skip to content

Commit 03e48fe

Browse files
committed
ContentEditableControl: Keep a disabled field focusable
1 parent a0807d9 commit 03e48fe

8 files changed

Lines changed: 29 additions & 22 deletions

File tree

‎packages/components/CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
- `ResizableBox`: Update `re-resizable` to `6.11.2`, which supports React 18 and renders the top and left handles before the content so focus order matches visual order ([#83754](https://github.com/WordPress/gutenberg/pull/83754)).
3939
- `PaletteEdit`: Keep an item's slug when it is renamed, so blocks and styles that reference the preset keep working ([#83750](https://github.com/WordPress/gutenberg/pull/83750)).
4040
- `FocalPointPicker`: End a drag when the pointer is released outside the control, including over the editor canvas ([#83348](https://github.com/WordPress/gutenberg/pull/83348)).
41+
- `ContentEditableControl`: Keep a disabled field focusable (out of the tab order), so a field disabled while it has focus keeps it instead of dropping focus on the body ([#84083](https://github.com/WordPress/gutenberg/pull/84083)).
4142

4243
### Internal
4344

‎packages/components/src/content-editable-control/README.md‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,8 @@ control in `@wordpress/dataviews` for the canonical assembly).
4141
- Required: No
4242
- Default: `false`
4343

44-
Whether the field is non-editable. A disabled field is not
45-
`contentEditable` (so it is neither focusable nor editable) and exposes
46-
`aria-disabled` to assistive technology.
44+
Whether the field is non-editable. A disabled field exposes
45+
`aria-disabled`, leaves the tab order, and keeps focus if it had it.
4746

4847
### `hideLabelFromVision`
4948

‎packages/components/src/content-editable-control/index.tsx‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,10 @@ function UnforwardedContentEditableControl(
5858
aria-disabled={ disabled || undefined }
5959
aria-required={ required || undefined }
6060
ref={ mergedRefs }
61-
// A disabled field is not `contentEditable`, which also
62-
// removes it from the tab order.
61+
// Not editable and out of the tab order, but still focusable:
62+
// a field disabled while focused keeps focus.
6363
contentEditable={ ! disabled }
64+
tabIndex={ disabled ? -1 : undefined }
6465
suppressContentEditableWarning
6566
{ ...additionalProps }
6667
{ ...controlProps }

‎packages/components/src/content-editable-control/test/index.jsdom.test.tsx‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,21 @@ describe( 'ContentEditableControl', () => {
6767
expect( textbox ).toHaveAttribute( 'contenteditable', 'false' );
6868
} );
6969

70+
it( 'keeps a disabled field focusable but out of the tab order', () => {
71+
const { rerender } = render( <ContentEditableControl label="Note" /> );
72+
73+
expect( screen.getByRole( 'textbox' ) ).not.toHaveAttribute(
74+
'tabindex'
75+
);
76+
77+
rerender( <ContentEditableControl label="Note" disabled /> );
78+
79+
expect( screen.getByRole( 'textbox' ) ).toHaveAttribute(
80+
'tabindex',
81+
'-1'
82+
);
83+
} );
84+
7085
it( 'exposes `required` via aria-required', () => {
7186
render( <ContentEditableControl label="Note" required /> );
7287

‎packages/components/src/content-editable-control/types.ts‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@ export type ContentEditableControlProps = Pick<
99
*/
1010
label: string;
1111
/**
12-
* Whether the field is non-editable. A disabled field is not
13-
* `contentEditable` (so it is neither focusable nor editable) and exposes
14-
* `aria-disabled` to assistive technology.
12+
* Whether the field is non-editable. A disabled field exposes
13+
* `aria-disabled`, leaves the tab order, and keeps focus if it had it.
1514
*
1615
* @default false
1716
*/

‎packages/editor/CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
- Notes: Add an Undo action to the "Note deleted." snackbar, and stop the editor's undo from bringing back the highlight of a deleted note ([#83988](https://github.com/WordPress/gutenberg/pull/83988)).
2222
- Notes: Stop the editor's undo from detaching a newly added note from its block, or bringing back the highlight of a resolved note ([#84079](https://github.com/WordPress/gutenberg/pull/84079)).
2323
- Notes: Delete notes permanently when they can't be moved to the trash, instead of failing with an error ([#84102](https://github.com/WordPress/gutenberg/pull/84102)).
24+
- Notes: Disable the note form field while a note or reply is being sent, instead of accepting text the save would not include ([#84083](https://github.com/WordPress/gutenberg/pull/84083)).
2425

2526
### Internal
2627

‎packages/editor/src/components/collab-sidebar/note-form.jsx‎

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,23 +50,15 @@ export function NoteForm( { onSubmit, onCancel, note, labels } ) {
5050
return;
5151
}
5252
setIsSubmitting( true );
53-
const submitted = inputComment;
5453

5554
/*
5655
* The note actions resolve with the saved record on success and
5756
* `undefined` on failure (they surface their own error notice),
58-
* so only discard the draft once the save actually succeeded.
57+
* so only discard the content once the save actually succeeded.
5958
*/
60-
const result = await onSubmit( submitted );
59+
const result = await onSubmit( inputComment );
6160
if ( result ) {
62-
/*
63-
* The field stays editable while the request is in flight, so
64-
* keep anything typed since; clearing unconditionally would
65-
* discard it.
66-
*/
67-
setInputComment( ( current ) =>
68-
current === submitted ? '' : current
69-
);
61+
setInputComment( '' );
7062
}
7163

7264
setIsSubmitting( false );
@@ -102,6 +94,7 @@ export function NoteForm( { onSubmit, onCancel, note, labels } ) {
10294
hideLabelFromVision
10395
value={ inputComment }
10496
onChange={ setInputComment }
97+
disabled={ isSubmitting }
10598
placeholder={ labels?.placeholder }
10699
allowedFormats={ ALLOWED_NOTE_FORMATS }
107100
completers={ NOTE_COMPLETERS }

‎packages/editor/src/components/collab-sidebar/rich-text-control/index.tsx‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -291,9 +291,7 @@ export default function RichTextControl( {
291291
*/
292292
const enterRef = useRefEffect< HTMLElement >(
293293
( element ) => {
294-
// A disabled field is not editable; nothing to handle. (Real
295-
// keyboard input cannot reach it either, since a
296-
// non-`contentEditable` div is not focusable.)
294+
// A disabled field is not editable; nothing to handle.
297295
if ( disabled ) {
298296
return;
299297
}

0 commit comments

Comments
 (0)