Skip to content

Commit

Permalink
Prevent interference of value entry in number input
Browse files Browse the repository at this point in the history
  • Loading branch information
stokesman committed May 2, 2022
1 parent 3800d28 commit ff6e10a
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 28 deletions.
50 changes: 22 additions & 28 deletions packages/components/src/range-control/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import BaseControl from '../base-control';
import Button from '../button';
import Icon from '../icon';
import { COLORS, useControlledValue } from '../utils';
import { floatClamp } from './utils';
import { useUnimpededRangedNumberEntry } from './utils';
import InputRange from './input-range';
import RangeRail from './rail';
import SimpleTooltip from './tooltip';
Expand Down Expand Up @@ -73,29 +73,17 @@ function RangeControl(
const isResetPendent = useRef( false );
const [ value, setValue ] = useControlledValue( {
defaultValue: initialPosition ?? null,
value: isResetPendent.current ? undefined : valueProp,
value: valueProp,
onChange: ( nextValue ) => {
/*
* Calls onChange only when nextValue is numeric
* otherwise may queue a reset for the blur event.
*/
if ( ! isNaN( nextValue ) ) {
if ( nextValue < min || nextValue > max ) {
nextValue = floatClamp( nextValue, min, max );
}
onChange( nextValue );
isResetPendent.current = false;
} else if ( nextValue === null ) {
if ( nextValue === null ) {
/*
* handleOnReset has led the execution here due to lack of a
* defined resetFallbackValue. In such conditions the onChange
* callback receives undefined as that was the behavior when the
* component was stablized.
* The value is reset without a resetFallbackValue. In such
* conditions the onChange callback receives undefined as that
* was the behavior when the component was stablized.
*/
onChange( undefined );
} else if ( allowReset ) {
isResetPendent.current = true;
nextValue = undefined;
}
onChange( nextValue );
},
} );

Expand Down Expand Up @@ -149,10 +137,19 @@ function RangeControl(
setValue( nextValue );
};

const handleOnChange = ( nextValue ) => {
nextValue = parseFloat( nextValue );
setValue( nextValue );
};
const someNumberInputProps = useUnimpededRangedNumberEntry( {
max,
min,
value: inputSliderValue,
onChange: ( nextValue ) => {
if ( ! isNaN( nextValue ) ) {
setValue( nextValue );
isResetPendent.current = false;
} else if ( allowReset ) {
isResetPendent.current = true;
}
},
} );

const handleOnInputNumberBlur = () => {
if ( isResetPendent.current ) {
Expand Down Expand Up @@ -275,13 +272,10 @@ function RangeControl(
disabled={ disabled }
inputMode="decimal"
isShiftStepEnabled={ isShiftStepEnabled }
max={ max }
min={ min }
onBlur={ handleOnInputNumberBlur }
onChange={ handleOnChange }
shiftStep={ shiftStep }
step={ step }
value={ inputSliderValue }
{ ...someNumberInputProps }
/>
) }
{ allowReset && (
Expand Down
37 changes: 37 additions & 0 deletions packages/components/src/range-control/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,43 @@ export function floatClamp( value, min, max ) {
return parseFloat( clamp( value, min, max ) );
}

/**
* Enables entry of out-of-range and invalid values that diverge from state.
*
* @param {Object} props Props
* @param {number|null} props.value Incoming value.
* @param {number} props.max Maximum valid value.
* @param {number} props.min Minimum valid value.
* @param {(next: number) => void} props.onChange Callback for changes.
*
* @return {Object} Assorted props for the input.
*/
export function useUnimpededRangedNumberEntry( { max, min, onChange, value } ) {
const ref = useRef();
const isDiverging = useRef( false );
/** @type {import('../input-control/types').InputChangeCallback}*/
const changeHandler = ( next ) => {
next = parseFloat( next );
const isOverflow = next < min || next > max;
isDiverging.current = isNaN( next ) || isOverflow;
if ( isOverflow ) {
next = Math.max( min, Math.min( max, next ) );
}
onChange( next );
};
useEffect( () => {
if ( ref.current && isDiverging.current ) {
const input = ref.current;
const entry = input.value;
const { defaultView } = input.ownerDocument;
defaultView.requestAnimationFrame( () => ( input.value = entry ) );
isDiverging.current = false;
}
}, [ value ] );

return { max, min, ref, value, onChange: changeHandler };
}

/**
* Hook to encapsulate the debouncing "hover" to better handle the showing
* and hiding of the Tooltip.
Expand Down

0 comments on commit ff6e10a

Please sign in to comment.