@elementor/editor-controls 0.36.0 → 1.1.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.
Files changed (30) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/index.d.mts +78 -45
  3. package/dist/index.d.ts +78 -45
  4. package/dist/index.js +951 -651
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +890 -596
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +8 -7
  9. package/src/bound-prop-context/use-bound-prop.ts +4 -1
  10. package/src/components/font-family-selector.tsx +23 -164
  11. package/src/components/popover-grid-container.tsx +7 -10
  12. package/src/components/repeater.tsx +24 -10
  13. package/src/components/size-control/size-input.tsx +125 -0
  14. package/src/components/{text-field-inner-selection.tsx → size-control/text-field-inner-selection.tsx} +33 -16
  15. package/src/components/text-field-popover.tsx +47 -0
  16. package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-position.tsx +11 -3
  17. package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-size.tsx +7 -3
  18. package/src/controls/box-shadow-repeater-control.tsx +8 -6
  19. package/src/controls/equal-unequal-sizes-control.tsx +24 -14
  20. package/src/controls/gap-control.tsx +17 -6
  21. package/src/controls/key-value-control.tsx +99 -0
  22. package/src/controls/linked-dimensions-control.tsx +62 -81
  23. package/src/controls/position-control.tsx +109 -0
  24. package/src/controls/repeatable-control.tsx +89 -0
  25. package/src/controls/size-control.tsx +181 -149
  26. package/src/controls/stroke-control.tsx +9 -6
  27. package/src/hooks/use-repeatable-control-context.ts +24 -0
  28. package/src/hooks/use-size-extended-options.ts +21 -0
  29. package/src/index.ts +4 -1
  30. package/src/utils/size-control.ts +10 -0
@@ -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
  };
@@ -0,0 +1,99 @@
1
+ import * as React from 'react';
2
+ import { type ChangeEvent, useMemo, useState } from 'react';
3
+ import { keyValuePropTypeUtil } from '@elementor/editor-props';
4
+ import { FormHelperText, FormLabel, Grid, type SxProps, TextField, type Theme } from '@elementor/ui';
5
+ import { __ } from '@wordpress/i18n';
6
+
7
+ import { useBoundProp } from '../bound-prop-context';
8
+ import ControlActions from '../control-actions/control-actions';
9
+ import { createControl } from '../create-control';
10
+
11
+ type FieldType = 'key' | 'value';
12
+
13
+ type KeyValueControlProps = {
14
+ keyName?: string;
15
+ valueName?: string;
16
+ sx?: SxProps< Theme >;
17
+ regexKey?: string;
18
+ regexValue?: string;
19
+ validationErrorMessage?: string;
20
+ };
21
+
22
+ export const KeyValueControl = createControl( ( props: KeyValueControlProps = {} ) => {
23
+ const { value, setValue } = useBoundProp( keyValuePropTypeUtil );
24
+ const [ keyError, setKeyError ] = useState< string | null >( null );
25
+ const [ valueError, setValueError ] = useState< string | null >( null );
26
+ const keyLabel = props.keyName || __( 'Key', 'elementor' );
27
+ const valueLabel = props.valueName || __( 'Value', 'elementor' );
28
+
29
+ const keyValue = value?.key?.value || '';
30
+ const valueValue = value?.value?.value || '';
31
+
32
+ const [ keyRegex, valueRegex, errMsg ] = useMemo< [ RegExp | undefined, RegExp | undefined, string ] >(
33
+ () => [
34
+ props.regexKey ? new RegExp( props.regexKey ) : undefined,
35
+ props.regexValue ? new RegExp( props.regexValue ) : undefined,
36
+ props.validationErrorMessage || __( 'Invalid Format', 'elementor' ),
37
+ ],
38
+ [ props.regexKey, props.regexValue, props.validationErrorMessage ]
39
+ );
40
+
41
+ const validate = ( newValue: string, FieldType: string ): void => {
42
+ if ( FieldType === 'key' && keyRegex ) {
43
+ const isValid = keyRegex.test( newValue );
44
+ setKeyError( isValid ? null : errMsg );
45
+ } else if ( FieldType === 'value' && valueRegex ) {
46
+ const isValid = valueRegex.test( newValue );
47
+ setValueError( isValid ? null : errMsg );
48
+ }
49
+ };
50
+
51
+ const handleChange = ( event: ChangeEvent< HTMLInputElement >, fieldType: FieldType ) => {
52
+ const newValue = event.target.value;
53
+
54
+ validate( newValue, fieldType );
55
+
56
+ setValue( {
57
+ ...value,
58
+ [ fieldType ]: {
59
+ value: newValue,
60
+ $$type: 'string',
61
+ },
62
+ } );
63
+ };
64
+
65
+ const isKeyInvalid = keyError !== null;
66
+ const isValueInvalid = valueError !== null;
67
+
68
+ return (
69
+ <ControlActions>
70
+ <Grid container gap={ 1.5 } p={ 1.5 } sx={ props.sx }>
71
+ <Grid item xs={ 12 }>
72
+ <FormLabel size="tiny">{ keyLabel }</FormLabel>
73
+ <TextField
74
+ sx={ { pt: 1 } }
75
+ size="tiny"
76
+ fullWidth
77
+ value={ keyValue }
78
+ onChange={ ( e: ChangeEvent< HTMLInputElement > ) => handleChange( e, 'key' ) }
79
+ error={ isKeyInvalid }
80
+ />
81
+ { isKeyInvalid && <FormHelperText error>{ keyError }</FormHelperText> }
82
+ </Grid>
83
+ <Grid item xs={ 12 }>
84
+ <FormLabel size="tiny">{ valueLabel }</FormLabel>
85
+ <TextField
86
+ sx={ { pt: 1 } }
87
+ size="tiny"
88
+ fullWidth
89
+ value={ valueValue }
90
+ onChange={ ( e: ChangeEvent< HTMLInputElement > ) => handleChange( e, 'value' ) }
91
+ disabled={ isKeyInvalid }
92
+ error={ isValueInvalid }
93
+ />
94
+ { isValueInvalid && <FormHelperText error>{ valueError }</FormHelperText> }
95
+ </Grid>
96
+ </Grid>
97
+ </ControlActions>
98
+ );
99
+ } );
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { type MutableRefObject, useRef } from 'react';
2
3
  import { dimensionsPropTypeUtil, type PropKey, sizePropTypeUtil } from '@elementor/editor-props';
