@elementor/editor-controls 1.3.0 → 3.32.0-20

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 (74) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/index.d.mts +104 -26
  3. package/dist/index.d.ts +104 -26
  4. package/dist/index.js +2271 -1119
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +2147 -990
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +18 -18
  9. package/src/components/control-toggle-button-group.tsx +78 -14
  10. package/src/components/floating-bar.tsx +45 -0
  11. package/src/components/item-selector.tsx +168 -0
  12. package/src/components/repeater.tsx +23 -12
  13. package/src/components/restricted-link-infotip.tsx +76 -0
  14. package/src/components/size-control/size-input.tsx +4 -3
  15. package/src/components/size-control/text-field-inner-selection.tsx +60 -14
  16. package/src/components/text-field-popover.tsx +30 -7
  17. package/src/components/unstable-repeater/actions/add-item-action.tsx +50 -0
  18. package/src/components/unstable-repeater/actions/disable-item-action.tsx +39 -0
  19. package/src/components/unstable-repeater/actions/duplicate-item-action.tsx +32 -0
  20. package/src/components/unstable-repeater/actions/remove-item-action.tsx +27 -0
  21. package/src/components/unstable-repeater/context/repeater-context.tsx +137 -0
  22. package/src/components/unstable-repeater/header/header.tsx +23 -0
  23. package/src/components/unstable-repeater/index.ts +5 -0
  24. package/src/components/unstable-repeater/items/edit-item-popover.tsx +28 -0
  25. package/src/components/unstable-repeater/items/item.tsx +71 -0
  26. package/src/components/unstable-repeater/items/items-container.tsx +49 -0
  27. package/src/components/unstable-repeater/items/use-popover.tsx +26 -0
  28. package/src/{locations.ts → components/unstable-repeater/locations.ts} +9 -1
  29. package/src/components/unstable-repeater/types.ts +26 -0
  30. package/src/components/unstable-repeater/unstable-repeater.tsx +24 -0
  31. package/src/control-actions/control-actions.tsx +3 -20
  32. package/src/control-replacements.tsx +41 -0
  33. package/src/controls/background-control/background-control.tsx +1 -8
  34. package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +17 -16
  35. package/src/controls/color-control.tsx +12 -1
  36. package/src/controls/equal-unequal-sizes-control.tsx +2 -9
  37. package/src/controls/filter-control/drop-shadow-item-content.tsx +67 -0
  38. package/src/controls/filter-control/drop-shadow-item-label.tsx +20 -0
  39. package/src/controls/filter-repeater-control.tsx +214 -88
  40. package/src/controls/font-family-control/font-family-control.tsx +22 -10
  41. package/src/controls/key-value-control.tsx +64 -50
  42. package/src/controls/link-control.tsx +8 -91
  43. package/src/controls/linked-dimensions-control.tsx +3 -16
  44. package/src/controls/number-control.tsx +10 -1
  45. package/src/controls/position-control.tsx +4 -16
  46. package/src/controls/repeatable-control.tsx +56 -34
  47. package/src/controls/select-control.tsx +7 -2
  48. package/src/controls/selection-size-control.tsx +74 -0
  49. package/src/controls/size-control.tsx +189 -121
  50. package/src/controls/stroke-control.tsx +2 -2
  51. package/src/controls/text-control.tsx +33 -18
  52. package/src/controls/toggle-control.tsx +3 -2
  53. package/src/controls/transform-control/functions/axis-row.tsx +4 -2
  54. package/src/controls/transform-control/functions/move.tsx +2 -1
  55. package/src/controls/transform-control/functions/rotate.tsx +48 -0
  56. package/src/controls/transform-control/functions/scale-axis-row.tsx +32 -0
  57. package/src/controls/transform-control/functions/scale.tsx +45 -0
  58. package/src/controls/transform-control/functions/skew.tsx +43 -0
  59. package/src/controls/transform-control/transform-content.tsx +60 -23
  60. package/src/controls/transform-control/transform-icon.tsx +10 -2
  61. package/src/controls/transform-control/transform-label.tsx +39 -2
  62. package/src/controls/transform-control/transform-repeater-control.tsx +2 -10
  63. package/src/controls/transform-control/types.ts +58 -0
  64. package/src/controls/transform-control/use-transform-tabs-history.tsx +107 -0
  65. package/src/controls/transition-control/data.ts +34 -0
  66. package/src/controls/transition-control/transition-repeater-control.tsx +63 -0
  67. package/src/controls/transition-control/transition-selector.tsx +88 -0
  68. package/src/controls/unstable-transform-control/unstable-transform-repeater-control.tsx +35 -0
  69. package/src/hooks/use-filtered-items-list.ts +24 -0
  70. package/src/hooks/use-size-extended-options.ts +1 -6
  71. package/src/index.ts +13 -3
  72. package/src/utils/size-control.ts +10 -2
  73. package/src/components/font-family-selector.tsx +0 -158
  74. package/src/hooks/use-filtered-font-families.ts +0 -24
