Tree Select Control: Allow parents to be searchable (#41559)
This commit is contained in:
parent
2d44b1f94b
commit
b7ba45d419
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Update Tree Select Control to allow for searching parent values when `individuallySelectParent` is turned on.
|
|
@ -260,12 +260,52 @@ const TreeSelectControl = ( {
|
|||
}
|
||||
return (
|
||||
! this.checked &&
|
||||
this.leaves.some(
|
||||
this.children.some(
|
||||
( opt ) => opt.checked || opt.partialChecked
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
isVisible: {
|
||||
/**
|
||||
* Returns whether this option should be visible based on search.
|
||||
* All options are visible when not searching. Otherwise, true if this option is
|
||||
* a search result or it has a descendent that is being searched for.
|
||||
*
|
||||
* @return {boolean} True if option should be visible, false otherwise.
|
||||
*/
|
||||
get() {
|
||||
// everything is visible when not searching.
|
||||
if ( ! isSearching ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Exit true if this is searched result.
|
||||
if ( this.isSearchResult ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If any children are search results, remain visible.
|
||||
if ( this.hasChildren ) {
|
||||
return this.children.some( ( opt ) => opt.isVisible );
|
||||
}
|
||||
|
||||
return this.leaves.some( ( opt ) => opt.isSearchResult );
|
||||
},
|
||||
},
|
||||
isSearchResult: {
|
||||
/**
|
||||
* Returns whether this option is a searched result.
|
||||
*
|
||||
* @return {boolean} True if option is being searched, false otherwise.
|
||||
*/
|
||||
get() {
|
||||
if ( ! isSearching ) {
|
||||
return false;
|
||||
}
|
||||
return !! this.filterMatch;
|
||||
},
|
||||
},
|
||||
expanded: {
|
||||
/**
|
||||
* Returns whether this option is expanded.
|
||||
|
@ -275,7 +315,7 @@ const TreeSelectControl = ( {
|
|||
*/
|
||||
get() {
|
||||
return (
|
||||
isSearching ||
|
||||
( isSearching && this.isVisible ) ||
|
||||
this.value === ROOT_VALUE ||
|
||||
cache.expandedValues.includes( this.value )
|
||||
);
|
||||
|
@ -294,20 +334,18 @@ const TreeSelectControl = ( {
|
|||
const reduceOptions = ( acc, { children = [], ...option } ) => {
|
||||
if ( children.length ) {
|
||||
option.children = children.reduce( reduceOptions, [] );
|
||||
}
|
||||
|
||||
if ( ! option.children.length ) {
|
||||
return acc;
|
||||
}
|
||||
} else if ( isSearching ) {
|
||||
if ( isSearching ) {
|
||||
const labelWithAccentsRemoved = removeAccents( option.label );
|
||||
const filterWithAccentsRemoved = removeAccents( filter );
|
||||
const match = labelWithAccentsRemoved
|
||||
.toLowerCase()
|
||||
.indexOf( filterWithAccentsRemoved );
|
||||
if ( match === -1 ) {
|
||||
return acc;
|
||||
if ( match > -1 ) {
|
||||
option.label = highlightOptionLabel( option.label, match );
|
||||
option.filterMatch = true;
|
||||
}
|
||||
option.label = highlightOptionLabel( option.label, match );
|
||||
}
|
||||
|
||||
Object.defineProperties( option, descriptors );
|
||||
|
|
|
@ -58,6 +58,7 @@ const Options = ( {
|
|||
const { hasChildren, checked, partialChecked, expanded } = option;
|
||||
|
||||
if ( ! option?.value ) return null;
|
||||
if ( ! isRoot && ! option?.isVisible ) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
|
@ -29,7 +29,7 @@ const treeSelectControlOptions = [
|
|||
children: [
|
||||
{
|
||||
value: 'TO',
|
||||
label: 'Tokio',
|
||||
label: 'Tokyo',
|
||||
children: [
|
||||
{ value: 'SI', label: 'Shibuya' },
|
||||
{ value: 'GI', label: 'Ginza' },
|
||||
|
@ -98,6 +98,7 @@ Base.args = {
|
|||
includeParent: false,
|
||||
alwaysShowPlaceholder: false,
|
||||
individuallySelectParent: false,
|
||||
clearOnSelect: true,
|
||||
};
|
||||
|
||||
Base.argTypes = {
|
||||
|
|
|
@ -168,6 +168,22 @@ describe( 'TreeSelectControl Component', () => {
|
|||
expect( queryByLabelText( 'Asia' ) ).toBeFalsy(); // doesn't match pain
|
||||
} );
|
||||
|
||||
it( 'Filters Option categories on Search', () => {
|
||||
const { queryByLabelText, queryByRole } = render(
|
||||
<TreeSelectControl options={ options } />
|
||||
);
|
||||
|
||||
const control = queryByRole( 'combobox' );
|
||||
fireEvent.click( control );
|
||||
expect( queryByLabelText( 'Europe' ) ).toBeTruthy();
|
||||
expect( queryByLabelText( 'Asia' ) ).toBeTruthy();
|
||||
|
||||
fireEvent.change( control, { target: { value: 'eur' } } );
|
||||
|
||||
expect( queryByLabelText( 'Europe' ) ).toBeTruthy(); // match 'eur'
|
||||
expect( queryByLabelText( 'Asia' ) ).toBeFalsy(); // doesn't match 'eur'
|
||||
} );
|
||||
|
||||
it( 'should call onInputChange when input field changed', () => {
|
||||
const onInputChangeMock = jest.fn();
|
||||
const { queryByRole } = render(
|
||||
|
|
Loading…
Reference in New Issue