@elementor/editor-editing-panel 0.9.1 → 0.11.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 +12 -0
- package/dist/index.js +171 -53
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +162 -44
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -6
- package/src/components/settings-tab.tsx +1 -1
- package/src/controls/control-context.tsx +2 -2
- package/src/controls/control-types/attachment-control.tsx +101 -0
- package/src/controls/control-types/size-control.tsx +30 -17
- package/src/controls/get-control-by-type.ts +2 -0
- package/src/controls/hooks/use-sync-external-state.tsx +49 -0
- package/src/types.ts +1 -1
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Button, Card, CardMedia, CardOverlay } from '@elementor/ui';
|
|
3
|
+
import { useControl } from '../control-context';
|
|
4
|
+
import { UploadIcon } from '@elementor/icons';
|
|
5
|
+
import { __ } from '@wordpress/i18n';
|
|
6
|
+
import { useWpMediaAttachment, useWpMediaFrame } from '@elementor/wp-media';
|
|
7
|
+
|
|
8
|
+
type ImageAttachment = {
|
|
9
|
+
$$type: 'image-attachment';
|
|
10
|
+
value: {
|
|
11
|
+
id: number;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type ImageURL = {
|
|
16
|
+
$$type: 'image-url';
|
|
17
|
+
value: {
|
|
18
|
+
url: string;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type Props = {
|
|
23
|
+
mediaTypes: Array< 'image' >;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const isImageAttachment = ( value: ImageAttachment | ImageURL | undefined ): value is ImageAttachment => {
|
|
27
|
+
return value?.$$type === 'image-attachment';
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const isImageUrl = ( value: ImageAttachment | ImageURL | undefined ): value is ImageURL => {
|
|
31
|
+
return value?.$$type === 'image-url';
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// TODO: Use schema to get default image.
|
|
35
|
+
const defaultState: ImageURL = {
|
|
36
|
+
$$type: 'image-url',
|
|
37
|
+
value: {
|
|
38
|
+
url: '/wp-content/plugins/elementor/assets/images/placeholder.png',
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const AttachmentControl = ( props: Props ) => {
|
|
43
|
+
const { value, setValue } = useControl< ImageAttachment | ImageURL >( defaultState );
|
|
44
|
+
const { data: attachment } = useWpMediaAttachment( isImageAttachment( value ) ? value.value.id : undefined );
|
|
45
|
+
|
|
46
|
+
const getImageSrc = () => {
|
|
47
|
+
if ( attachment?.url ) {
|
|
48
|
+
return attachment.url;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if ( isImageUrl( value ) ) {
|
|
52
|
+
return value.value.url;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return defaultState.value.url;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const { open } = useWpMediaFrame( {
|
|
59
|
+
types: props.mediaTypes,
|
|
60
|
+
multiple: false,
|
|
61
|
+
selected: isImageAttachment( value ) ? value.value?.id : undefined,
|
|
62
|
+
onSelect: ( val ) => {
|
|
63
|
+
setValue( {
|
|
64
|
+
$$type: 'image-attachment',
|
|
65
|
+
value: {
|
|
66
|
+
id: val.id,
|
|
67
|
+
},
|
|
68
|
+
} );
|
|
69
|
+
},
|
|
70
|
+
} );
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<Card variant="outlined">
|
|
74
|
+
<CardMedia image={ getImageSrc() } sx={ { height: 150 } } />
|
|
75
|
+
<CardOverlay>
|
|
76
|
+
<Button
|
|
77
|
+
color="inherit"
|
|
78
|
+
size="small"
|
|
79
|
+
variant="outlined"
|
|
80
|
+
onClick={ () => {
|
|
81
|
+
open( { mode: 'browse' } );
|
|
82
|
+
} }
|
|
83
|
+
>
|
|
84
|
+
{ __( 'Select Image', 'elementor' ) }
|
|
85
|
+
</Button>
|
|
86
|
+
|
|
87
|
+
<Button
|
|
88
|
+
color="inherit"
|
|
89
|
+
size="small"
|
|
90
|
+
variant="text"
|
|
91
|
+
startIcon={ <UploadIcon /> }
|
|
92
|
+
onClick={ () => {
|
|
93
|
+
open( { mode: 'upload' } );
|
|
94
|
+
} }
|
|
95
|
+
>
|
|
96
|
+
{ __( 'Upload Image', 'elementor' ) }
|
|
97
|
+
</Button>
|
|
98
|
+
</CardOverlay>
|
|
99
|
+
</Card>
|
|
100
|
+
);
|
|
101
|
+
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { MenuItem, Select, SelectChangeEvent, Stack, TextField } from '@elementor/ui';
|
|
3
|
-
import { useControl } from '../control-context';
|
|
4
3
|
import { TransformablePropValue } from '../../types';
|
|
4
|
+
import { useControl } from '../control-context';
|
|
5
|
+
import { useSyncExternalState } from '../hooks/use-sync-external-state';
|
|
5
6
|
|
|
6
7
|
export type SizeControlProps = {
|
|
7
8
|
units: Unit[];
|
|
@@ -13,23 +14,40 @@ export type Unit = 'px' | '%' | 'em' | 'rem' | 'vw';
|
|
|
13
14
|
export type SizeControlValue = TransformablePropValue< { unit: Unit; size: number } >;
|
|
14
15
|
|
|
15
16
|
export const SizeControl = ( { units, placeholder }: SizeControlProps ) => {
|
|
16
|
-
const { value, setValue } = useControl< SizeControlValue >(
|
|
17
|
-
|
|
17
|
+
const { value, setValue } = useControl< SizeControlValue >();
|
|
18
|
+
|
|
19
|
+
const [ state, setState ] = useSyncExternalState< SizeControlValue >( {
|
|
20
|
+
external: value,
|
|
21
|
+
setExternal: setValue,
|
|
22
|
+
persistWhen: ( controlValue ) => !! controlValue?.value.size || controlValue?.value.size === 0,
|
|
23
|
+
fallback: ( controlValue ) => ( {
|
|
24
|
+
$$type: 'size',
|
|
25
|
+
value: { unit: controlValue?.value.unit || 'px', size: NaN },
|
|
26
|
+
} ),
|
|
27
|
+
} );
|
|
18
28
|
|
|
19
29
|
const handleUnitChange = ( event: SelectChangeEvent< Unit > ) => {
|
|
20
30
|
const unit = event.target.value as Unit;
|
|
21
31
|
|
|
22
|
-
|
|
32
|
+
setState( ( prev ) => ( {
|
|
33
|
+
...prev,
|
|
34
|
+
value: {
|
|
35
|
+
...prev.value,
|
|
36
|
+
unit,
|
|
37
|
+
},
|
|
38
|
+
} ) );
|
|
23
39
|
};
|
|
24
40
|
|
|
25
41
|
const handleSizeChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
|
|
26
|
-
const {
|
|
42
|
+
const { value: size } = event.target;
|
|
27
43
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
44
|
+
setState( ( prev ) => ( {
|
|
45
|
+
...prev,
|
|
46
|
+
value: {
|
|
47
|
+
...prev.value,
|
|
48
|
+
size: size || size === '0' ? parseFloat( size ) : NaN,
|
|
49
|
+
},
|
|
50
|
+
} ) );
|
|
33
51
|
};
|
|
34
52
|
|
|
35
53
|
return (
|
|
@@ -37,13 +55,13 @@ export const SizeControl = ( { units, placeholder }: SizeControlProps ) => {
|
|
|
37
55
|
<TextField
|
|
38
56
|
size="tiny"
|
|
39
57
|
type="number"
|
|
40
|
-
value={
|
|
58
|
+
value={ Number.isNaN( state.value.size ) ? '' : state.value.size }
|
|
41
59
|
onChange={ handleSizeChange }
|
|
42
60
|
placeholder={ placeholder }
|
|
43
61
|
/>
|
|
44
62
|
<Select
|
|
45
63
|
size="tiny"
|
|
46
|
-
value={
|
|
64
|
+
value={ state.value.unit }
|
|
47
65
|
onChange={ handleUnitChange }
|
|
48
66
|
MenuProps={ {
|
|
49
67
|
anchorOrigin: { vertical: 'bottom', horizontal: 'right' },
|
|
@@ -59,8 +77,3 @@ export const SizeControl = ( { units, placeholder }: SizeControlProps ) => {
|
|
|
59
77
|
</Stack>
|
|
60
78
|
);
|
|
61
79
|
};
|
|
62
|
-
|
|
63
|
-
const defaultState: SizeControlValue = {
|
|
64
|
-
$$type: 'size',
|
|
65
|
-
value: { unit: 'px', size: 0 },
|
|
66
|
-
};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { SelectControl } from './control-types/select-control';
|
|
2
2
|
import { TextAreaControl } from './control-types/text-area-control';
|
|
3
3
|
import { TextControl } from './control-types/text-control';
|
|
4
|
+
import { AttachmentControl } from './control-types/attachment-control';
|
|
4
5
|
|
|
5
6
|
const controlTypes = {
|
|
7
|
+
attachment: AttachmentControl,
|
|
6
8
|
select: SelectControl,
|
|
7
9
|
text: TextControl,
|
|
8
10
|
textarea: TextAreaControl,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
type UseInternalStateOptions< TValue > = {
|
|
4
|
+
external: TValue | undefined;
|
|
5
|
+
setExternal: ( value: TValue | undefined ) => void;
|
|
6
|
+
persistWhen: ( value: TValue | undefined ) => boolean;
|
|
7
|
+
fallback: ( value: TValue | undefined ) => TValue;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const useSyncExternalState = < TValue, >( {
|
|
11
|
+
external,
|
|
12
|
+
setExternal,
|
|
13
|
+
persistWhen,
|
|
14
|
+
fallback,
|
|
15
|
+
}: UseInternalStateOptions< TValue > ) => {
|
|
16
|
+
function toExternal( internalValue: TValue | undefined ) {
|
|
17
|
+
if ( persistWhen( internalValue ) ) {
|
|
18
|
+
return internalValue;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function toInternal( externalValue: TValue | undefined, internalValue: TValue | undefined ) {
|
|
25
|
+
if ( ! externalValue ) {
|
|
26
|
+
return fallback( internalValue );
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return externalValue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const [ internal, setInternal ] = useState< TValue >( toInternal( external, undefined ) );
|
|
33
|
+
|
|
34
|
+
useEffect( () => {
|
|
35
|
+
setInternal( toInternal( external, internal ) );
|
|
36
|
+
}, [ external ] );
|
|
37
|
+
|
|
38
|
+
type SetterFunc = ( value: TValue ) => TValue;
|
|
39
|
+
|
|
40
|
+
const setInternalValue = ( setter: SetterFunc | TValue ) => {
|
|
41
|
+
const setterFn = ( typeof setter === 'function' ? setter : () => setter ) as SetterFunc;
|
|
42
|
+
const updated = setterFn( internal );
|
|
43
|
+
|
|
44
|
+
setInternal( updated );
|
|
45
|
+
setExternal( toExternal( updated ) );
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return [ internal, setInternalValue ] as const;
|
|
49
|
+
};
|