@elementor/editor-controls 1.2.0 → 1.3.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 +26 -0
  2. package/dist/index.d.mts +7 -3
  3. package/dist/index.d.ts +7 -3
  4. package/dist/index.js +645 -415
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +570 -341
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +7 -7
  9. package/src/bound-prop-context/prop-context.tsx +3 -3
  10. package/src/bound-prop-context/prop-key-context.tsx +1 -0
  11. package/src/bound-prop-context/use-bound-prop.ts +5 -1
  12. package/src/components/text-field-popover.tsx +19 -18
  13. package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +3 -15
  14. package/src/controls/box-shadow-repeater-control.tsx +1 -1
  15. package/src/controls/equal-unequal-sizes-control.tsx +1 -1
  16. package/src/controls/font-family-control/font-family-control.tsx +14 -2
  17. package/src/controls/image-control.tsx +45 -16
  18. package/src/controls/link-control.tsx +25 -20
  19. package/src/controls/linked-dimensions-control.tsx +1 -1
  20. package/src/controls/repeatable-control.tsx +77 -17
  21. package/src/controls/select-control.tsx +22 -2
  22. package/src/controls/switch-control.tsx +9 -1
  23. package/src/controls/transform-control/functions/axis-row.tsx +32 -0
  24. package/src/controls/transform-control/functions/move.tsx +44 -0
  25. package/src/controls/transform-control/transform-content.tsx +36 -0
  26. package/src/controls/transform-control/transform-icon.tsx +12 -0
  27. package/src/controls/transform-control/transform-label.tsx +27 -0
  28. package/src/controls/transform-control/transform-repeater-control.tsx +42 -0
  29. package/src/hooks/use-repeatable-control-context.ts +6 -1
  30. package/src/index.ts +1 -0
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": "1.2.0",
4
+ "version": "1.3.0",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -40,19 +40,19 @@
40
40
  "dev": "tsup --config=../../tsup.dev.ts"
41
41
  },
