@elementor/editor-controls 0.15.0 → 0.17.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 +45 -0
- package/dist/index.d.mts +19 -6
- package/dist/index.d.ts +19 -6
- package/dist/index.js +478 -178
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +484 -165
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -6
- package/src/components/control-toggle-button-group.tsx +5 -0
- package/src/components/enable-unfiltered-modal.tsx +130 -0
- package/src/components/repeater.tsx +39 -10
- package/src/components/sortable.tsx +2 -7
- package/src/controls/background-control/background-gradient-color-control.tsx +101 -0
- package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +80 -14
- package/src/controls/background-control/background-overlay/use-background-tabs-history.ts +29 -2
- package/src/controls/box-shadow-repeater-control.tsx +15 -4
- package/src/controls/equal-unequal-sizes-control.tsx +1 -1
- package/src/controls/font-family-control/font-family-control.tsx +35 -14
- package/src/controls/link-control.tsx +11 -2
- package/src/controls/svg-media-control.tsx +20 -5
- package/src/controls/toggle-control.tsx +36 -10
- package/src/hooks/use-filtered-font-families.ts +15 -16
- package/src/index.ts +2 -0
- package/src/utils/link-restriction.ts +47 -0
- package/src/utils/types.ts +5 -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.17.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Elementor Team",
|
|
7
7
|
"homepage": "https://elementor.com/",
|
|
@@ -40,17 +40,19 @@
|
|
|
40
40
|
"dev": "tsup --config=../../tsup.dev.ts"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@elementor/editor-
|
|
43
|
+
"@elementor/editor-current-user": "0.3.0",
|
|
44
|
+
"@elementor/editor-elements": "0.6.3",
|
|
45
|
+
"@elementor/editor-props": "0.11.0",
|
|
44
46
|
"@elementor/env": "0.3.5",
|
|
45
47
|
"@elementor/http": "0.1.4",
|
|
46
|
-
"@elementor/icons": "1.
|
|
48
|
+
"@elementor/icons": "1.37.0",
|
|
47
49
|
"@elementor/query": "0.2.4",
|
|
48
50
|
"@elementor/session": "0.1.0",
|
|
49
51
|
"@elementor/ui": "1.26.0",
|
|
50
52
|
"@elementor/utils": "0.4.0",
|
|
51
|
-
"@elementor/wp-media": "0.
|
|
52
|
-
"@
|
|
53
|
-
"@
|
|
53
|
+
"@elementor/wp-media": "0.6.0",
|
|
54
|
+
"@tanstack/react-virtual": "3.13.3",
|
|
55
|
+
"@wordpress/i18n": "^5.13.0"
|
|
54
56
|
},
|
|
55
57
|
"devDependencies": {
|
|
56
58
|
"tsup": "^8.3.5"
|
|
@@ -43,6 +43,8 @@ type Props< TValue > = {
|
|
|
43
43
|
}
|
|
44
44
|
);
|
|
45
45
|
|
|
46
|
+
// The maximum number of buttons that can be displayed in the group
|
|
47
|
+
const MAX_VISIBLE_ITEMS = 4;
|
|
46
48
|
export const ControlToggleButtonGroup = < TValue, >( {
|
|
47
49
|
justify = 'end',
|
|
48
50
|
size = 'tiny',
|
|
@@ -69,6 +71,9 @@ export const ControlToggleButtonGroup = < TValue, >( {
|
|
|
69
71
|
exclusive={ exclusive }
|
|
70
72
|
sx={ {
|
|
71
73
|
direction: isRtl ? 'rtl /* @noflip */' : 'ltr /* @noflip */',
|
|
74
|
+
display: 'grid',
|
|
75
|
+
gridTemplateColumns: `repeat(${ items.length }, 1fr)`,
|
|
76
|
+
width: `${ ( items.length / MAX_VISIBLE_ITEMS ) * 100 }%`,
|
|
72
77
|
} }
|
|
73
78
|
>
|
|
74
79
|
{ items.map( ( { label, value: buttonValue, renderContent: Content, showTooltip } ) =>
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { useCurrentUserCapabilities } from '@elementor/editor-current-user';
|
|
4
|
+
import {
|
|
5
|
+
Button,
|
|
6
|
+
CircularProgress,
|
|
7
|
+
Dialog,
|
|
8
|
+
DialogActions,
|
|
9
|
+
DialogContent,
|
|
10
|
+
DialogContentText,
|
|
11
|
+
DialogHeader,
|
|
12
|
+
DialogTitle,
|
|
13
|
+
Divider,
|
|
14
|
+
} from '@elementor/ui';
|
|
15
|
+
import { __ } from '@wordpress/i18n';
|
|
16
|
+
|
|
17
|
+
import { useUpdateUnfilteredFilesUpload } from '../hooks/use-unfiltered-files-upload';
|
|
18
|
+
|
|
19
|
+
type EnableUnfilteredModalProps = {
|
|
20
|
+
open: boolean;
|
|
21
|
+
onClose: ( enabled: boolean ) => void;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type LocalModalProps = {
|
|
25
|
+
open: boolean;
|
|
26
|
+
onClose: ( enabled: boolean ) => void;
|
|
27
|
+
isPending?: boolean;
|
|
28
|
+
isError?: boolean;
|
|
29
|
+
handleEnable: () => void;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const ADMIN_TITLE_TEXT = __( 'Enable Unfiltered Uploads', 'elementor' );
|
|
33
|
+
const ADMIN_CONTENT_TEXT = __(
|
|
34
|
+
'Before you enable unfiltered files upload, note that such files include a security risk. Elementor does run a process to remove possible malicious code, but there is still risk involved when using such files.',
|
|
35
|
+
'elementor'
|
|
36
|
+
);
|
|
37
|
+
const NON_ADMIN_TITLE_TEXT = __( "Sorry, you can't upload that file yet", 'elementor' );
|
|
38
|
+
const NON_ADMIN_CONTENT_TEXT = __(
|
|
39
|
+
'This is because this file type may pose a security risk. To upload them anyway, ask the site administrator to enable unfiltered file uploads.',
|
|
40
|
+
'elementor'
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const ADMIN_FAILED_CONTENT_TEXT_PT1 = __( 'Failed to enable unfiltered files upload.', 'elementor' );
|
|
44
|
+
|
|
45
|
+
const ADMIN_FAILED_CONTENT_TEXT_PT2 = __(
|
|
46
|
+
'You can try again, if the problem persists, please contact support.',
|
|
47
|
+
'elementor'
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const WAIT_FOR_CLOSE_TIMEOUT_MS = 300;
|
|
51
|
+
|
|
52
|
+
export const EnableUnfilteredModal = ( props: EnableUnfilteredModalProps ) => {
|
|
53
|
+
const { mutateAsync, isPending } = useUpdateUnfilteredFilesUpload();
|
|
54
|
+
const { canUser } = useCurrentUserCapabilities();
|
|
55
|
+
const [ isError, setIsError ] = useState( false );
|
|
56
|
+
const canManageOptions = canUser( 'manage_options' );
|
|
57
|
+
|
|
58
|
+
const onClose = ( enabled: boolean ) => {
|
|
59
|
+
props.onClose( enabled );
|
|
60
|
+
setTimeout( () => setIsError( false ), WAIT_FOR_CLOSE_TIMEOUT_MS );
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const handleEnable = async () => {
|
|
64
|
+
try {
|
|
65
|
+
const response = await mutateAsync( { allowUnfilteredFilesUpload: true } );
|
|
66
|
+
if ( response?.data?.success === false ) {
|
|
67
|
+
setIsError( true );
|
|
68
|
+
} else {
|
|
69
|
+
props.onClose( true );
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
setIsError( true );
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const dialogProps = { ...props, isPending, handleEnable, isError, onClose };
|
|
77
|
+
|
|
78
|
+
return canManageOptions ? <AdminDialog { ...dialogProps } /> : <NonAdminDialog { ...dialogProps } />;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const AdminDialog = ( { open, onClose, handleEnable, isPending, isError }: LocalModalProps ) => (
|
|
82
|
+
<Dialog open={ open } maxWidth={ 'sm' } onClose={ () => onClose( false ) }>
|
|
83
|
+
<DialogHeader logo={ false }>
|
|
84
|
+
<DialogTitle>{ ADMIN_TITLE_TEXT }</DialogTitle>
|
|
85
|
+
</DialogHeader>
|
|
86
|
+
<Divider />
|
|
87
|
+
<DialogContent>
|
|
88
|
+
<DialogContentText>
|
|
89
|
+
{ isError ? (
|
|
90
|
+
<>
|
|
91
|
+
{ ADMIN_FAILED_CONTENT_TEXT_PT1 } <br /> { ADMIN_FAILED_CONTENT_TEXT_PT2 }
|
|
92
|
+
</>
|
|
93
|
+
) : (
|
|
94
|
+
ADMIN_CONTENT_TEXT
|
|
95
|
+
) }
|
|
96
|
+
</DialogContentText>
|
|
97
|
+
</DialogContent>
|
|
98
|
+
<DialogActions>
|
|
99
|
+
<Button size={ 'medium' } color="secondary" onClick={ () => onClose( false ) }>
|
|
100
|
+
{ __( 'Cancel', 'elementor' ) }
|
|
101
|
+
</Button>
|
|
102
|
+
<Button
|
|
103
|
+
size={ 'medium' }
|
|
104
|
+
onClick={ () => handleEnable() }
|
|
105
|
+
variant="contained"
|
|
106
|
+
color="primary"
|
|
107
|
+
disabled={ isPending }
|
|
108
|
+
>
|
|
109
|
+
{ isPending ? <CircularProgress size={ 24 } /> : __( 'Enable', 'elementor' ) }
|
|
110
|
+
</Button>
|
|
111
|
+
</DialogActions>
|
|
112
|
+
</Dialog>
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const NonAdminDialog = ( { open, onClose }: LocalModalProps ) => (
|
|
116
|
+
<Dialog open={ open } maxWidth={ 'sm' } onClose={ () => onClose( false ) }>
|
|
117
|
+
<DialogHeader logo={ false }>
|
|
118
|
+
<DialogTitle>{ NON_ADMIN_TITLE_TEXT }</DialogTitle>
|
|
119
|
+
</DialogHeader>
|
|
120
|
+
<Divider />
|
|
121
|
+
<DialogContent>
|
|
122
|
+
<DialogContentText>{ NON_ADMIN_CONTENT_TEXT }</DialogContentText>
|
|
123
|
+
</DialogContent>
|
|
124
|
+
<DialogActions>
|
|
125
|
+
<Button size={ 'medium' } onClick={ () => onClose( false ) } variant="contained" color="primary">
|
|
126
|
+
{ __( 'Got it', 'elementor' ) }
|
|
127
|
+
</Button>
|
|
128
|
+
</DialogActions>
|
|
129
|
+
</Dialog>
|
|
130
|
+
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { useEffect, 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 {
|
|
@@ -33,6 +33,7 @@ type RepeaterProps< T > = {
|
|
|
33
33
|
label: string;
|
|
34
34
|
values?: T[];
|
|
35
35
|
addToBottom?: boolean;
|
|
36
|
+
openOnAdd?: boolean;
|
|
36
37
|
setValues: ( newValue: T[] ) => void;
|
|
37
38
|
itemSettings: {
|
|
38
39
|
initialValues: T;
|
|
@@ -49,10 +50,13 @@ type RepeaterProps< T > = {
|
|
|
49
50
|
export const Repeater = < T, >( {
|
|
50
51
|
label,
|
|
51
52
|
itemSettings,
|
|
53
|
+
openOnAdd = false,
|
|
52
54
|
addToBottom = false,
|
|
53
55
|
values: repeaterValues = [],
|
|
54
56
|
setValues: setRepeaterValues,
|
|
55
57
|
}: RepeaterProps< Item< T > > ) => {
|
|
58
|
+
const [ openItem, setOpenItem ] = useState( -1 );
|
|
59
|
+
|
|
56
60
|
const [ items, setItems ] = useSyncExternalState( {
|
|
57
61
|
external: repeaterValues,
|
|
58
62
|
// @ts-expect-error - as long as persistWhen => true, value will never be null
|
|
@@ -77,6 +81,10 @@ export const Repeater = < T, >( {
|
|
|
77
81
|
setItems( [ newItem, ...items ] );
|
|
78
82
|
setUniqueKeys( [ newKey, ...uniqueKeys ] );
|
|
79
83
|
}
|
|
84
|
+
|
|
85
|
+
if ( openOnAdd ) {
|
|
86
|
+
setOpenItem( newKey );
|
|
87
|
+
}
|
|
80
88
|
};
|
|
81
89
|
|
|
82
90
|
const duplicateRepeaterItem = ( index: number ) => {
|
|
@@ -158,6 +166,7 @@ export const Repeater = < T, >( {
|
|
|
158
166
|
removeItem={ () => removeRepeaterItem( index ) }
|
|
159
167
|
duplicateItem={ () => duplicateRepeaterItem( index ) }
|
|
160
168
|
toggleDisableItem={ () => toggleDisableRepeaterItem( index ) }
|
|
169
|
+
openOnMount={ openOnAdd && openItem === key }
|
|
161
170
|
>
|
|
162
171
|
{ ( props ) => (
|
|
163
172
|
<itemSettings.Content { ...props } value={ value } bind={ String( index ) } />
|
|
@@ -181,25 +190,21 @@ type RepeaterItemProps = {
|
|
|
181
190
|
duplicateItem: () => void;
|
|
182
191
|
toggleDisableItem: () => void;
|
|
183
192
|
children: ( { anchorEl }: { anchorEl: AnchorEl } ) => React.ReactNode;
|
|
193
|
+
openOnMount: boolean;
|
|
184
194
|
};
|
|
185
195
|
|
|
186
196
|
const RepeaterItem = ( {
|
|
187
197
|
label,
|
|
188
|
-
bind,
|
|
189
198
|
disabled,
|
|
190
199
|
startIcon,
|
|
191
200
|
children,
|
|
192
201
|
removeItem,
|
|
193
202
|
duplicateItem,
|
|
194
203
|
toggleDisableItem,
|
|
204
|
+
openOnMount,
|
|
195
205
|
}: RepeaterItemProps ) => {
|
|
196
|
-
const popupId = `repeater-popup-${ bind }`;
|
|
197
|
-
const controlRef = useRef< HTMLElement >( null );
|
|
198
206
|
const [ anchorEl, setAnchorEl ] = useState< AnchorEl >( null );
|
|
199
|
-
|
|
200
|
-
const popoverState = usePopupState( { popupId, variant: 'popover' } );
|
|
201
|
-
|
|
202
|
-
const popoverProps = bindPopover( popoverState );
|
|
207
|
+
const { popoverState, popoverProps, ref, setRef } = usePopover( openOnMount );
|
|
203
208
|
|
|
204
209
|
const duplicateLabel = __( 'Duplicate', 'elementor' );
|
|
205
210
|
const toggleLabel = disabled ? __( 'Show', 'elementor' ) : __( 'Hide', 'elementor' );
|
|
@@ -211,7 +216,7 @@ const RepeaterItem = ( {
|
|
|
211
216
|
label={ label }
|
|
212
217
|
showActionsOnHover
|
|
213
218
|
fullWidth
|
|
214
|
-
ref={
|
|
219
|
+
ref={ setRef }
|
|
215
220
|
variant="outlined"
|
|
216
221
|
aria-label={ __( 'Open item', 'elementor' ) }
|
|
217
222
|
{ ...bindTrigger( popoverState ) }
|
|
@@ -242,14 +247,38 @@ const RepeaterItem = ( {
|
|
|
242
247
|
slotProps={ {
|
|
243
248
|
paper: {
|
|
244
249
|
ref: setAnchorEl,
|
|
245
|
-
sx: { mt: 0.5, width:
|
|
250
|
+
sx: { mt: 0.5, width: ref?.getBoundingClientRect().width },
|
|
246
251
|
},
|
|
247
252
|
} }
|
|
248
253
|
anchorOrigin={ { vertical: 'bottom', horizontal: 'left' } }
|
|
249
254
|
{ ...popoverProps }
|
|
255
|
+
anchorEl={ ref }
|
|
250
256
|
>
|
|
251
257
|
<Box>{ children( { anchorEl } ) }</Box>
|
|
252
258
|
</Popover>
|
|
253
259
|
</>
|
|
254
260
|
);
|
|
255
261
|
};
|
|
262
|
+
|
|
263
|
+
const usePopover = ( openOnMount: boolean ) => {
|
|
264
|
+
const [ ref, setRef ] = useState< HTMLElement | null >( null );
|
|
265
|
+
|
|
266
|
+
const popoverState = usePopupState( { variant: 'popover' } );
|
|
267
|
+
|
|
268
|
+
const popoverProps = bindPopover( popoverState );
|
|
269
|
+
|
|
270
|
+
useEffect( () => {
|
|
271
|
+
if ( openOnMount && ref ) {
|
|
272
|
+
popoverState.open( ref );
|
|
273
|
+
}
|
|
274
|
+
// eslint-disable-next-line react-compiler/react-compiler
|
|
275
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
276
|
+
}, [ ref ] );
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
popoverState,
|
|
280
|
+
ref,
|
|
281
|
+
setRef,
|
|
282
|
+
popoverProps,
|
|
283
|
+
};
|
|
284
|
+
};
|
|
@@ -14,13 +14,8 @@ import {
|
|
|
14
14
|
|
|
15
15
|
export const SortableProvider = < T extends number >( props: UnstableSortableProviderProps< T > ) => {
|
|
16
16
|
return (
|
|
17
|
-
<List sx={ { p: 0,
|
|
18
|
-
<UnstableSortableProvider
|
|
19
|
-
restrictAxis={ true }
|
|
20
|
-
disableDragOverlay={ false }
|
|
21
|
-
variant={ 'static' }
|
|
22
|
-
{ ...props }
|
|
23
|
-
/>
|
|
17
|
+
<List sx={ { p: 0, my: -0.5, mx: 0 } }>
|
|
18
|
+
<UnstableSortableProvider restrictAxis disableDragOverlay={ false } variant={ 'static' } { ...props } />
|
|
24
19
|
</List>
|
|
25
20
|
);
|
|
26
21
|
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
backgroundGradientOverlayPropTypeUtil,
|
|
4
|
+
type BackgroundGradientOverlayPropValue,
|
|
5
|
+
type BackgroundOverlayItemPropValue,
|
|
6
|
+
colorPropTypeUtil,
|
|
7
|
+
type ColorPropValue,
|
|
8
|
+
colorStopPropTypeUtil,
|
|
9
|
+
gradientColorStopPropTypeUtil,
|
|
10
|
+
numberPropTypeUtil,
|
|
11
|
+
type NumberPropValue,
|
|
12
|
+
stringPropTypeUtil,
|
|
13
|
+
type TransformablePropValue,
|
|
14
|
+
} from '@elementor/editor-props';
|
|
15
|
+
import { UnstableGradientBox } from '@elementor/ui';
|
|
16
|
+
|
|
17
|
+
import { useBoundProp } from '../../bound-prop-context';
|
|
18
|
+
import ControlActions from '../../control-actions/control-actions';
|
|
19
|
+
import { createControl } from '../../create-control';
|
|
20
|
+
|
|
21
|
+
export type ColorStop = TransformablePropValue<
|
|
22
|
+
'color-stop',
|
|
23
|
+
{
|
|
24
|
+
color: ColorPropValue;
|
|
25
|
+
offset: NumberPropValue;
|
|
26
|
+
}
|
|
27
|
+
>;
|
|
28
|
+
|
|
29
|
+
export const BackgroundGradientColorControl = createControl( () => {
|
|
30
|
+
const { value, setValue } = useBoundProp( backgroundGradientOverlayPropTypeUtil );
|
|
31
|
+
|
|
32
|
+
const handleChange = ( newValue: BackgroundGradientOverlayPropValue[ 'value' ] ) => {
|
|
33
|
+
const transformedValue = createTransformableValue( newValue );
|
|
34
|
+
|
|
35
|
+
if ( transformedValue.positions ) {
|
|
36
|
+
transformedValue.positions = stringPropTypeUtil.create( newValue.positions.join( ' ' ) );
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setValue( transformedValue );
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// TODO: To support Global variables this won't be needed when we have a flexible Gradient Box
|
|
43
|
+
const createTransformableValue = ( newValue: BackgroundGradientOverlayPropValue[ 'value' ] ) => ( {
|
|
44
|
+
...newValue,
|
|
45
|
+
type: stringPropTypeUtil.create( newValue.type ),
|
|
46
|
+
angle: numberPropTypeUtil.create( newValue.angle ),
|
|
47
|
+
stops: gradientColorStopPropTypeUtil.create(
|
|
48
|
+
newValue.stops.map( ( { color, offset }: { color: string; offset: number } ) =>
|
|
49
|
+
colorStopPropTypeUtil.create( {
|
|
50
|
+
color: colorPropTypeUtil.create( color ),
|
|
51
|
+
offset: numberPropTypeUtil.create( offset ),
|
|
52
|
+
} )
|
|
53
|
+
)
|
|
54
|
+
),
|
|
55
|
+
} );
|
|
56
|
+
|
|
57
|
+
// TODO: To support Global variables this won't be needed when we have a flexible Gradient Box
|
|
58
|
+
const normalizeValue = () => {
|
|
59
|
+
if ( ! value ) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const { type, angle, stops, positions } = value;
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
type: type.value,
|
|
67
|
+
angle: angle.value,
|
|
68
|
+
stops: stops.value.map( ( { value: { color, offset } }: ColorStop ) => ( {
|
|
69
|
+
color: color.value,
|
|
70
|
+
offset: offset.value,
|
|
71
|
+
} ) ),
|
|
72
|
+
positions: positions?.value.split( ' ' ),
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<ControlActions>
|
|
78
|
+
<UnstableGradientBox
|
|
79
|
+
sx={ { width: 'auto', padding: 1.5 } }
|
|
80
|
+
value={ normalizeValue() }
|
|
81
|
+
onChange={ handleChange }
|
|
82
|
+
/>
|
|
83
|
+
</ControlActions>
|
|
84
|
+
);
|
|
85
|
+
} );
|
|
86
|
+
|
|
87
|
+
export const initialBackgroundGradientOverlay: BackgroundOverlayItemPropValue =
|
|
88
|
+
backgroundGradientOverlayPropTypeUtil.create( {
|
|
89
|
+
type: stringPropTypeUtil.create( 'linear' ),
|
|
90
|
+
angle: numberPropTypeUtil.create( 180 ),
|
|
91
|
+
stops: gradientColorStopPropTypeUtil.create( [
|
|
92
|
+
colorStopPropTypeUtil.create( {
|
|
93
|
+
color: colorPropTypeUtil.create( 'rgb(0,0,0)' ),
|
|
94
|
+
offset: numberPropTypeUtil.create( 0 ),
|
|
95
|
+
} ),
|
|
96
|
+
colorStopPropTypeUtil.create( {
|
|
97
|
+
color: colorPropTypeUtil.create( 'rgb(255,255,255)' ),
|
|
98
|
+
offset: numberPropTypeUtil.create( 100 ),
|
|
99
|
+
} ),
|
|
100
|
+
] ),
|
|
101
|
+
} );
|
package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
backgroundImageOverlayPropTypeUtil,
|
|
5
5
|
type BackgroundOverlayItemPropValue,
|
|
6
6
|
backgroundOverlayPropTypeUtil,
|
|
7
|
+
colorPropTypeUtil,
|
|
7
8
|
type PropKey,
|
|
8
9
|
} from '@elementor/editor-props';
|
|
9
10
|
import { Box, CardMedia, Grid, Tab, TabPanel, Tabs, UnstableColorIndicator } from '@elementor/ui';
|
|
@@ -17,6 +18,11 @@ import { createControl } from '../../../create-control';
|
|
|
17
18
|
import { env } from '../../../env';
|
|
18
19
|
import { ColorControl } from '../../color-control';
|
|
19
20
|
import { ImageControl } from '../../image-control';
|
|
21
|
+
import {
|
|
22
|
+
BackgroundGradientColorControl,
|
|
23
|
+
type ColorStop,
|
|
24
|
+
initialBackgroundGradientOverlay,
|
|
25
|
+
} from '../background-gradient-color-control';
|
|
20
26
|
import { BackgroundImageOverlayAttachment } from './background-image-overlay/background-image-overlay-attachment';
|
|
21
27
|
import { BackgroundImageOverlayPosition } from './background-image-overlay/background-image-overlay-position';
|
|
22
28
|
import { BackgroundImageOverlayRepeat } from './background-image-overlay/background-image-overlay-repeat';
|
|
@@ -24,10 +30,13 @@ import { BackgroundImageOverlaySize } from './background-image-overlay/backgroun
|
|
|
24
30
|
import { type BackgroundImageOverlay } from './types';
|
|
25
31
|
import { useBackgroundTabsHistory } from './use-background-tabs-history';
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
const DEFAULT_BACKGROUND_COLOR_OVERLAY_COLOR = '#00000033';
|
|
34
|
+
|
|
35
|
+
export const initialBackgroundColorOverlay: BackgroundOverlayItemPropValue = backgroundColorOverlayPropTypeUtil.create(
|
|
36
|
+
{
|
|
37
|
+
color: colorPropTypeUtil.create( DEFAULT_BACKGROUND_COLOR_OVERLAY_COLOR ),
|
|
38
|
+
}
|
|
39
|
+
);
|
|
31
40
|
|
|
32
41
|
export const getInitialBackgroundOverlay = (): BackgroundOverlayItemPropValue => ( {
|
|
33
42
|
$$type: 'background-image-overlay',
|
|
@@ -67,6 +76,7 @@ export const BackgroundOverlayRepeaterControl = createControl( () => {
|
|
|
67
76
|
return (
|
|
68
77
|
<PropProvider propType={ propType } value={ overlayValues } setValue={ setValue }>
|
|
69
78
|
<Repeater
|
|
79
|
+
openOnAdd
|
|
70
80
|
values={ overlayValues ?? [] }
|
|
71
81
|
setValues={ setValue }
|
|
72
82
|
label={ __( 'Overlay', 'elementor' ) }
|
|
@@ -93,6 +103,7 @@ const Content = () => {
|
|
|
93
103
|
const { getTabsProps, getTabProps, getTabPanelProps } = useBackgroundTabsHistory( {
|
|
94
104
|
image: getInitialBackgroundOverlay().value,
|
|
95
105
|
color: initialBackgroundColorOverlay.value,
|
|
106
|
+
gradient: initialBackgroundGradientOverlay.value,
|
|
96
107
|
} );
|
|
97
108
|
|
|
98
109
|
return (
|
|
@@ -100,6 +111,7 @@ const Content = () => {
|
|
|
100
111
|
<Box sx={ { borderBottom: 1, borderColor: 'divider' } }>
|
|
101
112
|
<Tabs { ...getTabsProps() } aria-label={ __( 'Background Overlay', 'elementor' ) }>
|
|
102
113
|
<Tab label={ __( 'Image', 'elementor' ) } { ...getTabProps( 'image' ) } />
|
|
114
|
+
<Tab label={ __( 'Gradient', 'elementor' ) } { ...getTabProps( 'gradient' ) } />
|
|
103
115
|
<Tab label={ __( 'Color', 'elementor' ) } { ...getTabProps( 'color' ) } />
|
|
104
116
|
</Tabs>
|
|
105
117
|
</Box>
|
|
@@ -108,12 +120,13 @@ const Content = () => {
|
|
|
108
120
|
<ImageOverlayContent />
|
|
109
121
|
</PopoverContent>
|
|
110
122
|
</TabPanel>
|
|
111
|
-
<TabPanel
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
123
|
+
<TabPanel sx={ { p: 1.5 } } { ...getTabPanelProps( 'gradient' ) }>
|
|
124
|
+
<BackgroundGradientColorControl />
|
|
125
|
+
</TabPanel>
|
|
126
|
+
<TabPanel sx={ { p: 1.5 } } { ...getTabPanelProps( 'color' ) }>
|
|
127
|
+
<PopoverContent>
|
|
128
|
+
<ColorOverlayContent />
|
|
129
|
+
</PopoverContent>
|
|
117
130
|
</TabPanel>
|
|
118
131
|
</Box>
|
|
119
132
|
);
|
|
@@ -125,13 +138,24 @@ const ItemIcon = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
|
|
|
125
138
|
return <ItemIconImage value={ value as BackgroundImageOverlay } />;
|
|
126
139
|
case 'background-color-overlay':
|
|
127
140
|
return <ItemIconColor value={ value } />;
|
|
141
|
+
case 'background-gradient-overlay':
|
|
142
|
+
return <ItemIconGradient value={ value } />;
|
|
128
143
|
default:
|
|
129
144
|
return null;
|
|
130
145
|
}
|
|
131
146
|
};
|
|
132
147
|
|
|
133
|
-
const
|
|
134
|
-
|
|
148
|
+
const extractColorFrom = ( prop: BackgroundOverlayItemPropValue ) => {
|
|
149
|
+
if ( prop?.value?.color?.value ) {
|
|
150
|
+
return prop.value.color.value;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return '';
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const ItemIconColor = ( { value: prop }: { value: BackgroundOverlayItemPropValue } ) => {
|
|
157
|
+
const color = extractColorFrom( prop );
|
|
158
|
+
return <UnstableColorIndicator size="inherit" component="span" value={ color } />;
|
|
135
159
|
};
|
|
136
160
|
|
|
137
161
|
const ItemIconImage = ( { value }: { value: BackgroundImageOverlay } ) => {
|
|
@@ -140,19 +164,28 @@ const ItemIconImage = ( { value }: { value: BackgroundImageOverlay } ) => {
|
|
|
140
164
|
return <CardMedia image={ imageUrl } sx={ { height: 13, width: 13, borderRadius: '4px' } } />;
|
|
141
165
|
};
|
|
142
166
|
|
|
167
|
+
const ItemIconGradient = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
|
|
168
|
+
const gradient = getGradientValue( value );
|
|
169
|
+
|
|
170
|
+
return <UnstableColorIndicator size="inherit" component="span" value={ gradient } />;
|
|
171
|
+
};
|
|
172
|
+
|
|
143
173
|
const ItemLabel = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
|
|
144
174
|
switch ( value.$$type ) {
|
|
145
175
|
case 'background-image-overlay':
|
|
146
176
|
return <ItemLabelImage value={ value as BackgroundImageOverlay } />;
|
|
147
177
|
case 'background-color-overlay':
|
|
148
178
|
return <ItemLabelColor value={ value } />;
|
|
179
|
+
case 'background-gradient-overlay':
|
|
180
|
+
return <ItemLabelGradient value={ value } />;
|
|
149
181
|
default:
|
|
150
182
|
return null;
|
|
151
183
|
}
|
|
152
184
|
};
|
|
153
185
|
|
|
154
|
-
const ItemLabelColor = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
|
|
155
|
-
|
|
186
|
+
const ItemLabelColor = ( { value: prop }: { value: BackgroundOverlayItemPropValue } ) => {
|
|
187
|
+
const color = extractColorFrom( prop );
|
|
188
|
+
return <span>{ color }</span>;
|
|
156
189
|
};
|
|
157
190
|
|
|
158
191
|
const ItemLabelImage = ( { value }: { value: BackgroundImageOverlay } ) => {
|
|
@@ -161,6 +194,25 @@ const ItemLabelImage = ( { value }: { value: BackgroundImageOverlay } ) => {
|
|
|
161
194
|
return <span>{ imageTitle }</span>;
|
|
162
195
|
};
|
|
163
196
|
|
|
197
|
+
const ItemLabelGradient = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
|
|
198
|
+
if ( value.value.type.value === 'linear' ) {
|
|
199
|
+
return <span>{ __( 'Linear Gradient', 'elementor' ) }</span>;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return <span>{ __( 'Radial Gradient', 'elementor' ) }</span>;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const ColorOverlayContent = () => {
|
|
206
|
+
const propContext = useBoundProp( backgroundColorOverlayPropTypeUtil );
|
|
207
|
+
return (
|
|
208
|
+
<PropProvider { ...propContext }>
|
|
209
|
+
<PropKeyProvider bind={ 'color' }>
|
|
210
|
+
<ColorControl />
|
|
211
|
+
</PropKeyProvider>
|
|
212
|
+
</PropProvider>
|
|
213
|
+
);
|
|
214
|
+
};
|
|
215
|
+
|
|
164
216
|
const ImageOverlayContent = () => {
|
|
165
217
|
const propContext = useBoundProp( backgroundImageOverlayPropTypeUtil );
|
|
166
218
|
|
|
@@ -210,3 +262,17 @@ const useImage = ( image: BackgroundImageOverlay ) => {
|
|
|
210
262
|
|
|
211
263
|
return { imageTitle, imageUrl };
|
|
212
264
|
};
|
|
265
|
+
|
|
266
|
+
const getGradientValue = ( value: BackgroundOverlayItemPropValue ) => {
|
|
267
|
+
const gradient = value.value;
|
|
268
|
+
|
|
269
|
+
const stops = gradient.stops.value
|
|
270
|
+
?.map( ( { value: { color, offset } }: ColorStop ) => `${ color.value } ${ offset.value ?? 0 }%` )
|
|
271
|
+
?.join( ',' );
|
|
272
|
+
|
|
273
|
+
if ( gradient.type.value === 'linear' ) {
|
|
274
|
+
return `linear-gradient(${ gradient.angle.value }deg, ${ stops })`;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return `radial-gradient(circle at ${ gradient.positions.value }, ${ stops })`;
|
|
278
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useRef } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
backgroundColorOverlayPropTypeUtil,
|
|
4
|
+
backgroundGradientOverlayPropTypeUtil,
|
|
4
5
|
backgroundImageOverlayPropTypeUtil,
|
|
5
6
|
type BackgroundOverlayItemPropValue,
|
|
6
7
|
} from '@elementor/editor-props';
|
|
@@ -9,25 +10,41 @@ import { useTabs } from '@elementor/ui';
|
|
|
9
10
|
import { useBoundProp } from '../../../bound-prop-context';
|
|
10
11
|
import { type BackgroundImageOverlay } from './types';
|
|
11
12
|
|
|
12
|
-
type OverlayType = 'image' | 'color';
|
|
13
|
+
type OverlayType = 'image' | 'gradient' | 'color';
|
|
13
14
|
|
|
14
15
|
type InitialBackgroundValues = {
|
|
15
16
|
color: BackgroundOverlayItemPropValue[ 'value' ];
|
|
16
17
|
image: BackgroundImageOverlay[ 'value' ];
|
|
18
|
+
gradient: BackgroundOverlayItemPropValue[ 'value' ];
|
|
17
19
|
};
|
|
18
20
|
|
|
19
21
|
export const useBackgroundTabsHistory = ( {
|
|
20
22
|
color: initialBackgroundColorOverlay,
|
|
21
23
|
image: initialBackgroundImageOverlay,
|
|
24
|
+
gradient: initialBackgroundGradientOverlay,
|
|
22
25
|
}: InitialBackgroundValues ) => {
|
|
23
26
|
const { value: imageValue, setValue: setImageValue } = useBoundProp( backgroundImageOverlayPropTypeUtil );
|
|
24
27
|
const { value: colorValue, setValue: setColorValue } = useBoundProp( backgroundColorOverlayPropTypeUtil );
|
|
28
|
+
const { value: gradientValue, setValue: setGradientValue } = useBoundProp( backgroundGradientOverlayPropTypeUtil );
|
|
25
29
|
|
|
26
|
-
const
|
|
30
|
+
const getCurrentOverlayType = (): OverlayType => {
|
|
31
|
+
if ( colorValue ) {
|
|
32
|
+
return 'color';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if ( gradientValue ) {
|
|
36
|
+
return 'gradient';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return 'image';
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const { getTabsProps, getTabProps, getTabPanelProps } = useTabs< OverlayType >( getCurrentOverlayType() );
|
|
27
43
|
|
|
28
44
|
const valuesHistory = useRef< InitialBackgroundValues >( {
|
|
29
45
|
image: initialBackgroundImageOverlay,
|
|
30
46
|
color: initialBackgroundColorOverlay,
|
|
47
|
+
gradient: initialBackgroundGradientOverlay,
|
|
31
48
|
} );
|
|
32
49
|
|
|
33
50
|
const saveToHistory = ( key: keyof InitialBackgroundValues, value: BackgroundOverlayItemPropValue[ 'value' ] ) => {
|
|
@@ -42,6 +59,15 @@ export const useBackgroundTabsHistory = ( {
|
|
|
42
59
|
setImageValue( valuesHistory.current.image );
|
|
43
60
|
|
|
44
61
|
saveToHistory( 'color', colorValue );
|
|
62
|
+
saveToHistory( 'gradient', gradientValue );
|
|
63
|
+
|
|
64
|
+
break;
|
|
65
|
+
|
|
66
|
+
case 'gradient':
|
|
67
|
+
setGradientValue( valuesHistory.current.gradient );
|
|
68
|
+
|
|
69
|
+
saveToHistory( 'color', colorValue );
|
|
70
|
+
saveToHistory( 'image', imageValue );
|
|
45
71
|
|
|
46
72
|
break;
|
|
47
73
|
|
|
@@ -49,6 +75,7 @@ export const useBackgroundTabsHistory = ( {
|
|
|
49
75
|
setColorValue( valuesHistory.current.color );
|
|
50
76
|
|
|
51
77
|
saveToHistory( 'image', imageValue );
|
|
78
|
+
saveToHistory( 'gradient', gradientValue );
|
|
52
79
|
}
|
|
53
80
|
|
|
54
81
|
return getTabsProps().onChange( e, tabName );
|