From 7b7196f06b6bbfc2d138fb313b9c1d84e2a5f066 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Tue, 3 Sep 2024 15:53:24 +0200 Subject: [PATCH] Dataviews Filter search widget: do not use Composite store (#64985) * Dataviews Filter search widget: do not use Composite store * Use internal CompositeHover and CompositeTypeahead version * Better comment * Refactor generateCompositeItemId arguments * Export Composite.Typeahead and Composite.Hover as private APIs * CHANGELOG --- Co-authored-by: ciampo Co-authored-by: tyxla --- packages/components/CHANGELOG.md | 1 + packages/components/src/private-apis.ts | 2 + .../dataviews-filters/search-widget.tsx | 60 ++++++++++++++----- 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 3b8d45be5861b..5a58ddfda38b9 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -66,6 +66,7 @@ ### Internal +- `DropdownMenu` v2: expose CompositeTypeaheadV2 and CompositeHoverV2 via private APIs ([#64985](https://github.com/WordPress/gutenberg/pull/64985)). - `DropdownMenu` v2: fix flashing menu item styles when using keyboard ([#64873](https://github.com/WordPress/gutenberg/pull/64873), [#64942](https://github.com/WordPress/gutenberg/pull/64942)). - `DropdownMenu` v2: refactor to overloaded naming convention ([#64654](https://github.com/WordPress/gutenberg/pull/64654)). - `DropdownMenu` v2: add `GroupLabel` subcomponent ([#64854](https://github.com/WordPress/gutenberg/pull/64854)). diff --git a/packages/components/src/private-apis.ts b/packages/components/src/private-apis.ts index fa9fece048617..7bcab0e052e02 100644 --- a/packages/components/src/private-apis.ts +++ b/packages/components/src/private-apis.ts @@ -18,6 +18,8 @@ lock( privateApis, { CompositeGroupV2: Composite.Group, CompositeItemV2: Composite.Item, CompositeRowV2: Composite.Row, + CompositeTypeaheadV2: Composite.Typeahead, + CompositeHoverV2: Composite.Hover, useCompositeStoreV2: useCompositeStore, __experimentalPopoverLegacyPositionToPlacement, createPrivateSlotFill, diff --git a/packages/dataviews/src/components/dataviews-filters/search-widget.tsx b/packages/dataviews/src/components/dataviews-filters/search-widget.tsx index 1b369222b8f28..24ef3b5594b41 100644 --- a/packages/dataviews/src/components/dataviews-filters/search-widget.tsx +++ b/packages/dataviews/src/components/dataviews-filters/search-widget.tsx @@ -8,6 +8,7 @@ import removeAccents from 'remove-accents'; /** * WordPress dependencies */ +import { useInstanceId } from '@wordpress/compose'; import { __, sprintf } from '@wordpress/i18n'; import { useState, useMemo, useDeferredValue } from '@wordpress/element'; import { @@ -27,7 +28,8 @@ import type { Filter, NormalizedFilter, View } from '../../types'; const { CompositeV2: Composite, CompositeItemV2: CompositeItem, - useCompositeStoreV2: useCompositeStore, + CompositeHoverV2: CompositeHover, + CompositeTypeaheadV2: CompositeTypeahead, } = unlock( componentsPrivateApis ); interface SearchWidgetProps { @@ -84,22 +86,37 @@ const getNewValue = ( return [ value ]; }; +function generateFilterElementCompositeItemId( + prefix: string, + filterElementValue: string +) { + return `${ prefix }-${ filterElementValue }`; +} + function ListBox( { view, filter, onChangeView }: SearchWidgetProps ) { - const compositeStore = useCompositeStore( { - virtualFocus: true, - focusLoop: true, - // When we have no or just one operator, we can set the first item as active. - // We do that by passing `undefined` to `defaultActiveId`. Otherwise, we set it to `null`, - // so the first item is not selected, since the focus is on the operators control. - defaultActiveId: filter.operators?.length === 1 ? undefined : null, - } ); + const baseId = useInstanceId( ListBox, 'dataviews-filter-list-box' ); + + const [ activeCompositeId, setActiveCompositeId ] = useState< + string | null | undefined + >( + // When there are one or less operators, the first item is set as active + // (by setting the initial `activeId` to `undefined`). + // With 2 or more operators, the focus is moved on the operators control + // (by setting the initial `activeId` to `null`), meaning that there won't + // be an active item initially. Focus is then managed via the + // `onFocusVisible` callback. + filter.operators?.length === 1 ? undefined : null + ); const currentFilter = view.filters?.find( ( f ) => f.field === filter.field ); const currentValue = getCurrentValue( filter, currentFilter ); return ( { - if ( ! compositeStore.getState().activeId ) { - compositeStore.move( compositeStore.first() ); + // `onFocusVisible` needs the `Composite` component to be focusable, + // which is implicitly achieved via the `virtualFocus: true` option + // in the `useCompositeStore` hook. + if ( ! activeCompositeId && filter.elements.length ) { + setActiveCompositeId( + generateFilterElementCompositeItemId( + baseId, + filter.elements[ 0 ].value + ) + ); } } } - render={ } + render={ } > { filter.elements.map( ( element ) => ( - { element.label } - + ) ) } );