@elementor/editor-controls 1.0.0 → 1.2.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 (33) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/index.d.mts +78 -41
  3. package/dist/index.d.ts +78 -41
  4. package/dist/index.js +875 -617
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +713 -467
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +11 -11
  9. package/src/components/font-family-selector.tsx +50 -174
  10. package/src/components/popover-content.tsx +3 -11
  11. package/src/components/repeater.tsx +27 -11
  12. package/src/components/text-field-popover.tsx +3 -3
  13. package/src/controls/aspect-ratio-control.tsx +20 -2
  14. package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-position.tsx +2 -2
  15. package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-size.tsx +2 -2
  16. package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +9 -4
  17. package/src/controls/box-shadow-repeater-control.tsx +2 -2
  18. package/src/controls/equal-unequal-sizes-control.tsx +3 -9
  19. package/src/controls/filter-repeater-control.tsx +186 -0
  20. package/src/controls/font-family-control/font-family-control.tsx +6 -2
  21. package/src/controls/gap-control.tsx +3 -3
  22. package/src/controls/image-control.tsx +22 -35
  23. package/src/controls/key-value-control.tsx +119 -0
  24. package/src/controls/link-control.tsx +3 -1
  25. package/src/controls/linked-dimensions-control.tsx +3 -3
  26. package/src/controls/number-control.tsx +3 -3
  27. package/src/controls/position-control.tsx +109 -0
  28. package/src/controls/repeatable-control.tsx +119 -0
  29. package/src/controls/size-control.tsx +11 -9
  30. package/src/controls/stroke-control.tsx +2 -2
  31. package/src/controls/svg-media-control.tsx +0 -2
  32. package/src/hooks/use-repeatable-control-context.ts +24 -0
  33. package/src/index.ts +6 -1
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.0.0",
4
+ "version": "1.2.0",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -41,27 +41,27 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@elementor/editor-current-user": "0.5.0",
44
- "@elementor/editor-elements": "0.8.5",
45
- "@elementor/editor-props": "0.13.0",
46
- "@elementor/editor-responsive": "0.13.5",
47
- "@elementor/editor-ui": "0.11.0",
48
- "@elementor/editor-v1-adapters": "0.12.0",
44
+ "@elementor/editor-elements": "0.8.7",
45
+ "@elementor/editor-props": "0.15.0",
46
+ "@elementor/editor-responsive": "0.13.6",
47
+ "@elementor/editor-ui": "0.13.0",
48
+ "@elementor/editor-v1-adapters": "0.12.1",
49
49
  "@elementor/env": "0.3.5",
50
50
  "@elementor/http-client": "0.3.0",
51
51
  "@elementor/icons": "1.44.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.34.5",
56
- "@elementor/utils": "0.4.0",
57
- "@elementor/wp-media": "0.6.0",
58
- "@tanstack/react-virtual": "3.13.3",
55
+ "@elementor/ui": "1.35.5",
56
+ "@elementor/utils": "0.5.0",
57
+ "@elementor/wp-media": "0.6.1",
59
58
  "@wordpress/i18n": "^5.13.0"
60
59
  },
61
60
  "devDependencies": {
62
61
  "tsup": "^8.3.5"
63
62
  },
64
63
  "peerDependencies": {
65
- "react": "^18.3.1"
64
+ "react": "^18.3.1",
65
+ "react-dom": "^18.3.1"
66
66
  }
67
67
  }
@@ -1,21 +1,9 @@
1
- import { useEffect, useRef, useState } from 'react';
2
1
  import * as React from 'react';
3
- import { PopoverHeader } from '@elementor/editor-ui';
4
- import { SearchIcon, TextIcon } from '@elementor/icons';
5
- import {
6
- Box,
7
- Divider,
8
- InputAdornment,
9
- Link,
10
- MenuList,
11
- MenuSubheader,
12
- Stack,
13
- styled,
14
- TextField,
15
- Typography,
16
- } from '@elementor/ui';
2
+ import { useEffect, useState } from 'react';
3
+ import { PopoverHeader, PopoverMenuList, PopoverScrollableContent, PopoverSearch } from '@elementor/editor-ui';
4
+ import { TextIcon } from '@elementor/icons';
5
+ import { Box, Divider, Link, Stack, Typography } from '@elementor/ui';
17
6
  import { debounce } from '@elementor/utils';
