@elementor/editor-controls 0.6.0 → 0.7.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.6.0",
4
+ "version": "0.7.0",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -40,12 +40,12 @@
40
40
  "dev": "tsup --config=../../tsup.dev.ts"
41
41
  },
42
42
  "dependencies": {
43
- "@elementor/editor-props": "0.7.0",
44
- "@elementor/icons": "^1.20.0",
43
+ "@elementor/editor-props": "0.8.0",
44
+ "@elementor/icons": "1.24.0",
45
45
  "@elementor/session": "0.1.0",
46
- "@elementor/ui": "^1.22.0",
46
+ "@elementor/ui": "1.23.3",
47
47
  "@elementor/utils": "0.3.0",
48
- "@elementor/wp-media": "0.2.3",
48
+ "@elementor/wp-media": "0.4.0",
49
49
  "@wordpress/i18n": "^5.13.0"
50
50
  },
51
51
  "peerDependencies": {
@@ -35,6 +35,7 @@ export type RepeaterProps< T > = {
35
35
  Content: React.ComponentType< {
36
36
  anchorEl: AnchorEl;
37
37
  bind: PropKey;
38
+ value: T;
38
39
  } >;
39
40
  };
40
41
  };
@@ -100,7 +101,7 @@ export const Repeater = < T, >( {
100
101
  duplicateItem={ () => duplicateRepeaterItem( index ) }
101
102
  toggleDisableItem={ () => toggleDisableRepeaterItem( index ) }
102
103
  >
103
- { ( props ) => <itemSettings.Content { ...props } bind={ String( index ) } /> }
104
+ { ( props ) => <itemSettings.Content { ...props } value={ value } bind={ String( index ) } /> }
104
105
  </RepeaterItem>
105
106
  ) ) }
106
107
  </Stack>
@@ -1,5 +1,4 @@
1
1
  import * as React from 'react';
2
- import { useState } from 'react';
3
2
  import { stringPropTypeUtil, type urlPropTypeUtil } from '@elementor/editor-props';
4
3
  import { XIcon } from '@elementor/icons';
