Add step buttons for Number block and component (#43045)
* Add showStepButtons attribute for Number block and component
* Use native step buttons
* Revert "Use native step buttons"
This reverts commit 8425d17399
.
* Use custom step buttons and hide them when field is not focused
* Revert type="number" as it's not working well with number formatting
* Disable autocomplete
* Remove leftover showStepButtons from documentation
* Prevent default event when pressing up/down
* Change to onKeyDown event
* Implement timeout to hold + and - buttons
* Remove unnecessary onBlur and prevent default event when onKeyUp
* Fix TS issue
* Update changelog
This commit is contained in:
parent
4114f989e5
commit
548987d758
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add step buttons for Number block and component
|
|
@ -1,11 +1,20 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createElement } from '@wordpress/element';
|
||||
import {
|
||||
createElement,
|
||||
Fragment,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from '@wordpress/element';
|
||||
import { useInstanceId } from '@wordpress/compose';
|
||||
import classNames from 'classnames';
|
||||
import { plus, reset } from '@wordpress/icons';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import {
|
||||
BaseControl,
|
||||
Button,
|
||||
// @ts-expect-error `__experimentalInputControl` does exist.
|
||||
__experimentalInputControl as InputControl,
|
||||
} from '@wordpress/components';
|
||||
|
@ -43,14 +52,45 @@ export const NumberControl: React.FC< NumberProps > = ( {
|
|||
tooltip,
|
||||
placeholder,
|
||||
disabled,
|
||||
step,
|
||||
step = 1,
|
||||
}: NumberProps ) => {
|
||||
const id = useInstanceId( BaseControl, 'product_number_field' ) as string;
|
||||
const [ isFocused, setIsFocused ] = useState( false );
|
||||
const unfocusIfOutside = ( event: React.FocusEvent ) => {
|
||||
if (
|
||||
! document
|
||||
.getElementById( id )
|
||||
?.parentElement?.contains( event.relatedTarget )
|
||||
) {
|
||||
setIsFocused( false );
|
||||
onBlur?.();
|
||||
}
|
||||
};
|
||||
const inputProps = useNumberInputProps( {
|
||||
value: value || '',
|
||||
onChange,
|
||||
onFocus: () => setIsFocused( true ),
|
||||
} );
|
||||
|
||||
const id = useInstanceId( BaseControl, 'product_number_field' ) as string;
|
||||
const [ increment, setIncrement ] = useState( 0 );
|
||||
|
||||
const timeoutRef = useRef< number | null >( null );
|
||||
|
||||
const incrementValue = () =>
|
||||
onChange( String( parseFloat( value || '0' ) + increment ) );
|
||||
|
||||
useEffect( () => {
|
||||
if ( increment !== 0 ) {
|
||||
timeoutRef.current = setTimeout( incrementValue, 100 );
|
||||
} else if ( timeoutRef.current ) {
|
||||
clearTimeout( timeoutRef.current );
|
||||
}
|
||||
return () => {
|
||||
if ( timeoutRef.current ) {
|
||||
clearTimeout( timeoutRef.current );
|
||||
}
|
||||
};
|
||||
}, [ increment, value ] );
|
||||
|
||||
return (
|
||||
<BaseControl
|
||||
|
@ -71,10 +111,64 @@ export const NumberControl: React.FC< NumberProps > = ( {
|
|||
{ ...inputProps }
|
||||
step={ step }
|
||||
disabled={ disabled }
|
||||
autoComplete="off"
|
||||
id={ id }
|
||||
suffix={ suffix }
|
||||
className="woocommerce-number-control"
|
||||
suffix={
|
||||
<>
|
||||
{ suffix }
|
||||
{ isFocused && (
|
||||
<>
|
||||
<Button
|
||||
className="woocommerce-number-control__increment"
|
||||
icon={ plus }
|
||||
onMouseDown={ () => {
|
||||
onChange(
|
||||
String(
|
||||
parseFloat( value || '0' ) +
|
||||
step
|
||||
)
|
||||
);
|
||||
setIncrement( step );
|
||||
} }
|
||||
onMouseUp={ () => setIncrement( 0 ) }
|
||||
onBlur={ unfocusIfOutside }
|
||||
isSmall
|
||||
aria-hidden="true"
|
||||
aria-label={ __(
|
||||
'Increment',
|
||||
'woocommerce'
|
||||
) }
|
||||
tabIndex={ -1 }
|
||||
/>
|
||||
<Button
|
||||
icon={ reset }
|
||||
className="woocommerce-number-control__decrement"
|
||||
onBlur={ unfocusIfOutside }
|
||||
onMouseDown={ () => {
|
||||
onChange(
|
||||
String(
|
||||
parseFloat( value || '0' ) -
|
||||
step
|
||||
)
|
||||
);
|
||||
setIncrement( -step );
|
||||
} }
|
||||
onMouseUp={ () => setIncrement( 0 ) }
|
||||
isSmall
|
||||
aria-hidden="true"
|
||||
aria-label={ __(
|
||||
'Decrement',
|
||||
'woocommerce'
|
||||
) }
|
||||
tabIndex={ -1 }
|
||||
/>
|
||||
</>
|
||||
) }
|
||||
</>
|
||||
}
|
||||
placeholder={ placeholder }
|
||||
onBlur={ onBlur }
|
||||
onBlur={ unfocusIfOutside }
|
||||
/>
|
||||
</BaseControl>
|
||||
);
|
||||
|
|
|
@ -8,6 +8,7 @@ export type NumberInputProps = {
|
|||
value: string;
|
||||
onChange: ( value: string ) => void;
|
||||
onFocus: ( event: React.FocusEvent< HTMLInputElement > ) => void;
|
||||
onKeyDown: ( event: React.KeyboardEvent< HTMLInputElement > ) => void;
|
||||
onKeyUp: ( event: React.KeyboardEvent< HTMLInputElement > ) => void;
|
||||
};
|
||||
|
||||
|
@ -15,14 +16,14 @@ type Props = {
|
|||
value: string;
|
||||
onChange: ( value: string ) => void;
|
||||
onFocus?: ( event: React.FocusEvent< HTMLInputElement > ) => void;
|
||||
onKeyUp?: ( event: React.KeyboardEvent< HTMLInputElement > ) => void;
|
||||
onKeyDown?: ( event: React.KeyboardEvent< HTMLInputElement > ) => void;
|
||||
};
|
||||
|
||||
export const useNumberInputProps = ( {
|
||||
value,
|
||||
onChange,
|
||||
onFocus,
|
||||
onKeyUp,
|
||||
onKeyDown,
|
||||
}: Props ) => {
|
||||
const { formatNumber, parseNumber } = useProductHelper();
|
||||
|
||||
|
@ -35,16 +36,23 @@ export const useNumberInputProps = ( {
|
|||
}
|
||||
},
|
||||
onKeyUp( event: React.KeyboardEvent< HTMLInputElement > ) {
|
||||
if ( event.code === 'ArrowUp' || event.code === 'ArrowDown' ) {
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
onKeyDown( event: React.KeyboardEvent< HTMLInputElement > ) {
|
||||
const amount = Number.parseFloat( value || '0' );
|
||||
const step = Number( event.currentTarget.step || '1' );
|
||||
if ( event.code === 'ArrowUp' ) {
|
||||
event.preventDefault();
|
||||
onChange( String( amount + step ) );
|
||||
}
|
||||
if ( event.code === 'ArrowDown' ) {
|
||||
event.preventDefault();
|
||||
onChange( String( amount - step ) );
|
||||
}
|
||||
if ( onKeyUp ) {
|
||||
onKeyUp( event );
|
||||
if ( onKeyDown ) {
|
||||
onKeyDown( event );
|
||||
}
|
||||
},
|
||||
onChange( newValue: string ) {
|
||||
|
|
Loading…
Reference in New Issue