@elementor/editor-controls 0.34.2 → 0.36.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/CHANGELOG.md +21 -0
- package/dist/index.d.mts +11 -5
- package/dist/index.d.ts +11 -5
- package/dist/index.js +363 -312
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +316 -272
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/components/control-form-label.tsx +3 -3
- package/src/components/control-label.tsx +1 -1
- package/src/components/font-family-selector.tsx +282 -0
- package/src/components/repeater.tsx +2 -4
- package/src/components/sortable.tsx +4 -2
- package/src/controls/aspect-ratio-control.tsx +51 -48
- package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-position.tsx +3 -2
- package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-size.tsx +2 -1
- package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +1 -1
- package/src/controls/box-shadow-repeater-control.tsx +3 -3
- package/src/controls/equal-unequal-sizes-control.tsx +14 -4
- package/src/controls/font-family-control/font-family-control.tsx +12 -273
- package/src/controls/gap-control.tsx +3 -1
- package/src/controls/image-control.tsx +2 -2
- package/src/controls/link-control.tsx +1 -1
- package/src/controls/linked-dimensions-control.tsx +11 -4
- package/src/index.ts +1 -0
|
@@ -1,36 +1,12 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { useEffect, useRef, useState } from 'react';
|
|
3
2
|
import { stringPropTypeUtil } from '@elementor/editor-props';
|
|
4
|
-
import { ChevronDownIcon
|
|
5
|
-
import {
|
|
6
|
-
bindPopover,
|
|
7
|
-
bindTrigger,
|
|
8
|
-
Box,
|
|
9
|
-
Divider,
|
|
10
|
-
IconButton,
|
|
11
|
-
InputAdornment,
|
|
12
|
-
Link,
|
|
13
|
-
MenuList,
|
|
14
|
-
MenuSubheader,
|
|
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';
|
|
3
|
+
import { ChevronDownIcon } from '@elementor/icons';
|
|
4
|
+
import { bindPopover, bindTrigger, Popover, UnstableTag, usePopupState } from '@elementor/ui';
|
|
26
5
|
|
|
27
6
|
import { useBoundProp } from '../../bound-prop-context';
|
|
7
|
+
import { FontFamilySelector } from '../../components/font-family-selector';
|
|
28
8
|
import ControlActions from '../../control-actions/control-actions';
|
|
29
9
|
import { createControl } from '../../create-control';
|
|
30
|
-
import { type FontListItem, useFilteredFontFamilies } from '../../hooks/use-filtered-font-families';
|
|
31
|
-
import { enqueueFont } from './enqueue-font';
|
|
32
|
-
|
|
33
|
-
const SIZE = 'tiny';
|
|
34
10
|
|
|
35
11
|
export type FontCategory = {
|
|
36
12
|
label: string;
|
|
@@ -41,31 +17,20 @@ type FontFamilyControlProps = {
|
|
|
41
17
|
fontFamilies: FontCategory[];
|
|
42
18
|
};
|
|
43
19
|
|
|
20
|
+
const SIZE = 'tiny';
|
|
21
|
+
|
|
44
22
|
export const FontFamilyControl = createControl( ( { fontFamilies }: FontFamilyControlProps ) => {
|
|
45
|
-
const [ searchValue, setSearchValue ] = useState( '' );
|
|
46
23
|
const { value: fontFamily, setValue: setFontFamily, disabled } = useBoundProp( stringPropTypeUtil );
|
|
47
24
|
|
|
48
25
|
const popoverState = usePopupState( { variant: 'popover' } );
|
|
49
26
|
|
|
50
|
-
const filteredFontFamilies = useFilteredFontFamilies( fontFamilies, searchValue );
|
|
51
|
-
|
|
52
|
-
const handleSearch = ( event: React.ChangeEvent< HTMLInputElement > ) => {
|
|
53
|
-
setSearchValue( event.target.value );
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const handleClose = () => {
|
|
57
|
-
setSearchValue( '' );
|
|
58
|
-
|
|
59
|
-
popoverState.close();
|
|
60
|
-
};
|
|
61
|
-
|
|
62
27
|
return (
|
|
63
28
|
<>
|
|
64
29
|
<ControlActions>
|
|
65
30
|
<UnstableTag
|
|
66
31
|
variant="outlined"
|
|
67
32
|
label={ fontFamily }
|
|
68
|
-
endIcon={ <ChevronDownIcon fontSize=
|
|
33
|
+
endIcon={ <ChevronDownIcon fontSize={ SIZE } /> }
|
|
69
34
|
{ ...bindTrigger( popoverState ) }
|
|
70
35
|
fullWidth
|
|
71
36
|
disabled={ disabled }
|
|
@@ -76,240 +41,14 @@ export const FontFamilyControl = createControl( ( { fontFamilies }: FontFamilyCo
|
|
|
76
41
|
disableScrollLock
|
|
77
42
|
anchorOrigin={ { vertical: 'bottom', horizontal: 'left' } }
|
|
78
43
|
{ ...bindPopover( popoverState ) }
|
|
79
|
-
onClose={ handleClose }
|
|
80
44
|
>
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
</IconButton>
|
|
88
|
-
</Stack>
|
|
89
|
-
|
|
90
|
-
<Box px={ 1.5 } pb={ 1 }>
|
|
91
|
-
<TextField
|
|
92
|
-
// eslint-disable-next-line jsx-a11y/no-autofocus
|
|
93
|
-
autoFocus
|
|
94
|
-
fullWidth
|
|
95
|
-
size={ SIZE }
|
|
96
|
-
value={ searchValue }
|
|
97
|
-
placeholder={ __( 'Search', 'elementor' ) }
|
|
98
|
-
onChange={ handleSearch }
|
|
99
|
-
InputProps={ {
|
|
100
|
-
startAdornment: (
|
|
101
|
-
<InputAdornment position="start">
|
|
102
|
-
<SearchIcon fontSize={ SIZE } />
|
|
103
|
-
</InputAdornment>
|
|
104
|
-
),
|
|
105
|
-
} }
|
|
106
|
-
/>
|
|
107
|
-
</Box>
|
|
108
|
-
<Divider />
|
|
109
|
-
{ filteredFontFamilies.length > 0 ? (
|
|
110
|
-
<FontList
|
|
111
|
-
fontListItems={ filteredFontFamilies }
|
|
112
|
-
setFontFamily={ setFontFamily }
|
|
113
|
-
handleClose={ handleClose }
|
|
114
|
-
fontFamily={ fontFamily }
|
|
115
|
-
/>
|
|
116
|
-
) : (
|
|
117
|
-
<Box sx={ { overflowY: 'auto', height: 260, width: 220 } }>
|
|
118
|
-
<Stack alignItems="center" p={ 2.5 } gap={ 1.5 } overflow={ 'hidden' }>
|
|
119
|
-
<TextIcon fontSize="large" />
|
|
120
|
-
<Box sx={ { maxWidth: 160, overflow: 'hidden' } }>
|
|
121
|
-
<Typography align="center" variant="subtitle2" color="text.secondary">
|
|
122
|
-
{ __( 'Sorry, nothing matched', 'elementor' ) }
|
|
123
|
-
</Typography>
|
|
124
|
-
<Typography
|
|
125
|
-
variant="subtitle2"
|
|
126
|
-
color="text.secondary"
|
|
127
|
-
sx={ {
|
|
128
|
-
display: 'flex',
|
|
129
|
-
width: '100%',
|
|
130
|
-
justifyContent: 'center',
|
|
131
|
-
} }
|
|
132
|
-
>
|
|
133
|
-
<span>“</span>
|
|
134
|
-
<span
|
|
135
|
-
style={ { maxWidth: '80%', overflow: 'hidden', textOverflow: 'ellipsis' } }
|
|
136
|
-
>
|
|
137
|
-
{ searchValue }
|
|
138
|
-
</span>
|
|
139
|
-
<span>”.</span>
|
|
140
|
-
</Typography>
|
|
141
|
-
</Box>
|
|
142
|
-
<Typography align="center" variant="caption" color="text.secondary">
|
|
143
|
-
{ __( 'Try something else.', 'elementor' ) }
|
|
144
|
-
<Link
|
|
145
|
-
color="secondary"
|
|
146
|
-
variant="caption"
|
|
147
|
-
component="button"
|
|
148
|
-
onClick={ () => setSearchValue( '' ) }
|
|
149
|
-
>
|
|
150
|
-
{ __( 'Clear & try again', 'elementor' ) }
|
|
151
|
-
</Link>
|
|
152
|
-
</Typography>
|
|
153
|
-
</Stack>
|
|
154
|
-
</Box>
|
|
155
|
-
) }
|
|
156
|
-
</Stack>
|
|
45
|
+
<FontFamilySelector
|
|
46
|
+
fontFamilies={ fontFamilies }
|
|
47
|
+
fontFamily={ fontFamily }
|
|
48
|
+
onFontFamilyChange={ setFontFamily }
|
|
49
|
+
onClose={ popoverState.close }
|
|
50
|
+
/>
|
|
157
51
|
</Popover>
|
|
158
52
|
</>
|
|
159
53
|
);
|
|
160
54
|
} );
|
|
161
|
-
|
|
162
|
-
type FontListProps = {
|
|
163
|
-
fontListItems: FontListItem[];
|
|
164
|
-
setFontFamily: ( fontFamily: string ) => void;
|
|
165
|
-
handleClose: () => void;
|
|
166
|
-
fontFamily: string | null;
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
const LIST_ITEM_HEIGHT = 36;
|
|
170
|
-
const LIST_ITEMS_BUFFER = 6;
|
|
171
|
-
|
|
172
|
-
const FontList = ( { fontListItems, setFontFamily, handleClose, fontFamily }: FontListProps ) => {
|
|
173
|
-
const containerRef = useRef< HTMLDivElement >( null );
|
|
174
|
-
const selectedItem = fontListItems.find( ( item ) => item.value === fontFamily );
|
|
175
|
-
|
|
176
|
-
const debouncedVirtualizeChange = useDebounce( ( { getVirtualIndexes }: { getVirtualIndexes: () => number[] } ) => {
|
|
177
|
-
getVirtualIndexes().forEach( ( index ) => {
|
|
178
|
-
const item = fontListItems[ index ];
|
|
179
|
-
if ( item && item.type === 'font' ) {
|
|
180
|
-
enqueueFont( item.value );
|
|
181
|
-
}
|
|
182
|
-
} );
|
|
183
|
-
}, 100 );
|
|
184
|
-
|
|
185
|
-
const virtualizer = useVirtualizer( {
|
|
186
|
-
count: fontListItems.length,
|
|
187
|
-
getScrollElement: () => containerRef.current,
|
|
188
|
-
estimateSize: () => LIST_ITEM_HEIGHT,
|
|
189
|
-
overscan: LIST_ITEMS_BUFFER,
|
|
190
|
-
onChange: debouncedVirtualizeChange,
|
|
191
|
-
} );
|
|
192
|
-
|
|
193
|
-
useEffect(
|
|
194
|
-
() => {
|
|
195
|
-
virtualizer.scrollToIndex( fontListItems.findIndex( ( item ) => item.value === fontFamily ) );
|
|
196
|
-
},
|
|
197
|
-
// eslint-disable-next-line react-compiler/react-compiler
|
|
198
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
199
|
-
[ fontFamily ]
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
return (
|
|
203
|
-
<Box
|
|
204
|
-
ref={ containerRef }
|
|
205
|
-
sx={ {
|
|
206
|
-
overflowY: 'auto',
|
|
207
|
-
height: 260,
|
|
208
|
-
width: 220,
|
|
209
|
-
} }
|
|
210
|
-
>
|
|
211
|
-
<StyledMenuList
|
|
212
|
-
role="listbox"
|
|
213
|
-
style={ {
|
|
214
|
-
height: `${ virtualizer.getTotalSize() }px`,
|
|
215
|
-
} }
|
|
216
|
-
data-testid="font-list"
|
|
217
|
-
>
|
|
218
|
-
{ virtualizer.getVirtualItems().map( ( virtualRow ) => {
|
|
219
|
-
const item = fontListItems[ virtualRow.index ];
|
|
220
|
-
const isLast = virtualRow.index === fontListItems.length - 1;
|
|
221
|
-
// Ignore the first item, which is a category, and use the second item instead.
|
|
222
|
-
const isFirst = virtualRow.index === 1;
|
|
223
|
-
const isSelected = selectedItem?.value === item.value;
|
|
224
|
-
|
|
225
|
-
// If no item is selected, the first item should be focused.
|
|
226
|
-
const tabIndexFallback = ! selectedItem ? 0 : -1;
|
|
227
|
-
|
|
228
|
-
if ( item.type === 'category' ) {
|
|
229
|
-
return (
|
|
230
|
-
<MenuSubheader
|
|
231
|
-
key={ virtualRow.key }
|
|
232
|
-
style={ {
|
|
233
|
-
transform: `translateY(${ virtualRow.start }px)`,
|
|
234
|
-
} }
|
|
235
|
-
>
|
|
236
|
-
{ item.value }
|
|
237
|
-
</MenuSubheader>
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return (
|
|
242
|
-
<li
|
|
243
|
-
key={ virtualRow.key }
|
|
244
|
-
role="option"
|
|
245
|
-
aria-selected={ isSelected }
|
|
246
|
-
onClick={ () => {
|
|
247
|
-
setFontFamily( item.value );
|
|
248
|
-
handleClose();
|
|
249
|
-
} }
|
|
250
|
-
onKeyDown={ ( event ) => {
|
|
251
|
-
if ( event.key === 'Enter' ) {
|
|
252
|
-
setFontFamily( item.value );
|
|
253
|
-
handleClose();
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if ( event.key === 'ArrowDown' && isLast ) {
|
|
257
|
-
event.preventDefault();
|
|
258
|
-
event.stopPropagation();
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
if ( event.key === 'ArrowUp' && isFirst ) {
|
|
262
|
-
event.preventDefault();
|
|
263
|
-
event.stopPropagation();
|
|
264
|
-
}
|
|
265
|
-
} }
|
|
266
|
-
tabIndex={ isSelected ? 0 : tabIndexFallback }
|
|
267
|
-
style={ {
|
|
268
|
-
transform: `translateY(${ virtualRow.start }px)`,
|
|
269
|
-
fontFamily: item.value,
|
|
270
|
-
} }
|
|
271
|
-
>
|
|
272
|
-
{ item.value }
|
|
273
|
-
</li>
|
|
274
|
-
);
|
|
275
|
-
} ) }
|
|
276
|
-
</StyledMenuList>
|
|
277
|
-
</Box>
|
|
278
|
-
);
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
const StyledMenuList = styled( MenuList )( ( { theme } ) => ( {
|
|
282
|
-
'& > li': {
|
|
283
|
-
height: LIST_ITEM_HEIGHT,
|
|
284
|
-
position: 'absolute',
|
|
285
|
-
top: 0,
|
|
286
|
-
left: 0,
|
|
287
|
-
width: '100%',
|
|
288
|
-
display: 'flex',
|
|
289
|
-
alignItems: 'center',
|
|
290
|
-
},
|
|
291
|
-
'& > [role="option"]': {
|
|
292
|
-
...theme.typography.caption,
|
|
293
|
-
lineHeight: 'inherit',
|
|
294
|
-
padding: theme.spacing( 0.75, 2, 0.75, 4 ),
|
|
295
|
-
'&:hover, &:focus': {
|
|
296
|
-
backgroundColor: theme.palette.action.hover,
|
|
297
|
-
},
|
|
298
|
-
'&[aria-selected="true"]': {
|
|
299
|
-
backgroundColor: theme.palette.action.selected,
|
|
300
|
-
},
|
|
301
|
-
cursor: 'pointer',
|
|
302
|
-
textOverflow: 'ellipsis',
|
|
303
|
-
},
|
|
304
|
-
width: '100%',
|
|
305
|
-
position: 'relative',
|
|
306
|
-
} ) );
|
|
307
|
-
|
|
308
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
309
|
-
const useDebounce = < TArgs extends any[] >( fn: ( ...args: TArgs ) => void, delay: number ) => {
|
|
310
|
-
const [ debouncedFn ] = useState( () => debounce( fn, delay ) );
|
|
311
|
-
|
|
312
|
-
useEffect( () => () => debouncedFn.cancel(), [ debouncedFn ] );
|
|
313
|
-
|
|
314
|
-
return debouncedFn;
|
|
315
|
-
};
|
|
@@ -44,6 +44,8 @@ export const GapControl = createControl( ( { label }: { label: string } ) => {
|
|
|
44
44
|
// translators: %s: Tooltip title.
|
|
45
45
|
const unlinkedLabel = __( 'Unlink %s', 'elementor' ).replace( '%s', tooltipLabel );
|
|
46
46
|
|
|
47
|
+
const disabled = sizeDisabled || directionDisabled;
|
|
48
|
+
|
|
47
49
|
return (
|
|
48
50
|
<PropProvider propType={ propType } value={ directionValue } setValue={ setDirectionValue }>
|
|
49
51
|
<Stack direction="row" gap={ 2 } flexWrap="nowrap">
|
|
@@ -56,7 +58,7 @@ export const GapControl = createControl( ( { label }: { label: string } ) => {
|
|
|
56
58
|
selected={ isLinked }
|
|
57
59
|
sx={ { marginLeft: 'auto' } }
|
|
58
60
|
onChange={ onLinkToggle }
|
|
59
|
-
disabled={
|
|
61
|
+
disabled={ disabled }
|
|
60
62
|
>
|
|
61
63
|
<LinkedIcon fontSize={ 'tiny' } />
|
|
62
64
|
</ToggleButton>
|
|
@@ -29,7 +29,7 @@ export const ImageControl = createControl(
|
|
|
29
29
|
<Stack gap={ 1.5 }>
|
|
30
30
|
{ [ 'all', 'media' ].includes( showMode ) ? (
|
|
31
31
|
<PropKeyProvider bind={ 'src' }>
|
|
32
|
-
<ControlFormLabel>
|
|
32
|
+
<ControlFormLabel>{ __( 'Image', 'elementor' ) }</ControlFormLabel>
|
|
33
33
|
<ImageMediaControl mediaTypes={ mediaTypes } />
|
|
34
34
|
</PropKeyProvider>
|
|
35
35
|
) : null }
|
|
@@ -37,7 +37,7 @@ export const ImageControl = createControl(
|
|
|
37
37
|
<PropKeyProvider bind={ 'size' }>
|
|
38
38
|
<Grid container gap={ 1.5 } alignItems="center" flexWrap="nowrap">
|
|
39
39
|
<Grid item xs={ 6 }>
|
|
40
|
-
<ControlFormLabel>
|
|
40
|
+
<ControlFormLabel>{ resolutionLabel }</ControlFormLabel>
|
|
41
41
|
</Grid>
|
|
42
42
|
<Grid item xs={ 6 } sx={ { overflow: 'hidden' } }>
|
|
43
43
|
<SelectControl options={ sizes } />
|
|
@@ -189,7 +189,7 @@ export const LinkControl = createControl( ( props: Props ) => {
|
|
|
189
189
|
</ControlActions>
|
|
190
190
|
</PropKeyProvider>
|
|
191
191
|
<PropKeyProvider bind={ 'isTargetBlank' }>
|
|
192
|
-
<SwitchControl disabled={ ! value } />
|
|
192
|
+
<SwitchControl disabled={ propContext.disabled || ! value } />
|
|
193
193
|
</PropKeyProvider>
|
|
194
194
|
</Stack>
|
|
195
195
|
</Collapse>
|
|
@@ -58,8 +58,15 @@ export const LinkedDimensionsControl = createControl(
|
|
|
58
58
|
// translators: %s: Tooltip title.
|
|
59
59
|
const unlinkedLabel = __( 'Unlink %s', 'elementor' ).replace( '%s', tooltipLabel );
|
|
60
60
|
|
|
61
|
+
const disabled = sizeDisabled || dimensionsDisabled;
|
|
62
|
+
|
|
61
63
|
return (
|
|
62
|
-
<PropProvider
|
|
64
|
+
<PropProvider
|
|
65
|
+
propType={ propType }
|
|
66
|
+
value={ dimensionsValue }
|
|
67
|
+
setValue={ setDimensionsValue }
|
|
68
|
+
disabled={ disabled }
|
|
69
|
+
>
|
|
63
70
|
<Stack direction="row" gap={ 2 } flexWrap="nowrap">
|
|
64
71
|
{ isUsingNestedProps ? (
|
|
65
72
|
<ControlFormLabel>{ label }</ControlFormLabel>
|
|
@@ -74,7 +81,7 @@ export const LinkedDimensionsControl = createControl(
|
|
|
74
81
|
selected={ isLinked }
|
|
75
82
|
sx={ { marginLeft: 'auto' } }
|
|
76
83
|
onChange={ onLinkToggle }
|
|
77
|
-
disabled={
|
|
84
|
+
disabled={ disabled }
|
|
78
85
|
>
|
|
79
86
|
<LinkedIcon fontSize={ 'tiny' } />
|
|
80
87
|
</ToggleButton>
|
|
@@ -141,6 +148,8 @@ export const LinkedDimensionsControl = createControl(
|
|
|
141
148
|
<Grid item xs={ 12 }>
|
|
142
149
|
<Control
|
|
143
150
|
bind={ 'inline-start' }
|
|
151
|
+
isLinked={ isLinked }
|
|
152
|
+
extendedValues={ extendedValues }
|
|
144
153
|
startIcon={
|
|
145
154
|
isSiteRtl ? (
|
|
146
155
|
<SideRightIcon fontSize={ 'tiny' } />
|
|
@@ -148,8 +157,6 @@ export const LinkedDimensionsControl = createControl(
|
|
|
148
157
|
<SideLeftIcon fontSize={ 'tiny' } />
|
|
149
158
|
)
|
|
150
159
|
}
|
|
151
|
-
isLinked={ isLinked }
|
|
152
|
-
extendedValues={ extendedValues }
|
|
153
160
|
/>
|
|
154
161
|
</Grid>
|
|
155
162
|
</Grid>
|
package/src/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ export { SwitchControl } from './controls/switch-control';
|
|
|
23
23
|
// components
|
|
24
24
|
export { ControlFormLabel } from './components/control-form-label';
|
|
25
25
|
export { ControlToggleButtonGroup } from './components/control-toggle-button-group';
|
|
26
|
+
export { FontFamilySelector } from './components/font-family-selector';
|
|
26
27
|
|
|
27
28
|
// types
|
|
28
29
|
export type { ControlComponent } from './create-control';
|