@elementor/editor-controls 0.16.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 +33 -0
- package/dist/index.d.mts +19 -6
- package/dist/index.d.ts +19 -6
- package/dist/index.js +340 -167
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +344 -160
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -5
- 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 +1 -1
- package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +38 -10
- 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 +7 -2
- 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,7 +40,9 @@
|
|
|
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
48
|
"@elementor/icons": "1.37.0",
|
|
@@ -48,9 +50,9 @@
|
|
|
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
|
};
|
|
@@ -90,7 +90,7 @@ export const initialBackgroundGradientOverlay: BackgroundOverlayItemPropValue =
|
|
|
90
90
|
angle: numberPropTypeUtil.create( 180 ),
|
|
91
91
|
stops: gradientColorStopPropTypeUtil.create( [
|
|
92
92
|
colorStopPropTypeUtil.create( {
|
|
93
|
-
color:
|
|
93
|
+
color: colorPropTypeUtil.create( 'rgb(0,0,0)' ),
|
|
94
94
|
offset: numberPropTypeUtil.create( 0 ),
|
|
95
95
|
} ),
|
|
96
96
|
colorStopPropTypeUtil.create( {
|
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';
|
|
@@ -29,10 +30,13 @@ import { BackgroundImageOverlaySize } from './background-image-overlay/backgroun
|
|
|
29
30
|
import { type BackgroundImageOverlay } from './types';
|
|
30
31
|
import { useBackgroundTabsHistory } from './use-background-tabs-history';
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
+
);
|
|
36
40
|
|
|
37
41
|
export const getInitialBackgroundOverlay = (): BackgroundOverlayItemPropValue => ( {
|
|
38
42
|
$$type: 'background-image-overlay',
|
|
@@ -72,6 +76,7 @@ export const BackgroundOverlayRepeaterControl = createControl( () => {
|
|
|
72
76
|
return (
|
|
73
77
|
<PropProvider propType={ propType } value={ overlayValues } setValue={ setValue }>
|
|
74
78
|
<Repeater
|
|
79
|
+
openOnAdd
|
|
75
80
|
values={ overlayValues ?? [] }
|
|
76
81
|
setValues={ setValue }
|
|
77
82
|
label={ __( 'Overlay', 'elementor' ) }
|
|
@@ -118,8 +123,10 @@ const Content = () => {
|
|
|
118
123
|
<TabPanel sx={ { p: 1.5 } } { ...getTabPanelProps( 'gradient' ) }>
|
|
119
124
|
<BackgroundGradientColorControl />
|
|
120
125
|
</TabPanel>
|
|
121
|
-
<TabPanel
|
|
122
|
-
<
|
|
126
|
+
<TabPanel sx={ { p: 1.5 } } { ...getTabPanelProps( 'color' ) }>
|
|
127
|
+
<PopoverContent>
|
|
128
|
+
<ColorOverlayContent />
|
|
129
|
+
</PopoverContent>
|
|
123
130
|
</TabPanel>
|
|
124
131
|
</Box>
|
|
125
132
|
);
|
|
@@ -138,8 +145,17 @@ const ItemIcon = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
|
|
|
138
145
|
}
|
|
139
146
|
};
|
|
140
147
|
|
|
141
|
-
const
|
|
142
|
-
|
|
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 } />;
|
|
143
159
|
};
|
|
144
160
|
|
|
145
161
|
const ItemIconImage = ( { value }: { value: BackgroundImageOverlay } ) => {
|
|
@@ -167,8 +183,9 @@ const ItemLabel = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
|
|
|
167
183
|
}
|
|
168
184
|
};
|
|
169
185
|
|
|
170
|
-
const ItemLabelColor = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
|
|
171
|
-
|
|
186
|
+
const ItemLabelColor = ( { value: prop }: { value: BackgroundOverlayItemPropValue } ) => {
|
|
187
|
+
const color = extractColorFrom( prop );
|
|
188
|
+
return <span>{ color }</span>;
|
|
172
189
|
};
|
|
173
190
|
|
|
174
191
|
const ItemLabelImage = ( { value }: { value: BackgroundImageOverlay } ) => {
|
|
@@ -185,6 +202,17 @@ const ItemLabelGradient = ( { value }: { value: BackgroundOverlayItemPropValue }
|
|
|
185
202
|
return <span>{ __( 'Radial Gradient', 'elementor' ) }</span>;
|
|
186
203
|
};
|
|
187
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
|
+
|
|
188
216
|
const ImageOverlayContent = () => {
|
|
189
217
|
const propContext = useBoundProp( backgroundImageOverlayPropTypeUtil );
|
|
190
218
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { boxShadowPropTypeUtil, type PropKey, shadowPropTypeUtil, type ShadowPropValue } from '@elementor/editor-props';
|
|
3
|
-
import { Grid, Typography, UnstableColorIndicator } from '@elementor/ui';
|
|
3
|
+
import { Grid, type SxProps, type Theme, Typography, UnstableColorIndicator } from '@elementor/ui';
|
|
4
4
|
import { __ } from '@wordpress/i18n';
|
|
5
5
|
|
|
6
6
|
import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-context';
|
|
@@ -18,6 +18,7 @@ export const BoxShadowRepeaterControl = createControl( () => {
|
|
|
18
18
|
return (
|
|
19
19
|
<PropProvider propType={ propType } value={ value } setValue={ setValue }>
|
|
20
20
|
<Repeater
|
|
21
|
+
openOnAdd
|
|
21
22
|
values={ value ?? [] }
|
|
22
23
|
setValues={ setValue }
|
|
23
24
|
label={ __( 'Box shadow', 'elementor' ) }
|
|
@@ -68,7 +69,7 @@ const Content = ( { anchorEl }: { anchorEl: HTMLElement | null } ) => {
|
|
|
68
69
|
} }
|
|
69
70
|
/>
|
|
70
71
|
</Control>
|
|
71
|
-
<Control bind="position" label={ __( 'Position', 'elementor' ) }>
|
|
72
|
+
<Control bind="position" label={ __( 'Position', 'elementor' ) } sx={ { overflow: 'hidden' } }>
|
|
72
73
|
<SelectControl
|
|
73
74
|
options={ [
|
|
74
75
|
{ label: __( 'Inset', 'elementor' ), value: 'inset' },
|
|
@@ -98,9 +99,19 @@ const Content = ( { anchorEl }: { anchorEl: HTMLElement | null } ) => {
|
|
|
98
99
|
);
|
|
99
100
|
};
|
|
100
101
|
|
|
101
|
-
const Control = ( {
|
|
102
|
+
const Control = ( {
|
|
103
|
+
label,
|
|
104
|
+
bind,
|
|
105
|
+
children,
|
|
106
|
+
sx,
|
|
107
|
+
}: {
|
|
108
|
+
bind: string;
|
|
109
|
+
label: string;
|
|
110
|
+
children: React.ReactNode;
|
|
111
|
+
sx?: SxProps< Theme >;
|
|
112
|
+
} ) => (
|
|
102
113
|
<PropKeyProvider bind={ bind }>
|
|
103
|
-
<Grid item xs={ 6 } sx={
|
|
114
|
+
<Grid item xs={ 6 } sx={ sx }>
|
|
104
115
|
<Grid container gap={ 1 } alignItems="center">
|
|
105
116
|
<Grid item xs={ 12 }>
|
|
106
117
|
<Typography component="label" variant="caption" color="text.secondary">
|
|
@@ -144,8 +144,8 @@ export function EqualUnequalSizesControl< TMultiPropType extends string, TPropVa
|
|
|
144
144
|
<MultiSizeValueControl item={ items[ 1 ] } />
|
|
145
145
|
</PopoverGridContainer>
|
|
146
146
|
<PopoverGridContainer>
|
|
147
|
-
<MultiSizeValueControl item={ items[ 3 ] } />
|
|
148
147
|
<MultiSizeValueControl item={ items[ 2 ] } />
|
|
148
|
+
<MultiSizeValueControl item={ items[ 3 ] } />
|
|
149
149
|
</PopoverGridContainer>
|
|
150
150
|
</PopoverContent>
|
|
151
151
|
</PropProvider>
|
|
@@ -31,8 +31,13 @@ import { enqueueFont } from './enqueue-font';
|
|
|
31
31
|
|
|
32
32
|
const SIZE = 'tiny';
|
|
33
33
|
|
|
34
|
+
export type FontCategory = {
|
|
35
|
+
label: string;
|
|
36
|
+
fonts: string[];
|
|
37
|
+
};
|
|
38
|
+
|
|
34
39
|
type FontFamilyControlProps = {
|
|
35
|
-
fontFamilies:
|
|
40
|
+
fontFamilies: FontCategory[];
|
|
36
41
|
};
|
|
37
42
|
|
|
38
43
|
export const FontFamilyControl = createControl( ( { fontFamilies }: FontFamilyControlProps ) => {
|
|
@@ -278,7 +283,7 @@ const StyledMenuList = styled( MenuList )( ( { theme } ) => ( {
|
|
|
278
283
|
'& > [role="option"]': {
|
|
279
284
|
...theme.typography.caption,
|
|
280
285
|
lineHeight: 'inherit',
|
|
281
|
-
padding: theme.spacing( 0.75, 2 ),
|
|
286
|
+
padding: theme.spacing( 0.75, 2, 0.75, 4 ),
|
|
282
287
|
'&:hover, &:focus': {
|
|
283
288
|
backgroundColor: theme.palette.action.hover,
|
|
284
289
|
},
|
|
@@ -26,8 +26,10 @@ import {
|
|
|
26
26
|
import { ControlLabel } from '../components/control-label';
|
|
27
27
|
import ControlActions from '../control-actions/control-actions';
|
|
28
28
|
import { createControl } from '../create-control';
|
|
29
|
+
import { getLinkRestriction } from '../utils/link-restriction';
|
|
30
|
+
import { type ControlProps } from '../utils/types';
|
|
29
31
|
|
|
30
|
-
type Props = {
|
|
32
|
+
type Props = ControlProps< {
|
|
31
33
|
queryOptions: {
|
|
32
34
|
requestParams: Record< string, unknown >;
|
|
33
35
|
endpoint: string;
|
|
@@ -35,7 +37,7 @@ type Props = {
|
|
|
35
37
|
allowCustomValues?: boolean;
|
|
36
38
|
minInputLength?: number;
|
|
37
39
|
placeholder?: string;
|
|
38
|
-
}
|
|
40
|
+
} >;
|
|
39
41
|
|
|
40
42
|
type LinkSessionValue = {
|
|
41
43
|
value?: LinkPropValue[ 'value' ] | null;
|
|
@@ -58,6 +60,7 @@ export const LinkControl = createControl( ( props: Props ) => {
|
|
|
58
60
|
queryOptions: { endpoint = '', requestParams = {} },
|
|
59
61
|
placeholder,
|
|
60
62
|
minInputLength = 2,
|
|
63
|
+
context: { elementId },
|
|
61
64
|
} = props || {};
|
|
62
65
|
|
|
63
66
|
const [ options, setOptions ] = useState< FlatOption[] | CategorizedOption[] >(
|
|
@@ -65,6 +68,12 @@ export const LinkControl = createControl( ( props: Props ) => {
|
|
|
65
68
|
);
|
|
66
69
|
|
|
67
70
|
const onEnabledChange = () => {
|
|
71
|
+
const { shouldRestrict } = getLinkRestriction( elementId );
|
|
72
|
+
|
|
73
|
+
if ( shouldRestrict && ! isEnabled ) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
68
77
|
setIsEnabled( ( prevState ) => ! prevState );
|
|
69
78
|
setValue( isEnabled ? null : linkSessionValue?.value ?? null );
|
|
70
79
|
setLinkSessionValue( { value, meta: { isEnabled: ! isEnabled } } );
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
+
import { useState } from 'react';
|
|
2
3
|
import { imageSrcPropTypeUtil } from '@elementor/editor-props';
|
|
3
4
|
import { UploadIcon } from '@elementor/icons';
|
|
4
5
|
import { Button, Card, CardMedia, CardOverlay, CircularProgress, Stack, styled } from '@elementor/ui';
|
|
@@ -7,6 +8,7 @@ import { __ } from '@wordpress/i18n';
|
|
|
7
8
|
|
|
8
9
|
import { useBoundProp } from '../bound-prop-context';
|
|
9
10
|
import { ControlLabel } from '../components/control-label';
|
|
11
|
+
import { EnableUnfilteredModal } from '../components/enable-unfiltered-modal';
|
|
10
12
|
import ControlActions from '../control-actions/control-actions';
|
|
11
13
|
import { createControl } from '../create-control';
|
|
12
14
|
import { useUnfilteredFilesUpload } from '../hooks/use-unfiltered-files-upload';
|
|
@@ -36,12 +38,16 @@ const StyledCardMediaContainer = styled( Stack )`
|
|
|
36
38
|
background-color: rgba( 255, 255, 255, 0.37 );
|
|
37
39
|
`;
|
|
38
40
|
|
|
41
|
+
const MODE_BROWSE: OpenOptions = { mode: 'browse' };
|
|
42
|
+
const MODE_UPLOAD: OpenOptions = { mode: 'upload' };
|
|
43
|
+
|
|
39
44
|
export const SvgMediaControl = createControl( () => {
|
|
40
45
|
const { value, setValue } = useBoundProp( imageSrcPropTypeUtil );
|
|
41
46
|
const { id, url } = value ?? {};
|
|
42
47
|
const { data: attachment, isFetching } = useWpMediaAttachment( id?.value || null );
|
|
43
48
|
const src = attachment?.url ?? url?.value ?? null;
|
|
44
49
|
const { data: allowSvgUpload } = useUnfilteredFilesUpload();
|
|
50
|
+
const [ unfilteredModalOpenState, setUnfilteredModalOpenState ] = useState( false );
|
|
45
51
|
|
|
46
52
|
const { open } = useWpMediaFrame( {
|
|
47
53
|
mediaTypes: [ 'svg' ],
|
|
@@ -58,16 +64,25 @@ export const SvgMediaControl = createControl( () => {
|
|
|
58
64
|
},
|
|
59
65
|
} );
|
|
60
66
|
|
|
67
|
+
const onCloseUnfilteredModal = ( enabled: boolean ) => {
|
|
68
|
+
setUnfilteredModalOpenState( false );
|
|
69
|
+
|
|
70
|
+
if ( enabled ) {
|
|
71
|
+
open( MODE_UPLOAD );
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
61
75
|
const handleClick = ( openOptions?: OpenOptions ) => {
|
|
62
|
-
if ( allowSvgUpload ) {
|
|
63
|
-
|
|
76
|
+
if ( ! allowSvgUpload && openOptions === MODE_UPLOAD ) {
|
|
77
|
+
setUnfilteredModalOpenState( true );
|
|
64
78
|
} else {
|
|
65
|
-
|
|
79
|
+
open( openOptions );
|
|
66
80
|
}
|
|
67
81
|
};
|
|
68
82
|
|
|
69
83
|
return (
|
|
70
84
|
<Stack gap={ 1 }>
|
|
85
|
+
<EnableUnfilteredModal open={ unfilteredModalOpenState } onClose={ onCloseUnfilteredModal } />
|
|
71
86
|
<ControlLabel> { __( 'SVG', 'elementor' ) } </ControlLabel>
|
|
72
87
|
<ControlActions>
|
|
73
88
|
<StyledCard variant="outlined">
|
|
@@ -95,7 +110,7 @@ export const SvgMediaControl = createControl( () => {
|
|
|
95
110
|
size="tiny"
|
|
96
111
|
color="inherit"
|
|
97
112
|
variant="outlined"
|
|
98
|
-
onClick={ () => handleClick(
|
|
113
|
+
onClick={ () => handleClick( MODE_BROWSE ) }
|
|
99
114
|
>
|
|
100
115
|
{ __( 'Select SVG', 'elementor' ) }
|
|
101
116
|
</Button>
|
|
@@ -104,7 +119,7 @@ export const SvgMediaControl = createControl( () => {
|
|
|
104
119
|
variant="text"
|
|
105
120
|
color="inherit"
|
|
106
121
|
startIcon={ <UploadIcon /> }
|
|
107
|
-
onClick={ () => handleClick(
|
|
122
|
+
onClick={ () => handleClick( MODE_UPLOAD ) }
|
|
108
123
|
>
|
|
109
124
|
{ __( 'Upload', 'elementor' ) }
|
|
110
125
|
</Button>
|