diff --git a/packages/js/components/changelog/tweak-select-tree-2 b/packages/js/components/changelog/tweak-select-tree-2
new file mode 100644
index 00000000000..ef4df972846
--- /dev/null
+++ b/packages/js/components/changelog/tweak-select-tree-2
@@ -0,0 +1,4 @@
+Significance: minor
+Type: update
+
+Use inline popover for select tree and allow selecting items through ENTER or comma
diff --git a/packages/js/components/src/experimental-select-tree-control/select-tree-menu.tsx b/packages/js/components/src/experimental-select-tree-control/select-tree-menu.tsx
index c4ec3f8b03e..1b7267389b4 100644
--- a/packages/js/components/src/experimental-select-tree-control/select-tree-menu.tsx
+++ b/packages/js/components/src/experimental-select-tree-control/select-tree-menu.tsx
@@ -7,7 +7,6 @@ import {
createElement,
useEffect,
useRef,
- createPortal,
useLayoutEffect,
useState,
} from '@wordpress/element';
@@ -44,6 +43,7 @@ export const SelectTreeMenu = ( {
onClose = () => {},
onEscape,
shouldShowCreateButton,
+ onFirstItemLoop,
...props
}: MenuProps ) => {
const [ boundingRect, setBoundingRect ] = useState< DOMRect >();
@@ -93,9 +93,9 @@ export const SelectTreeMenu = ( {
>
- createPortal(
-
- { /* @ts-expect-error name does exist on PopoverSlot see: https://github.com/WordPress/gutenberg/blob/trunk/packages/components/src/popover/index.tsx#L555 */ }
-
-
,
- document.body
- );
diff --git a/packages/js/components/src/experimental-select-tree-control/select-tree.tsx b/packages/js/components/src/experimental-select-tree-control/select-tree.tsx
index 53acfe8a634..c23a64d0ffd 100644
--- a/packages/js/components/src/experimental-select-tree-control/select-tree.tsx
+++ b/packages/js/components/src/experimental-select-tree-control/select-tree.tsx
@@ -3,10 +3,17 @@
*/
import { chevronDown, chevronUp, closeSmall } from '@wordpress/icons';
import classNames from 'classnames';
-import { createElement, useEffect, useState } from '@wordpress/element';
+import {
+ createElement,
+ useEffect,
+ useState,
+ Fragment,
+} from '@wordpress/element';
import { useInstanceId } from '@wordpress/compose';
import { BaseControl, Button, TextControl } from '@wordpress/components';
import { decodeEntities } from '@wordpress/html-entities';
+import { __ } from '@wordpress/i18n';
+import { speak } from '@wordpress/a11y';
/**
* Internal dependencies
@@ -26,6 +33,7 @@ interface SelectTreeProps extends TreeControlProps {
isLoading?: boolean;
disabled?: boolean;
label: string | JSX.Element;
+ help?: string | JSX.Element;
onInputChange?: ( value: string | undefined ) => void;
initialInputValue?: string | undefined;
isClearingAllowed?: boolean;
@@ -40,6 +48,7 @@ export const SelectTree = function SelectTree( {
initialInputValue,
onInputChange,
shouldShowCreateButton,
+ help = __( 'Separate with commas or the Enter key.', 'woocommerce' ),
isClearingAllowed = false,
onClear = () => {},
...props
@@ -110,6 +119,14 @@ export const SelectTree = function SelectTree( {
autoComplete: 'off',
disabled,
onFocus: ( event ) => {
+ if ( props.multiple ) {
+ speak(
+ __(
+ 'To select existing items, type its exact label and separate with commas or the Enter key.',
+ 'woocommerce'
+ )
+ );
+ }
if ( ! isOpen ) {
setIsOpen( true );
}
@@ -145,6 +162,24 @@ export const SelectTree = function SelectTree( {
setIsOpen( false );
recalculateInputValue();
}
+ if ( event.key === ',' || event.key === 'Enter' ) {
+ event.preventDefault();
+ const item = items.find(
+ ( i ) => i.label === escapeHTML( inputValue )
+ );
+ const isAlreadySelected =
+ Array.isArray( props.selected ) &&
+ Boolean(
+ props.selected.find(
+ ( i ) => i.label === escapeHTML( inputValue )
+ )
+ );
+ if ( props.onSelect && item && ! isAlreadySelected ) {
+ props.onSelect( item );
+ setInputValue( '' );
+ recalculateInputValue();
+ }
+ }
},
onChange: ( event ) => {
if ( onInputChange ) {
@@ -181,100 +216,111 @@ export const SelectTree = function SelectTree( {
}
) }
>
-
- { props.multiple ? (
-
- { isClearingAllowed && isOpen && (
-
-
-
- ) }
-
+ <>
+ { props.multiple ? (
+
+ { isClearingAllowed && isOpen && (
+
+
+
+ ) }
+
+
+ }
+ >
+
+ item?.label || ''
+ }
+ getItemValue={ ( item ) =>
+ item?.value || ''
+ }
+ onRemove={ ( item ) => {
+ if (
+ ! Array.isArray( item ) &&
+ props.onRemove
+ ) {
+ props.onRemove( item );
}
- />
-
- }
- >
- item?.label || '' }
- getItemValue={ ( item ) => item?.value || '' }
- onRemove={ ( item ) => {
- if (
- ! Array.isArray( item ) &&
- props.onRemove
- ) {
- props.onRemove( item );
+ } }
+ getSelectedItemProps={ () => ( {} ) }
+ />
+
+ ) : (
+ {
+ if ( onInputChange ) onInputChange( value );
+ const item = items.find(
+ ( i ) => i.label === escapeHTML( value )
+ );
+ if ( props.onSelect && item ) {
+ props.onSelect( item );
+ }
+ if ( ! value && props.onRemove ) {
+ props.onRemove(
+ props.selected as Item
+ );
}
} }
- getSelectedItemProps={ () => ( {} ) }
/>
-
- ) : (
- {
- if ( onInputChange ) onInputChange( value );
- const item = items.find(
- ( i ) => i.label === escapeHTML( value )
- );
- if ( props.onSelect && item ) {
+ ) }
+ {
+ if ( ! props.multiple && onInputChange ) {
+ onInputChange( ( item as Item ).label );
+ setIsOpen( false );
+ setIsFocused( false );
+ focusOnInput();
+ }
+ if ( props.onSelect ) {
props.onSelect( item );
}
- if ( ! value && props.onRemove ) {
- props.onRemove( props.selected as Item );
- }
} }
+ id={ menuInstanceId }
+ ref={ ref }
+ isEventOutside={ isEventOutside }
+ isLoading={ isLoading }
+ isOpen={ isOpen }
+ items={ linkedTree }
+ shouldShowCreateButton={ shouldShowCreateButton }
+ onClose={ () => {
+ setIsOpen( false );
+ } }
+ onFirstItemLoop={ focusOnInput }
/>
- ) }
+ >
- {
- if ( ! props.multiple && onInputChange ) {
- onInputChange( ( item as Item ).label );
- setIsOpen( false );
- setIsFocused( false );
- focusOnInput();
- }
- if ( props.onSelect ) {
- props.onSelect( item );
- }
- } }
- id={ menuInstanceId }
- ref={ ref }
- isEventOutside={ isEventOutside }
- isLoading={ isLoading }
- isOpen={ isOpen }
- items={ linkedTree }
- shouldShowCreateButton={ shouldShowCreateButton }
- onEscape={ () => {
- focusOnInput();
- setIsOpen( false );
- } }
- onClose={ () => {
- setIsOpen( false );
- } }
- />
);
};
diff --git a/packages/js/components/src/experimental-tree-control/hooks/use-keyboard.ts b/packages/js/components/src/experimental-tree-control/hooks/use-keyboard.ts
index 54db7555200..a4f539f2b41 100644
--- a/packages/js/components/src/experimental-tree-control/hooks/use-keyboard.ts
+++ b/packages/js/components/src/experimental-tree-control/hooks/use-keyboard.ts
@@ -111,6 +111,7 @@ export function useKeyboard( {
onCollapse,
onToggleExpand,
onLastItemLoop,
+ onFirstItemLoop,
}: {
item: LinkedTree;
isExpanded: boolean;
@@ -118,6 +119,7 @@ export function useKeyboard( {
onCollapse(): void;
onToggleExpand(): void;
onLastItemLoop?( event: React.KeyboardEvent< HTMLDivElement > ): void;
+ onFirstItemLoop?( event: React.KeyboardEvent< HTMLDivElement > ): void;
} ) {
function onKeyDown( event: React.KeyboardEvent< HTMLDivElement > ) {
if ( event.code === 'ArrowRight' ) {
@@ -159,6 +161,9 @@ export function useKeyboard( {
if ( event.code === 'ArrowDown' && ! element && onLastItemLoop ) {
onLastItemLoop( event );
}
+ if ( event.code === 'ArrowUp' && ! element && onFirstItemLoop ) {
+ onFirstItemLoop( event );
+ }
}
if ( event.code === 'Home' ) {
@@ -174,5 +179,5 @@ export function useKeyboard( {
}
}
- return { onKeyDown, onLastItemLoop };
+ return { onKeyDown };
}
diff --git a/packages/js/components/src/experimental-tree-control/hooks/use-tree-item.ts b/packages/js/components/src/experimental-tree-control/hooks/use-tree-item.ts
index 35a08d19042..9d90fe406bb 100644
--- a/packages/js/components/src/experimental-tree-control/hooks/use-tree-item.ts
+++ b/packages/js/components/src/experimental-tree-control/hooks/use-tree-item.ts
@@ -29,6 +29,7 @@ export function useTreeItem( {
onCreateNew,
shouldShowCreateButton,
onLastItemLoop,
+ onFirstItemLoop,
onTreeBlur,
...props
}: TreeItemProps ) {
@@ -64,6 +65,7 @@ export function useTreeItem( {
const { onKeyDown } = useKeyboard( {
...expander,
onLastItemLoop,
+ onFirstItemLoop,
item,
} );
diff --git a/packages/js/components/src/experimental-tree-control/hooks/use-tree.ts b/packages/js/components/src/experimental-tree-control/hooks/use-tree.ts
index 656473e9456..393a02873cb 100644
--- a/packages/js/components/src/experimental-tree-control/hooks/use-tree.ts
+++ b/packages/js/components/src/experimental-tree-control/hooks/use-tree.ts
@@ -23,6 +23,7 @@ export function useTree( {
onTreeBlur,
onCreateNew,
shouldShowCreateButton,
+ onFirstItemLoop,
...props
}: TreeProps ) {
return {
diff --git a/packages/js/components/src/experimental-tree-control/tree.tsx b/packages/js/components/src/experimental-tree-control/tree.tsx
index e33baf34a25..08957af0904 100644
--- a/packages/js/components/src/experimental-tree-control/tree.tsx
+++ b/packages/js/components/src/experimental-tree-control/tree.tsx
@@ -59,6 +59,7 @@ export const Tree = forwardRef( function ForwardedTree(
) as HTMLButtonElement
)?.focus();
} }
+ onFirstItemLoop={ props.onFirstItemLoop }
onEscape={ props.onEscape }
/>
) ) }
diff --git a/packages/js/components/src/experimental-tree-control/types.ts b/packages/js/components/src/experimental-tree-control/types.ts
index e30172d0d44..6d759e3801e 100644
--- a/packages/js/components/src/experimental-tree-control/types.ts
+++ b/packages/js/components/src/experimental-tree-control/types.ts
@@ -76,6 +76,8 @@ type BaseTreeProps = {
* Called when the create button is clicked to help closing any related popover.
*/
onTreeBlur?(): void;
+
+ onFirstItemLoop?( event: React.KeyboardEvent< HTMLDivElement > ): void;
/**
* Called when the escape key is pressed.
*/
diff --git a/packages/js/components/src/index.ts b/packages/js/components/src/index.ts
index 2e2b570b5d3..ff64f547fda 100644
--- a/packages/js/components/src/index.ts
+++ b/packages/js/components/src/index.ts
@@ -99,10 +99,7 @@ export {
TreeControl as __experimentalTreeControl,
Item as TreeItemType,
} from './experimental-tree-control';
-export {
- SelectTree as __experimentalSelectTreeControl,
- SelectTreeMenuSlot as __experimentalSelectTreeMenuSlot,
-} from './experimental-select-tree-control';
+export { SelectTree as __experimentalSelectTreeControl } from './experimental-select-tree-control';
export { default as TreeSelectControl } from './tree-select-control';
export { default as PhoneNumberInput } from './phone-number-input';
diff --git a/packages/js/product-editor/changelog/tweak-select-tree-2 b/packages/js/product-editor/changelog/tweak-select-tree-2
new file mode 100644
index 00000000000..82314a6b4f2
--- /dev/null
+++ b/packages/js/product-editor/changelog/tweak-select-tree-2
@@ -0,0 +1,4 @@
+Significance: minor
+Type: update
+
+Add 'help' to taxonomy block and to SelectTree
diff --git a/packages/js/product-editor/src/blocks/generic/taxonomy/block.json b/packages/js/product-editor/src/blocks/generic/taxonomy/block.json
index 98abfd3d3fb..69b5423852b 100644
--- a/packages/js/product-editor/src/blocks/generic/taxonomy/block.json
+++ b/packages/js/product-editor/src/blocks/generic/taxonomy/block.json
@@ -35,6 +35,10 @@
"placeholder": {
"type": "string",
"__experimentalRole": "content"
+ },
+ "help": {
+ "type": "string",
+ "__experimentalRole": "content"
}
},
"supports": {
diff --git a/packages/js/product-editor/src/blocks/generic/taxonomy/edit.tsx b/packages/js/product-editor/src/blocks/generic/taxonomy/edit.tsx
index e6dd85b43e9..0f700d00eaa 100644
--- a/packages/js/product-editor/src/blocks/generic/taxonomy/edit.tsx
+++ b/packages/js/product-editor/src/blocks/generic/taxonomy/edit.tsx
@@ -26,9 +26,11 @@ import type {
TaxonomyMetadata,
} from '../../../types';
import useProductEntityProp from '../../../hooks/use-product-entity-prop';
+import { Label } from '../../../components/label/label';
interface TaxonomyBlockAttributes extends BlockAttributes {
label: string;
+ help?: string;
slug: string;
property: string;
createTitle: string;
@@ -52,6 +54,7 @@ export function Edit( {
);
const {
label,
+ help,
slug,
property,
createTitle,
@@ -117,7 +120,7 @@ export function Edit( {
'woocommerce-taxonomy-select'
) as string
}
- label={ label }
+ label={ }
isLoading={ isResolving }
disabled={ disabled }
multiple
diff --git a/packages/js/product-editor/src/components/tags-field/tag-field.tsx b/packages/js/product-editor/src/components/tags-field/tag-field.tsx
index a7eb78b3e1d..cff7f5e412d 100644
--- a/packages/js/product-editor/src/components/tags-field/tag-field.tsx
+++ b/packages/js/product-editor/src/components/tags-field/tag-field.tsx
@@ -148,6 +148,11 @@ export const TagField: React.FC< TagFieldProps > = ( {
)
);
onChange( [ ...value, ...newItems ] );
+ } else {
+ onChange( [
+ ...value,
+ mapFromTreeItemToTag( selectedItems ),
+ ] );
}
} }
onRemove={ ( removedItems ) => {
diff --git a/plugins/woocommerce/changelog/tweak-select-tree-2 b/plugins/woocommerce/changelog/tweak-select-tree-2
new file mode 100644
index 00000000000..13f276452a1
--- /dev/null
+++ b/plugins/woocommerce/changelog/tweak-select-tree-2
@@ -0,0 +1,5 @@
+Significance: patch
+Type: fix
+Comment: Fix E2E test about Categories field
+
+
diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/organization-tab-product-block-editor.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/organization-tab-product-block-editor.spec.js
index 3b14855264e..d63d7a5511b 100644
--- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/organization-tab-product-block-editor.spec.js
+++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/organization-tab-product-block-editor.spec.js
@@ -62,9 +62,7 @@ test.describe( 'General tab', { tag: '@gutenberg' }, () => {
await clickOnTab( 'Organization', page );
- await page
- .locator( '[id^="woocommerce-taxonomy-select-"]' )
- .click();
+ await page.getByLabel( 'Categories' ).click();
await page.locator( 'text=Create new' ).click();
@@ -80,7 +78,7 @@ test.describe( 'General tab', { tag: '@gutenberg' }, () => {
} )
.click();
- await page.locator( '[id^="tag-field-"]' ).click();
+ await page.getByLabel( 'Tags' ).click();
await page.locator( 'text=Create new' ).click();