5
4
  import {
@@ -42,30 +41,23 @@ export const AutocompleteControl = createControl(
42
41
  minInputLength = 2,
43
42
  }: Props ) => {
44
43
  const { value = '', setValue } = useBoundProp( propType );
45
- const [ inputValue, setInputValue ] = useState(
46
- value && options[ value ]?.label ? options[ value ]?.label : value
47
- );
48
44
 
49
45
  const hasSelectedValue = !! (
50
- inputValue &&
51
- ( options[ inputValue ] || Object.values( options ).find( ( { label } ) => label === inputValue ) )
46
+ value &&
47
+ ( options[ value ] || Object.values( options ).find( ( { label } ) => label === value ) )
52
48
  );
53
- const allowClear = !! inputValue;
49
+ const allowClear = !! value;
54
50
  const formattedOptions = Object.keys( options );
55
51
 
56
- const handleChange = ( _?: React.SyntheticEvent | null, newValue: string | null = null ) => {
57
- const formattedInputValue = newValue && options[ newValue ]?.label ? options[ newValue ]?.label : newValue;
58
-
59
- setInputValue( formattedInputValue || '' );
60
-
61
- if ( ! allowCustomValues && newValue && ! options[ newValue ] ) {
62
- return;
63
- }
52
+ const onOptionSelect = ( _: React.SyntheticEvent | null, newValue: string | null ) => {
53
+ setValue( newValue );
54
+ };
64
55
 
56
+ const handleChange = ( newValue: string | null ) => {
65
57
  setValue( newValue );
66
58
  };
67
59
 
68
- const filterOptions = () => {
60
+ const filterOptions = ( _: string[], { inputValue }: { inputValue: string | null } ) => {
69
61
  const formattedValue = inputValue?.toLowerCase() || '';
70
62
 
71
63
  if ( formattedValue.length < minInputLength ) {
@@ -84,7 +76,7 @@ export const AutocompleteControl = createControl(
84
76
  };
85
77
 
86
78
  // Prevents MUI warning when freeSolo/allowCustomValues is false
87
- const muiWarningPreventer = () => allowCustomValues || !! filterOptions().length;
79
+ const muiWarningPreventer = () => allowCustomValues || !! filterOptions( [], { inputValue: value } ).length;
88
80
 
89
81
  return (
90
82
  <ControlActions>
@@ -92,11 +84,9 @@ export const AutocompleteControl = createControl(
92
84
  forcePopupIcon={ false }
93
85
  disableClearable={ true } // Disabled component's auto clear icon to use our custom one instead
94
86
  freeSolo={ muiWarningPreventer() }
95
- value={ inputValue || '' }
87
+ value={ value || '' }
96
88
  size={ 'tiny' }
97
- onChange={ handleChange }
98
- onInputChange={ handleChange }
99
- onBlur={ allowCustomValues ? undefined : () => handleChange( null, value ) }
89
+ onChange={ onOptionSelect }
100
90
  readOnly={ hasSelectedValue }
101
91
  options={ formattedOptions }
102
92
  getOptionKey={ ( option ) => option }
@@ -135,14 +125,19 @@ const TextInput = ( {
135
125
  }: {
136
126
  params: AutocompleteRenderInputParams;
137
127
  allowClear: boolean;
138
- handleChange: ( _?: React.SyntheticEvent | null, newValue?: string | null ) => void;
128
+ handleChange: ( newValue: string | null ) => void;
139
129
  placeholder: string;
140
130
  hasSelectedValue: boolean;
141
131
  } ) => {
132
+ const onChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
133
+ handleChange( event.target.value );
134
+ };
135
+
142
136
  return (
143
137
  <TextField
144
138
  { ...params }
145
139
  placeholder={ placeholder }
140
+ onChange={ onChange }
146
141
  sx={ {
147
142
  '& .MuiInputBase-input': {
148
143
  cursor: hasSelectedValue ? 'default' : undefined,
@@ -163,11 +158,11 @@ const ClearButton = ( {
163
158
  }: {
164
159
  params: AutocompleteRenderInputParams;
165
160
  allowClear: boolean;
166
- handleChange: ( _?: React.SyntheticEvent | null, newValue?: string | null ) => void;
161
+ handleChange: ( newValue: string | null ) => void;
167
162
  } ) => (
168
163
  <InputAdornment position="end">
169
164
  { allowClear && (
170
- <IconButton size={ params.size } onClick={ handleChange } sx={ { cursor: 'pointer' } }>
165
+ <IconButton size={ params.size } onClick={ () => handleChange( null ) } sx={ { cursor: 'pointer' } }>
171
166
  <XIcon fontSize={ params.size } />
172
167
  </IconButton>
173
168
  ) }
@@ -0,0 +1,38 @@
1
+ import * as React from 'react';
2
+ import { PinIcon, PinnedOffIcon } from '@elementor/icons';
3
+ import { Grid } from '@elementor/ui';
4
+ import { __ } from '@wordpress/i18n';
5
+
6
+ import { ControlLabel } from '../../../../components/control-label';
7
+ import { type ToggleButtonGroupItem } from '../../../../components/control-toggle-button-group';
8
+ import { ToggleControl } from '../../../toggle-control';
9
+
10
+ type Attachment = 'fixed' | 'scroll';
11
+
12
+ const attachmentControlOptions: ToggleButtonGroupItem< Attachment >[] = [
13
+ {
14
+ value: 'fixed',
15
+ label: __( 'Fixed', 'elementor' ),
16
+ renderContent: ( { size } ) => <PinIcon fontSize={ size } />,
17
+ showTooltip: true,
18
+ },
19
+ {
20
+ value: 'scroll',
21
+ label: __( 'Scroll', 'elementor' ),
22
+ renderContent: ( { size } ) => <PinnedOffIcon fontSize={ size } />,
23
+ showTooltip: true,
24
+ },
25
+ ];
26
+
27
+ export const BackgroundImageOverlayAttachment = () => {
28
+ return (
29
+ <Grid container gap={ 8 } alignItems="center" flexWrap="nowrap">
30
+ <Grid item xs={ 2 }>
31
+ <ControlLabel>{ __( 'Attachment', 'elementor' ) }</ControlLabel>
32
+ </Grid>
33
+ <Grid item justifyContent="flex-end" xs={ 8 } sx={ { display: 'flex' } }>
34
+ <ToggleControl options={ attachmentControlOptions } />
35
+ </Grid>
36
+ </Grid>
37
+ );
38
+ };
@@ -0,0 +1,31 @@
1
+ import * as React from 'react';
2
+ import { Grid } from '@elementor/ui';
3
+ import { __ } from '@wordpress/i18n';
4
+
5
+ import { ControlLabel } from '../../../../components/control-label';
6
+ import { SelectControl } from '../../../select-control';
7
+
8
+ const backgroundPositionOptions = [
9
+ { label: __( 'Center Center', 'elementor' ), value: 'center center' },
10
+ { label: __( 'Center Left', 'elementor' ), value: 'center left' },
11
+ { label: __( 'Center Right', 'elementor' ), value: 'center right' },
12
+ { label: __( 'Top Center', 'elementor' ), value: 'top center' },
13
+ { label: __( 'Top Left', 'elementor' ), value: 'top left' },
14
+ { label: __( 'Top Right', 'elementor' ), value: 'top right' },
15
+ { label: __( 'Bottom Center', 'elementor' ), value: 'bottom center' },
16
+ { label: __( 'Bottom Left', 'elementor' ), value: 'bottom left' },
17
+ { label: __( 'Bottom Right', 'elementor' ), value: 'bottom right' },
18
+ ];
19
+
20
+ export const BackgroundImageOverlayPosition = () => {
21
+ return (
22
+ <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
23
+ <Grid item xs={ 6 }>
24
+ <ControlLabel>{ __( 'Position', 'elementor' ) }</ControlLabel>
25
+ </Grid>
26
+ <Grid item xs={ 6 }>
27
+ <SelectControl options={ backgroundPositionOptions } />
28
+ </Grid>
29
+ </Grid>
30
+ );
31
+ };
@@ -0,0 +1,50 @@
1
+ import * as React from 'react';
2
+ import { DotsHorizontalIcon, DotsVerticalIcon, GridDotsIcon, XIcon } from '@elementor/icons';
3
+ import { Grid } from '@elementor/ui';
4
+ import { __ } from '@wordpress/i18n';
5
+
6
+ import { ControlLabel } from '../../../../components/control-label';
7
+ import { type ToggleButtonGroupItem } from '../../../../components/control-toggle-button-group';
8
+ import { ToggleControl } from '../../../toggle-control';
9
+
10
+ type Repeaters = 'repeat' | 'repeat-x' | 'repeat-y' | 'no-repeat';
11
+
12
+ const repeatControlOptions: ToggleButtonGroupItem< Repeaters >[] = [
13
+ {
14
+ value: 'repeat',
15
+ label: __( 'Repeat', 'elementor' ),
16
+ renderContent: ( { size } ) => <GridDotsIcon fontSize={ size } />,
17
+ showTooltip: true,
18
+ },
19
+ {
20
+ value: 'repeat-x',
21
+ label: __( 'Repeat-x', 'elementor' ),
22
+ renderContent: ( { size } ) => <DotsHorizontalIcon fontSize={ size } />,
23
+ showTooltip: true,
24
+ },
25
+ {
26
+ value: 'repeat-y',
27
+ label: __( 'Repeat-y', 'elementor' ),
28
+ renderContent: ( { size } ) => <DotsVerticalIcon fontSize={ size } />,
29
+ showTooltip: true,
30
+ },
31
+ {
32
+ value: 'no-repeat',
33
+ label: __( 'No-Repeat', 'elementor' ),
34
+ renderContent: ( { size } ) => <XIcon fontSize={ size } />,
35
+ showTooltip: true,
36
+ },
37
+ ];
38
+
39
+ export const BackgroundImageOverlayRepeat = () => {
40
+ return (
41
+ <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
42
+ <Grid item xs={ 6 }>
43
+ <ControlLabel>{ __( 'Repeat', 'elementor' ) }</ControlLabel>
44
+ </Grid>
45
+ <Grid item xs={ 6 }>
46
+ <ToggleControl options={ repeatControlOptions } />
47
+ </Grid>
48
+ </Grid>
49
+ );
50
+ };
@@ -0,0 +1,27 @@
1
+ import * as React from 'react';
2
+ import { Grid } from '@elementor/ui';
3
+ import { __ } from '@wordpress/i18n';
4
+
5
+ import { ControlLabel } from '../../../../components/control-label';
6
+ import { SelectControl } from '../../../select-control';
7
+
8
+ const backgroundResolutionOptions = [
9
+ { label: __( 'Thumbnail - 150 x 150', 'elementor' ), value: 'thumbnail' },
10
+ { label: __( 'Medium - 300 x 300', 'elementor' ), value: 'medium' },
11
+ { label: __( 'Medium Large - 768 x 768' ), value: 'medium_large' },
12
+ { label: __( 'Large 1024 x 1024', 'elementor' ), value: 'large' },
13
+ { label: __( 'Full', 'elementor' ), value: 'full' },
14
+ ];
15
+
16
+ export const BackgroundImageOverlayResolution = () => {
17
+ return (
18
+ <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
19
+ <Grid item xs={ 6 }>
20
+ <ControlLabel>{ __( 'Resolution', 'elementor' ) }</ControlLabel>
21
+ </Grid>
22
+ <Grid item xs={ 6 }>
23
+ <SelectControl options={ backgroundResolutionOptions } />
24
+ </Grid>
25
+ </Grid>
26
+ );
27
+ };
@@ -0,0 +1,44 @@
1
+ import * as React from 'react';
2
+ import { ArrowBarBothIcon, ArrowsMaximizeIcon } from '@elementor/icons';
3
+ import { Grid } from '@elementor/ui';
4
+ import { __ } from '@wordpress/i18n';
5
+
6
+ import { ControlLabel } from '../../../../components/control-label';
7
+ import { type ToggleButtonGroupItem } from '../../../../components/control-toggle-button-group';
8
+ import { ToggleControl } from '../../../toggle-control';
9
+
10
+ type Sizes = 'auto' | 'cover' | 'contain';
11
+
12
+ const sizeControlOptions: ToggleButtonGroupItem< Sizes >[] = [
13
+ {
14
+ value: 'auto',
15
+ label: __( 'Auto', 'elementor' ),
16
+ renderContent: () => 'Auto',
17
+ showTooltip: true,
18
+ },
19
+ {
20
+ value: 'cover',
21
+ label: __( 'Cover', 'elementor' ),
22
+ renderContent: ( { size } ) => <ArrowsMaximizeIcon fontSize={ size } />,
23
+ showTooltip: true,
24
+ },
25
+ {
26
+ value: 'contain',
27
+ label: __( 'Contain', 'elementor' ),
28
+ renderContent: ( { size } ) => <ArrowBarBothIcon fontSize={ size } />,
29
+ showTooltip: true,
30
+ },
31
+ ];
32
+
33
+ export const BackgroundImageOverlaySize = () => {
34
+ return (
35
+ <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
36
+ <Grid item xs={ 6 }>
37
+ <ControlLabel>{ __( 'Size', 'elementor' ) }</ControlLabel>
38
+ </Grid>
39
+ <Grid item xs={ 6 } sx={ { display: 'flex', justifyContent: 'flex-end' } }>
40
+ <ToggleControl options={ sizeControlOptions } />
41
+ </Grid>
42
+ </Grid>
43
+ );
44
+ };
@@ -6,22 +6,39 @@ import {
6
6
  backgroundOverlayPropTypeUtil,
7
7
  type PropKey,
8
8
  } from '@elementor/editor-props';
9
- import { Grid, Stack, UnstableColorIndicator } from '@elementor/ui';
9
+ import { Box, Grid, Stack, Tab, TabPanel, Tabs, UnstableColorIndicator, useTabs } from '@elementor/ui';
10
10
  import { useWpMediaAttachment } from '@elementor/wp-media';
11
11
  import { __ } from '@wordpress/i18n';
12
12
 
13
13
  import { PropKeyProvider, PropProvider, useBoundProp } from '../../../bound-prop-context';
14
- import { ControlLabel } from '../../../components/control-label';
15
14
  import { Repeater } from '../../../components/repeater';
16
15
  import { createControl } from '../../../create-control';
17
16
  import { ColorControl } from '../../color-control';
18
17
  import { ImageMediaControl } from '../../image-media-control';
18
+ import { BackgroundImageOverlayAttachment } from './background-image-overlay/background-image-overlay-attachment';
19
+ import { BackgroundImageOverlayPosition } from './background-image-overlay/background-image-overlay-position';
20
+ import { BackgroundImageOverlayRepeat } from './background-image-overlay/background-image-overlay-repeat';
21
+ import { BackgroundImageOverlayResolution } from './background-image-overlay/background-image-overlay-resolution';
22
+ import { BackgroundImageOverlaySize } from './background-image-overlay/background-image-overlay-size';
19
23
 
24
+ const defaultImagePlaceholderId = 1;
20
25
  const initialBackgroundOverlay: BackgroundOverlayItemPropValue = {
21
- $$type: 'background-color-overlay',
22
- value: 'rgba(0, 0, 0, 0.2)',
26
+ $$type: 'background-image-overlay',
27
+ value: {
28
+ 'image-src': {
29
+ $$type: 'image-src',
30
+ value: {
31
+ id: {
32
+ $$type: 'image-attachment-id',
33
+ value: defaultImagePlaceholderId,
34
+ },
35
+ },
36
+ },
37
+ },
23
38
  };
24
39
 
40
+ type OverlayType = 'image' | 'color';
41
+
25
42
  export const BackgroundOverlayRepeaterControl = createControl( () => {
26
43
  const { propType, value: overlayValues, setValue } = useBoundProp( backgroundOverlayPropTypeUtil );
27
44
 
@@ -46,37 +63,39 @@ const ItemIcon = ( { value }: { value: BackgroundOverlayItemPropValue } ) => (
46
63
  <UnstableColorIndicator size="inherit" component="span" value={ value.value } />
47
64
  );
48
65
 
49
- const ItemContent = ( { bind }: { bind: PropKey } ) => {
66
+ const ItemContent = ( { bind, value }: { bind: PropKey; value: BackgroundOverlayItemPropValue } ) => {
50
67
  return (
51
68
  <PropKeyProvider bind={ bind }>
52
- <Content />
69
+ <Content value={ value } />
53
70
  </PropKeyProvider>
54
71
  );
55
72
  };
56
73
 
57
- const Content = () => {
58
- const propContext = useBoundProp( backgroundImageOverlayPropTypeUtil );
74
+ const Content = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
75
+ const activeTab = deriveOverlayType( value.$$type );
76
+ const { getTabsProps, getTabProps, getTabPanelProps } = useTabs< OverlayType >( activeTab );
59
77
 
60
78
  return (
61
- <Stack gap={ 1.5 }>
62
- <Grid container spacing={ 1 } alignItems="center">
63
- <Grid item xs={ 12 }>
64
- <ControlLabel>{ __( 'Color', 'elementor' ) }</ControlLabel>
65
- </Grid>
66
- <Grid item xs={ 12 }>
67
- <ColorControl propTypeUtil={ backgroundColorOverlayPropTypeUtil } />
68
- </Grid>
69
- </Grid>
70
- <PropProvider { ...propContext }>
71
- <PropKeyProvider bind={ 'image-src' }>
72
- <Grid container spacing={ 1 } alignItems="center">
73
- <Grid item xs={ 12 }>
74
- <ImageMediaControl />
75
- </Grid>
79
+ <Box sx={ { width: '100%' } }>
80
+ <Box sx={ { borderBottom: 1, borderColor: 'divider' } }>
81
+ <Tabs { ...getTabsProps() } aria-label={ __( 'Background Overlay', 'elementor' ) }>
82
+ <Tab label={ __( 'Image', 'elementor' ) } { ...getTabProps( 'image' ) } />
83
+ <Tab label={ __( 'Color', 'elementor' ) } { ...getTabProps( 'color' ) } />
84
+ </Tabs>
85
+ </Box>
86
+ <TabPanel { ...getTabPanelProps( 'image' ) }>
87
+ <Stack gap={ 1.5 }>
88
+ <ImageOverlayContent />
89
+ </Stack>
90
+ </TabPanel>
91
+ <TabPanel { ...getTabPanelProps( 'color' ) }>
92
+ <Grid container spacing={ 1 } alignItems="center">
93
+ <Grid item xs={ 12 }>
94
+ <ColorControl propTypeUtil={ backgroundColorOverlayPropTypeUtil } />
76
95
  </Grid>
77
- </PropKeyProvider>
78
- </PropProvider>
79
- </Stack>
96
+ </Grid>
97
+ </TabPanel>
98
+ </Box>
80
99
  );
81
100
  };
82
101
 
@@ -101,3 +120,46 @@ const ItemLabelImage = ( { value }: { value: BackgroundOverlayItemPropValue } )
101
120
 
102
121
  return <span>{ imageTitle }</span>;
103
122
  };
123
+
124
+ const ImageOverlayContent = () => {
125
+ const propContext = useBoundProp( backgroundImageOverlayPropTypeUtil );
126
+
127
+ return (
128
+ <PropProvider { ...propContext }>
129
+ <PropKeyProvider bind={ 'image-src' }>
130
+ <Grid container spacing={ 1 } alignItems="center">
131
+ <Grid item xs={ 12 }>
132
+ <ImageMediaControl />
133
+ </Grid>
134
+ </Grid>
135
+ </PropKeyProvider>
136
+ <PropKeyProvider bind={ 'resolution' }>
137
+ <BackgroundImageOverlayResolution />
138
+ </PropKeyProvider>
139
+ <PropKeyProvider bind={ 'position' }>
140
+ <BackgroundImageOverlayPosition />
141
+ </PropKeyProvider>
142
+ <PropKeyProvider bind={ 'repeat' }>
143
+ <BackgroundImageOverlayRepeat />
144
+ </PropKeyProvider>
145
+ <PropKeyProvider bind={ 'size' }>
146
+ <BackgroundImageOverlaySize />
147
+ </PropKeyProvider>
148
+ <PropKeyProvider bind={ 'attachment' }>
149
+ <BackgroundImageOverlayAttachment />
150
+ </PropKeyProvider>
151
+ </PropProvider>
152
+ );
153
+ };
154
+
155
+ const deriveOverlayType = ( type: string ): OverlayType => {
156
+ if ( type === 'background-color-overlay' ) {
157
+ return 'color';
158
+ }
159
+
160
+ if ( type === 'background-image-overlay' ) {
161
+ return 'image';
162
+ }
163
+
164
+ throw new Error( `Invalid overlay type: ${ type }` );
165
+ };
@@ -70,7 +70,7 @@ const Content = ( { anchorEl }: { anchorEl: HTMLElement | null } ) => {
70
70
  <SelectControl
71
71
  options={ [
72
72
  { label: __( 'Inset', 'elementor' ), value: 'inset' },
73
- { label: __( 'Outset', 'elementor' ), value: '' },
73
+ { label: __( 'Outset', 'elementor' ), value: null },
74
74
  ] }
75
75
  />
76
76
  </Control>
@@ -120,6 +120,7 @@ const ItemLabel = ( { value }: { value: ShadowPropValue } ) => {
120
120
  const { size: spreadSize = '', unit: spreadUnit = '' } = spread?.value || {};
121
121
  const { size: hOffsetSize = 'unset', unit: hOffsetUnit = '' } = hOffset?.value || {};
122
122
  const { size: vOffsetSize = 'unset', unit: vOffsetUnit = '' } = vOffset?.value || {};
123
+ const positionLabel = position?.value || 'outset';
123
124
 
124
125
  const sizes = [
125
126
  hOffsetSize + hOffsetUnit,
@@ -130,7 +131,7 @@ const ItemLabel = ( { value }: { value: ShadowPropValue } ) => {
130
131
 
131
132
  return (
132
133
  <span style={ { textTransform: 'capitalize' } }>
133
- { position ?? 'outset' }: { sizes }
134
+ { positionLabel }: { sizes }
134
135
  </span>
135
136
  );
136
137
  };
@@ -77,7 +77,7 @@ export function EqualUnequalSizesControl< TMultiPropType extends string, TPropVa
77
77
  const isEqual = isEqualSizes( newMappedValues, items );
78
78
 
79
79
  if ( isEqual ) {
80
- return setSizeValue( Object.values( newMappedValues )[ 0 ].value );
80
+ return setSizeValue( Object.values( newMappedValues )[ 0 ]?.value );
81
81
  }
82
82
 
83
83
  setMultiSizeValue( newMappedValues );
@@ -91,6 +91,8 @@ export function EqualUnequalSizesControl< TMultiPropType extends string, TPropVa
91
91
  return splitEqualValue() ?? null;
92
92
  };
93
93
 
94
+ const isMixed = !! multiSizeValue;
95
+
94
96
  return (
95
97
  <>
96
98
  <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap" ref={ controlRef }>
@@ -99,7 +101,7 @@ export function EqualUnequalSizesControl< TMultiPropType extends string, TPropVa
99
101
  </Grid>
100
102
  <Grid item xs={ 6 }>
101
103
  <Stack direction="row" alignItems="center" gap={ 1 }>
102
- <SizeControl placeholder={ __( 'MIXED', 'elementor' ) } />
104
+ <SizeControl placeholder={ isMixed ? __( 'Mixed', 'elementor' ) : undefined } />
103
105
  <ToggleButton
104
106
  size={ 'tiny' }
105
107
  value={ 'check' }
@@ -1,48 +1,42 @@
1
1
  import * as React from 'react';
2
- import { gapPropTypeUtil, type GapPropValue } from '@elementor/editor-props';
2
+ import { layoutDirectionPropTypeUtil, type PropKey, sizePropTypeUtil } from '@elementor/editor-props';
3
3
  import { DetachIcon, LinkIcon } from '@elementor/icons';
4
4
  import { Grid, Stack, ToggleButton } from '@elementor/ui';
5
5
  import { __ } from '@wordpress/i18n';
6
6
 
7
- import { PropKeyProvider, PropProvider, type SetValue, useBoundProp } from '../bound-prop-context';
7
+ import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-context';
8
8
  import { ControlLabel } from '../components/control-label';
9
9
  import { createControl } from '../create-control';
10
10
  import { SizeControl } from './size-control';
11
11
 
12
- export type Gap = 'row' | 'column';
13
-
14
12
  export const GapControl = createControl( ( { label }: { label: string } ) => {
15
- const { propType, value, setValue } = useBoundProp( gapPropTypeUtil );
16
- const { column, row, isLinked = true } = value || {};
13
+ const {
14
+ value: directionValue,
15
+ setValue: setDirectionValue,
16
+ propType,
17
+ } = useBoundProp( layoutDirectionPropTypeUtil );
18
+ const { value: sizeValue, setValue: setSizeValue } = useBoundProp( sizePropTypeUtil );
19
+
20
+ const isLinked = ! directionValue && ! sizeValue ? true : !! sizeValue;
17
21
 
18
- const setLinkedValue: SetValue< GapPropValue[ 'value' ] > = ( newValue, _, meta ) => {
22
+ const onLinkToggle = () => {
19
23
  if ( ! isLinked ) {
20
- return setValue( newValue );
24
+ setSizeValue( directionValue?.column.value );
25
+ return;
21
26
  }
22
27
 
23
- const newDimension = newValue[ meta?.bind as Gap ];
28
+ const value = sizeValue ? sizePropTypeUtil.create( sizeValue ) : null;
24
29
 
25
- setValue( {
26
- isLinked,
27
- column: newDimension,
28
- row: newDimension,
30
+ setDirectionValue( {
31
+ row: value,
32
+ column: value,
29
33
  } );
30
34
  };
31
35
 
32
- const toggleLinked = () => {
33
- const updatedValue = {
34
- isLinked: ! isLinked,
35
- column,
36
- row: ! isLinked ? column : row,
37
- };
38
-
39
- setValue( updatedValue );
40
- };
41
-
42
36
  const LinkedIcon = isLinked ? LinkIcon : DetachIcon;
43
37
 
44
38
  return (
45
- <PropProvider propType={ propType } value={ value } setValue={ setLinkedValue }>
39
+ <PropProvider propType={ propType } value={ directionValue } setValue={ setDirectionValue }>
46
40
  <Stack direction="row" gap={ 2 } flexWrap="nowrap">
47
41
  <ControlLabel>{ label }</ControlLabel>
48
42
  <ToggleButton
@@ -51,7 +45,7 @@ export const GapControl = createControl( ( { label }: { label: string } ) => {
51
45
  value={ 'check' }
52
46
  selected={ isLinked }
53
47
  sx={ { marginLeft: 'auto' } }
54
- onChange={ toggleLinked }
48
+ onChange={ onLinkToggle }
55
49
  >
56
50
  <LinkedIcon fontSize={ 'tiny' } />
57
51
  </ToggleButton>
@@ -62,9 +56,7 @@ export const GapControl = createControl( ( { label }: { label: string } ) => {
62
56
  <ControlLabel>{ __( 'Column', 'elementor' ) }</ControlLabel>
63
57
  </Grid>
64
58
  <Grid item xs={ 12 }>
65
- <PropKeyProvider bind="column">
66
- <SizeControl />
67
- </PropKeyProvider>
59
+ <Control bind={ 'column' } isLinked={ isLinked } />
68
60
  </Grid>
69
61
  </Grid>
70
62
  <Grid container gap={ 1 } alignItems="center">
@@ -72,12 +64,22 @@ export const GapControl = createControl( ( { label }: { label: string } ) => {
72
64
  <ControlLabel>{ __( 'Row', 'elementor' ) }</ControlLabel>
73
65
  </Grid>
74
66
  <Grid item xs={ 12 }>
75
- <PropKeyProvider bind="row">
76
- <SizeControl />
77
- </PropKeyProvider>
67
+ <Control bind={ 'row' } isLinked={ isLinked } />
78
68
  </Grid>
79
69
  </Grid>
80
70
  </Stack>
81
71
  </PropProvider>
82
72
  );
83
73
  } );
74
+
75
+ const Control = ( { bind, isLinked }: { bind: PropKey; isLinked: boolean } ) => {
76
+ if ( isLinked ) {
77
+ return <SizeControl />;
78
+ }
79
+
80
+ return (
81
+ <PropKeyProvider bind={ bind }>
82
+ <SizeControl />
83
+ </PropKeyProvider>
84
+ );
85
+ };