@elementor/editor-editing-panel 0.17.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/index.d.mts +26 -9
  3. package/dist/index.d.ts +26 -9
  4. package/dist/index.js +859 -367
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +830 -329
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +8 -7
  9. package/src/components/settings-tab.tsx +5 -2
  10. package/src/components/style-sections/effects-section/box-shadow-repeater.tsx +224 -0
  11. package/src/components/style-sections/effects-section/effects-section.tsx +18 -0
  12. package/src/components/style-sections/position-section/z-index-control.tsx +11 -7
  13. package/src/components/style-sections/size-section.tsx +23 -20
  14. package/src/components/style-sections/spacing-section/linked-dimensions-control.tsx +62 -47
  15. package/src/components/style-sections/typography-section/font-size-control.tsx +10 -6
  16. package/src/components/style-sections/typography-section/font-weight-control.tsx +16 -12
  17. package/src/components/style-sections/typography-section/letter-spacing-control.tsx +10 -6
  18. package/src/components/style-sections/typography-section/text-alignment-control.tsx +12 -8
  19. package/src/components/style-sections/typography-section/text-color-control.tsx +10 -6
  20. package/src/components/style-sections/typography-section/text-direction-control.tsx +37 -0
  21. package/src/components/style-sections/typography-section/text-style-control.tsx +37 -34
  22. package/src/components/style-sections/typography-section/transform-control.tsx +14 -12
  23. package/src/components/style-sections/typography-section/typography-section.tsx +2 -0
  24. package/src/components/style-sections/typography-section/word-spacing-control.tsx +10 -6
  25. package/src/components/style-tab.tsx +5 -1
  26. package/src/controls/components/control-type-container.tsx +28 -0
  27. package/src/controls/components/repeater.tsx +197 -0
  28. package/src/controls/control-actions/actions/popover-action.tsx +58 -0
  29. package/src/controls/control-actions/control-actions-menu.ts +8 -0
  30. package/src/controls/control-actions/control-actions.tsx +43 -0
  31. package/src/controls/control-replacement.ts +15 -7
  32. package/src/controls/control-types/color-control.tsx +21 -18
  33. package/src/controls/control-types/image-control.tsx +56 -59
  34. package/src/controls/control-types/image-media-control.tsx +73 -0
  35. package/src/controls/control-types/number-control.tsx +13 -9
  36. package/src/controls/control-types/select-control.tsx +13 -9
  37. package/src/controls/control-types/size-control.tsx +17 -13
  38. package/src/controls/control-types/text-area-control.tsx +15 -11
  39. package/src/controls/control-types/text-control.tsx +9 -3
  40. package/src/controls/control-types/toggle-control.tsx +3 -2
  41. package/src/controls/control.tsx +1 -7
  42. package/src/controls/controls-registry.tsx +19 -10
  43. package/src/controls/create-control.tsx +31 -0
  44. package/src/controls/settings-control.tsx +2 -9
  45. package/src/dynamics/components/dynamic-selection-control.tsx +1 -1
  46. package/src/dynamics/components/dynamic-selection.tsx +1 -1
  47. package/src/dynamics/dynamic-control.tsx +1 -1
  48. package/src/dynamics/hooks/use-prop-dynamic-action.tsx +23 -0
  49. package/src/dynamics/hooks/use-prop-dynamic-tags.ts +4 -4
  50. package/src/dynamics/init.ts +9 -0
  51. package/src/dynamics/types.ts +6 -3
  52. package/src/dynamics/utils.ts +16 -3
  53. package/src/index.ts +2 -0
  54. package/src/types.ts +35 -14
  55. package/src/controls/components/control-container.tsx +0 -18
@@ -1,24 +1,27 @@
1
1
  import * as React from 'react';
2
- import { UnstableColorPicker } from '@elementor/ui';
2
+ import { UnstableColorPicker, UnstableColorPickerProps } from '@elementor/ui';
3
3
  import { useControl } from '../control-context';
4
+ import ControlActions from '../control-actions/control-actions';
5
+ import { createControl } from '../create-control';
6
+ import { TransformablePropValue } from '../../types';
4
7
 