42
42
  "dependencies": {
43
- "@elementor/editor-current-user": "0.5.0",
44
- "@elementor/editor-elements": "0.8.7",
45
- "@elementor/editor-props": "0.15.0",
43
+ "@elementor/editor-current-user": "0.6.0",
44
+ "@elementor/editor-elements": "0.9.0",
45
+ "@elementor/editor-props": "0.16.0",
46
46
  "@elementor/editor-responsive": "0.13.6",
47
- "@elementor/editor-ui": "0.13.0",
47
+ "@elementor/editor-ui": "0.14.0",
48
48
  "@elementor/editor-v1-adapters": "0.12.1",
49
49
  "@elementor/env": "0.3.5",
50
50
  "@elementor/http-client": "0.3.0",
51
- "@elementor/icons": "1.44.0",
51
+ "@elementor/icons": "1.46.0",
52
52
  "@elementor/locations": "0.8.0",
53
53
  "@elementor/query": "0.2.4",
54
54
  "@elementor/session": "0.1.0",
55
- "@elementor/ui": "1.35.5",
55
+ "@elementor/ui": "1.36.0",
56
56
  "@elementor/utils": "0.5.0",
57
57
  "@elementor/wp-media": "0.6.1",
58
58
  "@wordpress/i18n": "^5.13.0"
@@ -15,7 +15,7 @@ type PropContext< T extends PropValue, P extends PropType > = {
15
15
  value: T | null;
16
16
  propType: P;
17
17
  placeholder?: T;
18
- disabled?: boolean;
18
+ isDisabled?: ( propType: PropType ) => boolean | undefined;
19
19
  };
20
20
 
21
21
  const PropContext = createContext< PropContext< PropValue, PropType > | null >( null );
@@ -30,7 +30,7 @@ export const PropProvider = < T extends PropValue, P extends PropType >( {
30
30
  setValue,
31
31
  propType,
32
32
  placeholder,
33
- disabled,
33
+ isDisabled,
34
34
  }: PropProviderProps< T, P > ) => {
35
35
  return (
36
36
  <PropContext.Provider
@@ -39,7 +39,7 @@ export const PropProvider = < T extends PropValue, P extends PropType >( {
39
39
  propType,
40
40
  setValue: setValue as SetValue< PropValue >,
41
41
  placeholder,
42
- disabled,
42
+ isDisabled,
43
43
  } }
44
44
  >
45
45
  { children }
@@ -21,6 +21,7 @@ export type PropKeyContextValue< T, P > = {
21
21
  propType: P;
22
22
  placeholder?: T;
23
23
  path: PropKey[];
24
+ isDisabled?: ( propType: PropType ) => boolean | undefined;
24
25
  disabled?: boolean;
25
26
  };
26
27
 
@@ -19,6 +19,7 @@ type UseBoundProp< TValue extends PropValue > = {
19
19
  placeholder?: TValue;
20
20
  path: PropKey[];
21
21
  restoreValue: () => void;
22
+ isDisabled?: ( propType: PropType ) => boolean | undefined;
22
23
  disabled?: boolean;
23
24
  };
24
25
 
@@ -38,9 +39,11 @@ export function useBoundProp< TKey extends string, TValue extends PropValue >(
38
39
 
39
40
  const { isValid, validate, restoreValue } = useValidation( propKeyContext.propType );
40
41
 
42
+ const disabled = propKeyContext.isDisabled?.( propKeyContext.propType );
43
+
41
44
  // allow using the hook without a propTypeUtil, with no modifications or validations.
42
45
  if ( ! propTypeUtil ) {
43
- return propKeyContext;
46
+ return { ...propKeyContext, disabled } as PropKeyContextValue< PropValue, PropType >;
44
47
  }
45
48
 
46
49
  function setValue( value: TValue | null, options: CreateOptions, meta: { bind?: PropKey } ) {
@@ -67,6 +70,7 @@ export function useBoundProp< TKey extends string, TValue extends PropValue >(
67
70
  value: isValid ? value : null,
68
71
  restoreValue,
69
72
  placeholder,
73
+ disabled,
70
74
  };
71
75
  }
72
76
 
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { type RefObject } from 'react';
3
- import { bindPopover, Paper, Popover, type PopupState, TextField } from '@elementor/ui';
3
+ import { bindPopover, Popover, type PopupState, TextField } from '@elementor/ui';
4
4
 
5
5
  type Props = {
6
6
  popupState: PopupState;
@@ -16,6 +16,15 @@ export const TextFieldPopover = ( props: Props ) => {
16
16
  return (
17
17
  <Popover
18
18
  disablePortal
19
+ slotProps={ {
20
+ paper: {
21
+ sx: {
22
+ borderRadius: 2,
23
+ width: anchorRef.current?.offsetWidth + 'px',
24
+ p: 1.5,
25
+ },
26
+ },
27
+ } }
19
28
  { ...bindPopover( popupState ) }
20
29
  anchorOrigin={ { vertical: 'bottom', horizontal: 'center' } }
21
30
  transformOrigin={ { vertical: 'top', horizontal: 'center' } }
@@ -24,24 +33,16 @@ export const TextFieldPopover = ( props: Props ) => {
24
33
  popupState.close();
25
34
  } }
26
35
  >
27
- <Paper
28
- sx={ {
29
- width: anchorRef.current?.offsetWidth + 'px',
30
- borderRadius: 2,
31
- p: 1.5,
36
+ <TextField
37
+ value={ value }
38
+ onChange={ onChange }
39
+ size="tiny"
40
+ type="text"
41
+ fullWidth
42
+ inputProps={ {
43
+ autoFocus: true,
32
44
  } }
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
+ />
45
46
  </Popover>
46
47
  );
47
48
  };
@@ -7,12 +7,11 @@ import {
7
7
  colorPropTypeUtil,
8
8
  type PropKey,
9
9
  } from '@elementor/editor-props';
10
- import { Box, CardMedia, Grid, styled, Tab, TabPanel, Tabs, type Theme, UnstableColorIndicator } from '@elementor/ui';
10
+ import { Box, CardMedia, styled, Tab, TabPanel, Tabs, type Theme, UnstableColorIndicator } from '@elementor/ui';
11
11
  import { useWpMediaAttachment } from '@elementor/wp-media';
12
12
  import { __ } from '@wordpress/i18n';
13
13
 
14
14
  import { PropKeyProvider, PropProvider, useBoundProp } from '../../../bound-prop-context';
15
- import { ControlFormLabel } from '../../../components/control-form-label';
16
15
  import { PopoverContent } from '../../../components/popover-content';
17
16
  import { Repeater } from '../../../components/repeater';
18
17
  import { createControl } from '../../../create-control';
@@ -75,7 +74,7 @@ export const BackgroundOverlayRepeaterControl = createControl( () => {
75
74
  const { propType, value: overlayValues, setValue, disabled } = useBoundProp( backgroundOverlayPropTypeUtil );
76
75
 
77
76
  return (
78
- <PropProvider propType={ propType } value={ overlayValues } setValue={ setValue } disabled={ disabled }>
77
+ <PropProvider propType={ propType } value={ overlayValues } setValue={ setValue } isDisabled={ () => disabled }>
79
78
  <Repeater
80
79
  openOnAdd
81
80
  disabled={ disabled }
@@ -236,18 +235,7 @@ const ImageOverlayContent = () => {
236
235
  return (
237
236
  <PropProvider { ...propContext }>
238
237
  <PropKeyProvider bind={ 'image' }>
239
- <Grid container spacing={ 1 } alignItems="center">
240
- <Grid item xs={ 12 }>
241
- <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
242
- <Grid item xs={ 6 }>
243
- <ControlFormLabel>{ __( 'Resolution', 'elementor' ) }</ControlFormLabel>
244
- </Grid>
245
- <Grid item xs={ 6 } sx={ { overflow: 'hidden' } }>
246
- <ImageControl sizes={ backgroundResolutionOptions } />
247
- </Grid>
248
- </Grid>
249
- </Grid>
250
- </Grid>
238
+ <ImageControl sizes={ backgroundResolutionOptions } />
251
239
  </PropKeyProvider>
252
240
  <PropKeyProvider bind={ 'position' }>
253
241
  <BackgroundImageOverlayPosition />
@@ -17,7 +17,7 @@ export const BoxShadowRepeaterControl = createControl( () => {
17
17
  const { propType, value, setValue, disabled } = useBoundProp( boxShadowPropTypeUtil );
18
18
 
19
19
  return (
20
- <PropProvider propType={ propType } value={ value } setValue={ setValue } disabled={ disabled }>
20
+ <PropProvider propType={ propType } value={ value } setValue={ setValue } isDisabled={ () => disabled }>
21
21
  <Repeater
22
22
  openOnAdd
23
23
  disabled={ disabled }
@@ -154,7 +154,7 @@ export function EqualUnequalSizesControl< TMultiPropType extends string, TPropVa
154
154
  propType={ multiSizePropType }
155
155
  value={ getMultiSizeValues() }
156
156
  setValue={ setNestedProp }
157
- disabled={ multiSizeDisabled }
157
+ isDisabled={ () => multiSizeDisabled }
158
158
  >
159
159
  <PopoverContent p={ 1.5 }>
160
160
  <PopoverGridContainer ref={ rowRefs[ 1 ] }>
@@ -21,20 +21,32 @@ type FontFamilyControlProps = {
21
21
  const SIZE = 'tiny';
22
22
 
23
23
  export const FontFamilyControl = createControl( ( { fontFamilies, sectionWidth }: FontFamilyControlProps ) => {
24
- const { value: fontFamily, setValue: setFontFamily, disabled } = useBoundProp( stringPropTypeUtil );
24
+ const { value: fontFamily, setValue: setFontFamily, disabled, placeholder } = useBoundProp( stringPropTypeUtil );
25
25
 
26
26
  const popoverState = usePopupState( { variant: 'popover' } );
27
27
 
28
+ const isShowingPlaceholder = ! fontFamily && placeholder;
29
+
28
30
  return (
29
31
  <>
30
32
  <ControlActions>
31
33
  <UnstableTag
32
34
  variant="outlined"
33
- label={ fontFamily }
35
+ label={ fontFamily || placeholder }
34
36
  endIcon={ <ChevronDownIcon fontSize={ SIZE } /> }
35
37
  { ...bindTrigger( popoverState ) }
36
38
  fullWidth
37
39
  disabled={ disabled }
40
+ sx={
41
+ isShowingPlaceholder
42
+ ? {
43
+ '& .MuiTag-label': {
44
+ color: ( theme ) => theme.palette.text.tertiary,
45
+ },
46
+ textTransform: 'capitalize',
47
+ }
48
+ : undefined
49
+ }
38
50
  />
39
51
  </ControlActions>
40
52
  <Popover
@@ -1,9 +1,11 @@
1
1
  import * as React from 'react';
2
2
  import { imagePropTypeUtil } from '@elementor/editor-props';
3
- import { Stack } from '@elementor/ui';
3
+ import { Grid, Stack } from '@elementor/ui';
4
4
  import { type MediaType } from '@elementor/wp-media';
5
+ import { __ } from '@wordpress/i18n';
5
6
 
6
7
  import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-context';
8
+ import { ControlFormLabel } from '../components/control-form-label';
7
9
  import { createControl } from '../create-control';
8
10
  import { useUnfilteredFilesUpload } from '../hooks/use-unfiltered-files-upload';
9
11
  import { ImageMediaControl } from './image-media-control';
@@ -17,23 +19,50 @@ type ImageControlProps = {
17
19
  export const ImageControl = createControl( ( { sizes, showMode = 'all' }: ImageControlProps ) => {
18
20
  const propContext = useBoundProp( imagePropTypeUtil );
19
21
 
22
+ let componentToRender;
23
+ switch ( showMode ) {
24
+ case 'media':
25
+ componentToRender = <ImageSrcControl />;
26
+ break;
27
+ case 'sizes':
28
+ componentToRender = <ImageSizeControl sizes={ sizes } />;
29
+ break;
30
+ case 'all':
31
+ default:
32
+ componentToRender = (
33
+ <Stack gap={ 1.5 }>
34
+ <ControlFormLabel>{ __( 'Image', 'elementor' ) }</ControlFormLabel>
35
+ <ImageSrcControl />
36
+ <Grid container gap={ 1.5 } alignItems="center" flexWrap="nowrap">
37
+ <Grid item xs={ 6 }>
38
+ <ControlFormLabel>{ __( 'Resolution', 'elementor' ) }</ControlFormLabel>
39
+ </Grid>
40
+ <Grid item xs={ 6 } sx={ { overflow: 'hidden' } }>
41
+ <ImageSizeControl sizes={ sizes } />
42
+ </Grid>
43
+ </Grid>
44
+ </Stack>
45
+ );
46
+ }
47
+
48
+ return <PropProvider { ...propContext }>{ componentToRender }</PropProvider>;
49
+ } );
50
+
51
+ const ImageSrcControl = () => {
20
52
  const { data: allowSvgUpload } = useUnfilteredFilesUpload();
21
53
  const mediaTypes: MediaType[] = allowSvgUpload ? [ 'image', 'svg' ] : [ 'image' ];
22
54
 
23
55
  return (
24
- <PropProvider { ...propContext }>
25
- <Stack gap={ 1.5 }>
26
- { [ 'all', 'media' ].includes( showMode ) ? (
27
- <PropKeyProvider bind={ 'src' }>
28
- <ImageMediaControl mediaTypes={ mediaTypes } />
29
- </PropKeyProvider>
30
- ) : null }
31
- { [ 'all', 'sizes' ].includes( showMode ) ? (
32
- <PropKeyProvider bind={ 'size' }>
33
- <SelectControl options={ sizes } />
34
- </PropKeyProvider>
35
- ) : null }
36
- </Stack>
37
- </PropProvider>
56
+ <PropKeyProvider bind={ 'src' }>
57
+ <ImageMediaControl mediaTypes={ mediaTypes } />
58
+ </PropKeyProvider>
38
59
  );
39
- } );
60
+ };
61
+
62
+ const ImageSizeControl = ( { sizes }: { sizes: ImageControlProps[ 'sizes' ] } ) => {
63
+ return (
64
+ <PropKeyProvider bind={ 'size' }>
65
+ <SelectControl options={ sizes } />
66
+ </PropKeyProvider>
67
+ );
68
+ };
@@ -10,6 +10,7 @@ import {
10
10
  urlPropTypeUtil,
11
11
  } from '@elementor/editor-props';
12
12
  import { InfoTipCard } from '@elementor/editor-ui';
13
+ import { isExperimentActive } from '@elementor/editor-v1-adapters';
13
14
  import { type HttpResponse, httpService } from '@elementor/http-client';
14
15
  import { AlertTriangleIcon, MinusIcon, PlusIcon } from '@elementor/icons';
15
16
  import { useSessionStorage } from '@elementor/session';
@@ -29,6 +30,7 @@ import { ControlFormLabel } from '../components/control-form-label';
29
30
  import ControlActions from '../control-actions/control-actions';
30
31
  import { createControl } from '../create-control';
31
32
  import { type ControlProps } from '../utils/types';
33
+ import { SwitchControl } from './switch-control';
32
34
 
33
35
  type Props = ControlProps< {
34
36
  queryOptions: {
@@ -191,7 +193,14 @@ export const LinkControl = createControl( ( props: Props ) => {
191
193
  </ControlActions>
192
194
  </PropKeyProvider>
193
195
  <PropKeyProvider bind={ 'isTargetBlank' }>
194
- <SwitchControl disabled={ propContext.disabled || ! value } />
196
+ <Grid container alignItems="center" flexWrap="nowrap" justifyContent="space-between">
197
+ <Grid item>
198
+ <ControlFormLabel>{ __( 'Open in a new tab', 'elementor' ) }</ControlFormLabel>
199
+ </Grid>
200
+ <Grid item sx={ { marginInlineEnd: -1 } }>
201
+ <SwitchControlComponent disabled={ propContext.disabled || ! value } />
202
+ </Grid>
203
+ </Grid>
195
204
  </PropKeyProvider>
196
205
  </Stack>
197
206
  </Collapse>
@@ -215,31 +224,27 @@ const ToggleIconControl = ( { disabled, active, onIconClick, label }: ToggleIcon
215
224
  );
216
225
  };
217
226
 
218
- // @TODO Should be refactored in ED-16323
219
- const SwitchControl = ( { disabled }: { disabled: boolean } ) => {
220
- const { value = false, setValue } = useBoundProp( booleanPropTypeUtil );
227
+ const SwitchControlComponent = ( { disabled }: { disabled: boolean } ) => {
228
+ const { value, setValue } = useBoundProp( booleanPropTypeUtil );
229
+ const isVersion331Active = isExperimentActive( 'e_v_3_31' );
230
+
231
+ if ( isVersion331Active ) {
232
+ return <SwitchControl />;
233
+ }
221
234
 
222
235
  const onClick = () => {
223
236
  setValue( ! value );
224
237
  };
225
238
 
226
- const inputProps = disabled
227
- ? {
228
- style: {
229
- opacity: 0,
230
- },
231
- }
232
- : {};
233
-
234
239
  return (
235
- <Grid container alignItems="center" flexWrap="nowrap" justifyContent="space-between">
236
- <Grid item>
237
- <ControlFormLabel>{ __( 'Open in a new tab', 'elementor' ) }</ControlFormLabel>
238
- </Grid>
239
- <Grid item sx={ { marginInlineEnd: -1 } }>
240
- <Switch checked={ value } onClick={ onClick } disabled={ disabled } inputProps={ inputProps } />
241
- </Grid>
242
- </Grid>
240
+ <Switch
241
+ checked={ value ?? false }
242
+ onClick={ onClick }
243
+ disabled={ disabled }
244
+ inputProps={ {
245
+ ...( disabled ? { style: { opacity: 0 } } : {} ),
246
+ } }
247
+ />
243
248
  );
244
249
  };
245
250
 
@@ -68,7 +68,7 @@ export const LinkedDimensionsControl = createControl(
68
68
  propType={ propType }
69
69
  value={ dimensionsValue }
70
70
  setValue={ setDimensionsValue }
71
- disabled={ disabled }
71
+ isDisabled={ () => disabled }
72
72
  >
73
73
  <Stack direction="row" gap={ 2 } flexWrap="nowrap">
74
74
  { isUsingNestedProps ? (
@@ -47,25 +47,21 @@ export const RepeatableControl = createControl(
47
47
  [ childPropTypeUtil.key, childPropTypeUtil.schema ]
48
48
  );
49
49
 
50
- const { propType, value, setValue } = useBoundProp( childArrayPropTypeUtil );
51
- const ItemLabel = ( { value: itemValue }: { value: any } ) => {
52
- const pattern = patternLabel || '';
53
- const finalLabel = interpolate( pattern, itemValue.value );
50
+ const contextValue = useMemo(
51
+ () => ({
52
+ ...childControlConfig,
53
+ placeholder: placeholder || '',
54
+ patternLabel: patternLabel || '',
55
+ }),
56
+ [ childControlConfig, placeholder, patternLabel ]
57
+ );
54
58
 
55
- if ( finalLabel ) {
56
- return <span>{ finalLabel }</span>;
57
- }
59
+ const { propType, value, setValue } = useBoundProp( childArrayPropTypeUtil );
58
60
 
59
- return (
60
- <Box component="span" color="text.tertiary">
61
- { placeholder }
62
- </Box>
63
- );
64
- };
65
61
 
66
62
  return (
67
63
  <PropProvider propType={ propType } value={ value } setValue={ setValue }>
68
- <RepeatableControlContext.Provider value={ childControlConfig }>
64
+ <RepeatableControlContext.Provider value={ contextValue }>
69
65
  <Repeater
70
66
  openOnAdd
71
67
  values={ value ?? [] }
@@ -111,9 +107,73 @@ const Content = () => {
111
107
  };
112
108
 
113
109
  const interpolate = ( template: string, data: object ) => {
114
- //remove it to be generic
115
- if ( Object.values( data ).some( ( value ) => value.value === '' || value === '' ) ) {
116
- return null;
110
+ if ( ! data ) {
111
+ return template;
117
112
  }
113
+
118
114
  return new Function( ...Object.keys( data ), `return \`${ template }\`;` )( ...Object.values( data ) );
119
115
  };
116
+
117
+ const getNestedValue = ( obj: any, path: string ) => {
118
+ return path.split( '.' ).reduce( ( current, key ) => current?.[key], obj);
119
+ };
120
+
121
+ const isEmptyValue = (val: unknown) => {
122
+ if ( typeof val === 'string' ) {
123
+ return val.trim() === '';
124
+ }
125
+
126
+ if ( Number.isNaN( val ) ) {
127
+ return true;
128
+ }
129
+
130
+ if ( Array.isArray( val ) ) {
131
+ return val.length === 0;
132
+ }
133
+
134
+ if ( typeof val === 'object' && val !== null && Object.keys( val ).length === 0 ) {
135
+ return true;
136
+ }
137
+
138
+ return false;
139
+ };
140
+
141
+ const shouldShowPlaceholder = ( pattern: string, data: unknown ): boolean => {
142
+ const propertyPaths = getAllProperties( pattern );
143
+
144
+ const values = propertyPaths.map( path => getNestedValue( data, path ) );
145
+
146
+ if ( values.length === 0 ) {
147
+ return false;
148
+ }
149
+
150
+ if ( values.some( ( value ) => value === null || value === undefined ) ) {
151
+ return true;
152
+ }
153
+
154
+ if ( values.every( isEmptyValue ) ) {
155
+ return true;
156
+ }
157
+
158
+ return false;
159
+ };
160
+
161
+ const ItemLabel = ( { value }: { value: any } ) => {
162
+ const { placeholder, patternLabel } = useRepeatableControlContext();
163
+
164
+ const label = shouldShowPlaceholder( patternLabel, value ) ? placeholder : interpolate( patternLabel, value );
165
+
166
+ return (
167
+ <Box component="span" color="text.tertiary">
168
+ { label }
169
+ </Box>
170
+ );
171
+ };
172
+
173
+ const getAllProperties = ( pattern: string ) => {
174
+ const properties = pattern.match(/\$\{([^}]+)\}/g)?.map(match =>
175
+ match.slice(2, -1)
176
+ ) || [];
177
+
178
+ return properties;
179
+ };
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { stringPropTypeUtil, type StringPropValue } from '@elementor/editor-props';
3
3
  import { MenuListItem } from '@elementor/editor-ui';
4
- import { Select, type SelectChangeEvent } from '@elementor/ui';
4
+ import { Select, type SelectChangeEvent, Typography } from '@elementor/ui';
5
5
 
6
6
  import { useBoundProp } from '../bound-prop-context';
7
7
  import ControlActions from '../control-actions/control-actions';
@@ -13,7 +13,7 @@ type Props = {
13
13
  };
14
14
 
15
15
  export const SelectControl = createControl( ( { options, onChange }: Props ) => {
16
- const { value, setValue, disabled } = useBoundProp( stringPropTypeUtil );
16
+ const { value, setValue, disabled, placeholder } = useBoundProp( stringPropTypeUtil );
17
17
 
18
18
  const handleChange = ( event: SelectChangeEvent< StringPropValue[ 'value' ] > ) => {
19
19
  const newValue = event.target.value || null;
@@ -28,6 +28,26 @@ export const SelectControl = createControl( ( { options, onChange }: Props ) =>
28
28
  sx={ { overflow: 'hidden' } }
29
29
  displayEmpty
30
30
  size="tiny"
31
+ renderValue={ ( selectedValue: string | null ) => {
32
+ const findOptionByValue = ( searchValue: string | null ) =>
33
+ options.find( ( opt ) => opt.value === searchValue );
34
+
35
+ if ( ! selectedValue || selectedValue === '' ) {
36
+ if ( placeholder ) {
37
+ const placeholderOption = findOptionByValue( placeholder );
38
+ const displayText = placeholderOption?.label || placeholder;
39
+
40
+ return (
41
+ <Typography component="span" variant="caption" color="text.tertiary">
42
+ { displayText }
43
+ </Typography>
44
+ );
45
+ }
46
+ return '';
47
+ }
48
+ const option = findOptionByValue( selectedValue );
49
+ return option?.label || selectedValue;
50
+ } }
31
51
  value={ value ?? '' }
32
52
  onChange={ handleChange }
33
53
  disabled={ disabled }
@@ -14,7 +14,15 @@ export const SwitchControl = createControl( () => {
14
14
 
15
15
  return (
16
16
  <div style={ { display: 'flex', justifyContent: 'flex-end' } }>
17
- <Switch checked={ !! value } onChange={ handleChange } size="small" disabled={ disabled } />
17
+ <Switch
18
+ checked={ !! value }
19
+ onChange={ handleChange }
20
+ size="small"
21
+ disabled={ disabled }
22
+ inputProps={ {
23
+ ...( disabled ? { style: { opacity: 0 } } : {} ),
24
+ } }
25
+ />
18
26
  </div>
19
27
  );
20
28
  } );
@@ -0,0 +1,32 @@
1
+ import { type RefObject } from 'react';
2
+ import * as React from 'react';
3
+ import { Grid } from '@elementor/ui';
4
+
5
+ import { PropKeyProvider } from '../../../bound-prop-context';
6
+ import { ControlLabel } from '../../../components/control-label';
7
+ import { PopoverGridContainer } from '../../../components/popover-grid-container';
8
+ import { SizeControl } from '../../size-control';
9
+
10
+ type TransformAxisRowProps = {
11
+ label: string;
12
+ bindValue: 'x' | 'y' | 'z';
13
+ startIcon: React.ReactNode;
14
+ anchorRef?: RefObject< HTMLDivElement | null >;
15
+ };
16
+
17
+ export const AxisRow = ( { label, bindValue, startIcon, anchorRef }: TransformAxisRowProps ) => {
18
+ return (
19
+ <Grid item xs={ 12 }>
20
+ <PopoverGridContainer ref={ anchorRef }>
21
+ <Grid item xs={ 6 }>
22
+ <ControlLabel>{ label }</ControlLabel>
23
+ </Grid>
24
+ <Grid item xs={ 6 }>
25
+ <PropKeyProvider bind={ bindValue }>
26
+ <SizeControl anchorRef={ anchorRef } startIcon={ startIcon } />
27
+ </PropKeyProvider>
28
+ </Grid>
29
+ </PopoverGridContainer>
30
+ </Grid>
31
+ );
32
+ };