@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.
@@ -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 >( defaultState );
17
- const propValue = value.value;
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
- setValue( { $$type: 'size', value: { ...propValue, unit } } );
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 { valueAsNumber: size } = event.target;
42
+ const { value: size } = event.target;
27
43
 
28
- if ( Number.isNaN( size ) ) {
29
- return;
30
- }
31
-
32
- setValue( { $$type: 'size', value: { ...propValue, size } } );
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={ propValue.size }
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={ propValue.unit }
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
+ };
package/src/types.ts CHANGED
@@ -24,7 +24,7 @@ export type Control = {
24
24
  type: 'control';
25
25
  value: {
26
26
  bind: string;
27
- label: string;
27
+ label?: string;
28
28
  description?: string;
29
29
  type: string;
30
30
  props: Record< string, unknown >;