3
4
  import { isExperimentActive } from '@elementor/editor-v1-adapters';
4
5
  import { DetachIcon, LinkIcon, SideBottomIcon, SideLeftIcon, SideRightIcon, SideTopIcon } from '@elementor/icons';
@@ -9,19 +10,21 @@ import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-conte
9
10
  import { ControlFormLabel } from '../components/control-form-label';
10
11
  import { ControlLabel } from '../components/control-label';
11
12
  import { createControl } from '../create-control';
12
- import { type ExtendedValue, SizeControl } from './size-control';
13
+ import { type ExtendedOption } from '../utils/size-control';
14
+ import { SizeControl } from './size-control';
13
15
 
14
16
  export const LinkedDimensionsControl = createControl(
15
17
  ( {
16
18
  label,
17
19
  isSiteRtl = false,
18
- extendedValues,
20
+ extendedOptions,
19
21
  }: {
20
22
  label: string;
21
23
  isSiteRtl?: boolean;
22
- extendedValues?: ExtendedValue[];
24
+ extendedOptions?: ExtendedOption[];
23
25
  } ) => {
24
26
  const { value: sizeValue, setValue: setSizeValue, disabled: sizeDisabled } = useBoundProp( sizePropTypeUtil );
27
+ const gridRowRefs: MutableRefObject< HTMLElement | undefined >[] = [ useRef(), useRef() ];
25
28
 
26
29
  const {
27
30
  value: dimensionsValue,
@@ -87,80 +90,27 @@ export const LinkedDimensionsControl = createControl(
87
90
  </ToggleButton>
88
91
  </Tooltip>
89
92
  </Stack>
90
- <Stack direction="row" gap={ 2 } flexWrap="nowrap">
91
- <Grid container gap={ 0.75 } alignItems="center">
92
- <Grid item xs={ 12 }>
93
- <Label bind="block-start" label={ __( 'Top', 'elementor' ) } />
94
- </Grid>
95
- <Grid item xs={ 12 }>
96
- <Control
97
- bind={ 'block-start' }
98
- startIcon={ <SideTopIcon fontSize={ 'tiny' } /> }
99
- isLinked={ isLinked }
100
- extendedValues={ extendedValues }
101
- />
102
- </Grid>
103
- </Grid>
104
- <Grid container gap={ 0.75 } alignItems="center">
105
- <Grid item xs={ 12 }>
106
- <Label
107
- bind="inline-end"
108
- label={ isSiteRtl ? __( 'Left', 'elementor' ) : __( 'Right', 'elementor' ) }
109
- />
110
- </Grid>
111
- <Grid item xs={ 12 }>
112
- <Control
113
- bind={ 'inline-end' }
114
- startIcon={
115
- isSiteRtl ? (
116
- <SideLeftIcon fontSize={ 'tiny' } />
117
- ) : (
118
- <SideRightIcon fontSize={ 'tiny' } />
119
- )
120
- }
121
- isLinked={ isLinked }
122
- extendedValues={ extendedValues }
123
- />
124
- </Grid>
125
- </Grid>
126
- </Stack>
127
- <Stack direction="row" gap={ 2 } flexWrap="nowrap">
128
- <Grid container gap={ 0.75 } alignItems="center">
129
- <Grid item xs={ 12 }>
130
- <Label bind="block-end" label={ __( 'Bottom', 'elementor' ) } />
131
- </Grid>
132
- <Grid item xs={ 12 }>
133
- <Control
134
- bind={ 'block-end' }
135
- startIcon={ <SideBottomIcon fontSize={ 'tiny' } /> }
136
- isLinked={ isLinked }
137
- extendedValues={ extendedValues }
138
- />
139
- </Grid>
140
- </Grid>
141
- <Grid container gap={ 0.75 } alignItems="center">
142
- <Grid item xs={ 12 }>
143
- <Label
144
- bind="inline-start"
145
- label={ isSiteRtl ? __( 'Right', 'elementor' ) : __( 'Left', 'elementor' ) }
146
- />
147
- </Grid>
148
- <Grid item xs={ 12 }>
149
- <Control
150
- bind={ 'inline-start' }
151
- isLinked={ isLinked }
152
- extendedValues={ extendedValues }
153
- startIcon={
154
- isSiteRtl ? (
155
- <SideRightIcon fontSize={ 'tiny' } />
156
- ) : (
157
- <SideLeftIcon fontSize={ 'tiny' } />
158
- )
159
- }
160
- />
161
- </Grid>
162
- </Grid>
163
- </Stack>
93
+
94
+ { getCssMarginProps( isSiteRtl ).map( ( row, index ) => (
95
+ <Stack direction="row" gap={ 2 } flexWrap="nowrap" key={ index } ref={ gridRowRefs[ index ] }>
96
+ { row.map( ( { icon, ...props } ) => (
97
+ <Grid container gap={ 0.75 } alignItems="center" key={ props.bind }>
98
+ <Grid item xs={ 12 }>
99
+ <Label { ...props } />
100
+ </Grid>
101
+ <Grid item xs={ 12 }>
102
+ <Control
103
+ bind={ props.bind }
104
+ startIcon={ icon }
105
+ isLinked={ isLinked }
106
+ extendedOptions={ extendedOptions }
107
+ anchorRef={ gridRowRefs[ index ] }
108
+ />
109
+ </Grid>
110
+ </Grid>
111
+ ) ) }
112
+ </Stack>
113
+ ) ) }
164
114
  </PropProvider>
165
115
  );
166
116
  }
@@ -170,20 +120,22 @@ const Control = ( {
170
120
  bind,
171
121
  startIcon,
172
122
  isLinked,
173
- extendedValues,
123
+ extendedOptions,
124
+ anchorRef,
174
125
  }: {
175
126
  bind: PropKey;
176
127
  startIcon: React.ReactNode;
177
128
  isLinked: boolean;
178
- extendedValues?: ExtendedValue[];
129
+ extendedOptions?: ExtendedOption[];
130
+ anchorRef: MutableRefObject< HTMLElement | undefined >;
179
131
  } ) => {
180
132
  if ( isLinked ) {
181
- return <SizeControl startIcon={ startIcon } extendedValues={ extendedValues } />;
133
+ return <SizeControl startIcon={ startIcon } extendedOptions={ extendedOptions } anchorRef={ anchorRef } />;
182
134
  }
183
135
 
184
136
  return (
185
137
  <PropKeyProvider bind={ bind }>
186
- <SizeControl startIcon={ startIcon } extendedValues={ extendedValues } />
138
+ <SizeControl startIcon={ startIcon } extendedOptions={ extendedOptions } anchorRef={ anchorRef } />
187
139
  </PropKeyProvider>
188
140
  );
189
141
  };
@@ -201,3 +153,32 @@ const Label = ( { label, bind }: { label: string; bind: PropKey } ) => {
201
153
  </PropKeyProvider>
202
154
  );
203
155
  };
156
+
157
+ function getCssMarginProps( isSiteRtl: boolean ) {
158
+ return [
159
+ [
160
+ {
161
+ bind: 'block-start',
162
+ label: __( 'Top', 'elementor' ),
163
+ icon: <SideTopIcon fontSize={ 'tiny' } />,
164
+ },
165
+ {
166
+ bind: 'inline-end',
167
+ label: isSiteRtl ? __( 'Left', 'elementor' ) : __( 'Right', 'elementor' ),
168
+ icon: isSiteRtl ? <SideLeftIcon fontSize={ 'tiny' } /> : <SideRightIcon fontSize={ 'tiny' } />,
169
+ },
170
+ ],
171
+ [
172
+ {
173
+ bind: 'block-end',
174
+ label: __( 'Bottom', 'elementor' ),
175
+ icon: <SideBottomIcon fontSize={ 'tiny' } />,
176
+ },
177
+ {
178
+ bind: 'inline-start',
179
+ label: isSiteRtl ? __( 'Right', 'elementor' ) : __( 'Left', 'elementor' ),
180
+ icon: isSiteRtl ? <SideRightIcon fontSize={ 'tiny' } /> : <SideLeftIcon fontSize={ 'tiny' } />,
181
+ },
182
+ ],
183
+ ];
184
+ }