Create combobox-control (#49691)
* Create combobox-control * Exporting the combobox control * Integrate the combobox control into the custom field names component * Remove some non needed styles * Add changelog file * Fix comment text typo Co-authored-by: louwie17 <lourensschep@gmail.com> * Fix linter errors --------- Co-authored-by: louwie17 <lourensschep@gmail.com>
This commit is contained in:
parent
ac5b051fc2
commit
4689a09ec3
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add a ComboboxControl component
|
|
@ -0,0 +1,127 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import type { ForwardedRef } from 'react';
|
||||
import { ComboboxControl as Combobox } from '@wordpress/components';
|
||||
import { useInstanceId } from '@wordpress/compose';
|
||||
import {
|
||||
createElement,
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
} from '@wordpress/element';
|
||||
import classNames from 'classnames';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import type { ComboboxControlProps } from './types';
|
||||
|
||||
/**
|
||||
* This is a wrapper + a work around the Combobox to
|
||||
* expose important properties and events from the
|
||||
* internal input element that are required when
|
||||
* validating the field in the context of a form
|
||||
*/
|
||||
export const ComboboxControl = forwardRef( function ForwardedComboboxControl(
|
||||
{
|
||||
id,
|
||||
name,
|
||||
allowReset,
|
||||
className,
|
||||
help,
|
||||
hideLabelFromVision,
|
||||
label,
|
||||
messages,
|
||||
value,
|
||||
options,
|
||||
onFilterValueChange,
|
||||
onChange,
|
||||
onBlur,
|
||||
}: ComboboxControlProps,
|
||||
ref: ForwardedRef< HTMLInputElement >
|
||||
) {
|
||||
const inputElementRef = useRef< HTMLInputElement >();
|
||||
const generatedId = useInstanceId(
|
||||
ComboboxControl,
|
||||
'woocommerce-combobox-control'
|
||||
) as string;
|
||||
const currentId = id ?? generatedId;
|
||||
|
||||
useLayoutEffect(
|
||||
/**
|
||||
* The Combobox component does not expose the ref to the
|
||||
* internal native input element removing the ability to
|
||||
* focus the element when validating it in the context
|
||||
* of a form
|
||||
*/
|
||||
function initializeRefs() {
|
||||
inputElementRef.current = document.querySelector(
|
||||
`.${ currentId } [role="combobox"]`
|
||||
) as HTMLInputElement;
|
||||
|
||||
if ( name ) {
|
||||
inputElementRef.current?.setAttribute( 'name', name );
|
||||
}
|
||||
|
||||
if ( ref ) {
|
||||
if ( typeof ref === 'function' ) {
|
||||
ref( inputElementRef.current );
|
||||
} else {
|
||||
ref.current = inputElementRef.current;
|
||||
}
|
||||
}
|
||||
},
|
||||
[ currentId, name, ref ]
|
||||
);
|
||||
|
||||
useEffect(
|
||||
function overrideBlur() {
|
||||
/**
|
||||
* The Combobox component clear the value of its internal
|
||||
* input control when losing the focus, even when the
|
||||
* selected value is set, affecting the validation behavior
|
||||
* on bluring
|
||||
*/
|
||||
function handleBlur( event: FocusEvent ) {
|
||||
onBlur?.( {
|
||||
...event,
|
||||
target: {
|
||||
...event.target,
|
||||
value,
|
||||
},
|
||||
} as never );
|
||||
}
|
||||
|
||||
inputElementRef.current?.addEventListener( 'blur', handleBlur );
|
||||
|
||||
return () => {
|
||||
inputElementRef.current?.removeEventListener(
|
||||
'blur',
|
||||
handleBlur
|
||||
);
|
||||
};
|
||||
},
|
||||
[ value, onBlur ]
|
||||
);
|
||||
|
||||
return (
|
||||
<Combobox
|
||||
allowReset={ allowReset }
|
||||
help={ help }
|
||||
hideLabelFromVision={ hideLabelFromVision }
|
||||
label={ label }
|
||||
messages={ messages }
|
||||
value={ value }
|
||||
options={ options }
|
||||
onChange={ onChange }
|
||||
onFilterValueChange={ onFilterValueChange }
|
||||
className={ classNames(
|
||||
'woocommerce-combobox-control',
|
||||
currentId,
|
||||
className
|
||||
) }
|
||||
/>
|
||||
);
|
||||
} );
|
|
@ -0,0 +1,2 @@
|
|||
export * from './combobox-control';
|
||||
export * from './types';
|
|
@ -1,4 +1,4 @@
|
|||
.woocommerce-custom-field-name-control {
|
||||
.woocommerce-combobox-control {
|
||||
background-color: #fff;
|
||||
|
||||
&.has-error {
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { ComboboxControl as Combobox } from '@wordpress/components';
|
||||
|
||||
export type ComboboxControlProps = Combobox.Props &
|
||||
Pick<
|
||||
React.DetailedHTMLProps<
|
||||
React.InputHTMLAttributes< HTMLInputElement >,
|
||||
HTMLInputElement
|
||||
>,
|
||||
'id' | 'name' | 'onBlur'
|
||||
>;
|
|
@ -3,24 +3,20 @@
|
|||
*/
|
||||
import type { ForwardedRef } from 'react';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { ComboboxControl } from '@wordpress/components';
|
||||
import { useDebounce, useInstanceId } from '@wordpress/compose';
|
||||
import { useDebounce } from '@wordpress/compose';
|
||||
import {
|
||||
createElement,
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from '@wordpress/element';
|
||||
import { addQueryArgs } from '@wordpress/url';
|
||||
import classNames from 'classnames';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ComboboxControl, ComboboxControlProps } from '../../combobox-control';
|
||||
import type { ComboboxControlOption } from '../../attribute-combobox-field/types';
|
||||
import type { CustomFieldNameControlProps } from './types';
|
||||
|
||||
|
@ -56,35 +52,13 @@ async function searchCustomFieldNames( search?: string ) {
|
|||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a wrapper + a work around the Combobox to
|
||||
* expose important properties and events from the
|
||||
* internal input element that are required when
|
||||
* validating the field in the context of a form
|
||||
*/
|
||||
export const CustomFieldNameControl = forwardRef(
|
||||
function ForwardedCustomFieldNameControl(
|
||||
{
|
||||
allowReset,
|
||||
className,
|
||||
help,
|
||||
hideLabelFromVision,
|
||||
label,
|
||||
messages,
|
||||
value,
|
||||
onChange,
|
||||
onBlur,
|
||||
}: CustomFieldNameControlProps,
|
||||
{ value, onBlur, ...props }: CustomFieldNameControlProps,
|
||||
ref: ForwardedRef< HTMLInputElement >
|
||||
) {
|
||||
const inputElementRef = useRef< HTMLInputElement >();
|
||||
const id = useInstanceId(
|
||||
CustomFieldNameControl,
|
||||
'woocommerce-custom-field-name'
|
||||
);
|
||||
|
||||
const [ customFieldNames, setCustomFieldNames ] = useState<
|
||||
ComboboxControl.Props[ 'options' ]
|
||||
ComboboxControlProps[ 'options' ]
|
||||
>( [] );
|
||||
|
||||
const options = useMemo(
|
||||
|
@ -109,29 +83,6 @@ export const CustomFieldNameControl = forwardRef(
|
|||
[ customFieldNames, value ]
|
||||
);
|
||||
|
||||
useLayoutEffect(
|
||||
/**
|
||||
* The Combobox component does not expose the ref to the
|
||||
* internal native input element removing the ability to
|
||||
* focus the element when validating it in the context
|
||||
* of a form
|
||||
*/
|
||||
function initializeRefs() {
|
||||
inputElementRef.current = document.querySelector(
|
||||
`.${ id } [role="combobox"]`
|
||||
) as HTMLInputElement;
|
||||
|
||||
if ( ref ) {
|
||||
if ( typeof ref === 'function' ) {
|
||||
ref( inputElementRef.current );
|
||||
} else {
|
||||
ref.current = inputElementRef.current;
|
||||
}
|
||||
}
|
||||
},
|
||||
[ id, ref ]
|
||||
);
|
||||
|
||||
const handleFilterValueChange = useDebounce(
|
||||
useCallback(
|
||||
function onFilterValueChange( search: string ) {
|
||||
|
@ -144,50 +95,19 @@ export const CustomFieldNameControl = forwardRef(
|
|||
250
|
||||
);
|
||||
|
||||
useEffect(
|
||||
function overrideBlur() {
|
||||
/**
|
||||
* The Combobox component clear the value of its internal
|
||||
* input control when losing the focus, even when the
|
||||
* selected value is set, afecting the validation behavior
|
||||
* on bluring
|
||||
*/
|
||||
function handleBlur( event: FocusEvent ) {
|
||||
function handleBlur( event: React.FocusEvent< HTMLInputElement > ) {
|
||||
setCustomFieldNames( [] );
|
||||
if ( inputElementRef.current ) {
|
||||
inputElementRef.current.value = value;
|
||||
onBlur?.( event );
|
||||
}
|
||||
onBlur?.( event as never );
|
||||
}
|
||||
|
||||
inputElementRef.current?.addEventListener( 'blur', handleBlur );
|
||||
|
||||
return () => {
|
||||
inputElementRef.current?.removeEventListener(
|
||||
'blur',
|
||||
handleBlur
|
||||
);
|
||||
};
|
||||
},
|
||||
[ value, onBlur ]
|
||||
);
|
||||
|
||||
return (
|
||||
<ComboboxControl
|
||||
allowReset={ allowReset }
|
||||
help={ help }
|
||||
hideLabelFromVision={ hideLabelFromVision }
|
||||
label={ label }
|
||||
messages={ messages }
|
||||
{ ...props }
|
||||
ref={ ref }
|
||||
value={ value }
|
||||
options={ options }
|
||||
onChange={ onChange }
|
||||
onFilterValueChange={ handleFilterValueChange }
|
||||
className={ classNames(
|
||||
id,
|
||||
'woocommerce-custom-field-name-control',
|
||||
className
|
||||
) }
|
||||
onBlur={ handleBlur }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
@import "./create-modal/style.scss";
|
||||
@import "./edit-modal/style.scss";
|
||||
@import "./custom-field-name-control/style.scss";
|
||||
|
||||
.woocommerce-product-custom-fields {
|
||||
&__table {
|
||||
|
|
|
@ -104,3 +104,8 @@ export {
|
|||
|
||||
export { PluginSidebar as __experimentalModalBlockEditorPluginSidebar } from './iframe-editor';
|
||||
export { PluginMoreMenuItem as __experimentalModalBlockEditorPluginMoreMenuItem } from './iframe-editor';
|
||||
|
||||
export {
|
||||
ComboboxControl as __experimentalComboboxControl,
|
||||
type ComboboxControlProps,
|
||||
} from './combobox-control';
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
@import "components/attribute-combobox-field/styles.scss";
|
||||
@import "components/number-control/style.scss";
|
||||
@import "components/empty-state/style.scss";
|
||||
@import "components/combobox-control/style.scss";
|
||||
|
||||
/* Field Blocks */
|
||||
|
||||
|
|
Loading…
Reference in New Issue