Add coexistence checks to all enqueue methods to prevent loading both React and Grid.js assets simultaneously. Changes: - ReactAdmin.php: Only enqueue React assets when ?react=1 - Init.php: Skip Grid.js when React active on admin pages - Form.php, Coupon.php, Access.php: Restore classic assets when ?react=0 - Customer.php, Product.php, License.php: Add coexistence checks Now the toggle between Classic and React versions works correctly. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
256 lines
6.8 KiB
TypeScript
256 lines
6.8 KiB
TypeScript
/**
|
|
* External dependencies
|
|
*/
|
|
import type { KeyboardEvent, ForwardedRef, SyntheticEvent } from 'react';
|
|
import classnames from 'classnames';
|
|
|
|
/**
|
|
* WordPress dependencies
|
|
*/
|
|
import deprecated from '@wordpress/deprecated';
|
|
import { forwardRef, useMemo, useRef, useEffect } from '@wordpress/element';
|
|
import { __ } from '@wordpress/i18n';
|
|
|
|
/**
|
|
* Internal dependencies
|
|
*/
|
|
import type { WordPressComponentProps } from '../context';
|
|
import { ValueInput } from './styles/unit-control-styles';
|
|
import UnitSelectControl from './unit-select-control';
|
|
import {
|
|
CSS_UNITS,
|
|
getParsedQuantityAndUnit,
|
|
getUnitsWithCurrentUnit,
|
|
getValidParsedQuantityAndUnit,
|
|
} from './utils';
|
|
import { useControlledState } from '../utils/hooks';
|
|
import { escapeRegExp } from '../utils/strings';
|
|
import type { UnitControlProps, UnitControlOnChangeCallback } from './types';
|
|
import { useDeprecated36pxDefaultSizeProp } from '../utils/use-deprecated-props';
|
|
|
|
function UnforwardedUnitControl(
|
|
unitControlProps: WordPressComponentProps<
|
|
UnitControlProps,
|
|
'input',
|
|
false
|
|
>,
|
|
forwardedRef: ForwardedRef< any >
|
|
) {
|
|
const {
|
|
__unstableStateReducer,
|
|
autoComplete = 'off',
|
|
// @ts-expect-error Ensure that children is omitted from restProps
|
|
children,
|
|
className,
|
|
disabled = false,
|
|
disableUnits = false,
|
|
isPressEnterToChange = false,
|
|
isResetValueOnUnitChange = false,
|
|
isUnitSelectTabbable = true,
|
|
label,
|
|
onChange: onChangeProp,
|
|
onUnitChange,
|
|
size = 'default',
|
|
unit: unitProp,
|
|
units: unitsProp = CSS_UNITS,
|
|
value: valueProp,
|
|
onFocus: onFocusProp,
|
|
...props
|
|
} = useDeprecated36pxDefaultSizeProp(
|
|
unitControlProps,
|
|
'wp.components.UnitControl',
|
|
'6.4'
|
|
);
|
|
|
|
if ( 'unit' in unitControlProps ) {
|
|
deprecated( 'UnitControl unit prop', {
|
|
since: '5.6',
|
|
hint: 'The unit should be provided within the `value` prop.',
|
|
version: '6.2',
|
|
} );
|
|
}
|
|
|
|
// The `value` prop, in theory, should not be `null`, but the following line
|
|
// ensures it fallback to `undefined` in case a consumer of `UnitControl`
|
|
// still passes `null` as a `value`.
|
|
const nonNullValueProp = valueProp ?? undefined;
|
|
const [ units, reFirstCharacterOfUnits ] = useMemo( () => {
|
|
const list = getUnitsWithCurrentUnit(
|
|
nonNullValueProp,
|
|
unitProp,
|
|
unitsProp
|
|
);
|
|
const [ { value: firstUnitValue = '' } = {}, ...rest ] = list;
|
|
const firstCharacters = rest.reduce(
|
|
( carry, { value } ) => {
|
|
const first = escapeRegExp( value?.substring( 0, 1 ) || '' );
|
|
return carry.includes( first )
|
|
? carry
|
|
: `${ carry }|${ first }`;
|
|
},
|
|
escapeRegExp( firstUnitValue.substring( 0, 1 ) )
|
|
);
|
|
return [ list, new RegExp( `^(?:${ firstCharacters })$`, 'i' ) ];
|
|
}, [ nonNullValueProp, unitProp, unitsProp ] );
|
|
const [ parsedQuantity, parsedUnit ] = getParsedQuantityAndUnit(
|
|
nonNullValueProp,
|
|
unitProp,
|
|
units
|
|
);
|
|
|
|
const [ unit, setUnit ] = useControlledState< string | undefined >(
|
|
units.length === 1 ? units[ 0 ].value : unitProp,
|
|
{
|
|
initial: parsedUnit,
|
|
fallback: '',
|
|
}
|
|
);
|
|
|
|
useEffect( () => {
|
|
if ( parsedUnit !== undefined ) {
|
|
setUnit( parsedUnit );
|
|
}
|
|
}, [ parsedUnit, setUnit ] );
|
|
|
|
const classes = classnames(
|
|
'components-unit-control',
|
|
// This class is added for legacy purposes to maintain it on the outer
|
|
// wrapper. See: https://github.com/WordPress/gutenberg/pull/45139
|
|
'components-unit-control-wrapper',
|
|
className
|
|
);
|
|
|
|
const handleOnQuantityChange = (
|
|
nextQuantityValue: number | string | undefined,
|
|
changeProps: {
|
|
event: SyntheticEvent;
|
|
}
|
|
) => {
|
|
if (
|
|
nextQuantityValue === '' ||
|
|
typeof nextQuantityValue === 'undefined' ||
|
|
nextQuantityValue === null
|
|
) {
|
|
onChangeProp?.( '', changeProps );
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Customizing the onChange callback.
|
|
* This allows as to broadcast a combined value+unit to onChange.
|
|
*/
|
|
const onChangeValue = getValidParsedQuantityAndUnit(
|
|
nextQuantityValue,
|
|
units,
|
|
parsedQuantity,
|
|
unit
|
|
).join( '' );
|
|
|
|
onChangeProp?.( onChangeValue, changeProps );
|
|
};
|
|
|
|
const handleOnUnitChange: UnitControlOnChangeCallback = (
|
|
nextUnitValue,
|
|
changeProps
|
|
) => {
|
|
const { data } = changeProps;
|
|
|
|
let nextValue = `${ parsedQuantity ?? '' }${ nextUnitValue }`;
|
|
|
|
if ( isResetValueOnUnitChange && data?.default !== undefined ) {
|
|
nextValue = `${ data.default }${ nextUnitValue }`;
|
|
}
|
|
|
|
onChangeProp?.( nextValue, changeProps );
|
|
onUnitChange?.( nextUnitValue, changeProps );
|
|
|
|
setUnit( nextUnitValue );
|
|
};
|
|
|
|
let handleOnKeyDown;
|
|
if ( ! disableUnits && isUnitSelectTabbable && units.length ) {
|
|
handleOnKeyDown = ( event: KeyboardEvent< HTMLInputElement > ) => {
|
|
props.onKeyDown?.( event );
|
|
// Unless the meta key was pressed (to avoid interfering with
|
|
// shortcuts, e.g. pastes), moves focus to the unit select if a key
|
|
// matches the first character of a unit.
|
|
if ( ! event.metaKey && reFirstCharacterOfUnits.test( event.key ) )
|
|
refInputSuffix.current?.focus();
|
|
};
|
|
}
|
|
|
|
const refInputSuffix = useRef< HTMLSelectElement >( null );
|
|
const inputSuffix = ! disableUnits ? (
|
|
<UnitSelectControl
|
|
ref={ refInputSuffix }
|
|
aria-label={ __( 'Select unit' ) }
|
|
disabled={ disabled }
|
|
isUnitSelectTabbable={ isUnitSelectTabbable }
|
|
onChange={ handleOnUnitChange }
|
|
size={
|
|
[ 'small', 'compact' ].includes( size ) ||
|
|
( size === 'default' && ! props.__next40pxDefaultSize )
|
|
? 'small'
|
|
: 'default'
|
|
}
|
|
unit={ unit }
|
|
units={ units }
|
|
onFocus={ onFocusProp }
|
|
onBlur={ unitControlProps.onBlur }
|
|
/>
|
|
) : null;
|
|
|
|
let step = props.step;
|
|
|
|
/*
|
|
* If no step prop has been passed, lookup the active unit and
|
|
* try to get step from `units`, or default to a value of `1`
|
|
*/
|
|
if ( ! step && units ) {
|
|
const activeUnit = units.find( ( option ) => option.value === unit );
|
|
step = activeUnit?.step ?? 1;
|
|
}
|
|
|
|
return (
|
|
<ValueInput
|
|
{ ...props }
|
|
autoComplete={ autoComplete }
|
|
className={ classes }
|
|
disabled={ disabled }
|
|
spinControls="none"
|
|
isPressEnterToChange={ isPressEnterToChange }
|
|
label={ label }
|
|
onKeyDown={ handleOnKeyDown }
|
|
onChange={ handleOnQuantityChange }
|
|
ref={ forwardedRef }
|
|
size={ size }
|
|
suffix={ inputSuffix }
|
|
type={ isPressEnterToChange ? 'text' : 'number' }
|
|
value={ parsedQuantity ?? '' }
|
|
step={ step }
|
|
onFocus={ onFocusProp }
|
|
__unstableStateReducer={ __unstableStateReducer }
|
|
/>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* `UnitControl` allows the user to set a numeric quantity as well as a unit (e.g. `px`).
|
|
*
|
|
*
|
|
* ```jsx
|
|
* import { __experimentalUnitControl as UnitControl } from '@wordpress/components';
|
|
* import { useState } from '@wordpress/element';
|
|
*
|
|
* const Example = () => {
|
|
* const [ value, setValue ] = useState( '10px' );
|
|
*
|
|
* return <UnitControl onChange={ setValue } value={ value } />;
|
|
* };
|
|
* ```
|
|
*/
|
|
export const UnitControl = forwardRef( UnforwardedUnitControl );
|
|
|
|
export { parseQuantityAndUnitFromRawValue, useCustomUnits } from './utils';
|
|
export default UnitControl;
|