Switch from Select to Combobox for Country and State Inputs (https://github.com/woocommerce/woocommerce-blocks/pull/4369)
* Add combobox control * Implement in country and state * mobile styling * styling across themes * Remove validated select component * Use focus-within * Update tests * Use @wordpress/compose * Move field clearing to effect hook * Patch combobox component PR https://github.com/WordPress/gutenberg/pull/33928 * patch package after install * update package * Prevent autofill handling impacting manual input * Add todo * combo requires option to be selected
This commit is contained in:
parent
946e05d70b
commit
a40893ae3a
|
@ -112,6 +112,18 @@ const AddressForm = ( {
|
|||
);
|
||||
}, [ currentFields, fieldConfig, values.country ] );
|
||||
|
||||
// Clear values for hidden fields.
|
||||
useEffect( () => {
|
||||
addressFormFields.forEach( ( field ) => {
|
||||
if ( field.hidden && values[ field.key ] ) {
|
||||
onChange( {
|
||||
...values,
|
||||
[ field.key ]: '',
|
||||
} );
|
||||
}
|
||||
} );
|
||||
}, [ addressFormFields, onChange, values ] );
|
||||
|
||||
useEffect( () => {
|
||||
if ( type === 'shipping' ) {
|
||||
validateShippingCountry(
|
||||
|
@ -161,8 +173,6 @@ const AddressForm = ( {
|
|||
...values,
|
||||
country: newValue,
|
||||
state: '',
|
||||
city: '',
|
||||
postcode: '',
|
||||
} )
|
||||
}
|
||||
errorId={
|
||||
|
|
|
@ -21,7 +21,7 @@ const renderInCheckoutProvider = ( ui, options = {} ) => {
|
|||
// Countries used in testing addresses must be in the wcSettings global.
|
||||
// See: tests/js/setup-globals.js
|
||||
const primaryAddress = {
|
||||
country: 'United Kingdom (UK)',
|
||||
country: 'United Kingdom',
|
||||
countryKey: 'GB',
|
||||
city: 'London',
|
||||
state: 'Greater London',
|
||||
|
@ -46,25 +46,22 @@ const cityRegExp = /city/i;
|
|||
const stateRegExp = /county|province|state/i;
|
||||
const postalCodeRegExp = /postal code|postcode|zip/i;
|
||||
|
||||
const inputAddress = ( {
|
||||
const inputAddress = async ( {
|
||||
country = null,
|
||||
city = null,
|
||||
state = null,
|
||||
postcode = null,
|
||||
} ) => {
|
||||
if ( country ) {
|
||||
const countryButton = screen.getByRole( 'button', {
|
||||
name: countryRegExp,
|
||||
} );
|
||||
userEvent.click( countryButton );
|
||||
userEvent.click( screen.getByRole( 'option', { name: country } ) );
|
||||
const countryInput = screen.getByLabelText( countryRegExp );
|
||||
userEvent.type( countryInput, country + '{arrowdown}{enter}' );
|
||||
}
|
||||
if ( city ) {
|
||||
const cityInput = screen.getByLabelText( cityRegExp );
|
||||
userEvent.type( cityInput, city );
|
||||
}
|
||||
if ( state ) {
|
||||
const stateButton = screen.queryByRole( 'button', {
|
||||
const stateButton = screen.queryByRole( 'combobox', {
|
||||
name: stateRegExp,
|
||||
} );
|
||||
// State input might be a select or a text input.
|
||||
|
@ -162,17 +159,11 @@ describe( 'AddressForm Component', () => {
|
|||
inputAddress( secondaryAddress );
|
||||
// Only update `country` to verify other values are reset.
|
||||
inputAddress( { country: primaryAddress.country } );
|
||||
|
||||
expect( screen.getByLabelText( cityRegExp ).value ).toBe( '' );
|
||||
expect( screen.getByLabelText( stateRegExp ).value ).toBe( '' );
|
||||
expect( screen.getByLabelText( postalCodeRegExp ).value ).toBe( '' );
|
||||
|
||||
// Repeat the test with an address which has a select for the state.
|
||||
inputAddress( tertiaryAddress );
|
||||
inputAddress( { country: primaryAddress.country } );
|
||||
|
||||
expect( screen.getByLabelText( cityRegExp ).value ).toBe( '' );
|
||||
expect( screen.getByLabelText( stateRegExp ).value ).toBe( '' );
|
||||
expect( screen.getByLabelText( postalCodeRegExp ).value ).toBe( '' );
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useEffect, useRef } from '@wordpress/element';
|
||||
import { withInstanceId } from '@wordpress/compose';
|
||||
import { ComboboxControl } from 'wordpress-components';
|
||||
import {
|
||||
ValidationInputError,
|
||||
useValidationContext,
|
||||
} from '@woocommerce/base-context';
|
||||
import { isObject } from '@woocommerce/types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
export interface ComboboxControlOption {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the WordPress ComboboxControl which supports validation.
|
||||
*/
|
||||
const Combobox = ( {
|
||||
id,
|
||||
className,
|
||||
label,
|
||||
onChange,
|
||||
options,
|
||||
value,
|
||||
required = false,
|
||||
errorMessage = __(
|
||||
'Please select a value.',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
errorId: incomingErrorId,
|
||||
instanceId = '0',
|
||||
autoComplete = 'off',
|
||||
}: {
|
||||
id: string;
|
||||
className: string;
|
||||
label: string;
|
||||
onChange: ( filterValue: string ) => void;
|
||||
options: ComboboxControlOption[];
|
||||
value: string;
|
||||
required: boolean;
|
||||
errorMessage: string;
|
||||
errorId: string;
|
||||
instanceId: string;
|
||||
autoComplete: string;
|
||||
} ): JSX.Element => {
|
||||
const {
|
||||
getValidationError,
|
||||
setValidationErrors,
|
||||
clearValidationError,
|
||||
} = useValidationContext();
|
||||
|
||||
const controlRef = useRef< HTMLDivElement >( null );
|
||||
const controlId = id || 'control-' + instanceId;
|
||||
const errorId = incomingErrorId || controlId;
|
||||
const error = ( getValidationError( errorId ) || {
|
||||
message: '',
|
||||
hidden: false,
|
||||
} ) as {
|
||||
message: string;
|
||||
hidden: boolean;
|
||||
};
|
||||
|
||||
useEffect( () => {
|
||||
if ( ! required || value ) {
|
||||
clearValidationError( errorId );
|
||||
} else {
|
||||
setValidationErrors( {
|
||||
[ errorId ]: {
|
||||
message: errorMessage,
|
||||
hidden: true,
|
||||
},
|
||||
} );
|
||||
}
|
||||
return () => {
|
||||
clearValidationError( errorId );
|
||||
};
|
||||
}, [
|
||||
clearValidationError,
|
||||
value,
|
||||
errorId,
|
||||
errorMessage,
|
||||
required,
|
||||
setValidationErrors,
|
||||
] );
|
||||
|
||||
// @todo Remove patch for ComboboxControl once https://github.com/WordPress/gutenberg/pull/33928 is released
|
||||
return (
|
||||
<div
|
||||
id={ controlId }
|
||||
className={ classnames( 'wc-block-components-combobox', className, {
|
||||
'is-active': value,
|
||||
'has-error': error.message && ! error.hidden,
|
||||
} ) }
|
||||
ref={ controlRef }
|
||||
>
|
||||
<ComboboxControl
|
||||
className={ 'wc-block-components-combobox-control' }
|
||||
label={ label }
|
||||
onChange={ onChange }
|
||||
onFilterValueChange={ ( filterValue: string ) => {
|
||||
if ( filterValue.length ) {
|
||||
// If we have a value and the combobox is not focussed, this could be from browser autofill.
|
||||
const activeElement = isObject( controlRef.current )
|
||||
? controlRef.current.ownerDocument.activeElement
|
||||
: undefined;
|
||||
|
||||
if (
|
||||
activeElement &&
|
||||
isObject( controlRef.current ) &&
|
||||
controlRef.current.contains( activeElement )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to match.
|
||||
const normalizedFilterValue = filterValue.toLocaleUpperCase();
|
||||
const foundOption = options.find(
|
||||
( option ) =>
|
||||
option.label
|
||||
.toLocaleUpperCase()
|
||||
.startsWith( normalizedFilterValue ) ||
|
||||
option.value.toLocaleUpperCase() ===
|
||||
normalizedFilterValue
|
||||
);
|
||||
if ( foundOption ) {
|
||||
onChange( foundOption.value );
|
||||
}
|
||||
}
|
||||
} }
|
||||
options={ options }
|
||||
value={ value || '' }
|
||||
allowReset={ false }
|
||||
autoComplete={ autoComplete }
|
||||
/>
|
||||
<ValidationInputError propertyName={ errorId } />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default withInstanceId( Combobox );
|
|
@ -0,0 +1,156 @@
|
|||
.wc-block-components-form .wc-block-components-combobox,
|
||||
.wc-block-components-combobox {
|
||||
.wc-block-components-combobox-control {
|
||||
@include reset-typography();
|
||||
@include reset-box();
|
||||
|
||||
.components-base-control__field {
|
||||
@include reset-box();
|
||||
}
|
||||
.components-combobox-control__suggestions-container {
|
||||
@include reset-typography();
|
||||
@include reset-box();
|
||||
position: relative;
|
||||
}
|
||||
input.components-combobox-control__input {
|
||||
@include reset-typography();
|
||||
@include font-size(regular);
|
||||
|
||||
box-sizing: border-box;
|
||||
outline: inherit;
|
||||
border: 1px solid $input-border-gray;
|
||||
background: #fff;
|
||||
box-shadow: none;
|
||||
color: $input-text-active;
|
||||
font-family: inherit;
|
||||
font-weight: normal;
|
||||
height: 3em;
|
||||
letter-spacing: inherit;
|
||||
line-height: 1;
|
||||
padding: em($gap-large) $gap em($gap-smallest);
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
text-transform: none;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
opacity: initial;
|
||||
border-radius: 4px;
|
||||
|
||||
&[aria-expanded="true"],
|
||||
&:focus {
|
||||
background-color: #fff;
|
||||
color: $input-text-active;
|
||||
}
|
||||
|
||||
&[aria-expanded="true"] {
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
.has-dark-controls & {
|
||||
background-color: $input-background-dark;
|
||||
border-color: $input-border-dark;
|
||||
color: $input-text-dark;
|
||||
|
||||
&:focus {
|
||||
background-color: $input-background-dark;
|
||||
color: $input-text-dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
.components-form-token-field__suggestions-list {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
background-color: $select-dropdown-light;
|
||||
border: 1px solid $input-border-gray;
|
||||
border-top: 0;
|
||||
margin: 3em 0 0 0;
|
||||
padding: 0;
|
||||
max-height: 300px;
|
||||
min-width: 100%;
|
||||
overflow: auto;
|
||||
color: $input-text-active;
|
||||
|
||||
.has-dark-controls & {
|
||||
background-color: $select-dropdown-dark;
|
||||
color: $input-text-dark;
|
||||
}
|
||||
|
||||
.components-form-token-field__suggestion {
|
||||
@include font-size(regular);
|
||||
color: $gray-700;
|
||||
cursor: default;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: em($gap-smallest) $gap;
|
||||
|
||||
&.is-selected {
|
||||
background-color: $gray-300;
|
||||
.has-dark-controls & {
|
||||
background-color: $select-item-dark;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&.is-highlighted,
|
||||
&:active {
|
||||
background-color: #00669e;
|
||||
color: #fff;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
label.components-base-control__label {
|
||||
@include reset-typography();
|
||||
@include font-size(regular);
|
||||
line-height: 1.375; // =22px when font-size is 16px.
|
||||
position: absolute;
|
||||
transform: translateY(0.75em);
|
||||
transform-origin: top left;
|
||||
transition: all 200ms ease;
|
||||
color: $gray-700;
|
||||
z-index: 1;
|
||||
margin: 0 0 0 #{$gap + 1px};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: calc(100% - #{2 * $gap});
|
||||
white-space: nowrap;
|
||||
|
||||
.has-dark-controls & {
|
||||
color: $input-placeholder-dark;
|
||||
}
|
||||
@media screen and (prefers-reduced-motion: reduce) {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.is-active,
|
||||
&:focus-within {
|
||||
.wc-block-components-combobox-control label.components-base-control__label {
|
||||
transform: translateY(#{$gap-smallest}) scale(0.75);
|
||||
}
|
||||
}
|
||||
|
||||
&.has-error {
|
||||
.wc-block-components-combobox-control {
|
||||
label.components-base-control__label {
|
||||
color: $alert-red;
|
||||
}
|
||||
input.components-combobox-control__input {
|
||||
&,
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
border-color: $alert-red;
|
||||
}
|
||||
&:focus {
|
||||
outline: 1px dotted $alert-red;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ import classnames from 'classnames';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ValidatedSelect } from '../select';
|
||||
import Combobox from '../combobox';
|
||||
import './style.scss';
|
||||
import type { CountryInputWithCountriesProps } from './CountryInputProps';
|
||||
|
||||
|
@ -31,8 +31,8 @@ const CountryInput = ( {
|
|||
const options = useMemo(
|
||||
() =>
|
||||
Object.keys( countries ).map( ( key ) => ( {
|
||||
key,
|
||||
name: decodeEntities( countries[ key ] ),
|
||||
value: key,
|
||||
label: decodeEntities( countries[ key ] ),
|
||||
} ) ),
|
||||
[ countries ]
|
||||
);
|
||||
|
@ -44,15 +44,16 @@ const CountryInput = ( {
|
|||
'wc-block-components-country-input'
|
||||
) }
|
||||
>
|
||||
<ValidatedSelect
|
||||
<Combobox
|
||||
id={ id }
|
||||
label={ label }
|
||||
onChange={ onChange }
|
||||
options={ options }
|
||||
value={ options.find( ( option ) => option.key === value ) }
|
||||
value={ value }
|
||||
errorId={ errorId }
|
||||
errorMessage={ errorMessage }
|
||||
required={ required }
|
||||
autoComplete={ autoComplete }
|
||||
/>
|
||||
{ autoComplete !== 'off' && (
|
||||
<input
|
||||
|
@ -61,11 +62,17 @@ const CountryInput = ( {
|
|||
autoComplete={ autoComplete }
|
||||
value={ value }
|
||||
onChange={ ( event ) => {
|
||||
const textValue = event.target.value;
|
||||
const textValue = event.target.value.toLocaleUpperCase();
|
||||
const foundOption = options.find(
|
||||
( option ) => option.key === textValue
|
||||
( option ) =>
|
||||
( textValue.length !== 2 &&
|
||||
option.label.toLocaleUpperCase() ===
|
||||
textValue ) ||
|
||||
( textValue.length === 2 &&
|
||||
option.value.toLocaleUpperCase() ===
|
||||
textValue )
|
||||
);
|
||||
onChange( foundOption ? foundOption.key : '' );
|
||||
onChange( foundOption ? foundOption.value : '' );
|
||||
} }
|
||||
style={ {
|
||||
minHeight: '0',
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
export { default as Select } from './select';
|
||||
export { default as ValidatedSelect } from './validated';
|
|
@ -1,60 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { CustomSelectControl } from 'wordpress-components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
const Select = ( {
|
||||
className,
|
||||
feedback,
|
||||
id,
|
||||
label,
|
||||
onChange,
|
||||
options,
|
||||
value,
|
||||
} ) => {
|
||||
return (
|
||||
<div
|
||||
id={ id }
|
||||
className={ classnames( 'wc-block-components-select', className, {
|
||||
'is-active': value,
|
||||
} ) }
|
||||
>
|
||||
<CustomSelectControl
|
||||
label={ label }
|
||||
onChange={ ( { selectedItem } ) => {
|
||||
onChange( selectedItem.key );
|
||||
} }
|
||||
options={ options }
|
||||
value={ value || null }
|
||||
/>
|
||||
{ feedback }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Select.propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
options: PropTypes.arrayOf(
|
||||
PropTypes.shape( {
|
||||
key: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
} ).isRequired
|
||||
).isRequired,
|
||||
className: PropTypes.string,
|
||||
feedback: PropTypes.node,
|
||||
id: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
value: PropTypes.shape( {
|
||||
key: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
} ),
|
||||
};
|
||||
|
||||
export default Select;
|
|
@ -1,162 +0,0 @@
|
|||
.wc-block-components-form .wc-block-components-select,
|
||||
.wc-block-components-select {
|
||||
height: 3em;
|
||||
position: relative;
|
||||
|
||||
label.components-custom-select-control__label {
|
||||
@include reset-typography();
|
||||
@include font-size(regular);
|
||||
line-height: 1.375; // =22px when font-size is 16px.
|
||||
position: absolute;
|
||||
transform: translateY(0.75em);
|
||||
transform-origin: top left;
|
||||
transition: all 200ms ease;
|
||||
color: $gray-700;
|
||||
z-index: 1;
|
||||
margin: 0 0 0 #{$gap + 1px};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: calc(100% - #{2 * $gap});
|
||||
white-space: nowrap;
|
||||
|
||||
.has-dark-controls & {
|
||||
color: $input-placeholder-dark;
|
||||
}
|
||||
@media screen and (prefers-reduced-motion: reduce) {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-active label {
|
||||
transform: translateY(#{$gap-smallest}) scale(0.75);
|
||||
}
|
||||
|
||||
&.has-error {
|
||||
.components-custom-select-control__button {
|
||||
&,
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
border-color: $alert-red;
|
||||
}
|
||||
&:focus {
|
||||
outline: 1px dotted $alert-red;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.has-error label {
|
||||
color: $alert-red;
|
||||
}
|
||||
|
||||
.components-custom-select-control__button {
|
||||
&,
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
@include font-size(regular);
|
||||
background: #fff;
|
||||
box-shadow: none;
|
||||
color: $input-text-active;
|
||||
font-family: inherit;
|
||||
font-weight: normal;
|
||||
height: 3em;
|
||||
letter-spacing: inherit;
|
||||
line-height: 1;
|
||||
overflow: hidden;
|
||||
padding: em($gap-large) $gap em($gap-smallest);
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
text-transform: none;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
opacity: initial;
|
||||
border-radius: 4px;
|
||||
.has-dark-controls & {
|
||||
background: $input-background-dark;
|
||||
border-color: $input-border-dark;
|
||||
color: $input-text-dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.components-custom-select-control__button-icon {
|
||||
right: #{$gap - 4px};
|
||||
.has-dark-controls & {
|
||||
fill: $input-text-dark;
|
||||
}
|
||||
}
|
||||
|
||||
.components-custom-select-control__menu {
|
||||
background-color: $select-dropdown-light;
|
||||
margin: 0;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
color: $input-text-active;
|
||||
|
||||
// Required by IE11.
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
.has-dark-controls & {
|
||||
background-color: $select-dropdown-dark;
|
||||
color: $input-text-dark;
|
||||
}
|
||||
}
|
||||
|
||||
.components-custom-select-control__item {
|
||||
@include font-size(regular);
|
||||
margin-left: 0;
|
||||
padding-left: $gap;
|
||||
&:hover,
|
||||
&:focus,
|
||||
&.is-highlighted {
|
||||
.has-dark-controls & {
|
||||
background-color: $select-item-dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.components-custom-select-control__item-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-twentytwentyone {
|
||||
// Extra classes for specificity.
|
||||
&.theme-twentytwentyone.theme-twentytwentyone .components-custom-select-control__button {
|
||||
background-color: #fff;
|
||||
color: $input-text-active;
|
||||
}
|
||||
|
||||
&.is-dark-theme {
|
||||
// If the theme is in dark mode, as well as the block, then this selector will match.
|
||||
.has-dark-controls {
|
||||
.components-custom-select-control__item {
|
||||
color: $input-text-dark;
|
||||
}
|
||||
}
|
||||
|
||||
// If the theme is in dark mode, but the block isn't, then this selector will match.
|
||||
.components-custom-select-control__item {
|
||||
color: $input-text-active;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.theme-twentyseventeen {
|
||||
// Extra classes for specificity.
|
||||
&.theme-twentyseventeen.theme-twentyseventeen {
|
||||
.components-custom-select-control__button {
|
||||
background-color: $select-dropdown-light;
|
||||
color: $input-text-active;
|
||||
}
|
||||
.has-dark-controls {
|
||||
.components-custom-select-control__button {
|
||||
background-color: $select-dropdown-dark;
|
||||
color: $input-text-dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useEffect } from 'react';
|
||||
import { useShallowEqual } from '@woocommerce/base-hooks';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { withInstanceId } from '@wordpress/compose';
|
||||
import {
|
||||
ValidationInputError,
|
||||
useValidationContext,
|
||||
} from '@woocommerce/base-context';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Select from './select';
|
||||
import './style.scss';
|
||||
|
||||
const ValidatedSelect = ( {
|
||||
className,
|
||||
id,
|
||||
value,
|
||||
instanceId,
|
||||
required,
|
||||
errorId,
|
||||
errorMessage = __(
|
||||
'Please select a value.',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
...rest
|
||||
} ) => {
|
||||
const selectId = id || 'select-' + instanceId;
|
||||
errorId = errorId || selectId;
|
||||
|
||||
// Prevents re-renders when value is an object, e.g. {key: "NY", name: "New York"}
|
||||
const currentValue = useShallowEqual( value );
|
||||
|
||||
const {
|
||||
getValidationError,
|
||||
setValidationErrors,
|
||||
clearValidationError,
|
||||
} = useValidationContext();
|
||||
|
||||
useEffect( () => {
|
||||
if ( ! required || currentValue ) {
|
||||
clearValidationError( errorId );
|
||||
} else {
|
||||
setValidationErrors( {
|
||||
[ errorId ]: {
|
||||
message: errorMessage,
|
||||
hidden: true,
|
||||
},
|
||||
} );
|
||||
}
|
||||
}, [
|
||||
clearValidationError,
|
||||
currentValue,
|
||||
errorId,
|
||||
errorMessage,
|
||||
required,
|
||||
setValidationErrors,
|
||||
] );
|
||||
|
||||
// Remove validation errors when unmounted.
|
||||
useEffect( () => {
|
||||
return () => {
|
||||
clearValidationError( errorId );
|
||||
};
|
||||
}, [ clearValidationError, errorId ] );
|
||||
|
||||
const error = getValidationError( errorId ) || {};
|
||||
|
||||
return (
|
||||
<Select
|
||||
id={ selectId }
|
||||
className={ classnames( className, {
|
||||
'has-error': error.message && ! error.hidden,
|
||||
} ) }
|
||||
feedback={ <ValidationInputError propertyName={ errorId } /> }
|
||||
value={ currentValue }
|
||||
{ ...rest }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
ValidatedSelect.propTypes = {
|
||||
className: PropTypes.string,
|
||||
errorId: PropTypes.string,
|
||||
errorMessage: PropTypes.string,
|
||||
id: PropTypes.string,
|
||||
required: PropTypes.bool,
|
||||
value: PropTypes.shape( {
|
||||
key: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
} ),
|
||||
};
|
||||
|
||||
export default withInstanceId( ValidatedSelect );
|
|
@ -10,7 +10,7 @@ import classnames from 'classnames';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { ValidatedTextInput } from '../text-input';
|
||||
import { ValidatedSelect } from '../select';
|
||||
import Combobox from '../combobox';
|
||||
import './style.scss';
|
||||
import type { StateInputWithStatesProps } from './StateInputProps';
|
||||
|
||||
|
@ -30,8 +30,8 @@ const StateInput = ( {
|
|||
() =>
|
||||
countryStates
|
||||
? Object.keys( countryStates ).map( ( key ) => ( {
|
||||
key,
|
||||
name: decodeEntities( countryStates[ key ] ),
|
||||
value: key,
|
||||
label: decodeEntities( countryStates[ key ] ),
|
||||
} ) )
|
||||
: [],
|
||||
[ countryStates ]
|
||||
|
@ -47,10 +47,12 @@ const StateInput = ( {
|
|||
if ( options.length > 0 ) {
|
||||
const foundOption = options.find(
|
||||
( option ) =>
|
||||
option.key === stateValue || option.name === stateValue
|
||||
option.label.toLocaleUpperCase() ===
|
||||
stateValue.toLocaleUpperCase() ||
|
||||
option.value.toLocaleUpperCase() ===
|
||||
stateValue.toLocaleUpperCase()
|
||||
);
|
||||
|
||||
onChange( foundOption ? foundOption.key : '' );
|
||||
onChange( foundOption ? foundOption.value : '' );
|
||||
return;
|
||||
}
|
||||
onChange( stateValue );
|
||||
|
@ -61,7 +63,7 @@ const StateInput = ( {
|
|||
if ( options.length > 0 ) {
|
||||
return (
|
||||
<>
|
||||
<ValidatedSelect
|
||||
<Combobox
|
||||
className={ classnames(
|
||||
className,
|
||||
'wc-block-components-state-input'
|
||||
|
@ -70,12 +72,13 @@ const StateInput = ( {
|
|||
label={ label }
|
||||
onChange={ onChangeState }
|
||||
options={ options }
|
||||
value={ options.find( ( option ) => option.key === value ) }
|
||||
value={ value }
|
||||
errorMessage={ __(
|
||||
'Please select a state.',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
required={ required }
|
||||
autoComplete={ autoComplete }
|
||||
/>
|
||||
{ autoComplete !== 'off' && (
|
||||
<input
|
||||
|
|
|
@ -91,6 +91,8 @@ const entries = {
|
|||
'./node_modules/wordpress-components/src/spinner/style.scss',
|
||||
'snackbar-notice-style':
|
||||
'./node_modules/wordpress-components/src/snackbar/style.scss',
|
||||
'combobox-control-style':
|
||||
'./node_modules/wordpress-components/src/combobox-control/style.scss',
|
||||
|
||||
'general-style': glob.sync( './assets/**/*.scss', {
|
||||
ignore: [
|
||||
|
|
|
@ -9682,6 +9682,12 @@
|
|||
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@yarnpkg/lockfile": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
|
||||
"integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==",
|
||||
"dev": true
|
||||
},
|
||||
"abab": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz",
|
||||
|
@ -11042,6 +11048,16 @@
|
|||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"bindings": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
|
@ -16835,6 +16851,13 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"filelist": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz",
|
||||
|
@ -17007,6 +17030,60 @@
|
|||
"locate-path": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"find-yarn-workspace-root": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz",
|
||||
"integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"micromatch": "^4.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fill-range": "^7.0.1"
|
||||
}
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true
|
||||
},
|
||||
"micromatch": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
|
||||
"integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"braces": "^3.0.1",
|
||||
"picomatch": "^2.2.3"
|
||||
}
|
||||
},
|
||||
"to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-number": "^7.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"findup-sync": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz",
|
||||
|
@ -21488,6 +21565,15 @@
|
|||
"graceful-fs": "^4.1.9"
|
||||
}
|
||||
},
|
||||
"klaw-sync": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
|
||||
"integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.11"
|
||||
}
|
||||
},
|
||||
"kleur": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
|
||||
|
@ -23559,6 +23645,13 @@
|
|||
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
|
||||
"dev": true
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.15.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
|
||||
"integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "3.1.23",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz",
|
||||
|
@ -24601,6 +24694,75 @@
|
|||
"integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
|
||||
"dev": true
|
||||
},
|
||||
"patch-package": {
|
||||
"version": "6.4.7",
|
||||
"resolved": "https://registry.npmjs.org/patch-package/-/patch-package-6.4.7.tgz",
|
||||
"integrity": "sha512-S0vh/ZEafZ17hbhgqdnpunKDfzHQibQizx9g8yEf5dcVk3KOflOfdufRXQX8CSEkyOQwuM/bNz1GwKvFj54kaQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@yarnpkg/lockfile": "^1.1.0",
|
||||
"chalk": "^2.4.2",
|
||||
"cross-spawn": "^6.0.5",
|
||||
"find-yarn-workspace-root": "^2.0.0",
|
||||
"fs-extra": "^7.0.1",
|
||||
"is-ci": "^2.0.0",
|
||||
"klaw-sync": "^6.0.0",
|
||||
"minimist": "^1.2.0",
|
||||
"open": "^7.4.2",
|
||||
"rimraf": "^2.6.3",
|
||||
"semver": "^5.6.0",
|
||||
"slash": "^2.0.0",
|
||||
"tmp": "^0.0.33"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
|
||||
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"path": {
|
||||
"version": "0.12.7",
|
||||
"resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz",
|
||||
|
@ -33366,7 +33528,11 @@
|
|||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
|
||||
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"bindings": "^1.5.0",
|
||||
"nan": "^2.12.1"
|
||||
}
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "3.1.0",
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
"package-plugin:zip-only": "rimraf woocommerce-gutenberg-products-block.zip && ./bin/build-plugin-zip.sh -z",
|
||||
"package-plugin:deploy": "npm run build:deploy && npm run package-plugin:zip-only",
|
||||
"phpunit": "docker-compose up -d db && docker-compose up -d --build wordpress-unit-tests && docker exec --workdir /var/www/html/wp-content/plugins/woocommerce-gutenberg-products-block wordpress_test php ./vendor/bin/phpunit",
|
||||
"postinstall": "patch-package",
|
||||
"reformat-files": "prettier --ignore-path .eslintignore --write \"**/*.{js,jsx,json,ts,tsx}\"",
|
||||
"release": "sh ./bin/wordpress-deploy.sh",
|
||||
"start": "rimraf build/* && cross-env BABEL_ENV=default CHECK_CIRCULAR_DEPS=true webpack --watch --info-verbosity none",
|
||||
|
@ -157,6 +158,7 @@
|
|||
"lodash": "4.17.21",
|
||||
"merge-config": "2.0.0",
|
||||
"mini-css-extract-plugin": "1.3.6",
|
||||
"patch-package": "^6.4.7",
|
||||
"postcss": "8.2.10",
|
||||
"postcss-loader": "4.2.0",
|
||||
"prettier": "npm:wp-prettier@2.0.5",
|
||||
|
|
|
@ -0,0 +1,267 @@
|
|||
diff --git a/node_modules/wordpress-components/build-module/combobox-control/index.js b/node_modules/wordpress-components/build-module/combobox-control/index.js
|
||||
index 51c59c1..61bda14 100644
|
||||
--- a/node_modules/wordpress-components/build-module/combobox-control/index.js
|
||||
+++ b/node_modules/wordpress-components/build-module/combobox-control/index.js
|
||||
@@ -1,19 +1,10 @@
|
||||
-import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
|
||||
-import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck";
|
||||
-import _createClass from "@babel/runtime/helpers/esm/createClass";
|
||||
-import _inherits from "@babel/runtime/helpers/esm/inherits";
|
||||
-import _possibleConstructorReturn from "@babel/runtime/helpers/esm/possibleConstructorReturn";
|
||||
-import _getPrototypeOf from "@babel/runtime/helpers/esm/getPrototypeOf";
|
||||
import { createElement } from "@wordpress/element";
|
||||
|
||||
-function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
|
||||
-
|
||||
-function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }
|
||||
-
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
+import { noop, deburr } from 'lodash';
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
@@ -34,77 +25,46 @@ import BaseControl from '../base-control';
|
||||
import Button from '../button';
|
||||
import { Flex, FlexBlock, FlexItem } from '../flex';
|
||||
import withFocusOutside from '../higher-order/with-focus-outside';
|
||||
-var DetectOutside = withFocusOutside( /*#__PURE__*/function (_Component) {
|
||||
- _inherits(_class, _Component);
|
||||
-
|
||||
- var _super = _createSuper(_class);
|
||||
-
|
||||
- function _class() {
|
||||
- _classCallCheck(this, _class);
|
||||
-
|
||||
- return _super.apply(this, arguments);
|
||||
+const DetectOutside = withFocusOutside(class extends Component {
|
||||
+ handleFocusOutside(event) {
|
||||
+ this.props.onFocusOutside(event);
|
||||
}
|
||||
|
||||
- _createClass(_class, [{
|
||||
- key: "handleFocusOutside",
|
||||
- value: function handleFocusOutside(event) {
|
||||
- this.props.onFocusOutside(event);
|
||||
- }
|
||||
- }, {
|
||||
- key: "render",
|
||||
- value: function render() {
|
||||
- return this.props.children;
|
||||
- }
|
||||
- }]);
|
||||
-
|
||||
- return _class;
|
||||
-}(Component));
|
||||
+ render() {
|
||||
+ return this.props.children;
|
||||
+ }
|
||||
|
||||
-function ComboboxControl(_ref) {
|
||||
+});
|
||||
+
|
||||
+function ComboboxControl({
|
||||
+ value,
|
||||
+ label,
|
||||
+ options,
|
||||
+ onChange,
|
||||
+ onFilterValueChange = noop,
|
||||
+ hideLabelFromVision,
|
||||
+ help,
|
||||
+ allowReset = true,
|
||||
+ className,
|
||||
+ messages = {
|
||||
+ selected: __('Item selected.')
|
||||
+ }
|
||||
+}) {
|
||||
var _currentOption$label;
|
||||
|
||||
- var value = _ref.value,
|
||||
- label = _ref.label,
|
||||
- options = _ref.options,
|
||||
- onChange = _ref.onChange,
|
||||
- onFilterValueChange = _ref.onFilterValueChange,
|
||||
- hideLabelFromVision = _ref.hideLabelFromVision,
|
||||
- help = _ref.help,
|
||||
- _ref$allowReset = _ref.allowReset,
|
||||
- allowReset = _ref$allowReset === void 0 ? true : _ref$allowReset,
|
||||
- className = _ref.className,
|
||||
- _ref$messages = _ref.messages,
|
||||
- messages = _ref$messages === void 0 ? {
|
||||
- selected: __('Item selected.')
|
||||
- } : _ref$messages;
|
||||
- var instanceId = useInstanceId(ComboboxControl);
|
||||
-
|
||||
- var _useState = useState(null),
|
||||
- _useState2 = _slicedToArray(_useState, 2),
|
||||
- selectedSuggestion = _useState2[0],
|
||||
- setSelectedSuggestion = _useState2[1];
|
||||
-
|
||||
- var _useState3 = useState(false),
|
||||
- _useState4 = _slicedToArray(_useState3, 2),
|
||||
- isExpanded = _useState4[0],
|
||||
- setIsExpanded = _useState4[1];
|
||||
-
|
||||
- var _useState5 = useState(''),
|
||||
- _useState6 = _slicedToArray(_useState5, 2),
|
||||
- inputValue = _useState6[0],
|
||||
- setInputValue = _useState6[1];
|
||||
-
|
||||
- var inputContainer = useRef();
|
||||
- var currentOption = options.find(function (option) {
|
||||
- return option.value === value;
|
||||
- });
|
||||
- var currentLabel = (_currentOption$label = currentOption === null || currentOption === void 0 ? void 0 : currentOption.label) !== null && _currentOption$label !== void 0 ? _currentOption$label : '';
|
||||
- var matchingSuggestions = useMemo(function () {
|
||||
- var startsWithMatch = [];
|
||||
- var containsMatch = [];
|
||||
- var match = inputValue.toLocaleLowerCase();
|
||||
- options.forEach(function (option) {
|
||||
- var index = option.label.toLocaleLowerCase().indexOf(match);
|
||||
+ const currentOption = options.find(option => option.value === value);
|
||||
+ const currentLabel = (_currentOption$label = currentOption === null || currentOption === void 0 ? void 0 : currentOption.label) !== null && _currentOption$label !== void 0 ? _currentOption$label : '';
|
||||
+ const instanceId = useInstanceId(ComboboxControl);
|
||||
+ const [selectedSuggestion, setSelectedSuggestion] = useState(currentOption || null);
|
||||
+ const [isExpanded, setIsExpanded] = useState(false);
|
||||
+ const [inputValue, setInputValue] = useState('');
|
||||
+ const inputContainer = useRef();
|
||||
+ const matchingSuggestions = useMemo(() => {
|
||||
+ const startsWithMatch = [];
|
||||
+ const containsMatch = [];
|
||||
+ const match = deburr(inputValue.toLocaleLowerCase());
|
||||
+ options.forEach(option => {
|
||||
+ const index = deburr(option.label).toLocaleLowerCase().indexOf(match);
|
||||
|
||||
if (index === 0) {
|
||||
startsWithMatch.push(option);
|
||||
@@ -115,7 +75,7 @@ function ComboboxControl(_ref) {
|
||||
return startsWithMatch.concat(containsMatch);
|
||||
}, [inputValue, options, value]);
|
||||
|
||||
- var onSuggestionSelected = function onSuggestionSelected(newSelectedSuggestion) {
|
||||
+ const onSuggestionSelected = newSelectedSuggestion => {
|
||||
onChange(newSelectedSuggestion.value);
|
||||
speak(messages.selected, 'assertive');
|
||||
setSelectedSuggestion(newSelectedSuggestion);
|
||||
@@ -123,10 +83,9 @@ function ComboboxControl(_ref) {
|
||||
setIsExpanded(false);
|
||||
};
|
||||
|
||||
- var handleArrowNavigation = function handleArrowNavigation() {
|
||||
- var offset = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
|
||||
- var index = matchingSuggestions.indexOf(selectedSuggestion);
|
||||
- var nextIndex = index + offset;
|
||||
+ const handleArrowNavigation = (offset = 1) => {
|
||||
+ const index = matchingSuggestions.indexOf(selectedSuggestion);
|
||||
+ let nextIndex = index + offset;
|
||||
|
||||
if (nextIndex < 0) {
|
||||
nextIndex = matchingSuggestions.length - 1;
|
||||
@@ -138,8 +97,12 @@ function ComboboxControl(_ref) {
|
||||
setIsExpanded(true);
|
||||
};
|
||||
|
||||
- var onKeyDown = function onKeyDown(event) {
|
||||
- var preventDefault = false;
|
||||
+ const onKeyDown = event => {
|
||||
+ let preventDefault = false;
|
||||
+
|
||||
+ if (event.defaultPrevented) {
|
||||
+ return;
|
||||
+ }
|
||||
|
||||
switch (event.keyCode) {
|
||||
case ENTER:
|
||||
@@ -164,7 +127,6 @@ function ComboboxControl(_ref) {
|
||||
setIsExpanded(false);
|
||||
setSelectedSuggestion(null);
|
||||
preventDefault = true;
|
||||
- event.stopPropagation();
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -176,34 +138,44 @@ function ComboboxControl(_ref) {
|
||||
}
|
||||
};
|
||||
|
||||
- var onFocus = function onFocus() {
|
||||
+ const onFocus = () => {
|
||||
setIsExpanded(true);
|
||||
onFilterValueChange('');
|
||||
setInputValue('');
|
||||
};
|
||||
|
||||
- var onFocusOutside = function onFocusOutside() {
|
||||
+ const onFocusOutside = () => {
|
||||
setIsExpanded(false);
|
||||
};
|
||||
|
||||
- var onInputChange = function onInputChange(event) {
|
||||
- var text = event.value;
|
||||
+ const onInputChange = event => {
|
||||
+ const text = event.value;
|
||||
setInputValue(text);
|
||||
onFilterValueChange(text);
|
||||
setIsExpanded(true);
|
||||
};
|
||||
|
||||
- var handleOnReset = function handleOnReset() {
|
||||
+ const handleOnReset = () => {
|
||||
onChange(null);
|
||||
inputContainer.current.input.focus();
|
||||
- }; // Announcements
|
||||
+ }; // Update current selections when the filter input changes.
|
||||
|
||||
|
||||
- useEffect(function () {
|
||||
- var hasMatchingSuggestions = matchingSuggestions.length > 0;
|
||||
+ useEffect(() => {
|
||||
+ const hasMatchingSuggestions = matchingSuggestions.length > 0;
|
||||
+ const hasSelectedMatchingSuggestions = matchingSuggestions.indexOf(selectedSuggestion) > 0;
|
||||
+
|
||||
+ if (hasMatchingSuggestions && !hasSelectedMatchingSuggestions) {
|
||||
+ // If the current selection isn't present in the list of suggestions, then automatically select the first item from the list of suggestions.
|
||||
+ setSelectedSuggestion(matchingSuggestions[0]);
|
||||
+ }
|
||||
+ }, [matchingSuggestions, selectedSuggestion]); // Announcements
|
||||
+
|
||||
+ useEffect(() => {
|
||||
+ const hasMatchingSuggestions = matchingSuggestions.length > 0;
|
||||
|
||||
if (isExpanded) {
|
||||
- var message = hasMatchingSuggestions ? sprintf(
|
||||
+ const message = hasMatchingSuggestions ? sprintf(
|
||||
/* translators: %d: number of results. */
|
||||
_n('%d result found, use up and down arrow keys to navigate.', '%d results found, use up and down arrow keys to navigate.', matchingSuggestions.length), matchingSuggestions.length) : __('No results.');
|
||||
speak(message, 'polite');
|
||||
@@ -220,7 +192,7 @@ function ComboboxControl(_ref) {
|
||||
className: classnames(className, 'components-combobox-control'),
|
||||
tabIndex: "-1",
|
||||
label: label,
|
||||
- id: "components-form-token-input-".concat(instanceId),
|
||||
+ id: `components-form-token-input-${instanceId}`,
|
||||
hideLabelFromVision: hideLabelFromVision,
|
||||
help: help
|
||||
}, createElement("div", {
|
||||
@@ -232,7 +204,7 @@ function ComboboxControl(_ref) {
|
||||
instanceId: instanceId,
|
||||
ref: inputContainer,
|
||||
value: isExpanded ? inputValue : currentLabel,
|
||||
- "aria-label": currentLabel ? "".concat(currentLabel, ", ").concat(label) : null,
|
||||
+ "aria-label": currentLabel ? `${currentLabel}, ${label}` : null,
|
||||
onFocus: onFocus,
|
||||
isExpanded: isExpanded,
|
||||
selectedSuggestionIndex: matchingSuggestions.indexOf(selectedSuggestion),
|
||||
@@ -248,9 +220,7 @@ function ComboboxControl(_ref) {
|
||||
match: {
|
||||
label: inputValue
|
||||
},
|
||||
- displayTransform: function displayTransform(suggestion) {
|
||||
- return suggestion.label;
|
||||
- },
|
||||
+ displayTransform: suggestion => suggestion.label,
|
||||
suggestions: matchingSuggestions,
|
||||
selectedIndex: matchingSuggestions.indexOf(selectedSuggestion),
|
||||
onHover: setSelectedSuggestion,
|
Loading…
Reference in New Issue