@elementor/editor-controls 0.4.1 → 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 +29 -0
- package/dist/index.d.mts +64 -23
- package/dist/index.d.ts +64 -23
- package/dist/index.js +609 -643
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +565 -599
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -2
- package/src/bound-prop-context/errors.ts +16 -0
- package/src/bound-prop-context/index.ts +3 -0
- package/src/bound-prop-context/prop-context.tsx +57 -0
- package/src/bound-prop-context/prop-key-context.tsx +110 -0
- package/src/bound-prop-context/use-bound-prop.ts +70 -0
- package/src/components/repeater.tsx +8 -15
- package/src/controls/autocomplete-control.tsx +181 -0
- package/src/controls/background-control/background-control.tsx +19 -42
- package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +64 -50
- package/src/controls/box-shadow-repeater-control.tsx +75 -139
- package/src/controls/color-control.tsx +16 -20
- package/src/controls/equal-unequal-sizes-control.tsx +65 -139
- package/src/controls/gap-control.tsx +20 -25
- package/src/controls/image-control.tsx +19 -34
- package/src/controls/image-media-control.tsx +1 -1
- package/src/controls/link-control.tsx +89 -60
- package/src/controls/linked-dimensions-control.tsx +25 -54
- package/src/controls/number-control.tsx +1 -1
- package/src/controls/stroke-control.tsx +18 -48
- package/src/controls/url-control.tsx +4 -9
- package/src/index.ts +4 -2
- package/src/bound-prop-context.tsx +0 -67
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,9 +40,11 @@
|
|
|
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",
|
|
47
|
+
"@elementor/utils": "0.3.0",
|
|
46
48
|
"@elementor/wp-media": "0.2.3",
|
|
47
49
|
"@wordpress/i18n": "^5.13.0"
|
|
48
50
|
},
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createError } from '@elementor/utils';
|
|
2
|
+
|
|
3
|
+
export const MissingPropTypeError = createError( {
|
|
4
|
+
code: 'missing_prop_provider_prop_type',
|
|
5
|
+
message: 'Prop type is missing',
|
|
6
|
+
} );
|
|
7
|
+
|
|
8
|
+
export const UnsupportedParentError = createError( {
|
|
9
|
+
code: 'unsupported_prop_provider_prop_type',
|
|
10
|
+
message: 'Parent prop type is not supported',
|
|
11
|
+
} );
|
|
12
|
+
|
|
13
|
+
export const HookOutsideProviderError = createError( {
|
|
14
|
+
code: 'hook_outside_provider',
|
|
15
|
+
message: 'Hook used outside of provider',
|
|
16
|
+
} );
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { createContext, useContext } from 'react';
|
|
3
|
+
import { type CreateOptions, type PropKey, type PropType, type PropValue } from '@elementor/editor-props';
|
|
4
|
+
|
|
5
|
+
import { HookOutsideProviderError } from './errors';
|
|
6
|
+
|
|
7
|
+
type SetValueMeta = {
|
|
8
|
+
bind?: PropKey;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type SetValue< T > = ( value: T, options?: CreateOptions, meta?: SetValueMeta ) => void;
|
|
12
|
+
|
|
13
|
+
type PropContext< T extends PropValue, P extends PropType > = {
|
|
14
|
+
setValue: SetValue< T >;
|
|
15
|
+
value: T | null;
|
|
16
|
+
propType: P;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const PropContext = createContext< PropContext< PropValue, PropType > | null >( null );
|
|
20
|
+
|
|
21
|
+
export type PropProviderProps< T extends PropValue, P extends PropType > = React.PropsWithChildren<
|
|
22
|
+
PropContext< T, P >
|
|
23
|
+
>;
|
|
24
|
+
|
|
25
|
+
export const PropProvider = < T extends PropValue, P extends PropType >( {
|
|
26
|
+
children,
|
|
27
|
+
value,
|
|
28
|
+
setValue,
|
|
29
|
+
propType,
|
|
30
|
+
}: PropProviderProps< T, P > ) => {
|
|
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
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const usePropContext = < T extends PropValue, P extends PropType >() => {
|
|
45
|
+
const context = useContext( PropContext ) as PropContext< T, P > | null;
|
|
46
|
+
|
|
47
|
+
if ( ! context ) {
|
|
48
|
+
throw new HookOutsideProviderError( {
|
|
49
|
+
context: {
|
|
50
|
+
hook: 'usePropContext',
|
|
51
|
+
provider: 'PropProvider',
|
|
52
|
+
},
|
|
53
|
+
} );
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return context;
|
|
57
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { createContext, useContext } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
type ArrayPropType,
|
|
5
|
+
type ArrayPropValue,
|
|
6
|
+
type CreateOptions,
|
|
7
|
+
type ObjectPropType,
|
|
8
|
+
type ObjectPropValue,
|
|
9
|
+
type PropKey,
|
|
10
|
+
type PropType,
|
|
11
|
+
type PropValue,
|
|
12
|
+
} from '@elementor/editor-props';
|
|
13
|
+
|
|
14
|
+
import { HookOutsideProviderError, MissingPropTypeError, UnsupportedParentError } from './errors';
|
|
15
|
+
import { type SetValue, usePropContext } from './prop-context';
|
|
16
|
+
|
|
17
|
+
export type PropKeyContextValue< T, P > = {
|
|
18
|
+
bind: PropKey;
|
|
19
|
+
setValue: SetValue< T >;
|
|
20
|
+
value: T;
|
|
21
|
+
propType: P;
|
|
22
|
+
path: PropKey[];
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const PropKeyContext = createContext< PropKeyContextValue< PropValue, PropType > | null >( null );
|
|
26
|
+
|
|
27
|
+
type PropKeyProviderProps = React.PropsWithChildren< {
|
|
28
|
+
bind: PropKey;
|
|
29
|
+
} >;
|
|
30
|
+
|
|
31
|
+
export const PropKeyProvider = ( { children, bind }: PropKeyProviderProps ) => {
|
|
32
|
+
const { propType } = usePropContext();
|
|
33
|
+
|
|
34
|
+
if ( ! propType ) {
|
|
35
|
+
throw new MissingPropTypeError( { context: { bind } } );
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if ( propType.kind === 'array' ) {
|
|
39
|
+
return <ArrayPropKeyProvider bind={ bind }>{ children }</ArrayPropKeyProvider>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if ( propType.kind === 'object' ) {
|
|
43
|
+
return <ObjectPropKeyProvider bind={ bind }>{ children }</ObjectPropKeyProvider>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
throw new UnsupportedParentError( { context: { propType } } );
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const ObjectPropKeyProvider = ( { children, bind }: PropKeyProviderProps ) => {
|
|
50
|
+
const context = usePropContext< ObjectPropValue[ 'value' ], ObjectPropType >();
|
|
51
|
+
const { path } = useContext( PropKeyContext ) ?? {};
|
|
52
|
+
|
|
53
|
+
const setValue: SetValue< PropValue > = ( value, options, meta ) => {
|
|
54
|
+
const newValue = {
|
|
55
|
+
...context.value,
|
|
56
|
+
[ bind ]: value,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return context?.setValue( newValue, options, { ...meta, bind } );
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const value = context.value?.[ bind ];
|
|
63
|
+
|
|
64
|
+
const propType = context.propType.shape[ bind ];
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<PropKeyContext.Provider
|
|
68
|
+
value={ { ...context, value, setValue, bind, propType, path: [ ...( path ?? [] ), bind ] } }
|
|
69
|
+
>
|
|
70
|
+
{ children }
|
|
71
|
+
</PropKeyContext.Provider>
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const ArrayPropKeyProvider = ( { children, bind }: PropKeyProviderProps ) => {
|
|
76
|
+
const context = usePropContext< ArrayPropValue[ 'value' ], ArrayPropType >();
|
|
77
|
+
const { path } = useContext( PropKeyContext ) ?? {};
|
|
78
|
+
|
|
79
|
+
const setValue = ( value: PropValue, options?: CreateOptions ) => {
|
|
80
|
+
const newValue = [ ...( context.value ?? [] ) ];
|
|
81
|
+
|
|
82
|
+
newValue[ Number( bind ) ] = value;
|
|
83
|
+
|
|
84
|
+
return context?.setValue( newValue, options, { bind } );
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const value = context.value?.[ Number( bind ) ];
|
|
88
|
+
|
|
89
|
+
const propType = context.propType.item_prop_type;
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<PropKeyContext.Provider
|
|
93
|
+
value={ { ...context, value, setValue, bind, propType, path: [ ...( path ?? [] ), bind ] } }
|
|
94
|
+
>
|
|
95
|
+
{ children }
|
|
96
|
+
</PropKeyContext.Provider>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const usePropKeyContext = () => {
|
|
101
|
+
const context = useContext( PropKeyContext );
|
|
102
|
+
|
|
103
|
+
if ( ! context ) {
|
|
104
|
+
throw new HookOutsideProviderError( {
|
|
105
|
+
context: { hook: 'usePropKeyContext', provider: 'PropKeyProvider' },
|
|
106
|
+
} );
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return context;
|
|
110
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type CreateOptions,
|
|
3
|
+
type PropKey,
|
|
4
|
+
type PropType,
|
|
5
|
+
type PropTypeUtil,
|
|
6
|
+
type PropValue,
|
|
7
|
+
} from '@elementor/editor-props';
|
|
8
|
+
|
|
9
|
+
import { MissingPropTypeError } from './errors';
|
|
10
|
+
import { type SetValue } from './prop-context';
|
|
11
|
+
import { type PropKeyContextValue, usePropKeyContext } from './prop-key-context';
|
|
12
|
+
|
|
13
|
+
type UseBoundProp< TValue extends PropValue > = {
|
|
14
|
+
bind: PropKey;
|
|
15
|
+
setValue: SetValue< TValue | null >;
|
|
16
|
+
value: TValue;
|
|
17
|
+
propType: PropType;
|
|
18
|
+
path: PropKey[];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function useBoundProp< T extends PropValue = PropValue >(): PropKeyContextValue< T, PropType >;
|
|
22
|
+
|
|
23
|
+
export function useBoundProp< TKey extends string, TValue extends PropValue >(
|
|
24
|
+
propTypeUtil: PropTypeUtil< TKey, TValue >
|
|
25
|
+
): UseBoundProp< TValue >;
|
|
26
|
+
|
|
27
|
+
export function useBoundProp< TKey extends string, TValue extends PropValue >(
|
|
28
|
+
propTypeUtil?: PropTypeUtil< TKey, TValue >
|
|
29
|
+
) {
|
|
30
|
+
const propKeyContext = usePropKeyContext();
|
|
31
|
+
|
|
32
|
+
// allow using the hook without a propTypeUtil, with no modifications or validations.
|
|
33
|
+
if ( ! propTypeUtil ) {
|
|
34
|
+
return propKeyContext;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function setValue( value: TValue | null, options: CreateOptions, meta: { bind?: PropKey } ) {
|
|
38
|
+
if ( value === null ) {
|
|
39
|
+
return propKeyContext?.setValue( null, options, meta );
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return propKeyContext?.setValue( propTypeUtil?.create( value, options ), {}, meta );
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const propType = resolveUnionPropType( propKeyContext.propType, propTypeUtil.key );
|
|
46
|
+
|
|
47
|
+
const value = propTypeUtil.extract( propKeyContext.value ?? propType.default ?? null );
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
...propKeyContext,
|
|
51
|
+
setValue,
|
|
52
|
+
value,
|
|
53
|
+
propType,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// utils
|
|
58
|
+
const resolveUnionPropType = ( propType: PropType, key: string ): PropType => {
|
|
59
|
+
let resolvedPropType = propType;
|
|
60
|
+
|
|
61
|
+
if ( propType.kind === 'union' ) {
|
|
62
|
+
resolvedPropType = propType.prop_types[ key ];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if ( ! resolvedPropType ) {
|
|
66
|
+
throw new MissingPropTypeError( { context: { key } } );
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return resolvedPropType;
|
|
70
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { useRef, useState } from 'react';
|
|
3
|
+
import { type PropKey } from '@elementor/editor-props';
|
|
3
4
|
import { CopyIcon, EyeIcon, EyeOffIcon, PlusIcon, XIcon } from '@elementor/icons';
|
|
4
5
|
import {
|
|
5
6
|
bindPopover,
|
|
@@ -32,9 +33,8 @@ export type RepeaterProps< T > = {
|
|
|
32
33
|
Label: React.ComponentType< { value: T } >;
|
|
33
34
|
Icon: React.ComponentType< { value: T } >;
|
|
34
35
|
Content: React.ComponentType< {
|
|
35
|
-
value: T;
|
|
36
|
-
setValue: ( newValue: T ) => void;
|
|
37
36
|
anchorEl: AnchorEl;
|
|
37
|
+
bind: PropKey;
|
|
38
38
|
} >;
|
|
39
39
|
};
|
|
40
40
|
};
|
|
@@ -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 } /> }
|
|
@@ -99,17 +100,7 @@ export const Repeater = < T, >( {
|
|
|
99
100
|
duplicateItem={ () => duplicateRepeaterItem( index ) }
|
|
100
101
|
toggleDisableItem={ () => toggleDisableRepeaterItem( index ) }
|
|
101
102
|
>
|
|
102
|
-
{ ( props ) => (
|
|
103
|
-
<itemSettings.Content
|
|
104
|
-
{ ...props }
|
|
105
|
-
value={ value }
|
|
106
|
-
setValue={ ( newValue ) =>
|
|
107
|
-
setRepeaterValues(
|
|
108
|
-
repeaterValues.map( ( item, i ) => ( i === index ? newValue : item ) )
|
|
109
|
-
)
|
|
110
|
-
}
|
|
111
|
-
/>
|
|
112
|
-
) }
|
|
103
|
+
{ ( props ) => <itemSettings.Content { ...props } bind={ String( index ) } /> }
|
|
113
104
|
</RepeaterItem>
|
|
114
105
|
) ) }
|
|
115
106
|
</Stack>
|
|
@@ -119,6 +110,7 @@ export const Repeater = < T, >( {
|
|
|
119
110
|
|
|
120
111
|
type RepeaterItemProps = {
|
|
121
112
|
label: React.ReactNode;
|
|
113
|
+
bind: string;
|
|
122
114
|
disabled?: boolean;
|
|
123
115
|
startIcon: UnstableTagProps[ 'startIcon' ];
|
|
124
116
|
removeItem: () => void;
|
|
@@ -129,6 +121,7 @@ type RepeaterItemProps = {
|
|
|
129
121
|
|
|
130
122
|
const RepeaterItem = ( {
|
|
131
123
|
label,
|
|
124
|
+
bind,
|
|
132
125
|
disabled,
|
|
133
126
|
startIcon,
|
|
134
127
|
children,
|
|
@@ -136,7 +129,7 @@ const RepeaterItem = ( {
|
|
|
136
129
|
duplicateItem,
|
|
137
130
|
toggleDisableItem,
|
|
138
131
|
}: RepeaterItemProps ) => {
|
|
139
|
-
const popupId =
|
|
132
|
+
const popupId = `repeater-popup-${ bind }`;
|
|
140
133
|
const controlRef = useRef< HTMLElement >( null );
|
|
141
134
|
const [ anchorEl, setAnchorEl ] = useState< AnchorEl >( null );
|
|
142
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
|
+
}
|
|
@@ -1,57 +1,34 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
type BackgroundColorOverlayPropValue,
|
|
4
|
-
backgroundPropTypeUtil,
|
|
5
|
-
type ColorPropValue,
|
|
6
|
-
type PropValue,
|
|
7
|
-
} from '@elementor/editor-props';
|
|
2
|
+
import { backgroundPropTypeUtil } from '@elementor/editor-props';
|
|
8
3
|
import { Grid, Stack } from '@elementor/ui';
|
|
9
4
|
import { __ } from '@wordpress/i18n';
|
|
10
5
|
|
|
11
|
-
import {
|
|
6
|
+
import { PropKeyProvider, PropProvider, useBoundProp } from '../../bound-prop-context';
|
|
12
7
|
import { ControlLabel } from '../../components/control-label';
|
|
13
8
|
import { createControl } from '../../create-control';
|
|
14
9
|
import { ColorControl } from '../color-control';
|
|
15
10
|
import { BackgroundOverlayRepeaterControl } from './background-overlay/background-overlay-repeater-control';
|
|
16
11
|
|
|
17
|
-
type SetContextValue = ( v: PropValue ) => void;
|
|
18
|
-
|
|
19
12
|
export const BackgroundControl = createControl( () => {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
const setColor = ( newValue: ColorPropValue ) => {
|
|
23
|
-
setValue( {
|
|
24
|
-
...value,
|
|
25
|
-
color: newValue,
|
|
26
|
-
} );
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const setBackgroundColorOverlay = ( newValue: BackgroundColorOverlayPropValue ) => {
|
|
30
|
-
setValue( {
|
|
31
|
-
...value,
|
|
32
|
-
'background-overlay': newValue,
|
|
33
|
-
} );
|
|
34
|
-
};
|
|
13
|
+
const propContext = useBoundProp( backgroundPropTypeUtil );
|
|
35
14
|
|
|
36
15
|
return (
|
|
37
|
-
<
|
|
38
|
-
<
|
|
39
|
-
bind="background-overlay"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
<Grid item xs={ 6 }>
|
|
51
|
-
<ColorControl />
|
|
16
|
+
<PropProvider { ...propContext }>
|
|
17
|
+
<Stack gap={ 1.5 }>
|
|
18
|
+
<PropKeyProvider bind="background-overlay">
|
|
19
|
+
<BackgroundOverlayRepeaterControl />
|
|
20
|
+
</PropKeyProvider>
|
|
21
|
+
<PropKeyProvider bind="color">
|
|
22
|
+
<Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
|
|
23
|
+
<Grid item xs={ 6 }>
|
|
24
|
+
<ControlLabel>{ __( 'Color', 'elementor' ) }</ControlLabel>
|
|
25
|
+
</Grid>
|
|
26
|
+
<Grid item xs={ 6 }>
|
|
27
|
+
<ColorControl />
|
|
28
|
+
</Grid>
|
|
52
29
|
</Grid>
|
|
53
|
-
</
|
|
54
|
-
</
|
|
55
|
-
</
|
|
30
|
+
</PropKeyProvider>
|
|
31
|
+
</Stack>
|
|
32
|
+
</PropProvider>
|
|
56
33
|
);
|
|
57
34
|
} );
|