@elementor/editor-controls 0.5.0 → 0.6.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 +17 -0
- package/dist/index.d.mts +37 -13
- package/dist/index.d.ts +37 -13
- package/dist/index.js +390 -212
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +331 -147
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
- package/src/bound-prop-context/prop-context.tsx +11 -2
- package/src/bound-prop-context/prop-key-context.tsx +9 -2
- package/src/bound-prop-context/use-bound-prop.ts +1 -0
- package/src/components/repeater.tsx +5 -2
- package/src/controls/autocomplete-control.tsx +181 -0
- package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +32 -2
- package/src/controls/link-control.tsx +56 -26
- package/src/controls/url-control.tsx +7 -2
- package/src/index.ts +1 -0
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.6.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Elementor Team",
|
|
7
7
|
"homepage": "https://elementor.com/",
|
|
@@ -40,8 +40,9 @@
|
|
|
40
40
|
"dev": "tsup --config=../../tsup.dev.ts"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@elementor/editor-props": "0.
|
|
43
|
+
"@elementor/editor-props": "0.7.0",
|
|
44
44
|
"@elementor/icons": "^1.20.0",
|
|
45
|
+
"@elementor/session": "0.1.0",
|
|
45
46
|
"@elementor/ui": "^1.22.0",
|
|
46
47
|
"@elementor/utils": "0.3.0",
|
|
47
48
|
"@elementor/wp-media": "0.2.3",
|
|
@@ -28,8 +28,17 @@ export const PropProvider = < T extends PropValue, P extends PropType >( {
|
|
|
28
28
|
setValue,
|
|
29
29
|
propType,
|
|
30
30
|
}: PropProviderProps< T, P > ) => {
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
return (
|
|
32
|
+
<PropContext.Provider
|
|
33
|
+
value={ {
|
|
34
|
+
value,
|
|
35
|
+
propType,
|
|
36
|
+
setValue: setValue as SetValue< PropValue >,
|
|
37
|
+
} }
|
|
38
|
+
>
|
|
39
|
+
{ children }
|
|
40
|
+
</PropContext.Provider>
|
|
41
|
+
);
|
|
33
42
|
};
|
|
34
43
|
|
|
35
44
|
export const usePropContext = < T extends PropValue, P extends PropType >() => {
|
|
@@ -19,6 +19,7 @@ export type PropKeyContextValue< T, P > = {
|
|
|
19
19
|
setValue: SetValue< T >;
|
|
20
20
|
value: T;
|
|
21
21
|
propType: P;
|
|
22
|
+
path: PropKey[];
|
|
22
23
|
};
|
|
23
24
|
|
|
24
25
|
export const PropKeyContext = createContext< PropKeyContextValue< PropValue, PropType > | null >( null );
|
|
@@ -47,6 +48,7 @@ export const PropKeyProvider = ( { children, bind }: PropKeyProviderProps ) => {
|
|
|
47
48
|
|
|
48
49
|
const ObjectPropKeyProvider = ( { children, bind }: PropKeyProviderProps ) => {
|
|
49
50
|
const context = usePropContext< ObjectPropValue[ 'value' ], ObjectPropType >();
|
|
51
|
+
const { path } = useContext( PropKeyContext ) ?? {};
|
|
50
52
|
|
|
51
53
|
const setValue: SetValue< PropValue > = ( value, options, meta ) => {
|
|
52
54
|
const newValue = {
|
|
@@ -62,7 +64,9 @@ const ObjectPropKeyProvider = ( { children, bind }: PropKeyProviderProps ) => {
|
|
|
62
64
|
const propType = context.propType.shape[ bind ];
|
|
63
65
|
|
|
64
66
|
return (
|
|
65
|
-
<PropKeyContext.Provider
|
|
67
|
+
<PropKeyContext.Provider
|
|
68
|
+
value={ { ...context, value, setValue, bind, propType, path: [ ...( path ?? [] ), bind ] } }
|
|
69
|
+
>
|
|
66
70
|
{ children }
|
|
67
71
|
</PropKeyContext.Provider>
|
|
68
72
|
);
|
|
@@ -70,6 +74,7 @@ const ObjectPropKeyProvider = ( { children, bind }: PropKeyProviderProps ) => {
|
|
|
70
74
|
|
|
71
75
|
const ArrayPropKeyProvider = ( { children, bind }: PropKeyProviderProps ) => {
|
|
72
76
|
const context = usePropContext< ArrayPropValue[ 'value' ], ArrayPropType >();
|
|
77
|
+
const { path } = useContext( PropKeyContext ) ?? {};
|
|
73
78
|
|
|
74
79
|
const setValue = ( value: PropValue, options?: CreateOptions ) => {
|
|
75
80
|
const newValue = [ ...( context.value ?? [] ) ];
|
|
@@ -84,7 +89,9 @@ const ArrayPropKeyProvider = ( { children, bind }: PropKeyProviderProps ) => {
|
|
|
84
89
|
const propType = context.propType.item_prop_type;
|
|
85
90
|
|
|
86
91
|
return (
|
|
87
|
-
<PropKeyContext.Provider
|
|
92
|
+
<PropKeyContext.Provider
|
|
93
|
+
value={ { ...context, value, setValue, bind, propType, path: [ ...( path ?? [] ), bind ] } }
|
|
94
|
+
>
|
|
88
95
|
{ children }
|
|
89
96
|
</PropKeyContext.Provider>
|
|
90
97
|
);
|
|
@@ -15,6 +15,7 @@ type UseBoundProp< TValue extends PropValue > = {
|
|
|
15
15
|
setValue: SetValue< TValue | null >;
|
|
16
16
|
value: TValue;
|
|
17
17
|
propType: PropType;
|
|
18
|
+
path: PropKey[];
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
export function useBoundProp< T extends PropValue = PropValue >(): PropKeyContextValue< T, PropType >;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { useRef, useState } from 'react';
|
|
3
3
|
import { type PropKey } from '@elementor/editor-props';
|
|
4
4
|
import { CopyIcon, EyeIcon, EyeOffIcon, PlusIcon, XIcon } from '@elementor/icons';
|
|
5
5
|
import {
|
|
@@ -92,6 +92,7 @@ export const Repeater = < T, >( {
|
|
|
92
92
|
{ repeaterValues.map( ( value, index ) => (
|
|
93
93
|
<RepeaterItem
|
|
94
94
|
key={ index }
|
|
95
|
+
bind={ String( index ) }
|
|
95
96
|
disabled={ value.disabled }
|
|
96
97
|
label={ <itemSettings.Label value={ value } /> }
|
|
97
98
|
startIcon={ <itemSettings.Icon value={ value } /> }
|
|
@@ -109,6 +110,7 @@ export const Repeater = < T, >( {
|
|
|
109
110
|
|
|
110
111
|
type RepeaterItemProps = {
|
|
111
112
|
label: React.ReactNode;
|
|
113
|
+
bind: string;
|
|
112
114
|
disabled?: boolean;
|
|
113
115
|
startIcon: UnstableTagProps[ 'startIcon' ];
|
|
114
116
|
removeItem: () => void;
|
|
@@ -119,6 +121,7 @@ type RepeaterItemProps = {
|
|
|
119
121
|
|
|
120
122
|
const RepeaterItem = ( {
|
|
121
123
|
label,
|
|
124
|
+
bind,
|
|
122
125
|
disabled,
|
|
123
126
|
startIcon,
|
|
124
127
|
children,
|
|
@@ -126,7 +129,7 @@ const RepeaterItem = ( {
|
|
|
126
129
|
duplicateItem,
|
|
127
130
|
toggleDisableItem,
|
|
128
131
|
}: RepeaterItemProps ) => {
|
|
129
|
-
const popupId =
|
|
132
|
+
const popupId = `repeater-popup-${ bind }`;
|
|
130
133
|
const controlRef = useRef< HTMLElement >( null );
|
|
131
134
|
const [ anchorEl, setAnchorEl ] = useState< AnchorEl >( null );
|
|
132
135
|
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { stringPropTypeUtil, type urlPropTypeUtil } from '@elementor/editor-props';
|
|
4
|
+
import { XIcon } from '@elementor/icons';
|
|
5
|
+
import {
|
|
6
|
+
Autocomplete,
|
|
7
|
+
type AutocompleteRenderInputParams,
|
|
8
|
+
Box,
|
|
9
|
+
IconButton,
|
|
10
|
+
InputAdornment,
|
|
11
|
+
TextField,
|
|
12
|
+
} from '@elementor/ui';
|
|
13
|
+
|
|
14
|
+
import { useBoundProp } from '../bound-prop-context';
|
|
15
|
+
import ControlActions from '../control-actions/control-actions';
|
|
16
|
+
import { createControl } from '../create-control';
|
|
17
|
+
|
|
18
|
+
export type Option = {
|
|
19
|
+
label: string;
|
|
20
|
+
groupLabel?: never;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type GroupedOption = {
|
|
24
|
+
label: string;
|
|
25
|
+
groupLabel: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type Props = {
|
|
29
|
+
options: Record< string, Option > | Record< string, GroupedOption >;
|
|
30
|
+
allowCustomValues?: boolean;
|
|
31
|
+
placeholder?: string;
|
|
32
|
+
propType?: typeof urlPropTypeUtil | typeof stringPropTypeUtil;
|
|
33
|
+
minInputLength?: number;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const AutocompleteControl = createControl(
|
|
37
|
+
( {
|
|
38
|
+
options,
|
|
39
|
+
placeholder = '',
|
|
40
|
+
allowCustomValues = false,
|
|
41
|
+
propType = stringPropTypeUtil,
|
|
42
|
+
minInputLength = 2,
|
|
43
|
+
}: Props ) => {
|
|
44
|
+
const { value = '', setValue } = useBoundProp( propType );
|
|
45
|
+
const [ inputValue, setInputValue ] = useState(
|
|
46
|
+
value && options[ value ]?.label ? options[ value ]?.label : value
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const hasSelectedValue = !! (
|
|
50
|
+
inputValue &&
|
|
51
|
+
( options[ inputValue ] || Object.values( options ).find( ( { label } ) => label === inputValue ) )
|
|
52
|
+
);
|
|
53
|
+
const allowClear = !! inputValue;
|
|
54
|
+
const formattedOptions = Object.keys( options );
|
|
55
|
+
|
|
56
|
+
const handleChange = ( _?: React.SyntheticEvent | null, newValue: string | null = null ) => {
|
|
57
|
+
const formattedInputValue = newValue && options[ newValue ]?.label ? options[ newValue ]?.label : newValue;
|
|
58
|
+
|
|
59
|
+
setInputValue( formattedInputValue || '' );
|
|
60
|
+
|
|
61
|
+
if ( ! allowCustomValues && newValue && ! options[ newValue ] ) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
setValue( newValue );
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const filterOptions = () => {
|
|
69
|
+
const formattedValue = inputValue?.toLowerCase() || '';
|
|
70
|
+
|
|
71
|
+
if ( formattedValue.length < minInputLength ) {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return formattedOptions.filter(
|
|
76
|
+
( optionValue ) =>
|
|
77
|
+
optionValue.toLowerCase().indexOf( formattedValue ) !== -1 ||
|
|
78
|
+
options[ optionValue ].label.toLowerCase().indexOf( formattedValue ) !== -1
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const isOptionEqualToValue = () => {
|
|
83
|
+
return muiWarningPreventer() ? undefined : () => true;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Prevents MUI warning when freeSolo/allowCustomValues is false
|
|
87
|
+
const muiWarningPreventer = () => allowCustomValues || !! filterOptions().length;
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<ControlActions>
|
|
91
|
+
<Autocomplete
|
|
92
|
+
forcePopupIcon={ false }
|
|
93
|
+
disableClearable={ true } // Disabled component's auto clear icon to use our custom one instead
|
|
94
|
+
freeSolo={ muiWarningPreventer() }
|
|
95
|
+
value={ inputValue || '' }
|
|
96
|
+
size={ 'tiny' }
|
|
97
|
+
onChange={ handleChange }
|
|
98
|
+
onInputChange={ handleChange }
|
|
99
|
+
onBlur={ allowCustomValues ? undefined : () => handleChange( null, value ) }
|
|
100
|
+
readOnly={ hasSelectedValue }
|
|
101
|
+
options={ formattedOptions }
|
|
102
|
+
getOptionKey={ ( option ) => option }
|
|
103
|
+
getOptionLabel={ ( option ) => options[ option ]?.label ?? option }
|
|
104
|
+
groupBy={
|
|
105
|
+
shouldGroupOptions( options ) ? ( option: string ) => options[ option ]?.groupLabel : undefined
|
|
106
|
+
}
|
|
107
|
+
isOptionEqualToValue={ isOptionEqualToValue() }
|
|
108
|
+
filterOptions={ filterOptions }
|
|
109
|
+
renderOption={ ( optionProps, option ) => (
|
|
110
|
+
<Box component="li" { ...optionProps } key={ optionProps.id }>
|
|
111
|
+
{ options[ option ]?.label ?? option }
|
|
112
|
+
</Box>
|
|
113
|
+
) }
|
|
114
|
+
renderInput={ ( params ) => (
|
|
115
|
+
<TextInput
|
|
116
|
+
params={ params }
|
|
117
|
+
handleChange={ handleChange }
|
|
118
|
+
allowClear={ allowClear }
|
|
119
|
+
placeholder={ placeholder }
|
|
120
|
+
hasSelectedValue={ hasSelectedValue }
|
|
121
|
+
/>
|
|
122
|
+
) }
|
|
123
|
+
/>
|
|
124
|
+
</ControlActions>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const TextInput = ( {
|
|
130
|
+
params,
|
|
131
|
+
allowClear,
|
|
132
|
+
placeholder,
|
|
133
|
+
handleChange,
|
|
134
|
+
hasSelectedValue,
|
|
135
|
+
}: {
|
|
136
|
+
params: AutocompleteRenderInputParams;
|
|
137
|
+
allowClear: boolean;
|
|
138
|
+
handleChange: ( _?: React.SyntheticEvent | null, newValue?: string | null ) => void;
|
|
139
|
+
placeholder: string;
|
|
140
|
+
hasSelectedValue: boolean;
|
|
141
|
+
} ) => {
|
|
142
|
+
return (
|
|
143
|
+
<TextField
|
|
144
|
+
{ ...params }
|
|
145
|
+
placeholder={ placeholder }
|
|
146
|
+
sx={ {
|
|
147
|
+
'& .MuiInputBase-input': {
|
|
148
|
+
cursor: hasSelectedValue ? 'default' : undefined,
|
|
149
|
+
},
|
|
150
|
+
} }
|
|
151
|
+
InputProps={ {
|
|
152
|
+
...params.InputProps,
|
|
153
|
+
endAdornment: <ClearButton params={ params } allowClear={ allowClear } handleChange={ handleChange } />,
|
|
154
|
+
} }
|
|
155
|
+
/>
|
|
156
|
+
);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const ClearButton = ( {
|
|
160
|
+
allowClear,
|
|
161
|
+
handleChange,
|
|
162
|
+
params,
|
|
163
|
+
}: {
|
|
164
|
+
params: AutocompleteRenderInputParams;
|
|
165
|
+
allowClear: boolean;
|
|
166
|
+
handleChange: ( _?: React.SyntheticEvent | null, newValue?: string | null ) => void;
|
|
167
|
+
} ) => (
|
|
168
|
+
<InputAdornment position="end">
|
|
169
|
+
{ allowClear && (
|
|
170
|
+
<IconButton size={ params.size } onClick={ handleChange } sx={ { cursor: 'pointer' } }>
|
|
171
|
+
<XIcon fontSize={ params.size } />
|
|
172
|
+
</IconButton>
|
|
173
|
+
) }
|
|
174
|
+
</InputAdornment>
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
function shouldGroupOptions(
|
|
178
|
+
options: Record< string, Option | GroupedOption >
|
|
179
|
+
): options is Record< string, GroupedOption > {
|
|
180
|
+
return Object.values( options ).every( ( option ) => 'groupLabel' in option );
|
|
181
|
+
}
|
package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import {
|
|
3
3
|
backgroundColorOverlayPropTypeUtil,
|
|
4
|
+
backgroundImageOverlayPropTypeUtil,
|
|
4
5
|
type BackgroundOverlayItemPropValue,
|
|
5
6
|
backgroundOverlayPropTypeUtil,
|
|
6
7
|
type PropKey,
|
|
7
8
|
} from '@elementor/editor-props';
|
|
8
9
|
import { Grid, Stack, UnstableColorIndicator } from '@elementor/ui';
|
|
10
|
+
import { useWpMediaAttachment } from '@elementor/wp-media';
|
|
9
11
|
import { __ } from '@wordpress/i18n';
|
|
10
12
|
|
|
11
13
|
import { PropKeyProvider, PropProvider, useBoundProp } from '../../../bound-prop-context';
|
|
@@ -13,6 +15,7 @@ import { ControlLabel } from '../../../components/control-label';
|
|
|
13
15
|
import { Repeater } from '../../../components/repeater';
|
|
14
16
|
import { createControl } from '../../../create-control';
|
|
15
17
|
import { ColorControl } from '../../color-control';
|
|
18
|
+
import { ImageMediaControl } from '../../image-media-control';
|
|
16
19
|
|
|
17
20
|
const initialBackgroundOverlay: BackgroundOverlayItemPropValue = {
|
|
18
21
|
$$type: 'background-color-overlay',
|
|
@@ -52,6 +55,8 @@ const ItemContent = ( { bind }: { bind: PropKey } ) => {
|
|
|
52
55
|
};
|
|
53
56
|
|
|
54
57
|
const Content = () => {
|
|
58
|
+
const propContext = useBoundProp( backgroundImageOverlayPropTypeUtil );
|
|
59
|
+
|
|
55
60
|
return (
|
|
56
61
|
<Stack gap={ 1.5 }>
|
|
57
62
|
<Grid container spacing={ 1 } alignItems="center">
|
|
@@ -62,12 +67,37 @@ const Content = () => {
|
|
|
62
67
|
<ColorControl propTypeUtil={ backgroundColorOverlayPropTypeUtil } />
|
|
63
68
|
</Grid>
|
|
64
69
|
</Grid>
|
|
70
|
+
<PropProvider { ...propContext }>
|
|
71
|
+
<PropKeyProvider bind={ 'image-src' }>
|
|
72
|
+
<Grid container spacing={ 1 } alignItems="center">
|
|
73
|
+
<Grid item xs={ 12 }>
|
|
74
|
+
<ImageMediaControl />
|
|
75
|
+
</Grid>
|
|
76
|
+
</Grid>
|
|
77
|
+
</PropKeyProvider>
|
|
78
|
+
</PropProvider>
|
|
65
79
|
</Stack>
|
|
66
80
|
);
|
|
67
81
|
};
|
|
68
82
|
|
|
69
83
|
const ItemLabel = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
|
|
70
|
-
const
|
|
84
|
+
const type = value.$$type;
|
|
85
|
+
|
|
86
|
+
if ( type === 'background-color-overlay' ) {
|
|
87
|
+
return <ItemLabelColor value={ value } />;
|
|
88
|
+
}
|
|
89
|
+
if ( type === 'background-image-overlay' ) {
|
|
90
|
+
return <ItemLabelImage value={ value } />;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const ItemLabelColor = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
|
|
95
|
+
return <span>{ value.value }</span>;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const ItemLabelImage = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
|
|
99
|
+
const { data: attachment } = useWpMediaAttachment( value?.value[ 'image-src' ]?.value.id.value || null );
|
|
100
|
+
const imageTitle = attachment?.title || null;
|
|
71
101
|
|
|
72
|
-
return <span>{
|
|
102
|
+
return <span>{ imageTitle }</span>;
|
|
73
103
|
};
|
|
@@ -1,30 +1,52 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { linkPropTypeUtil, type LinkPropValue } from '@elementor/editor-props';
|
|
2
|
+
import { booleanPropTypeUtil, linkPropTypeUtil, type LinkPropValue, stringPropTypeUtil } from '@elementor/editor-props';
|
|
3
3
|
import { MinusIcon, PlusIcon } from '@elementor/icons';
|
|
4
|
+
import { useSessionStorage } from '@elementor/session';
|
|
4
5
|
import { Collapse, Divider, Grid, IconButton, Stack, Switch } from '@elementor/ui';
|
|
5
6
|
import { __ } from '@wordpress/i18n';
|
|
6
7
|
|
|
7
8
|
import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-context';
|
|
8
9
|
import { ControlLabel } from '../components/control-label';
|
|
9
10
|
import { createControl } from '../create-control';
|
|
10
|
-
import {
|
|
11
|
+
import { AutocompleteControl, type GroupedOption, type Option } from './autocomplete-control';
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
type Props = {
|
|
14
|
+
options?: Record< string, Option > | Record< string, GroupedOption >;
|
|
15
|
+
allowCustomValues?: boolean;
|
|
16
|
+
placeholder?: string;
|
|
17
|
+
};
|
|
13
18
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
},
|
|
20
|
-
isTargetBlank: false,
|
|
19
|
+
type LinkSessionValue = {
|
|
20
|
+
value?: LinkPropValue[ 'value' ];
|
|
21
|
+
meta?: {
|
|
22
|
+
isEnabled?: boolean;
|
|
23
|
+
};
|
|
21
24
|
};
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
const SIZE = 'tiny';
|
|
27
|
+
|
|
28
|
+
export const LinkControl = createControl( ( props?: Props ) => {
|
|
29
|
+
const { value, path, setValue, ...propContext } = useBoundProp( linkPropTypeUtil );
|
|
30
|
+
|
|
31
|
+
const [ linkSessionValue, setLinkSessionValue ] = useSessionStorage< LinkSessionValue >( path.join( '/' ) );
|
|
32
|
+
|
|
33
|
+
const { allowCustomValues = false, options = {}, placeholder } = props || {};
|
|
34
|
+
|
|
35
|
+
const onEnabledChange = () => {
|
|
36
|
+
const { meta } = linkSessionValue ?? {};
|
|
37
|
+
const { isEnabled } = meta ?? {};
|
|
38
|
+
|
|
39
|
+
if ( isEnabled && value ) {
|
|
40
|
+
setValue( null );
|
|
41
|
+
} else if ( linkSessionValue?.value ) {
|
|
42
|
+
setValue( linkSessionValue?.value ?? null );
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
setLinkSessionValue( { value, meta: { isEnabled: ! isEnabled } } );
|
|
46
|
+
};
|
|
25
47
|
|
|
26
48
|
return (
|
|
27
|
-
<PropProvider { ...propContext } value={ value }>
|
|
49
|
+
<PropProvider { ...propContext } value={ value } setValue={ setValue }>
|
|
28
50
|
<Stack gap={ 1.5 }>
|
|
29
51
|
<Divider />
|
|
30
52
|
<Stack
|
|
@@ -35,14 +57,21 @@ export const LinkControl = createControl( () => {
|
|
|
35
57
|
} }
|
|
36
58
|
>
|
|
37
59
|
<ControlLabel>{ __( 'Link', 'elementor' ) }</ControlLabel>
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
60
|
+
<ToggleIconControl
|
|
61
|
+
enabled={ linkSessionValue?.meta?.isEnabled ?? false }
|
|
62
|
+
onIconClick={ onEnabledChange }
|
|
63
|
+
label={ __( 'Toggle Link', 'elementor' ) }
|
|
64
|
+
/>
|
|
41
65
|
</Stack>
|
|
42
|
-
<Collapse in={
|
|
66
|
+
<Collapse in={ linkSessionValue?.meta?.isEnabled } timeout="auto" unmountOnExit>
|
|
43
67
|
<Stack gap={ 1.5 }>
|
|
44
68
|
<PropKeyProvider bind={ 'href' }>
|
|
45
|
-
<
|
|
69
|
+
<AutocompleteControl
|
|
70
|
+
allowCustomValues={ Object.keys( options ).length ? allowCustomValues : true }
|
|
71
|
+
options={ options }
|
|
72
|
+
propType={ stringPropTypeUtil }
|
|
73
|
+
placeholder={ placeholder }
|
|
74
|
+
/>
|
|
46
75
|
</PropKeyProvider>
|
|
47
76
|
|
|
48
77
|
<PropKeyProvider bind={ 'isTargetBlank' }>
|
|
@@ -55,22 +84,23 @@ export const LinkControl = createControl( () => {
|
|
|
55
84
|
);
|
|
56
85
|
} );
|
|
57
86
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
87
|
+
type ToggleIconControlProps = {
|
|
88
|
+
enabled: boolean;
|
|
89
|
+
onIconClick: () => void;
|
|
90
|
+
label?: string;
|
|
91
|
+
};
|
|
63
92
|
|
|
93
|
+
const ToggleIconControl = ( { enabled, onIconClick, label }: ToggleIconControlProps ) => {
|
|
64
94
|
return (
|
|
65
|
-
<IconButton size={ SIZE } onClick={
|
|
66
|
-
{
|
|
95
|
+
<IconButton size={ SIZE } onClick={ onIconClick } aria-label={ label }>
|
|
96
|
+
{ enabled ? <MinusIcon fontSize={ SIZE } /> : <PlusIcon fontSize={ SIZE } /> }
|
|
67
97
|
</IconButton>
|
|
68
98
|
);
|
|
69
99
|
};
|
|
70
100
|
|
|
71
101
|
// @TODO Should be refactored in ED-16323
|
|
72
102
|
const SwitchControl = () => {
|
|
73
|
-
const { value = false, setValue } = useBoundProp();
|
|
103
|
+
const { value = false, setValue } = useBoundProp( booleanPropTypeUtil );
|
|
74
104
|
|
|
75
105
|
const onChange = () => {
|
|
76
106
|
setValue( ! value );
|
|
@@ -8,12 +8,17 @@ import { createControl } from '../create-control';
|
|
|
8
8
|
|
|
9
9
|
export const UrlControl = createControl( ( { placeholder }: { placeholder?: string } ) => {
|
|
10
10
|
const { value, setValue } = useBoundProp( urlPropTypeUtil );
|
|
11
|
-
|
|
12
11
|
const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => setValue( event.target.value );
|
|
13
12
|
|
|
14
13
|
return (
|
|
15
14
|
<ControlActions>
|
|
16
|
-
<TextField
|
|
15
|
+
<TextField
|
|
16
|
+
size="tiny"
|
|
17
|
+
fullWidth
|
|
18
|
+
value={ value ?? '' }
|
|
19
|
+
onChange={ handleChange }
|
|
20
|
+
placeholder={ placeholder }
|
|
21
|
+
/>
|
|
17
22
|
</ControlActions>
|
|
18
23
|
);
|
|
19
24
|
} );
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// control types
|
|
2
2
|
export { ImageControl } from './controls/image-control';
|
|
3
|
+
export { AutocompleteControl } from './controls/autocomplete-control';
|
|
3
4
|
export { TextControl } from './controls/text-control';
|
|
4
5
|
export { TextAreaControl } from './controls/text-area-control';
|
|
5
6
|
export { SizeControl } from './controls/size-control';
|