@elementor/editor-controls 4.3.0-beta1 → 4.3.0-beta3
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/dist/index.d.mts +25 -2
- package/dist/index.d.ts +25 -2
- package/dist/index.js +2108 -922
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1950 -751
- package/dist/index.mjs.map +1 -1
- package/package.json +17 -16
- package/src/controls/icon-library/font-awesome-7-catalog.ts +113 -0
- package/src/controls/icon-library/font-awesome-7-data.ts +260 -0
- package/src/controls/icon-library/font-awesome-glyph.tsx +30 -0
- package/src/controls/icon-library/get-icon-library-anchor.ts +29 -0
- package/src/controls/icon-library/icon-library-filter.tsx +166 -0
- package/src/controls/icon-library/icon-library-grid.tsx +344 -0
- package/src/controls/icon-library/icon-library-popover.tsx +305 -0
- package/src/controls/icon-library/icon-library-tooltip.ts +1 -0
- package/src/controls/icon-library/icon-library-view-toggle.tsx +104 -0
- package/src/controls/icon-library/use-font-awesome-7-catalog.ts +14 -0
- package/src/controls/notice-control.tsx +105 -0
- package/src/controls/svg-media-control.tsx +132 -159
- package/src/controls/svg-media-overlay.tsx +137 -0
- package/src/controls/svg-media-preview.tsx +68 -0
- package/src/controls/toggle-control.tsx +13 -2
- package/src/index.ts +7 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useId } from 'react';
|
|
3
|
+
import { CheckIcon, ListIcon, WidgetsIcon } from '@elementor/icons';
|
|
4
|
+
import {
|
|
5
|
+
bindMenu,
|
|
6
|
+
bindToggle,
|
|
7
|
+
Menu,
|
|
8
|
+
MenuItem,
|
|
9
|
+
Stack,
|
|
10
|
+
ToggleButton,
|
|
11
|
+
Tooltip,
|
|
12
|
+
Typography,
|
|
13
|
+
usePopupState,
|
|
14
|
+
} from '@elementor/ui';
|
|
15
|
+
import { __ } from '@wordpress/i18n';
|
|
16
|
+
|
|
17
|
+
import { ICON_LIBRARY_ACTION_TOOLTIP_ENTER_DELAY } from './icon-library-tooltip';
|
|
18
|
+
|
|
19
|
+
export type IconLibraryView = 'grid' | 'list';
|
|
20
|
+
|
|
21
|
+
const VIEW_MENU_WIDTH = 122;
|
|
22
|
+
|
|
23
|
+
type IconLibraryViewToggleProps = {
|
|
24
|
+
value: IconLibraryView;
|
|
25
|
+
onChange: ( value: IconLibraryView ) => void;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const IconLibraryViewToggle = ( { value, onChange }: IconLibraryViewToggleProps ) => {
|
|
29
|
+
const popupId = useId();
|
|
30
|
+
const popupState = usePopupState( {
|
|
31
|
+
variant: 'popover',
|
|
32
|
+
popupId,
|
|
33
|
+
} );
|
|
34
|
+
const isGrid = value === 'grid';
|
|
35
|
+
const ViewIcon = isGrid ? WidgetsIcon : ListIcon;
|
|
36
|
+
const viewButtonLabel = isGrid ? __( 'Grid view', 'elementor' ) : __( 'List view', 'elementor' );
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<>
|
|
40
|
+
<Tooltip
|
|
41
|
+
title={ viewButtonLabel }
|
|
42
|
+
placement="top"
|
|
43
|
+
enterDelay={ ICON_LIBRARY_ACTION_TOOLTIP_ENTER_DELAY }
|
|
44
|
+
enterNextDelay={ ICON_LIBRARY_ACTION_TOOLTIP_ENTER_DELAY }
|
|
45
|
+
disableInteractive
|
|
46
|
+
>
|
|
47
|
+
<ToggleButton
|
|
48
|
+
aria-label={ viewButtonLabel }
|
|
49
|
+
value="view"
|
|
50
|
+
size="tiny"
|
|
51
|
+
selected={ popupState.isOpen }
|
|
52
|
+
sx={ { flexShrink: 0 } }
|
|
53
|
+
{ ...bindToggle( popupState ) }
|
|
54
|
+
aria-expanded={ popupState.isOpen }
|
|
55
|
+
>
|
|
56
|
+
<ViewIcon fontSize="tiny" />
|
|
57
|
+
</ToggleButton>
|
|
58
|
+
</Tooltip>
|
|
59
|
+
<Menu
|
|
60
|
+
{ ...bindMenu( popupState ) }
|
|
61
|
+
MenuListProps={ {
|
|
62
|
+
dense: true,
|
|
63
|
+
autoFocusItem: true,
|
|
64
|
+
'aria-label': __( 'View', 'elementor' ),
|
|
65
|
+
} }
|
|
66
|
+
sx={ { '& .MuiPaper-root': { minWidth: VIEW_MENU_WIDTH } } }
|
|
67
|
+
>
|
|
68
|
+
<ViewMenuItem
|
|
69
|
+
label={ __( 'List', 'elementor' ) }
|
|
70
|
+
selected={ value === 'list' }
|
|
71
|
+
onClick={ () => {
|
|
72
|
+
onChange( 'list' );
|
|
73
|
+
popupState.close();
|
|
74
|
+
} }
|
|
75
|
+
/>
|
|
76
|
+
<ViewMenuItem
|
|
77
|
+
label={ __( 'Grid', 'elementor' ) }
|
|
78
|
+
selected={ value === 'grid' }
|
|
79
|
+
onClick={ () => {
|
|
80
|
+
onChange( 'grid' );
|
|
81
|
+
popupState.close();
|
|
82
|
+
} }
|
|
83
|
+
/>
|
|
84
|
+
</Menu>
|
|
85
|
+
</>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
type ViewMenuItemProps = {
|
|
90
|
+
label: string;
|
|
91
|
+
selected: boolean;
|
|
92
|
+
onClick: () => void;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const ViewMenuItem = ( { label, selected, onClick }: ViewMenuItemProps ) => (
|
|
96
|
+
<MenuItem role="menuitemradio" aria-checked={ selected } selected={ selected } onClick={ onClick }>
|
|
97
|
+
<Stack direction="row" alignItems="center" gap={ 1 } width="100%">
|
|
98
|
+
<Typography variant="caption" sx={ { flex: 1 } }>
|
|
99
|
+
{ label }
|
|
100
|
+
</Typography>
|
|
101
|
+
{ selected ? <CheckIcon fontSize="tiny" aria-hidden="true" /> : null }
|
|
102
|
+
</Stack>
|
|
103
|
+
</MenuItem>
|
|
104
|
+
);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useQuery } from '@elementor/query';
|
|
2
|
+
|
|
3
|
+
import { type FontAwesome7Icon, loadFontAwesome7Catalog } from './font-awesome-7-catalog';
|
|
4
|
+
|
|
5
|
+
const FONT_AWESOME_7_CATALOG_QUERY_KEY = [ 'font-awesome-7-catalog' ];
|
|
6
|
+
|
|
7
|
+
export function useFontAwesome7Catalog( enabled: boolean ) {
|
|
8
|
+
return useQuery< FontAwesome7Icon[] >( {
|
|
9
|
+
queryKey: FONT_AWESOME_7_CATALOG_QUERY_KEY,
|
|
10
|
+
queryFn: ( { signal } ) => loadFontAwesome7Catalog( signal ),
|
|
11
|
+
enabled,
|
|
12
|
+
staleTime: Infinity,
|
|
13
|
+
} );
|
|
14
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { ajax } from '@elementor/editor-v1-adapters';
|
|
4
|
+
import { BulbIcon } from '@elementor/icons';
|
|
5
|
+
import { Alert, AlertAction, type AlertProps, AlertTitle, styled, Typography } from '@elementor/ui';
|
|
6
|
+
|
|
7
|
+
import { createControl } from '../create-control';
|
|
8
|
+
|
|
9
|
+
type NoticeType = 'info' | 'success' | 'warning' | 'danger';
|
|
10
|
+
|
|
11
|
+
// Elementor's `Alert` forces `.MuiAlertTitle-root { marginBottom: 0 }` via a selector scoped to the
|
|
12
|
+
// Alert's own class. Repeating the `.MuiAlertTitle-root` class here matches that selector's specificity
|
|
13
|
+
// so this override applies reliably regardless of stylesheet insertion order.
|
|
14
|
+
const NoticeTitle = styled( AlertTitle )( {
|
|
15
|
+
'&.MuiAlertTitle-root.MuiAlertTitle-root': {
|
|
16
|
+
marginBottom: 4,
|
|
17
|
+
fontStyle: 'italic',
|
|
18
|
+
},
|
|
19
|
+
} );
|
|
20
|
+
|
|
21
|
+
type NoticeControlProps = {
|
|
22
|
+
noticeType?: NoticeType;
|
|
23
|
+
heading?: string;
|
|
24
|
+
content?: string;
|
|
25
|
+
dismissible?: string;
|
|
26
|
+
buttonText?: string;
|
|
27
|
+
buttonUrl?: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type ExtendedWindow = Window & {
|
|
31
|
+
elementor?: {
|
|
32
|
+
config?: {
|
|
33
|
+
user?: {
|
|
34
|
+
dismissed_editor_notices?: string[];
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const markNoticeAsDismissedInSession = ( dismissId: string ) => {
|
|
41
|
+
const dismissedNotices = ( window as ExtendedWindow ).elementor?.config?.user?.dismissed_editor_notices;
|
|
42
|
+
|
|
43
|
+
if ( dismissedNotices && ! dismissedNotices.includes( dismissId ) ) {
|
|
44
|
+
dismissedNotices.push( dismissId );
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const NoticeControl = createControl(
|
|
49
|
+
( { noticeType = 'info', heading, content, dismissible, buttonText, buttonUrl }: NoticeControlProps ) => {
|
|
50
|
+
const [ isDismissed, setIsDismissed ] = useState( false );
|
|
51
|
+
|
|
52
|
+
if ( isDismissed || ! content ) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const severity: AlertProps[ 'severity' ] = noticeType === 'danger' ? 'error' : noticeType;
|
|
57
|
+
const icon = noticeType === 'info' ? <BulbIcon fontSize="inherit" /> : undefined;
|
|
58
|
+
|
|
59
|
+
const handleDismiss = () => {
|
|
60
|
+
setIsDismissed( true );
|
|
61
|
+
|
|
62
|
+
if ( ! dismissible ) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
markNoticeAsDismissedInSession( dismissible );
|
|
67
|
+
|
|
68
|
+
ajax.load( {
|
|
69
|
+
action: 'dismissed_editor_notices',
|
|
70
|
+
unique_id: `dismiss-editor-notice-${ dismissible }`,
|
|
71
|
+
data: { dismissId: dismissible },
|
|
72
|
+
} ).catch( () => {} );
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const handleActionClick = () => {
|
|
76
|
+
setIsDismissed( true );
|
|
77
|
+
|
|
78
|
+
if ( dismissible ) {
|
|
79
|
+
markNoticeAsDismissedInSession( dismissible );
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<Alert variant="outlined" severity={ severity } icon={ icon } size="small" onClose={ handleDismiss }>
|
|
85
|
+
{ heading && <NoticeTitle>{ heading }</NoticeTitle> }
|
|
86
|
+
<Typography variant="caption" color="textSecondary" sx={ { fontStyle: 'italic' } }>
|
|
87
|
+
{ content }
|
|
88
|
+
</Typography>
|
|
89
|
+
{ buttonText && buttonUrl && (
|
|
90
|
+
<AlertAction
|
|
91
|
+
variant="contained"
|
|
92
|
+
color={ severity }
|
|
93
|
+
target="_blank"
|
|
94
|
+
rel="noopener noreferrer"
|
|
95
|
+
href={ buttonUrl }
|
|
96
|
+
onClick={ handleActionClick }
|
|
97
|
+
sx={ { mt: 2 } }
|
|
98
|
+
>
|
|
99
|
+
{ buttonText }
|
|
100
|
+
</AlertAction>
|
|
101
|
+
) }
|
|
102
|
+
</Alert>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
);
|
|
@@ -1,43 +1,29 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
3
|
import { useCurrentUserCapabilities } from '@elementor/editor-current-user';
|
|
4
4
|
import { iconPropTypeUtil, svgSrcPropTypeUtil, urlPropTypeUtil } from '@elementor/editor-props';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
Box,
|
|
8
|
-
Button,
|
|
9
|
-
Card,
|
|
10
|
-
CardMedia,
|
|
11
|
-
CardOverlay,
|
|
12
|
-
CircularProgress,
|
|
13
|
-
Stack,
|
|
14
|
-
styled,
|
|
15
|
-
ThemeProvider,
|
|
16
|
-
} from '@elementor/ui';
|
|
5
|
+
import { Box, Card, CardOverlay, Popover, Stack, styled, usePopupState } from '@elementor/ui';
|
|
17
6
|
import { type OpenOptions, useWpMediaAttachment, useWpMediaFrame } from '@elementor/wp-media';
|
|
18
7
|
import { __ } from '@wordpress/i18n';
|
|
19
8
|
|
|
20
9
|
import { useBoundProp } from '../bound-prop-context';
|
|
21
|
-
import { ConditionalControlInfotip } from '../components/conditional-control-infotip';
|
|
22
10
|
import { EnableUnfilteredModal } from '../components/enable-unfiltered-modal';
|
|
23
11
|
import ControlActions from '../control-actions/control-actions';
|
|
24
12
|
import { createControl } from '../create-control';
|
|
25
13
|
import { useUnfilteredFilesUpload } from '../hooks/use-unfiltered-files-upload';
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
openIconLibrary,
|
|
32
|
-
} from './open-icon-library';
|
|
14
|
+
import { type IconLibraryAnchor, measureIconLibraryAnchor } from './icon-library/get-icon-library-anchor';
|
|
15
|
+
import { ICON_LIBRARY_POPOVER_WIDTH, IconLibraryPopover } from './icon-library/icon-library-popover';
|
|
16
|
+
import { createIconPropValue } from './open-icon-library';
|
|
17
|
+
import { SVG_MEDIA_CONTROL_CONTAINER_TEST_ID, SvgMediaOverlay } from './svg-media-overlay';
|
|
18
|
+
import { SvgMediaPreview } from './svg-media-preview';
|
|
33
19
|
|
|
34
20
|
const TILE_SIZE = 8;
|
|
35
21
|
const TILE_WHITE = 'transparent';
|
|
36
22
|
const TILE_BLACK = '#c1c1c1';
|
|
37
|
-
const ICON_PREVIEW_FONT_SIZE = 50;
|
|
38
23
|
export const TILES_GRADIENT_FORMULA = `linear-gradient(45deg, ${ TILE_BLACK } 25%, ${ TILE_WHITE } 0, ${ TILE_WHITE } 75%, ${ TILE_BLACK } 0, ${ TILE_BLACK })`;
|
|
39
24
|
|
|
40
25
|
const StyledCard = styled( Card )`
|
|
26
|
+
position: relative;
|
|
41
27
|
background-color: white;
|
|
42
28
|
background-image: ${ TILES_GRADIENT_FORMULA }, ${ TILES_GRADIENT_FORMULA };
|
|
43
29
|
background-size: ${ TILE_SIZE }px ${ TILE_SIZE }px;
|
|
@@ -47,6 +33,8 @@ const StyledCard = styled( Card )`
|
|
|
47
33
|
border: none;
|
|
48
34
|
`;
|
|
49
35
|
|
|
36
|
+
const PREVIEW_ICON_COLOR = '#000000';
|
|
37
|
+
|
|
50
38
|
const StyledCardMediaContainer = styled( Stack )`
|
|
51
39
|
position: relative;
|
|
52
40
|
height: 140px;
|
|
@@ -55,6 +43,7 @@ const StyledCardMediaContainer = styled( Stack )`
|
|
|
55
43
|
justify-content: center;
|
|
56
44
|
align-items: center;
|
|
57
45
|
background-color: rgba( 255, 255, 255, 0.37 );
|
|
46
|
+
color: ${ PREVIEW_ICON_COLOR };
|
|
58
47
|
`;
|
|
59
48
|
|
|
60
49
|
const MODE_BROWSE: OpenOptions = { mode: 'browse' };
|
|
@@ -73,6 +62,10 @@ export const SvgMediaControl = createControl( ( { showIconLibrary = false }: Svg
|
|
|
73
62
|
const src = attachment?.url ?? url?.value ?? null;
|
|
74
63
|
const { data: allowSvgUpload } = useUnfilteredFilesUpload();
|
|
75
64
|
const [ unfilteredModalOpenState, setUnfilteredModalOpenState ] = useState( false );
|
|
65
|
+
const iconLibraryPopoverState = usePopupState( { variant: 'popover' } );
|
|
66
|
+
const controlContainerRef = useRef< HTMLDivElement >( null );
|
|
67
|
+
const buttonGroupRef = useRef< HTMLDivElement >( null );
|
|
68
|
+
const [ iconLibraryAnchor, setIconLibraryAnchor ] = useState< IconLibraryAnchor | null >( null );
|
|
76
69
|
const { isAdmin } = useCurrentUserCapabilities();
|
|
77
70
|
const selectedIconClass =
|
|
78
71
|
showIconLibrary && typeof iconValue?.value?.value === 'string' ? iconValue.value.value : null;
|
|
@@ -94,7 +87,7 @@ export const SvgMediaControl = createControl( ( { showIconLibrary = false }: Svg
|
|
|
94
87
|
},
|
|
95
88
|
} );
|
|
96
89
|
|
|
97
|
-
const
|
|
90
|
+
const handleCloseUnfilteredModal = ( enabled: boolean ) => {
|
|
98
91
|
setUnfilteredModalOpenState( false );
|
|
99
92
|
|
|
100
93
|
if ( enabled ) {
|
|
@@ -110,152 +103,132 @@ export const SvgMediaControl = createControl( ( { showIconLibrary = false }: Svg
|
|
|
110
103
|
}
|
|
111
104
|
};
|
|
112
105
|
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
if ( isSvgLibrarySelection( icon ) ) {
|
|
118
|
-
setSvgValue( {
|
|
119
|
-
id: icon.value.id
|
|
120
|
-
? {
|
|
121
|
-
$$type: 'image-attachment-id',
|
|
122
|
-
value: icon.value.id,
|
|
123
|
-
}
|
|
124
|
-
: null,
|
|
125
|
-
url: icon.value.url ? urlPropTypeUtil.create( icon.value.url ) : null,
|
|
126
|
-
} );
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
106
|
+
const handleSelectSvg = () => {
|
|
107
|
+
handleClick( MODE_BROWSE );
|
|
108
|
+
};
|
|
129
109
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
110
|
+
const handleUpload = () => {
|
|
111
|
+
handleClick( MODE_UPLOAD );
|
|
133
112
|
};
|
|
134
113
|
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
<>
|
|
139
|
-
{ __( 'To upload them anyway, ask the site administrator to enable unfiltered', 'elementor' ) }
|
|
140
|
-
<br />
|
|
141
|
-
{ __( 'file uploads.', 'elementor' ) }
|
|
142
|
-
</>
|
|
143
|
-
),
|
|
144
|
-
isEnabled: ! isAdmin,
|
|
114
|
+
const handleCloseIconLibrary = () => {
|
|
115
|
+
iconLibraryPopoverState.close();
|
|
116
|
+
setIconLibraryAnchor( null );
|
|
145
117
|
};
|
|
146
118
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
sx={ {
|
|
162
|
-
'&:hover': {
|
|
163
|
-
backgroundColor: 'rgba( 0, 0, 0, 0.75 )',
|
|
164
|
-
},
|
|
165
|
-
} }
|
|
166
|
-
>
|
|
167
|
-
<Stack gap={ 1 }>
|
|
168
|
-
<Button
|
|
169
|
-
size="tiny"
|
|
170
|
-
color="inherit"
|
|
171
|
-
variant="outlined"
|
|
172
|
-
onClick={ () => handleClick( MODE_BROWSE ) }
|
|
173
|
-
aria-label="Select SVG"
|
|
174
|
-
>
|
|
175
|
-
{ __( 'Select SVG', 'elementor' ) }
|
|
176
|
-
</Button>
|
|
177
|
-
{ showIconLibrary ? (
|
|
178
|
-
<Button
|
|
179
|
-
size="tiny"
|
|
180
|
-
variant="text"
|
|
181
|
-
color="inherit"
|
|
182
|
-
onClick={ () =>
|
|
183
|
-
openIconLibrary( {
|
|
184
|
-
selected:
|
|
185
|
-
selectedIconClass && selectedIconLibrary
|
|
186
|
-
? { value: selectedIconClass, library: selectedIconLibrary }
|
|
187
|
-
: undefined,
|
|
188
|
-
onSelect: handleIconLibrarySelect,
|
|
189
|
-
} )
|
|
190
|
-
}
|
|
191
|
-
aria-label={ __( 'Icon library', 'elementor' ) }
|
|
192
|
-
>
|
|
193
|
-
{ __( 'Icon library', 'elementor' ) }
|
|
194
|
-
</Button>
|
|
195
|
-
) : null }
|
|
196
|
-
<ConditionalControlInfotip { ...infotipProps }>
|
|
197
|
-
<span>
|
|
198
|
-
<ThemeProvider colorScheme={ isAdmin ? 'light' : 'dark' }>
|
|
199
|
-
<Button
|
|
200
|
-
size="tiny"
|
|
201
|
-
variant="text"
|
|
202
|
-
color="inherit"
|
|
203
|
-
startIcon={ <UploadIcon /> }
|
|
204
|
-
disabled={ ! isAdmin }
|
|
205
|
-
onClick={ () => isAdmin && handleClick( MODE_UPLOAD ) }
|
|
206
|
-
aria-label="Upload SVG"
|
|
207
|
-
>
|
|
208
|
-
{ __( 'Upload', 'elementor' ) }
|
|
209
|
-
</Button>
|
|
210
|
-
</ThemeProvider>
|
|
211
|
-
</span>
|
|
212
|
-
</ConditionalControlInfotip>
|
|
213
|
-
</Stack>
|
|
214
|
-
</CardOverlay>
|
|
215
|
-
</StyledCard>
|
|
216
|
-
</ControlActions>
|
|
217
|
-
</Stack>
|
|
218
|
-
);
|
|
219
|
-
} );
|
|
119
|
+
const handleIconLibrarySelect = ( icon: { value: string; library: string } ) => {
|
|
120
|
+
setIconValue( createIconPropValue( icon.value, icon.library ) );
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const handleOpenIconLibrary = ( event: React.MouseEvent< HTMLElement > ) => {
|
|
124
|
+
const anchor = measureIconLibraryAnchor( controlContainerRef.current, buttonGroupRef.current );
|
|
125
|
+
|
|
126
|
+
if ( ! anchor ) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
setIconLibraryAnchor( anchor );
|
|
131
|
+
iconLibraryPopoverState.open( event );
|
|
132
|
+
};
|
|
220
133
|
|
|
221
|
-
function SvgMediaPreview( {
|
|
222
|
-
isFetching,
|
|
223
|
-
src,
|
|
224
|
-
iconClassName,
|
|
225
|
-
iconLibrary,
|
|
226
|
-
}: {
|
|
227
|
-
isFetching: boolean;
|
|
228
|
-
src: string | null;
|
|
229
|
-
iconClassName: string | null;
|
|
230
|
-
iconLibrary: string | null;
|
|
231
|
-
} ) {
|
|
232
134
|
useEffect( () => {
|
|
233
|
-
if (
|
|
234
|
-
|
|
135
|
+
if ( ! iconLibraryPopoverState.isOpen ) {
|
|
136
|
+
return;
|
|
235
137
|
}
|
|
236
|
-
}, [ iconLibrary ] );
|
|
237
138
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
139
|
+
const handleResize = () => {
|
|
140
|
+
const nextAnchor = measureIconLibraryAnchor( controlContainerRef.current, buttonGroupRef.current );
|
|
241
141
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
142
|
+
if ( nextAnchor ) {
|
|
143
|
+
setIconLibraryAnchor( nextAnchor );
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
window.addEventListener( 'resize', handleResize );
|
|
148
|
+
|
|
149
|
+
return () => {
|
|
150
|
+
window.removeEventListener( 'resize', handleResize );
|
|
151
|
+
};
|
|
152
|
+
}, [ iconLibraryPopoverState.isOpen ] );
|
|
153
|
+
|
|
154
|
+
const iconLibraryWidth = iconLibraryAnchor?.width ?? ICON_LIBRARY_POPOVER_WIDTH;
|
|
252
155
|
|
|
253
156
|
return (
|
|
254
|
-
<
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
157
|
+
<Stack gap={ 1 } aria-label={ __( 'SVG control', 'elementor' ) }>
|
|
158
|
+
<EnableUnfilteredModal open={ unfilteredModalOpenState } onClose={ handleCloseUnfilteredModal } />
|
|
159
|
+
{ showIconLibrary && iconLibraryAnchor ? (
|
|
160
|
+
<Popover
|
|
161
|
+
disableScrollLock
|
|
162
|
+
open={ iconLibraryPopoverState.isOpen }
|
|
163
|
+
onClose={ handleCloseIconLibrary }
|
|
164
|
+
anchorReference="anchorPosition"
|
|
165
|
+
anchorPosition={ { top: iconLibraryAnchor.top, left: iconLibraryAnchor.left } }
|
|
166
|
+
anchorOrigin={ { vertical: 'top', horizontal: 'left' } }
|
|
167
|
+
transformOrigin={ { vertical: 'top', horizontal: 'left' } }
|
|
168
|
+
marginThreshold={ 0 }
|
|
169
|
+
PaperProps={ {
|
|
170
|
+
sx: {
|
|
171
|
+
width: iconLibraryWidth,
|
|
172
|
+
minWidth: iconLibraryWidth,
|
|
173
|
+
maxWidth: iconLibraryWidth,
|
|
174
|
+
m: 0,
|
|
175
|
+
},
|
|
176
|
+
} }
|
|
177
|
+
>
|
|
178
|
+
<IconLibraryPopover
|
|
179
|
+
open={ iconLibraryPopoverState.isOpen }
|
|
180
|
+
selectedIconClass={ selectedIconClass }
|
|
181
|
+
selectedIconLibrary={ selectedIconLibrary }
|
|
182
|
+
onSelect={ handleIconLibrarySelect }
|
|
183
|
+
onClose={ handleCloseIconLibrary }
|
|
184
|
+
width={ iconLibraryWidth }
|
|
185
|
+
/>
|
|
186
|
+
</Popover>
|
|
187
|
+
) : null }
|
|
188
|
+
<Box
|
|
189
|
+
ref={ controlContainerRef }
|
|
190
|
+
data-testid={ SVG_MEDIA_CONTROL_CONTAINER_TEST_ID }
|
|
191
|
+
sx={ { width: '100%' } }
|
|
192
|
+
>
|
|
193
|
+
<ControlActions>
|
|
194
|
+
<StyledCard variant="outlined">
|
|
195
|
+
<StyledCardMediaContainer>
|
|
196
|
+
<SvgMediaPreview
|
|
197
|
+
isFetching={ isFetching }
|
|
198
|
+
src={ src }
|
|
199
|
+
iconClassName={ selectedIconClass }
|
|
200
|
+
iconLibrary={ selectedIconLibrary }
|
|
201
|
+
/>
|
|
202
|
+
</StyledCardMediaContainer>
|
|
203
|
+
<CardOverlay
|
|
204
|
+
sx={ {
|
|
205
|
+
'&:hover': {
|
|
206
|
+
backgroundColor: 'rgba( 0, 0, 0, 0.75 )',
|
|
207
|
+
},
|
|
208
|
+
} }
|
|
209
|
+
>
|
|
210
|
+
<SvgMediaOverlay
|
|
211
|
+
isAdmin={ isAdmin }
|
|
212
|
+
showIconLibrary={ showIconLibrary }
|
|
213
|
+
buttonGroupRef={ buttonGroupRef }
|
|
214
|
+
onSelectSvg={ handleSelectSvg }
|
|
215
|
+
onUpload={ handleUpload }
|
|
216
|
+
onOpenIconLibrary={ handleOpenIconLibrary }
|
|
217
|
+
infotipTitle={ __( "Sorry, you can't upload that file yet.", 'elementor' ) }
|
|
218
|
+
infotipDescription={ <UnfilteredUploadInfotipDescription /> }
|
|
219
|
+
/>
|
|
220
|
+
</CardOverlay>
|
|
221
|
+
</StyledCard>
|
|
222
|
+
</ControlActions>
|
|
223
|
+
</Box>
|
|
224
|
+
</Stack>
|
|
260
225
|
);
|
|
261
|
-
}
|
|
226
|
+
} );
|
|
227
|
+
|
|
228
|
+
const UnfilteredUploadInfotipDescription = () => (
|
|
229
|
+
<>
|
|
230
|
+
{ __( 'To upload them anyway, ask the site administrator to enable unfiltered', 'elementor' ) }
|
|
231
|
+
<br />
|
|
232
|
+
{ __( 'file uploads.', 'elementor' ) }
|
|
233
|
+
</>
|
|
234
|
+
);
|