@elementor/editor-controls 0.30.1 → 0.32.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.30.1",
4
+ "version": "0.32.0",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -40,17 +40,18 @@
40
40
  "dev": "tsup --config=../../tsup.dev.ts"
41
41
  },
42
42
  "dependencies": {
43
- "@elementor/editor-current-user": "0.3.2",
44
- "@elementor/editor-elements": "0.8.3",
45
- "@elementor/editor-props": "0.12.0",
46
- "@elementor/editor-ui": "0.8.2",
43
+ "@elementor/editor-current-user": "0.5.0",
44
+ "@elementor/editor-elements": "0.8.4",
45
+ "@elementor/editor-props": "0.12.1",
46
+ "@elementor/editor-ui": "0.9.0",
47
+ "@elementor/editor-v1-adapters": "0.12.0",
47
48
  "@elementor/env": "0.3.5",
48
49
  "@elementor/http-client": "0.3.0",
49
50
  "@elementor/icons": "1.40.1",
50
51
  "@elementor/locations": "0.8.0",
51
52
  "@elementor/query": "0.2.4",
52
53
  "@elementor/session": "0.1.0",
53
- "@elementor/ui": "1.34.2",
54
+ "@elementor/ui": "1.34.5",
54
55
  "@elementor/utils": "0.4.0",
55
56
  "@elementor/wp-media": "0.6.0",
56
57
  "@tanstack/react-virtual": "3.13.3",
@@ -11,6 +11,7 @@ type TextFieldInnerSelectionProps = {
11
11
  onChange: ( event: React.ChangeEvent< HTMLInputElement > ) => void;
12
12
  onBlur?: ( event: React.FocusEvent< HTMLInputElement > ) => void;
13
13
  onKeyDown?: ( event: React.KeyboardEvent< HTMLInputElement > ) => void;
14
+ onKeyUp?: ( event: React.KeyboardEvent< HTMLInputElement > ) => void;
14
15
  endAdornment: React.ReactNode;
15
16
  startAdornment?: React.ReactNode;
16
17
  };
@@ -24,6 +25,7 @@ export const TextFieldInnerSelection = forwardRef(
24
25
  onChange,
25
26
  onBlur,
26
27
  onKeyDown,
28
+ onKeyUp,
27
29
  endAdornment,
28
30
  startAdornment,
29
31
  }: TextFieldInnerSelectionProps,
@@ -38,6 +40,7 @@ export const TextFieldInnerSelection = forwardRef(
38
40
  value={ value }
39
41
  onChange={ onChange }
40
42
  onKeyDown={ onKeyDown }
43
+ onKeyUp={ onKeyUp }
41
44
  onBlur={ onBlur }
42
45
  placeholder={ placeholder }
43
46
  InputProps={ {
@@ -0,0 +1,121 @@
1
+ import * as React from 'react';
2
+ import { useState } from 'react';
3
+ import { stringPropTypeUtil } from '@elementor/editor-props';
4
+ import { MenuListItem } from '@elementor/editor-ui';
5
+ import { Grid, Select, type SelectChangeEvent, Stack, TextField } from '@elementor/ui';
6
+ import { __ } from '@wordpress/i18n';
7
+
8
+ import { useBoundProp } from '../bound-prop-context';
9
+ import { ControlLabel } from '../components/control-label';
10
+ import { createControl } from '../create-control';
11
+
12
+ const RATIO_OPTIONS = [
13
+ { label: __( 'Auto', 'elementor' ), value: 'auto' },
14
+ { label: '1/1', value: '1/1' },
15
+ { label: '4/3', value: '4/3' },
16
+ { label: '3/4', value: '3/4' },
17
+ { label: '16/9', value: '16/9' },
18
+ { label: '9/16', value: '9/16' },
19
+ { label: '3/2', value: '3/2' },
20
+ { label: '2/3', value: '2/3' },
21
+ ];
22
+
23
+ const CUSTOM_RATIO = 'custom';
24
+
25
+ export const AspectRatioControl = createControl( ( { label }: { label: string } ) => {
26
+ const { value: aspectRatioValue, setValue: setAspectRatioValue } = useBoundProp( stringPropTypeUtil );
27
+
28
+ const isCustomSelected =
29
+ aspectRatioValue && ! RATIO_OPTIONS.some( ( option ) => option.value === aspectRatioValue );
30
+ const [ initialWidth, initialHeight ] = isCustomSelected ? aspectRatioValue.split( '/' ) : [ '', '' ];
31
+
32
+ const [ isCustom, setIsCustom ] = useState( isCustomSelected );
33
+ const [ customWidth, setCustomWidth ] = useState< string >( initialWidth );
34
+ const [ customHeight, setCustomHeight ] = useState< string >( initialHeight );
35
+ const [ selectedValue, setSelectedValue ] = useState< string >(
36
+ isCustomSelected ? CUSTOM_RATIO : aspectRatioValue || ''
37
+ );
38
+
39
+ const handleSelectChange = ( event: SelectChangeEvent< string > ) => {
40
+ const newValue = event.target.value;
41
+ const isCustomRatio = newValue === CUSTOM_RATIO;
42
+
43
+ setIsCustom( isCustomRatio );
44
+ setSelectedValue( newValue );
45
+
46
+ if ( isCustomRatio ) {
47
+ return;
48
+ }
49
+
50
+ setAspectRatioValue( newValue );
51
+ };
52
+ const handleCustomWidthChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
53
+ const newWidth = event.target.value;
54
+ setCustomWidth( newWidth );
55
+
56
+ if ( newWidth && customHeight ) {
57
+ setAspectRatioValue( `${ newWidth }/${ customHeight }` );
58
+ }
59
+ };
60
+
61
+ const handleCustomHeightChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
62
+ const newHeight = event.target.value;
63
+ setCustomHeight( newHeight );
64
+
65
+ if ( customWidth && newHeight ) {
66
+ setAspectRatioValue( `${ customWidth }/${ newHeight }` );
67
+ }
68
+ };
69
+
70
+ return (
71
+ <Stack direction="column" gap={ 2 }>
72
+ <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
73
+ <Grid item xs={ 6 }>
74
+ <ControlLabel>{ label }</ControlLabel>
75
+ </Grid>
76
+ <Grid item xs={ 6 }>
77
+ <Select
78
+ sx={ { overflow: 'hidden' } }
79
+ displayEmpty
80
+ size="tiny"
81
+ value={ selectedValue }
82
+ onChange={ handleSelectChange }
83
+ fullWidth
84
+ >
85
+ { [ ...RATIO_OPTIONS, { label: __( 'Custom', 'elementor' ), value: CUSTOM_RATIO } ].map(
86
+ ( { label: optionLabel, ...props } ) => (
87
+ <MenuListItem key={ props.value } { ...props } value={ props.value ?? '' }>
88
+ { optionLabel }
89
+ </MenuListItem>
90
+ )
91
+ ) }
92
+ </Select>
93
+ </Grid>
94
+ </Grid>
95
+ { isCustom && (
96
+ <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
97
+ <Grid item xs={ 6 }>
98
+ <TextField
99
+ size="tiny"
100
+ type="number"
101
+ fullWidth
102
+ value={ customWidth }
103
+ onChange={ handleCustomWidthChange }
104
+ placeholder={ __( 'Width', 'elementor' ) }
105
+ />
106
+ </Grid>
107
+ <Grid item xs={ 6 }>
108
+ <TextField
109
+ size="tiny"
110
+ type="number"
111
+ fullWidth
112
+ value={ customHeight }
113
+ onChange={ handleCustomHeightChange }
114
+ placeholder={ __( 'Height', 'elementor' ) }
115
+ />
116
+ </Grid>
117
+ </Grid>
118
+ ) }
119
+ </Stack>
120
+ );
121
+ } );
@@ -1,10 +1,12 @@
1
1
  import * as React from 'react';
2
2
  import { backgroundPropTypeUtil } from '@elementor/editor-props';
3
+ import { isExperimentActive } from '@elementor/editor-v1-adapters';
3
4
  import { Grid } from '@elementor/ui';
4
5
  import { __ } from '@wordpress/i18n';
5
6
 
6
7
  import { PropKeyProvider, PropProvider, useBoundProp } from '../../bound-prop-context';
7
8
  import { ControlFormLabel } from '../../components/control-form-label';
9
+ import { ControlLabel } from '../../components/control-label';
8
10
  import { SectionContent } from '../../components/section-content';
9
11
  import { createControl } from '../../create-control';
10
12
  import { ColorControl } from '../color-control';
@@ -12,6 +14,9 @@ import { BackgroundOverlayRepeaterControl } from './background-overlay/backgroun
12
14
 
13
15
  export const BackgroundControl = createControl( () => {
14
16
  const propContext = useBoundProp( backgroundPropTypeUtil );
17
+ const isUsingNestedProps = isExperimentActive( 'e_v_3_30' );
18
+
19
+ const colorLabel = __( 'Color', 'elementor' );
15
20
 
16
21
  return (
17
22
  <PropProvider { ...propContext }>
@@ -22,7 +27,11 @@ export const BackgroundControl = createControl( () => {
22
27
  <PropKeyProvider bind="color">
23
28
  <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
24
29
  <Grid item xs={ 6 }>
25
- <ControlFormLabel>{ __( 'Color', 'elementor' ) }</ControlFormLabel>
30
+ { isUsingNestedProps ? (
31
+ <ControlLabel>{ colorLabel }</ControlLabel>
32
+ ) : (
33
+ <ControlFormLabel>{ colorLabel }</ControlFormLabel>
34
+ ) }
26
35
  </Grid>
27
36
  <Grid item xs={ 6 }>
28
37
  <ColorControl />
@@ -91,15 +91,15 @@ export const BackgroundOverlayRepeaterControl = createControl( () => {
91
91
  );
92
92
  } );
93
93
 
94
- export const ItemContent = ( { bind }: { bind: PropKey } ) => {
94
+ export const ItemContent = ( { anchorEl = null, bind }: { anchorEl?: HTMLElement | null; bind: PropKey } ) => {
95
95
  return (
96
96
  <PropKeyProvider bind={ bind }>
97
- <Content />
97
+ <Content anchorEl={ anchorEl } />
98
98
  </PropKeyProvider>
99
99
  );
100
100
  };
101
101
 
102
- const Content = () => {
102
+ const Content = ( { anchorEl }: { anchorEl: HTMLElement | null } ) => {
103
103
  const { getTabsProps, getTabProps, getTabPanelProps } = useBackgroundTabsHistory( {
104
104
  image: getInitialBackgroundOverlay().value,
105
105
  color: initialBackgroundColorOverlay.value,
@@ -130,7 +130,7 @@ const Content = () => {
130
130
  </TabPanel>
131
131
  <TabPanel sx={ { p: 1.5 } } { ...getTabPanelProps( 'color' ) }>
132
132
  <PopoverContent>
133
- <ColorOverlayContent />
133
+ <ColorOverlayContent anchorEl={ anchorEl } />
134
134
  </PopoverContent>
135
135
  </TabPanel>
136
136
  </Box>
@@ -217,12 +217,12 @@ const ItemLabelGradient = ( { value }: { value: BackgroundOverlayItemPropValue }
217
217
  return <span>{ __( 'Radial Gradient', 'elementor' ) }</span>;
218
218
  };
219
219
 
220
- const ColorOverlayContent = () => {
220
+ const ColorOverlayContent = ( { anchorEl }: { anchorEl: HTMLElement | null } ) => {
221
221
  const propContext = useBoundProp( backgroundColorOverlayPropTypeUtil );
222
222
  return (
223
223
  <PropProvider { ...propContext }>
224
224
  <PropKeyProvider bind={ 'color' }>
225
- <ColorControl />
225
+ <ColorControl anchorEl={ anchorEl } />
226
226
  </PropKeyProvider>
227
227
  </PropProvider>
228
228
  );
@@ -53,21 +53,7 @@ const Content = ( { anchorEl }: { anchorEl: HTMLElement | null } ) => {
53
53
  <PopoverContent p={ 1.5 }>
54
54
  <PopoverGridContainer>
55
55
  <Control bind="color" label={ __( 'Color', 'elementor' ) }>
56
- <ColorControl
57
- slotProps={ {
58
- colorPicker: {
59
- anchorEl,
60
- anchorOrigin: {
61
- vertical: 'top',
62
- horizontal: 'right',
63
- },
64
- transformOrigin: {
65
- vertical: 'top',
66
- horizontal: -10,
67
- },
68
- },
69
- } }
70
- />
56
+ <ColorControl anchorEl={ anchorEl } />
71
57
  </Control>
72
58
  <Control bind="position" label={ __( 'Position', 'elementor' ) } sx={ { overflow: 'hidden' } }>
73
59
  <SelectControl
@@ -8,18 +8,41 @@ import { createControl } from '../create-control';
8
8
 
9
9
  type Props = Partial< Omit< UnstableColorFieldProps, 'value' | 'onChange' > > & {
10
10
  propTypeUtil?: PropTypeUtil< string, string >;
11
+ anchorEl?: HTMLElement | null;
11
12
  };
12
13
 
13
- export const ColorControl = createControl( ( { propTypeUtil = colorPropTypeUtil, ...props }: Props ) => {
14
- const { value, setValue } = useBoundProp( propTypeUtil );
14
+ export const ColorControl = createControl(
15
+ ( { propTypeUtil = colorPropTypeUtil, anchorEl, slotProps = {}, ...props }: Props ) => {
16
+ const { value, setValue } = useBoundProp( propTypeUtil );
15
17
 
16
- const handleChange = ( selectedColor: string ) => {
17
- setValue( selectedColor || null );
18
- };
18
+ const handleChange = ( selectedColor: string ) => {
19
+ setValue( selectedColor || null );
20
+ };
19
21
 
20
- return (
21
- <ControlActions>
22
- <UnstableColorField size="tiny" { ...props } value={ value ?? '' } onChange={ handleChange } fullWidth />
23
- </ControlActions>
24
- );
25
- } );
22
+ return (
23
+ <ControlActions>
24
+ <UnstableColorField
25
+ size="tiny"
26
+ fullWidth
27
+ value={ value ?? '' }
28
+ onChange={ handleChange }
29
+ { ...props }
30
+ slotProps={ {
31
+ ...slotProps,
32
+ colorPicker: {
33
+ anchorEl,
34
+ anchorOrigin: {
35
+ vertical: 'top',
36
+ horizontal: 'right',
37
+ },
38
+ transformOrigin: {
39
+ vertical: 'top',
40
+ horizontal: -10,
41
+ },
42
+ },
43
+ } }
44
+ />
45
+ </ControlActions>
46
+ );
47
+ }
48
+ );
@@ -93,7 +93,7 @@ export const LinkControl = createControl( ( props: Props ) => {
93
93
  }
94
94
 
95
95
  setLinkSessionValue( {
96
- value: newState ? value : linkSessionValue?.value,
96
+ value: linkSessionValue?.value,
97
97
  meta: { isEnabled: newState },
98
98
  } );
99
99
  };
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { useRef } from 'react';
2
3
  import { sizePropTypeUtil, stringPropTypeUtil } from '@elementor/editor-props';
3
4
  import { InputAdornment } from '@elementor/ui';
4
5
 
@@ -116,6 +117,31 @@ const SizeInput = ( {
116
117
  size,
117
118
  unit,
118
119
  }: SizeInputProps ) => {
120
+ const unitInputBufferRef = useRef( '' );
121
+
122
+ const handleKeyUp = ( event: React.KeyboardEvent< HTMLInputElement > ) => {
123
+ const { key } = event;
124
+
125
+ if ( ! /^[a-zA-Z%]$/.test( key ) ) {
126
+ return;
127
+ }
128
+
129
+ event.preventDefault();
130
+
131
+ const newChar = key.toLowerCase();
132
+ const updatedBuffer = ( unitInputBufferRef.current + newChar ).slice( -3 );
133
+ unitInputBufferRef.current = updatedBuffer;
134
+
135
+ const matchedUnit =
136
+ units.find( ( u ) => u.includes( updatedBuffer ) ) ||
137
+ units.find( ( u ) => u.startsWith( newChar ) ) ||
138
+ units.find( ( u ) => u.includes( newChar ) );
139
+
140
+ if ( matchedUnit ) {
141
+ handleUnitChange( matchedUnit );
142
+ }
143
+ };
144
+
119
145
  return (
120
146
  <ControlActions>
121
147
  <TextFieldInnerSelection
@@ -133,12 +159,16 @@ const SizeInput = ( {
133
159
  type="number"
134
160
  value={ Number.isNaN( size ) ? '' : size }
135
161
  onChange={ handleSizeChange }
136
- onBlur={ onBlur }
162
+ onBlur={ ( event ) => {
163
+ unitInputBufferRef.current = '';
164
+ onBlur?.( event );
165
+ } }
137
166
  onKeyDown={ ( event ) => {
138
167
  if ( RESTRICTED_INPUT_KEYS.includes( event.key ) ) {
139
168
  event.preventDefault();
140
169
  }
141
170
  } }
171
+ onKeyUp={ handleKeyUp }
142
172
  />
143
173
  </ControlActions>
144
174
  );
@@ -0,0 +1,20 @@
1
+ import * as React from 'react';
2
+ import { booleanPropTypeUtil } from '@elementor/editor-props';
3
+ import { Switch } from '@elementor/ui';
4
+
5
+ import { useBoundProp } from '../bound-prop-context/use-bound-prop';
6
+ import { createControl } from '../create-control';
7
+
8
+ export const SwitchControl = createControl( () => {
9
+ const { value, setValue } = useBoundProp( booleanPropTypeUtil );
10
+
11
+ const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
12
+ setValue( event.target.checked );
13
+ };
14
+
15
+ return (
16
+ <div style={ { display: 'flex', justifyContent: 'flex-end' } }>
17
+ <Switch checked={ !! value } onChange={ handleChange } size="small" />
18
+ </div>
19
+ );
20
+ } );
package/src/index.ts CHANGED
@@ -15,8 +15,10 @@ export { FontFamilyControl } from './controls/font-family-control/font-family-co
15
15
  export { UrlControl } from './controls/url-control';
16
16
  export { LinkControl } from './controls/link-control';
17
17
  export { GapControl } from './controls/gap-control';
18
+ export { AspectRatioControl } from './controls/aspect-ratio-control';
18
19
  export { SvgMediaControl } from './controls/svg-media-control';
19
20
  export { BackgroundControl } from './controls/background-control/background-control';
21
+ export { SwitchControl } from './controls/switch-control';
20
22
 
21
23
  // components
22
24
  export { ControlFormLabel } from './components/control-form-label';