@elementor/editor-controls 4.4.0-1145 → 4.4.0-1147
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 +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +297 -177
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +274 -155
- package/dist/index.mjs.map +1 -1
- package/package.json +16 -16
- package/src/controls/icon-library/font-awesome-7-catalog.ts +2 -0
- package/src/controls/icon-library/font-awesome-glyph.tsx +67 -1
- package/src/controls/icon-library/icon-library-grid.tsx +2 -1
- package/src/controls/icon-library/icon-library-popover.tsx +14 -5
- package/src/controls/icon-library/icon-library-providers.ts +48 -4
- package/src/controls/icon-library/merge-icons-by-id.ts +25 -0
- package/src/controls/svg-media-control.tsx +4 -7
- package/src/controls/svg-media-overlay.tsx +15 -19
- package/src/controls/svg-media-preview.tsx +32 -7
- package/src/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-controls",
|
|
3
3
|
"description": "This package contains the controls model and utils for the Elementor editor",
|
|
4
|
-
"version": "4.4.0-
|
|
4
|
+
"version": "4.4.0-1147",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Elementor Team",
|
|
7
7
|
"homepage": "https://elementor.com/",
|
|
@@ -40,23 +40,23 @@
|
|
|
40
40
|
"dev": "tsup --config=../../tsup.dev.ts"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@elementor/editor-current-user": "4.4.0-
|
|
44
|
-
"@elementor/editor-elements": "4.4.0-
|
|
45
|
-
"@elementor/editor-props": "4.4.0-
|
|
46
|
-
"@elementor/editor-responsive": "4.4.0-
|
|
47
|
-
"@elementor/editor-ui": "4.4.0-
|
|
48
|
-
"@elementor/editor-v1-adapters": "4.4.0-
|
|
49
|
-
"@elementor/env": "4.4.0-
|
|
50
|
-
"@elementor/events": "4.4.0-
|
|
51
|
-
"@elementor/http-client": "4.4.0-
|
|
43
|
+
"@elementor/editor-current-user": "4.4.0-1147",
|
|
44
|
+
"@elementor/editor-elements": "4.4.0-1147",
|
|
45
|
+
"@elementor/editor-props": "4.4.0-1147",
|
|
46
|
+
"@elementor/editor-responsive": "4.4.0-1147",
|
|
47
|
+
"@elementor/editor-ui": "4.4.0-1147",
|
|
48
|
+
"@elementor/editor-v1-adapters": "4.4.0-1147",
|
|
49
|
+
"@elementor/env": "4.4.0-1147",
|
|
50
|
+
"@elementor/events": "4.4.0-1147",
|
|
51
|
+
"@elementor/http-client": "4.4.0-1147",
|
|
52
52
|
"@elementor/icons": "~1.78.1",
|
|
53
|
-
"@elementor/locations": "4.4.0-
|
|
54
|
-
"@elementor/query": "4.4.0-
|
|
55
|
-
"@elementor/schema": "4.4.0-
|
|
56
|
-
"@elementor/session": "4.4.0-
|
|
53
|
+
"@elementor/locations": "4.4.0-1147",
|
|
54
|
+
"@elementor/query": "4.4.0-1147",
|
|
55
|
+
"@elementor/schema": "4.4.0-1147",
|
|
56
|
+
"@elementor/session": "4.4.0-1147",
|
|
57
57
|
"@elementor/ui": "1.41.4",
|
|
58
|
-
"@elementor/utils": "4.4.0-
|
|
59
|
-
"@elementor/wp-media": "4.4.0-
|
|
58
|
+
"@elementor/utils": "4.4.0-1147",
|
|
59
|
+
"@elementor/wp-media": "4.4.0-1147",
|
|
60
60
|
"@monaco-editor/react": "^4.7.0",
|
|
61
61
|
"@tanstack/react-virtual": "^3.13.3",
|
|
62
62
|
"@tiptap/extension-bold": "^3.11.1",
|
|
@@ -18,6 +18,8 @@ export type FontAwesome7Icon = FontAwesome7IconDefinition & {
|
|
|
18
18
|
value: string;
|
|
19
19
|
glyphClass?: string;
|
|
20
20
|
svgMarkup?: string;
|
|
21
|
+
/** Markup is resolved on demand by the icon library providers, once the icon is displayed. */
|
|
22
|
+
lazySvg?: boolean;
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
export async function loadFontAwesome7Catalog( signal?: AbortSignal ): Promise< FontAwesome7Icon[] > {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
3
|
import { Box } from '@elementor/ui';
|
|
3
4
|
|
|
4
5
|
import { type FontAwesome7Icon } from './font-awesome-7-catalog';
|
|
6
|
+
import { resolveProviderIconPreviewSvg } from './icon-library-providers';
|
|
5
7
|
import { sanitizeIconSvgMarkup } from './sanitize-icon-svg-markup';
|
|
6
8
|
|
|
7
9
|
type FontAwesomeGlyphProps = {
|
|
@@ -11,7 +13,71 @@ type FontAwesomeGlyphProps = {
|
|
|
11
13
|
label?: string;
|
|
12
14
|
};
|
|
13
15
|
|
|
14
|
-
export const FontAwesomeGlyph = (
|
|
16
|
+
export const FontAwesomeGlyph = ( props: FontAwesomeGlyphProps ) => {
|
|
17
|
+
const { icon } = props;
|
|
18
|
+
|
|
19
|
+
if ( icon.lazySvg && ! icon.svgMarkup ) {
|
|
20
|
+
return <LazyProviderGlyph { ...props } />;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return <ResolvedGlyph { ...props } />;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const LazyProviderGlyph = ( props: FontAwesomeGlyphProps ) => {
|
|
27
|
+
const { icon, size } = props;
|
|
28
|
+
const [ resolved, setResolved ] = useState< { id: string; markup: string | null } | null >( null );
|
|
29
|
+
const [ isVisible, setIsVisible ] = useState( typeof IntersectionObserver === 'undefined' );
|
|
30
|
+
const placeholderRef = useRef< HTMLSpanElement >( null );
|
|
31
|
+
const svgMarkup = resolved?.id === icon.id ? resolved.markup : null;
|
|
32
|
+
|
|
33
|
+
useEffect( () => {
|
|
34
|
+
const element = placeholderRef.current;
|
|
35
|
+
|
|
36
|
+
if ( isVisible || ! element ) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const observer = new IntersectionObserver( ( entries ) => {
|
|
41
|
+
if ( entries.some( ( entry ) => entry.isIntersecting ) ) {
|
|
42
|
+
setIsVisible( true );
|
|
43
|
+
observer.disconnect();
|
|
44
|
+
}
|
|
45
|
+
} );
|
|
46
|
+
|
|
47
|
+
observer.observe( element );
|
|
48
|
+
|
|
49
|
+
return () => observer.disconnect();
|
|
50
|
+
}, [ isVisible ] );
|
|
51
|
+
|
|
52
|
+
useEffect( () => {
|
|
53
|
+
if ( ! isVisible ) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const controller = new AbortController();
|
|
58
|
+
|
|
59
|
+
resolveProviderIconPreviewSvg( icon.library, icon.value, controller.signal )
|
|
60
|
+
.then( ( markup ) => ! controller.signal.aborted && setResolved( { id: icon.id, markup } ) )
|
|
61
|
+
.catch( () => {} );
|
|
62
|
+
|
|
63
|
+
return () => controller.abort();
|
|
64
|
+
}, [ isVisible, icon.id, icon.library, icon.value ] );
|
|
65
|
+
|
|
66
|
+
if ( ! svgMarkup ) {
|
|
67
|
+
return (
|
|
68
|
+
<Box
|
|
69
|
+
component="span"
|
|
70
|
+
ref={ placeholderRef }
|
|
71
|
+
aria-hidden="true"
|
|
72
|
+
sx={ { display: 'inline-block', width: size, height: size } }
|
|
73
|
+
/>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return <ResolvedGlyph { ...props } icon={ { ...icon, svgMarkup } } />;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const ResolvedGlyph = ( { icon, size, color, label }: FontAwesomeGlyphProps ) => {
|
|
15
81
|
const svgMarkup = sanitizeIconSvgMarkup( icon.svgMarkup );
|
|
16
82
|
|
|
17
83
|
if ( svgMarkup ) {
|
|
@@ -43,6 +43,7 @@ const getGridMetrics = ( containerWidth: number, columnGap: number, inlinePaddin
|
|
|
43
43
|
type IconLibraryGridItem = FontAwesome7Icon & {
|
|
44
44
|
id: string;
|
|
45
45
|
value: string;
|
|
46
|
+
iconValue?: string;
|
|
46
47
|
};
|
|
47
48
|
|
|
48
49
|
type IconLibraryGridProps = {
|
|
@@ -329,7 +330,7 @@ export const IconLibraryGrid = ( {
|
|
|
329
330
|
} }
|
|
330
331
|
>
|
|
331
332
|
<FontAwesomeGlyph
|
|
332
|
-
icon={ item }
|
|
333
|
+
icon={ { ...item, value: item.iconValue ?? item.value } }
|
|
333
334
|
size={ ICON_GLYPH_SIZE }
|
|
334
335
|
color="currentColor"
|
|
335
336
|
/>
|
|
@@ -27,6 +27,7 @@ import { IconLibraryGrid } from './icon-library-grid';
|
|
|
27
27
|
import { useIconLibraryProviderIcons } from './icon-library-providers';
|
|
28
28
|
import { IconLibraryUploadAction, IconLibraryUploadActionSlot } from './icon-library-upload-action';
|
|
29
29
|
import { type IconLibraryView, IconLibraryViewToggle } from './icon-library-view-toggle';
|
|
30
|
+
import { mergeIconsById } from './merge-icons-by-id';
|
|
30
31
|
import { SvgElementIcon } from './svg-element-icon';
|
|
31
32
|
import { useFontAwesome7Catalog } from './use-font-awesome-7-catalog';
|
|
32
33
|
|
|
@@ -54,7 +55,7 @@ const CompactIconLibraryMenuList = styled( StyledMenuList )( ( { theme } ) => (
|
|
|
54
55
|
},
|
|
55
56
|
} ) );
|
|
56
57
|
|
|
57
|
-
type IconLibraryItem = VirtualizedItem< 'item', string > & Omit< FontAwesome7Icon, 'value'
|
|
58
|
+
type IconLibraryItem = VirtualizedItem< 'item', string > & Omit< FontAwesome7Icon, 'value' > & { iconValue: string };
|
|
58
59
|
|
|
59
60
|
type IconLibraryPopoverProps = {
|
|
60
61
|
open: boolean;
|
|
@@ -87,13 +88,16 @@ export const IconLibraryPopover = ( {
|
|
|
87
88
|
const view = isIconLibraryView( storedView ) ? storedView : DEFAULT_ICON_LIBRARY_VIEW;
|
|
88
89
|
const { data: fontAwesomeIcons = [], isLoading: isFontAwesomeLoading } = useFontAwesome7Catalog( open );
|
|
89
90
|
const { data: providerIcons = [], isLoading: isProviderLoading } = useIconLibraryProviderIcons( open );
|
|
90
|
-
const
|
|
91
|
-
|
|
91
|
+
const icons = useMemo(
|
|
92
|
+
() => mergeIconsById( fontAwesomeIcons, providerIcons ),
|
|
93
|
+
[ providerIcons, fontAwesomeIcons ]
|
|
94
|
+
);
|
|
92
95
|
|
|
93
96
|
const items = useMemo(
|
|
94
97
|
() => createIconLibraryItems( icons, searchValue, activeLibraries ),
|
|
95
98
|
[ activeLibraries, icons, searchValue ]
|
|
96
99
|
);
|
|
100
|
+
const isLoading = isFontAwesomeLoading || ( isProviderLoading && items.length === 0 );
|
|
97
101
|
const selectedValue = useMemo(
|
|
98
102
|
() => findFontAwesome7Icon( icons, selectedIconClass, selectedIconLibrary )?.id,
|
|
99
103
|
[ icons, selectedIconClass, selectedIconLibrary ]
|
|
@@ -239,7 +243,7 @@ const IconLibraryContent = ( {
|
|
|
239
243
|
|
|
240
244
|
const IconLibraryLoadingState = () => (
|
|
241
245
|
<Stack alignItems="center" justifyContent="center" height="100%">
|
|
242
|
-
<CircularProgress role="progressbar" size={ 24 } />
|
|
246
|
+
<CircularProgress role="progressbar" size={ 24 } aria-label={ __( 'Loading icons', 'elementor' ) } />
|
|
243
247
|
</Stack>
|
|
244
248
|
);
|
|
245
249
|
|
|
@@ -278,7 +282,11 @@ const IconLibraryRow = ( item: VirtualizedItem< string, string > ) => {
|
|
|
278
282
|
flexShrink: 0,
|
|
279
283
|
} }
|
|
280
284
|
>
|
|
281
|
-
<FontAwesomeGlyph
|
|
285
|
+
<FontAwesomeGlyph
|
|
286
|
+
icon={ { ...icon, value: icon.iconValue } }
|
|
287
|
+
size={ ICON_GLYPH_SIZE }
|
|
288
|
+
color="currentColor"
|
|
289
|
+
/>
|
|
282
290
|
</Box>
|
|
283
291
|
<Typography variant="caption" color="text.primary" noWrap>
|
|
284
292
|
{ icon.label }
|
|
@@ -324,4 +332,5 @@ const createIconLibraryItems = (
|
|
|
324
332
|
...icon,
|
|
325
333
|
type: 'item',
|
|
326
334
|
value: icon.id,
|
|
335
|
+
iconValue: icon.value,
|
|
327
336
|
} ) );
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
1
|
+
import { useRef, useState } from 'react';
|
|
2
2
|
import { useQuery } from '@elementor/query';
|
|
3
3
|
|
|
4
4
|
import { type FontAwesome7Icon } from './font-awesome-7-catalog';
|
|
@@ -12,6 +12,11 @@ export type IconLibraryProvider = {
|
|
|
12
12
|
id: string;
|
|
13
13
|
useIcons: ( enabled: boolean ) => IconLibraryProviderState;
|
|
14
14
|
resolveSvg?: ( library: string, iconValue: string, signal?: AbortSignal ) => Promise< string | null >;
|
|
15
|
+
/**
|
|
16
|
+
* Resolves markup for picker tiles and the control preview. Falls back to `resolveSvg` when omitted.
|
|
17
|
+
* Use it when `resolveSvg` has side effects that should only happen for icons that are actually rendered.
|
|
18
|
+
*/
|
|
19
|
+
resolvePreviewSvg?: ( library: string, iconValue: string, signal?: AbortSignal ) => Promise< string | null >;
|
|
15
20
|
};
|
|
16
21
|
|
|
17
22
|
const PROVIDER_ICON_SVG_QUERY_KEY = 'icon-library-provider-svg';
|
|
@@ -35,20 +40,44 @@ export function useIconLibraryProviderIcons( enabled: boolean ): Required< IconL
|
|
|
35
40
|
const [ snapshot ] = useState( () => [ ...providers.values() ] );
|
|
36
41
|
const states = snapshot.map( ( provider ) => provider.useIcons( enabled ) );
|
|
37
42
|
|
|
43
|
+
const data = useStableConcat( states.map( ( state ) => state.data ) );
|
|
44
|
+
|
|
38
45
|
return {
|
|
39
|
-
data
|
|
46
|
+
data,
|
|
40
47
|
isLoading: states.some( ( state ) => state.isLoading ),
|
|
41
48
|
};
|
|
42
49
|
}
|
|
43
50
|
|
|
44
|
-
export
|
|
51
|
+
export function resolveProviderIconSvg(
|
|
52
|
+
library: string,
|
|
53
|
+
iconValue: string,
|
|
54
|
+
signal?: AbortSignal
|
|
55
|
+
): Promise< string | null > {
|
|
56
|
+
return resolveFromProviders( ( provider ) => provider.resolveSvg, library, iconValue, signal );
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function resolveProviderIconPreviewSvg(
|
|
60
|
+
library: string,
|
|
61
|
+
iconValue: string,
|
|
62
|
+
signal?: AbortSignal
|
|
63
|
+
): Promise< string | null > {
|
|
64
|
+
return resolveFromProviders(
|
|
65
|
+
( provider ) => provider.resolvePreviewSvg ?? provider.resolveSvg,
|
|
66
|
+
library,
|
|
67
|
+
iconValue,
|
|
68
|
+
signal
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function resolveFromProviders(
|
|
73
|
+
pickResolver: ( provider: IconLibraryProvider ) => IconLibraryProvider[ 'resolveSvg' ],
|
|
45
74
|
library: string,
|
|
46
75
|
iconValue: string,
|
|
47
76
|
signal?: AbortSignal
|
|
48
77
|
): Promise< string | null > {
|
|
49
78
|
for ( const provider of providers.values() ) {
|
|
50
79
|
try {
|
|
51
|
-
const markup = await provider
|
|
80
|
+
const markup = await pickResolver( provider )?.( library, iconValue, signal );
|
|
52
81
|
|
|
53
82
|
if ( markup ) {
|
|
54
83
|
return markup;
|
|
@@ -63,6 +92,21 @@ export async function resolveProviderIconSvg(
|
|
|
63
92
|
return null;
|
|
64
93
|
}
|
|
65
94
|
|
|
95
|
+
// Keeps the merged array identity while every provider returns the same data reference, so downstream memos hold.
|
|
96
|
+
function useStableConcat( lists: Array< FontAwesome7Icon[] | undefined > ): FontAwesome7Icon[] {
|
|
97
|
+
const previous = useRef< { lists: typeof lists; merged: FontAwesome7Icon[] } | null >( null );
|
|
98
|
+
|
|
99
|
+
if ( ! previous.current || ! areSameLists( previous.current.lists, lists ) ) {
|
|
100
|
+
previous.current = { lists, merged: lists.flatMap( ( list ) => list ?? [] ) };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return previous.current.merged;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function areSameLists( a: Array< unknown >, b: Array< unknown > ) {
|
|
107
|
+
return a.length === b.length && a.every( ( item, index ) => item === b[ index ] );
|
|
108
|
+
}
|
|
109
|
+
|
|
66
110
|
export function useProviderIconSvg( library: string | null, iconValue: string | null, enabled: boolean ) {
|
|
67
111
|
return useQuery< string | null >( {
|
|
68
112
|
queryKey: [ PROVIDER_ICON_SVG_QUERY_KEY, library, iconValue ],
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
type MergeableIcon = { id: string; library?: string; name?: string; aliases?: string[] };
|
|
2
|
+
|
|
3
|
+
const getNames = ( icon: MergeableIcon ) => [ icon.name, ...( icon.aliases ?? [] ) ].filter( Boolean );
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Providers may name an icon by one of its aliases (Pro lists `home` where Free lists `house`),
|
|
7
|
+
* so an icon already present under any of its names in the same library is not added again.
|
|
8
|
+
* @param primary
|
|
9
|
+
* @param secondary
|
|
10
|
+
*/
|
|
11
|
+
export function mergeIconsById< T extends MergeableIcon >( primary: T[], secondary: T[] ): T[] {
|
|
12
|
+
const ids = new Set( primary.map( ( icon ) => icon.id ) );
|
|
13
|
+
const names = new Set(
|
|
14
|
+
primary.flatMap( ( icon ) => getNames( icon ).map( ( name ) => `${ icon.library }:${ name }` ) )
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
return [
|
|
18
|
+
...primary,
|
|
19
|
+
...secondary.filter(
|
|
20
|
+
( icon ) =>
|
|
21
|
+
! ids.has( icon.id ) &&
|
|
22
|
+
! getNames( icon ).some( ( name ) => names.has( `${ icon.library }:${ name }` ) )
|
|
23
|
+
),
|
|
24
|
+
];
|
|
25
|
+
}
|
|
@@ -50,11 +50,10 @@ const MODE_BROWSE: OpenOptions = { mode: 'browse' };
|
|
|
50
50
|
const MODE_UPLOAD: OpenOptions = { mode: 'upload' };
|
|
51
51
|
|
|
52
52
|
type SvgMediaControlProps = {
|
|
53
|
-
showIconLibrary?: boolean;
|
|
54
53
|
label?: string;
|
|
55
54
|
};
|
|
56
55
|
|
|
57
|
-
export const SvgMediaControl = createControl( ( {
|
|
56
|
+
export const SvgMediaControl = createControl( ( { label }: SvgMediaControlProps ) => {
|
|
58
57
|
const groupLabel = label ?? __( 'SVG', 'elementor' );
|
|
59
58
|
const { value: svgValue, setValue: setSvgValue, placeholder: svgPlaceholder } = useBoundProp( svgSrcPropTypeUtil );
|
|
60
59
|
const { value: iconValue, setValue: setIconValue, placeholder: iconPlaceholder } = useBoundProp( iconPropTypeUtil );
|
|
@@ -79,10 +78,9 @@ export const SvgMediaControl = createControl( ( { showIconLibrary = false, label
|
|
|
79
78
|
const buttonGroupRef = useRef< HTMLDivElement >( null );
|
|
80
79
|
const [ iconLibraryAnchor, setIconLibraryAnchor ] = useState< IconLibraryAnchor | null >( null );
|
|
81
80
|
const { isAdmin } = useCurrentUserCapabilities();
|
|
82
|
-
const selectedIconClass =
|
|
83
|
-
showIconLibrary && typeof previewIconValue?.value?.value === 'string' ? previewIconValue.value.value : null;
|
|
81
|
+
const selectedIconClass = typeof previewIconValue?.value?.value === 'string' ? previewIconValue.value.value : null;
|
|
84
82
|
const selectedIconLibrary =
|
|
85
|
-
|
|
83
|
+
typeof previewIconValue?.library?.value === 'string' ? previewIconValue.library.value : null;
|
|
86
84
|
|
|
87
85
|
const { open } = useWpMediaFrame( {
|
|
88
86
|
mediaTypes: [ 'svg' ],
|
|
@@ -168,7 +166,7 @@ export const SvgMediaControl = createControl( ( { showIconLibrary = false, label
|
|
|
168
166
|
return (
|
|
169
167
|
<Stack gap={ 1 } aria-label={ __( 'SVG control', 'elementor' ) }>
|
|
170
168
|
<EnableUnfilteredModal open={ unfilteredModalOpenState } onClose={ handleCloseUnfilteredModal } />
|
|
171
|
-
{
|
|
169
|
+
{ iconLibraryAnchor ? (
|
|
172
170
|
<Popover
|
|
173
171
|
disableScrollLock
|
|
174
172
|
open={ iconLibraryPopoverState.isOpen }
|
|
@@ -222,7 +220,6 @@ export const SvgMediaControl = createControl( ( { showIconLibrary = false, label
|
|
|
222
220
|
<SvgMediaOverlay
|
|
223
221
|
label={ groupLabel }
|
|
224
222
|
isAdmin={ isAdmin }
|
|
225
|
-
showIconLibrary={ showIconLibrary }
|
|
226
223
|
buttonGroupRef={ buttonGroupRef }
|
|
227
224
|
onSelectSvg={ handleSelectSvg }
|
|
228
225
|
onUpload={ handleUpload }
|
|
@@ -15,7 +15,6 @@ const ICON_LIBRARY_PADDING = 0.625;
|
|
|
15
15
|
type SvgMediaOverlayProps = {
|
|
16
16
|
label: string;
|
|
17
17
|
isAdmin: boolean;
|
|
18
|
-
showIconLibrary: boolean;
|
|
19
18
|
buttonGroupRef: React.Ref< HTMLDivElement >;
|
|
20
19
|
onSelectSvg: () => void;
|
|
21
20
|
onUpload: () => void;
|
|
@@ -27,7 +26,6 @@ type SvgMediaOverlayProps = {
|
|
|
27
26
|
export const SvgMediaOverlay = ( {
|
|
28
27
|
label,
|
|
29
28
|
isAdmin,
|
|
30
|
-
showIconLibrary,
|
|
31
29
|
buttonGroupRef,
|
|
32
30
|
onSelectSvg,
|
|
33
31
|
onUpload,
|
|
@@ -53,23 +51,21 @@ export const SvgMediaOverlay = ( {
|
|
|
53
51
|
</ConditionalControlInfotip>
|
|
54
52
|
}
|
|
55
53
|
/>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
>
|
|
70
|
-
|
|
71
|
-
</Button>
|
|
72
|
-
) : null }
|
|
54
|
+
<Button
|
|
55
|
+
size="tiny"
|
|
56
|
+
color="inherit"
|
|
57
|
+
variant="text"
|
|
58
|
+
startIcon={ <LibraryIcon sx={ { height: '18px', width: '16px' } } /> }
|
|
59
|
+
aria-label={ __( 'Icon library', 'elementor' ) }
|
|
60
|
+
onClick={ onOpenIconLibrary }
|
|
61
|
+
sx={ {
|
|
62
|
+
height: '28px',
|
|
63
|
+
p: ICON_LIBRARY_PADDING,
|
|
64
|
+
'.MuiButton-icon': { ml: 0 },
|
|
65
|
+
} }
|
|
66
|
+
>
|
|
67
|
+
<Typography>{ __( 'Icon library', 'elementor' ) }</Typography>
|
|
68
|
+
</Button>
|
|
73
69
|
</Stack>
|
|
74
70
|
);
|
|
75
71
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
+
import { useMemo } from 'react';
|
|
2
3
|
import { Box, CardMedia, CircularProgress } from '@elementor/ui';
|
|
3
4
|
import { __ } from '@wordpress/i18n';
|
|
4
5
|
|
|
5
6
|
import { findFontAwesome7Icon, type FontAwesome7Icon } from './icon-library/font-awesome-7-catalog';
|
|
6
7
|
import { FontAwesomeGlyph } from './icon-library/font-awesome-glyph';
|
|
7
8
|
import { useIconLibraryProviderIcons, useProviderIconSvg } from './icon-library/icon-library-providers';
|
|
9
|
+
import { mergeIconsById } from './icon-library/merge-icons-by-id';
|
|
8
10
|
import { useFontAwesome7Catalog } from './icon-library/use-font-awesome-7-catalog';
|
|
9
11
|
|
|
10
12
|
const ICON_PREVIEW_SIZE = 50;
|
|
@@ -21,25 +23,35 @@ export const SvgMediaPreview = ( { isFetching, src, iconClassName, iconLibrary }
|
|
|
21
23
|
const shouldLoadIconCatalog = Boolean( iconClassName && iconLibrary );
|
|
22
24
|
const { data: fontAwesomeIcons = [], isLoading: isFontAwesomeLoading } =
|
|
23
25
|
useFontAwesome7Catalog( shouldLoadIconCatalog );
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
const isFontAwesomeLibrary = Boolean( iconLibrary?.startsWith( 'fa-' ) );
|
|
27
|
+
const { data: providerIcons = [], isLoading: isProviderLoading } = useIconLibraryProviderIcons(
|
|
28
|
+
shouldLoadIconCatalog && ! isFontAwesomeLibrary
|
|
29
|
+
);
|
|
30
|
+
const icons = useMemo(
|
|
31
|
+
() => mergeIconsById( fontAwesomeIcons, providerIcons ),
|
|
32
|
+
[ fontAwesomeIcons, providerIcons ]
|
|
33
|
+
);
|
|
27
34
|
const selectedIcon = findFontAwesome7Icon( icons, iconClassName, iconLibrary );
|
|
28
|
-
const isLoadingCatalog =
|
|
35
|
+
const isLoadingCatalog =
|
|
36
|
+
shouldLoadIconCatalog && ( isFontAwesomeLoading || ( ! isFontAwesomeLibrary && isProviderLoading ) );
|
|
29
37
|
const { data: savedIconSvg, isLoading: isSavedIconLoading } = useProviderIconSvg(
|
|
30
38
|
iconLibrary,
|
|
31
39
|
iconClassName,
|
|
32
|
-
shouldLoadIconCatalog && ! isLoadingCatalog && ! selectedIcon
|
|
40
|
+
shouldLoadIconCatalog && ! isLoadingCatalog && ! selectedIcon && ! isFontAwesomeLibrary
|
|
33
41
|
);
|
|
34
42
|
|
|
35
43
|
if ( isFetching || isLoadingCatalog || isSavedIconLoading ) {
|
|
36
|
-
return <CircularProgress role="progressbar" />;
|
|
44
|
+
return <CircularProgress role="progressbar" aria-label={ __( 'Loading icon', 'elementor' ) } />;
|
|
37
45
|
}
|
|
38
46
|
|
|
39
47
|
if ( selectedIcon ) {
|
|
40
48
|
return <IconPreview icon={ selectedIcon } />;
|
|
41
49
|
}
|
|
42
50
|
|
|
51
|
+
if ( shouldLoadIconCatalog && iconClassName && iconLibrary && isFontAwesomeLibrary ) {
|
|
52
|
+
return <IconPreview icon={ createLazyIcon( iconLibrary, iconClassName ) } />;
|
|
53
|
+
}
|
|
54
|
+
|
|
43
55
|
if ( savedIconSvg ) {
|
|
44
56
|
return <IconPreview icon={ toSvgMarkupIcon( savedIconSvg, iconLibrary, iconClassName ) } />;
|
|
45
57
|
}
|
|
@@ -51,6 +63,19 @@ export const SvgMediaPreview = ( { isFetching, src, iconClassName, iconLibrary }
|
|
|
51
63
|
return <SvgPreview src={ src } />;
|
|
52
64
|
};
|
|
53
65
|
|
|
66
|
+
const createLazyIcon = ( library: string, value: string ): FontAwesome7Icon => ( {
|
|
67
|
+
id: `${ library }:${ value }`,
|
|
68
|
+
name: value,
|
|
69
|
+
label: value,
|
|
70
|
+
library,
|
|
71
|
+
value,
|
|
72
|
+
aliases: [],
|
|
73
|
+
width: ICON_PREVIEW_SIZE,
|
|
74
|
+
height: ICON_PREVIEW_SIZE,
|
|
75
|
+
paths: [],
|
|
76
|
+
lazySvg: true,
|
|
77
|
+
} );
|
|
78
|
+
|
|
54
79
|
const toSvgMarkupIcon = ( svgMarkup: string, library: string | null, value: string | null ): FontAwesome7Icon => ( {
|
|
55
80
|
id: `${ library }:${ value }`,
|
|
56
81
|
label: value ?? '',
|
|
@@ -65,7 +90,7 @@ const toSvgMarkupIcon = ( svgMarkup: string, library: string | null, value: stri
|
|
|
65
90
|
} );
|
|
66
91
|
|
|
67
92
|
const IconPreview = ( { icon }: { icon: FontAwesome7Icon } ) => (
|
|
68
|
-
<Box sx={ { color: ICON_PREVIEW_COLOR } }>
|
|
93
|
+
<Box sx={ { color: ICON_PREVIEW_COLOR, minWidth: ICON_PREVIEW_SIZE, minHeight: ICON_PREVIEW_SIZE } }>
|
|
69
94
|
<FontAwesomeGlyph
|
|
70
95
|
icon={ icon }
|
|
71
96
|
size={ ICON_PREVIEW_SIZE }
|
package/src/index.ts
CHANGED
|
@@ -39,6 +39,7 @@ export { PopoverContent } from './components/popover-content';
|
|
|
39
39
|
export { enqueueFont } from './controls/font-family-control/enqueue-font';
|
|
40
40
|
export {
|
|
41
41
|
registerIconLibraryProvider,
|
|
42
|
+
resolveProviderIconPreviewSvg,
|
|
42
43
|
resolveProviderIconSvg,
|
|
43
44
|
unregisterIconLibraryProvider,
|
|
44
45
|
type IconLibraryProvider,
|