@@ -10,154 +10,217 @@ import { TextFieldPopover } from '../components/text-field-popover';
10
10
  import { createControl } from '../create-control';
11
11
  import { useSizeExtendedOptions } from '../hooks/use-size-extended-options';
12
12
  import { useSyncExternalState } from '../hooks/use-sync-external-state';
13
- import { defaultUnits, type ExtendedOption, isUnitExtendedOption, type Unit } from '../utils/size-control';
14
-
15
- const DEFAULT_UNIT = 'px';
16
- const DEFAULT_SIZE = NaN;
13
+ import {
14
+ type AngleUnit,
15
+ angleUnits,
16
+ DEFAULT_SIZE,
17
+ DEFAULT_UNIT,
18
+ type ExtendedOption,
19
+ isUnitExtendedOption,
20
+ type LengthUnit,
21
+ lengthUnits,
22
+ type TimeUnit,
23
+ timeUnits,
24
+ type Unit,
25
+ } from '../utils/size-control';
17
26
 
18
27
  type SizeValue = SizePropValue[ 'value' ];
19
28
 
20
- type SizeControlProps = {
29
+ type SizeVariant = 'length' | 'angle' | 'time';
30
+
31
+ type UnitProps< T extends readonly Unit[] > = {
32
+ units?: T;
33
+ defaultUnit?: T[ number ];
34
+ };
35
+
36
+ type BaseSizeControlProps = {
21
37
  placeholder?: string;
22
38
  startIcon?: React.ReactNode;
23
- units?: Unit[];
24
39
  extendedOptions?: ExtendedOption[];
25
40
  disableCustom?: boolean;
26
41
  anchorRef?: RefObject< HTMLDivElement | null >;
27
- defaultUnit?: Unit;
28
42
  };
29
43
 
44
+ type LengthSizeControlProps = BaseSizeControlProps &
45
+ UnitProps< LengthUnit[] > & {
46
+ variant: 'length';
47
+ };
48
+
49
+ type AngleSizeControlProps = BaseSizeControlProps &
50
+ UnitProps< AngleUnit[] > & {
51
+ variant: 'angle';
52
+ };
53
+
54
+ type TimeSizeControlProps = BaseSizeControlProps &
55
+ UnitProps< TimeUnit[] > & {
56
+ variant: 'time';
57
+ };
58
+
59
+ export type SizeControlProps = LengthSizeControlProps | AngleSizeControlProps | TimeSizeControlProps;
60
+
30
61
  type State = {
31
62
  numeric: number;
32
63
  custom: string;
33
64
  unit: Unit | ExtendedOption;
34
65
  };
35
66
 
36
- export const SizeControl = createControl( ( props: SizeControlProps ) => {
37
- const defaultUnit = props.defaultUnit ?? DEFAULT_UNIT;
38
- const { units = [ ...defaultUnits ], placeholder, startIcon, anchorRef } = props;
39
- const { value: sizeValue, setValue: setSizeValue, disabled, restoreValue } = useBoundProp( sizePropTypeUtil );
40
- const [ internalState, setInternalState ] = useState( createStateFromSizeProp( sizeValue, defaultUnit ) );
41
- const activeBreakpoint = useActiveBreakpoint();
42
-
43
- const extendedOptions = useSizeExtendedOptions( props.extendedOptions || [], props.disableCustom ?? false );
44
- const popupState = usePopupState( { variant: 'popover' } );
45
-
46
- const [ state, setState ] = useSyncExternalState( {
47
- external: internalState,
48
- setExternal: ( newState: State | null ) => setSizeValue( extractValueFromState( newState ) ),
49
- persistWhen: ( newState ) => {
50
- if ( ! newState?.unit ) {
51
- return false;
52
- }
53
-
54
- if ( isUnitExtendedOption( newState.unit ) ) {
55
- return newState.unit === 'auto' ? true : !! newState.custom;
67
+ const defaultSelectedUnit: Record< SizeControlProps[ 'variant' ], Unit > = {
68
+ length: 'px',
69
+ angle: 'deg',
70
+ time: 'ms',
71
+ } as const;
72
+
73
+ const defaultUnits: Record< SizeControlProps[ 'variant' ], Unit[] > = {
74
+ length: [ ...lengthUnits ] as LengthUnit[],
75
+ angle: [ ...angleUnits ] as AngleUnit[],
76
+ time: [ ...timeUnits ] as TimeUnit[],
77
+ } as const;
78
+
79
+ export const SizeControl = createControl(
80
+ ( {
81
+ variant = 'length' as SizeControlProps[ 'variant' ],
82
+ defaultUnit,
83
+ units,
84
+ placeholder,
85
+ startIcon,
86
+ anchorRef,
87
+ extendedOptions,
88
+ disableCustom,
89
+ }: Omit< SizeControlProps, 'variant' > & { variant?: SizeVariant } ) => {
90
+ const {
91
+ value: sizeValue,
92
+ setValue: setSizeValue,
93
+ disabled,
94
+ restoreValue,
95
+ placeholder: externalPlaceholder,
96
+ } = useBoundProp( sizePropTypeUtil );
97
+ const actualDefaultUnit = defaultUnit ?? externalPlaceholder?.unit ?? defaultSelectedUnit[ variant ];
98
+ const actualUnits = units ?? [ ...defaultUnits[ variant ] ];
99
+ const [ internalState, setInternalState ] = useState( createStateFromSizeProp( sizeValue, actualDefaultUnit ) );
100
+ const activeBreakpoint = useActiveBreakpoint();
101
+
102
+ const actualExtendedOptions = useSizeExtendedOptions( extendedOptions || [], disableCustom ?? false );
103
+ const popupState = usePopupState( { variant: 'popover' } );
104
+
105
+ const [ state, setState ] = useSyncExternalState( {
106
+ external: internalState,
107
+ setExternal: ( newState: State | null ) => setSizeValue( extractValueFromState( newState ) ),
108
+ persistWhen: ( newState ) => {
109
+ if ( ! newState?.unit ) {
110
+ return false;
111
+ }
112
+
113
+ if ( isUnitExtendedOption( newState.unit ) ) {
114
+ return newState.unit === 'auto' ? true : !! newState.custom;
115
+ }
116
+
117
+ return !! newState?.numeric || newState?.numeric === 0;
118
+ },
119
+ fallback: ( newState ) => ( {
120
+ unit: newState?.unit ?? actualDefaultUnit,
121
+ numeric: newState?.numeric ?? DEFAULT_SIZE,
122
+ custom: newState?.custom ?? '',
123
+ } ),
124
+ } );
125
+
126
+ const { size: controlSize = DEFAULT_SIZE, unit: controlUnit = actualDefaultUnit } =
127
+ extractValueFromState( state ) || {};
128
+
129
+ const handleUnitChange = ( newUnit: Unit | ExtendedOption ) => {
130
+ if ( newUnit === 'custom' ) {
131
+ popupState.open( anchorRef?.current );
56
132
  }
57
133
 
58
- return !! newState?.numeric || newState?.numeric === 0;
59
- },
60
- fallback: ( newState ) => ( {
61
- unit: newState?.unit ?? props.defaultUnit ?? DEFAULT_UNIT,
62
- numeric: newState?.numeric ?? DEFAULT_SIZE,
63
- custom: newState?.custom ?? '',
64
- } ),
65
- } );
66
-
67
- const { size: controlSize = DEFAULT_SIZE, unit: controlUnit = DEFAULT_UNIT } = extractValueFromState( state ) || {};
134
+ setState( ( prev ) => ( { ...prev, unit: newUnit } ) );
135
+ };
68
136
 
69
- const handleUnitChange = ( newUnit: Unit | ExtendedOption ) => {
70
- if ( newUnit === 'custom' ) {
71
- popupState.open( anchorRef?.current );
72
- }
73
-
74
- setState( ( prev ) => ( { ...prev, unit: newUnit } ) );
75
- };
137
+ const handleSizeChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
138
+ const { value: size } = event.target;
76
139
 
77
- const handleSizeChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
78
- const { value: size } = event.target;
140
+ if ( controlUnit === 'auto' ) {
141
+ setState( ( prev ) => ( { ...prev, unit: controlUnit } ) );
79
142
 
80
- if ( controlUnit === 'auto' ) {
81
- setState( ( prev ) => ( { ...prev, unit: controlUnit } ) );
82
-
83
- return;
84
- }
85
-
86
- setState( ( prev ) => ( {
87
- ...prev,
88
- [ controlUnit === 'custom' ? 'custom' : 'numeric' ]: formatSize( size, controlUnit ),
89
- unit: controlUnit,
90
- } ) );
91
- };
92
-
93
- const onInputFocus = ( event: React.FocusEvent< HTMLInputElement > ) => {
94
- if ( isUnitExtendedOption( state.unit ) ) {
95
- ( event.target as HTMLElement )?.blur();
96
- }
97
- };
98
-
99
- const onInputClick = ( event: React.MouseEvent ) => {
100
- if ( ( event.target as HTMLElement ).closest( 'input' ) && 'custom' === state.unit ) {
101
- popupState.open( anchorRef?.current );
102
- }
103
- };
143
+ return;
144
+ }
104
145
 
105
- useEffect( () => {
106
- const newState = createStateFromSizeProp( sizeValue, defaultUnit );
107
- const currentUnit = isUnitExtendedOption( state.unit ) ? 'custom' : 'numeric';
108
- const mergedStates = { ...state, [ currentUnit ]: newState[ currentUnit ] };
146
+ setState( ( prev ) => ( {
147
+ ...prev,
148
+ [ controlUnit === 'custom' ? 'custom' : 'numeric' ]: formatSize( size, controlUnit ),
149
+ unit: controlUnit,
150
+ } ) );
151
+ };
109
152
 
110
- if ( mergedStates.unit !== 'auto' && areStatesEqual( state, mergedStates ) ) {
111
- return;
112
- }
153
+ const onInputClick = ( event: React.MouseEvent ) => {
154
+ if ( ( event.target as HTMLElement ).closest( 'input' ) && 'custom' === state.unit ) {
155
+ popupState.open( anchorRef?.current );
156
+ }
157
+ };
158
+
159
+ useEffect( () => {
160
+ const newState = createStateFromSizeProp(
161
+ sizeValue,
162
+ state.unit === 'custom' ? state.unit : actualDefaultUnit,
163
+ '',
164
+ state.custom
165
+ );
166
+ const currentUnitType = isUnitExtendedOption( state.unit ) ? 'custom' : 'numeric';
167
+ const mergedStates = {
168
+ ...state,
169
+ unit: newState.unit ?? state.unit,
170
+ [ currentUnitType ]: newState[ currentUnitType ],
171
+ };
172
+
173
+ if ( mergedStates.unit !== 'auto' && areStatesEqual( state, mergedStates ) ) {
174
+ return;
175
+ }
113
176
 
114
- if ( state.unit === newState.unit ) {
115
- setInternalState( mergedStates );
177
+ if ( state.unit === newState.unit ) {
178
+ setInternalState( mergedStates );
116
179
 
117
- return;
118
- }
180
+ return;
181
+ }
119
182
 
120
- setState( newState );
121
- // eslint-disable-next-line react-hooks/exhaustive-deps
122
- }, [ sizeValue ] );
183
+ setState( newState );
184
+ // eslint-disable-next-line react-hooks/exhaustive-deps
185
+ }, [ sizeValue ] );
123
186
 
124
- useEffect( () => {
125
- const newState = createStateFromSizeProp( sizeValue, defaultUnit );
187
+ useEffect( () => {
188
+ const newState = createStateFromSizeProp( sizeValue, actualDefaultUnit, '', state.custom );
126
189
 
127
- if ( activeBreakpoint && ! areStatesEqual( newState, state ) ) {
128
- setState( newState );
129
- }
130
- // eslint-disable-next-line react-hooks/exhaustive-deps
131
- }, [ activeBreakpoint ] );
132
-
133
- return (
134
- <>
135
- <SizeInput
136
- disabled={ disabled }
137
- size={ controlSize }
138
- unit={ controlUnit }
139
- units={ [ ...units, ...( extendedOptions || [] ) ] }
140
- placeholder={ placeholder }
141
- startIcon={ startIcon }
142
- handleSizeChange={ handleSizeChange }
143
- handleUnitChange={ handleUnitChange }
144
- onBlur={ restoreValue }
145
- onFocus={ onInputFocus }
146
- onClick={ onInputClick }
147
- popupState={ popupState }
148
- />
149
- { anchorRef?.current && (
150
- <TextFieldPopover
190
+ if ( activeBreakpoint && ! areStatesEqual( newState, state ) ) {
191
+ setState( newState );
192
+ }
193
+ // eslint-disable-next-line react-hooks/exhaustive-deps
194
+ }, [ activeBreakpoint ] );
195
+
196
+ return (
197
+ <>
198
+ <SizeInput
199
+ disabled={ disabled }
200
+ size={ controlSize }
201
+ unit={ controlUnit }
202
+ units={ [ ...actualUnits, ...( actualExtendedOptions || [] ) ] }
203
+ placeholder={ placeholder }
204
+ startIcon={ startIcon }
205
+ handleSizeChange={ handleSizeChange }
206
+ handleUnitChange={ handleUnitChange }
207
+ onBlur={ restoreValue }
208
+ onClick={ onInputClick }
151
209
  popupState={ popupState }
152
- anchorRef={ anchorRef }
153
- restoreValue={ restoreValue }
154
- value={ controlSize as string }
155
- onChange={ handleSizeChange }
156
210
  />
157
- ) }
158
- </>
159
- );
160
- } );
211
+ { anchorRef?.current && (
212
+ <TextFieldPopover
213
+ popupState={ popupState }
214
+ anchorRef={ anchorRef }
215
+ restoreValue={ restoreValue }
216
+ value={ controlSize as string }
217
+ onChange={ handleSizeChange }
218
+ />
219
+ ) }
220
+ </>
221
+ );
222
+ }
223
+ );
161
224
 
