@elementor/editor-controls 0.8.0 → 0.10.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 +49 -0
- package/dist/index.d.mts +37 -25
- package/dist/index.d.ts +37 -25
- package/dist/index.js +609 -289
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +583 -254
- 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 +92 -46
- package/src/controls/background-control/background-overlay/types.ts +22 -0
- package/src/controls/background-control/background-overlay/use-background-tabs-history.ts +62 -0
- 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,38 +6,59 @@ import {
|
|
|
6
6
|
backgroundOverlayPropTypeUtil,
|
|
7
7
|
type PropKey,
|
|
8
8
|
} from '@elementor/editor-props';
|
|
9
|
-
import { Box, Grid, Stack, Tab, TabPanel, Tabs, UnstableColorIndicator
|
|
9
|
+
import { Box, CardMedia, Grid, Stack, Tab, TabPanel, Tabs, UnstableColorIndicator } 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
|
+
import { type BackgroundImageOverlay } from './types';
|
|
24
|
+
import { useBackgroundTabsHistory } from './use-background-tabs-history';
|
|
23
25
|
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
+
export const initialBackgroundColorOverlay: BackgroundOverlayItemPropValue = {
|
|
27
|
+
$$type: 'background-color-overlay',
|
|
28
|
+
value: '#00000033',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const getInitialBackgroundOverlay = (): BackgroundOverlayItemPropValue => ( {
|
|
26
32
|
$$type: 'background-image-overlay',
|
|
27
33
|
value: {
|
|
28
|
-
|
|
29
|
-
$$type: 'image
|
|
34
|
+
image: {
|
|
35
|
+
$$type: 'image',
|
|
30
36
|
value: {
|
|
31
|
-
|
|
32
|
-
$$type: 'image-
|
|
33
|
-
value:
|
|
37
|
+
src: {
|
|
38
|
+
$$type: 'image-src',
|
|
39
|
+
value: {
|
|
40
|
+
url: {
|
|
41
|
+
$$type: 'url',
|
|
42
|
+
value: env.background_placeholder_image,
|
|
43
|
+
},
|
|
44
|
+
id: null,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
size: {
|
|
48
|
+
$$type: 'string',
|
|
49
|
+
value: 'large',
|
|
34
50
|
},
|
|
35
51
|
},
|
|
36
52
|
},
|
|
37
53
|
},
|
|
38
|
-
};
|
|
54
|
+
} );
|
|
39
55
|
|
|
40
|
-
|
|
56
|
+
const backgroundResolutionOptions = [
|
|
57
|
+
{ label: __( 'Thumbnail - 150 x 150', 'elementor' ), value: 'thumbnail' },
|
|
58
|
+
{ label: __( 'Medium - 300 x 300', 'elementor' ), value: 'medium' },
|
|
59
|
+
{ label: __( 'Large 1024 x 1024', 'elementor' ), value: 'large' },
|
|
60
|
+
{ label: __( 'Full', 'elementor' ), value: 'full' },
|
|
61
|
+
];
|
|
41
62
|
|
|
42
63
|
export const BackgroundOverlayRepeaterControl = createControl( () => {
|
|
43
64
|
const { propType, value: overlayValues, setValue } = useBoundProp( backgroundOverlayPropTypeUtil );
|
|
@@ -52,28 +73,26 @@ export const BackgroundOverlayRepeaterControl = createControl( () => {
|
|
|
52
73
|
Icon: ItemIcon,
|
|
53
74
|
Label: ItemLabel,
|
|
54
75
|
Content: ItemContent,
|
|
55
|
-
initialValues:
|
|
76
|
+
initialValues: getInitialBackgroundOverlay(),
|
|
56
77
|
} }
|
|
57
78
|
/>
|
|
58
79
|
</PropProvider>
|
|
59
80
|
);
|
|
60
81
|
} );
|
|
61
82
|
|
|
62
|
-
const
|
|
63
|
-
<UnstableColorIndicator size="inherit" component="span" value={ value.value } />
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
const ItemContent = ( { bind, value }: { bind: PropKey; value: BackgroundOverlayItemPropValue } ) => {
|
|
83
|
+
export const ItemContent = ( { bind }: { bind: PropKey } ) => {
|
|
67
84
|
return (
|
|
68
85
|
<PropKeyProvider bind={ bind }>
|
|
69
|
-
<Content
|
|
86
|
+
<Content />
|
|
70
87
|
</PropKeyProvider>
|
|
71
88
|
);
|
|
72
89
|
};
|
|
73
90
|
|
|
74
|
-
const Content = (
|
|
75
|
-
const
|
|
76
|
-
|
|
91
|
+
const Content = () => {
|
|
92
|
+
const { getTabsProps, getTabProps, getTabPanelProps } = useBackgroundTabsHistory( {
|
|
93
|
+
image: getInitialBackgroundOverlay().value,
|
|
94
|
+
color: initialBackgroundColorOverlay.value,
|
|
95
|
+
} );
|
|
77
96
|
|
|
78
97
|
return (
|
|
79
98
|
<Box sx={ { width: '100%' } }>
|
|
@@ -83,12 +102,12 @@ const Content = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
|
|
|
83
102
|
<Tab label={ __( 'Color', 'elementor' ) } { ...getTabProps( 'color' ) } />
|
|
84
103
|
</Tabs>
|
|
85
104
|
</Box>
|
|
86
|
-
<TabPanel { ...getTabPanelProps( 'image' ) }>
|
|
105
|
+
<TabPanel sx={ { p: 1.5 } } { ...getTabPanelProps( 'image' ) }>
|
|
87
106
|
<Stack gap={ 1.5 }>
|
|
88
107
|
<ImageOverlayContent />
|
|
89
108
|
</Stack>
|
|
90
109
|
</TabPanel>
|
|
91
|
-
<TabPanel { ...getTabPanelProps( 'color' ) }>
|
|
110
|
+
<TabPanel { ...getTabPanelProps( 'color' ) } sx={ { p: 1.5 } }>
|
|
92
111
|
<Grid container spacing={ 1 } alignItems="center">
|
|
93
112
|
<Grid item xs={ 12 }>
|
|
94
113
|
<ColorControl propTypeUtil={ backgroundColorOverlayPropTypeUtil } />
|
|
@@ -99,14 +118,35 @@ const Content = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
|
|
|
99
118
|
);
|
|
100
119
|
};
|
|
101
120
|
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
121
|
+
const ItemIcon = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
|
|
122
|
+
switch ( value.$$type ) {
|
|
123
|
+
case 'background-image-overlay':
|
|
124
|
+
return <ItemIconImage value={ value as BackgroundImageOverlay } />;
|
|
125
|
+
case 'background-color-overlay':
|
|
126
|
+
return <ItemIconColor value={ value } />;
|
|
127
|
+
default:
|
|
128
|
+
return null;
|
|
107
129
|
}
|
|
108
|
-
|
|
109
|
-
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const ItemIconColor = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
|
|
133
|
+
return <UnstableColorIndicator size="inherit" component="span" value={ value.value } />;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const ItemIconImage = ( { value }: { value: BackgroundImageOverlay } ) => {
|
|
137
|
+
const { imageUrl } = useImage( value );
|
|
138
|
+
|
|
139
|
+
return <CardMedia image={ imageUrl } sx={ { height: 13, width: 13, borderRadius: '4px' } } />;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const ItemLabel = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
|
|
143
|
+
switch ( value.$$type ) {
|
|
144
|
+
case 'background-image-overlay':
|
|
145
|
+
return <ItemLabelImage value={ value as BackgroundImageOverlay } />;
|
|
146
|
+
case 'background-color-overlay':
|
|
147
|
+
return <ItemLabelColor value={ value } />;
|
|
148
|
+
default:
|
|
149
|
+
return null;
|
|
110
150
|
}
|
|
111
151
|
};
|
|
112
152
|
|
|
@@ -114,9 +154,8 @@ const ItemLabelColor = ( { value }: { value: BackgroundOverlayItemPropValue } )
|
|
|
114
154
|
return <span>{ value.value }</span>;
|
|
115
155
|
};
|
|
116
156
|
|
|
117
|
-
const ItemLabelImage = ( { value }: { value:
|
|
118
|
-
const {
|
|
119
|
-
const imageTitle = attachment?.title || null;
|
|
157
|
+
const ItemLabelImage = ( { value }: { value: BackgroundImageOverlay } ) => {
|
|
158
|
+
const { imageTitle } = useImage( value );
|
|
120
159
|
|
|
121
160
|
return <span>{ imageTitle }</span>;
|
|
122
161
|
};
|
|
@@ -126,16 +165,16 @@ const ImageOverlayContent = () => {
|
|
|
126
165
|
|
|
127
166
|
return (
|
|
128
167
|
<PropProvider { ...propContext }>
|
|
129
|
-
<PropKeyProvider bind={ 'image
|
|
168
|
+
<PropKeyProvider bind={ 'image' }>
|
|
130
169
|
<Grid container spacing={ 1 } alignItems="center">
|
|
131
170
|
<Grid item xs={ 12 }>
|
|
132
|
-
<
|
|
171
|
+
<ImageControl
|
|
172
|
+
resolutionLabel={ __( 'Resolution', 'elementor' ) }
|
|
173
|
+
sizes={ backgroundResolutionOptions }
|
|
174
|
+
/>
|
|
133
175
|
</Grid>
|
|
134
176
|
</Grid>
|
|
135
177
|
</PropKeyProvider>
|
|
136
|
-
<PropKeyProvider bind={ 'resolution' }>
|
|
137
|
-
<BackgroundImageOverlayResolution />
|
|
138
|
-
</PropKeyProvider>
|
|
139
178
|
<PropKeyProvider bind={ 'position' }>
|
|
140
179
|
<BackgroundImageOverlayPosition />
|
|
141
180
|
</PropKeyProvider>
|
|
@@ -152,14 +191,21 @@ const ImageOverlayContent = () => {
|
|
|
152
191
|
);
|
|
153
192
|
};
|
|
154
193
|
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
194
|
+
const useImage = ( image: BackgroundImageOverlay ) => {
|
|
195
|
+
let imageTitle,
|
|
196
|
+
imageUrl: string | null = null;
|
|
197
|
+
|
|
198
|
+
const imageSrc = image?.value.image.value?.src.value;
|
|
199
|
+
const { data: attachment } = useWpMediaAttachment( imageSrc.id?.value || null );
|
|
159
200
|
|
|
160
|
-
if (
|
|
161
|
-
|
|
201
|
+
if ( imageSrc.id ) {
|
|
202
|
+
const imageFileTypeExtension = attachment?.subtype ? `.${ attachment.subtype }` : '';
|
|
203
|
+
imageTitle = `${ attachment?.title }${ imageFileTypeExtension }` || null;
|
|
204
|
+
imageUrl = attachment?.url || null;
|
|
205
|
+
} else if ( imageSrc.url ) {
|
|
206
|
+
imageUrl = imageSrc.url.value;
|
|
207
|
+
imageTitle = imageUrl?.substring( imageUrl.lastIndexOf( '/' ) + 1 ) || null;
|
|
162
208
|
}
|
|
163
209
|
|
|
164
|
-
|
|
210
|
+
return { imageTitle, imageUrl };
|
|
165
211
|
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
type ImageSrcAttachment = { id: { $$type: 'image-attachment-id'; value: number }; url: null };
|
|
2
|
+
|
|
3
|
+
type ImageSrcUrl = { url: { $$type: 'url'; value: string }; id: null };
|
|
4
|
+
|
|
5
|
+
export type BackgroundImageOverlay = {
|
|
6
|
+
$$type: 'background-image-overlay';
|
|
7
|
+
value: {
|
|
8
|
+
image: {
|
|
9
|
+
$$type: 'image';
|
|
10
|
+
value: {
|
|
11
|
+
src: {
|
|
12
|
+
$$type: 'image-src';
|
|
13
|
+
value: ImageSrcAttachment | ImageSrcUrl;
|
|
14
|
+
};
|
|
15
|
+
size: {
|
|
16
|
+
$$type: 'string';
|
|
17
|
+
value: 'thumbnail' | 'medium' | 'large' | 'full';
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { useRef } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
backgroundColorOverlayPropTypeUtil,
|
|
4
|
+
backgroundImageOverlayPropTypeUtil,
|
|
5
|
+
type BackgroundOverlayItemPropValue,
|
|
6
|
+
} from '@elementor/editor-props';
|
|
7
|
+
import { useTabs } from '@elementor/ui';
|
|
8
|
+
|
|
9
|
+
import { useBoundProp } from '../../../bound-prop-context';
|
|
10
|
+
import { type BackgroundImageOverlay } from './types';
|
|
11
|
+
|
|
12
|
+
type OverlayType = 'image' | 'color';
|
|
13
|
+
|
|
14
|
+
type InitialBackgroundValues = {
|
|
15
|
+
color: BackgroundOverlayItemPropValue[ 'value' ];
|
|
16
|
+
image: BackgroundImageOverlay[ 'value' ];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const useBackgroundTabsHistory = ( {
|
|
20
|
+
color: initialBackgroundColorOverlay,
|
|
21
|
+
image: initialBackgroundImageOverlay,
|
|
22
|
+
}: InitialBackgroundValues ) => {
|
|
23
|
+
const { value: imageValue, setValue: setImageValue } = useBoundProp( backgroundImageOverlayPropTypeUtil );
|
|
24
|
+
const { value: colorValue, setValue: setColorValue } = useBoundProp( backgroundColorOverlayPropTypeUtil );
|
|
25
|
+
|
|
26
|
+
const { getTabsProps, getTabProps, getTabPanelProps } = useTabs< OverlayType >( colorValue ? 'color' : 'image' );
|
|
27
|
+
|
|
28
|
+
const valuesHistory = useRef< InitialBackgroundValues >( {
|
|
29
|
+
image: initialBackgroundImageOverlay,
|
|
30
|
+
color: initialBackgroundColorOverlay,
|
|
31
|
+
} );
|
|
32
|
+
|
|
33
|
+
const saveToHistory = ( key: keyof InitialBackgroundValues, value: BackgroundOverlayItemPropValue[ 'value' ] ) => {
|
|
34
|
+
if ( value ) {
|
|
35
|
+
valuesHistory.current[ key ] = value;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const onTabChange = ( e: React.SyntheticEvent, tabName: OverlayType ) => {
|
|
40
|
+
switch ( tabName ) {
|
|
41
|
+
case 'image':
|
|
42
|
+
setImageValue( valuesHistory.current.image );
|
|
43
|
+
|
|
44
|
+
saveToHistory( 'color', colorValue );
|
|
45
|
+
|
|
46
|
+
break;
|
|
47
|
+
|
|
48
|
+
case 'color':
|
|
49
|
+
setColorValue( valuesHistory.current.color );
|
|
50
|
+
|
|
51
|
+
saveToHistory( 'image', imageValue );
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return getTabsProps().onChange( e, tabName );
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
getTabProps,
|
|
59
|
+
getTabPanelProps,
|
|
60
|
+
getTabsProps: () => ( { ...getTabsProps(), onChange: onTabChange } ),
|
|
61
|
+
};
|
|
62
|
+
};
|
|
@@ -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;
|