@elementor/editor-controls 0.14.0 → 0.15.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.14.0",
4
+ "version": "0.15.0",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -40,15 +40,17 @@
40
40
  "dev": "tsup --config=../../tsup.dev.ts"
41
41
  },
42
42
  "dependencies": {
43
- "@elementor/editor-props": "0.9.3",
43
+ "@elementor/editor-props": "0.9.4",
44
44
  "@elementor/env": "0.3.5",
45
- "@elementor/http": "0.1.3",
46
- "@elementor/icons": "1.31.0",
45
+ "@elementor/http": "0.1.4",
46
+ "@elementor/icons": "1.35.0",
47
+ "@elementor/query": "0.2.4",
47
48
  "@elementor/session": "0.1.0",
48
49
  "@elementor/ui": "1.26.0",
49
50
  "@elementor/utils": "0.4.0",
50
- "@elementor/wp-media": "0.4.2",
51
- "@wordpress/i18n": "^5.13.0"
51
+ "@elementor/wp-media": "0.5.0",
52
+ "@wordpress/i18n": "^5.13.0",
53
+ "@tanstack/react-virtual": "3.13.3"
52
54
  },
53
55
  "devDependencies": {
54
56
  "tsup": "^8.3.5"
package/src/api.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { httpService } from '@elementor/http';
2
+
3
+ const ELEMENTOR_SETTING_URL = 'elementor/v1/settings';
4
+
5
+ type Response< T > = { data: { value: T }; success: boolean };
6
+
7
+ export const apiClient = {
8
+ getElementorSetting: < T >( key: string ) =>
9
+ httpService()
10
+ .get< Response< T > >( `${ ELEMENTOR_SETTING_URL }/${ key }` )
11
+ .then( ( res ) => formatSettingResponse( res.data ) ),
12
+ updateElementorSetting: < T >( key: string, value: T ) =>
13
+ httpService().put( `${ ELEMENTOR_SETTING_URL }/${ key }`, { value } ),
14
+ };
15
+
16
+ const formatSettingResponse = < T >( response: Response< T > ) => response.data.value;
@@ -9,6 +9,7 @@ import {
9
9
  IconButton,
10
10
  Popover,
11
11
  Stack,
12
+ Tooltip,
12
13
  Typography,
13
14
  UnstableTag,
14
15
  type UnstableTagProps,
@@ -200,6 +201,10 @@ const RepeaterItem = ( {
200
201
 
201
202
  const popoverProps = bindPopover( popoverState );
202
203
 
204
+ const duplicateLabel = __( 'Duplicate', 'elementor' );
205
+ const toggleLabel = disabled ? __( 'Show', 'elementor' ) : __( 'Hide', 'elementor' );
206
+ const removeLabel = __( 'Remove', 'elementor' );
207
+
203
208
  return (
204
209
  <>
205
210
  <UnstableTag
@@ -213,29 +218,21 @@ const RepeaterItem = ( {
213
218
  startIcon={ startIcon }
214
219
  actions={
215
220
  <>
216
- <IconButton
217
- size={ SIZE }
218
- onClick={ duplicateItem }
219
- aria-label={ __( 'Duplicate item', 'elementor' ) }
220
- >
221
- <CopyIcon fontSize={ SIZE } />
222
- </IconButton>
223
- <IconButton
224
- size={ SIZE }
225
- onClick={ toggleDisableItem }
226
- aria-label={
227
- disabled ? __( 'Enable item', 'elementor' ) : __( 'Disable item', 'elementor' )
228
- }
229
- >
230
- { disabled ? <EyeOffIcon fontSize={ SIZE } /> : <EyeIcon fontSize={ SIZE } /> }
231
- </IconButton>
232
- <IconButton
233
- size={ SIZE }
234
- onClick={ removeItem }
235
- aria-label={ __( 'Remove item', 'elementor' ) }
236
- >
237
- <XIcon fontSize={ SIZE } />
238
- </IconButton>
221
+ <Tooltip title={ duplicateLabel } placement="top">
222
+ <IconButton size={ SIZE } onClick={ duplicateItem } aria-label={ duplicateLabel }>
223
+ <CopyIcon fontSize={ SIZE } />
224
+ </IconButton>
225
+ </Tooltip>
226
+ <Tooltip title={ toggleLabel } placement="top">
227
+ <IconButton size={ SIZE } onClick={ toggleDisableItem } aria-label={ toggleLabel }>
228
+ { disabled ? <EyeOffIcon fontSize={ SIZE } /> : <EyeIcon fontSize={ SIZE } /> }
229
+ </IconButton>
230
+ </Tooltip>
231
+ <Tooltip title={ removeLabel } placement="top">
232
+ <IconButton size={ SIZE } onClick={ removeItem } aria-label={ removeLabel }>
233
+ <XIcon fontSize={ SIZE } />
234
+ </IconButton>
235
+ </Tooltip>
239
236
  </>
240
237
  }
241
238
  sx={ { backgroundColor: 'background.paper' } }
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { type ReactNode, useId, useRef } from 'react';
3
3
  import { type PropKey, type PropTypeUtil, sizePropTypeUtil, type SizePropValue } from '@elementor/editor-props';
4
- import { bindPopover, bindToggle, Grid, Popover, Stack, ToggleButton, usePopupState } from '@elementor/ui';
4
+ import { bindPopover, bindToggle, Grid, Popover, Stack, ToggleButton, Tooltip, usePopupState } from '@elementor/ui';
5
5
  import { __ } from '@wordpress/i18n';
6
6
 
7
7
  import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-context';
@@ -23,6 +23,7 @@ export type EqualUnequalItems = [ Item, Item, Item, Item ];
23
23
  type Props< TMultiPropType extends string, TPropValue extends MultiSizePropValue > = {
24
24
  label: string;
25
25
  icon: ReactNode;
26
+ tooltipLabel: string;
26
27
  items: EqualUnequalItems;
27
28
  multiSizePropTypeUtil: PropTypeUtil< TMultiPropType, TPropValue >;
28
29
  };
@@ -44,6 +45,7 @@ const isEqualSizes = ( propValue: MultiSizePropValue, items: EqualUnequalItems )
44
45
  export function EqualUnequalSizesControl< TMultiPropType extends string, TPropValue extends MultiSizePropValue >( {
45
46
  label,
46
47
  icon,
48
+ tooltipLabel,
47
49
  items,
48
50
  multiSizePropTypeUtil,
49
51
  }: Props< TMultiPropType, TPropValue > ) {
@@ -104,15 +106,18 @@ export function EqualUnequalSizesControl< TMultiPropType extends string, TPropVa
104
106
  <Grid item xs={ 6 }>
105
107
  <Stack direction="row" alignItems="center" gap={ 1 }>
106
108
  <SizeControl placeholder={ isMixed ? __( 'Mixed', 'elementor' ) : undefined } />
107
- <ToggleButton
108
- size={ 'tiny' }
109
- value={ 'check' }
110
- sx={ { marginLeft: 'auto' } }
111
- { ...bindToggle( popupState ) }
112
- selected={ popupState.isOpen }
113
- >
114
- { icon }
115
- </ToggleButton>
109
+ <Tooltip title={ tooltipLabel } placement="top">
110
+ <ToggleButton
111
+ size={ 'tiny' }
112
+ value={ 'check' }
113
+ sx={ { marginLeft: 'auto' } }
114
+ { ...bindToggle( popupState ) }
115
+ selected={ popupState.isOpen }
116
+ aria-label={ tooltipLabel }
117
+ >
118
+ { icon }
119
+ </ToggleButton>
120
+ </Tooltip>
116
121
  </Stack>
117
122
  </Grid>
118
123
  </Grid>
@@ -0,0 +1,15 @@
1
+ type EnqueueFont = ( fontFamily: string, context?: 'preview' | 'editor' ) => void;
2
+
3
+ type ExtendedWindow = Window & {
4
+ elementor?: {
5
+ helpers?: {
6
+ enqueueFont?: EnqueueFont;
7
+ };
8
+ };
9
+ };
10
+
11
+ export const enqueueFont: EnqueueFont = ( fontFamily, context = 'editor' ) => {
12
+ const extendedWindow = window as unknown as ExtendedWindow;
13
+
14
+ return extendedWindow.elementor?.helpers?.enqueueFont?.( fontFamily, context ) ?? null;
15
+ };
@@ -0,0 +1,286 @@
1
+ import * as React from 'react';
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { stringPropTypeUtil } from '@elementor/editor-props';
4
+ import { ChevronDownIcon, EditIcon, PhotoIcon, SearchIcon, XIcon } from '@elementor/icons';
5
+ import {
6
+ bindPopover,
7
+ bindTrigger,
8
+ Box,
9
+ Divider,
10
+ IconButton,
11
+ InputAdornment,
12
+ Link,
13
+ ListSubheader,
14
+ MenuList,
15
+ Popover,
16
+ Stack,
17
+ styled,
18
+ TextField,
19
+ Typography,
20
+ UnstableTag,
21
+ usePopupState,
22
+ } from '@elementor/ui';
23
+ import { debounce } from '@elementor/utils';
24
+ import { useVirtualizer } from '@tanstack/react-virtual';
25
+ import { __ } from '@wordpress/i18n';
26
+
27
+ import { useBoundProp } from '../../bound-prop-context';
28
+ import { createControl } from '../../create-control';
29
+ import { type FontListItem, useFilteredFontFamilies } from '../../hooks/use-filtered-font-families';
30
+ import { enqueueFont } from './enqueue-font';
31
+
32
+ const SIZE = 'tiny';
33
+
34
+ type FontFamilyControlProps = {
35
+ fontFamilies: Record< string, string[] >;
36
+ };
37
+
38
+ export const FontFamilyControl = createControl( ( { fontFamilies }: FontFamilyControlProps ) => {
39
+ const [ searchValue, setSearchValue ] = useState( '' );
40
+ const { value: fontFamily, setValue: setFontFamily } = useBoundProp( stringPropTypeUtil );
41
+
42
+ const popoverState = usePopupState( { variant: 'popover' } );
43
+
44
+ const filteredFontFamilies = useFilteredFontFamilies( fontFamilies, searchValue );
45
+
46
+ const handleSearch = ( event: React.ChangeEvent< HTMLInputElement > ) => {
47
+ setSearchValue( event.target.value );
48
+ };
49
+
50
+ const handleClose = () => {
51
+ setSearchValue( '' );
52
+
53
+ popoverState.close();
54
+ };
55
+
56
+ return (
57
+ <>
58
+ <UnstableTag
59
+ variant="outlined"
60
+ label={ fontFamily }
61
+ endIcon={ <ChevronDownIcon fontSize="tiny" /> }
62
+ { ...bindTrigger( popoverState ) }
63
+ fullWidth
64
+ />
65
+
66
+ <Popover
67
+ disablePortal
68
+ disableScrollLock
69
+ anchorOrigin={ { vertical: 'bottom', horizontal: 'left' } }
70
+ { ...bindPopover( popoverState ) }
71
+ onClose={ handleClose }
72
+ >
73
+ <Stack>
74
+ <Stack direction="row" alignItems="center" pl={ 1.5 } pr={ 0.5 } py={ 1.5 }>
75
+ <EditIcon fontSize={ SIZE } sx={ { mr: 0.5 } } />
76
+ <Typography variant="subtitle2">{ __( 'Font Family', 'elementor' ) }</Typography>
77
+ <IconButton size={ SIZE } sx={ { ml: 'auto' } } onClick={ handleClose }>
78
+ <XIcon fontSize={ SIZE } />
79
+ </IconButton>
80
+ </Stack>
81
+
82
+ <Box px={ 1.5 } pb={ 1 }>
83
+ <TextField
84
+ fullWidth
85
+ size={ SIZE }
86
+ value={ searchValue }
87
+ placeholder={ __( 'Search', 'elementor' ) }
88
+ onChange={ handleSearch }
89
+ InputProps={ {
90
+ startAdornment: (
91
+ <InputAdornment position="start">
92
+ <SearchIcon fontSize={ SIZE } />
93
+ </InputAdornment>
94
+ ),
95
+ } }
96
+ />
97
+ </Box>
98
+ <Divider />
99
+ { filteredFontFamilies.length > 0 ? (
100
+ <FontList
101
+ fontListItems={ filteredFontFamilies }
102
+ setFontFamily={ setFontFamily }
103
+ handleClose={ handleClose }
104
+ fontFamily={ fontFamily }
105
+ />
106
+ ) : (
107
+ <Box sx={ { overflowY: 'auto', height: 260, width: 220 } }>
108
+ <Stack alignItems="center" p={ 2.5 } gap={ 1.5 }>
109
+ <PhotoIcon fontSize="large" />
110
+ <Typography align="center" variant="caption" color="text.secondary">
111
+ { __( 'Sorry, nothing matched', 'elementor' ) }
112
+ <br />
113
+ &ldquo;{ searchValue }&rdquo;.
114
+ </Typography>
115
+ <Typography align="center" variant="caption" color="text.secondary">
116
+ <Link
117
+ color="secondary"
118
+ variant="caption"
119
+ component="button"
120
+ onClick={ () => setSearchValue( '' ) }
121
+ >
122
+ { __( 'Clear the filters', 'elementor' ) }
123
+ </Link>
124
+ &nbsp;
125
+ { __( 'and try again.', 'elementor' ) }
126
+ </Typography>
127
+ </Stack>
128
+ </Box>
129
+ ) }
130
+ </Stack>
131
+ </Popover>
132
+ </>
133
+ );
134
+ } );
135
+
136
+ type FontListProps = {
137
+ fontListItems: FontListItem[];
138
+ setFontFamily: ( fontFamily: string ) => void;
139
+ handleClose: () => void;
140
+ fontFamily: string | null;
141
+ };
142
+
143
+ const LIST_ITEM_HEIGHT = 36;
144
+ const LIST_ITEMS_BUFFER = 6;
145
+
146
+ const FontList = ( { fontListItems, setFontFamily, handleClose, fontFamily }: FontListProps ) => {
147
+ const containerRef = useRef< HTMLDivElement >( null );
148
+ const selectedItem = fontListItems.find( ( item ) => item.value === fontFamily );
149
+
150
+ const debouncedVirtualizeChange = useDebounce( ( { getVirtualIndexes }: { getVirtualIndexes: () => number[] } ) => {
151
+ getVirtualIndexes().forEach( ( index ) => {
152
+ const item = fontListItems[ index ];
153
+ if ( item && item.type === 'font' ) {
154
+ enqueueFont( item.value );
155
+ }
156
+ } );
157
+ }, 100 );
158
+
159
+ const virtualizer = useVirtualizer( {
160
+ count: fontListItems.length,
161
+ getScrollElement: () => containerRef.current,
162
+ estimateSize: () => LIST_ITEM_HEIGHT,
163
+ overscan: LIST_ITEMS_BUFFER,
164
+ onChange: debouncedVirtualizeChange,
165
+ } );
166
+
167
+ useEffect(
168
+ () => {
169
+ virtualizer.scrollToIndex( fontListItems.findIndex( ( item ) => item.value === fontFamily ) );
170
+ },
171
+ // eslint-disable-next-line react-hooks/exhaustive-deps
172
+ [ fontFamily ]
173
+ );
174
+
175
+ return (
176
+ <Box
177
+ ref={ containerRef }
178
+ sx={ {
179
+ overflowY: 'auto',
180
+ height: 260,
181
+ width: 220,
182
+ } }
183
+ >
184
+ <StyledMenuList
185
+ role="listbox"
186
+ style={ {
187
+ height: `${ virtualizer.getTotalSize() }px`,
188
+ } }
189
+ data-testid="font-list"
190
+ >
191
+ { virtualizer.getVirtualItems().map( ( virtualRow ) => {
192
+ const item = fontListItems[ virtualRow.index ];
193
+ const isLast = virtualRow.index === fontListItems.length - 1;
194
+ // Ignore the first item, which is a category, and use the second item instead.
195
+ const isFirst = virtualRow.index === 1;
196
+ const isSelected = selectedItem?.value === item.value;
197
+
198
+ // If no item is selected, the first item should be focused.
199
+ const tabIndexFallback = ! selectedItem ? 0 : -1;
200
+
201
+ if ( item.type === 'category' ) {
202
+ return (
203
+ <ListSubheader
204
+ key={ virtualRow.key }
205
+ style={ {
206
+ transform: `translateY(${ virtualRow.start }px)`,
207
+ } }
208
+ >
209
+ { item.value }
210
+ </ListSubheader>
211
+ );
212
+ }
213
+
214
+ return (
215
+ <li
216
+ key={ virtualRow.key }
217
+ role="option"
218
+ aria-selected={ isSelected }
219
+ onClick={ () => {
220
+ setFontFamily( item.value );
221
+ handleClose();
222
+ } }
223
+ onKeyDown={ ( event ) => {
224
+ if ( event.key === 'Enter' ) {
225
+ setFontFamily( item.value );
226
+ handleClose();
227
+ }
228
+
229
+ if ( event.key === 'ArrowDown' && isLast ) {
230
+ event.preventDefault();
231
+ event.stopPropagation();
232
+ }
233
+
234
+ if ( event.key === 'ArrowUp' && isFirst ) {
235
+ event.preventDefault();
236
+ event.stopPropagation();
237
+ }
238
+ } }
239
+ tabIndex={ isSelected ? 0 : tabIndexFallback }
240
+ style={ {
241
+ transform: `translateY(${ virtualRow.start }px)`,
242
+ fontFamily: item.value,
243
+ } }
244
+ >
245
+ { item.value }
246
+ </li>
247
+ );
248
+ } ) }
249
+ </StyledMenuList>
250
+ </Box>
251
+ );
252
+ };
253
+
254
+ const StyledMenuList = styled( MenuList )( ( { theme } ) => ( {
255
+ '& > li': {
256
+ height: LIST_ITEM_HEIGHT,
257
+ position: 'absolute',
258
+ top: 0,
259
+ left: 0,
260
+ width: '100%',
261
+ },
262
+ '& > [role="option"]': {
263
+ ...theme.typography.caption,
264
+ lineHeight: 'inherit',
265
+ padding: theme.spacing( 0.75, 2 ),
266
+ '&:hover, &:focus': {
267
+ backgroundColor: theme.palette.action.hover,
268
+ },
269
+ '&[aria-selected="true"]': {
270
+ backgroundColor: theme.palette.action.selected,
271
+ },
272
+ cursor: 'pointer',
273
+ textOverflow: 'ellipsis',
274
+ },
275
+ width: '100%',
276
+ position: 'relative',
277
+ } ) );
278
+
279
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
280
+ const useDebounce = < TArgs extends any[] >( fn: ( ...args: TArgs ) => void, delay: number ) => {
281
+ const [ debouncedFn ] = useState( () => debounce( fn, delay ) );
282
+
283
+ useEffect( () => () => debouncedFn.cancel(), [ debouncedFn ] );
284
+
285
+ return debouncedFn;
286
+ };
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { layoutDirectionPropTypeUtil, type PropKey, sizePropTypeUtil } from '@elementor/editor-props';
3
3
  import { DetachIcon, LinkIcon } from '@elementor/icons';
4
- import { Grid, Stack, ToggleButton } from '@elementor/ui';
4
+ import { Grid, Stack, ToggleButton, Tooltip } from '@elementor/ui';
5
5
  import { __ } from '@wordpress/i18n';
6
6
 
7
7
  import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-context';
@@ -33,22 +33,30 @@ export const GapControl = createControl( ( { label }: { label: string } ) => {
33
33
  } );
34
34
  };
35
35
 
36
+ const tooltipLabel = label.toLowerCase();
37
+
36
38
  const LinkedIcon = isLinked ? LinkIcon : DetachIcon;
39
+ // translators: %s: Tooltip title.
40
+ const linkedLabel = __( 'Link %s', 'elementor' ).replace( '%s', tooltipLabel );
41
+ // translators: %s: Tooltip title.
42
+ const unlinkedLabel = __( 'Unlink %s', 'elementor' ).replace( '%s', tooltipLabel );
37
43
 
38
44
  return (
39
45
  <PropProvider propType={ propType } value={ directionValue } setValue={ setDirectionValue }>
40
46
  <Stack direction="row" gap={ 2 } flexWrap="nowrap">
41
47
  <ControlLabel>{ label }</ControlLabel>
42
- <ToggleButton
43
- aria-label={ __( 'Link inputs', 'elementor' ) }
44
- size={ 'tiny' }
45
- value={ 'check' }
46
- selected={ isLinked }
47
- sx={ { marginLeft: 'auto' } }
48
- onChange={ onLinkToggle }
49
- >
50
- <LinkedIcon fontSize={ 'tiny' } />
51
- </ToggleButton>
48
+ <Tooltip title={ isLinked ? unlinkedLabel : linkedLabel } placement="top">
49
+ <ToggleButton
50
+ aria-label={ isLinked ? unlinkedLabel : linkedLabel }
51
+ size={ 'tiny' }
52
+ value={ 'check' }
53
+ selected={ isLinked }
54
+ sx={ { marginLeft: 'auto' } }
55
+ onChange={ onLinkToggle }
56
+ >
57
+ <LinkedIcon fontSize={ 'tiny' } />
58
+ </ToggleButton>
59
+ </Tooltip>
52
60
  </Stack>
53
61
  <Stack direction="row" gap={ 2 } flexWrap="nowrap">
54
62
  <Grid container gap={ 1 } alignItems="center">
@@ -1,11 +1,13 @@
1
1
  import * as React from 'react';
2
2
  import { imagePropTypeUtil } from '@elementor/editor-props';
3
3
  import { Grid, Stack } from '@elementor/ui';
4
+ import { type MediaType } from '@elementor/wp-media';
4
5
  import { __ } from '@wordpress/i18n';
5
6
 
6
7
  import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-context';
7
8
  import { ControlLabel } from '../components/control-label';
8
9
  import { createControl } from '../create-control';
10
+ import { useUnfilteredFilesUpload } from '../hooks/use-unfiltered-files-upload';
9
11
  import { ImageMediaControl } from './image-media-control';
10
12
  import { SelectControl } from './select-control';
11
13
 
@@ -18,12 +20,15 @@ export const ImageControl = createControl(
18
20
  ( { sizes, resolutionLabel = __( 'Image resolution', 'elementor' ) }: ImageControlProps ) => {
19
21
  const propContext = useBoundProp( imagePropTypeUtil );
20
22
 
23
+ const { data: allowSvgUpload } = useUnfilteredFilesUpload();
24
+ const mediaTypes: MediaType[] = allowSvgUpload ? [ 'image', 'svg' ] : [ 'image' ];
25
+
21
26
  return (
22
27
  <PropProvider { ...propContext }>
23
28
  <Stack gap={ 1.5 }>
24
29
  <PropKeyProvider bind={ 'src' }>
25
30
  <ControlLabel> { __( 'Image', 'elementor' ) } </ControlLabel>
26
- <ImageMediaControl />
31
+ <ImageMediaControl mediaTypes={ mediaTypes } />
27
32
  </PropKeyProvider>
28
33
  <PropKeyProvider bind={ 'size' }>
29
34
  <Grid container gap={ 1.5 } alignItems="center" flexWrap="nowrap">
@@ -2,8 +2,7 @@ import * as React from 'react';
2
2
  import { imageSrcPropTypeUtil } from '@elementor/editor-props';
3
3
  import { UploadIcon } from '@elementor/icons';
4
4
  import { Button, Card, CardMedia, CardOverlay, CircularProgress, Stack } from '@elementor/ui';
5
- import { useWpMediaAttachment, useWpMediaFrame } from '@elementor/wp-media';
6
- import { type ImageExtension } from '@elementor/wp-media';
5
+ import { type MediaType, useWpMediaAttachment, useWpMediaFrame } from '@elementor/wp-media';
7
6
  import { __ } from '@wordpress/i18n';
8
7
 
9
8
  import { useBoundProp } from '../bound-prop-context';
@@ -11,10 +10,10 @@ import ControlActions from '../control-actions/control-actions';
11
10
  import { createControl } from '../create-control';
12
11
 
13
12
  type ImageMediaControlProps = {
14
- allowedExtensions?: ImageExtension[];
13
+ mediaTypes?: MediaType[];
15
14
  };
16
15
 
17
- export const ImageMediaControl = createControl( ( props: ImageMediaControlProps ) => {
16
+ export const ImageMediaControl = createControl( ( { mediaTypes = [ 'image' ] }: ImageMediaControlProps ) => {
18
17
  const { value, setValue } = useBoundProp( imageSrcPropTypeUtil );
19
18
  const { id, url } = value ?? {};
20
19
 
@@ -22,8 +21,7 @@ export const ImageMediaControl = createControl( ( props: ImageMediaControlProps
22
21
  const src = attachment?.url ?? url?.value ?? null;
23
22
 
24
23
  const { open } = useWpMediaFrame( {
25
- types: [ 'image', 'image/svg+xml' ],
26
- allowedExtensions: props.allowedExtensions,
24
+ mediaTypes,
27
25
  multiple: false,
28
26
  selected: id?.value || null,
29
27
  onSelect: ( selectedAttachment ) => {