162
225
  function formatSize< TSize extends string | number >( size: TSize, unit: Unit | ExtendedOption ): TSize {
163
226
  if ( isUnitExtendedOption( unit ) ) {
@@ -167,16 +230,21 @@ function formatSize< TSize extends string | number >( size: TSize, unit: Unit |
167
230
  return size || size === 0 ? ( Number( size ) as TSize ) : ( NaN as TSize );
168
231
  }
169
232
 
170
- function createStateFromSizeProp( sizeValue: SizeValue | null, defaultUnit: Unit ): State {
233
+ function createStateFromSizeProp(
234
+ sizeValue: SizeValue | null,
235
+ defaultUnit: Unit | ExtendedOption,
236
+ defaultSize: string | number = '',
237
+ customState: string = ''
238
+ ): State {
171
239
  const unit = sizeValue?.unit ?? defaultUnit;
172
- const size = sizeValue?.size ?? '';
240
+ const size = sizeValue?.size ?? defaultSize;
173
241
 
174
242
  return {
175
243
  numeric:
176
244
  ! isUnitExtendedOption( unit ) && ! isNaN( Number( size ) ) && ( size || size === 0 )
177
245
  ? Number( size )
178
246
  : DEFAULT_SIZE,
179
- custom: unit === 'custom' ? String( size ) : '',
247
+ custom: unit === 'custom' ? String( size ) : customState,
180
248
  unit,
181
249
  };
182
250
  }
@@ -8,7 +8,7 @@ import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-conte
8
8
  import { ControlFormLabel } from '../components/control-form-label';
9
9
  import { SectionContent } from '../components/section-content';
10
10
  import { createControl } from '../create-control';
11
- import { type Unit } from '../utils/size-control';
11
+ import { type LengthUnit } from '../utils/size-control';
12
12
  import { ColorControl } from './color-control';
13
13
  import { SizeControl } from './size-control';
14
14
 
@@ -18,7 +18,7 @@ type StrokeProps = {
18
18
  children: React.ReactNode;
19
19
  };
20
20
 
21
- const units: Unit[] = [ 'px', 'em', 'rem' ];
21
+ const units: LengthUnit[] = [ 'px', 'em', 'rem' ];
22
22
 
23
23
  export const StrokeControl = createControl( () => {
24
24
  const propContext = useBoundProp( strokePropTypeUtil );
@@ -1,26 +1,41 @@
1
1
  import * as React from 'react';
2
2
  import { stringPropTypeUtil } from '@elementor/editor-props';
3
- import { TextField } from '@elementor/ui';
3
+ import { type SxProps, TextField } from '@elementor/ui';
4
4
 
5
5
  import { useBoundProp } from '../bound-prop-context';
6
6
  import ControlActions from '../control-actions/control-actions';
7
7
  import { createControl } from '../create-control';
8
8
 
9
- export const TextControl = createControl( ( { placeholder }: { placeholder?: string } ) => {
10
- const { value, setValue, disabled } = useBoundProp( stringPropTypeUtil );
9
+ export const TextControl = createControl(
10
+ ( {
11
+ placeholder,
12
+ error,
13
+ inputValue,
14
+ inputDisabled,
15
+ sx,
16
+ }: {
17
+ placeholder?: string;
18
+ error?: boolean;
19
+ inputValue?: string;
20
+ inputDisabled?: boolean;
21
+ sx?: SxProps;
22
+ } ) => {
23
+ const { value, setValue, disabled } = useBoundProp( stringPropTypeUtil );
24
+ const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => setValue( event.target.value );
11
25
 
12
- const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => setValue( event.target.value );
13
-
14
- return (
15
- <ControlActions>
16
- <TextField
17
- size="tiny"
18
- fullWidth
19
- disabled={ disabled }
20
- value={ value ?? '' }
21
- onChange={ handleChange }
22
- placeholder={ placeholder }
23
- />
24
- </ControlActions>
25
- );
26
- } );
26
+ return (
27
+ <ControlActions>
28
+ <TextField
29
+ size="tiny"
30
+ fullWidth
31
+ disabled={ inputDisabled ?? disabled }
32
+ value={ inputValue ?? value ?? '' }
33
+ onChange={ handleChange }
34
+ placeholder={ placeholder }
35
+ error={ error }
36
+ sx={ sx }
37
+ />
38
+ </ControlActions>
39
+ );
40
+ }
41
+ );
@@ -42,12 +42,13 @@ export const ToggleControl = createControl(
42
42
  maxItems,
43
43
  fullWidth,
44
44
  size,
45
+ placeholder,
45
46
  };
46
47
 
47
48
  return exclusive ? (
48
49
  <ControlToggleButtonGroup
49
50
  { ...toggleButtonGroupProps }
50
- value={ value ?? placeholder ?? null }
51
+ value={ value ?? null }
51
52
  onChange={ setValue }
52
53
  disabled={ disabled }
53
54
  exclusive={ true }
@@ -55,7 +56,7 @@ export const ToggleControl = createControl(
55
56
  ) : (
56
57
  <ControlToggleButtonGroup
57
58
  { ...toggleButtonGroupProps }
58
- value={ ( value ?? placeholder )?.split( ' ' ) ?? [] }
59
+ value={ value?.split( ' ' ) ?? [] }
59
60
  onChange={ handleNonExclusiveToggle }
60
61
  disabled={ disabled }
61
62
  exclusive={ false }
@@ -1,5 +1,6 @@
1
1
  import { type RefObject } from 'react';
2
2
  import * as React from 'react';
3
+ import { type AngleUnit } from '@elementor/editor-controls';
3
4
  import { Grid } from '@elementor/ui';
4
5
 
5
6
  import { PropKeyProvider } from '../../../bound-prop-context';
@@ -12,9 +13,10 @@ type TransformAxisRowProps = {
12
13
  bindValue: 'x' | 'y' | 'z';
13
14
  startIcon: React.ReactNode;
14
15
  anchorRef?: RefObject< HTMLDivElement | null >;
16
+ units?: AngleUnit[];
15
17
  };
16
18
 
17
- export const AxisRow = ( { label, bindValue, startIcon, anchorRef }: TransformAxisRowProps ) => {
19
+ export const AxisRow = ( { label, bindValue, startIcon, anchorRef, units }: TransformAxisRowProps ) => {
18
20
  return (
19
21
  <Grid item xs={ 12 }>
20
22
  <PopoverGridContainer ref={ anchorRef }>
@@ -23,7 +25,7 @@ export const AxisRow = ( { label, bindValue, startIcon, anchorRef }: TransformAx
23
25
  </Grid>
24
26
  <Grid item xs={ 6 }>
25
27
  <PropKeyProvider bind={ bindValue }>
26
- <SizeControl anchorRef={ anchorRef } startIcon={ startIcon } />
28
+ <SizeControl anchorRef={ anchorRef } startIcon={ startIcon } units={ units } variant="angle" />
27
29
  </PropKeyProvider>
28
30
  </Grid>
29
31
  </PopoverGridContainer>
@@ -6,6 +6,7 @@ import { Grid } from '@elementor/ui';
6
6
  import { __ } from '@wordpress/i18n';
7
7
 
8
8
  import { PropKeyProvider, PropProvider, useBoundProp } from '../../../bound-prop-context';
9
+ import { TransformFunctionKeys } from '../types';
9
10
  import { AxisRow } from './axis-row';
10
11
 
11
12
  const moveAxisControls: { label: string; bindValue: 'x' | 'y' | 'z'; startIcon: React.ReactNode }[] = [
@@ -33,7 +34,7 @@ export const Move = () => {
33
34
  return (
34
35
  <Grid container spacing={ 1.5 }>
35
36
  <PropProvider { ...context }>
36
- <PropKeyProvider bind={ 'transform-move' }>
37
+ <PropKeyProvider bind={ TransformFunctionKeys.move }>
37
38
  { moveAxisControls.map( ( control ) => (
38
39
  <AxisRow key={ control.bindValue } { ...control } anchorRef={ rowRef } />
39
40
  ) ) }
@@ -0,0 +1,48 @@
1
+ import * as React from 'react';
2
+ import { useRef } from 'react';
3
+ import { rotateTransformPropTypeUtil } from '@elementor/editor-props';
4
+ import { Arrow360Icon, RotateClockwiseIcon } from '@elementor/icons';
5
+ import { Grid } from '@elementor/ui';
6
+ import { __ } from '@wordpress/i18n';
7
+
8
+ import { PropKeyProvider, PropProvider, useBoundProp } from '../../../bound-prop-context';
9
+ import { type AngleUnit } from '../../../utils/size-control';
10
+ import { TransformFunctionKeys } from '../types';
11
+ import { AxisRow } from './axis-row';
12
+
13
+ const rotateAxisControls: { label: string; bindValue: 'x' | 'y' | 'z'; startIcon: React.ReactNode }[] = [
14
+ {
15
+ label: __( 'Rotate X', 'elementor' ),
16
+ bindValue: 'x',
17
+ startIcon: <Arrow360Icon fontSize={ 'tiny' } />,
18
+ },
19
+ {
20
+ label: __( 'Rotate Y', 'elementor' ),
21
+ bindValue: 'y',
22
+ startIcon: <Arrow360Icon fontSize="tiny" style={ { transform: 'scaleX(-1) rotate(-90deg)' } } />,
23
+ },
24
+ {
25
+ label: __( 'Rotate Z', 'elementor' ),
26
+ bindValue: 'z',
27
+ startIcon: <RotateClockwiseIcon fontSize={ 'tiny' } />,
28
+ },
29
+ ];
30
+
31
+ const rotateUnits: AngleUnit[] = [ 'deg', 'rad', 'grad', 'turn' ];
32
+
33
+ export const Rotate = () => {
34
+ const context = useBoundProp( rotateTransformPropTypeUtil );
35
+ const rowRef = useRef< HTMLDivElement >( null );
36
+
37
+ return (
38
+ <Grid container spacing={ 1.5 }>
39
+ <PropProvider { ...context }>
40
+ <PropKeyProvider bind={ TransformFunctionKeys.rotate }>
41
+ { rotateAxisControls.map( ( control ) => (
42
+ <AxisRow key={ control.bindValue } { ...control } anchorRef={ rowRef } units={ rotateUnits } />
43
+ ) ) }
44
+ </PropKeyProvider>
45
+ </PropProvider>
46
+ </Grid>
47
+ );
48
+ };
@@ -0,0 +1,32 @@
1
+ import { type RefObject } from 'react';
2
+ import * as React from 'react';
3
+ import { Grid } from '@elementor/ui';
4
+
5
+ import { PropKeyProvider } from '../../../bound-prop-context';
6
+ import { ControlLabel } from '../../../components/control-label';
7
+ import { PopoverGridContainer } from '../../../components/popover-grid-container';
8
+ import { NumberControl } from '../../number-control';
9
+
10
+ type ScaleAxisRowProps = {
11
+ label: string;
12
+ bindValue: 'x' | 'y' | 'z';
13
+ startIcon: React.ReactNode;
14
+ anchorRef?: RefObject< HTMLDivElement | null >;
15
+ };
16
+
17
+ export const ScaleAxisRow = ( { label, bindValue, startIcon, anchorRef }: ScaleAxisRowProps ) => {
18
+ return (
19
+ <Grid item xs={ 12 }>
20
+ <PopoverGridContainer ref={ anchorRef }>
21
+ <Grid item xs={ 6 }>
22
+ <ControlLabel>{ label }</ControlLabel>
23
+ </Grid>
24
+ <Grid item xs={ 6 }>
25
+ <PropKeyProvider bind={ bindValue }>
26
+ <NumberControl step={ 0.1 } placeholder="1" startIcon={ startIcon } />
27
+ </PropKeyProvider>
28
+ </Grid>
29
+ </PopoverGridContainer>
30
+ </Grid>
31
+ );
32
+ };
@@ -0,0 +1,45 @@
1
+ import * as React from 'react';
2
+ import { useRef } from 'react';
3
+ import { scaleTransformPropTypeUtil } from '@elementor/editor-props';
4
+ import { ArrowDownLeftIcon, ArrowDownSmallIcon, ArrowRightIcon } from '@elementor/icons';
5
+ import { Grid } from '@elementor/ui';
6
+ import { __ } from '@wordpress/i18n';
7
+
8
+ import { PropKeyProvider, PropProvider, useBoundProp } from '../../../bound-prop-context';
9
+ import { TransformFunctionKeys } from '../types';
10
+ import { ScaleAxisRow } from './scale-axis-row';
11
+
12
+ const scaleAxisControls: { label: string; bindValue: 'x' | 'y' | 'z'; startIcon: React.ReactNode }[] = [
13
+ {
14
+ label: __( 'Scale X', 'elementor' ),
15
+ bindValue: 'x',
16
+ startIcon: <ArrowRightIcon fontSize={ 'tiny' } />,
17
+ },
18
+ {
19
+ label: __( 'Scale Y', 'elementor' ),
20
+ bindValue: 'y',
21
+ startIcon: <ArrowDownSmallIcon fontSize={ 'tiny' } />,
22
+ },
23
+ {
24
+ label: __( 'Scale Z', 'elementor' ),
25
+ bindValue: 'z',
26
+ startIcon: <ArrowDownLeftIcon fontSize={ 'tiny' } />,
27
+ },
28
+ ];
29
+
30
+ export const Scale = () => {
31
+ const context = useBoundProp( scaleTransformPropTypeUtil );
32
+ const rowRef = useRef< HTMLDivElement >( null );
33
+
34
+ return (
35
+ <Grid container spacing={ 1.5 }>
36
+ <PropProvider { ...context }>
37
+ <PropKeyProvider bind={ TransformFunctionKeys.scale }>
38
+ { scaleAxisControls.map( ( control ) => (
39
+ <ScaleAxisRow key={ control.bindValue } { ...control } anchorRef={ rowRef } />
40
+ ) ) }
41
+ </PropKeyProvider>
42
+ </PropProvider>
43
+ </Grid>
44
+ );
45
+ };