@elementor/editor-controls 0.7.0 → 0.9.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 +48 -0
- package/dist/index.d.mts +37 -25
- package/dist/index.d.ts +37 -25
- package/dist/index.js +558 -274
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +529 -239
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -6
- package/src/bound-prop-context/prop-key-context.tsx +1 -1
- package/src/components/repeater.tsx +10 -4
- package/src/components/text-field-inner-selection.tsx +2 -2
- package/src/control-actions/control-actions-context.tsx +1 -1
- package/src/control-actions/control-actions.tsx +1 -1
- package/src/controls/autocomplete-control.tsx +99 -80
- package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-attachment.tsx +3 -3
- package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-position.tsx +72 -8
- package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-repeat.tsx +1 -1
- package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-size.tsx +71 -11
- package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +107 -33
- package/src/controls/box-shadow-repeater-control.tsx +1 -1
- package/src/controls/image-control.tsx +26 -22
- package/src/controls/image-media-control.tsx +1 -1
- package/src/controls/link-control.tsx +134 -17
- package/src/controls/size-control.tsx +1 -1
- package/src/controls/stroke-control.tsx +1 -1
- package/src/controls/svg-media-control.tsx +107 -0
- package/src/create-control-replacement.tsx +2 -2
- package/src/env.ts +5 -0
- package/src/index.ts +2 -1
- package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-resolution.tsx +0 -27
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.
|
|
4
|
+
"version": "0.9.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Elementor Team",
|
|
7
7
|
"homepage": "https://elementor.com/",
|
|
@@ -40,14 +40,19 @@
|
|
|
40
40
|
"dev": "tsup --config=../../tsup.dev.ts"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@elementor/editor-props": "0.
|
|
44
|
-
"@elementor/
|
|
43
|
+
"@elementor/editor-props": "0.9.0",
|
|
44
|
+
"@elementor/env": "0.3.5",
|
|
45
|
+
"@elementor/http": "0.1.3",
|
|
46
|
+
"@elementor/icons": "1.31.0",
|
|
45
47
|
"@elementor/session": "0.1.0",
|
|
46
|
-
"@elementor/ui": "1.
|
|
47
|
-
"@elementor/utils": "0.3.
|
|
48
|
-
"@elementor/wp-media": "0.4.
|
|
48
|
+
"@elementor/ui": "1.26.0",
|
|
49
|
+
"@elementor/utils": "0.3.1",
|
|
50
|
+
"@elementor/wp-media": "0.4.1",
|
|
49
51
|
"@wordpress/i18n": "^5.13.0"
|
|
50
52
|
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"tsup": "^8.3.5"
|
|
55
|
+
},
|
|
51
56
|
"peerDependencies": {
|
|
52
57
|
"react": "^18.3.1"
|
|
53
58
|
}
|
|
@@ -22,7 +22,7 @@ export type PropKeyContextValue< T, P > = {
|
|
|
22
22
|
path: PropKey[];
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
const PropKeyContext = createContext< PropKeyContextValue< PropValue, PropType > | null >( null );
|
|
26
26
|
|
|
27
27
|
type PropKeyProviderProps = React.PropsWithChildren< {
|
|
28
28
|
bind: PropKey;
|
|
@@ -24,9 +24,10 @@ type Item< T > = {
|
|
|
24
24
|
disabled?: boolean;
|
|
25
25
|
} & T;
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
type RepeaterProps< T > = {
|
|
28
28
|
label: string;
|
|
29
29
|
values?: T[];
|
|
30
|
+
addToBottom?: boolean;
|
|
30
31
|
setValues: ( newValue: T[] ) => void;
|
|
31
32
|
itemSettings: {
|
|
32
33
|
initialValues: T;
|
|
@@ -43,13 +44,18 @@ export type RepeaterProps< T > = {
|
|
|
43
44
|
export const Repeater = < T, >( {
|
|
44
45
|
label,
|
|
45
46
|
itemSettings,
|
|
47
|
+
addToBottom = false,
|
|
46
48
|
values: repeaterValues = [],
|
|
47
49
|
setValues: setRepeaterValues,
|
|
48
50
|
}: RepeaterProps< Item< T > > ) => {
|
|
49
51
|
const addRepeaterItem = () => {
|
|
50
52
|
const newItem = structuredClone( itemSettings.initialValues );
|
|
51
53
|
|
|
52
|
-
|
|
54
|
+
if ( addToBottom ) {
|
|
55
|
+
return setRepeaterValues( [ ...repeaterValues, newItem ] );
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
setRepeaterValues( [ newItem, ...repeaterValues ] );
|
|
53
59
|
};
|
|
54
60
|
|
|
55
61
|
const duplicateRepeaterItem = ( index: number ) => {
|
|
@@ -181,13 +187,13 @@ const RepeaterItem = ( {
|
|
|
181
187
|
slotProps={ {
|
|
182
188
|
paper: {
|
|
183
189
|
ref: setAnchorEl,
|
|
184
|
-
sx: { mt: 0.5,
|
|
190
|
+
sx: { mt: 0.5, width: controlRef.current?.getBoundingClientRect().width },
|
|
185
191
|
},
|
|
186
192
|
} }
|
|
187
193
|
anchorOrigin={ { vertical: 'bottom', horizontal: 'left' } }
|
|
188
194
|
{ ...popoverProps }
|
|
189
195
|
>
|
|
190
|
-
<Box
|
|
196
|
+
<Box>{ children( { anchorEl } ) }</Box>
|
|
191
197
|
</Popover>
|
|
192
198
|
</>
|
|
193
199
|
);
|
|
@@ -3,7 +3,7 @@ import { forwardRef, useId } from 'react';
|
|
|
3
3
|
import { type PropValue } from '@elementor/editor-props';
|
|
4
4
|
import { bindMenu, bindTrigger, Button, InputAdornment, Menu, MenuItem, TextField, usePopupState } from '@elementor/ui';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
type TextFieldInnerSelectionProps = {
|
|
7
7
|
placeholder?: string;
|
|
8
8
|
type: string;
|
|
9
9
|
value: PropValue;
|
|
@@ -32,7 +32,7 @@ export const TextFieldInnerSelection = forwardRef(
|
|
|
32
32
|
}
|
|
33
33
|
);
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
type SelectionEndAdornmentProps< T extends string > = {
|
|
36
36
|
options: T[];
|
|
37
37
|
onClick: ( value: T ) => void;
|
|
38
38
|
value: T;
|
|
@@ -12,7 +12,7 @@ type ControlActionsContext = {
|
|
|
12
12
|
|
|
13
13
|
const Context = createContext< ControlActionsContext | null >( null );
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
type ControlActionsProviderProps = PropsWithChildren< ControlActionsContext >;
|
|
16
16
|
|
|
17
17
|
export const ControlActionsProvider = ( { children, items }: ControlActionsProviderProps ) => (
|
|
18
18
|
<Context.Provider value={ { items } }>{ children }</Context.Provider>
|
|
@@ -13,7 +13,7 @@ const FloatingBarContainer = styled( 'span' )`
|
|
|
13
13
|
}
|
|
14
14
|
`;
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
type ControlActionsProps = PropsWithChildren< object >;
|
|
17
17
|
|
|
18
18
|
export default function ControlActions( { children }: ControlActionsProps ) {
|
|
19
19
|
const { items } = useControlActions();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { type PropTypeUtil } from '@elementor/editor-props';
|
|
3
3
|
import { XIcon } from '@elementor/icons';
|
|
4
4
|
import {
|
|
5
5
|
Autocomplete,
|
|
@@ -11,107 +11,103 @@ import {
|
|
|
11
11
|
} from '@elementor/ui';
|
|
12
12
|
|
|
13
13
|
import { useBoundProp } from '../bound-prop-context';
|
|
14
|
-
import ControlActions from '../control-actions/control-actions';
|
|
15
14
|
import { createControl } from '../create-control';
|
|
16
15
|
|
|
17
|
-
export type
|
|
16
|
+
export type FlatOption = {
|
|
17
|
+
id: string;
|
|
18
18
|
label: string;
|
|
19
19
|
groupLabel?: never;
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
export type
|
|
22
|
+
export type CategorizedOption = {
|
|
23
|
+
id: string;
|
|
23
24
|
label: string;
|
|
24
25
|
groupLabel: string;
|
|
25
26
|
};
|
|
26
27
|
|
|
27
|
-
type Props = {
|
|
28
|
-
options:
|
|
28
|
+
export type Props< TOptionKey extends string, TCustomKey extends string = '' > = {
|
|
29
|
+
options: FlatOption[] | CategorizedOption[];
|
|
30
|
+
optionRestrictedPropTypeUtil: PropTypeUtil< TOptionKey, number | null >;
|
|
31
|
+
onOptionChangeCallback?: ( newValue: number | null ) => void;
|
|
32
|
+
onTextChangeCallback?: ( newValue: string | null ) => void;
|
|
29
33
|
allowCustomValues?: boolean;
|
|
30
34
|
placeholder?: string;
|
|
31
|
-
propType?: typeof urlPropTypeUtil | typeof stringPropTypeUtil;
|
|
32
35
|
minInputLength?: number;
|
|
36
|
+
customValue?: TCustomKey;
|
|
33
37
|
};
|
|
34
38
|
|
|
35
39
|
export const AutocompleteControl = createControl(
|
|
36
|
-
( {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
40
|
+
< TOptionKey extends string, TCustomKey extends string >( props: Props< TOptionKey, TCustomKey > ) => {
|
|
41
|
+
const {
|
|
42
|
+
options,
|
|
43
|
+
optionRestrictedPropTypeUtil,
|
|
44
|
+
onOptionChangeCallback,
|
|
45
|
+
onTextChangeCallback,
|
|
46
|
+
allowCustomValues = false,
|
|
47
|
+
placeholder = '',
|
|
48
|
+
minInputLength = 2,
|
|
49
|
+
customValue,
|
|
50
|
+
} = props;
|
|
51
|
+
|
|
52
|
+
const { value: selectableValue, setValue: setSelectableValue } = useBoundProp( optionRestrictedPropTypeUtil );
|
|
53
|
+
|
|
54
|
+
const value = selectableValue || customValue || '';
|
|
55
|
+
|
|
56
|
+
const optionKeys = _factoryFilter( selectableValue || customValue || null, options, minInputLength ).map(
|
|
57
|
+
( { id } ) => id
|
|
48
58
|
);
|
|
49
59
|
const allowClear = !! value;
|
|
50
|
-
const formattedOptions = Object.keys( options );
|
|
51
60
|
|
|
52
|
-
|
|
53
|
-
|
|
61
|
+
// Prevents MUI warning when freeSolo/allowCustomValues is false
|
|
62
|
+
const muiWarningPreventer = allowCustomValues || !! value?.toString()?.length;
|
|
63
|
+
const isOptionEqualToValue = () => {
|
|
64
|
+
return muiWarningPreventer ? undefined : () => true;
|
|
54
65
|
};
|
|
66
|
+
const hasSelectedValue = !! findMatchingOption( options, selectableValue?.toString() );
|
|
55
67
|
|
|
56
|
-
const
|
|
57
|
-
|
|
68
|
+
const onOptionChange = ( newValue: number | null ) => {
|
|
69
|
+
setSelectableValue( newValue );
|
|
70
|
+
onOptionChangeCallback?.( newValue );
|
|
58
71
|
};
|
|
59
72
|
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if ( formattedValue.length < minInputLength ) {
|
|
64
|
-
return [];
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return formattedOptions.filter(
|
|
68
|
-
( optionValue ) =>
|
|
69
|
-
optionValue.toLowerCase().indexOf( formattedValue ) !== -1 ||
|
|
70
|
-
options[ optionValue ].label.toLowerCase().indexOf( formattedValue ) !== -1
|
|
71
|
-
);
|
|
73
|
+
const onTextChange = ( newValue: string | null ) => {
|
|
74
|
+
onTextChangeCallback?.( newValue );
|
|
72
75
|
};
|
|
73
76
|
|
|
74
|
-
const isOptionEqualToValue = () => {
|
|
75
|
-
return muiWarningPreventer() ? undefined : () => true;
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
// Prevents MUI warning when freeSolo/allowCustomValues is false
|
|
79
|
-
const muiWarningPreventer = () => allowCustomValues || !! filterOptions( [], { inputValue: value } ).length;
|
|
80
|
-
|
|
81
77
|
return (
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
78
|
+
<Autocomplete
|
|
79
|
+
forcePopupIcon={ false }
|
|
80
|
+
disableClearable={ true } // Disabled component's auto clear icon to use our custom one instead
|
|
81
|
+
freeSolo={ allowCustomValues }
|
|
82
|
+
value={ value?.toString() || '' }
|
|
83
|
+
size={ 'tiny' }
|
|
84
|
+
onChange={ ( _, newValue ) => onOptionChange( Number( newValue ) ) }
|
|
85
|
+
readOnly={ hasSelectedValue }
|
|
86
|
+
options={ optionKeys }
|
|
87
|
+
getOptionKey={ ( optionId ) => findMatchingOption( options, optionId )?.id || optionId }
|
|
88
|
+
getOptionLabel={ ( optionId ) => findMatchingOption( options, optionId )?.label || optionId.toString() }
|
|
89
|
+
groupBy={
|
|
90
|
+
isCategorizedOptionPool( options )
|
|
91
|
+
? ( optionId: string ) => findMatchingOption( options, optionId )?.groupLabel || optionId
|
|
92
|
+
: undefined
|
|
93
|
+
}
|
|
94
|
+
isOptionEqualToValue={ isOptionEqualToValue() }
|
|
95
|
+
filterOptions={ () => optionKeys }
|
|
96
|
+
renderOption={ ( optionProps, optionId ) => (
|
|
97
|
+
<Box component="li" { ...optionProps } key={ optionProps.id }>
|
|
98
|
+
{ findMatchingOption( options, optionId )?.label ?? optionId }
|
|
99
|
+
</Box>
|
|
100
|
+
) }
|
|
101
|
+
renderInput={ ( params ) => (
|
|
102
|
+
<TextInput
|
|
103
|
+
params={ params }
|
|
104
|
+
handleChange={ onTextChange }
|
|
105
|
+
allowClear={ allowClear }
|
|
106
|
+
placeholder={ placeholder }
|
|
107
|
+
hasSelectedValue={ hasSelectedValue }
|
|
108
|
+
/>
|
|
109
|
+
) }
|
|
110
|
+
/>
|
|
115
111
|
);
|
|
116
112
|
}
|
|
117
113
|
);
|
|
@@ -169,8 +165,31 @@ const ClearButton = ( {
|
|
|
169
165
|
</InputAdornment>
|
|
170
166
|
);
|
|
171
167
|
|
|
172
|
-
function
|
|
173
|
-
options
|
|
174
|
-
|
|
175
|
-
|
|
168
|
+
export function findMatchingOption( options: FlatOption[] | CategorizedOption[], optionId: string | null = null ) {
|
|
169
|
+
return options.find( ( { id } ) => optionId?.toString() === id.toString() );
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function isCategorizedOptionPool( options: FlatOption[] | CategorizedOption[] ): options is CategorizedOption[] {
|
|
173
|
+
return options.every( ( option ) => 'groupLabel' in option );
|
|
174
|
+
}
|
|
175
|
+
function _factoryFilter< T extends FlatOption[] | CategorizedOption[] >(
|
|
176
|
+
newValue: string | number | null,
|
|
177
|
+
options: T,
|
|
178
|
+
minInputLength: number
|
|
179
|
+
): T {
|
|
180
|
+
if ( null === newValue ) {
|
|
181
|
+
return options;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const formattedValue = String( newValue || '' )?.toLowerCase();
|
|
185
|
+
|
|
186
|
+
if ( formattedValue.length < minInputLength ) {
|
|
187
|
+
return new Array( 0 ) as T;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return options.filter(
|
|
191
|
+
( option ) =>
|
|
192
|
+
String( option.id ).toLowerCase().includes( formattedValue ) ||
|
|
193
|
+
option.label.toLowerCase().includes( formattedValue )
|
|
194
|
+
) as T;
|
|
176
195
|
}
|
|
@@ -26,11 +26,11 @@ const attachmentControlOptions: ToggleButtonGroupItem< Attachment >[] = [
|
|
|
26
26
|
|
|
27
27
|
export const BackgroundImageOverlayAttachment = () => {
|
|
28
28
|
return (
|
|
29
|
-
<Grid container gap={
|
|
30
|
-
<Grid item xs={
|
|
29
|
+
<Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
|
|
30
|
+
<Grid item xs={ 6 }>
|
|
31
31
|
<ControlLabel>{ __( 'Attachment', 'elementor' ) }</ControlLabel>
|
|
32
32
|
</Grid>
|
|
33
|
-
<Grid item
|
|
33
|
+
<Grid item xs={ 6 } sx={ { display: 'flex', justifyContent: 'flex-end' } }>
|
|
34
34
|
<ToggleControl options={ attachmentControlOptions } />
|
|
35
35
|
</Grid>
|
|
36
36
|
</Grid>
|
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { backgroundImagePositionOffsetPropTypeUtil, stringPropTypeUtil } from '@elementor/editor-props';
|
|
3
|
+
import { LetterXIcon, LetterYIcon } from '@elementor/icons';
|
|
4
|
+
import { Grid, MenuItem, Select, type SelectChangeEvent } from '@elementor/ui';
|
|
3
5
|
import { __ } from '@wordpress/i18n';
|
|
4
6
|
|
|
7
|
+
import { PropKeyProvider, PropProvider, useBoundProp } from '../../../../bound-prop-context';
|
|
5
8
|
import { ControlLabel } from '../../../../components/control-label';
|
|
6
|
-
import {
|
|
9
|
+
import { SizeControl } from '../../../size-control';
|
|
10
|
+
|
|
11
|
+
type Positions =
|
|
12
|
+
| 'center center'
|
|
13
|
+
| 'center left'
|
|
14
|
+
| 'center right'
|
|
15
|
+
| 'top center'
|
|
16
|
+
| 'top left'
|
|
17
|
+
| 'top right'
|
|
18
|
+
| 'bottom center'
|
|
19
|
+
| 'bottom left'
|
|
20
|
+
| 'bottom right'
|
|
21
|
+
| 'custom';
|
|
7
22
|
|
|
8
23
|
const backgroundPositionOptions = [
|
|
9
24
|
{ label: __( 'Center Center', 'elementor' ), value: 'center center' },
|
|
@@ -15,17 +30,66 @@ const backgroundPositionOptions = [
|
|
|
15
30
|
{ label: __( 'Bottom Center', 'elementor' ), value: 'bottom center' },
|
|
16
31
|
{ label: __( 'Bottom Left', 'elementor' ), value: 'bottom left' },
|
|
17
32
|
{ label: __( 'Bottom Right', 'elementor' ), value: 'bottom right' },
|
|
33
|
+
{ label: __( 'Custom', 'elementor' ), value: 'custom' },
|
|
18
34
|
];
|
|
19
35
|
|
|
20
36
|
export const BackgroundImageOverlayPosition = () => {
|
|
37
|
+
const backgroundImageOffsetContext = useBoundProp( backgroundImagePositionOffsetPropTypeUtil );
|
|
38
|
+
const stringPropContext = useBoundProp( stringPropTypeUtil );
|
|
39
|
+
|
|
40
|
+
const isCustom = !! backgroundImageOffsetContext.value;
|
|
41
|
+
|
|
42
|
+
const handlePositionChange = ( event: SelectChangeEvent< Positions > ) => {
|
|
43
|
+
const value = event.target.value || null;
|
|
44
|
+
|
|
45
|
+
if ( value === 'custom' ) {
|
|
46
|
+
backgroundImageOffsetContext.setValue( { x: null, y: null } );
|
|
47
|
+
} else {
|
|
48
|
+
stringPropContext.setValue( value );
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
21
52
|
return (
|
|
22
|
-
<Grid container
|
|
23
|
-
<Grid item xs={
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
53
|
+
<Grid container spacing={ 2 }>
|
|
54
|
+
<Grid item xs={ 12 }>
|
|
55
|
+
<Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
|
|
56
|
+
<Grid item xs={ 6 }>
|
|
57
|
+
<ControlLabel>{ __( 'Position', 'elementor' ) }</ControlLabel>
|
|
58
|
+
</Grid>
|
|
59
|
+
<Grid item xs={ 6 } sx={ { display: 'flex', justifyContent: 'flex-end' } }>
|
|
60
|
+
<Select
|
|
61
|
+
size="tiny"
|
|
62
|
+
value={ ( backgroundImageOffsetContext.value ? 'custom' : stringPropContext.value ) ?? '' }
|
|
63
|
+
onChange={ handlePositionChange }
|
|
64
|
+
fullWidth
|
|
65
|
+
>
|
|
66
|
+
{ backgroundPositionOptions.map( ( { label, value } ) => (
|
|
67
|
+
<MenuItem key={ value } value={ value ?? '' }>
|
|
68
|
+
{ label }
|
|
69
|
+
</MenuItem>
|
|
70
|
+
) ) }
|
|
71
|
+
</Select>
|
|
72
|
+
</Grid>
|
|
73
|
+
</Grid>
|
|
28
74
|
</Grid>
|
|
75
|
+
{ isCustom ? (
|
|
76
|
+
<PropProvider { ...backgroundImageOffsetContext }>
|
|
77
|
+
<Grid item xs={ 12 }>
|
|
78
|
+
<Grid container spacing={ 2 }>
|
|
79
|
+
<Grid item xs={ 6 }>
|
|
80
|
+
<PropKeyProvider bind={ 'x' }>
|
|
81
|
+
<SizeControl startIcon={ <LetterXIcon fontSize={ 'tiny' } /> } />
|
|
82
|
+
</PropKeyProvider>
|
|
83
|
+
</Grid>
|
|
84
|
+
<Grid item xs={ 6 }>
|
|
85
|
+
<PropKeyProvider bind={ 'y' }>
|
|
86
|
+
<SizeControl startIcon={ <LetterYIcon fontSize={ 'tiny' } /> } />
|
|
87
|
+
</PropKeyProvider>
|
|
88
|
+
</Grid>
|
|
89
|
+
</Grid>
|
|
90
|
+
</Grid>
|
|
91
|
+
</PropProvider>
|
|
92
|
+
) : null }
|
|
29
93
|
</Grid>
|
|
30
94
|
);
|
|
31
95
|
};
|
|
@@ -42,7 +42,7 @@ export const BackgroundImageOverlayRepeat = () => {
|
|
|
42
42
|
<Grid item xs={ 6 }>
|
|
43
43
|
<ControlLabel>{ __( 'Repeat', 'elementor' ) }</ControlLabel>
|
|
44
44
|
</Grid>
|
|
45
|
-
<Grid item xs={ 6 }>
|
|
45
|
+
<Grid item xs={ 6 } sx={ { display: 'flex', justifyContent: 'flex-end' } }>
|
|
46
46
|
<ToggleControl options={ repeatControlOptions } />
|
|
47
47
|
</Grid>
|
|
48
48
|
</Grid>
|
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { backgroundImageSizeScalePropTypeUtil, stringPropTypeUtil } from '@elementor/editor-props';
|
|
3
|
+
import {
|
|
4
|
+
ArrowBarBothIcon,
|
|
5
|
+
ArrowsMaximizeIcon,
|
|
6
|
+
ArrowsMoveHorizontalIcon,
|
|
7
|
+
ArrowsMoveVerticalIcon,
|
|
8
|
+
LetterAIcon,
|
|
9
|
+
PencilIcon,
|
|
10
|
+
} from '@elementor/icons';
|
|
3
11
|
import { Grid } from '@elementor/ui';
|
|
4
12
|
import { __ } from '@wordpress/i18n';
|
|
5
13
|
|
|
14
|
+
import { PropKeyProvider, PropProvider, useBoundProp } from '../../../../bound-prop-context';
|
|
6
15
|
import { ControlLabel } from '../../../../components/control-label';
|
|
7
|
-
import {
|
|
8
|
-
|
|
16
|
+
import {
|
|
17
|
+
ControlToggleButtonGroup,
|
|
18
|
+
type ToggleButtonGroupItem,
|
|
19
|
+
} from '../../../../components/control-toggle-button-group';
|
|
20
|
+
import { SizeControl } from '../../../size-control';
|
|
9
21
|
|
|
10
|
-
type Sizes = 'auto' | 'cover' | 'contain';
|
|
22
|
+
type Sizes = 'auto' | 'cover' | 'contain' | 'custom';
|
|
11
23
|
|
|
12
24
|
const sizeControlOptions: ToggleButtonGroupItem< Sizes >[] = [
|
|
13
25
|
{
|
|
14
26
|
value: 'auto',
|
|
15
27
|
label: __( 'Auto', 'elementor' ),
|
|
16
|
-
renderContent: () =>
|
|
28
|
+
renderContent: ( { size } ) => <LetterAIcon fontSize={ size } />,
|
|
17
29
|
showTooltip: true,
|
|
18
30
|
},
|
|
19
31
|
{
|
|
@@ -28,17 +40,65 @@ const sizeControlOptions: ToggleButtonGroupItem< Sizes >[] = [
|
|
|
28
40
|
renderContent: ( { size } ) => <ArrowBarBothIcon fontSize={ size } />,
|
|
29
41
|
showTooltip: true,
|
|
30
42
|
},
|
|
43
|
+
{
|
|
44
|
+
value: 'custom',
|
|
45
|
+
label: __( 'Custom', 'elementor' ),
|
|
46
|
+
renderContent: ( { size } ) => <PencilIcon fontSize={ size } />,
|
|
47
|
+
showTooltip: true,
|
|
48
|
+
},
|
|
31
49
|
];
|
|
32
50
|
|
|
33
51
|
export const BackgroundImageOverlaySize = () => {
|
|
52
|
+
const backgroundImageScaleContext = useBoundProp( backgroundImageSizeScalePropTypeUtil );
|
|
53
|
+
const stringPropContext = useBoundProp( stringPropTypeUtil );
|
|
54
|
+
|
|
55
|
+
const isCustom = !! backgroundImageScaleContext.value;
|
|
56
|
+
|
|
57
|
+
const handleSizeChange = ( size: Sizes | null ) => {
|
|
58
|
+
if ( size === 'custom' ) {
|
|
59
|
+
backgroundImageScaleContext.setValue( { width: null, height: null } );
|
|
60
|
+
} else {
|
|
61
|
+
stringPropContext.setValue( size );
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
34
65
|
return (
|
|
35
|
-
<Grid container
|
|
36
|
-
<Grid item xs={
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
66
|
+
<Grid container spacing={ 1.5 }>
|
|
67
|
+
<Grid item xs={ 12 }>
|
|
68
|
+
<Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
|
|
69
|
+
<Grid item xs={ 6 }>
|
|
70
|
+
<ControlLabel>{ __( 'Size', 'elementor' ) }</ControlLabel>
|
|
71
|
+
</Grid>
|
|
72
|
+
<Grid item xs={ 6 } sx={ { display: 'flex', justifyContent: 'flex-end' } }>
|
|
73
|
+
<ControlToggleButtonGroup
|
|
74
|
+
exclusive
|
|
75
|
+
items={ sizeControlOptions }
|
|
76
|
+
value={
|
|
77
|
+
( backgroundImageScaleContext.value ? 'custom' : stringPropContext.value ) as Sizes
|
|
78
|
+
}
|
|
79
|
+
onChange={ handleSizeChange }
|
|
80
|
+
/>
|
|
81
|
+
</Grid>
|
|
82
|
+
</Grid>
|
|
41
83
|
</Grid>
|
|
84
|
+
{ isCustom ? (
|
|
85
|
+
<PropProvider { ...backgroundImageScaleContext }>
|
|
86
|
+
<Grid item xs={ 12 }>
|
|
87
|
+
<Grid container spacing={ 2 }>
|
|
88
|
+
<Grid item xs={ 6 }>
|
|
89
|
+
<PropKeyProvider bind={ 'width' }>
|
|
90
|
+
<SizeControl startIcon={ <ArrowsMoveHorizontalIcon fontSize={ 'tiny' } /> } />
|
|
91
|
+
</PropKeyProvider>
|
|
92
|
+
</Grid>
|
|
93
|
+
<Grid item xs={ 6 }>
|
|
94
|
+
<PropKeyProvider bind={ 'height' }>
|
|
95
|
+
<SizeControl startIcon={ <ArrowsMoveVerticalIcon fontSize={ 'tiny' } /> } />
|
|
96
|
+
</PropKeyProvider>
|
|
97
|
+
</Grid>
|
|
98
|
+
</Grid>
|
|
99
|
+
</Grid>
|
|
100
|
+
</PropProvider>
|
|
101
|
+
) : null }
|
|
42
102
|
</Grid>
|
|
43
103
|
);
|
|
44
104
|
};
|