@elementor/editor-controls 1.2.0 → 1.5.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 (41) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/dist/index.d.mts +20 -8
  3. package/dist/index.d.ts +20 -8
  4. package/dist/index.js +1092 -714
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +937 -549
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +7 -7
  9. package/src/bound-prop-context/prop-context.tsx +3 -3
  10. package/src/bound-prop-context/prop-key-context.tsx +1 -0
  11. package/src/bound-prop-context/use-bound-prop.ts +5 -1
  12. package/src/components/font-family-selector.tsx +54 -56
  13. package/src/components/repeater.tsx +22 -11
  14. package/src/components/size-control/size-input.tsx +4 -4
  15. package/src/components/text-field-popover.tsx +19 -18
  16. package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +3 -15
  17. package/src/controls/box-shadow-repeater-control.tsx +1 -1
  18. package/src/controls/color-control.tsx +12 -1
  19. package/src/controls/equal-unequal-sizes-control.tsx +1 -1
  20. package/src/controls/filter-control/drop-shadow-item-content.tsx +69 -0
  21. package/src/controls/filter-control/drop-shadow-item-label.tsx +20 -0
  22. package/src/controls/filter-repeater-control.tsx +108 -21
  23. package/src/controls/font-family-control/font-family-control.tsx +14 -2
  24. package/src/controls/image-control.tsx +45 -16
  25. package/src/controls/key-value-control.tsx +57 -46
  26. package/src/controls/link-control.tsx +25 -20
  27. package/src/controls/linked-dimensions-control.tsx +1 -1
  28. package/src/controls/repeatable-control.tsx +100 -21
  29. package/src/controls/select-control.tsx +22 -2
  30. package/src/controls/size-control.tsx +25 -12
  31. package/src/controls/switch-control.tsx +9 -1
  32. package/src/controls/text-control.tsx +33 -18
  33. package/src/controls/transform-control/functions/axis-row.tsx +32 -0
  34. package/src/controls/transform-control/functions/move.tsx +44 -0
  35. package/src/controls/transform-control/transform-content.tsx +36 -0
  36. package/src/controls/transform-control/transform-icon.tsx +12 -0
  37. package/src/controls/transform-control/transform-label.tsx +27 -0
  38. package/src/controls/transform-control/transform-repeater-control.tsx +42 -0
  39. package/src/hooks/use-repeatable-control-context.ts +6 -1
  40. package/src/index.ts +1 -0
  41. package/src/utils/size-control.ts +4 -2
@@ -3,7 +3,6 @@ import { useMemo } from 'react';
3
3
  import { createArrayPropUtils, type PropKey } from '@elementor/editor-props';
4
4
  import { Box } from '@elementor/ui';
5
5
 
6
- /* eslint-disable */
7
6
  import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-context';
8
7
  import { PopoverContent } from '../components/popover-content';
9
8
  import { PopoverGridContainer } from '../components/popover-grid-container';
@@ -26,6 +25,8 @@ type RepeatableControlProps = {
26
25
  placeholder?: string;
27
26
  };
28
27
 