5
- export const ColorControl = () => {
6
- const { value, setValue } = useControl< string >();
8
+ export type ColorPropValue = TransformablePropValue< 'color', string >;
7
9
 
8
- const handleChange = debounce( ( selectedColor: string ) => {
9
- setValue( selectedColor );
10
- } );
10
+ export const ColorControl = createControl(
11
+ ( props: Partial< Omit< UnstableColorPickerProps, 'value' | 'onChange' > > ) => {
12
+ const { value, setValue } = useControl< ColorPropValue >();
11
13
 
12
- return <UnstableColorPicker value={ value } onChange={ handleChange }></UnstableColorPicker>;
13
- };
14
+ const handleChange = ( selectedColor: string ) => {
15
+ setValue( {
16
+ $$type: 'color',
17
+ value: selectedColor,
18
+ } );
19
+ };
14
20
 
15
- // TODO: Remove this when the color picker component sends one event per color change [DES-422].
16
- const debounce = < TArgs extends unknown[], TReturn >( func: ( ...args: TArgs ) => TReturn, wait = 300 ) => {
17
- let timer: ReturnType< typeof setTimeout >;
18
-
19
- return ( ...args: TArgs ) => {
20
- clearTimeout( timer );
21
-
22
- timer = setTimeout( () => func( ...args ), wait );
23
- };
24
- };
21
+ return (
22
+ <ControlActions>
23
+ <UnstableColorPicker size="tiny" { ...props } value={ value?.value } onChange={ handleChange } />
24
+ </ControlActions>
25
+ );
26
+ }
27
+ );
@@ -1,69 +1,66 @@
1
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';
2
+ import { Grid, Stack } from '@elementor/ui';
5
3
  import { __ } from '@wordpress/i18n';
6
- import { useWpMediaAttachment, useWpMediaFrame } from '@elementor/wp-media';
4
+ import { ControlContext, useControl } from '../control-context';
5
+ import { type ImageSrc, ImageMediaControl } from './image-media-control';
6
+ import { SettingsControl } from '../settings-control';
7
+ import { PropValue, TransformablePropValue } from '../../types';
8
+ import { SelectControl } from './select-control';
9
+ import { createControl } from '../create-control';
7
10
 
