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;
|
background-color: #fff;
|
||||||
|
|
||||||
&.has-error {
|
&.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 type { ForwardedRef } from 'react';
|
||||||
import apiFetch from '@wordpress/api-fetch';
|
import apiFetch from '@wordpress/api-fetch';
|
||||||
import { ComboboxControl } from '@wordpress/components';
|
import { useDebounce } from '@wordpress/compose';
|
||||||
import { useDebounce, useInstanceId } from '@wordpress/compose';
|
|
||||||
import {
|
import {
|
||||||
createElement,
|
createElement,
|
||||||
forwardRef,
|
forwardRef,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
|
||||||
useLayoutEffect,
|
|
||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
|
||||||
useState,
|
useState,
|
||||||
} from '@wordpress/element';
|
} from '@wordpress/element';
|
||||||
import { addQueryArgs } from '@wordpress/url';
|
import { addQueryArgs } from '@wordpress/url';
|
||||||
import classNames from 'classnames';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
|
import { ComboboxControl, ComboboxControlProps } from '../../combobox-control';
|
||||||
import type { ComboboxControlOption } from '../../attribute-combobox-field/types';
|
import type { ComboboxControlOption } from '../../attribute-combobox-field/types';
|
||||||
import type { CustomFieldNameControlProps } from './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(
|
export const CustomFieldNameControl = forwardRef(
|
||||||
function ForwardedCustomFieldNameControl(
|
function ForwardedCustomFieldNameControl(
|
||||||
{
|
{ value, onBlur, ...props }: CustomFieldNameControlProps,
|
||||||
allowReset,
|
|
||||||
className,
|
|
||||||
help,
|
|
||||||
hideLabelFromVision,
|
|
||||||
label,
|
|
||||||
messages,
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
onBlur,
|
|
||||||
}: CustomFieldNameControlProps,
|
|
||||||
ref: ForwardedRef< HTMLInputElement >
|
ref: ForwardedRef< HTMLInputElement >
|
||||||
) {
|
) {
|
||||||
const inputElementRef = useRef< HTMLInputElement >();
|
|
||||||
const id = useInstanceId(
|
|
||||||
CustomFieldNameControl,
|
|
||||||
'woocommerce-custom-field-name'
|
|
||||||
);
|
|
||||||
|
|
||||||
const [ customFieldNames, setCustomFieldNames ] = useState<
|
const [ customFieldNames, setCustomFieldNames ] = useState<
|
||||||
ComboboxControl.Props[ 'options' ]
|
ComboboxControlProps[ 'options' ]
|
||||||
>( [] );
|
>( [] );
|
||||||
|
|
||||||
const options = useMemo(
|
const options = useMemo(
|
||||||
|
@ -109,29 +83,6 @@ export const CustomFieldNameControl = forwardRef(
|
||||||
[ customFieldNames, value ]
|
[ 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(
|
const handleFilterValueChange = useDebounce(
|
||||||
useCallback(
|
useCallback(
|
||||||
function onFilterValueChange( search: string ) {
|
function onFilterValueChange( search: string ) {
|
||||||
|
@ -144,50 +95,19 @@ export const CustomFieldNameControl = forwardRef(
|
||||||
250
|
250
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(
|
function handleBlur( event: React.FocusEvent< HTMLInputElement > ) {
|
||||||
function overrideBlur() {
|
setCustomFieldNames( [] );
|
||||||
/**
|
onBlur?.( event );
|
||||||
* 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 ) {
|
|
||||||
setCustomFieldNames( [] );
|
|
||||||
if ( inputElementRef.current ) {
|
|
||||||
inputElementRef.current.value = value;
|
|
||||||
}
|
|
||||||
onBlur?.( event as never );
|
|
||||||
}
|
|
||||||
|
|
||||||
inputElementRef.current?.addEventListener( 'blur', handleBlur );
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
inputElementRef.current?.removeEventListener(
|
|
||||||
'blur',
|
|
||||||
handleBlur
|
|
||||||
);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
[ value, onBlur ]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ComboboxControl
|
<ComboboxControl
|
||||||
allowReset={ allowReset }
|
{ ...props }
|
||||||
help={ help }
|
ref={ ref }
|
||||||
hideLabelFromVision={ hideLabelFromVision }
|
|
||||||
label={ label }
|
|
||||||
messages={ messages }
|
|
||||||
value={ value }
|
value={ value }
|
||||||
options={ options }
|
options={ options }
|
||||||
onChange={ onChange }
|
|
||||||
onFilterValueChange={ handleFilterValueChange }
|
onFilterValueChange={ handleFilterValueChange }
|
||||||
className={ classNames(
|
onBlur={ handleBlur }
|
||||||
id,
|
|
||||||
'woocommerce-custom-field-name-control',
|
|
||||||
className
|
|
||||||
) }
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
@import "./create-modal/style.scss";
|
@import "./create-modal/style.scss";
|
||||||
@import "./edit-modal/style.scss";
|
@import "./edit-modal/style.scss";
|
||||||
@import "./custom-field-name-control/style.scss";
|
|
||||||
|
|
||||||
.woocommerce-product-custom-fields {
|
.woocommerce-product-custom-fields {
|
||||||
&__table {
|
&__table {
|
||||||
|
|
|
@ -104,3 +104,8 @@ export {
|
||||||
|
|
||||||
export { PluginSidebar as __experimentalModalBlockEditorPluginSidebar } from './iframe-editor';
|
export { PluginSidebar as __experimentalModalBlockEditorPluginSidebar } from './iframe-editor';
|
||||||
export { PluginMoreMenuItem as __experimentalModalBlockEditorPluginMoreMenuItem } 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/attribute-combobox-field/styles.scss";
|
||||||
@import "components/number-control/style.scss";
|
@import "components/number-control/style.scss";
|
||||||
@import "components/empty-state/style.scss";
|
@import "components/empty-state/style.scss";
|
||||||
|
@import "components/combobox-control/style.scss";
|
||||||
|
|
||||||
/* Field Blocks */
|
/* Field Blocks */
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue