@elementor/editor-editing-panel 0.18.0 → 1.0.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 (115) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/dist/index.d.mts +8 -24
  3. package/dist/index.d.ts +8 -24
  4. package/dist/index.js +1452 -1122
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +1436 -1099
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +15 -14
  9. package/src/components/accordion-section.tsx +3 -2
  10. package/src/components/add-or-remove-content.tsx +42 -0
  11. package/src/components/collapsible-field.tsx +34 -0
  12. package/src/components/editing-panel-error-fallback.tsx +12 -0
  13. package/src/components/editing-panel.tsx +27 -19
  14. package/src/components/settings-tab.tsx +12 -11
  15. package/src/components/style-sections/background-section/background-color-field.tsx +21 -0
  16. package/src/components/style-sections/background-section/background-section.tsx +15 -0
  17. package/src/components/style-sections/border-section/border-color-field.tsx +21 -0
  18. package/src/components/style-sections/border-section/border-field.tsx +47 -0
  19. package/src/components/style-sections/border-section/border-radius-field.tsx +48 -0
  20. package/src/components/style-sections/border-section/border-section.tsx +16 -0
  21. package/src/components/style-sections/border-section/border-style-field.tsx +32 -0
  22. package/src/components/style-sections/border-section/border-width-field.tsx +42 -0
  23. package/src/components/style-sections/effects-section/effects-section.tsx +5 -5
  24. package/src/components/style-sections/position-section/dimensions-field.tsx +46 -0
  25. package/src/components/style-sections/position-section/position-field.tsx +28 -0
  26. package/src/components/style-sections/position-section/position-section.tsx +15 -2
  27. package/src/components/style-sections/position-section/z-index-field.tsx +21 -0
  28. package/src/components/style-sections/size-section/overflow-field.tsx +45 -0
  29. package/src/components/style-sections/size-section/size-section.tsx +55 -0
  30. package/src/components/style-sections/spacing-section/spacing-section.tsx +6 -6
  31. package/src/components/style-sections/typography-section/font-size-field.tsx +21 -0
  32. package/src/components/style-sections/typography-section/{font-weight-control.tsx → font-weight-field.tsx} +7 -6
  33. package/src/components/style-sections/typography-section/letter-spacing-field.tsx +21 -0
  34. package/src/components/style-sections/typography-section/{text-alignment-control.tsx → text-alignment-field.tsx} +8 -8
  35. package/src/components/style-sections/typography-section/text-color-field.tsx +21 -0
  36. package/src/components/style-sections/typography-section/{text-direction-control.tsx → text-direction-field.tsx} +8 -8
  37. package/src/components/style-sections/typography-section/text-stroke-field.tsx +16 -0
  38. package/src/components/style-sections/typography-section/{text-style-control.tsx → text-style-field.tsx} +5 -5
  39. package/src/components/style-sections/typography-section/{transform-control.tsx → transform-field.tsx} +8 -7
  40. package/src/components/style-sections/typography-section/typography-section.tsx +20 -18
  41. package/src/components/style-sections/typography-section/word-spacing-field.tsx +21 -0
  42. package/src/components/style-tab.tsx +44 -25
  43. package/src/contexts/classes-prop-context.tsx +24 -0
  44. package/src/contexts/element-context.tsx +6 -10
  45. package/src/contexts/style-context.tsx +8 -22
  46. package/src/control-replacement.tsx +3 -0
  47. package/src/controls/bound-prop-context.tsx +30 -0
  48. package/src/controls/components/control-toggle-button-group.tsx +15 -6
  49. package/src/controls/components/repeater.tsx +1 -1
  50. package/src/controls/components/text-field-inner-selection.tsx +20 -24
  51. package/src/controls/control-actions/control-actions-context.tsx +27 -0
  52. package/src/controls/control-actions/control-actions-menu.ts +6 -7
  53. package/src/controls/control-actions/control-actions.tsx +14 -26
  54. package/src/{components/style-sections/effects-section/box-shadow-repeater.tsx → controls/controls/box-shadow-repeater-control.tsx} +51 -65
  55. package/src/controls/controls/color-control.tsx +25 -0
  56. package/src/controls/controls/equal-unequal-sizes-control.tsx +196 -0
  57. package/src/controls/{control-types → controls}/image-control.tsx +15 -23
  58. package/src/controls/{control-types → controls}/image-media-control.tsx +5 -14
  59. package/src/{components/style-sections/spacing-section → controls/controls}/linked-dimensions-control.tsx +10 -26
  60. package/src/controls/{control-types → controls}/number-control.tsx +2 -2
  61. package/src/controls/{control-types → controls}/select-control.tsx +4 -4
  62. package/src/controls/{control-types → controls}/size-control.tsx +8 -8
  63. package/src/controls/controls/stroke-control.tsx +105 -0
  64. package/src/controls/{control-types → controls}/text-area-control.tsx +3 -3
  65. package/src/controls/{control-types → controls}/text-control.tsx +3 -3
  66. package/src/controls/{control-types → controls}/toggle-control.tsx +4 -4
  67. package/src/controls/create-control-replacement.tsx +53 -0
  68. package/src/controls/create-control.tsx +12 -3
  69. package/src/controls/index.ts +24 -0
  70. package/src/controls-actions.ts +8 -0
  71. package/src/{controls/components → controls-registry}/control-type-container.tsx +1 -1
  72. package/src/{controls → controls-registry}/controls-registry.tsx +1 -6
  73. package/src/controls-registry/settings-field.tsx +35 -0
  74. package/src/controls-registry/styles-field.tsx +19 -0
  75. package/src/dynamics/components/dynamic-selection-control.tsx +11 -11
  76. package/src/dynamics/components/dynamic-selection.tsx +6 -6
  77. package/src/dynamics/dynamic-control.tsx +6 -5
  78. package/src/dynamics/hooks/use-dynamic-tag.ts +2 -2
  79. package/src/dynamics/hooks/use-prop-dynamic-action.tsx +7 -7
  80. package/src/dynamics/hooks/use-prop-dynamic-tags.ts +3 -3
  81. package/src/dynamics/hooks/use-prop-value-history.ts +3 -3
  82. package/src/dynamics/init.ts +2 -4
  83. package/src/dynamics/types.ts +2 -1
  84. package/src/dynamics/utils.ts +1 -2
  85. package/src/hooks/use-styles-field.ts +30 -0
  86. package/src/index.ts +3 -4
  87. package/src/sync/should-use-v2-panel.ts +1 -2
  88. package/src/sync/types.ts +3 -2
  89. package/src/components/style-sections/position-section/z-index-control.tsx +0 -20
  90. package/src/components/style-sections/size-section.tsx +0 -49
  91. package/src/components/style-sections/typography-section/font-size-control.tsx +0 -20
  92. package/src/components/style-sections/typography-section/letter-spacing-control.tsx +0 -20
  93. package/src/components/style-sections/typography-section/text-color-control.tsx +0 -20
  94. package/src/components/style-sections/typography-section/word-spacing-control.tsx +0 -20
  95. package/src/controls/control-context.tsx +0 -22
  96. package/src/controls/control-replacement.ts +0 -34
  97. package/src/controls/control-types/color-control.tsx +0 -27
  98. package/src/controls/hooks/use-style-control.ts +0 -29
  99. package/src/controls/settings-control.tsx +0 -37
  100. package/src/controls/style-control.tsx +0 -20
  101. package/src/hooks/use-element-style-prop.ts +0 -45
  102. package/src/hooks/use-element-styles.ts +0 -13
  103. package/src/hooks/use-element-type.ts +0 -33
  104. package/src/hooks/use-selected-elements.ts +0 -9
  105. package/src/hooks/use-widget-settings.ts +0 -16
  106. package/src/props/is-transformable.ts +0 -14
  107. package/src/sync/get-container.ts +0 -8
  108. package/src/sync/get-element-styles.ts +0 -9
  109. package/src/sync/get-selected-elements.ts +0 -21
  110. package/src/sync/get-widgets-cache.ts +0 -7
  111. package/src/sync/update-settings.ts +0 -14
  112. package/src/sync/update-style.ts +0 -24
  113. package/src/types.ts +0 -89
  114. package/src/{controls → controls-registry}/control.tsx +0 -0
  115. package/src/{controls/control-actions/actions/popover-action.tsx → popover-action.tsx} +1 -1
@@ -0,0 +1,196 @@
1
+ import * as React from 'react';
2
+ import { ReactNode, useId, useRef } from 'react';
3
+ import { PropValue, TransformablePropValue } from '@elementor/editor-props';
4
+ import { bindPopover, bindToggle, Grid, Popover, Stack, ToggleButton, usePopupState } from '@elementor/ui';
5
+ import { __ } from '@wordpress/i18n';
6
+ import { ControlLabel } from '../../components/control-label';
7
+ import { BoundPropProvider, SizeControl, SizeControlValue, useBoundProp } from '../index';
8
+
9
+ type MultiSizePropValue< TMultiPropType extends string > = TransformablePropValue<
10
+ TMultiPropType,
11
+ Record< string, SizeControlValue >
12
+ >;
13
+
14
+ type Item< TMultiPropType extends string, TPropValue extends MultiSizePropValue< TMultiPropType > > = {
15
+ icon: ReactNode;
16
+ label: string;
17
+ bind: keyof TPropValue[ 'value' ];
18
+ };
19
+
20
+ export type EqualUnequalItems<
21
+ TMultiPropType extends string,
22
+ TPropValue extends MultiSizePropValue< TMultiPropType >,
23
+ > = [
24
+ Item< TMultiPropType, TPropValue >,
25
+ Item< TMultiPropType, TPropValue >,
26
+ Item< TMultiPropType, TPropValue >,
27
+ Item< TMultiPropType, TPropValue >,
28
+ ];
29
+
30
+ type Props< TMultiPropType extends string, TPropValue extends MultiSizePropValue< TMultiPropType > > = {
31
+ label: string;
32
+ icon: ReactNode;
33
+ items: EqualUnequalItems< TMultiPropType, TPropValue >;
34
+ multiSizeType: TMultiPropType;
35
+ };
36
+
37
+ function hasMixedSizes( values: SizeControlValue[] ): boolean {
38
+ const [ firstValue, ...restValues ] = values;
39
+
40
+ return restValues.some(
41
+ ( value ) => value?.value?.size !== firstValue?.value?.size || value?.value?.unit !== firstValue?.value?.unit
42
+ );
43
+ }
44
+
45
+ export function EqualUnequalSizesControl<
46
+ TMultiPropType extends string,
47
+ TPropValue extends MultiSizePropValue< TMultiPropType >,
48
+ >( { label, icon, items, multiSizeType }: Props< TMultiPropType, TPropValue > ) {
49
+ const popupId = useId();
50
+ const controlRef = useRef< HTMLElement >( null );
51
+ const { value: controlValue, setValue: setControlValue } = useBoundProp< TPropValue >();
52
+
53
+ const actualValue: TPropValue[ 'value' ] = controlValue?.value ?? {};
54
+ const setActualValue = ( newValue: TPropValue[ 'value' ] ) => {
55
+ setControlValue( { $$type: multiSizeType, value: newValue } as TPropValue );
56
+ };
57
+
58
+ const setNestedProp = ( item: Item< TMultiPropType, TPropValue >, newValue: PropValue ) => {
59
+ const { bind } = item;
60
+
61
+ const newValues = {
62
+ ...actualValue,
63
+ [ bind ]: newValue,
64
+ };
65
+
66
+ setActualValue( newValues );
67
+ };
68
+
69
+ const setEqualValues = ( newValue: PropValue ) => {
70
+ const equalValues = items.reduce( ( values: TPropValue[ 'value' ], item ) => {
71
+ return {
72
+ ...values,
73
+ [ item.bind ]: newValue,
74
+ };
75
+ }, {} );
76
+
77
+ setActualValue( equalValues );
78
+ };
79
+
80
+ const popupState = usePopupState( {
81
+ variant: 'popover',
82
+ popupId,
83
+ } );
84
+
85
+ return (
86
+ <>
87
+ <Grid container alignItems="center" ref={ controlRef }>
88
+ <Grid item xs={ 6 }>
89
+ <ControlLabel>{ label }</ControlLabel>
90
+ </Grid>
91
+ <Grid item xs={ 6 }>
92
+ <EqualValuesControl
93
+ value={ actualValue }
94
+ setValue={ setEqualValues }
95
+ iconButton={
96
+ <ToggleButton
97
+ size={ 'tiny' }
98
+ value={ 'check' }
99
+ sx={ { marginLeft: 'auto' } }
100
+ { ...bindToggle( popupState ) }
101
+ selected={ popupState.isOpen }
102
+ >
103
+ { icon }
104
+ </ToggleButton>
105
+ }
106
+ />
107
+ </Grid>
108
+ </Grid>
109
+ <Popover
110
+ disablePortal
111
+ disableScrollLock
112
+ anchorOrigin={ {
113
+ vertical: 'bottom',
114
+ horizontal: 'right',
115
+ } }
116
+ transformOrigin={ {
117
+ vertical: 'top',
118
+ horizontal: 'right',
119
+ } }
120
+ { ...bindPopover( popupState ) }
121
+ slotProps={ {
122
+ paper: { sx: { mt: 0.5, p: 2, pt: 1, width: controlRef.current?.getBoundingClientRect().width } },
123
+ } }
124
+ >
125
+ <Stack gap={ 1.5 }>
126
+ <Grid container spacing={ 2 } alignItems="center">
127
+ <NestedValueControl item={ items[ 0 ] } value={ actualValue } setNestedProp={ setNestedProp } />
128
+ <NestedValueControl item={ items[ 1 ] } value={ actualValue } setNestedProp={ setNestedProp } />
129
+ </Grid>
130
+ <Grid container spacing={ 2 } alignItems="center">
131
+ <NestedValueControl item={ items[ 3 ] } value={ actualValue } setNestedProp={ setNestedProp } />
132
+ <NestedValueControl item={ items[ 2 ] } value={ actualValue } setNestedProp={ setNestedProp } />
133
+ </Grid>
134
+ </Stack>
135
+ </Popover>
136
+ </>
137
+ );
138
+ }
139
+
140
+ const NestedValueControl = < TMultiPropType extends string, TPropValue extends MultiSizePropValue< TMultiPropType > >( {
141
+ item,
142
+ value,
143
+ setNestedProp,
144
+ }: {
145
+ item: Item< TMultiPropType, TPropValue >;
146
+ value: TPropValue[ 'value' ] | undefined;
147
+ setNestedProp: ( item: Item< TMultiPropType, TPropValue >, newValue: PropValue ) => void;
148
+ } ) => {
149
+ const { bind } = item;
150
+
151
+ const nestedValue = value?.[ bind ] ? value[ bind ] : undefined;
152
+
153
+ return (
154
+ <BoundPropProvider bind={ '' } setValue={ ( val ) => setNestedProp( item, val ) } value={ nestedValue }>
155
+ <Grid item xs={ 6 }>
156
+ <Grid container spacing={ 1 } alignItems="center">
157
+ <Grid item xs={ 12 }>
158
+ <ControlLabel>{ item.label }</ControlLabel>
159
+ </Grid>
160
+ <Grid item xs={ 12 }>
161
+ <SizeControl startIcon={ item.icon } />
162
+ </Grid>
163
+ </Grid>
164
+ </Grid>
165
+ </BoundPropProvider>
166
+ );
167
+ };
168
+
169
+ const EqualValuesControl = <
170
+ TMultiPropType extends string,
171
+ TPropValue extends MultiSizePropValue< TMultiPropType >[ 'value' ],
172
+ >( {
173
+ value,
174
+ setValue,
175
+ iconButton,
176
+ }: {
177
+ value: TPropValue | undefined;
178
+ setValue: ( newValue: TPropValue ) => void;
179
+ iconButton: ReactNode;
180
+ } ) => {
181
+ const values = Object.values( value ?? {} ) as SizeControlValue[];
182
+ const isMixed = hasMixedSizes( values );
183
+
184
+ return (
185
+ <BoundPropProvider
186
+ bind={ '' }
187
+ setValue={ ( val ) => setValue( val as TPropValue ) }
188
+ value={ isMixed ? undefined : values[ 0 ] }
189
+ >
190
+ <Stack direction="row" alignItems="center" gap={ 1 }>
191
+ <SizeControl placeholder={ __( 'MIXED', 'elementor' ) } />
192
+ { iconButton }
193
+ </Stack>
194
+ </BoundPropProvider>
195
+ );
196
+ };
@@ -1,20 +1,12 @@
1
1
  import * as React from 'react';