8
- type Image = {
9
- $$type: 'image';
10
- value:
11
- | {
12
- attachmentId: number;
13
- url?: never;
14
- }
15
- | {
16
- attachmentId?: never;
17
- url: string;
18
- };
11
+ type Image = TransformablePropValue<
12
+ 'image',
13
+ {
14
+ src?: ImageSrc;
15
+ size?: string;
16
+ }
17
+ >;
18
+
19
+ type SetContextValue = ( v: PropValue ) => void;
20
+
21
+ export type ImageControlProps = {
22
+ sizes: { label: string; value: string }[];
19
23
  };
20
24
 
21
- export const ImageControl = () => {
25
+ export const ImageControl = createControl( ( props: ImageControlProps ) => {
22
26
  const { value, setValue } = useControl< Image >();
23
- const { data: attachment } = useWpMediaAttachment( value?.value?.attachmentId );
24
- const src = attachment?.url ?? value?.value?.url;
27
+ const { src, size } = value?.value || {};
25
28
 
26
- const { open } = useWpMediaFrame( {
27
- types: [ 'image' ],
28
- multiple: false,
29
- selected: value?.value?.attachmentId,
30
- onSelect: ( selectedAttachment ) => {
31
- setValue( {
32
- $$type: 'image',
33
- value: {
34
- attachmentId: selectedAttachment.id,
35
- },
36
- } );
37
- },
38
- } );
29
+ const setImageSrc = ( newValue: ImageSrc ) => {
30
+ setValue( {
31
+ $$type: 'image',
32
+ value: {
33
+ src: newValue,
34
+ size,
35
+ },
36
+ } );
37
+ };
39
38
 
40
- return (
41
- <Card variant="outlined">
42
- <CardMedia image={ src } sx={ { height: 150 } } />
43
- <CardOverlay>
44
- <Button
45
- color="inherit"
46
- size="small"
47
- variant="outlined"
48
- onClick={ () => {
49
- open( { mode: 'browse' } );
50
- } }
51
- >
52
- { __( 'Select Image', 'elementor' ) }
53
- </Button>
39
+ const setImageSize = ( newValue: string ) => {
40
+ setValue( {
41
+ $$type: 'image',
42
+ value: {
43
+ src,
44
+ size: newValue,
45
+ },
46
+ } );
47
+ };
54
48
 
55
- <Button
56
- color="inherit"
57
- size="small"
58
- variant="text"
59
- startIcon={ <UploadIcon /> }
60
- onClick={ () => {
61
- open( { mode: 'upload' } );
62
- } }
63
- >
64
- { __( 'Upload Image', 'elementor' ) }
65
- </Button>
66
- </CardOverlay>
67
- </Card>
49
+ return (
50
+ <Stack gap={ 2 }>
51
+ <ControlContext.Provider value={ { setValue: setImageSrc as SetContextValue, value: src, bind: 'src' } }>
52
+ <ImageMediaControl />
53
+ </ControlContext.Provider>
54
+ <ControlContext.Provider value={ { setValue: setImageSize as SetContextValue, value: size, bind: 'size' } }>
55
+ <Grid container spacing={ 1 } alignItems="center">
56
+ <Grid item xs={ 6 }>
57
+ <SettingsControl.Label> { __( 'Image Resolution', 'elementor' ) }</SettingsControl.Label>
58
+ </Grid>
59
+ <Grid item xs={ 6 }>
60
+ <SelectControl options={ props.sizes } />
61
+ </Grid>
62
+ </Grid>
63
+ </ControlContext.Provider>
64
+ </Stack>
68
65
  );
69
- };
66
+ } );
@@ -0,0 +1,73 @@
1
+ import * as React from 'react';
2
+ import { Button, Card, CardMedia, CardOverlay, Stack } from '@elementor/ui';
3
+ import { UploadIcon } from '@elementor/icons';
4
+ import { useWpMediaAttachment, useWpMediaFrame } from '@elementor/wp-media';
5
+ import { useControl } from '../control-context';
6
+ import { TransformablePropValue } from '../../types';
7
+ import { __ } from '@wordpress/i18n';
8
+ import ControlActions from '../control-actions/control-actions';
9
+ import { createControl } from '../create-control';
10
+
11
+ type ImageAttachmentID = TransformablePropValue< 'image-attachment-id', number >;
12
+
13
+ type Url = TransformablePropValue< 'url', string >;
14
+
15
+ export type ImageSrc = TransformablePropValue<
16
+ 'image-src',
17
+ { id: ImageAttachmentID; url: null } | { url: Url; id: null }
18
+ >;
19
+
20
+ export const ImageMediaControl = createControl( () => {
21
+ const { value, setValue } = useControl< ImageSrc >();
22
+ const { id, url } = value?.value ?? {};
23
+
24
+ const { data: attachment } = useWpMediaAttachment( id?.value || null );
25
+ const src = attachment?.url ?? url;
26
+
27
+ const { open } = useWpMediaFrame( {
28
+ types: [ 'image' ],
29
+ multiple: false,
30
+ selected: id?.value || null,
31
+ onSelect: ( selectedAttachment ) => {
32
+ setValue( {
33
+ $$type: 'image-src',
34
+ value: {
35
+ id: {
36
+ $$type: 'image-attachment-id',
37
+ value: selectedAttachment.id,
38
+ },
39
+ url: null,
40
+ },
41
+ } );
42
+ },
43
+ } );
44
+
45
+ return (
46
+ <Card variant="outlined">
47
+ <CardMedia image={ src } sx={ { height: 150 } } />
48
+ <CardOverlay>
49
+ <ControlActions>
50
+ <Stack gap={ 0.5 }>
51
+ <Button
52
+ size="tiny"
53
+ color="inherit"
54
+ variant="outlined"
55
+ onClick={ () => open( { mode: 'browse' } ) }
56
+ >
57
+ { __( 'Select Image', 'elementor' ) }
58
+ </Button>
59
+ <Button
60
+ size="tiny"
61
+ variant="text"
62
+ color="inherit"
63
+ startIcon={ <UploadIcon /> }
64
+ onClick={ () => open( { mode: 'upload' } ) }
65
+ >
66
+ { __( 'Upload Image', 'elementor' ) }
67
+ </Button>
68
+ </Stack>
69
+ </ControlActions>
70
+ </CardOverlay>
71
+ </Card>
72
+ );
73
+ } );
@@ -1,11 +1,13 @@
1
1
  import * as React from 'react';
2
2
  import { TextField } from '@elementor/ui';
3
3
  import { useControl } from '../control-context';
4
+ import ControlActions from '../control-actions/control-actions';
5
+ import { createControl } from '../create-control';
4
6
 
5
7
  const isEmptyOrNaN = ( value?: string | number ) =>
6
8
  value === undefined || value === '' || Number.isNaN( Number( value ) );
7
9
 
8
- export const NumberControl = ( { placeholder }: { placeholder?: string } ) => {
10
+ export const NumberControl = createControl( ( { placeholder }: { placeholder?: string } ) => {
9
11
  const { value, setValue } = useControl< number | undefined >();
10
12
 
11
13
  const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
@@ -14,12 +16,14 @@ export const NumberControl = ( { placeholder }: { placeholder?: string } ) => {
14
16
  };
15
17
 
16
18
  return (
17
- <TextField
18
- size="tiny"
19
- type="number"
20
- value={ isEmptyOrNaN( value ) ? '' : value }
21
- onChange={ handleChange }
22
- placeholder={ placeholder }
23
- />
19
+ <ControlActions>
20
+ <TextField
21
+ size="tiny"
22
+ type="number"
23
+ value={ isEmptyOrNaN( value ) ? '' : value }
24
+ onChange={ handleChange }
25
+ placeholder={ placeholder }
26
+ />
27
+ </ControlActions>
24
28
  );
25
- };
29
+ } );
@@ -2,12 +2,14 @@ import * as React from 'react';
2
2
  import { MenuItem, Select, SelectChangeEvent } from '@elementor/ui';
3
3
  import { useControl } from '../control-context';
4
4
  import { PropValue } from '../../types';
5
+ import ControlActions from '../control-actions/control-actions';
6
+ import { createControl } from '../create-control';
5
7
 
6
8
  type Props< T > = {
7
9
  options: Array< { label: string; value: T; disabled?: boolean } >;
8
10
  };
9
11
 
10
- export const SelectControl = < T extends PropValue >( { options }: Props< T > ) => {
12
+ export const SelectControl = createControl( < T extends PropValue >( { options }: Props< T > ) => {
11
13
  const { value, setValue } = useControl< T >();
12
14
 
13
15
  const handleChange = ( event: SelectChangeEvent< T > ) => {
@@ -15,12 +17,14 @@ export const SelectControl = < T extends PropValue >( { options }: Props< T > )
15
17
  };
16
18
 
17
19
  return (
18
- <Select size="tiny" value={ value ?? '' } onChange={ handleChange }>
19
- { options.map( ( { label, ...props } ) => (
20
- <MenuItem key={ props.value } { ...props }>
21
- { label }
22
- </MenuItem>
23
- ) ) }
24
- </Select>
20
+ <ControlActions>
21
+ <Select size="tiny" value={ value ?? '' } onChange={ handleChange }>
22
+ { options.map( ( { label, ...props } ) => (
23
+ <MenuItem key={ props.value } { ...props }>
24
+ { label }
25
+ </MenuItem>
26
+ ) ) }
27
+ </Select>
28
+ </ControlActions>
25
29
  );
26
- };
30
+ } );
@@ -4,12 +4,14 @@ import { TransformablePropValue } from '../../types';
4
4
  import { useControl } from '../control-context';
5
5
  import { useSyncExternalState } from '../hooks/use-sync-external-state';
6
6
  import { SelectionEndAdornment, TextFieldInnerSelection } from '../components/text-field-inner-selection';
7
+ import ControlActions from '../control-actions/control-actions';
8
+ import { createControl } from '../create-control';
7
9
 
8
10
  export type Unit = 'px' | '%' | 'em' | 'rem' | 'vw';
9
11
 
10
12
  const defaultUnits: Unit[] = [ 'px', '%', 'em', 'rem', 'vw' ];
11
13
 
12
- export type SizeControlValue = TransformablePropValue< { unit: Unit; size: number } >;
14
+ export type SizeControlValue = TransformablePropValue< 'size', { unit: Unit; size: number } >;
13
15
 
14
16
  export type SizeControlProps = {
15
17
  placeholder?: string;
@@ -17,7 +19,7 @@ export type SizeControlProps = {
17
19
  units?: Unit[];
18
20
  };
19
21
 
20
- export const SizeControl = ( { units = defaultUnits, placeholder, startIcon }: SizeControlProps ) => {
22
+ export const SizeControl = createControl( ( { units = defaultUnits, placeholder, startIcon }: SizeControlProps ) => {
21
23
  const { value, setValue } = useControl< SizeControlValue >();
22
24
 
23
25
  const [ state, setState ] = useSyncExternalState< SizeControlValue >( {
@@ -53,15 +55,17 @@ export const SizeControl = ( { units = defaultUnits, placeholder, startIcon }: S
53
55
  };
54
56
 
55
57
  return (
56
- <TextFieldInnerSelection
57
- endAdornment={
58
- <SelectionEndAdornment options={ units } onClick={ handleUnitChange } value={ state.value.unit } />
59
- }
60
- placeholder={ placeholder }
61
- startAdornment={ startIcon ?? <InputAdornment position="start">{ startIcon }</InputAdornment> }
62
- type="number"
63
- value={ Number.isNaN( state.value.size ) ? '' : state.value.size }
64
- onChange={ handleSizeChange }
65
- />
58
+ <ControlActions>
59
+ <TextFieldInnerSelection
60
+ endAdornment={
61
+ <SelectionEndAdornment options={ units } onClick={ handleUnitChange } value={ state.value.unit } />
62
+ }
63
+ placeholder={ placeholder }
64
+ startAdornment={ startIcon ?? <InputAdornment position="start">{ startIcon }</InputAdornment> }
65
+ type="number"
66
+ value={ Number.isNaN( state.value.size ) ? '' : state.value.size }
67
+ onChange={ handleSizeChange }
68
+ />
69
+ </ControlActions>
66
70
  );
67
- };
71
+ } );
@@ -1,12 +1,14 @@
1
1
  import * as React from 'react';
2
2
  import { TextField } from '@elementor/ui';
3
3
  import { useControl } from '../control-context';
4
+ import ControlActions from '../control-actions/control-actions';
5
+ import { createControl } from '../create-control';
4
6
 
5
7
  type Props = {
6
8
  placeholder?: string;
7
9
  };
8
10
 
9
- export const TextAreaControl = ( { placeholder }: Props ) => {
11
+ export const TextAreaControl = createControl( ( { placeholder }: Props ) => {
10
12
  const { value, setValue } = useControl< string >();
11
13
 
12
14
  const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
@@ -14,14 +16,16 @@ export const TextAreaControl = ( { placeholder }: Props ) => {
14
16
  };
15
17
 
16
18
  return (
17
- <TextField
18
- size="tiny"
19
- multiline
20
- fullWidth
21
- rows={ 5 }
22
- value={ value }
23
- onChange={ handleChange }
24
- placeholder={ placeholder }
25
- />
19
+ <ControlActions fullWidth>
20
+ <TextField
21
+ size="tiny"
22
+ multiline
23
+ fullWidth
24
+ rows={ 5 }
25
+ value={ value }
26
+ onChange={ handleChange }
27
+ placeholder={ placeholder }
28
+ />
29
+ </ControlActions>
26
30
  );
27
- };
31
+ } );
@@ -1,11 +1,17 @@
1
1
  import * as React from 'react';
2
2
  import { TextField } from '@elementor/ui';
3
3
  import { useControl } from '../control-context';
4
+ import ControlActions from '../control-actions/control-actions';
5
+ import { createControl } from '../create-control';
4
6
 
5
- export const TextControl = ( { placeholder }: { placeholder?: string } ) => {
7
+ export const TextControl = createControl( ( { placeholder }: { placeholder?: string } ) => {
6
8
  const { value, setValue } = useControl< string >( '' );
7
9
 
8
10
  const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => setValue( event.target.value );
9
11
 
10
- return <TextField type="text" size="tiny" value={ value } onChange={ handleChange } placeholder={ placeholder } />;
11
- };
12
+ return (
13
+ <ControlActions fullWidth>
14
+ <TextField type="text" size="tiny" value={ value } onChange={ handleChange } placeholder={ placeholder } />
15
+ </ControlActions>
16
+ );
17
+ } );
@@ -2,12 +2,13 @@ import * as React from 'react';
2
2
  import { useControl } from '../control-context';
3
3
  import { ControlToggleButtonGroup, ToggleButtonGroupItem } from '../components/control-toggle-button-group';
4
4
  import { PropValue } from '../../types';
5
+ import { createControl } from '../create-control';
5
6
 
6
7
  type ToggleControlProps< T extends PropValue > = {
7
8
  options: ToggleButtonGroupItem< T >[];
8
9
  };
9
10
 
10
- export const ToggleControl = < T extends PropValue >( { options }: ToggleControlProps< T > ) => {
11
+ export const ToggleControl = createControl( < T extends PropValue >( { options }: ToggleControlProps< T > ) => {
11
12
  const { value, setValue } = useControl< T >();
12
13
 
13
14
  const handleToggle = ( option: T | null ) => {
@@ -22,4 +23,4 @@ export const ToggleControl = < T extends PropValue >( { options }: ToggleControl
22
23
  exclusive={ true }
23
24
  />
24
25
  );
25
- };
26
+ } );
@@ -1,7 +1,5 @@
1
1
  import * as React from 'react';
2
2
  import type { ComponentProps } from 'react';
3
- import { getControlReplacement } from './control-replacement';
4
- import { useControl } from './control-context';
5
3
  import { createError } from '@elementor/utils';
6
4
  import { ControlType, ControlTypes, getControlByType } from './controls-registry';
7
5
 
@@ -33,8 +31,6 @@ type ControlProps< T extends ControlType > = AnyPropertyRequired< ComponentProps
33
31
  };
34
32
 
35
33
  export const Control = < T extends ControlType >( { props, type }: ControlProps< T > ) => {
36
- const { value } = useControl();
37
-
38
34
  const ControlByType = getControlByType( type );
39
35
 
40
36
  if ( ! ControlByType ) {
@@ -43,8 +39,6 @@ export const Control = < T extends ControlType >( { props, type }: ControlProps<
43
39
  } );
44
40
  }
45
41
 
46
- const ControlComponent = getControlReplacement( { value } ) || ControlByType;
47
-
48
42
  // @ts-expect-error ControlComponent props are inferred from the type (T).
49
- return <ControlComponent { ...props } />;
43
+ return <ControlByType { ...props } />;
50
44
  };
@@ -3,17 +3,26 @@ import { TextControl } from './control-types/text-control';
3
3
  import { TextAreaControl } from './control-types/text-area-control';
4
4
  import { SizeControl } from './control-types/size-control';
5
5
  import { SelectControl } from './control-types/select-control';
6
+ import { ControlComponent } from './create-control';
6
7
 
7
- export const controlTypes = {
8
- image: ImageControl,
9
- text: TextControl,
10
- textarea: TextAreaControl,
11
- size: SizeControl,
12
- select: SelectControl,
13
- } as const;
8
+ export type ControlLayout = 'full' | 'two-columns';
14
9
 
15
- export type ControlTypes = typeof controlTypes;
10
+ type ControlRegistry = Record< string, { component: ControlComponent; layout: ControlLayout } >;
16
11
 
17
- export type ControlType = keyof ControlTypes;
12
+ const controlTypes = {
13
+ image: { component: ImageControl, layout: 'full' },
14
+ text: { component: TextControl, layout: 'two-columns' },
15
+ textarea: { component: TextAreaControl, layout: 'full' },
16
+ size: { component: SizeControl, layout: 'two-columns' },
17
+ select: { component: SelectControl, layout: 'two-columns' },
18
+ } as const satisfies ControlRegistry;
18
19
 
19
- export const getControlByType = ( type: ControlType ) => controlTypes[ type ];
20
+ export type ControlType = keyof typeof controlTypes;
21
+
22
+ export type ControlTypes = {
23
+ [ key in ControlType ]: ( typeof controlTypes )[ key ][ 'component' ];
24
+ };
25
+
26
+ export const getControlByType = ( type: ControlType ) => controlTypes[ type ]?.component;
27
+
28
+ export const getLayoutByType = ( type: ControlType ) => controlTypes[ type ].layout;
@@ -0,0 +1,31 @@
1
+ import * as React from 'react';
2
+ import { ComponentProps, ComponentType } from 'react';
3
+ import { useControlReplacement } from './control-replacement';
4
+
5
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6
+ type AnyComponentType = ComponentType< any >;
7
+
8
+ type Options = {
9
+ supportsReplacements?: boolean;
10
+ };
11
+
12
+ const brandSymbol = Symbol( 'control' );
13
+
14
+ export type ControlComponent< TComponent extends AnyComponentType = AnyComponentType > = TComponent & {
15
+ [ brandSymbol ]: true;
16
+ };
17
+
18
+ export function createControl< T extends AnyComponentType >(
19
+ Component: T,
20
+ { supportsReplacements = true }: Options = {}
21
+ ) {
22
+ return ( ( props: ComponentProps< T > ) => {
23
+ const ControlReplacement = useControlReplacement();
24
+
25
+ if ( ControlReplacement && supportsReplacements ) {
26
+ return <ControlReplacement { ...props } />;
27
+ }
28
+
29
+ return <Component { ...props } />;
30
+ } ) as ControlComponent< T >;
31
+ }
@@ -5,17 +5,16 @@ import { useElementContext } from '../contexts/element-context';
5
5
  import { useWidgetSettings } from '../hooks/use-widget-settings';
6
6
  import { updateSettings } from '../sync/update-settings';
7
7
  import { ControlLabel } from '../components/control-label';
8
- import { ControlContainer } from './components/control-container';
9
8
 
10
9
  type Props = {
11
10
  bind: PropKey;
12
11
  children: React.ReactNode;
13
12
  };
14
13
 
15
- export const SettingsControlProvider = ( { bind, children }: Props ) => {
14
+ const SettingsControl = ( { bind, children }: Props ) => {
16
15
  const { element, elementType } = useElementContext();
17
16
 
18
- const defaultValue = elementType.propsSchema[ bind ]?.type.default;
17
+ const defaultValue = elementType.propsSchema[ bind ]?.default;
19
18
  const settingsValue = useWidgetSettings( { id: element.id, bind } );
20
19
  const value = settingsValue ?? defaultValue ?? null;
21
20
 
@@ -31,12 +30,6 @@ export const SettingsControlProvider = ( { bind, children }: Props ) => {
31
30
  return <ControlContext.Provider value={ { setValue, value, bind } }>{ children }</ControlContext.Provider>;
32
31
  };
33
32
 
34
- const SettingsControl = ( { children, bind }: Props ) => (
35
- <SettingsControlProvider bind={ bind }>
36
- <ControlContainer flexWrap="wrap">{ children }</ControlContainer>
37
- </SettingsControlProvider>
38
- );
39
-
40
33
  // TODO: When we start using useControl inside the label component, we should create a new component for it,
41
34
  // and keep ControlLabel as a simple label component without context.
42
35
  SettingsControl.Label = ControlLabel;
@@ -51,7 +51,7 @@ export const DynamicSelectionControl = () => {
51
51
  }
52
52
 
53
53
  return (
54
- <Box sx={ { width: '100%' } }>
54
+ <Box>
55
55
  <Tag
56
56
  fullWidth
57
57
  showActionsOnHover
@@ -54,7 +54,7 @@ export const DynamicSelection = ( { onSelect }: DynamicSelectionProps ) => {
54
54
  updatePropValueHistory( currentValue );
55
55
  }
56
56
 
57
- setValue( { $$type: 'dynamic', value: { name: value } } );
57
+ setValue( { $$type: 'dynamic', value: { name: value, settings: {} } } );
58
58
 
59
59
  onSelect?.();
60
60
  };
@@ -18,7 +18,7 @@ export const DynamicControl = ( { bind, children }: DynamicControlProps ) => {
18
18
  throw new Error( `Dynamic tag ${ name } not found` );
19
19
  }
20
20
 
21
- const defaultValue = dynamicTag.props_schema[ bind ]?.type.default;
21
+ const defaultValue = dynamicTag.props_schema[ bind ]?.default;
22
22
  const dynamicValue = settings?.[ bind ] ?? defaultValue;
23
23
 
24
24
  const setDynamicValue = ( newValue: PropValue ) => {
@@ -0,0 +1,23 @@
1
+ import * as React from 'react';
2
+ import { useElementContext } from '../../contexts/element-context';
3
+ import { supportsDynamic } from '../utils';
4
+ import { DynamicSelection } from '../components/dynamic-selection';
5
+ import { DatabaseIcon } from '@elementor/icons';
6
+ import { __ } from '@wordpress/i18n';
7
+ import { useControl } from '../../controls/control-context';
8
+ import { PopoverActionProps } from '../../controls/control-actions/actions/popover-action';
9
+
10
+ export const usePropDynamicAction = (): PopoverActionProps => {
11
+ const { bind } = useControl();
12
+ const { elementType } = useElementContext();
13
+
14
+ const propType = elementType.propsSchema[ bind ];
15
+ const visible = !! propType && supportsDynamic( propType );
16
+
17
+ return {
18
+ visible,
19
+ icon: DatabaseIcon,
20
+ title: __( 'Dynamic Tags', 'elementor' ),
21
+ popoverContent: ( { closePopover } ) => <DynamicSelection onSelect={ closePopover } />,
22
+ };
23
+ };