diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index d9e21c2788d9e2..914e9e771ddff1 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -6,6 +6,10 @@ - `ComboboxControl`: Introduce Combobox expandOnFocus prop ([#61705](https://github.com/WordPress/gutenberg/pull/61705)). +### Bug Fixes + +- `Autocomplete`: Stabilize rendering of autocomplete items ([#61877](https://github.com/WordPress/gutenberg/pull/61877)). + ### Internal - Add type support for CSS Custom Properties ([#61872](https://github.com/WordPress/gutenberg/pull/61872)). diff --git a/packages/components/src/autocomplete/autocompleter-ui.tsx b/packages/components/src/autocomplete/autocompleter-ui.tsx index a3e3cb503c483e..a22e197decd154 100644 --- a/packages/components/src/autocomplete/autocompleter-ui.tsx +++ b/packages/components/src/autocomplete/autocompleter-ui.tsx @@ -27,10 +27,57 @@ import { VisuallyHidden } from '../visually-hidden'; import { createPortal } from 'react-dom'; import type { AutocompleterUIProps, KeyedOption, WPCompleter } from './types'; +type ListBoxProps = { + items: KeyedOption[]; + onSelect: ( option: KeyedOption ) => void; + selectedIndex: number; + instanceId: number; + listBoxId: string | undefined; + className?: string; + Component?: React.ElementType; +}; + +function ListBox( { + items, + onSelect, + selectedIndex, + instanceId, + listBoxId, + className, + Component = 'div', +}: ListBoxProps ) { + return ( + + { items.map( ( option, index ) => ( + + ) ) } + + ); +} + export function getAutoCompleterUI( autocompleter: WPCompleter ) { - const useItems = autocompleter.useItems - ? autocompleter.useItems - : getDefaultUseItems( autocompleter ); + const useItems = + autocompleter.useItems ?? getDefaultUseItems( autocompleter ); function AutocompleterUI( { filterValue, @@ -62,7 +109,7 @@ export function getAutoCompleterUI( autocompleter: WPCompleter ) { // If the popover is rendered in a different document than // the content, we need to duplicate the options list in the // content document so that it's available to the screen - // readers, which check the DOM ID based aira-* attributes. + // readers, which check the DOM ID based aria-* attributes. setNeedsA11yCompat( node.ownerDocument !== contentRef.current.ownerDocument ); @@ -124,38 +171,6 @@ export function getAutoCompleterUI( autocompleter: WPCompleter ) { return null; } - const ListBox = ( { - Component = 'div', - }: { - Component?: React.ElementType; - } ) => ( - - { items.map( ( option, index ) => ( - - ) ) } - - ); - return ( <> - + { contentRef.current && needsA11yCompat && createPortal( - , + , contentRef.current.ownerDocument.body ) } diff --git a/test/e2e/specs/editor/various/autocomplete-and-mentions.spec.js b/test/e2e/specs/editor/various/autocomplete-and-mentions.spec.js index aae72f50cd25f1..059f3a73ee13d8 100644 --- a/test/e2e/specs/editor/various/autocomplete-and-mentions.spec.js +++ b/test/e2e/specs/editor/various/autocomplete-and-mentions.spec.js @@ -421,6 +421,43 @@ test.describe( 'Autocomplete (@firefox, @webkit)', () => { } ); } ); + test( `should insert mention in a table block`, async ( { + page, + editor, + } ) => { + // Insert table block. + await editor.insertBlock( { name: 'core/table' } ); + + // Create the table. + await editor.canvas + .locator( 'role=button[name="Create Table"i]' ) + .click(); + + // Select the first cell. + await editor.canvas + .locator( 'role=textbox[name="Body cell text"i] >> nth=0' ) + .click(); + + // Type autocomplete text. + await page.keyboard.type( '@j' ); + + // Verify that option is selected. + const selectedOption = page.getByRole( 'option', { + name: 'Jane Doe', + selected: true, + } ); + await expect( selectedOption ).toBeVisible(); + + // Insert the option. + await selectedOption.click(); + + // Verify it's been inserted. + const snapshot = ` +
@testuser
+`; + await expect.poll( editor.getEditedPostContent ).toBe( snapshot ); + } ); + // The following test concerns an infinite loop regression (https://github.com/WordPress/gutenberg/issues/41709). // When present, the regression will cause this test to time out. test( 'should insert elements from multiple completers in a single block', async ( {