28
+ const PLACEHOLDER_REGEX = /\$\{([^}]+)\}/g;
29
+
29
30
  export const RepeatableControl = createControl(
30
31
  ( {
31
32
  repeaterLabel,
@@ -47,25 +48,20 @@ export const RepeatableControl = createControl(
47
48
  [ childPropTypeUtil.key, childPropTypeUtil.schema ]
48
49
  );
49
50
 
50
- const { propType, value, setValue } = useBoundProp( childArrayPropTypeUtil );
51
- const ItemLabel = ( { value: itemValue }: { value: any } ) => {
52
- const pattern = patternLabel || '';
53
- const finalLabel = interpolate( pattern, itemValue.value );
54
-
55
- if ( finalLabel ) {
56
- return <span>{ finalLabel }</span>;
57
- }
51
+ const contextValue = useMemo(
52
+ () => ( {
53
+ ...childControlConfig,
54
+ placeholder: placeholder || '',
55
+ patternLabel: patternLabel || '',
56
+ } ),
57
+ [ childControlConfig, placeholder, patternLabel ]
58
+ );
58
59
 
59
- return (
60
- <Box component="span" color="text.tertiary">
61
- { placeholder }
62
- </Box>
63
- );
64
- };
60
+ const { propType, value, setValue } = useBoundProp( childArrayPropTypeUtil );
65
61
 
66
62
  return (
67
63
  <PropProvider propType={ propType } value={ value } setValue={ setValue }>
68
- <RepeatableControlContext.Provider value={ childControlConfig }>
64
+ <RepeatableControlContext.Provider value={ contextValue }>
69
65
  <Repeater
70
66
  openOnAdd
71
67
  values={ value ?? [] }
@@ -110,10 +106,93 @@ const Content = () => {
110
106
  );
111
107
  };
112
108
 
113
- const interpolate = ( template: string, data: object ) => {
114
- //remove it to be generic
115
- if ( Object.values( data ).some( ( value ) => value.value === '' || value === '' ) ) {
116
- return null;
109
+ const interpolate = ( template: string, data: Record< string, unknown > ) => {
110
+ if ( ! data ) {
111
+ return template;
112
+ }
113
+
114
+ return template.replace( PLACEHOLDER_REGEX, ( _, path ): string => {
115
+ const value = getNestedValue( data, path );
116
+
117
+ if ( typeof value === 'object' && value !== null && ! Array.isArray( value ) ) {
118
+ if ( value.name ) {
119
+ return value.name as string;
120
+ }
121
+
122
+ return JSON.stringify( value );
123
+ }
124
+
125
+ if ( Array.isArray( value ) ) {
126
+ return value.join( ', ' );
127
+ }
128
+
129
+ return String( value ?? '' );
130
+ } );
131
+ };
132
+
133
+ const getNestedValue = ( obj: Record< string, unknown >, path: string ) => {
134
+ return path.split( '.' ).reduce( ( current: Record< string, unknown >, key ) => {
135
+ if ( current && typeof current === 'object' ) {
136
+ return current[ key ] as Record< string, unknown >;
137
+ }
138
+ return {};
139
+ }, obj );
140
+ };
141
+
142
+ const isEmptyValue = ( val: unknown ) => {
143
+ if ( typeof val === 'string' ) {
144
+ return val.trim() === '';
145
+ }
146
+
147
+ if ( Number.isNaN( val ) ) {
148
+ return true;
149
+ }
150
+
151
+ if ( Array.isArray( val ) ) {
152
+ return val.length === 0;
153
+ }
154
+
155
+ if ( typeof val === 'object' && val !== null ) {
156
+ return Object.keys( val ).length === 0;
157
+ }
158
+
159
+ return false;
160
+ };
161
+
162
+ const shouldShowPlaceholder = ( pattern: string, data: Record< string, unknown > ): boolean => {
163
+ const propertyPaths = getAllProperties( pattern );
164
+
165
+ const values = propertyPaths.map( ( path ) => getNestedValue( data, path ) );
166
+
167
+ if ( values.length === 0 ) {
168
+ return false;
117
169
  }
118
- return new Function( ...Object.keys( data ), `return \`${ template }\`;` )( ...Object.values( data ) );
170
+
171
+ if ( values.some( ( value ) => value === null || value === undefined ) ) {
172
+ return true;
173
+ }
174
+
175
+ if ( values.every( isEmptyValue ) ) {
176
+ return true;
177
+ }
178
+
179
+ return false;
180
+ };
181
+
182
+ const ItemLabel = ( { value }: { value: Record< string, unknown > } ) => {
183
+ const { placeholder, patternLabel } = useRepeatableControlContext();
184
+
185
+ const label = shouldShowPlaceholder( patternLabel, value ) ? placeholder : interpolate( patternLabel, value );
186
+
187
+ return (
188
+ <Box component="span" color="text.tertiary">
189
+ { label }
190
+ </Box>
191
+ );
192
+ };
193
+
194
+ const getAllProperties = ( pattern: string ) => {
195
+ const properties = pattern.match( PLACEHOLDER_REGEX )?.map( ( match ) => match.slice( 2, -1 ) ) || [];
196
+
197
+ return properties;
119
198
  };
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { stringPropTypeUtil, type StringPropValue } from '@elementor/editor-props';
3
3
  import { MenuListItem } from '@elementor/editor-ui';
4
- import { Select, type SelectChangeEvent } from '@elementor/ui';
4
+ import { Select, type SelectChangeEvent, Typography } from '@elementor/ui';
5
5
 
6
6
  import { useBoundProp } from '../bound-prop-context';
7
7
  import ControlActions from '../control-actions/control-actions';
@@ -13,7 +13,7 @@ type Props = {
13
13
  };
14
14
 
15
15
  export const SelectControl = createControl( ( { options, onChange }: Props ) => {
16
- const { value, setValue, disabled } = useBoundProp( stringPropTypeUtil );
16
+ const { value, setValue, disabled, placeholder } = useBoundProp( stringPropTypeUtil );
17
17
 
18
18
  const handleChange = ( event: SelectChangeEvent< StringPropValue[ 'value' ] > ) => {
19
19
  const newValue = event.target.value || null;
@@ -28,6 +28,26 @@ export const SelectControl = createControl( ( { options, onChange }: Props ) =>
28
28
  sx={ { overflow: 'hidden' } }
29
29
  displayEmpty
30
30
  size="tiny"
31
+ renderValue={ ( selectedValue: string | null ) => {
32
+ const findOptionByValue = ( searchValue: string | null ) =>
33
+ options.find( ( opt ) => opt.value === searchValue );
34
+
35
+ if ( ! selectedValue || selectedValue === '' ) {
36
+ if ( placeholder ) {
37
+ const placeholderOption = findOptionByValue( placeholder );
38
+ const displayText = placeholderOption?.label || placeholder;
39
+
40
+ return (
41
+ <Typography component="span" variant="caption" color="text.tertiary">
42
+ { displayText }
43
+ </Typography>
44
+ );
45
+ }
46
+ return '';
47
+ }
48
+ const option = findOptionByValue( selectedValue );
49
+ return option?.label || selectedValue;
50
+ } }
31
51
  value={ value ?? '' }
32
52
  onChange={ handleChange }
33
53
  disabled={ disabled }
@@ -10,7 +10,13 @@ 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';
13
+ import {
14
+ defaultUnits,
15
+ type DegreeUnit,
16
+ type ExtendedOption,
17
+ isUnitExtendedOption,
18
+ type Unit,
19
+ } from '../utils/size-control';
14
20
 
15
21
  const DEFAULT_UNIT = 'px';
16
22
  const DEFAULT_SIZE = NaN;
@@ -20,17 +26,17 @@ type SizeValue = SizePropValue[ 'value' ];
20
26
  type SizeControlProps = {
21
27
  placeholder?: string;
22
28
  startIcon?: React.ReactNode;
23
- units?: Unit[];
29
+ units?: ( Unit | DegreeUnit )[];
24
30
  extendedOptions?: ExtendedOption[];
25
31
  disableCustom?: boolean;
26
32
  anchorRef?: RefObject< HTMLDivElement | null >;
27
- defaultUnit?: Unit;
33
+ defaultUnit?: Unit | DegreeUnit;
28
34
  };
29
35
 
30
36
  type State = {
31
37
  numeric: number;
32
38
  custom: string;
33
- unit: Unit | ExtendedOption;
39
+ unit: Unit | DegreeUnit | ExtendedOption;
34
40
  };
35
41
 
36
42
  export const SizeControl = createControl( ( props: SizeControlProps ) => {
@@ -58,15 +64,15 @@ export const SizeControl = createControl( ( props: SizeControlProps ) => {
58
64
  return !! newState?.numeric || newState?.numeric === 0;
59
65
  },
60
66
  fallback: ( newState ) => ( {
61
- unit: newState?.unit ?? props.defaultUnit ?? DEFAULT_UNIT,
67
+ unit: newState?.unit ?? defaultUnit,
62
68
  numeric: newState?.numeric ?? DEFAULT_SIZE,
63
69
  custom: newState?.custom ?? '',
64
70
  } ),
65
71
  } );
66
72
 
67
- const { size: controlSize = DEFAULT_SIZE, unit: controlUnit = DEFAULT_UNIT } = extractValueFromState( state ) || {};
73
+ const { size: controlSize = DEFAULT_SIZE, unit: controlUnit = defaultUnit } = extractValueFromState( state ) || {};
68
74
 
69
- const handleUnitChange = ( newUnit: Unit | ExtendedOption ) => {
75
+ const handleUnitChange = ( newUnit: Unit | DegreeUnit | ExtendedOption ) => {
70
76
  if ( newUnit === 'custom' ) {
71
77
  popupState.open( anchorRef?.current );
72
78
  }
@@ -103,9 +109,13 @@ export const SizeControl = createControl( ( props: SizeControlProps ) => {
103
109
  };
104
110
 
105
111
  useEffect( () => {
106
- const newState = createStateFromSizeProp( sizeValue, defaultUnit );
107
- const currentUnit = isUnitExtendedOption( state.unit ) ? 'custom' : 'numeric';
108
- const mergedStates = { ...state, [ currentUnit ]: newState[ currentUnit ] };
112
+ const newState = createStateFromSizeProp( sizeValue, state.unit === 'custom' ? state.unit : defaultUnit );
113
+ const currentUnitType = isUnitExtendedOption( state.unit ) ? 'custom' : 'numeric';
114
+ const mergedStates = {
115
+ ...state,
116
+ unit: newState.unit ?? state.unit,
117
+ [ currentUnitType ]: newState[ currentUnitType ],
118
+ };
109
119
 
110
120
  if ( mergedStates.unit !== 'auto' && areStatesEqual( state, mergedStates ) ) {
111
121
  return;
@@ -159,7 +169,7 @@ export const SizeControl = createControl( ( props: SizeControlProps ) => {
159
169
  );
160
170
  } );
161
171
 
162
- function formatSize< TSize extends string | number >( size: TSize, unit: Unit | ExtendedOption ): TSize {
172
+ function formatSize< TSize extends string | number >( size: TSize, unit: Unit | DegreeUnit | ExtendedOption ): TSize {
163
173
  if ( isUnitExtendedOption( unit ) ) {
164
174
  return unit === 'auto' ? ( '' as TSize ) : ( String( size ?? '' ) as TSize );
165
175
  }
@@ -167,7 +177,10 @@ function formatSize< TSize extends string | number >( size: TSize, unit: Unit |
167
177
  return size || size === 0 ? ( Number( size ) as TSize ) : ( NaN as TSize );
168
178
  }
169
179
 
170
- function createStateFromSizeProp( sizeValue: SizeValue | null, defaultUnit: Unit ): State {
180
+ function createStateFromSizeProp(
181
+ sizeValue: SizeValue | null,
182
+ defaultUnit: Unit | DegreeUnit | ExtendedOption
183
+ ): State {
171
184
  const unit = sizeValue?.unit ?? defaultUnit;
172
185
  const size = sizeValue?.size ?? '';
173
186
 
@@ -14,7 +14,15 @@ export const SwitchControl = createControl( () => {
14
14
 
15
15
  return (
16
16
  <div style={ { display: 'flex', justifyContent: 'flex-end' } }>
17
- <Switch checked={ !! value } onChange={ handleChange } size="small" disabled={ disabled } />
17
+ <Switch
18
+ checked={ !! value }
19
+ onChange={ handleChange }
20
+ size="small"
21
+ disabled={ disabled }
22
+ inputProps={ {
23
+ ...( disabled ? { style: { opacity: 0 } } : {} ),
24
+ } }
25
+ />
18
26
  </div>
19
27
  );
20
28
  } );
@@ -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
+ );
@@ -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 { SizeControl } from '../../size-control';
9
+
10
+ type TransformAxisRowProps = {
11
+ label: string;
12
+ bindValue: 'x' | 'y' | 'z';
13
+ startIcon: React.ReactNode;
14
+ anchorRef?: RefObject< HTMLDivElement | null >;
15
+ };
16
+
17
+ export const AxisRow = ( { label, bindValue, startIcon, anchorRef }: TransformAxisRowProps ) => {
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
+ <SizeControl anchorRef={ anchorRef } startIcon={ startIcon } />
27
+ </PropKeyProvider>
28
+ </Grid>
29
+ </PopoverGridContainer>
30
+ </Grid>
31
+ );
32
+ };
@@ -0,0 +1,44 @@
1
+ import * as React from 'react';
2
+ import { useRef } from 'react';
3
+ import { moveTransformPropTypeUtil } 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 { AxisRow } from './axis-row';
10
+
11
+ const moveAxisControls: { label: string; bindValue: 'x' | 'y' | 'z'; startIcon: React.ReactNode }[] = [
12
+ {
13
+ label: __( 'Move X', 'elementor' ),
14
+ bindValue: 'x',
15
+ startIcon: <ArrowRightIcon fontSize={ 'tiny' } />,
16
+ },
17
+ {
18
+ label: __( 'Move Y', 'elementor' ),
19
+ bindValue: 'y',
20
+ startIcon: <ArrowDownSmallIcon fontSize={ 'tiny' } />,
21
+ },
22
+ {
23
+ label: __( 'Move Z', 'elementor' ),
24
+ bindValue: 'z',
25
+ startIcon: <ArrowDownLeftIcon fontSize={ 'tiny' } />,
26
+ },
27
+ ];
28
+
29
+ export const Move = () => {
30
+ const context = useBoundProp( moveTransformPropTypeUtil );
31
+ const rowRef = useRef< HTMLDivElement >( null );
32
+
33
+ return (
34
+ <Grid container spacing={ 1.5 }>
35
+ <PropProvider { ...context }>
36
+ <PropKeyProvider bind={ 'transform-move' }>
37
+ { moveAxisControls.map( ( control ) => (
38
+ <AxisRow key={ control.bindValue } { ...control } anchorRef={ rowRef } />
39
+ ) ) }
40
+ </PropKeyProvider>
41
+ </PropProvider>
42
+ </Grid>
43
+ );
44
+ };
@@ -0,0 +1,36 @@
1
+ import * as React from 'react';
2
+ import type { PropKey } from '@elementor/editor-props';
3
+ import { Box, Tab, TabPanel, Tabs, useTabs } from '@elementor/ui';
4
+ import { __ } from '@wordpress/i18n';
5
+
6
+ import { PropKeyProvider } from '../../bound-prop-context';
7
+ import { PopoverContent } from '../../components/popover-content';
8
+ import { Move } from './functions/move';
9
+
10
+ type TransformTabValue = 'transform-move';
11
+
12
+ export const TransformContent = ( { bind }: { anchorEl?: HTMLElement | null; bind: PropKey } ) => {
13
+ const { getTabsProps, getTabProps, getTabPanelProps } = useTabs< TransformTabValue >( 'transform-move' );
14
+
15
+ return (
16
+ <PropKeyProvider bind={ bind }>
17
+ <PopoverContent>
18
+ <Box sx={ { width: '100%' } }>
19
+ <Box sx={ { borderBottom: 1, borderColor: 'divider' } }>
20
+ <Tabs
21
+ size="small"
22
+ variant="fullWidth"
23
+ { ...getTabsProps() }
24
+ aria-label={ __( 'Transform', 'elementor' ) }
25
+ >
26
+ <Tab label={ __( 'Move', 'elementor' ) } { ...getTabProps( 'transform-move' ) } />
27
+ </Tabs>
28
+ </Box>
29
+ <TabPanel sx={ { p: 1.5 } } { ...getTabPanelProps( 'transform-move' ) }>
30
+ <Move />
31
+ </TabPanel>
32
+ </Box>
33
+ </PopoverContent>
34
+ </PropKeyProvider>
35
+ );
36
+ };
@@ -0,0 +1,12 @@
1
+ import * as React from 'react';
2
+ import { type TransformItemPropValue } from '@elementor/editor-props';
3
+ import { ArrowsMaximizeIcon } from '@elementor/icons';
4
+
5
+ export const TransformIcon = ( { value }: { value: TransformItemPropValue } ) => {
6
+ switch ( value.$$type ) {
7
+ case 'transform-move':
8
+ return <ArrowsMaximizeIcon fontSize="tiny" />;
9
+ default:
10
+ return null;
11
+ }
12
+ };
@@ -0,0 +1,27 @@
1
+ import * as React from 'react';
2
+ import type { TransformItemPropValue } from '@elementor/editor-props';
3
+ import { Box } from '@elementor/ui';
4
+ import { __ } from '@wordpress/i18n';
5
+
6
+ const transformMoveValue = ( value: TransformItemPropValue[ 'value' ] ) =>
7
+ Object.values( value )
8
+ .map( ( axis ) => `${ axis?.value.size }${ axis?.value.unit }` )
9
+ .join( ', ' );
10
+
11
+ export const TransformLabel = ( props: { value: TransformItemPropValue } ) => {
12
+ const { $$type, value } = props.value;
13
+ switch ( $$type ) {
14
+ case 'transform-move':
15
+ return <Label label={ __( 'Move', 'elementor' ) } value={ transformMoveValue( value ) } />;
16
+ default:
17
+ return '';
18
+ }
19
+ };
20
+
21
+ const Label = ( { label, value }: { label: string; value: string } ) => {
22
+ return (
23
+ <Box component="span">
24
+ { label }: { value }
25
+ </Box>
26
+ );
27
+ };
@@ -0,0 +1,42 @@
1
+ import * as React from 'react';
2
+ import { type TransformItemPropValue, transformPropTypeUtil } from '@elementor/editor-props';
3
+ import { __ } from '@wordpress/i18n';
4
+
5
+ import { PropProvider, useBoundProp } from '../../bound-prop-context';
6
+ import { Repeater } from '../../components/repeater';
7
+ import { createControl } from '../../create-control';
8
+ import { TransformContent } from './transform-content';
9
+ import { TransformIcon } from './transform-icon';
10
+ import { TransformLabel } from './transform-label';
11
+
12
+ const initialTransformValue: TransformItemPropValue = {
13
+ $$type: 'transform-move',
14
+ value: {
15
+ x: { $$type: 'size', value: { size: 0, unit: 'px' } },
16
+ y: { $$type: 'size', value: { size: 0, unit: 'px' } },
17
+ z: { $$type: 'size', value: { size: 0, unit: 'px' } },
18
+ },
19
+ };
20
+
21
+ export const TransformRepeaterControl = createControl( () => {
22
+ const { propType, value: transformValues, setValue, disabled } = useBoundProp( transformPropTypeUtil );
23
+
24
+ return (
25
+ <PropProvider propType={ propType } value={ transformValues } setValue={ setValue }>
26
+ <Repeater
27
+ openOnAdd
28
+ disabled={ disabled }
29
+ values={ transformValues ?? [] }
30
+ setValues={ setValue }
31
+ label={ __( 'Transform', 'elementor' ) }
32
+ showDuplicate={ false }
33
+ itemSettings={ {
34
+ Icon: TransformIcon,
35
+ Label: TransformLabel,
36
+ Content: TransformContent,
37
+ initialValues: initialTransformValue,
38
+ } }
39
+ />
40
+ </PropProvider>
41
+ );
42
+ } );
@@ -9,7 +9,12 @@ export type ChildControlConfig = {
9
9
  label?: string;
10
10
  };
11
11
 
12
- const RepeatableControlContext = createContext< ChildControlConfig | undefined >( undefined );
12
+ type RepeatableControlContextType = ChildControlConfig & {
13
+ placeholder: string;
14
+ patternLabel: string;
15
+ };
16
+
17
+ const RepeatableControlContext = createContext< RepeatableControlContextType | undefined >( undefined );
13
18
 
14
19
  const useRepeatableControlContext = () => {
15
20
  const context = useContext( RepeatableControlContext );
package/src/index.ts CHANGED
@@ -23,6 +23,7 @@ export { SwitchControl } from './controls/switch-control';
23
23
  export { RepeatableControl } from './controls/repeatable-control';
24
24
  export { KeyValueControl } from './controls/key-value-control';
25
25
  export { PositionControl } from './controls/position-control';
26
+ export { TransformRepeaterControl } from './controls/transform-control/transform-repeater-control';
26
27
  export { PopoverContent } from './components/popover-content';
27
28
 
28
29
  // components
@@ -1,10 +1,12 @@
1
1
  export const defaultUnits = [ 'px', '%', 'em', 'rem', 'vw', 'vh' ] as const;
2
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
3
+ const degreeUnits = [ 'deg', 'rad', 'grad', 'turn' ] as const;
2
4
  const defaultExtendedOptions = [ 'auto', 'custom' ] as const;
3
5
 
4
6
  export type Unit = ( typeof defaultUnits )[ number ];
5
-
7
+ export type DegreeUnit = ( typeof degreeUnits )[ number ];
6
8
  export type ExtendedOption = ( typeof defaultExtendedOptions )[ number ];
7
9
 
8
- export function isUnitExtendedOption( unit: Unit | ExtendedOption ): unit is ExtendedOption {
10
+ export function isUnitExtendedOption( unit: Unit | DegreeUnit | ExtendedOption ): unit is ExtendedOption {
9
11
  return defaultExtendedOptions.includes( unit as ExtendedOption );
10
12
  }