From 7ebe31efcf27ffa98e5a1be1b0d0c23e375cc710 Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Wed, 22 May 2024 19:58:49 +0200 Subject: [PATCH 1/4] getAutocompleterUI: don't redefine ListBox component on every render --- .../src/autocomplete/autocompleter-ui.tsx | 99 ++++++++++++------- 1 file changed, 65 insertions(+), 34 deletions(-) diff --git a/packages/components/src/autocomplete/autocompleter-ui.tsx b/packages/components/src/autocomplete/autocompleter-ui.tsx index a3e3cb503c483e..e4d6efeec9415a 100644 --- a/packages/components/src/autocomplete/autocompleter-ui.tsx +++ b/packages/components/src/autocomplete/autocompleter-ui.tsx @@ -27,11 +27,59 @@ 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; +}; + export function getAutoCompleterUI( autocompleter: WPCompleter ) { const useItems = autocompleter.useItems ? autocompleter.useItems : getDefaultUseItems( autocompleter ); + function ListBox( { + items, + onSelect, + selectedIndex, + instanceId, + listBoxId, + className, + Component = 'div', + }: ListBoxProps ) { + return ( + + { items.map( ( option, index ) => ( + + ) ) } + + ); + } + function AutocompleterUI( { filterValue, instanceId, @@ -124,38 +172,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 ) } From dc3334d4e65fe641abc2c94de4ca68bd2a4e1e0e Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Thu, 23 May 2024 07:56:17 +0200 Subject: [PATCH 2/4] Move ListBox to top level --- .../src/autocomplete/autocompleter-ui.tsx | 83 +++++++++---------- 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/packages/components/src/autocomplete/autocompleter-ui.tsx b/packages/components/src/autocomplete/autocompleter-ui.tsx index e4d6efeec9415a..a22e197decd154 100644 --- a/packages/components/src/autocomplete/autocompleter-ui.tsx +++ b/packages/components/src/autocomplete/autocompleter-ui.tsx @@ -37,48 +37,47 @@ type ListBoxProps = { Component?: React.ElementType; }; -export function getAutoCompleterUI( autocompleter: WPCompleter ) { - const useItems = autocompleter.useItems - ? autocompleter.useItems - : getDefaultUseItems( autocompleter ); +function ListBox( { + items, + onSelect, + selectedIndex, + instanceId, + listBoxId, + className, + Component = 'div', +}: ListBoxProps ) { + return ( + + { items.map( ( option, index ) => ( + + ) ) } + + ); +} - 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 ?? getDefaultUseItems( autocompleter ); function AutocompleterUI( { filterValue, @@ -110,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 ); From 61284bdb238e030b93bb5ef54c56d17cd603e0e9 Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Thu, 23 May 2024 08:11:56 +0200 Subject: [PATCH 3/4] Add components changelog entry --- packages/components/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) 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)). From d166614af25b5bed1e1356e0d85f69bc5c8fa752 Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Thu, 23 May 2024 08:39:01 +0200 Subject: [PATCH 4/4] Add e2e test for autocomplete inside table --- .../various/autocomplete-and-mentions.spec.js | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) 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 ( {