2
- import { Grid, Stack } from '@elementor/ui';
3
2
  import { __ } from '@wordpress/i18n';
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';
3
+ import { BoundPropProvider, useBoundProp } from '../bound-prop-context';
4
+ import { Grid, Stack } from '@elementor/ui';
5
+ import { PropValue, ImagePropValue, SizePropValue, ImageSrcPropValue } from '@elementor/editor-props';
6
+ import { ImageMediaControl } from './image-media-control';
8
7
  import { SelectControl } from './select-control';
9
8
  import { createControl } from '../create-control';
10
-
11
- type Image = TransformablePropValue<
12
- 'image',
13
- {
14
- src?: ImageSrc;
15
- size?: string;
16
- }
17
- >;
9
+ import { ControlLabel } from '../../components/control-label';
18
10
 
19
11
  type SetContextValue = ( v: PropValue ) => void;
20
12
 
@@ -23,24 +15,24 @@ export type ImageControlProps = {
23
15
  };
24
16
 
25
17
  export const ImageControl = createControl( ( props: ImageControlProps ) => {
26
- const { value, setValue } = useControl< Image >();
18
+ const { value, setValue } = useBoundProp< ImagePropValue | undefined >();
27
19
  const { src, size } = value?.value || {};
28
20
 
29
- const setImageSrc = ( newValue: ImageSrc ) => {
21
+ const setImageSrc = ( newValue: ImageSrcPropValue ) => {
30
22
  setValue( {
31
23
  $$type: 'image',
32
24
  value: {
33
25
  src: newValue,
34
- size,
26
+ size: size as SizePropValue,
35
27
  },
36
28
  } );
37
29
  };
38
30
 
39
- const setImageSize = ( newValue: string ) => {
31
+ const setImageSize = ( newValue: SizePropValue ) => {
40
32
  setValue( {
41
33
  $$type: 'image',
42
34
  value: {
43
- src,
35
+ src: src as ImageSrcPropValue,
44
36
  size: newValue,
45
37
  },
46
38
  } );
@@ -48,19 +40,19 @@ export const ImageControl = createControl( ( props: ImageControlProps ) => {
48
40
 
49
41
  return (
50
42
  <Stack gap={ 2 }>
51
- <ControlContext.Provider value={ { setValue: setImageSrc as SetContextValue, value: src, bind: 'src' } }>
43
+ <BoundPropProvider value={ src } setValue={ setImageSrc as SetContextValue } bind={ 'src' }>
52
44
  <ImageMediaControl />
53
- </ControlContext.Provider>
54
- <ControlContext.Provider value={ { setValue: setImageSize as SetContextValue, value: size, bind: 'size' } }>
45
+ </BoundPropProvider>
46
+ <BoundPropProvider value={ size } setValue={ setImageSize as SetContextValue } bind={ 'size' }>
55
47
  <Grid container spacing={ 1 } alignItems="center">
56
48
  <Grid item xs={ 6 }>
57
- <SettingsControl.Label> { __( 'Image Resolution', 'elementor' ) }</SettingsControl.Label>
49
+ <ControlLabel> { __( 'Image Resolution', 'elementor' ) }</ControlLabel>
58
50
  </Grid>
59
51
  <Grid item xs={ 6 }>
60
52
  <SelectControl options={ props.sizes } />
61
53
  </Grid>
62
54
  </Grid>
63
- </ControlContext.Provider>
55
+ </BoundPropProvider>
64
56
  </Stack>
65
57
  );
66
58
  } );
@@ -1,24 +1,15 @@
1
1
  import * as React from 'react';
2
- import { Button, Card, CardMedia, CardOverlay, Stack } from '@elementor/ui';
2
+ import { __ } from '@wordpress/i18n';
3
+ import { ImageSrcPropValue } from '@elementor/editor-props';
3
4
  import { UploadIcon } from '@elementor/icons';
5
+ import { Button, Card, CardMedia, CardOverlay, Stack } from '@elementor/ui';
4
6
  import { useWpMediaAttachment, useWpMediaFrame } from '@elementor/wp-media';
5
- import { useControl } from '../control-context';
6
- import { TransformablePropValue } from '../../types';
7
- import { __ } from '@wordpress/i18n';
7
+ import { useBoundProp } from '../bound-prop-context';
8
8
  import ControlActions from '../control-actions/control-actions';
9
9
  import { createControl } from '../create-control';
10
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
11
  export const ImageMediaControl = createControl( () => {
21
- const { value, setValue } = useControl< ImageSrc >();
12
+ const { value, setValue } = useBoundProp< ImageSrcPropValue >();
22
13
  const { id, url } = value?.value ?? {};
23
14
 
24
15
  const { data: attachment } = useWpMediaAttachment( id?.value || null );
@@ -1,27 +1,17 @@
1
1
  import * as React from 'react';
2
2
  import { __ } from '@wordpress/i18n';
3
3
  import { Grid, Stack, ToggleButton } from '@elementor/ui';
4
+ import { PropValue, LinkedDimensionsPropValue } from '@elementor/editor-props';
4
5
  import { DetachIcon, LinkIcon, SideBottomIcon, SideLeftIcon, SideRightIcon, SideTopIcon } from '@elementor/icons';
5
- import { PropValue, TransformablePropValue } from '../../../types';
6
- import { SizeControl } from '../../../controls/control-types/size-control';
7
- import { ControlLabel } from '../../control-label';
8
- import { ControlContext, useControl } from '../../../controls/control-context';
6
+ import { createControl } from '../create-control';
7
+ import { ControlLabel } from '../../components/control-label';
8
+ import { SizeControl } from './size-control';
9
+ import { BoundPropProvider, useBoundProp } from '../bound-prop-context';
9
10
 
10
11
  export type Position = 'top' | 'right' | 'bottom' | 'left';
11
12
 
12
- export type LinkedDimensionsValue = TransformablePropValue<
13
- 'linked-dimensions',
14
- {
15
- isLinked: boolean;
16
- top: PropValue;
17
- right: PropValue;
18
- bottom: PropValue;
19
- left: PropValue;
20
- }
21
- >;
22
-
23
- export const LinkedDimensionsControl = ( { label }: { label: string } ) => {
24
- const { value, setValue } = useControl< LinkedDimensionsValue >();
13
+ export const LinkedDimensionsControl = createControl( ( { label }: { label: string } ) => {
14
+ const { value, setValue } = useBoundProp< LinkedDimensionsPropValue >();
25
15
  const { top, right, bottom, left, isLinked = true } = value?.value || {};
26
16
 
27
17
  const setLinkedValue = ( position: Position, newValue: PropValue ) => {
@@ -130,7 +120,7 @@ export const LinkedDimensionsControl = ( { label }: { label: string } ) => {
130
120
  </Stack>
131
121
  </>
132
122
  );
133
- };
123
+ } );
134
124
 
135
125
  const Control = ( {
136
126
  bind,
@@ -143,13 +133,7 @@ const Control = ( {
143
133
  startIcon: React.ReactNode;
144
134
  setValue: ( bind: Position, newValue: PropValue ) => void;
145
135
  } ) => (
146
- <ControlContext.Provider
147
- value={ {
148
- bind,
149
- setValue: ( newValue ) => setValue( bind, newValue ),
150
- value,
151
- } }
152
- >
136
+ <BoundPropProvider setValue={ ( newValue ) => setValue( bind, newValue ) } value={ value } bind={ bind }>
153
137
  <SizeControl startIcon={ startIcon } />
154
- </ControlContext.Provider>
138
+ </BoundPropProvider>
155
139
  );
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { TextField } from '@elementor/ui';
3
- import { useControl } from '../control-context';
3
+ import { useBoundProp } from '../bound-prop-context';
4
4
  import ControlActions from '../control-actions/control-actions';
5
5
  import { createControl } from '../create-control';
6
6
 
@@ -8,7 +8,7 @@ const isEmptyOrNaN = ( value?: string | number ) =>
8
8
  value === undefined || value === '' || Number.isNaN( Number( value ) );
9
9
 
10
10
  export const NumberControl = createControl( ( { placeholder }: { placeholder?: string } ) => {
11
- const { value, setValue } = useControl< number | undefined >();
11
+ const { value, setValue } = useBoundProp< number | undefined >();
12
12
 
13
13
  const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
14
14
  const eventValue: string = event.target.value;
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
+ import { PropValue } from '@elementor/editor-props';
2
3
  import { MenuItem, Select, SelectChangeEvent } from '@elementor/ui';
3
- import { useControl } from '../control-context';
4
- import { PropValue } from '../../types';
4
+ import { useBoundProp } from '../bound-prop-context';
5
5
  import ControlActions from '../control-actions/control-actions';
6
6
  import { createControl } from '../create-control';
7
7
 
@@ -10,7 +10,7 @@ type Props< T > = {
10
10
  };
11
11
 
12
12
  export const SelectControl = createControl( < T extends PropValue >( { options }: Props< T > ) => {
13
- const { value, setValue } = useControl< T >();
13
+ const { value, setValue } = useBoundProp< T >();
14
14
 
15
15
  const handleChange = ( event: SelectChangeEvent< T > ) => {
16
16
  setValue( event.target.value as T );
@@ -18,7 +18,7 @@ export const SelectControl = createControl( < T extends PropValue >( { options }
18
18
 
19
19
  return (
20
20
  <ControlActions>
21
- <Select size="tiny" value={ value ?? '' } onChange={ handleChange }>
21
+ <Select displayEmpty size="tiny" value={ value ?? '' } onChange={ handleChange } fullWidth>
22
22
  { options.map( ( { label, ...props } ) => (
23
23
  <MenuItem key={ props.value } { ...props }>
24
24
  { label }
@@ -1,17 +1,15 @@
1
1
  import * as React from 'react';
2
+ import { SizePropValue, TransformablePropValue } from '@elementor/editor-props';
2
3
  import { InputAdornment } from '@elementor/ui';
3
- import { TransformablePropValue } from '../../types';
4
- import { useControl } from '../control-context';
4
+ import { useBoundProp } from '../bound-prop-context';
5
5
  import { useSyncExternalState } from '../hooks/use-sync-external-state';
6
6
  import { SelectionEndAdornment, TextFieldInnerSelection } from '../components/text-field-inner-selection';
7
7
  import ControlActions from '../control-actions/control-actions';
8
8
  import { createControl } from '../create-control';
9
9
 
10
- export type Unit = 'px' | '%' | 'em' | 'rem' | 'vw';
10
+ export type Unit = 'px' | '%' | 'em' | 'rem' | 'vw' | 'vh';
11
11
 
12
- const defaultUnits: Unit[] = [ 'px', '%', 'em', 'rem', 'vw' ];
13
-
14
- export type SizeControlValue = TransformablePropValue< 'size', { unit: Unit; size: number } >;
12
+ const defaultUnits: Unit[] = [ 'px', '%', 'em', 'rem', 'vw', 'vh' ];
15
13
 
16
14
  export type SizeControlProps = {
17
15
  placeholder?: string;
@@ -19,10 +17,12 @@ export type SizeControlProps = {
19
17
  units?: Unit[];
20
18
  };
21
19
 
20
+ export type SizeControlValue = TransformablePropValue< 'size', { unit: Unit; size: number } >;
21
+
22
22
  export const SizeControl = createControl( ( { units = defaultUnits, placeholder, startIcon }: SizeControlProps ) => {
23
- const { value, setValue } = useControl< SizeControlValue >();
23
+ const { value, setValue } = useBoundProp< SizePropValue | undefined >();
24
24
 
25
- const [ state, setState ] = useSyncExternalState< SizeControlValue >( {
25
+ const [ state, setState ] = useSyncExternalState< SizePropValue >( {
26
26
  external: value,
27
27
  setExternal: setValue,
28
28
  persistWhen: ( controlValue ) => !! controlValue?.value.size || controlValue?.value.size === 0,
@@ -0,0 +1,105 @@
1
+ import * as React from 'react';
2
+ import { __ } from '@wordpress/i18n';
3
+ import { Grid, Stack } from '@elementor/ui';
4
+ import { createControl } from '../create-control';
5
+ import { SizeControl, Unit } from './size-control';
6
+ import { ControlLabel } from '../../components/control-label';
7
+ import { ColorControl } from './color-control';
8
+ import { PropValue, StrokePropValue, TransformablePropValue } from '@elementor/editor-props';
9
+ import { BoundPropProvider, useBoundProp } from '../bound-prop-context';
10
+
11
+ type SetContextValue = ( v: PropValue ) => void;
12
+
13
+ const defaultStrokeControlValue: StrokePropValue = {
14
+ $$type: 'stroke',
15
+ value: {
16
+ color: {
17
+ $$type: 'color',
18
+ value: '#000000',
19
+ },
20
+ width: {
21
+ $$type: 'size',
22
+ value: {
23
+ unit: 'px',
24
+ size: NaN,
25
+ },
26
+ },
27
+ },
28
+ };
29
+
30
+ const units: Unit[] = [ 'px', 'em', 'rem' ];
31
+
32
+ export const StrokeControl = createControl( () => {
33
+ const { value, setValue } = useBoundProp< StrokePropValue >( defaultStrokeControlValue );
34
+
35
+ const setStrokeWidth = ( newValue: TransformablePropValue< 'size', { unit: Unit; size: number } > ) => {
36
+ const updatedValue = {
37
+ ...( value?.value ?? defaultStrokeControlValue.value ),
38
+ width: newValue,
39
+ };
40
+
41
+ setValue( {
42
+ $$type: 'stroke',
43
+ value: updatedValue,
44
+ } );
45
+ };
46
+
47
+ const setStrokeColor = ( newValue: TransformablePropValue< 'color', string > ) => {
48
+ const updatedValue = {
49
+ ...( value?.value ?? defaultStrokeControlValue.value ),
50
+ color: newValue,
51
+ };
52
+
53
+ setValue( {
54
+ $$type: 'stroke',
55
+ value: updatedValue,
56
+ } );
57
+ };
58
+
59
+ return (
60
+ <Stack gap={ 1.5 }>
61
+ <Control
62
+ bind="width"
63
+ label={ __( 'Stroke Width', 'elementor' ) }
64
+ value={ value?.value.width ?? defaultStrokeControlValue.value.width }
65
+ setValue={ setStrokeWidth }
66
+ >
67
+ <SizeControl units={ units } />
68
+ </Control>
69
+
70
+ <Control
71
+ bind="color"
72
+ label={ __( 'Stroke Color', 'elementor' ) }
73
+ value={ value?.value.color ?? defaultStrokeControlValue.value.color }
74
+ setValue={ setStrokeColor }
75
+ >
76
+ <ColorControl />
77
+ </Control>
78
+ </Stack>
79
+ );
80
+ } );
81
+
82
+ const Control = < T extends PropValue >( {
83
+ bind,
84
+ value,
85
+ setValue,
86
+ label,
87
+ children,
88
+ }: {
89
+ bind: string;
90
+ value: T;
91
+ setValue: ( v: T ) => void;
92
+ label: string;
93
+ children: React.ReactNode;
94
+ } ) => (
95
+ <BoundPropProvider bind={ bind } value={ value } setValue={ setValue as SetContextValue }>
96
+ <Grid container spacing={ 1 } alignItems="center">
97
+ <Grid item xs={ 6 }>
98
+ <ControlLabel>{ label }</ControlLabel>
99
+ </Grid>
100
+ <Grid item xs={ 6 }>
101
+ { children }
102
+ </Grid>
103
+ </Grid>
104
+ </BoundPropProvider>
105
+ );
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { TextField } from '@elementor/ui';
3
- import { useControl } from '../control-context';
3
+ import { useBoundProp } from '../bound-prop-context';
4
4
  import ControlActions from '../control-actions/control-actions';
5
5
  import { createControl } from '../create-control';
6
6
 
@@ -9,14 +9,14 @@ type Props = {
9
9
  };
10
10
 
11
11
  export const TextAreaControl = createControl( ( { placeholder }: Props ) => {
12
- const { value, setValue } = useControl< string >();
12
+ const { value, setValue } = useBoundProp< string >();
13
13
 
14
14
  const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
15
15
  setValue( event.target.value );
16
16
  };
17
17
 
18
18
  return (
19
- <ControlActions fullWidth>
19
+ <ControlActions>
20
20
  <TextField
21
21
  size="tiny"
22
22
  multiline
@@ -1,16 +1,16 @@
1
1
  import * as React from 'react';
2
2
  import { TextField } from '@elementor/ui';
3
- import { useControl } from '../control-context';
3
+ import { useBoundProp } from '../bound-prop-context';
4
4
  import ControlActions from '../control-actions/control-actions';
5
5
  import { createControl } from '../create-control';
6
6
 
7
7
  export const TextControl = createControl( ( { placeholder }: { placeholder?: string } ) => {
8
- const { value, setValue } = useControl< string >( '' );
8
+ const { value, setValue } = useBoundProp< string >( '' );
9
9
 
10
10
  const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => setValue( event.target.value );
11
11
 
12
12
  return (
13
- <ControlActions fullWidth>
13
+ <ControlActions>
14
14
  <TextField type="text" size="tiny" value={ value } onChange={ handleChange } placeholder={ placeholder } />
15
15
  </ControlActions>
16
16
  );
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
- import { useControl } from '../control-context';
2
+ import { useBoundProp } from '../bound-prop-context';
3
3
  import { ControlToggleButtonGroup, ToggleButtonGroupItem } from '../components/control-toggle-button-group';
4
- import { PropValue } from '../../types';
4
+ import { PropValue } from '@elementor/editor-props';
5
5
  import { createControl } from '../create-control';
6
6
 
7
7
  type ToggleControlProps< T extends PropValue > = {
@@ -9,10 +9,10 @@ type ToggleControlProps< T extends PropValue > = {
9
9
  };
10
10
 
11
11
  export const ToggleControl = createControl( < T extends PropValue >( { options }: ToggleControlProps< T > ) => {
12
- const { value, setValue } = useControl< T >();
12
+ const { value, setValue } = useBoundProp< T | null >();
13
13
 
14
14
  const handleToggle = ( option: T | null ) => {
15
- setValue( option || undefined );
15
+ setValue( option );
16
16
  };
17
17
 
18
18
  return (
@@ -0,0 +1,53 @@
1
+ import * as React from 'react';
2
+ import { ComponentType, createContext, useContext } from 'react';
3
+ import { useBoundProp } from './bound-prop-context';
4
+ import { PropValue } from '@elementor/editor-props';
5
+
6
+ export type ReplaceWhenParams = {
7
+ value: PropValue;
8
+ };
9
+
10
+ export type CreateControlReplacement = {
11
+ component: ComponentType;
12
+ condition: ( { value }: ReplaceWhenParams ) => boolean;
13
+ };
14
+
15
+ const ControlReplacementContext = createContext< CreateControlReplacement | undefined >( undefined );
16
+
17
+ export const ControlReplacementProvider = ( {
18
+ component,
19
+ condition,
20
+ children,
21
+ }: React.PropsWithChildren< CreateControlReplacement > ) => {
22
+ return (
23
+ <ControlReplacementContext.Provider value={ { component, condition } }>
24
+ { children }
25
+ </ControlReplacementContext.Provider>
26
+ );
27
+ };
28
+ export const useControlReplacement = () => {
29
+ const { value } = useBoundProp();
30
+ const controlReplacement = useContext( ControlReplacementContext );
31
+
32
+ let shouldReplace = false;
33
+
34
+ try {
35
+ shouldReplace = !! controlReplacement?.condition( { value } ) && !! controlReplacement.component;
36
+ } catch {}
37
+
38
+ return shouldReplace ? controlReplacement?.component : undefined;
39
+ };
40
+
41
+ export const createControlReplacement = () => {
42
+ let controlReplacement: CreateControlReplacement;
43
+
44
+ function replaceControl( { component, condition }: CreateControlReplacement ) {
45
+ controlReplacement = { component, condition };
46
+ }
47
+
48
+ function getControlReplacement() {
49
+ return controlReplacement;
50
+ }
51
+
52
+ return { replaceControl, getControlReplacement };
53
+ };