@elementor/editor-controls 4.0.0-manual → 4.1.0-684

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@elementor/editor-controls",
3
3
  "description": "This package contains the controls model and utils for the Elementor editor",
4
- "version": "4.0.0-manual",
4
+ "version": "4.1.0-684",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -40,22 +40,22 @@
40
40
  "dev": "tsup --config=../../tsup.dev.ts"
41
41
  },
42
42
  "dependencies": {
43
- "@elementor/editor-current-user": "4.0.0-manual",
44
- "@elementor/editor-elements": "4.0.0-manual",
45
- "@elementor/editor-props": "4.0.0-manual",
46
- "@elementor/editor-responsive": "4.0.0-manual",
47
- "@elementor/editor-ui": "4.0.0-manual",
48
- "@elementor/editor-v1-adapters": "4.0.0-manual",
49
- "@elementor/env": "4.0.0-manual",
50
- "@elementor/http-client": "4.0.0-manual",
43
+ "@elementor/editor-current-user": "4.1.0-684",
44
+ "@elementor/editor-elements": "4.1.0-684",
45
+ "@elementor/editor-props": "4.1.0-684",
46
+ "@elementor/editor-responsive": "4.1.0-684",
47
+ "@elementor/editor-ui": "4.1.0-684",
48
+ "@elementor/editor-v1-adapters": "4.1.0-684",
49
+ "@elementor/env": "4.1.0-684",
50
+ "@elementor/http-client": "4.1.0-684",
51
51
  "@elementor/icons": "^1.68.0",
52
- "@elementor/locations": "4.0.0-manual",
53
- "@elementor/events": "4.0.0-manual",
54
- "@elementor/query": "4.0.0-manual",
55
- "@elementor/session": "4.0.0-manual",
52
+ "@elementor/locations": "4.1.0-684",
53
+ "@elementor/events": "4.1.0-684",
54
+ "@elementor/query": "4.1.0-684",
55
+ "@elementor/session": "4.1.0-684",
56
56
  "@elementor/ui": "1.36.17",
57
- "@elementor/utils": "4.0.0-manual",
58
- "@elementor/wp-media": "4.0.0-manual",
57
+ "@elementor/utils": "4.1.0-684",
58
+ "@elementor/wp-media": "4.1.0-684",
59
59
  "@wordpress/i18n": "^5.13.0",
60
60
  "@monaco-editor/react": "^4.7.0",
61
61
  "dayjs": "^1.11.18",
@@ -8,6 +8,7 @@ import { createControl } from '../../create-control';
8
8
  import { PromotionTrigger, type PromotionTriggerRef } from './promotion-trigger';
9
9
 
10
10
  const ARIA_LABEL = __( 'Attributes', 'elementor' );
11
+ const TRACKING_DATA = { target_name: 'attributes', location_l2: 'general' } as const;
11
12
 
12
13
  export const AttributesControl = createControl( () => {
13
14
  const triggerRef = useRef< PromotionTriggerRef >( null );
@@ -21,7 +22,7 @@ export const AttributesControl = createControl( () => {
21
22
  alignItems: 'center',
22
23
  } }
23
24
  >
24
- <PromotionTrigger ref={ triggerRef } promotionKey="attributes" />
25
+ <PromotionTrigger ref={ triggerRef } promotionKey="attributes" trackingData={ TRACKING_DATA } />
25
26
  <Tooltip title={ ARIA_LABEL } placement="top">
26
27
  <PlusIcon
27
28
  aria-label={ ARIA_LABEL }
@@ -8,6 +8,7 @@ import { createControl } from '../../create-control';
8
8
  import { PromotionTrigger, type PromotionTriggerRef } from './promotion-trigger';
9
9
 
10
10
  const ARIA_LABEL = __( 'Display Conditions', 'elementor' );
11
+ const TRACKING_DATA = { target_name: 'display_conditions', location_l2: 'general' } as const;
11
12
 
12
13
  export const DisplayConditionsControl = createControl( () => {
13
14
  const triggerRef = useRef< PromotionTriggerRef >( null );
@@ -21,7 +22,7 @@ export const DisplayConditionsControl = createControl( () => {
21
22
  alignItems: 'center',
22
23
  } }
23
24
  >
24
- <PromotionTrigger ref={ triggerRef } promotionKey="displayConditions" />
25
+ <PromotionTrigger ref={ triggerRef } promotionKey="displayConditions" trackingData={ TRACKING_DATA } />
25
26
  <Tooltip title={ ARIA_LABEL } placement="top">
26
27
  <IconButton
27
28
  size="tiny"
@@ -1,8 +1,9 @@
1
1
  import * as React from 'react';
2
- import { forwardRef, type ReactNode, useImperativeHandle, useState } from 'react';
2
+ import { forwardRef, type ReactNode, useCallback, useImperativeHandle, useState } from 'react';
3
3
  import { PromotionChip, PromotionInfotip } from '@elementor/editor-ui';
4
4
  import { Box } from '@elementor/ui';
5
5
 
6
+ import { type PromotionTrackingData, trackUpgradePromotionClick, trackViewPromotion } from '../../utils/tracking';
6
7
  import type { V4PromotionData, V4PromotionKey } from './types';
7
8
 
8
9
  function getV4Promotion( key: V4PromotionKey ): V4PromotionData | undefined {
@@ -12,6 +13,7 @@ function getV4Promotion( key: V4PromotionKey ): V4PromotionData | undefined {
12
13
  type PromotionTriggerProps = {
13
14
  promotionKey: V4PromotionKey;
14
15
  children?: ReactNode;
16
+ trackingData: PromotionTrackingData;
15
17
  };
16
18
 
17
19
  export type PromotionTriggerRef = {
@@ -19,13 +21,20 @@ export type PromotionTriggerRef = {
19
21
  };
20
22
 
21
23
  export const PromotionTrigger = forwardRef< PromotionTriggerRef, PromotionTriggerProps >(
22
- ( { promotionKey, children }, ref ) => {
24
+ ( { promotionKey, children, trackingData }, ref ) => {
23
25
  const [ isOpen, setIsOpen ] = useState( false );
24
26
  const promotion = getV4Promotion( promotionKey );
25
27
 
26
- const toggle = () => setIsOpen( ( prev ) => ! prev );
28
+ const toggle = useCallback( () => {
29
+ setIsOpen( ( prev ) => {
30
+ if ( ! prev ) {
31
+ trackViewPromotion( trackingData );
32
+ }
33
+ return ! prev;
34
+ } );
35
+ }, [ trackingData ] );
27
36
 
28
- useImperativeHandle( ref, () => ( { toggle } ), [] );
37
+ useImperativeHandle( ref, () => ( { toggle } ), [ toggle ] );
29
38
 
30
39
  return (
31
40
  <>
@@ -40,6 +49,7 @@ export const PromotionTrigger = forwardRef< PromotionTriggerRef, PromotionTrigge
40
49
  e.stopPropagation();
41
50
  setIsOpen( false );
42
51
  } }
52
+ onCtaClick={ () => trackUpgradePromotionClick( trackingData ) }
43
53
  >
44
54
  <Box
45
55
  onClick={ ( e: MouseEvent ) => {
@@ -31,7 +31,7 @@ export const ChipsControl = createControl( ( { options }: ChipsControlProps ) =>
31
31
 
32
32
  const handleChange = ( _: SyntheticEvent, newValue: ChipsOption[] ) => {
33
33
  const values = newValue.map( ( option ) => stringPropTypeUtil.create( option.value ) );
34
- setValue( values.length > 0 ? values : null );
34
+ setValue( values );
35
35
  };
36
36
 
37
37
  return (
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { numberPropTypeUtil, type PropType } from '@elementor/editor-props';
3
- import { InputAdornment } from '@elementor/ui';
3
+ import { InputAdornment, Typography } from '@elementor/ui';
4
4
 
5
5
  import { useBoundProp } from '../bound-prop-context';
6
6
  import { NumberInput } from '../components/number-input';
@@ -12,7 +12,13 @@ const isEmptyOrNaN = ( value?: string | number | null ) =>
12
12
 
13
13
  const renderSuffix = ( propType: PropType ) => {
14
14
  if ( propType.meta?.suffix ) {
15
- return <InputAdornment position="end">{ propType.meta.suffix }</InputAdornment>;
15
+ return (
16
+ <InputAdornment position="end">
17
+ <Typography variant="caption" color="text.secondary">
18
+ { propType.meta.suffix as string }
19
+ </Typography>
20
+ </InputAdornment>
21
+ );
16
22
  }
17
23
  return <></>;
18
24
  };
@@ -0,0 +1,55 @@
1
+ import type * as React from 'react';
2
+
3
+ import { useTypingBuffer } from '../../../hooks/use-typing-buffer';
4
+ import { type SizeUnit } from '../types';
5
+ import { isExtendedUnit } from '../utils/is-extended-unit';
6
+ import { isNumericValue } from '../utils/is-numeric-value';
7
+
8
+ const UNIT_KEY_PATTERN = /^[a-zA-Z%]$/;
9
+
10
+ type Props = {
11
+ unit: SizeUnit;
12
+ units: SizeUnit[];
13
+ onUnitChange: ( unit: SizeUnit ) => void;
14
+ };
15
+
16
+ export const useSizeUnitKeyboard = ( { unit, units, onUnitChange }: Props ) => {
17
+ const { appendKey, startsWith } = useTypingBuffer();
18
+
19
+ const onUnitKeyDown = ( event: React.KeyboardEvent< HTMLInputElement > ) => {
20
+ if ( units.length === 0 ) {
21
+ return;
22
+ }
23
+
24
+ const { key, altKey, ctrlKey, metaKey } = event;
25
+
26
+ if ( altKey || ctrlKey || metaKey ) {
27
+ return;
28
+ }
29
+
30
+ if ( isExtendedUnit( unit ) && isNumericValue( key ) ) {
31
+ const [ defaultUnit ] = units;
32
+
33
+ if ( defaultUnit ) {
34
+ onUnitChange( defaultUnit );
35
+ }
36
+
37
+ return;
38
+ }
39
+
40
+ if ( ! UNIT_KEY_PATTERN.test( key ) ) {
41
+ return;
42
+ }
43
+
44
+ event.preventDefault();
45
+
46
+ const updatedBuffer = appendKey( key.toLowerCase() );
47
+ const matchedUnit = units.find( ( u ) => startsWith( u, updatedBuffer ) );
48
+
49
+ if ( matchedUnit ) {
50
+ onUnitChange( matchedUnit );
51
+ }
52
+ };
53
+
54
+ return { onUnitKeyDown };
55
+ };
@@ -0,0 +1,75 @@
1
+ import { useMemo } from 'react';
2
+ import { type SizePropValue } from '@elementor/editor-props';
3
+
4
+ import { useSyncExternalState } from '../../../hooks/use-sync-external-state';
5
+ import { type SizeUnit } from '../types';
6
+ import { isExtendedUnit } from '../utils/is-extended-unit';
7
+ import { createDefaultSizeValue, resolveSizeOnUnitChange, resolveSizeValue } from '../utils/resolve-size-value';
8
+
9
+ type SizeValue = SizePropValue[ 'value' ];
10
+
11
+ type UseSizeValueProps< T, U > = {
12
+ value: T | null;
13
+ onChange: ( value: T ) => void;
14
+ units: U[];
15
+ defaultUnit?: U;
16
+ };
17
+
18
+ export const useSizeValue = < T extends SizeValue, U extends SizeUnit >( {
19
+ value,
20
+ onChange,
21
+ units,
22
+ defaultUnit,
23
+ }: UseSizeValueProps< T, U > ) => {
24
+ const resolvedValue = useMemo(
25
+ () => resolveSizeValue( value, { units, defaultUnit } ),
26
+ // eslint-disable-next-line react-hooks/exhaustive-deps
27
+ [ value?.size, value?.unit, defaultUnit ]
28
+ );
29
+
30
+ const [ sizeValue, setSizeValue ] = useSyncExternalState< T >( {
31
+ external: resolvedValue as T,
32
+ setExternal: ( newState ) => {
33
+ // TODO we need to check behaviour that low level doesn't set to null only the high level components size component
34
+ // This will fix the issue of if size is empty string '' it gets sends to the model
35
+ // but on blur the size component set to null.
36
+ // But we need to test this behaviour
37
+ if ( newState !== null ) {
38
+ onChange( newState );
39
+ }
40
+ }, // TODO we will need to handle options, meta if context need them
41
+ persistWhen: ( next ) => hasChanged( next, resolvedValue as T ),
42
+ fallback: () => createDefaultSizeValue< T >( units, defaultUnit ),
43
+ } );
44
+
45
+ const setSize = ( newSize: string ) => {
46
+ if ( isExtendedUnit( sizeValue.unit ) ) {
47
+ return;
48
+ }
49
+
50
+ const trimmed = newSize.trim();
51
+ const parsed = Number( trimmed );
52
+
53
+ const newState = {
54
+ ...sizeValue,
55
+ size: trimmed && ! isNaN( parsed ) ? parsed : '',
56
+ };
57
+
58
+ setSizeValue( newState );
59
+ };
60
+
61
+ const setUnit = ( unit: SizeValue[ 'unit' ] ) => {
62
+ setSizeValue( { unit, size: resolveSizeOnUnitChange( sizeValue.size, unit ) } as T );
63
+ };
64
+
65
+ return {
66
+ size: sizeValue.size,
67
+ unit: sizeValue.unit,
68
+ setSize,
69
+ setUnit,
70
+ };
71
+ };
72
+
73
+ const hasChanged = ( next?: SizeValue | null, current?: SizeValue | null ): boolean => {
74
+ return next?.size !== current?.size || next?.unit !== current?.unit;
75
+ };
@@ -0,0 +1,73 @@
1
+ import * as React from 'react';
2
+ import { type RefObject, useEffect } from 'react';
3
+ import type { SizePropValue } from '@elementor/editor-props';
4
+ import { usePopupState } from '@elementor/ui';
5
+
6
+ import { SizeField, type SizeFieldProps } from './size-field';
7
+ import { TextFieldPopover } from './ui/text-field-popover';
8
+ import { EXTENDED_UNITS } from './utils/resolve-size-value';
9
+
10
+ type SizeValue = SizePropValue[ 'value' ];
11
+
12
+ type SizeUnit = SizePropValue[ 'value' ][ 'unit' ];
13
+
14
+ type Props = SizeFieldProps< SizeValue, SizeUnit > & {
15
+ anchorRef?: RefObject< HTMLDivElement | null >;
16
+ };
17
+
18
+ export const SizeComponent = ( { anchorRef, ...sizeFieldProps }: Props ) => {
19
+ const popupState = usePopupState( { variant: 'popover' } );
20
+
21
+ const isCustomUnit = sizeFieldProps?.value?.unit === EXTENDED_UNITS.custom;
22
+ const hasCustomUnitOption = sizeFieldProps.units.includes( EXTENDED_UNITS.custom );
23
+
24
+ useEffect( () => {
25
+ if ( isCustomUnit && anchorRef?.current && ! popupState.isOpen ) {
26
+ popupState.open( anchorRef?.current );
27
+ }
28
+
29
+ // eslint-disable-next-line react-hooks/exhaustive-deps
30
+ }, [ anchorRef, isCustomUnit ] );
31
+
32
+ const handleCustomSizeChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
33
+ sizeFieldProps.onChange( {
34
+ size: event.target.value,
35
+ unit: EXTENDED_UNITS.custom,
36
+ } );
37
+ };
38
+
39
+ const handleSizeFieldClick = ( event: React.MouseEvent ) => {
40
+ if ( ( event.target as HTMLElement ).closest( 'input' ) && isCustomUnit ) {
41
+ popupState.open( anchorRef?.current );
42
+ }
43
+ };
44
+
45
+ const popupAttributes = {
46
+ 'aria-controls': popupState.isOpen ? popupState.popupId : undefined,
47
+ 'aria-haspopup': true,
48
+ };
49
+
50
+ return (
51
+ <>
52
+ <SizeField
53
+ { ...sizeFieldProps }
54
+ InputProps={ {
55
+ ...popupAttributes,
56
+ onClick: handleSizeFieldClick,
57
+ } }
58
+ unitSelectorProps={ {
59
+ menuItemsAttributes: hasCustomUnitOption ? { custom: popupAttributes } : undefined,
60
+ } }
61
+ />
62
+ { popupState.isOpen && anchorRef?.current && (
63
+ <TextFieldPopover
64
+ popupState={ popupState }
65
+ anchorRef={ anchorRef as RefObject< HTMLDivElement > }
66
+ value={ String( sizeFieldProps?.value?.size ?? '' ) }
67
+ onChange={ handleCustomSizeChange }
68
+ onClose={ () => {} }
69
+ />
70
+ ) }
71
+ </>
72
+ );
73
+ };
@@ -0,0 +1,89 @@
1
+ import * as React from 'react';
2
+ import type { PropValue, SizePropValue } from '@elementor/editor-props';
3
+ import { MathFunctionIcon } from '@elementor/icons';
4
+ import { InputAdornment, type TextFieldProps } from '@elementor/ui';
5
+
6
+ import { useSizeUnitKeyboard } from './hooks/use-size-unit-keyboard';
7
+ import { useSizeValue } from './hooks/use-size-value';
8
+ import { type SizeUnit } from './types';
9
+ import { SizeInput } from './ui/size-input';
10
+ import { UnitSelector, type UnitSelectorProps } from './ui/unit-selector';
11
+ import { isExtendedUnit } from './utils/is-extended-unit';
12
+
13
+ export type SizeFieldProps< TValue extends SizePropValue[ 'value' ], TUnit extends SizeUnit > = {
14
+ units: TUnit[];
15
+ value: TValue | null;
16
+ placeholder?: string;
17
+ defaultUnit?: SizeUnit;
18
+ startIcon?: React.ReactNode;
19
+ onChange: ( value: TValue ) => void;
20
+ onBlur?: ( event: React.FocusEvent< HTMLInputElement > ) => void;
21
+ onKeyDown?: ( event: React.KeyboardEvent< HTMLInputElement > ) => void;
22
+ disabled?: boolean;
23
+ InputProps?: TextFieldProps[ 'InputProps' ];
24
+ unitSelectorProps?: Partial< UnitSelectorProps< TUnit > >;
25
+ };
26
+
27
+ const UNIT_DISPLAY_LABELS_OVERRIDES: Partial< Record< SizeUnit, React.ReactNode > > = {
28
+ custom: <MathFunctionIcon fontSize="tiny" />,
29
+ };
30
+
31
+ export const SizeField = < T extends SizePropValue[ 'value' ], U extends SizeUnit >( {
32
+ value,
33
+ disabled,
34
+ InputProps,
35
+ defaultUnit,
36
+ startIcon,
37
+ onKeyDown,
38
+ onChange,
39
+ onBlur,
40
+ units,
41
+ unitSelectorProps,
42
+ }: SizeFieldProps< T, U > ) => {
43
+ const { size, unit, setSize, setUnit } = useSizeValue( { value, onChange, units, defaultUnit } );
44
+ const { onUnitKeyDown } = useSizeUnitKeyboard( { unit, onUnitChange: setUnit, units } );
45
+
46
+ const handleKeyDown = ( event: React.KeyboardEvent< HTMLInputElement > ) => {
47
+ onUnitKeyDown( event );
48
+
49
+ onKeyDown?.( event );
50
+ };
51
+
52
+ const inputType = isExtendedUnit( unit ) ? 'text' : 'number';
53
+
54
+ return (
55
+ <SizeInput
56
+ type={ inputType }
57
+ value={ size }
58
+ onBlur={ onBlur }
59
+ onKeyDown={ handleKeyDown }
60
+ onChange={ ( event ) => setSize( event.target.value ) }
61
+ InputProps={ {
62
+ ...InputProps,
63
+ autoComplete: 'off',
64
+ readOnly: isExtendedUnit( unit ),
65
+ startAdornment: startIcon && (
66
+ <InputAdornment position="start" disabled={ disabled }>
67
+ { startIcon }
68
+ </InputAdornment>
69
+ ),
70
+ endAdornment: (
71
+ <InputAdornment position="end">
72
+ <UnitSelector< U >
73
+ options={ units }
74
+ value={ unit as U }
75
+ onSelect={ ( newUnit ) => setUnit( newUnit ) }
76
+ isActive={ hasValue( size ) }
77
+ { ...unitSelectorProps }
78
+ optionLabelOverrides={ UNIT_DISPLAY_LABELS_OVERRIDES }
79
+ />
80
+ </InputAdornment>
81
+ ),
82
+ } }
83
+ />
84
+ );
85
+ };
86
+
87
+ const hasValue = ( value: PropValue ): boolean => {
88
+ return Boolean( value ) || value === 0;
89
+ };
@@ -0,0 +1,3 @@
1
+ export const getExtendedUnits = () => {
2
+ return window.elementor?.config?.size_units?.extended_units ?? [];
3
+ };
@@ -0,0 +1,9 @@
1
+ type LengthUnit = 'px' | '%' | 'em' | 'rem' | 'vw' | 'vh' | 'ch';
2
+ type AngleUnit = 'deg' | 'rad' | 'grad' | 'turn';
3
+ type TimeUnit = 's' | 'ms';
4
+
5
+ type ExtendedSizeOption = 'auto' | 'custom';
6
+
7
+ type Unit = LengthUnit | AngleUnit | TimeUnit;
8
+
9
+ export type SizeUnit = Unit | ExtendedSizeOption;
@@ -0,0 +1,68 @@
1
+ import * as React from 'react';
2
+ import { forwardRef } from 'react';
3
+ import type { PropValue } from '@elementor/editor-props';
4
+ import type { TextFieldProps } from '@elementor/ui';
5
+
6
+ import { NumberInput } from '../../../components/number-input';
7
+
8
+ type Props = {
9
+ type: 'number' | 'text';
10
+ value: PropValue;
11
+ placeholder?: string;
12
+ focused?: boolean;
13
+ disabled?: boolean;
14
+ id?: string;
15
+ onChange: ( event: React.ChangeEvent< HTMLInputElement > ) => void;
16
+ onKeyDown?: ( event: React.KeyboardEvent< HTMLInputElement > ) => void;
17
+ onKeyUp?: ( event: React.KeyboardEvent< HTMLInputElement > ) => void;
18
+ onBlur?: ( event: React.FocusEvent< HTMLInputElement > ) => void;
19
+ InputProps: TextFieldProps[ 'InputProps' ] & {
20
+ endAdornment: React.JSX.Element;
21
+ };
22
+ inputProps?: TextFieldProps[ 'inputProps' ];
23
+ };
24
+
25
+ export const SizeInput = forwardRef(
26
+ (
27
+ {
28
+ id,
29
+ type,
30
+ value,
31
+ onBlur,
32
+ onKeyUp,
33
+ focused,
34
+ disabled,
35
+ onChange,
36
+ onKeyDown,
37
+ InputProps,
38
+ inputProps,
39
+ placeholder,
40
+ }: Props,
41
+ ref
42
+ ) => {
43
+ return (
44
+ <NumberInput
45
+ id={ id }
46
+ ref={ ref }
47
+ size="tiny"
48
+ fullWidth
49
+ type={ type }
50
+ value={ value }
51
+ placeholder={ placeholder }
52
+ onKeyUp={ onKeyUp }
53
+ focused={ focused }
54
+ disabled={ disabled }
55
+ onKeyDown={ onKeyDown }
56
+ onInput={ onChange }
57
+ onBlur={ onBlur }
58
+ InputProps={ InputProps }
59
+ inputProps={ inputProps }
60
+ sx={ getCursorStyle( InputProps?.readOnly ?? false ) }
61
+ />
62
+ );
63
+ }
64
+ );
65
+
66
+ const getCursorStyle = ( readOnly: boolean ) => ( {
67
+ input: { cursor: readOnly ? 'default !important' : undefined },
68
+ } );
@@ -0,0 +1,78 @@
1
+ import * as React from 'react';
2
+ import { type RefObject, useEffect, useRef } from 'react';
3
+ import { PopoverHeader } from '@elementor/editor-ui';
4
+ import { MathFunctionIcon } from '@elementor/icons';
5
+ import { bindPopover, Popover, type PopupState, TextField } from '@elementor/ui';
6
+ import { __ } from '@wordpress/i18n';
7
+
8
+ type Props = {
9
+ popupState: PopupState;
10
+ anchorRef: RefObject< HTMLDivElement | null >;
11
+ onClose?: () => void;
12
+ value: string;
13
+ onChange: ( event: React.ChangeEvent< HTMLInputElement > ) => void;
14
+ };
15
+
16
+ const SIZE = 'tiny';
17
+
18
+ export const TextFieldPopover = ( { popupState, anchorRef, value, onChange, onClose }: Props ) => {
19
+ const inputRef = useRef< HTMLInputElement >( null );
20
+
21
+ useEffect( () => {
22
+ if ( popupState.isOpen ) {
23
+ requestAnimationFrame( () => {
24
+ if ( inputRef.current ) {
25
+ inputRef.current.focus();
26
+ }
27
+ } );
28
+ }
29
+ }, [ popupState.isOpen ] );
30
+
31
+ const handleKeyDown = ( event: React.KeyboardEvent< HTMLInputElement > ) => {
32
+ if ( event.key.toLowerCase() === 'enter' ) {
33
+ handleClose();
34
+ }
35
+ };
36
+
37
+ const handleClose = () => {
38
+ onClose?.();
39
+
40
+ popupState.close();
41
+ };
42
+
43
+ return (
44
+ <Popover
45
+ disablePortal
46
+ slotProps={ {
47
+ paper: {
48
+ sx: {
49
+ borderRadius: 2,
50
+ width: anchorRef.current?.offsetWidth + 'px',
51
+ },
52
+ },
53
+ } }
54
+ { ...bindPopover( popupState ) }
55
+ anchorOrigin={ { vertical: 'bottom', horizontal: 'center' } }
56
+ transformOrigin={ { vertical: 'top', horizontal: 'center' } }
57
+ onClose={ handleClose }
58
+ >
59
+ <PopoverHeader
60
+ title={ __( 'CSS function', 'elementor' ) }
61
+ onClose={ handleClose }
62
+ icon={ <MathFunctionIcon fontSize={ SIZE } /> }
63
+ />
64
+ <TextField
65
+ value={ value }
66
+ onChange={ onChange }
67
+ onKeyDown={ handleKeyDown }
68
+ size="tiny"
69
+ type="text"
70
+ fullWidth
71
+ inputProps={ {
72
+ ref: inputRef,
73
+ } }
74
+ sx={ { pt: 0, pr: 1.5, pb: 1.5, pl: 1.5 } }
75
+ />
76
+ </Popover>
77
+ );
78
+ };