@elementor/editor-controls 0.36.0 → 1.0.0

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": "0.36.0",
4
+ "version": "1.0.0",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -41,8 +41,9 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@elementor/editor-current-user": "0.5.0",
44
- "@elementor/editor-elements": "0.8.4",
45
- "@elementor/editor-props": "0.12.1",
44
+ "@elementor/editor-elements": "0.8.5",
45
+ "@elementor/editor-props": "0.13.0",
46
+ "@elementor/editor-responsive": "0.13.5",
46
47
  "@elementor/editor-ui": "0.11.0",
47
48
  "@elementor/editor-v1-adapters": "0.12.0",
48
49
  "@elementor/env": "0.3.5",
@@ -22,7 +22,10 @@ type UseBoundProp< TValue extends PropValue > = {
22
22
  disabled?: boolean;
23
23
  };
24
24
 
25
- export function useBoundProp< T extends PropValue = PropValue >(): PropKeyContextValue< T, PropType >;
25
+ export function useBoundProp< T extends PropValue = PropValue, P extends PropType = PropType >(): PropKeyContextValue<
26
+ T,
27
+ P
28
+ >;
26
29
 
27
30
  export function useBoundProp< TKey extends string, TValue extends PropValue >(
28
31
  propTypeUtil: PropTypeUtil< TKey, TValue >
@@ -1,4 +1,4 @@
1
- import { type FC, type PropsWithChildren } from 'react';
1
+ import { forwardRef, type PropsWithChildren } from 'react';
2
2
  import * as React from 'react';
3
3
  import { Grid } from '@elementor/ui';
4
4
 
@@ -8,13 +8,10 @@ type PopoverGridContainerProps = PropsWithChildren< {
8
8
  flexWrap?: React.ComponentProps< typeof Grid >[ 'flexWrap' ];
9
9
  } >;
10
10
 
11
- export const PopoverGridContainer: FC< PopoverGridContainerProps > = ( {
12
- gap = 1.5,
13
- alignItems = 'center',
14
- flexWrap = 'nowrap',
15
- children,
16
- } ) => (
17
- <Grid container gap={ gap } alignItems={ alignItems } flexWrap={ flexWrap }>
18
- { children }
19
- </Grid>
11
+ export const PopoverGridContainer = forwardRef(
12
+ ( { gap = 1.5, alignItems = 'center', flexWrap = 'nowrap', children }: PopoverGridContainerProps, ref ) => (
13
+ <Grid container gap={ gap } alignItems={ alignItems } flexWrap={ flexWrap } ref={ ref }>
14
+ { children }
15
+ </Grid>
16
+ )
20
17
  );
@@ -0,0 +1,125 @@
1
+ import * as React from 'react';
2
+ import { useRef } from 'react';
3
+ import { PencilIcon } from '@elementor/icons';
4
+ import { Box, InputAdornment, type PopupState } from '@elementor/ui';
5
+
6
+ import ControlActions from '../../control-actions/control-actions';
7
+ import { type ExtendedOption, isUnitExtendedOption, type Unit } from '../../utils/size-control';
8
+ import { SelectionEndAdornment, TextFieldInnerSelection } from '../size-control/text-field-inner-selection';
9
+
10
+ type SizeInputProps = {
11
+ unit: Unit | ExtendedOption;
12
+ size: number | string;
13
+ placeholder?: string;
14
+ startIcon?: React.ReactNode;
15
+ units: ( Unit | ExtendedOption )[];
16
+ onBlur?: ( event: React.FocusEvent< HTMLInputElement > ) => void;
17
+ onFocus?: ( event: React.FocusEvent< HTMLInputElement > ) => void;
18
+ onClick?: ( event: React.MouseEvent< HTMLInputElement > ) => void;
19
+ handleUnitChange: ( unit: Unit | ExtendedOption ) => void;
20
+ handleSizeChange: ( event: React.ChangeEvent< HTMLInputElement > ) => void;
21
+ popupState: PopupState;
22
+ disabled?: boolean;
23
+ };
24
+
25
+ const RESTRICTED_INPUT_KEYS = [ 'e', 'E', '+', '-' ];
26
+
27
+ export const SizeInput = ( {
28
+ units,
29
+ handleUnitChange,
30
+ handleSizeChange,
31
+ placeholder,
32
+ startIcon,
33
+ onBlur,
34
+ onFocus,
35
+ onClick,
36
+ size,
37
+ unit,
38
+ popupState,
39
+ disabled,
40
+ }: SizeInputProps ) => {
41
+ const unitInputBufferRef = useRef( '' );
42
+ const inputType = isUnitExtendedOption( unit ) ? 'text' : 'number';
43
+ const inputValue = ! isUnitExtendedOption( unit ) && Number.isNaN( size ) ? '' : size ?? '';
44
+
45
+ const handleKeyUp = ( event: React.KeyboardEvent< HTMLInputElement > ) => {
46
+ const { key } = event;
47
+
48
+ if ( ! /^[a-zA-Z%]$/.test( key ) ) {
49
+ return;
50
+ }
51
+
52
+ event.preventDefault();
53
+
54
+ const newChar = key.toLowerCase();
55
+ const updatedBuffer = ( unitInputBufferRef.current + newChar ).slice( -3 );
56
+ unitInputBufferRef.current = updatedBuffer;
57
+
58
+ const matchedUnit =
59
+ units.find( ( u ) => u.includes( updatedBuffer ) ) ||
60
+ units.find( ( u ) => u.startsWith( newChar ) ) ||
61
+ units.find( ( u ) => u.includes( newChar ) );
62
+
63
+ if ( matchedUnit ) {
64
+ handleUnitChange( matchedUnit );
65
+ }
66
+ };
67
+
68
+ const popupAttributes = {
69
+ 'aria-controls': popupState.isOpen ? popupState.popupId : undefined,
70
+ 'aria-haspopup': true,
71
+ };
72
+
73
+ const inputProps = {
74
+ ...popupAttributes,
75
+ autoComplete: 'off',
76
+ onClick,
77
+ onFocus,
78
+ startAdornment: startIcon ? (
79
+ <InputAdornment position="start" disabled={ disabled }>
80
+ { startIcon }
81
+ </InputAdornment>
82
+ ) : undefined,
83
+ endAdornment: (
84
+ <SelectionEndAdornment
85
+ disabled={ disabled }
86
+ options={ units }
87
+ onClick={ handleUnitChange }
88
+ value={ unit }
89
+ alternativeOptionLabels={ {
90
+ custom: <PencilIcon fontSize="small" />,
91
+ } }
92
+ menuItemsAttributes={
93
+ units.includes( 'custom' )
94
+ ? {
95
+ custom: popupAttributes,
96
+ }
97
+ : undefined
98
+ }
99
+ />
100
+ ),
101
+ };
102
+
103
+ return (
104
+ <ControlActions>
105
+ <Box>
106
+ <TextFieldInnerSelection
107
+ disabled={ disabled }
108
+ placeholder={ placeholder }
109
+ type={ inputType }
110
+ value={ inputValue }
111
+ onChange={ handleSizeChange }
112
+ onKeyDown={ ( event ) => {
113
+ if ( RESTRICTED_INPUT_KEYS.includes( event.key ) ) {
114
+ event.preventDefault();
115
+ }
116
+ } }
117
+ onKeyUp={ handleKeyUp }
118
+ onBlur={ onBlur }
119
+ shouldBlockInput={ isUnitExtendedOption( unit ) }
120
+ inputProps={ inputProps }
121
+ />
122
+ </Box>
123
+ </ControlActions>
124
+ );
125
+ };
@@ -2,7 +2,16 @@ import * as React from 'react';
2
2
  import { forwardRef, useId } from 'react';
3
3
  import { type PropValue } from '@elementor/editor-props';
4
4
  import { MenuListItem } from '@elementor/editor-ui';
5
- import { bindMenu, bindTrigger, Button, InputAdornment, Menu, TextField, usePopupState } from '@elementor/ui';
5
+ import {
6
+ bindMenu,
7
+ bindTrigger,
8
+ Button,
9
+ InputAdornment,
10
+ Menu,
11
+ TextField,
12
+ type TextFieldProps,
13
+ usePopupState,
14
+ } from '@elementor/ui';
6
15
 
7
16
  type TextFieldInnerSelectionProps = {
8
17
  placeholder?: string;
@@ -12,8 +21,10 @@ type TextFieldInnerSelectionProps = {
12
21
  onBlur?: ( event: React.FocusEvent< HTMLInputElement > ) => void;
13
22
  onKeyDown?: ( event: React.KeyboardEvent< HTMLInputElement > ) => void;
14
23
  onKeyUp?: ( event: React.KeyboardEvent< HTMLInputElement > ) => void;
15
- endAdornment: React.ReactNode;
16
- startAdornment?: React.ReactNode;
24
+ shouldBlockInput?: boolean;
25
+ inputProps: TextFieldProps[ 'InputProps' ] & {
26
+ endAdornment: React.JSX.Element;
27
+ };
17
28
  disabled?: boolean;
18
29
  };
19
30
 
@@ -27,8 +38,8 @@ export const TextFieldInnerSelection = forwardRef(
27
38
  onBlur,
28
39
  onKeyDown,
29
40
  onKeyUp,
30
- endAdornment,
31
- startAdornment,
41
+ shouldBlockInput = false,
42
+ inputProps,
32
43
  disabled,
33
44
  }: TextFieldInnerSelectionProps,
34
45
  ref
@@ -36,20 +47,18 @@ export const TextFieldInnerSelection = forwardRef(
36
47
  return (
37
48
  <TextField
38
49
  ref={ ref }
50
+ sx={ { input: { cursor: shouldBlockInput ? 'default !important' : undefined } } }
39
51
  size="tiny"
40
52
  fullWidth
41
- type={ type }
53
+ type={ shouldBlockInput ? undefined : type }
42
54
  value={ value }
55
+ onChange={ shouldBlockInput ? undefined : onChange }
56
+ onKeyDown={ shouldBlockInput ? undefined : onKeyDown }
57
+ onKeyUp={ shouldBlockInput ? undefined : onKeyUp }
43
58
  disabled={ disabled }
44
- onChange={ onChange }
45
- onKeyDown={ onKeyDown }
46
- onKeyUp={ onKeyUp }
47
59
  onBlur={ onBlur }
48
60
  placeholder={ placeholder }
49
- InputProps={ {
50
- endAdornment,
51
- startAdornment,
52
- } }
61
+ InputProps={ inputProps }
53
62
  />
54
63
  );
55
64
  }
@@ -59,13 +68,17 @@ type SelectionEndAdornmentProps< T extends string > = {
59
68
  options: T[];
60
69
  onClick: ( value: T ) => void;
61
70
  value: T;
71
+ alternativeOptionLabels?: { [ key in T ]?: React.ReactNode };
72
+ menuItemsAttributes?: { [ key in T ]?: Record< string, unknown > };
62
73
  disabled?: boolean;
63
74
  };
64
75
 
65
76
  export const SelectionEndAdornment = < T extends string >( {
66
77
  options,
78
+ alternativeOptionLabels = {} as Record< T, React.ReactNode >,
67
79
  onClick,
68
80
  value,
81
+ menuItemsAttributes = {},
69
82
  disabled,
70
83
  }: SelectionEndAdornmentProps< T > ) => {
71
84
  const popupState = usePopupState( {
@@ -87,13 +100,17 @@ export const SelectionEndAdornment = < T extends string >( {
87
100
  sx={ { font: 'inherit', minWidth: 'initial', textTransform: 'uppercase' } }
88
101
  { ...bindTrigger( popupState ) }
89
102
  >
90
- { value }
103
+ { alternativeOptionLabels[ value ] ?? value }
91
104
  </Button>
92
105
 
93
106
  <Menu MenuListProps={ { dense: true } } { ...bindMenu( popupState ) }>
94
107
  { options.map( ( option, index ) => (
95
- <MenuListItem key={ option } onClick={ () => handleMenuItemClick( index ) }>
96
- { option.toUpperCase() }
108
+ <MenuListItem
109
+ key={ option }
110
+ onClick={ () => handleMenuItemClick( index ) }
111
+ { ...menuItemsAttributes?.[ option ] }
112
+ >
113
+ { alternativeOptionLabels[ option ] ?? option.toUpperCase() }
97
114
  </MenuListItem>
98
115
  ) ) }
99
116
  </Menu>
@@ -0,0 +1,47 @@
1
+ import * as React from 'react';
2
+ import { type MutableRefObject } from 'react';
3
+ import { bindPopover, Paper, Popover, type PopupState, TextField } from '@elementor/ui';
4
+
5
+ type Props = {
6
+ popupState: PopupState;
7
+ anchorRef: MutableRefObject< HTMLElement >;
8
+ restoreValue: () => void;
9
+ value: string;
10
+ onChange: ( event: React.ChangeEvent< HTMLInputElement > ) => void;
11
+ };
12
+
13
+ export const TextFieldPopover = ( props: Props ) => {
14
+ const { popupState, restoreValue, anchorRef, value, onChange } = props;
15
+
16
+ return (
17
+ <Popover
18
+ disablePortal
19
+ { ...bindPopover( popupState ) }
20
+ anchorOrigin={ { vertical: 'bottom', horizontal: 'center' } }
21
+ transformOrigin={ { vertical: 'top', horizontal: 'center' } }
22
+ onClose={ () => {
23
+ restoreValue();
24
+ popupState.close();
25
+ } }
26
+ >
27
+ <Paper
28
+ sx={ {
29
+ width: anchorRef.current.offsetWidth + 'px',
30
+ borderRadius: 2,
31
+ p: 1.5,
32
+ } }
33
+ >
34
+ <TextField
35
+ value={ value }
36
+ onChange={ onChange }
37
+ size="tiny"
38
+ type="text"
39
+ fullWidth
40
+ inputProps={ {
41
+ autoFocus: true,
42
+ } }
43
+ />
44
+ </Paper>
45
+ </Popover>
46
+ );
47
+ };
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { type MutableRefObject, useRef } from 'react';
2
3
  import { backgroundImagePositionOffsetPropTypeUtil, stringPropTypeUtil } from '@elementor/editor-props';
3
4
  import { MenuListItem } from '@elementor/editor-ui';
4
5
  import { LetterXIcon, LetterYIcon } from '@elementor/icons';
@@ -40,6 +41,7 @@ export const BackgroundImageOverlayPosition = () => {
40
41
  const stringPropContext = useBoundProp( stringPropTypeUtil );
41
42
 
42
43
  const isCustom = !! backgroundImageOffsetContext.value;
44
+ const rowRef: MutableRefObject< HTMLElement | undefined > = useRef();
43
45
 
44
46
  const handlePositionChange = ( event: SelectChangeEvent< Positions > ) => {
45
47
  const value = event.target.value || null;
@@ -78,15 +80,21 @@ export const BackgroundImageOverlayPosition = () => {
78
80
  { isCustom ? (
79
81
  <PropProvider { ...backgroundImageOffsetContext }>
80
82
  <Grid item xs={ 12 }>
81
- <Grid container spacing={ 1.5 }>
83
+ <Grid container spacing={ 1.5 } ref={ rowRef }>
82
84
  <Grid item xs={ 6 }>
83
85
  <PropKeyProvider bind={ 'x' }>
84
- <SizeControl startIcon={ <LetterXIcon fontSize={ 'tiny' } /> } />
86
+ <SizeControl
87
+ startIcon={ <LetterXIcon fontSize={ 'tiny' } /> }
88
+ anchorRef={ rowRef }
89
+ />
85
90
  </PropKeyProvider>
86
91
  </Grid>
87
92
  <Grid item xs={ 6 }>
88
93
  <PropKeyProvider bind={ 'y' }>
89
- <SizeControl startIcon={ <LetterYIcon fontSize={ 'tiny' } /> } />
94
+ <SizeControl
95
+ startIcon={ <LetterYIcon fontSize={ 'tiny' } /> }
96
+ anchorRef={ rowRef }
97
+ />
90
98
  </PropKeyProvider>
91
99
  </Grid>
92
100
  </Grid>
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { type MutableRefObject, useRef } from 'react';
2
3
  import { backgroundImageSizeScalePropTypeUtil, stringPropTypeUtil } from '@elementor/editor-props';
3
4
  import {
4
5
  ArrowBarBothIcon,
@@ -54,6 +55,7 @@ export const BackgroundImageOverlaySize = () => {
54
55
  const stringPropContext = useBoundProp( stringPropTypeUtil );
55
56
 
56
57
  const isCustom = !! backgroundImageScaleContext.value;
58
+ const rowRef: MutableRefObject< HTMLElement | undefined > = useRef();
57
59
 
58
60
  const handleSizeChange = ( size: Sizes | null ) => {
59
61
  if ( size === 'custom' ) {
@@ -85,13 +87,14 @@ export const BackgroundImageOverlaySize = () => {
85
87
  </Grid>
86
88
  { isCustom ? (
87
89
  <PropProvider { ...backgroundImageScaleContext }>
88
- <Grid item xs={ 12 }>
90
+ <Grid item xs={ 12 } ref={ rowRef }>
89
91
  <PopoverGridContainer>
90
92
  <Grid item xs={ 6 }>
91
93
  <PropKeyProvider bind={ 'width' }>
92
94
  <SizeControl
93
95
  startIcon={ <ArrowsMoveHorizontalIcon fontSize={ 'tiny' } /> }
94
- extendedValues={ [ 'auto' ] }
96
+ extendedOptions={ [ 'auto' ] }
97
+ anchorRef={ rowRef }
95
98
  />
96
99
  </PropKeyProvider>
97
100
  </Grid>
@@ -99,7 +102,8 @@ export const BackgroundImageOverlaySize = () => {
99
102
  <PropKeyProvider bind={ 'height' }>
100
103
  <SizeControl
101
104
  startIcon={ <ArrowsMoveVerticalIcon fontSize={ 'tiny' } /> }
102
- extendedValues={ [ 'auto' ] }
105
+ extendedOptions={ [ 'auto' ] }
106
+ anchorRef={ rowRef }
103
107
  />
104
108
  </PropKeyProvider>
105
109
  </Grid>
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { type MutableRefObject, useRef } from 'react';
2
3
  import { boxShadowPropTypeUtil, type PropKey, shadowPropTypeUtil, type ShadowPropValue } from '@elementor/editor-props';
3
4
  import { FormLabel, Grid, type SxProps, type Theme, UnstableColorIndicator } from '@elementor/ui';
4
5
  import { __ } from '@wordpress/i18n';
@@ -48,6 +49,7 @@ const ItemContent = ( { anchorEl, bind }: { anchorEl: HTMLElement | null; bind:
48
49
 
49
50
  const Content = ( { anchorEl }: { anchorEl: HTMLElement | null } ) => {
50
51
  const context = useBoundProp( shadowPropTypeUtil );
52
+ const rowRef: MutableRefObject< HTMLElement | undefined >[] = [ useRef(), useRef() ];
51
53
 
52
54
  return (
53
55
  <PropProvider { ...context }>
@@ -65,20 +67,20 @@ const Content = ( { anchorEl }: { anchorEl: HTMLElement | null } ) => {
65
67
  />
66
68
  </Control>
67
69
  </PopoverGridContainer>
68
- <PopoverGridContainer>
70
+ <PopoverGridContainer ref={ rowRef[ 0 ] }>
69
71
  <Control bind="hOffset" label={ __( 'Horizontal', 'elementor' ) }>
70
- <SizeControl />
72
+ <SizeControl anchorRef={ rowRef[ 0 ] } />
71
73
  </Control>
72
74
  <Control bind="vOffset" label={ __( 'Vertical', 'elementor' ) }>
73
- <SizeControl />
75
+ <SizeControl anchorRef={ rowRef[ 0 ] } />
74
76
  </Control>
75
77
  </PopoverGridContainer>
76
- <PopoverGridContainer>
78
+ <PopoverGridContainer ref={ rowRef[ 1 ] }>
77
79
  <Control bind="blur" label={ __( 'Blur', 'elementor' ) }>
78
- <SizeControl />
80
+ <SizeControl anchorRef={ rowRef[ 1 ] } />
79
81
  </Control>
80
82
  <Control bind="spread" label={ __( 'Spread', 'elementor' ) }>
81
- <SizeControl />
83
+ <SizeControl anchorRef={ rowRef[ 1 ] } />
82
84
  </Control>
83
85
  </PopoverGridContainer>
84
86
  </PopoverContent>
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { type ReactNode, useId, useRef } from 'react';
2
+ import { type MutableRefObject, type ReactNode, useId, useRef } from 'react';
3
3
  import { type PropKey, type PropTypeUtil, sizePropTypeUtil, type SizePropValue } from '@elementor/editor-props';
4
4
  import { isExperimentActive } from '@elementor/editor-v1-adapters';
5
5
  import { bindPopover, bindToggle, Grid, Popover, Stack, ToggleButton, Tooltip, usePopupState } from '@elementor/ui';
@@ -52,7 +52,6 @@ export function EqualUnequalSizesControl< TMultiPropType extends string, TPropVa
52
52
  multiSizePropTypeUtil,
53
53
  }: Props< TMultiPropType, TPropValue > ) {
54
54
  const popupId = useId();
55
- const controlRef = useRef< HTMLElement >( null );
56
55
  const popupState = usePopupState( { variant: 'popover', popupId } );
57
56
 
58
57
  const {
@@ -64,6 +63,8 @@ export function EqualUnequalSizesControl< TMultiPropType extends string, TPropVa
64
63
 
65
64
  const { value: sizeValue, setValue: setSizeValue } = useBoundProp( sizePropTypeUtil );
66
65
 
66
+ const rowRefs: MutableRefObject< HTMLElement | undefined >[] = [ useRef(), useRef() ];
67
+
67
68
  const splitEqualValue = () => {
68
69
  if ( ! sizeValue ) {
69
70
  return null;
@@ -104,7 +105,7 @@ export function EqualUnequalSizesControl< TMultiPropType extends string, TPropVa
104
105
 
105
106
  return (
106
107
  <>
107
- <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap" ref={ controlRef }>
108
+ <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap" ref={ rowRefs[ 0 ] }>
108
109
  <Grid item xs={ 6 }>
109
110
  { ! isShowingGeneralIndicator ? (
110
111
  <ControlFormLabel>{ label }</ControlFormLabel>
@@ -114,7 +115,10 @@ export function EqualUnequalSizesControl< TMultiPropType extends string, TPropVa
114
115
  </Grid>
115
116
  <Grid item xs={ 6 }>
116
117
  <Stack direction="row" alignItems="center" gap={ 1 }>
117
- <SizeControl placeholder={ isMixed ? __( 'Mixed', 'elementor' ) : undefined } />
118
+ <SizeControl
119
+ placeholder={ isMixed ? __( 'Mixed', 'elementor' ) : undefined }
120
+ anchorRef={ rowRefs[ 0 ] }
121
+ />
118
122
  <Tooltip title={ tooltipLabel } placement="top">
119
123
  <ToggleButton
120
124
  size={ 'tiny' }
@@ -143,7 +147,7 @@ export function EqualUnequalSizesControl< TMultiPropType extends string, TPropVa
143
147
  } }
144
148
  { ...bindPopover( popupState ) }
145
149
  slotProps={ {
146
- paper: { sx: { mt: 0.5, width: controlRef.current?.getBoundingClientRect().width } },
150
+ paper: { sx: { mt: 0.5, width: rowRefs[ 0 ].current?.getBoundingClientRect().width } },
147
151
  } }
148
152
  >
149
153
  <PropProvider
@@ -152,14 +156,14 @@ export function EqualUnequalSizesControl< TMultiPropType extends string, TPropVa
152
156
  setValue={ setNestedProp }
153
157
  disabled={ multiSizeDisabled }
154
158
  >
155
- <PopoverContent p={ 1.5 } pt={ 2.5 } pb={ 3 }>
156
- <PopoverGridContainer>
157
- <MultiSizeValueControl item={ items[ 0 ] } />
158
- <MultiSizeValueControl item={ items[ 1 ] } />
159
+ <PopoverContent p={ 1.5 }>
160
+ <PopoverGridContainer ref={ rowRefs[ 1 ] }>
161
+ <MultiSizeValueControl item={ items[ 0 ] } rowRef={ rowRefs[ 1 ] } />
162
+ <MultiSizeValueControl item={ items[ 1 ] } rowRef={ rowRefs[ 1 ] } />
159
163
  </PopoverGridContainer>
160
- <PopoverGridContainer>
161
- <MultiSizeValueControl item={ items[ 2 ] } />
162
- <MultiSizeValueControl item={ items[ 3 ] } />
164
+ <PopoverGridContainer ref={ rowRefs[ 2 ] }>
165
+ <MultiSizeValueControl item={ items[ 2 ] } rowRef={ rowRefs[ 2 ] } />
166
+ <MultiSizeValueControl item={ items[ 3 ] } rowRef={ rowRefs[ 2 ] } />
163
167
  </PopoverGridContainer>
164
168
  </PopoverContent>
165
169
  </PropProvider>
@@ -168,7 +172,13 @@ export function EqualUnequalSizesControl< TMultiPropType extends string, TPropVa
168
172
  );
169
173
  }
170
174
 
171
- const MultiSizeValueControl = ( { item }: { item: Item } ) => {
175
+ const MultiSizeValueControl = ( {
176
+ item,
177
+ rowRef,
178
+ }: {
179
+ item: Item;
180
+ rowRef: MutableRefObject< HTMLElement | undefined >;
181
+ } ) => {
172
182
  const isUsingNestedProps = isExperimentActive( 'e_v_3_30' );
173
183
 
174
184
  return (
@@ -183,7 +193,7 @@ const MultiSizeValueControl = ( { item }: { item: Item } ) => {
183
193
  ) }
184
194
  </Grid>
185
195
  <Grid item xs={ 12 }>
186
- <SizeControl startIcon={ item.icon } />
196
+ <SizeControl startIcon={ item.icon } anchorRef={ rowRef } />
187
197
  </Grid>
188
198
  </Grid>
189
199
  </Grid>
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { type MutableRefObject, useRef } from 'react';
2
3
  import { layoutDirectionPropTypeUtil, type PropKey, sizePropTypeUtil } from '@elementor/editor-props';
3
4
  import { DetachIcon, LinkIcon } from '@elementor/icons';
4
5
  import { Grid, Stack, ToggleButton, Tooltip } from '@elementor/ui';
@@ -18,6 +19,8 @@ export const GapControl = createControl( ( { label }: { label: string } ) => {
18
19
  disabled: directionDisabled,
19
20
  } = useBoundProp( layoutDirectionPropTypeUtil );
20
21
 
22
+ const stackRef: MutableRefObject< HTMLElement | undefined > = useRef();
23
+
21
24
  const { value: sizeValue, setValue: setSizeValue, disabled: sizeDisabled } = useBoundProp( sizePropTypeUtil );
22
25
 
23
26
  const isLinked = ! directionValue && ! sizeValue ? true : !! sizeValue;
@@ -64,13 +67,13 @@ export const GapControl = createControl( ( { label }: { label: string } ) => {
64
67
  </ToggleButton>
65
68
  </Tooltip>
66
69
  </Stack>
67
- <Stack direction="row" gap={ 2 } flexWrap="nowrap">
70
+ <Stack direction="row" gap={ 2 } flexWrap="nowrap" ref={ stackRef }>
68
71
  <Grid container gap={ 0.75 } alignItems="center">
69
72
  <Grid item xs={ 12 }>
70
73
  <ControlFormLabel>{ __( 'Column', 'elementor' ) }</ControlFormLabel>
71
74
  </Grid>
72
75
  <Grid item xs={ 12 }>
73
- <Control bind={ 'column' } isLinked={ isLinked } />
76
+ <Control bind={ 'column' } isLinked={ isLinked } anchorRef={ stackRef } />
74
77
  </Grid>
75
78
  </Grid>
76
79
  <Grid container gap={ 0.75 } alignItems="center">
@@ -78,7 +81,7 @@ export const GapControl = createControl( ( { label }: { label: string } ) => {
78
81
  <ControlFormLabel>{ __( 'Row', 'elementor' ) }</ControlFormLabel>
79
82
  </Grid>
80
83
  <Grid item xs={ 12 }>
81
- <Control bind={ 'row' } isLinked={ isLinked } />
84
+ <Control bind={ 'row' } isLinked={ isLinked } anchorRef={ stackRef } />
82
85
  </Grid>
83
86
  </Grid>
84
87
  </Stack>
@@ -86,14 +89,22 @@ export const GapControl = createControl( ( { label }: { label: string } ) => {
86
89
  );
87
90
  } );
88
91
 
89
- const Control = ( { bind, isLinked }: { bind: PropKey; isLinked: boolean } ) => {
92
+ const Control = ( {
93
+ bind,
94
+ isLinked,
95
+ anchorRef,
96
+ }: {
97
+ bind: PropKey;
98
+ isLinked: boolean;
99
+ anchorRef: MutableRefObject< HTMLElement | undefined >;
100
+ } ) => {
90
101
  if ( isLinked ) {
91
- return <SizeControl />;
102
+ return <SizeControl anchorRef={ anchorRef } />;
92
103
  }
93
104
 
94
105
  return (
95
106
  <PropKeyProvider bind={ bind }>
96
- <SizeControl />
107
+ <SizeControl anchorRef={ anchorRef } />
97
108
  </PropKeyProvider>
98
109
  );
99
110
  };