Implement browser autocomplete for checkout address fields (https://github.com/woocommerce/woocommerce-blocks/pull/1755)
* Add autocomplete support for textinput * Add autocomplete fields to forms * Prefix default ids * Hack for autocomplete on custom select components * Restore labels and avoid reset of state * State field autocomplete * Fix calculator autocomplete * Simplify existance of hidden field * move label on autofill preview in chrome * Put back state clearance Co-authored-by: Seghir Nadir <nadir.seghir@gmail.com>
This commit is contained in:
parent
7b53486be3
commit
2a25cfd0ed
|
@ -15,6 +15,7 @@ const CountryInput = ( {
|
|||
label,
|
||||
onChange,
|
||||
value = '',
|
||||
autoComplete = 'off',
|
||||
} ) => {
|
||||
const options = Object.keys( countries ).map( ( key ) => ( {
|
||||
key,
|
||||
|
@ -22,6 +23,7 @@ const CountryInput = ( {
|
|||
} ) );
|
||||
|
||||
return (
|
||||
<>
|
||||
<Select
|
||||
className={ className }
|
||||
label={ label }
|
||||
|
@ -29,6 +31,28 @@ const CountryInput = ( {
|
|||
options={ options }
|
||||
value={ options.find( ( option ) => option.key === value ) }
|
||||
/>
|
||||
{ autoComplete !== 'off' && (
|
||||
<input
|
||||
type="text"
|
||||
aria-hidden={ true }
|
||||
autoComplete={ autoComplete }
|
||||
value={ value }
|
||||
onChange={ ( event ) => {
|
||||
const textValue = event.target.value;
|
||||
const foundOption = options.find(
|
||||
( option ) => option.key === textValue
|
||||
);
|
||||
onChange( foundOption ? foundOption.key : '' );
|
||||
} }
|
||||
style={ {
|
||||
height: '0',
|
||||
border: '0',
|
||||
padding: '0',
|
||||
position: 'absolute',
|
||||
} }
|
||||
/>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -38,6 +62,7 @@ CountryInput.propTypes = {
|
|||
className: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
autoComplete: PropTypes.string,
|
||||
};
|
||||
|
||||
export default CountryInput;
|
||||
|
|
|
@ -19,7 +19,7 @@ const ShippingCalculatorAddress = ( { address: initialAddress, onUpdate } ) => {
|
|||
const [ address, setAddress ] = useState( initialAddress );
|
||||
|
||||
return (
|
||||
<div className="wc-block-shipping-calculator-address">
|
||||
<form className="wc-block-shipping-calculator-address">
|
||||
<ShippingCountryInput
|
||||
className="wc-block-shipping-calculator-address__input"
|
||||
label={ __(
|
||||
|
@ -27,6 +27,7 @@ const ShippingCalculatorAddress = ( { address: initialAddress, onUpdate } ) => {
|
|||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
value={ address.country }
|
||||
autoComplete="country"
|
||||
onChange={ ( newValue ) =>
|
||||
setAddress( {
|
||||
...address,
|
||||
|
@ -40,6 +41,7 @@ const ShippingCalculatorAddress = ( { address: initialAddress, onUpdate } ) => {
|
|||
country={ address.country }
|
||||
label={ __( 'State / County', 'woo-gutenberg-products-block' ) }
|
||||
value={ address.state }
|
||||
autoComplete="address-level1"
|
||||
onChange={ ( newValue ) =>
|
||||
setAddress( {
|
||||
...address,
|
||||
|
@ -51,6 +53,7 @@ const ShippingCalculatorAddress = ( { address: initialAddress, onUpdate } ) => {
|
|||
className="wc-block-shipping-calculator-address__input"
|
||||
label={ __( 'City', 'woo-gutenberg-products-block' ) }
|
||||
value={ address.city }
|
||||
autoComplete="address-level2"
|
||||
onChange={ ( newValue ) =>
|
||||
setAddress( {
|
||||
...address,
|
||||
|
@ -62,6 +65,7 @@ const ShippingCalculatorAddress = ( { address: initialAddress, onUpdate } ) => {
|
|||
className="wc-block-shipping-calculator-address__input"
|
||||
label={ __( 'Postal code', 'woo-gutenberg-products-block' ) }
|
||||
value={ address.postcode }
|
||||
autoComplete="postal-code"
|
||||
onChange={ ( newValue ) =>
|
||||
setAddress( {
|
||||
...address,
|
||||
|
@ -76,7 +80,7 @@ const ShippingCalculatorAddress = ( { address: initialAddress, onUpdate } ) => {
|
|||
>
|
||||
{ __( 'Update', 'woo-gutenberg-products-block' ) }
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*/
|
||||
import PropTypes from 'prop-types';
|
||||
import { decodeEntities } from '@wordpress/html-entities';
|
||||
import { useCallback } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -16,33 +17,74 @@ const StateInput = ( {
|
|||
country,
|
||||
label,
|
||||
onChange,
|
||||
autoComplete = 'off',
|
||||
value = '',
|
||||
} ) => {
|
||||
const countryCounties = counties[ country ];
|
||||
if ( ! countryCounties || Object.keys( countryCounties ).length === 0 ) {
|
||||
return (
|
||||
<TextInput
|
||||
className={ className }
|
||||
label={ label }
|
||||
onChange={ onChange }
|
||||
value={ value }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const options = Object.keys( countryCounties ).map( ( key ) => ( {
|
||||
const options =
|
||||
countryCounties && Object.keys( countryCounties ).length > 0
|
||||
? Object.keys( countryCounties ).map( ( key ) => ( {
|
||||
key,
|
||||
name: decodeEntities( countryCounties[ key ] ),
|
||||
} ) );
|
||||
} ) )
|
||||
: [];
|
||||
|
||||
return (
|
||||
/**
|
||||
* Handles state selection onChange events. Finds a matching state by key or value.
|
||||
*
|
||||
* @param {Object} event event data.
|
||||
*/
|
||||
const onChangeState = useCallback(
|
||||
( stateValue ) => {
|
||||
if ( options.length > 0 ) {
|
||||
const foundOption = options.find(
|
||||
( option ) =>
|
||||
option.key === stateValue || option.name === stateValue
|
||||
);
|
||||
|
||||
onChange( foundOption ? foundOption.key : '' );
|
||||
return;
|
||||
}
|
||||
onChange( stateValue );
|
||||
},
|
||||
[ onChange, options ]
|
||||
);
|
||||
|
||||
return options.length > 0 ? (
|
||||
<>
|
||||
<Select
|
||||
className={ className }
|
||||
label={ label }
|
||||
onChange={ onChange }
|
||||
onChange={ onChangeState }
|
||||
options={ options }
|
||||
value={ options.find( ( option ) => option.key === value ) }
|
||||
/>
|
||||
{ autoComplete !== 'off' && (
|
||||
<input
|
||||
type="text"
|
||||
aria-hidden={ true }
|
||||
autoComplete={ autoComplete }
|
||||
value={ value }
|
||||
onChange={ ( event ) =>
|
||||
onChangeState( event.target.value )
|
||||
}
|
||||
style={ {
|
||||
height: '0',
|
||||
border: '0',
|
||||
padding: '0',
|
||||
position: 'absolute',
|
||||
} }
|
||||
/>
|
||||
) }
|
||||
</>
|
||||
) : (
|
||||
<TextInput
|
||||
className={ className }
|
||||
label={ label }
|
||||
onChange={ onChangeState }
|
||||
autoComplete={ autoComplete }
|
||||
value={ value }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -54,6 +96,7 @@ StateInput.propTypes = {
|
|||
] )
|
||||
).isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
autoComplete: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
country: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
|
|
|
@ -22,12 +22,13 @@ const TextInput = ( {
|
|||
screenReaderLabel,
|
||||
disabled,
|
||||
help,
|
||||
autoComplete = 'off',
|
||||
value = '',
|
||||
onChange,
|
||||
} ) => {
|
||||
const [ isActive, setIsActive ] = useState( false );
|
||||
const onChangeValue = ( event ) => onChange( event.target.value );
|
||||
const textInputId = id || componentId;
|
||||
const textInputId = id || 'textinput-' + componentId;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -35,19 +36,11 @@ const TextInput = ( {
|
|||
'is-active': isActive || value,
|
||||
} ) }
|
||||
>
|
||||
<Label
|
||||
label={ label }
|
||||
screenReaderLabel={ screenReaderLabel || label }
|
||||
wrapperElement="label"
|
||||
wrapperProps={ {
|
||||
htmlFor: textInputId,
|
||||
} }
|
||||
htmlFor={ textInputId }
|
||||
/>
|
||||
<input
|
||||
type={ type }
|
||||
id={ textInputId }
|
||||
value={ value }
|
||||
autoComplete={ autoComplete }
|
||||
onChange={ onChangeValue }
|
||||
onFocus={ () => setIsActive( true ) }
|
||||
onBlur={ () => setIsActive( false ) }
|
||||
|
@ -57,6 +50,15 @@ const TextInput = ( {
|
|||
!! help ? textInputId + '__help' : undefined
|
||||
}
|
||||
/>
|
||||
<Label
|
||||
label={ label }
|
||||
screenReaderLabel={ screenReaderLabel || label }
|
||||
wrapperElement="label"
|
||||
wrapperProps={ {
|
||||
htmlFor: textInputId,
|
||||
} }
|
||||
htmlFor={ textInputId }
|
||||
/>
|
||||
{ !! help && (
|
||||
<p
|
||||
id={ textInputId + '__help' }
|
||||
|
@ -78,6 +80,7 @@ TextInput.propTypes = {
|
|||
screenReaderLabel: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
help: PropTypes.string,
|
||||
autoComplete: PropTypes.string,
|
||||
};
|
||||
|
||||
export default withComponentId( TextInput );
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
.wc-block-text-input label {
|
||||
position: absolute;
|
||||
transform: translateY(#{$gap-small});
|
||||
left: 0;
|
||||
transform-origin: top left;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
|
@ -20,7 +21,9 @@
|
|||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-text-input input:-webkit-autofill + label {
|
||||
transform: translateY(#{$gap-smallest}) scale(0.75);
|
||||
}
|
||||
.wc-block-text-input.is-active label {
|
||||
transform: translateY(#{$gap-smallest}) scale(0.75);
|
||||
}
|
||||
|
|
|
@ -73,6 +73,7 @@ const Block = ( { shippingMethods = [], isEditor = false } ) => {
|
|||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
value={ contactFields.email }
|
||||
autoComplete="email"
|
||||
onChange={ ( newValue ) =>
|
||||
setContactFields( {
|
||||
...contactFields,
|
||||
|
@ -115,6 +116,7 @@ const Block = ( { shippingMethods = [], isEditor = false } ) => {
|
|||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
value={ shippingFields.firstName }
|
||||
autoComplete="given-name"
|
||||
onChange={ ( newValue ) =>
|
||||
setShippingFields( {
|
||||
...shippingFields,
|
||||
|
@ -128,6 +130,7 @@ const Block = ( { shippingMethods = [], isEditor = false } ) => {
|
|||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
value={ shippingFields.lastName }
|
||||
autoComplete="family-name"
|
||||
onChange={ ( newValue ) =>
|
||||
setShippingFields( {
|
||||
...shippingFields,
|
||||
|
@ -142,6 +145,7 @@ const Block = ( { shippingMethods = [], isEditor = false } ) => {
|
|||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
value={ shippingFields.streetAddress }
|
||||
autoComplete="address-line1"
|
||||
onChange={ ( newValue ) =>
|
||||
setShippingFields( {
|
||||
...shippingFields,
|
||||
|
@ -155,6 +159,7 @@ const Block = ( { shippingMethods = [], isEditor = false } ) => {
|
|||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
value={ shippingFields.apartment }
|
||||
autoComplete="address-line2"
|
||||
onChange={ ( newValue ) =>
|
||||
setShippingFields( {
|
||||
...shippingFields,
|
||||
|
@ -169,6 +174,7 @@ const Block = ( { shippingMethods = [], isEditor = false } ) => {
|
|||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
value={ shippingFields.country }
|
||||
autoComplete="country"
|
||||
onChange={ ( newValue ) =>
|
||||
setShippingFields( {
|
||||
...shippingFields,
|
||||
|
@ -183,6 +189,7 @@ const Block = ( { shippingMethods = [], isEditor = false } ) => {
|
|||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
value={ shippingFields.city }
|
||||
autoComplete="address-level2"
|
||||
onChange={ ( newValue ) =>
|
||||
setShippingFields( {
|
||||
...shippingFields,
|
||||
|
@ -199,6 +206,7 @@ const Block = ( { shippingMethods = [], isEditor = false } ) => {
|
|||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
value={ shippingFields.state }
|
||||
autoComplete="address-level1"
|
||||
onChange={ ( newValue ) =>
|
||||
setShippingFields( {
|
||||
...shippingFields,
|
||||
|
@ -212,6 +220,7 @@ const Block = ( { shippingMethods = [], isEditor = false } ) => {
|
|||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
value={ shippingFields.postcode }
|
||||
autoComplete="postal-code"
|
||||
onChange={ ( newValue ) =>
|
||||
setShippingFields( {
|
||||
...shippingFields,
|
||||
|
@ -224,6 +233,7 @@ const Block = ( { shippingMethods = [], isEditor = false } ) => {
|
|||
type="tel"
|
||||
label={ __( 'Phone', 'woo-gutenberg-products-block' ) }
|
||||
value={ shippingFields.phone }
|
||||
autoComplete="tel"
|
||||
onChange={ ( newValue ) =>
|
||||
setShippingFields( {
|
||||
...shippingFields,
|
||||
|
|
Loading…
Reference in New Issue