@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/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx
CHANGED
|
@@ -6,39 +6,78 @@ import {
|
|
|
6
6
|
backgroundOverlayPropTypeUtil,
|
|
7
7
|
type PropKey,
|
|
8
8
|
} from '@elementor/editor-props';
|
|
9
|
-
import { Box, Grid, Stack, Tab, TabPanel, Tabs, UnstableColorIndicator, useTabs } from '@elementor/ui';
|
|
9
|
+
import { Box, CardMedia, Grid, Stack, Tab, TabPanel, Tabs, UnstableColorIndicator, useTabs } from '@elementor/ui';
|
|
10
10
|
import { useWpMediaAttachment } from '@elementor/wp-media';
|
|
11
11
|
import { __ } from '@wordpress/i18n';
|
|
12
12
|
|
|
13
13
|
import { PropKeyProvider, PropProvider, useBoundProp } from '../../../bound-prop-context';
|
|
14
14
|
import { Repeater } from '../../../components/repeater';
|
|
15
15
|
import { createControl } from '../../../create-control';
|
|
16
|
+
import { env } from '../../../env';
|
|
16
17
|
import { ColorControl } from '../../color-control';
|
|
17
|
-
import {
|
|
18
|
+
import { ImageControl } from '../../image-control';
|
|
18
19
|
import { BackgroundImageOverlayAttachment } from './background-image-overlay/background-image-overlay-attachment';
|
|
19
20
|
import { BackgroundImageOverlayPosition } from './background-image-overlay/background-image-overlay-position';
|
|
20
21
|
import { BackgroundImageOverlayRepeat } from './background-image-overlay/background-image-overlay-repeat';
|
|
21
|
-
import { BackgroundImageOverlayResolution } from './background-image-overlay/background-image-overlay-resolution';
|
|
22
22
|
import { BackgroundImageOverlaySize } from './background-image-overlay/background-image-overlay-size';
|
|
23
23
|
|
|
24
|
-
const
|
|
25
|
-
const initialBackgroundOverlay: BackgroundOverlayItemPropValue = {
|
|
24
|
+
const initialBackgroundOverlay = ( backgroundPlaceholderImage: string ): BackgroundOverlayItemPropValue => ( {
|
|
26
25
|
$$type: 'background-image-overlay',
|
|
27
26
|
value: {
|
|
28
|
-
|
|
29
|
-
$$type: 'image
|
|
27
|
+
image: {
|
|
28
|
+
$$type: 'image',
|
|
30
29
|
value: {
|
|
31
|
-
|
|
32
|
-
$$type: 'image-
|
|
33
|
-
value:
|
|
30
|
+
src: {
|
|
31
|
+
$$type: 'image-src',
|
|
32
|
+
value: {
|
|
33
|
+
url: {
|
|
34
|
+
$$type: 'url',
|
|
35
|
+
value: backgroundPlaceholderImage,
|
|
36
|
+
},
|
|
37
|
+
id: null,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
size: {
|
|
41
|
+
$$type: 'string',
|
|
42
|
+
value: 'large',
|
|
34
43
|
},
|
|
35
44
|
},
|
|
36
45
|
},
|
|
37
46
|
},
|
|
38
|
-
};
|
|
47
|
+
} );
|
|
48
|
+
|
|
49
|
+
const backgroundResolutionOptions = [
|
|
50
|
+
{ label: __( 'Thumbnail - 150 x 150', 'elementor' ), value: 'thumbnail' },
|
|
51
|
+
{ label: __( 'Medium - 300 x 300', 'elementor' ), value: 'medium' },
|
|
52
|
+
{ label: __( 'Large 1024 x 1024', 'elementor' ), value: 'large' },
|
|
53
|
+
{ label: __( 'Full', 'elementor' ), value: 'full' },
|
|
54
|
+
];
|
|
39
55
|
|
|
40
56
|
type OverlayType = 'image' | 'color';
|
|
41
57
|
|
|
58
|
+
type ImageSrcAttachment = { id: { $$type: 'image-attachment-id'; value: number }; url: null };
|
|
59
|
+
|
|
60
|
+
type ImageSrcUrl = { url: { $$type: 'url'; value: string }; id: null };
|
|
61
|
+
|
|
62
|
+
type BackgroundImageOverlay = {
|
|
63
|
+
$$type: 'background-image-overlay';
|
|
64
|
+
value: {
|
|
65
|
+
image: {
|
|
66
|
+
$$type: 'image';
|
|
67
|
+
value: {
|
|
68
|
+
src: {
|
|
69
|
+
$$type: 'image-src';
|
|
70
|
+
value: ImageSrcAttachment | ImageSrcUrl;
|
|
71
|
+
};
|
|
72
|
+
size: {
|
|
73
|
+
$$type: 'string';
|
|
74
|
+
value: 'thumbnail' | 'medium' | 'large' | 'full';
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
|
|
42
81
|
export const BackgroundOverlayRepeaterControl = createControl( () => {
|
|
43
82
|
const { propType, value: overlayValues, setValue } = useBoundProp( backgroundOverlayPropTypeUtil );
|
|
44
83
|
|
|
@@ -52,17 +91,13 @@ export const BackgroundOverlayRepeaterControl = createControl( () => {
|
|
|
52
91
|
Icon: ItemIcon,
|
|
53
92
|
Label: ItemLabel,
|
|
54
93
|
Content: ItemContent,
|
|
55
|
-
initialValues: initialBackgroundOverlay,
|
|
94
|
+
initialValues: initialBackgroundOverlay( env.background_placeholder_image ),
|
|
56
95
|
} }
|
|
57
96
|
/>
|
|
58
97
|
</PropProvider>
|
|
59
98
|
);
|
|
60
99
|
} );
|
|
61
100
|
|
|
62
|
-
const ItemIcon = ( { value }: { value: BackgroundOverlayItemPropValue } ) => (
|
|
63
|
-
<UnstableColorIndicator size="inherit" component="span" value={ value.value } />
|
|
64
|
-
);
|
|
65
|
-
|
|
66
101
|
const ItemContent = ( { bind, value }: { bind: PropKey; value: BackgroundOverlayItemPropValue } ) => {
|
|
67
102
|
return (
|
|
68
103
|
<PropKeyProvider bind={ bind }>
|
|
@@ -83,12 +118,12 @@ const Content = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
|
|
|
83
118
|
<Tab label={ __( 'Color', 'elementor' ) } { ...getTabProps( 'color' ) } />
|
|
84
119
|
</Tabs>
|
|
85
120
|
</Box>
|
|
86
|
-
<TabPanel { ...getTabPanelProps( 'image' ) }>
|
|
121
|
+
<TabPanel sx={ { p: 1.5 } } { ...getTabPanelProps( 'image' ) }>
|
|
87
122
|
<Stack gap={ 1.5 }>
|
|
88
123
|
<ImageOverlayContent />
|
|
89
124
|
</Stack>
|
|
90
125
|
</TabPanel>
|
|
91
|
-
<TabPanel { ...getTabPanelProps( 'color' ) }>
|
|
126
|
+
<TabPanel { ...getTabPanelProps( 'color' ) } sx={ { p: 1.5 } }>
|
|
92
127
|
<Grid container spacing={ 1 } alignItems="center">
|
|
93
128
|
<Grid item xs={ 12 }>
|
|
94
129
|
<ColorControl propTypeUtil={ backgroundColorOverlayPropTypeUtil } />
|
|
@@ -99,14 +134,35 @@ const Content = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
|
|
|
99
134
|
);
|
|
100
135
|
};
|
|
101
136
|
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
137
|
+
const ItemIcon = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
|
|
138
|
+
switch ( value.$$type ) {
|
|
139
|
+
case 'background-image-overlay':
|
|
140
|
+
return <ItemIconImage value={ value as BackgroundImageOverlay } />;
|
|
141
|
+
case 'background-color-overlay':
|
|
142
|
+
return <ItemIconColor value={ value } />;
|
|
143
|
+
default:
|
|
144
|
+
return null;
|
|
107
145
|
}
|
|
108
|
-
|
|
109
|
-
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const ItemIconColor = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
|
|
149
|
+
return <UnstableColorIndicator size="inherit" component="span" value={ value.value } />;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const ItemIconImage = ( { value }: { value: BackgroundImageOverlay } ) => {
|
|
153
|
+
const { imageUrl } = useImage( value );
|
|
154
|
+
|
|
155
|
+
return <CardMedia image={ imageUrl } sx={ { height: 13, width: 13, borderRadius: '4px' } } />;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const ItemLabel = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
|
|
159
|
+
switch ( value.$$type ) {
|
|
160
|
+
case 'background-image-overlay':
|
|
161
|
+
return <ItemLabelImage value={ value as BackgroundImageOverlay } />;
|
|
162
|
+
case 'background-color-overlay':
|
|
163
|
+
return <ItemLabelColor value={ value } />;
|
|
164
|
+
default:
|
|
165
|
+
return null;
|
|
110
166
|
}
|
|
111
167
|
};
|
|
112
168
|
|
|
@@ -114,9 +170,8 @@ const ItemLabelColor = ( { value }: { value: BackgroundOverlayItemPropValue } )
|
|
|
114
170
|
return <span>{ value.value }</span>;
|
|
115
171
|
};
|
|
116
172
|
|
|
117
|
-
const ItemLabelImage = ( { value }: { value:
|
|
118
|
-
const {
|
|
119
|
-
const imageTitle = attachment?.title || null;
|
|
173
|
+
const ItemLabelImage = ( { value }: { value: BackgroundImageOverlay } ) => {
|
|
174
|
+
const { imageTitle } = useImage( value );
|
|
120
175
|
|
|
121
176
|
return <span>{ imageTitle }</span>;
|
|
122
177
|
};
|
|
@@ -126,16 +181,16 @@ const ImageOverlayContent = () => {
|
|
|
126
181
|
|
|
127
182
|
return (
|
|
128
183
|
<PropProvider { ...propContext }>
|
|
129
|
-
<PropKeyProvider bind={ 'image
|
|
184
|
+
<PropKeyProvider bind={ 'image' }>
|
|
130
185
|
<Grid container spacing={ 1 } alignItems="center">
|
|
131
186
|
<Grid item xs={ 12 }>
|
|
132
|
-
<
|
|
187
|
+
<ImageControl
|
|
188
|
+
resolutionLabel={ __( 'Resolution', 'elementor' ) }
|
|
189
|
+
sizes={ backgroundResolutionOptions }
|
|
190
|
+
/>
|
|
133
191
|
</Grid>
|
|
134
192
|
</Grid>
|
|
135
193
|
</PropKeyProvider>
|
|
136
|
-
<PropKeyProvider bind={ 'resolution' }>
|
|
137
|
-
<BackgroundImageOverlayResolution />
|
|
138
|
-
</PropKeyProvider>
|
|
139
194
|
<PropKeyProvider bind={ 'position' }>
|
|
140
195
|
<BackgroundImageOverlayPosition />
|
|
141
196
|
</PropKeyProvider>
|
|
@@ -163,3 +218,22 @@ const deriveOverlayType = ( type: string ): OverlayType => {
|
|
|
163
218
|
|
|
164
219
|
throw new Error( `Invalid overlay type: ${ type }` );
|
|
165
220
|
};
|
|
221
|
+
|
|
222
|
+
const useImage = ( image: BackgroundImageOverlay ) => {
|
|
223
|
+
let imageTitle,
|
|
224
|
+
imageUrl: string | null = null;
|
|
225
|
+
|
|
226
|
+
const imageSrc = image?.value.image.value?.src.value;
|
|
227
|
+
const { data: attachment } = useWpMediaAttachment( imageSrc.id?.value || null );
|
|
228
|
+
|
|
229
|
+
if ( imageSrc.id ) {
|
|
230
|
+
const imageFileTypeExtension = attachment?.subtype ? `.${ attachment.subtype }` : '';
|
|
231
|
+
imageTitle = `${ attachment?.title }${ imageFileTypeExtension }` || null;
|
|
232
|
+
imageUrl = attachment?.url || null;
|
|
233
|
+
} else if ( imageSrc.url ) {
|
|
234
|
+
imageUrl = imageSrc.url.value;
|
|
235
|
+
imageTitle = imageUrl?.substring( imageUrl.lastIndexOf( '/' ) + 1 ) || null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return { imageTitle, imageUrl };
|
|
239
|
+
};
|
|
@@ -47,7 +47,7 @@ const Content = ( { anchorEl }: { anchorEl: HTMLElement | null } ) => {
|
|
|
47
47
|
|
|
48
48
|
return (
|
|
49
49
|
<PropProvider propType={ propType } value={ value } setValue={ setValue }>
|
|
50
|
-
<Stack gap={ 1.5 }>
|
|
50
|
+
<Stack gap={ 1.5 } sx={ { p: 1.5 } }>
|
|
51
51
|
<Grid container gap={ 2 } flexWrap="nowrap">
|
|
52
52
|
<Control bind="color" label={ __( 'Color', 'elementor' ) }>
|
|
53
53
|
<ColorControl
|
|
@@ -9,30 +9,34 @@ import { createControl } from '../create-control';
|
|
|
9
9
|
import { ImageMediaControl } from './image-media-control';
|
|
10
10
|
import { SelectControl } from './select-control';
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
type ImageControlProps = {
|
|
13
13
|
sizes: { label: string; value: string }[];
|
|
14
|
+
resolutionLabel?: string;
|
|
14
15
|
};
|
|
15
16
|
|
|
16
|
-
export const ImageControl = createControl(
|
|
17
|
-
|
|
17
|
+
export const ImageControl = createControl(
|
|
18
|
+
( { sizes, resolutionLabel = __( 'Image resolution', 'elementor' ) }: ImageControlProps ) => {
|
|
19
|
+
const propContext = useBoundProp( imagePropTypeUtil );
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
<
|
|
27
|
-
<Grid
|
|
28
|
-
<
|
|
21
|
+
return (
|
|
22
|
+
<PropProvider { ...propContext }>
|
|
23
|
+
<Stack gap={ 1.5 }>
|
|
24
|
+
<PropKeyProvider bind={ 'src' }>
|
|
25
|
+
<ControlLabel> { __( 'Choose image', 'elementor' ) } </ControlLabel>
|
|
26
|
+
<ImageMediaControl />
|
|
27
|
+
</PropKeyProvider>
|
|
28
|
+
<PropKeyProvider bind={ 'size' }>
|
|
29
|
+
<Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
|
|
30
|
+
<Grid item xs={ 6 }>
|
|
31
|
+
<ControlLabel> { resolutionLabel } </ControlLabel>
|
|
32
|
+
</Grid>
|
|
33
|
+
<Grid item xs={ 6 }>
|
|
34
|
+
<SelectControl options={ sizes } />
|
|
35
|
+
</Grid>
|
|
29
36
|
</Grid>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
</PropProvider>
|
|
37
|
-
);
|
|
38
|
-
} );
|
|
37
|
+
</PropKeyProvider>
|
|
38
|
+
</Stack>
|
|
39
|
+
</PropProvider>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
);
|
|
@@ -10,7 +10,7 @@ import { useBoundProp } from '../bound-prop-context';
|
|
|
10
10
|
import ControlActions from '../control-actions/control-actions';
|
|
11
11
|
import { createControl } from '../create-control';
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
type ImageMediaControlProps = {
|
|
14
14
|
allowedExtensions?: ImageExtension[];
|
|
15
15
|
};
|
|
16
16
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { booleanPropTypeUtil, linkPropTypeUtil, type LinkPropValue, numberPropTypeUtil } from '@elementor/editor-props';
|
|
4
|
+
import { type HttpResponse, httpService } from '@elementor/http';
|
|
3
5
|
import { MinusIcon, PlusIcon } from '@elementor/icons';
|
|
4
6
|
import { useSessionStorage } from '@elementor/session';
|
|
5
7
|
import { Collapse, Divider, Grid, IconButton, Stack, Switch } from '@elementor/ui';
|
|
@@ -8,11 +10,21 @@ import { __ } from '@wordpress/i18n';
|
|
|
8
10
|
import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-context';
|
|
9
11
|
import { ControlLabel } from '../components/control-label';
|
|
10
12
|
import { createControl } from '../create-control';
|
|
11
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
AutocompleteControl,
|
|
15
|
+
type CategorizedOption,
|
|
16
|
+
findMatchingOption,
|
|
17
|
+
type FlatOption,
|
|
18
|
+
isCategorizedOptionPool,
|
|
19
|
+
} from './autocomplete-control';
|
|
12
20
|
|
|
13
21
|
type Props = {
|
|
14
|
-
|
|
22
|
+
queryOptions: {
|
|
23
|
+
requestParams: object;
|
|
24
|
+
endpoint: string;
|
|
25
|
+
};
|
|
15
26
|
allowCustomValues?: boolean;
|
|
27
|
+
minInputLength?: number;
|
|
16
28
|
placeholder?: string;
|
|
17
29
|
};
|
|
18
30
|
|
|
@@ -23,28 +35,81 @@ type LinkSessionValue = {
|
|
|
23
35
|
};
|
|
24
36
|
};
|
|
25
37
|
|
|
38
|
+
type Response = HttpResponse< { value: FlatOption[] | CategorizedOption[] } >;
|
|
39
|
+
|
|
26
40
|
const SIZE = 'tiny';
|
|
27
41
|
|
|
28
|
-
export const LinkControl = createControl( ( props
|
|
42
|
+
export const LinkControl = createControl( ( props: Props ) => {
|
|
29
43
|
const { value, path, setValue, ...propContext } = useBoundProp( linkPropTypeUtil );
|
|
30
|
-
|
|
31
44
|
const [ linkSessionValue, setLinkSessionValue ] = useSessionStorage< LinkSessionValue >( path.join( '/' ) );
|
|
32
45
|
|
|
33
|
-
const {
|
|
46
|
+
const {
|
|
47
|
+
allowCustomValues,
|
|
48
|
+
queryOptions: { endpoint = '', requestParams = {} },
|
|
49
|
+
placeholder,
|
|
50
|
+
minInputLength = 2,
|
|
51
|
+
} = props || {};
|
|
52
|
+
|
|
53
|
+
const [ options, setOptions ] = useState< FlatOption[] | CategorizedOption[] >(
|
|
54
|
+
generateFirstLoadedOption( value )
|
|
55
|
+
);
|
|
34
56
|
|
|
35
57
|
const onEnabledChange = () => {
|
|
36
58
|
const { meta } = linkSessionValue ?? {};
|
|
37
59
|
const { isEnabled } = meta ?? {};
|
|
38
60
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
61
|
+
setValue( isEnabled ? null : linkSessionValue?.value ?? { destination: null } );
|
|
62
|
+
setLinkSessionValue( { value, meta: { isEnabled: ! isEnabled } } );
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const onOptionChange = ( newValue: number | null ) => {
|
|
66
|
+
const valueToSave = {
|
|
67
|
+
...value,
|
|
68
|
+
destination: { $$type: 'number', value: newValue },
|
|
69
|
+
label: {
|
|
70
|
+
$$type: 'string',
|
|
71
|
+
value: findMatchingOption( options, newValue?.toString() )?.label || '',
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
onSaveNewValue( valueToSave );
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const onTextChange = ( newValue: string | null ) => {
|
|
79
|
+
const valueToSave = {
|
|
80
|
+
...value,
|
|
81
|
+
destination: { $$type: 'url', value: newValue },
|
|
82
|
+
label: null,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
onSaveNewValue( valueToSave );
|
|
86
|
+
updateOptions( newValue );
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const onSaveNewValue = ( newValue: typeof value ) => {
|
|
90
|
+
setValue( newValue );
|
|
91
|
+
setLinkSessionValue( { ...linkSessionValue, value: newValue } );
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const updateOptions = ( newValue: string | null ) => {
|
|
95
|
+
setOptions( [] );
|
|
96
|
+
|
|
97
|
+
if ( ! newValue || ! endpoint || newValue.length < minInputLength ) {
|
|
98
|
+
return;
|
|
43
99
|
}
|
|
44
100
|
|
|
45
|
-
|
|
101
|
+
debounceFetch( newValue )();
|
|
46
102
|
};
|
|
47
103
|
|
|
104
|
+
const debounceFetch = ( newValue: string ) =>
|
|
105
|
+
debounce(
|
|
106
|
+
() =>
|
|
107
|
+
fetchOptions( endpoint, { ...requestParams, term: newValue } ).then( ( newOptions ) => {
|
|
108
|
+
setOptions( formatOptions( newOptions ) );
|
|
109
|
+
} ),
|
|
110
|
+
400
|
|
111
|
+
);
|
|
112
|
+
|
|
48
113
|
return (
|
|
49
114
|
<PropProvider { ...propContext } value={ value } setValue={ setValue }>
|
|
50
115
|
<Stack gap={ 1.5 }>
|
|
@@ -65,15 +130,18 @@ export const LinkControl = createControl( ( props?: Props ) => {
|
|
|
65
130
|
</Stack>
|
|
66
131
|
<Collapse in={ linkSessionValue?.meta?.isEnabled } timeout="auto" unmountOnExit>
|
|
67
132
|
<Stack gap={ 1.5 }>
|
|
68
|
-
<PropKeyProvider bind={ '
|
|
133
|
+
<PropKeyProvider bind={ 'destination' }>
|
|
69
134
|
<AutocompleteControl
|
|
70
|
-
allowCustomValues={ Object.keys( options ).length ? allowCustomValues : true }
|
|
71
135
|
options={ options }
|
|
72
|
-
|
|
136
|
+
allowCustomValues={ allowCustomValues }
|
|
73
137
|
placeholder={ placeholder }
|
|
138
|
+
optionRestrictedPropTypeUtil={ numberPropTypeUtil }
|
|
139
|
+
onOptionChangeCallback={ onOptionChange }
|
|
140
|
+
onTextChangeCallback={ onTextChange }
|
|
141
|
+
minInputLength={ minInputLength }
|
|
142
|
+
customValue={ value?.destination?.value?.toString() }
|
|
74
143
|
/>
|
|
75
144
|
</PropKeyProvider>
|
|
76
|
-
|
|
77
145
|
<PropKeyProvider bind={ 'isTargetBlank' }>
|
|
78
146
|
<SwitchControl />
|
|
79
147
|
</PropKeyProvider>
|
|
@@ -102,7 +170,7 @@ const ToggleIconControl = ( { enabled, onIconClick, label }: ToggleIconControlPr
|
|
|
102
170
|
const SwitchControl = () => {
|
|
103
171
|
const { value = false, setValue } = useBoundProp( booleanPropTypeUtil );
|
|
104
172
|
|
|
105
|
-
const
|
|
173
|
+
const onClick = () => {
|
|
106
174
|
setValue( ! value );
|
|
107
175
|
};
|
|
108
176
|
|
|
@@ -112,8 +180,57 @@ const SwitchControl = () => {
|
|
|
112
180
|
<ControlLabel>{ __( 'Open in new tab', 'elementor' ) }</ControlLabel>
|
|
113
181
|
</Grid>
|
|
114
182
|
<Grid item>
|
|
115
|
-
<Switch checked={ value }
|
|
183
|
+
<Switch checked={ value } onClick={ onClick } />
|
|
116
184
|
</Grid>
|
|
117
185
|
</Grid>
|
|
118
186
|
);
|
|
119
187
|
};
|
|
188
|
+
|
|
189
|
+
async function fetchOptions( ajaxUrl: string, params: object ) {
|
|
190
|
+
if ( ! params || ! ajaxUrl ) {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const { data: response } = await httpService().get< Response >( ajaxUrl, { params } );
|
|
196
|
+
|
|
197
|
+
return response.data.value;
|
|
198
|
+
} catch {
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function formatOptions( options: FlatOption[] | CategorizedOption[] ): FlatOption[] | CategorizedOption[] {
|
|
204
|
+
const compareKey = isCategorizedOptionPool( options ) ? 'groupLabel' : 'label';
|
|
205
|
+
|
|
206
|
+
return options.sort( ( a, b ) =>
|
|
207
|
+
a[ compareKey ] && b[ compareKey ] ? a[ compareKey ].localeCompare( b[ compareKey ] ) : 0
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function generateFirstLoadedOption( unionValue: LinkPropValue[ 'value' ] | null ): FlatOption[] {
|
|
212
|
+
const value = unionValue?.destination?.value;
|
|
213
|
+
const label = unionValue?.label?.value;
|
|
214
|
+
const type = unionValue?.destination?.$$type || 'url';
|
|
215
|
+
|
|
216
|
+
return value && label && type === 'number'
|
|
217
|
+
? [
|
|
218
|
+
{
|
|
219
|
+
id: value.toString(),
|
|
220
|
+
label,
|
|
221
|
+
},
|
|
222
|
+
]
|
|
223
|
+
: [];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function debounce< TArgs extends unknown[] >( fn: ( ...args: TArgs ) => unknown, timeout: number ) {
|
|
227
|
+
let timer: ReturnType< typeof setTimeout >;
|
|
228
|
+
|
|
229
|
+
return ( ...args: TArgs ) => {
|
|
230
|
+
clearTimeout( timer );
|
|
231
|
+
|
|
232
|
+
timer = setTimeout( () => {
|
|
233
|
+
fn( ...args );
|
|
234
|
+
}, timeout );
|
|
235
|
+
};
|
|
236
|
+
}
|
|
@@ -15,7 +15,7 @@ const defaultUnits: Unit[] = [ 'px', '%', 'em', 'rem', 'vw', 'vh' ];
|
|
|
15
15
|
const defaultUnit = 'px';
|
|
16
16
|
const defaultSize = NaN;
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
type SizeControlProps = {
|
|
19
19
|
placeholder?: string;
|
|
20
20
|
startIcon?: React.ReactNode;
|
|
21
21
|
units?: Unit[];
|
|
@@ -9,7 +9,7 @@ import { createControl } from '../create-control';
|
|
|
9
9
|
import { ColorControl } from './color-control';
|
|
10
10
|
import { SizeControl, type Unit } from './size-control';
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
type StrokeProps = {
|
|
13
13
|
bind: string;
|
|
14
14
|
label: string;
|
|
15
15
|
children: React.ReactNode;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { imageSrcPropTypeUtil } from '@elementor/editor-props';
|
|
3
|
+
import { UploadIcon } from '@elementor/icons';
|
|
4
|
+
import { Button, Card, CardMedia, CardOverlay, CircularProgress, Stack, styled } from '@elementor/ui';
|
|
5
|
+
import { useWpMediaAttachment, useWpMediaFrame } from '@elementor/wp-media';
|
|
6
|
+
import { __ } from '@wordpress/i18n';
|
|
7
|
+
|
|
8
|
+
import { useBoundProp } from '../bound-prop-context';
|
|
9
|
+
import { ControlLabel } from '../components/control-label';
|
|
10
|
+
import ControlActions from '../control-actions/control-actions';
|
|
11
|
+
import { createControl } from '../create-control';
|
|
12
|
+
|
|
13
|
+
const TILE_SIZE = 8;
|
|
14
|
+
const TILE_WHITE = 'transparent';
|
|
15
|
+
const TILE_BLACK = '#c1c1c1';
|
|
16
|
+
const TILES_GRADIENT_FORMULA = `linear-gradient(45deg, ${ TILE_BLACK } 25%, ${ TILE_WHITE } 0, ${ TILE_WHITE } 75%, ${ TILE_BLACK } 0, ${ TILE_BLACK })`;
|
|
17
|
+
|
|
18
|
+
const StyledCard = styled( Card )`
|
|
19
|
+
background-color: white;
|
|
20
|
+
background-image: ${ TILES_GRADIENT_FORMULA }, ${ TILES_GRADIENT_FORMULA };
|
|
21
|
+
background-size: ${ TILE_SIZE }px ${ TILE_SIZE }px;
|
|
22
|
+
background-position:
|
|
23
|
+
0 0,
|
|
24
|
+
${ TILE_SIZE / 2 }px ${ TILE_SIZE / 2 }px;
|
|
25
|
+
border: none;
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
const StyledCardMediaContainer = styled( Stack )`
|
|
29
|
+
position: relative;
|
|
30
|
+
height: 140px;
|
|
31
|
+
object-fit: contain;
|
|
32
|
+
padding: 5px;
|
|
33
|
+
justify-content: center;
|
|
34
|
+
align-items: center;
|
|
35
|
+
background-color: rgba( 255, 255, 255, 0.37 );
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
export const SvgMediaControl = createControl( () => {
|
|
39
|
+
const { value, setValue } = useBoundProp( imageSrcPropTypeUtil );
|
|
40
|
+
const { id, url } = value ?? {};
|
|
41
|
+
const { data: attachment, isFetching } = useWpMediaAttachment( id?.value || null );
|
|
42
|
+
const src = attachment?.url ?? url?.value ?? null;
|
|
43
|
+
const { open } = useWpMediaFrame( {
|
|
44
|
+
types: [ 'image/svg+xml' ],
|
|
45
|
+
allowedExtensions: [ 'svg' ],
|
|
46
|
+
multiple: false,
|
|
47
|
+
selected: id?.value || null,
|
|
48
|
+
onSelect: ( selectedAttachment ) => {
|
|
49
|
+
setValue( {
|
|
50
|
+
id: {
|
|
51
|
+
$$type: 'image-attachment-id',
|
|
52
|
+
value: selectedAttachment.id,
|
|
53
|
+
},
|
|
54
|
+
url: null,
|
|
55
|
+
} );
|
|
56
|
+
},
|
|
57
|
+
} );
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Stack gap={ 1 }>
|
|
61
|
+
<ControlLabel> { __( 'Choose SVG', 'elementor' ) } </ControlLabel>
|
|
62
|
+
<ControlActions>
|
|
63
|
+
<StyledCard variant="outlined">
|
|
64
|
+
<StyledCardMediaContainer>
|
|
65
|
+
{ isFetching ? (
|
|
66
|
+
<CircularProgress role="progressbar" />
|
|
67
|
+
) : (
|
|
68
|
+
<CardMedia
|
|
69
|
+
component="img"
|
|
70
|
+
image={ src }
|
|
71
|
+
alt={ __( 'Preview SVG', 'elementor' ) }
|
|
72
|
+
sx={ { maxHeight: '140px', width: '50px' } }
|
|
73
|
+
/>
|
|
74
|
+
) }
|
|
75
|
+
</StyledCardMediaContainer>
|
|
76
|
+
<CardOverlay
|
|
77
|
+
sx={ {
|
|
78
|
+
'&:hover': {
|
|
79
|
+
backgroundColor: 'rgba( 0, 0, 0, 0.75 )',
|
|
80
|
+
},
|
|
81
|
+
} }
|
|
82
|
+
>
|
|
83
|
+
<Stack gap={ 1 }>
|
|
84
|
+
<Button
|
|
85
|
+
size="tiny"
|
|
86
|
+
color="inherit"
|
|
87
|
+
variant="outlined"
|
|
88
|
+
onClick={ () => open( { mode: 'browse' } ) }
|
|
89
|
+
>
|
|
90
|
+
{ __( 'Select SVG', 'elementor' ) }
|
|
91
|
+
</Button>
|
|
92
|
+
<Button
|
|
93
|
+
size="tiny"
|
|
94
|
+
variant="text"
|
|
95
|
+
color="inherit"
|
|
96
|
+
startIcon={ <UploadIcon /> }
|
|
97
|
+
onClick={ () => open( { mode: 'upload' } ) }
|
|
98
|
+
>
|
|
99
|
+
{ __( 'Upload SVG', 'elementor' ) }
|
|
100
|
+
</Button>
|
|
101
|
+
</Stack>
|
|
102
|
+
</CardOverlay>
|
|
103
|
+
</StyledCard>
|
|
104
|
+
</ControlActions>
|
|
105
|
+
</Stack>
|
|
106
|
+
);
|
|
107
|
+
} );
|
|
@@ -4,11 +4,11 @@ import { type PropValue } from '@elementor/editor-props';
|
|
|
4
4
|
|
|
5
5
|
import { useBoundProp } from './bound-prop-context';
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
type ReplaceWhenParams = {
|
|
8
8
|
value: PropValue;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
type CreateControlReplacement = {
|
|
12
12
|
component: ComponentType;
|
|
13
13
|
condition: ( { value }: ReplaceWhenParams ) => boolean;
|
|
14
14
|
};
|
package/src/env.ts
ADDED
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// control types
|
|
2
|
-
export { ImageControl } from './controls/image-control';
|
|
3
2
|
export { AutocompleteControl } from './controls/autocomplete-control';
|
|
3
|
+
export { ImageControl } from './controls/image-control';
|
|
4
4
|
export { TextControl } from './controls/text-control';
|
|
5
5
|
export { TextAreaControl } from './controls/text-area-control';
|
|
6
6
|
export { SizeControl } from './controls/size-control';
|
|
@@ -16,6 +16,7 @@ export { FontFamilyControl } from './controls/font-family-control';
|
|
|
16
16
|
export { UrlControl } from './controls/url-control';
|
|
17
17
|
export { LinkControl } from './controls/link-control';
|
|
18
18
|
export { GapControl } from './controls/gap-control';
|
|
19
|
+
export { SvgMediaControl } from './controls/svg-media-control';
|
|
19
20
|
export { BackgroundControl } from './controls/background-control/background-control';
|
|
20
21
|
|
|
21
22
|
// components
|