18
- import { useVirtualizer } from '@tanstack/react-virtual';
19
7
  import { __ } from '@wordpress/i18n';
20
8
 
21
9
  import { enqueueFont } from '../controls/font-family-control/enqueue-font';
@@ -29,6 +17,7 @@ type FontFamilySelectorProps = {
29
17
  fontFamily: string | null;
30
18
  onFontFamilyChange: ( fontFamily: string ) => void;
31
19
  onClose: () => void;
20
+ sectionWidth: number;
32
21
  };
33
22
 
34
23
  export const FontFamilySelector = ( {
@@ -36,13 +25,14 @@ export const FontFamilySelector = ( {
36
25
  fontFamily,
37
26
  onFontFamilyChange,
38
27
  onClose,
28
+ sectionWidth,
39
29
  }: FontFamilySelectorProps ) => {
40
30
  const [ searchValue, setSearchValue ] = useState( '' );
41
31
 
42
32
  const filteredFontFamilies = useFilteredFontFamilies( fontFamilies, searchValue );
43
33
 
44
- const handleSearch = ( event: React.ChangeEvent< HTMLInputElement > ) => {
45
- setSearchValue( event.target.value );
34
+ const handleSearch = ( value: string ) => {
35
+ setSearchValue( value );
46
36
  };
47
37
 
48
38
  const handleClose = () => {
@@ -58,35 +48,31 @@ export const FontFamilySelector = ( {
58
48
  icon={ <TextIcon fontSize={ SIZE } /> }
59
49
  />
60
50
 
61
- <Box px={ 1.5 } pb={ 1 }>
62
- <TextField
63
- // eslint-disable-next-line jsx-a11y/no-autofocus
64
- autoFocus
65
- fullWidth
66
- size={ SIZE }
67
- value={ searchValue }
68
- placeholder={ __( 'Search', 'elementor' ) }
69
- onChange={ handleSearch }
70
- InputProps={ {
71
- startAdornment: (
72
- <InputAdornment position="start">
73
- <SearchIcon fontSize={ SIZE } />
74
- </InputAdornment>
75
- ),
76
- } }
77
- />
78
- </Box>
51
+ <PopoverSearch
52
+ value={ searchValue }
53
+ onSearch={ handleSearch }
54
+ placeholder={ __( 'Search', 'elementor' ) }
55
+ />
56
+
79
57
  <Divider />
80
- { filteredFontFamilies.length > 0 ? (
81
- <FontList
82
- fontListItems={ filteredFontFamilies }
83
- setFontFamily={ onFontFamilyChange }
84
- handleClose={ handleClose }
85
- fontFamily={ fontFamily }
86
- />
87
- ) : (
88
- <Box sx={ { overflowY: 'auto', height: 260, width: 220 } }>
89
- <Stack alignItems="center" p={ 2.5 } gap={ 1.5 } overflow={ 'hidden' }>
58
+
59
+ <PopoverScrollableContent width={ sectionWidth }>
60
+ { filteredFontFamilies.length > 0 ? (
61
+ <FontList
62
+ fontListItems={ filteredFontFamilies }
63
+ setFontFamily={ onFontFamilyChange }
64
+ handleClose={ handleClose }
65
+ fontFamily={ fontFamily }
66
+ />
67
+ ) : (
68
+ <Stack
69
+ alignItems="center"
70
+ justifyContent="center"
71
+ height="100%"
72
+ p={ 2.5 }
73
+ gap={ 1.5 }
74
+ overflow={ 'hidden' }
75
+ >
90
76
  <TextIcon fontSize="large" />
91
77
  <Box sx={ { maxWidth: 160, overflow: 'hidden' } }>
92
78
  <Typography align="center" variant="subtitle2" color="text.secondary">
@@ -108,7 +94,12 @@ export const FontFamilySelector = ( {
108
94
  <span>&rdquo;.</span>
109
95
  </Typography>
110
96
  </Box>
111
- <Typography align="center" variant="caption" color="text.secondary">
97
+ <Typography
98
+ align="center"
99
+ variant="caption"
100
+ color="text.secondary"
101
+ sx={ { display: 'flex', flexDirection: 'column' } }
102
+ >
112
103
  { __( 'Try something else.', 'elementor' ) }
113
104
  <Link
114
105
  color="secondary"
@@ -120,8 +111,8 @@ export const FontFamilySelector = ( {
120
111
  </Link>
121
112
  </Typography>
122
113
  </Stack>
123
- </Box>
124
- ) }
114
+ ) }
115
+ </PopoverScrollableContent>
125
116
  </Stack>
126
117
  );
127
118
  };
@@ -133,11 +124,7 @@ type FontListProps = {
133
124
  fontFamily: string | null;
134
125
  };
135
126
 
136
- const LIST_ITEM_HEIGHT = 36;
137
- const LIST_ITEMS_BUFFER = 6;
138
-
139
127
  const FontList = ( { fontListItems, setFontFamily, handleClose, fontFamily }: FontListProps ) => {
140
- const containerRef = useRef< HTMLDivElement >( null );
141
128
  const selectedItem = fontListItems.find( ( item ) => item.value === fontFamily );
142
129
 
143
130
  const debouncedVirtualizeChange = useDebounce( ( { getVirtualIndexes }: { getVirtualIndexes: () => number[] } ) => {
@@ -149,131 +136,20 @@ const FontList = ( { fontListItems, setFontFamily, handleClose, fontFamily }: Fo
149
136
  } );
150
137
  }, 100 );
151
138
 
152
- const virtualizer = useVirtualizer( {
153
- count: fontListItems.length,
154
- getScrollElement: () => containerRef.current,
155
- estimateSize: () => LIST_ITEM_HEIGHT,
156
- overscan: LIST_ITEMS_BUFFER,
157
- onChange: debouncedVirtualizeChange,
158
- } );
159
-
160
- useEffect(
161
- () => {
162
- virtualizer.scrollToIndex( fontListItems.findIndex( ( item ) => item.value === fontFamily ) );
163
- },
164
- // eslint-disable-next-line react-compiler/react-compiler
165
- // eslint-disable-next-line react-hooks/exhaustive-deps
166
- [ fontFamily ]
167
- );
168
-
169
139
  return (
170
- <Box
171
- ref={ containerRef }
172
- sx={ {
173
- overflowY: 'auto',
174
- height: 260,
175
- width: 220,
176
- } }
177
- >
178
- <StyledMenuList
179
- role="listbox"
180
- style={ {
181
- height: `${ virtualizer.getTotalSize() }px`,
182
- } }
183
- data-testid="font-list"
184
- >
185
- { virtualizer.getVirtualItems().map( ( virtualRow ) => {
186
- const item = fontListItems[ virtualRow.index ];
187
- const isLast = virtualRow.index === fontListItems.length - 1;
188
- // Ignore the first item, which is a category, and use the second item instead.
189
- const isFirst = virtualRow.index === 1;
190
- const isSelected = selectedItem?.value === item.value;
191
-
192
- // If no item is selected, the first item should be focused.
193
- const tabIndexFallback = ! selectedItem ? 0 : -1;
194
-
195
- if ( item.type === 'category' ) {
196
- return (
197
- <MenuSubheader
198
- key={ virtualRow.key }
199
- style={ {
200
- transform: `translateY(${ virtualRow.start }px)`,
201
- } }
202
- >
203
- { item.value }
204
- </MenuSubheader>
205
- );
206
- }
207
-
208
- return (
209
- <li
210
- key={ virtualRow.key }
211
- role="option"
212
- aria-selected={ isSelected }
213
- onClick={ () => {
214
- setFontFamily( item.value );
215
- handleClose();
216
- } }
217
- onKeyDown={ ( event ) => {
218
- if ( event.key === 'Enter' ) {
219
- setFontFamily( item.value );
220
- handleClose();
221
- }
222
-
223
- if ( event.key === 'ArrowDown' && isLast ) {
224
- event.preventDefault();
225
- event.stopPropagation();
226
- }
227
-
228
- if ( event.key === 'ArrowUp' && isFirst ) {
229
- event.preventDefault();
230
- event.stopPropagation();
231
- }
232
- } }
233
- tabIndex={ isSelected ? 0 : tabIndexFallback }
234
- style={ {
235
- transform: `translateY(${ virtualRow.start }px)`,
236
- fontFamily: item.value,
237
- } }
238
- >
239
- { item.value }
240
- </li>
241
- );
242
- } ) }
243
- </StyledMenuList>
244
- </Box>
140
+ <PopoverMenuList
141
+ items={ fontListItems }
142
+ selectedValue={ selectedItem?.value }
143
+ onChange={ debouncedVirtualizeChange }
144
+ onSelect={ setFontFamily }
145
+ onClose={ handleClose }
146
+ itemStyle={ ( item ) => ( { fontFamily: item.value } ) }
147
+ data-testid="font-list"
148
+ />
245
149
  );
246
150
  };
247
151
 
248
- const StyledMenuList = styled( MenuList )( ( { theme } ) => ( {
249
- '& > li': {
250
- height: LIST_ITEM_HEIGHT,
251
- position: 'absolute',
252
- top: 0,
253
- left: 0,
254
- width: '100%',
255
- display: 'flex',
256
- alignItems: 'center',
257
- },
258
- '& > [role="option"]': {
259
- ...theme.typography.caption,
260
- lineHeight: 'inherit',
261
- padding: theme.spacing( 0.75, 2, 0.75, 4 ),
262
- '&:hover, &:focus': {
263
- backgroundColor: theme.palette.action.hover,
264
- },
265
- '&[aria-selected="true"]': {
266
- backgroundColor: theme.palette.action.selected,
267
- },
268
- cursor: 'pointer',
269
- textOverflow: 'ellipsis',
270
- },
271
- width: '100%',
272
- position: 'relative',
273
- } ) );
274
-
275
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
276
- const useDebounce = < TArgs extends any[] >( fn: ( ...args: TArgs ) => void, delay: number ) => {
152
+ const useDebounce = < TArgs extends unknown[] >( fn: ( ...args: TArgs ) => void, delay: number ) => {
277
153
  const [ debouncedFn ] = useState( () => debounce( fn, delay ) );
278
154
 
279
155
  useEffect( () => () => debouncedFn.cancel(), [ debouncedFn ] );
@@ -1,17 +1,9 @@
1
1
  import { type FC, type PropsWithChildren } from 'react';
2
2
  import * as React from 'react';
3
- import { Stack } from '@elementor/ui';
3
+ import { Stack, type StackProps } from '@elementor/ui';
4
4
 
5
- type PopoverContentProps = PropsWithChildren< {
6
- alignItems?: 'center';
7
- gap?: number;
8
- p?: 1.5 | 2 | 2.5;
9
- pt?: 2.5;
10
- pb?: 3;
11
- } >;
12
-
13
- export const PopoverContent: FC< PopoverContentProps > = ( { alignItems, gap = 1.5, p, pt, pb, children } ) => (
14
- <Stack alignItems={ alignItems } gap={ gap } p={ p } pt={ pt } pb={ pb }>
5
+ export const PopoverContent: FC< PropsWithChildren< StackProps > > = ( { gap = 1.5, children, ...props } ) => (
6
+ <Stack { ...props } gap={ gap }>
15
7
  { children }
16
8
  </Stack>
17
9
  );
@@ -48,6 +48,9 @@ type RepeaterProps< T > = {
48
48
  value: T;
49
49
  } >;
50
50
  };
51
+ showDuplicate?: boolean;
52
+ showToggle?: boolean;
53
+ isSortable?: boolean;
51
54
  };
52
55
 
53
56
  const EMPTY_OPEN_ITEM = -1;
@@ -60,6 +63,9 @@ export const Repeater = < T, >( {
60
63
  addToBottom = false,
61
64
  values: repeaterValues = [],
62
65
  setValues: setRepeaterValues,
66
+ showDuplicate = true,
67
+ showToggle = true,
68
+ isSortable = true,
63
69
  }: RepeaterProps< Item< T > > ) => {
64
70
  const [ openItem, setOpenItem ] = useState( EMPTY_OPEN_ITEM );
65
71
 
@@ -176,7 +182,7 @@ export const Repeater = < T, >( {
176
182
  }
177
183
 
178
184
  return (
179
- <SortableItem id={ key } key={ `sortable-${ key }` } disabled={ disabled }>
185
+ <SortableItem id={ key } key={ `sortable-${ key }` } disabled={ ! isSortable }>
180
186
  <RepeaterItem
181
187
  disabled={ disabled }
182
188
  propDisabled={ value?.disabled }
@@ -195,6 +201,8 @@ export const Repeater = < T, >( {
195
201
  toggleDisableItem={ () => toggleDisableRepeaterItem( index ) }
196
202
  openOnMount={ openOnAdd && openItem === key }
197
203
  onOpen={ () => setOpenItem( EMPTY_OPEN_ITEM ) }
204
+ showDuplicate={ showDuplicate }
205
+ showToggle={ showToggle }
198
206
  >
199
207
  { ( props ) => (
200
208
  <itemSettings.Content { ...props } value={ value } bind={ String( index ) } />
@@ -219,6 +227,8 @@ type RepeaterItemProps = {
219
227
  children: ( { anchorEl }: { anchorEl: AnchorEl } ) => React.ReactNode;
220
228
  openOnMount: boolean;
221
229
  onOpen: () => void;
230
+ showDuplicate: boolean;
231
+ showToggle: boolean;
222
232
  disabled?: boolean;
223
233
  };
224
234
 
@@ -232,6 +242,8 @@ const RepeaterItem = ( {
232
242
  toggleDisableItem,
233
243
  openOnMount,
234
244
  onOpen,
245
+ showDuplicate,
246
+ showToggle,
235
247
  disabled,
236
248
  }: RepeaterItemProps ) => {
237
249
  const [ anchorEl, setAnchorEl ] = useState< AnchorEl >( null );
@@ -255,16 +267,20 @@ const RepeaterItem = ( {
255
267
  startIcon={ startIcon }
256
268
  actions={
257
269
  <>
258
- <Tooltip title={ duplicateLabel } placement="top">
259
- <IconButton size={ SIZE } onClick={ duplicateItem } aria-label={ duplicateLabel }>
260
- <CopyIcon fontSize={ SIZE } />
261
- </IconButton>
262
- </Tooltip>
263
- <Tooltip title={ toggleLabel } placement="top">
264
- <IconButton size={ SIZE } onClick={ toggleDisableItem } aria-label={ toggleLabel }>
265
- { propDisabled ? <EyeOffIcon fontSize={ SIZE } /> : <EyeIcon fontSize={ SIZE } /> }
266
- </IconButton>
267
- </Tooltip>
270
+ { showDuplicate && (
271
+ <Tooltip title={ duplicateLabel } placement="top">
272
+ <IconButton size={ SIZE } onClick={ duplicateItem } aria-label={ duplicateLabel }>
273
+ <CopyIcon fontSize={ SIZE } />
274
+ </IconButton>
275
+ </Tooltip>
276
+ ) }
277
+ { showToggle && (
278
+ <Tooltip title={ toggleLabel } placement="top">
279
+ <IconButton size={ SIZE } onClick={ toggleDisableItem } aria-label={ toggleLabel }>
280
+ { propDisabled ? <EyeOffIcon fontSize={ SIZE } /> : <EyeIcon fontSize={ SIZE } /> }
281
+ </IconButton>
282
+ </Tooltip>
283
+ ) }
268
284
  <Tooltip title={ removeLabel } placement="top">
269
285
  <IconButton size={ SIZE } onClick={ removeItem } aria-label={ removeLabel }>
270
286
  <XIcon fontSize={ SIZE } />
@@ -1,10 +1,10 @@
1
1
  import * as React from 'react';
2
- import { type MutableRefObject } from 'react';
2
+ import { type RefObject } from 'react';
3
3
  import { bindPopover, Paper, Popover, type PopupState, TextField } from '@elementor/ui';
4
4
 
5
5
  type Props = {
6
6
  popupState: PopupState;
7
- anchorRef: MutableRefObject< HTMLElement >;
7
+ anchorRef: RefObject< HTMLDivElement | null >;
8
8
  restoreValue: () => void;
9
9
  value: string;
10
10
  onChange: ( event: React.ChangeEvent< HTMLInputElement > ) => void;
@@ -26,7 +26,7 @@ export const TextFieldPopover = ( props: Props ) => {
26
26
  >
27
27
  <Paper
28
28
  sx={ {
29
- width: anchorRef.current.offsetWidth + 'px',
29
+ width: anchorRef.current?.offsetWidth + 'px',
30
30
  borderRadius: 2,
31
31
  p: 1.5,
32
32
  } }
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { useState } from 'react';
2
+ import { useEffect, useState } from 'react';
3
3
  import { stringPropTypeUtil } from '@elementor/editor-props';
4
4
  import { MenuListItem } from '@elementor/editor-ui';
5
5
  import { ArrowsMoveHorizontalIcon, ArrowsMoveVerticalIcon } from '@elementor/icons';
@@ -38,6 +38,24 @@ export const AspectRatioControl = createControl( ( { label }: { label: string }
38
38
  isCustomSelected ? CUSTOM_RATIO : aspectRatioValue || ''
39
39
  );
40
40
 
41
+ useEffect( () => {
42
+ const isCustomValue =
43
+ aspectRatioValue && ! RATIO_OPTIONS.some( ( option ) => option.value === aspectRatioValue );
44
+
45
+ if ( isCustomValue ) {
46
+ const [ width, height ] = aspectRatioValue.split( '/' );
47
+ setCustomWidth( width || '' );
48
+ setCustomHeight( height || '' );
49
+ setSelectedValue( CUSTOM_RATIO );
50
+ setIsCustom( true );
51
+ } else {
52
+ setSelectedValue( aspectRatioValue || '' );
53
+ setIsCustom( false );
54
+ setCustomWidth( '' );
55
+ setCustomHeight( '' );
56
+ }
57
+ }, [ aspectRatioValue ] );
58
+
41
59
  const handleSelectChange = ( event: SelectChangeEvent< string > ) => {
42
60
  const newValue = event.target.value;
43
61
  const isCustomRatio = newValue === CUSTOM_RATIO;
@@ -71,7 +89,7 @@ export const AspectRatioControl = createControl( ( { label }: { label: string }
71
89
 
72
90
  return (
73
91
  <ControlActions>
74
- <Stack direction="column" pt={ 2 } gap={ 2 }>
92
+ <Stack direction="column" gap={ 2 }>
75
93
  <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
76
94
  <Grid item xs={ 6 }>
77
95
  <ControlLabel>{ label }</ControlLabel>
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { type MutableRefObject, useRef } from 'react';
2
+ import { useRef } from 'react';
3
3
  import { backgroundImagePositionOffsetPropTypeUtil, stringPropTypeUtil } from '@elementor/editor-props';
4
4
  import { MenuListItem } from '@elementor/editor-ui';
5
5
  import { LetterXIcon, LetterYIcon } from '@elementor/icons';
@@ -41,7 +41,7 @@ export const BackgroundImageOverlayPosition = () => {
41
41
  const stringPropContext = useBoundProp( stringPropTypeUtil );
42
42
 
43
43
  const isCustom = !! backgroundImageOffsetContext.value;
44
- const rowRef: MutableRefObject< HTMLElement | undefined > = useRef();
44
+ const rowRef = useRef< HTMLDivElement >( null );
45
45
 
46
46
  const handlePositionChange = ( event: SelectChangeEvent< Positions > ) => {
47
47
  const value = event.target.value || null;
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { type MutableRefObject, useRef } from 'react';
2
+ import { useRef } from 'react';
3
3
  import { backgroundImageSizeScalePropTypeUtil, stringPropTypeUtil } from '@elementor/editor-props';
4
4
  import {
5
5
  ArrowBarBothIcon,
@@ -55,7 +55,7 @@ export const BackgroundImageOverlaySize = () => {
55
55
  const stringPropContext = useBoundProp( stringPropTypeUtil );
56
56
 
57
57
  const isCustom = !! backgroundImageScaleContext.value;
58
- const rowRef: MutableRefObject< HTMLElement | undefined > = useRef();
58
+ const rowRef = useRef< HTMLDivElement >( null );
59
59
 
60
60
  const handleSizeChange = ( size: Sizes | null ) => {
61
61
  if ( size === 'custom' ) {
@@ -12,6 +12,7 @@ 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';
15
16
  import { PopoverContent } from '../../../components/popover-content';
16
17
  import { Repeater } from '../../../components/repeater';
17
18
  import { createControl } from '../../../create-control';
@@ -237,10 +238,14 @@ const ImageOverlayContent = () => {
237
238
  <PropKeyProvider bind={ 'image' }>
238
239
  <Grid container spacing={ 1 } alignItems="center">
239
240
  <Grid item xs={ 12 }>
240
- <ImageControl
241
- resolutionLabel={ __( 'Resolution', 'elementor' ) }
242
- sizes={ backgroundResolutionOptions }
243
- />
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>
244
249
  </Grid>
245
250
  </Grid>
246
251
  </PropKeyProvider>
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { type MutableRefObject, useRef } from 'react';
2
+ import { type RefObject, useRef } from 'react';
3
3
  import { boxShadowPropTypeUtil, type PropKey, shadowPropTypeUtil, type ShadowPropValue } from '@elementor/editor-props';
4
4
  import { FormLabel, Grid, type SxProps, type Theme, UnstableColorIndicator } from '@elementor/ui';
5
5
  import { __ } from '@wordpress/i18n';
@@ -49,7 +49,7 @@ const ItemContent = ( { anchorEl, bind }: { anchorEl: HTMLElement | null; bind:
49
49
 
50
50
  const Content = ( { anchorEl }: { anchorEl: HTMLElement | null } ) => {
51
51
  const context = useBoundProp( shadowPropTypeUtil );
52
- const rowRef: MutableRefObject< HTMLElement | undefined >[] = [ useRef(), useRef() ];
52
+ const rowRef: RefObject< HTMLDivElement >[] = [ useRef( null ), useRef( null ) ];
53
53
 
54
54
  return (
55
55
  <PropProvider { ...context }>
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { type MutableRefObject, type ReactNode, useId, useRef } from 'react';
2
+ import { type ReactNode, type RefObject, 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';
@@ -63,7 +63,7 @@ export function EqualUnequalSizesControl< TMultiPropType extends string, TPropVa
63
63
 
64
64
  const { value: sizeValue, setValue: setSizeValue } = useBoundProp( sizePropTypeUtil );
65
65
 
66
- const rowRefs: MutableRefObject< HTMLElement | undefined >[] = [ useRef(), useRef() ];
66
+ const rowRefs: RefObject< HTMLDivElement >[] = [ useRef( null ), useRef( null ) ];
67
67
 
68
68
  const splitEqualValue = () => {
69
69
  if ( ! sizeValue ) {
@@ -172,13 +172,7 @@ export function EqualUnequalSizesControl< TMultiPropType extends string, TPropVa
172
172
  );
173
173
  }
174
174
 
175
- const MultiSizeValueControl = ( {
176
- item,
177
- rowRef,
178
- }: {
179
- item: Item;
180
- rowRef: MutableRefObject< HTMLElement | undefined >;
181
- } ) => {
175
+ const MultiSizeValueControl = ( { item, rowRef }: { item: Item; rowRef: RefObject< HTMLDivElement > } ) => {
182
176
  const isUsingNestedProps = isExperimentActive( 'e_v_3_30' );
183
177
 
184
178
  return (