Add reference to number control (#49357)

* Add ref to NumberControl

* Add identifier

# Conflicts:
#	packages/js/product-editor/src/hooks/use-error-handler.ts

* Remove console.log

* Add changelog

* Stop using setTimeout and use mutation observer to check if the tab is visible before focusing field

* Rename prop

* Refactor number-control functions

* Fix lint

* resetIncrement funciton

---------

Co-authored-by: Nathan Schneider <nsschneider1@gmail.com>
This commit is contained in:
Fernando Marichal 2024-07-15 15:49:19 -03:00 committed by GitHub
parent 6bc551c71b
commit 67ecc95633
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 207 additions and 160 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Add reference to number control #49357

View File

@ -88,12 +88,14 @@ export function Edit( {
}; };
} }
const widthFieldId = `dimensions_width-${ clientId }`;
const { const {
ref: dimensionsWidthRef, ref: dimensionsWidthRef,
error: dimensionsWidthValidationError, error: dimensionsWidthValidationError,
validate: validateDimensionsWidth, validate: validateDimensionsWidth,
} = useValidation< Product >( } = useValidation< Product >(
`dimensions_width-${ clientId }`, widthFieldId,
async function dimensionsWidthValidator() { async function dimensionsWidthValidator() {
if ( dimensions?.width && +dimensions.width <= 0 ) { if ( dimensions?.width && +dimensions.width <= 0 ) {
return { return {
@ -108,12 +110,14 @@ export function Edit( {
[ dimensions?.width ] [ dimensions?.width ]
); );
const lengthFieldId = `dimensions_length-${ clientId }`;
const { const {
ref: dimensionsLengthRef, ref: dimensionsLengthRef,
error: dimensionsLengthValidationError, error: dimensionsLengthValidationError,
validate: validateDimensionsLength, validate: validateDimensionsLength,
} = useValidation< Product >( } = useValidation< Product >(
`dimensions_length-${ clientId }`, lengthFieldId,
async function dimensionsLengthValidator() { async function dimensionsLengthValidator() {
if ( dimensions?.length && +dimensions.length <= 0 ) { if ( dimensions?.length && +dimensions.length <= 0 ) {
return { return {
@ -128,12 +132,14 @@ export function Edit( {
[ dimensions?.length ] [ dimensions?.length ]
); );
const heightFieldId = `dimensions_height-${ clientId }`;
const { const {
ref: dimensionsHeightRef, ref: dimensionsHeightRef,
error: dimensionsHeightValidationError, error: dimensionsHeightValidationError,
validate: validateDimensionsHeight, validate: validateDimensionsHeight,
} = useValidation< Product >( } = useValidation< Product >(
`dimensions_height-${ clientId }`, heightFieldId,
async function dimensionsHeightValidator() { async function dimensionsHeightValidator() {
if ( dimensions?.height && +dimensions.height <= 0 ) { if ( dimensions?.height && +dimensions.height <= 0 ) {
return { return {
@ -148,12 +154,14 @@ export function Edit( {
[ dimensions?.height ] [ dimensions?.height ]
); );
const weightFieldId = `weight-${ clientId }`;
const { const {
ref: weightRef, ref: weightRef,
error: weightValidationError, error: weightValidationError,
validate: validateWeight, validate: validateWeight,
} = useValidation< Product >( } = useValidation< Product >(
`weight-${ clientId }`, weightFieldId,
async function weightValidator() { async function weightValidator() {
if ( weight && +weight <= 0 ) { if ( weight && +weight <= 0 ) {
return { return {
@ -172,18 +180,22 @@ export function Edit( {
...getDimensionsControlProps( 'width', 'A' ), ...getDimensionsControlProps( 'width', 'A' ),
ref: dimensionsWidthRef, ref: dimensionsWidthRef,
onBlur: validateDimensionsWidth, onBlur: validateDimensionsWidth,
id: widthFieldId,
}; };
const dimensionsLengthProps = { const dimensionsLengthProps = {
...getDimensionsControlProps( 'length', 'B' ), ...getDimensionsControlProps( 'length', 'B' ),
ref: dimensionsLengthRef, ref: dimensionsLengthRef,
onBlur: validateDimensionsLength, onBlur: validateDimensionsLength,
id: lengthFieldId,
}; };
const dimensionsHeightProps = { const dimensionsHeightProps = {
...getDimensionsControlProps( 'height', 'C' ), ...getDimensionsControlProps( 'height', 'C' ),
ref: dimensionsHeightRef, ref: dimensionsHeightRef,
onBlur: validateDimensionsHeight, onBlur: validateDimensionsHeight,
id: heightFieldId,
}; };
const weightProps = { const weightProps = {
id: weightFieldId,
name: 'weight', name: 'weight',
value: weight ?? '', value: weight ?? '',
onChange: setWeight, onChange: setWeight,

View File

@ -3,6 +3,7 @@
*/ */
import { import {
createElement, createElement,
forwardRef,
Fragment, Fragment,
isValidElement, isValidElement,
useEffect, useEffect,
@ -13,6 +14,7 @@ import { useInstanceId } from '@wordpress/compose';
import classNames from 'classnames'; import classNames from 'classnames';
import { plus, reset } from '@wordpress/icons'; import { plus, reset } from '@wordpress/icons';
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import type { ForwardedRef } from 'react';
import { import {
BaseControl, BaseControl,
Button, Button,
@ -27,6 +29,7 @@ import { useNumberInputProps } from '../../hooks/use-number-input-props';
import { Label } from '../label/label'; import { Label } from '../label/label';
export type NumberProps = { export type NumberProps = {
id?: string;
value: string; value: string;
onChange: ( selected: string ) => void; onChange: ( selected: string ) => void;
label: string | JSX.Element; label: string | JSX.Element;
@ -47,7 +50,10 @@ export type NumberProps = {
const MEDIUM_DELAY = 500; const MEDIUM_DELAY = 500;
const SHORT_DELAY = 100; const SHORT_DELAY = 100;
export const NumberControl: React.FC< NumberProps > = ( { export const NumberControl: React.FC< NumberProps > = forwardRef(
(
{
id,
value, value,
onChange, onChange,
label, label,
@ -63,26 +69,35 @@ export const NumberControl: React.FC< NumberProps > = ( {
step = 1, step = 1,
min = -Infinity, min = -Infinity,
max = Infinity, max = Infinity,
}: NumberProps ) => { }: NumberProps,
const id = useInstanceId( BaseControl, 'product_number_field' ) as string; ref: ForwardedRef< HTMLInputElement >
) => {
const instanceId = useInstanceId(
BaseControl,
'product_number_field'
) as string;
const identifier = id ?? instanceId;
const [ isFocused, setIsFocused ] = useState( false ); const [ isFocused, setIsFocused ] = useState( false );
const unfocusIfOutside = ( event: React.FocusEvent ) => { const unfocusIfOutside = ( event: React.FocusEvent ) => {
if ( if (
! document ! document
.getElementById( id ) .getElementById( identifier )
?.parentElement?.contains( event.relatedTarget ) ?.parentElement?.contains( event.relatedTarget )
) { ) {
setIsFocused( false ); setIsFocused( false );
onBlur?.(); onBlur?.();
} }
}; };
function handleOnFocus() {
setIsFocused( true );
onFocus?.();
}
const inputProps = useNumberInputProps( { const inputProps = useNumberInputProps( {
value: value || '', value: value || '',
onChange, onChange,
onFocus: () => { onFocus: handleOnFocus,
setIsFocused( true );
onFocus?.();
},
min, min,
max, max,
} ); } );
@ -93,11 +108,11 @@ export const NumberControl: React.FC< NumberProps > = ( {
const isInitialClick = useRef< boolean >( false ); const isInitialClick = useRef< boolean >( false );
const incrementValue = () => { function incrementValue() {
const newValue = parseFloat( value || '0' ) + increment; const newValue = parseFloat( value || '0' ) + increment;
if ( newValue >= min && newValue <= max ) if ( newValue >= min && newValue <= max )
onChange( String( newValue ) ); onChange( String( newValue ) );
}; }
useEffect( () => { useEffect( () => {
if ( increment !== 0 ) { if ( increment !== 0 ) {
@ -116,23 +131,25 @@ export const NumberControl: React.FC< NumberProps > = ( {
}; };
}, [ increment, value ] ); }, [ increment, value ] );
const resetIncrement = () => setIncrement( 0 ); function resetIncrement() {
setIncrement( 0 );
}
const handleIncrement = ( thisStep: number ) => { function handleIncrement( thisStep: number ) {
const newValue = parseFloat( value || '0' ) + thisStep; const newValue = parseFloat( value || '0' ) + thisStep;
if ( newValue >= min && newValue <= max ) { if ( newValue >= min && newValue <= max ) {
onChange( String( parseFloat( value || '0' ) + thisStep ) ); onChange( String( parseFloat( value || '0' ) + thisStep ) );
setIncrement( thisStep ); setIncrement( thisStep );
isInitialClick.current = true; isInitialClick.current = true;
} }
}; }
return ( return (
<BaseControl <BaseControl
className={ classNames( { className={ classNames( {
'has-error': error, 'has-error': error,
} ) } } ) }
id={ id } id={ identifier }
label={ label={
isValidElement( label ) ? ( isValidElement( label ) ? (
label label
@ -148,10 +165,11 @@ export const NumberControl: React.FC< NumberProps > = ( {
> >
<InputControl <InputControl
{ ...inputProps } { ...inputProps }
ref={ ref }
step={ step } step={ step }
disabled={ disabled } disabled={ disabled }
autoComplete="off" autoComplete="off"
id={ id } id={ identifier }
className="woocommerce-number-control" className="woocommerce-number-control"
suffix={ suffix={
<> <>
@ -207,4 +225,5 @@ export const NumberControl: React.FC< NumberProps > = ( {
/> />
</BaseControl> </BaseControl>
); );
}; }
);

View File

@ -19,8 +19,20 @@ export function useValidations< T = unknown >() {
async function focusByValidatorId( validatorId: string ) { async function focusByValidatorId( validatorId: string ) {
const field = await context.getFieldByValidatorId( validatorId ); const field = await context.getFieldByValidatorId( validatorId );
if ( field ) { const tab = field.closest(
'.wp-block-woocommerce-product-tab__content'
);
const observer = new MutationObserver( () => {
if ( tab && getComputedStyle( tab ).display !== 'none' ) {
field.focus(); field.focus();
observer.disconnect();
}
} );
if ( tab ) {
observer.observe( tab, {
attributes: true,
} );
} }
} }