diff --git a/settings/src/components/auto-tabbing-input.js b/settings/src/components/auto-tabbing-input.js
index 1e00e12e..e89c4115 100644
--- a/settings/src/components/auto-tabbing-input.js
+++ b/settings/src/components/auto-tabbing-input.js
@@ -9,44 +9,60 @@ import { useCallback } from '@wordpress/element';
import NumericControl from './numeric-control';
const AutoTabbingInput = ( props ) => {
- const { inputs, setInputs, onComplete, error } = props;
+ const { inputs, setInputs, error, setError } = props;
- const handleChange = useCallback( ( value, event, index, inputRef ) => {
+ const handleChange = useCallback( ( value, event, index ) => {
setInputs( ( prevInputs ) => {
const newInputs = [ ...prevInputs ];
- // Clean input
- if ( value.trim() === '' ) {
- event.target.value = '';
- value = '';
- }
-
- newInputs[ index ] = value;
-
- // Check if all inputs are filled
- const allFilled = newInputs.every( ( input ) => '' !== input );
- if ( allFilled && onComplete ) {
- onComplete( true );
- } else {
- onComplete( false );
- }
+ newInputs[ index ] = value.trim() === '' ? '' : value;
return newInputs;
} );
+ }, [] );
+
+ const handleKeyDown = useCallback( ( value, event, index, inputElement ) => {
+ // Ignore keys associated with input navigation and paste events.
+ if ( [ 'Tab', 'Shift', 'Meta', 'Backspace' ].includes( event.key ) ) {
+ return;
+ }
+
+ if ( !! value && inputElement.nextElementSibling ) {
+ inputElement.nextElementSibling.focus();
+ }
+ }, [] );
- if ( value && '' !== value.trim() && inputRef.current.nextElementSibling ) {
- inputRef.current.nextElementSibling.focus();
+ const handleKeyUp = useCallback( ( value, event, index, inputElement ) => {
+ if ( event.key === 'Backspace' && inputElement.previousElementSibling ) {
+ inputElement.previousElementSibling.focus();
}
}, [] );
- const handleKeyDown = useCallback( ( value, event, index, inputRef ) => {
- if ( event.key === 'Backspace' && ! value && inputRef.current.previousElementSibling ) {
- inputRef.current.previousElementSibling.focus();
+ const handleFocus = useCallback(
+ ( value, event, index, inputElement ) => inputElement.select(),
+ []
+ );
+
+ const handlePaste = useCallback( ( event ) => {
+ event.preventDefault();
+
+ const newInputs = event.clipboardData
+ .getData( 'Text' )
+ .replace( /[^0-9]/g, '' )
+ .split( '' );
+
+ if ( inputs.length === newInputs.length ) {
+ setInputs( newInputs );
+ } else {
+ setError( 'The code you pasted is not the correct length.' );
}
}, [] );
return (
-
+
{ inputs.map( ( value, index ) => (
{
index={ index }
onChange={ handleChange }
onKeyDown={ handleKeyDown }
+ onKeyUp={ handleKeyUp }
+ onFocus={ handleFocus }
maxLength="1"
required
/>
diff --git a/settings/src/components/numeric-control.js b/settings/src/components/numeric-control.js
index 78429427..b13fd64c 100644
--- a/settings/src/components/numeric-control.js
+++ b/settings/src/components/numeric-control.js
@@ -13,27 +13,47 @@ import { useRef, useCallback } from '@wordpress/element';
* using the underlying `input[type="number"]`, which has some accessibility issues.
*
* @param props
+ * @param props.autoComplete
+ * @param props.pattern
+ * @param props.title
+ * @param props.onChange
+ * @param props.onFocus
+ * @param props.onKeyDown
+ * @param props.onKeyUp
+ * @param props.index
+ * @param props.value
+ * @param props.maxLength
+ * @param props.required
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number#accessibility
* @see https://technology.blog.gov.uk/2020/02/24/why-the-gov-uk-design-system-team-changed-the-input-type-for-numbers/
* @see https://stackoverflow.com/a/66759105/450127
*/
-export default function NumericControl( props ) {
- const { autoComplete, pattern, title, onChange, onKeyDown, index, value, maxLength, required } =
- props;
-
+export default function NumericControl( {
+ autoComplete,
+ pattern,
+ title,
+ onChange,
+ onFocus,
+ onKeyDown,
+ onKeyUp,
+ index,
+ value,
+ maxLength,
+ required,
+} ) {
const inputRef = useRef( null );
- const handleChange = useCallback(
+ const createHandler = ( handler ) => ( event ) =>
// Most callers will only need the value, so make it convenient for them.
- ( event ) => onChange && onChange( event.target.value, event, index, inputRef ),
- []
- );
+ handler && handler( event.target.value, event, index, inputRef.current );
- const handleKeyDown = useCallback(
- // Most callers will only need the value, so make it convenient for them.
- ( event ) => onKeyDown && onKeyDown( event.target.value, event, index, inputRef ),
- []
- );
+ const handleChange = useCallback( createHandler( onChange ), [] );
+
+ const handleFocus = useCallback( createHandler( onFocus ), [] );
+
+ const handleKeyDown = useCallback( createHandler( onKeyDown ), [] );
+
+ const handleKeyUp = useCallback( createHandler( onKeyUp ), [] );
return (
{
- if ( error && inputs.some( ( input ) => input === '' ) ) {
+ const prevInputs = inputsRef.current;
+ inputsRef.current = inputs;
+
+ // Clear the error if any of the inputs have changed
+ if ( error && inputs.some( ( input, index ) => input !== prevInputs[ index ] ) ) {
setError( '' );
}
- }, [ error, inputs ] );
+ }, [ error, inputs, inputsRef ] );
- const handleComplete = useCallback( ( isComplete ) => setIsInputComplete( isComplete ), [] );
- const handleClearClick = useCallback( () => setInputs( Array( 6 ).fill( '' ) ), [] );
+ const handleClearClick = useCallback( () => {
+ setInputs( Array( 6 ).fill( '' ) );
+ }, [] );
- const canSubmit = qrCodeUrl && secretKey && isInputComplete;
+ const canSubmit = qrCodeUrl && secretKey && inputs.every( ( input ) => !! input );
return (