@elementor/editor-controls 0.16.0 → 0.18.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 +44 -0
- package/dist/index.d.mts +31 -6
- package/dist/index.d.ts +31 -6
- package/dist/index.js +364 -167
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +366 -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/control-adornments/control-adornments-context.tsx +25 -0
- package/src/control-adornments/control-adornments.tsx +19 -0
- package/src/control-adornments/control-label-with-adornments.tsx +15 -0
- 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 +4 -1
- package/src/utils/link-restriction.ts +47 -0
- package/src/utils/types.ts +5 -0
|
@@ -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>
|
|
@@ -6,28 +6,54 @@ import { useBoundProp } from '../bound-prop-context';
|
|
|
6
6
|
import { ControlToggleButtonGroup, type ToggleButtonGroupItem } from '../components/control-toggle-button-group';
|
|
7
7
|
import { createControl } from '../create-control';
|
|
8
8
|
|
|
9
|
-
type ToggleControlProps< T extends PropValue > = {
|
|
10
|
-
options: ToggleButtonGroupItem< T >
|
|
9
|
+
export type ToggleControlProps< T extends PropValue > = {
|
|
10
|
+
options: Array< ToggleButtonGroupItem< T > & { exclusive?: boolean } >;
|
|
11
11
|
fullWidth?: boolean;
|
|
12
12
|
size?: ToggleButtonProps[ 'size' ];
|
|
13
|
+
exclusive?: boolean;
|
|
13
14
|
};
|
|
14
15
|
|
|
15
16
|
export const ToggleControl = createControl(
|
|
16
|
-
( {
|
|
17
|
+
( {
|
|
18
|
+
options,
|
|
19
|
+
fullWidth = false,
|
|
20
|
+
size = 'tiny',
|
|
21
|
+
exclusive = true,
|
|
22
|
+
}: ToggleControlProps< StringPropValue[ 'value' ] > ) => {
|
|
17
23
|
const { value, setValue } = useBoundProp( stringPropTypeUtil );
|
|
18
24
|
|
|
19
|
-
const
|
|
20
|
-
|
|
25
|
+
const exclusiveValues = options.filter( ( option ) => option.exclusive ).map( ( option ) => option.value );
|
|
26
|
+
|
|
27
|
+
const handleNonExclusiveToggle = ( selectedValues: StringPropValue[ 'value' ][] ) => {
|
|
28
|
+
const newSelectedValue = selectedValues[ selectedValues.length - 1 ];
|
|
29
|
+
const isNewSelectedValueExclusive = exclusiveValues.includes( newSelectedValue );
|
|
30
|
+
|
|
31
|
+
const updatedValues = isNewSelectedValueExclusive
|
|
32
|
+
? [ newSelectedValue ]
|
|
33
|
+
: selectedValues?.filter( ( val ) => ! exclusiveValues.includes( val ) );
|
|
34
|
+
|
|
35
|
+
setValue( updatedValues?.join( ' ' ) || null );
|
|
21
36
|
};
|
|
22
37
|
|
|
23
|
-
|
|
38
|
+
const toggleButtonGroupProps = {
|
|
39
|
+
items: options,
|
|
40
|
+
fullWidth,
|
|
41
|
+
size,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return exclusive ? (
|
|
24
45
|
<ControlToggleButtonGroup
|
|
25
|
-
|
|
46
|
+
{ ...toggleButtonGroupProps }
|
|
26
47
|
value={ value ?? null }
|
|
27
|
-
onChange={
|
|
48
|
+
onChange={ setValue }
|
|
28
49
|
exclusive={ true }
|
|
29
|
-
|
|
30
|
-
|
|
50
|
+
/>
|
|
51
|
+
) : (
|
|
52
|
+
<ControlToggleButtonGroup
|
|
53
|
+
{ ...toggleButtonGroupProps }
|
|
54
|
+
value={ value?.split( ' ' ) ?? [] }
|
|
55
|
+
onChange={ handleNonExclusiveToggle }
|
|
56
|
+
exclusive={ false }
|
|
31
57
|
/>
|
|
32
58
|
);
|
|
33
59
|
}
|
|
@@ -1,25 +1,24 @@
|
|
|
1
|
+
import { type FontCategory } from '@elementor/editor-controls';
|
|
2
|
+
|
|
1
3
|
export type FontListItem = {
|
|
2
4
|
type: 'font' | 'category';
|
|
3
5
|
value: string;
|
|
4
6
|
};
|
|
5
7
|
|
|
6
|
-
export const useFilteredFontFamilies = ( fontFamilies:
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if ( filteredFonts.length ) {
|
|
12
|
-
acc.push( { type: 'category', value: category } );
|
|
8
|
+
export const useFilteredFontFamilies = ( fontFamilies: FontCategory[], searchValue: string ) => {
|
|
9
|
+
return fontFamilies.reduce< FontListItem[] >( ( acc, category ) => {
|
|
10
|
+
const filteredFonts = category.fonts.filter( ( font ) =>
|
|
11
|
+
font.toLowerCase().includes( searchValue.toLowerCase() )
|
|
12
|
+
);
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
} );
|
|
17
|
-
}
|
|
14
|
+
if ( filteredFonts.length ) {
|
|
15
|
+
acc.push( { type: 'category', value: category.label } );
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
filteredFonts.forEach( ( font ) => {
|
|
18
|
+
acc.push( { type: 'font', value: font } );
|
|
19
|
+
} );
|
|
20
|
+
}
|
|
23
21
|
|
|
24
|
-
|
|
22
|
+
return acc;
|
|
23
|
+
}, [] );
|
|
25
24
|
};
|
package/src/index.ts
CHANGED
|
@@ -30,11 +30,14 @@ export type { ControlActionsItems } from './control-actions/control-actions-cont
|
|
|
30
30
|
export type { PropProviderProps } from './bound-prop-context';
|
|
31
31
|
export type { SetValue } from './bound-prop-context/prop-context';
|
|
32
32
|
export type { ExtendedValue } from './controls/size-control';
|
|
33
|
+
export type { ToggleControlProps } from './controls/toggle-control';
|
|
34
|
+
export type { FontCategory } from './controls/font-family-control/font-family-control';
|
|
33
35
|
|
|
34
36
|
// providers
|
|
35
37
|
export { createControlReplacement, ControlReplacementProvider } from './create-control-replacement';
|
|
36
38
|
export { ControlActionsProvider, useControlActions } from './control-actions/control-actions-context';
|
|
37
39
|
export { useBoundProp, PropProvider, PropKeyProvider } from './bound-prop-context';
|
|
38
|
-
|
|
40
|
+
export { ControlAdornmentsProvider } from './control-adornments/control-adornments-context';
|
|
41
|
+
export { ControlAdornments } from './control-adornments/control-adornments';
|
|
39
42
|
// hooks
|
|
40
43
|
export { useSyncExternalState } from './hooks/use-sync-external-state';
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { getContainer } from '@elementor/editor-elements';
|
|
2
|
+
|
|
3
|
+
type LinkRestriction =
|
|
4
|
+
| {
|
|
5
|
+
shouldRestrict: true;
|
|
6
|
+
restrictReason: 'ancestor' | 'descendant';
|
|
7
|
+
}
|
|
8
|
+
| {
|
|
9
|
+
shouldRestrict: false;
|
|
10
|
+
restrictReason?: never;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function getLinkRestriction( elementId: string ): LinkRestriction {
|
|
14
|
+
if ( getAncestorAnchor( elementId ) ) {
|
|
15
|
+
return {
|
|
16
|
+
shouldRestrict: true,
|
|
17
|
+
restrictReason: 'ancestor',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if ( getDescendantAnchor( elementId ) ) {
|
|
22
|
+
return {
|
|
23
|
+
shouldRestrict: true,
|
|
24
|
+
restrictReason: 'descendant',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return { shouldRestrict: false };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getAncestorAnchor( elementId: string ) {
|
|
32
|
+
const element = getElementView( elementId );
|
|
33
|
+
|
|
34
|
+
return element?.closest( 'a' ) || null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getDescendantAnchor( elementId: string ) {
|
|
38
|
+
const element = getElementView( elementId );
|
|
39
|
+
|
|
40
|
+
return element?.querySelector( 'a' ) || null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getElementView( id: string ) {
|
|
44
|
+
const elementContainer = getContainer( id );
|
|
45
|
+
|
|
46
|
+
return elementContainer?.view?.el || null;
|
|
47
|
+
}
|