@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.
- package/CHANGELOG.md +66 -0
- package/dist/index.d.mts +20 -8
- package/dist/index.d.ts +20 -8
- package/dist/index.js +1092 -714
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +937 -549
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -7
- package/src/bound-prop-context/prop-context.tsx +3 -3
- package/src/bound-prop-context/prop-key-context.tsx +1 -0
- package/src/bound-prop-context/use-bound-prop.ts +5 -1
- package/src/components/font-family-selector.tsx +54 -56
- package/src/components/repeater.tsx +22 -11
- package/src/components/size-control/size-input.tsx +4 -4
- package/src/components/text-field-popover.tsx +19 -18
- package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +3 -15
- package/src/controls/box-shadow-repeater-control.tsx +1 -1
- package/src/controls/color-control.tsx +12 -1
- package/src/controls/equal-unequal-sizes-control.tsx +1 -1
- package/src/controls/filter-control/drop-shadow-item-content.tsx +69 -0
- package/src/controls/filter-control/drop-shadow-item-label.tsx +20 -0
- package/src/controls/filter-repeater-control.tsx +108 -21
- package/src/controls/font-family-control/font-family-control.tsx +14 -2
- package/src/controls/image-control.tsx +45 -16
- package/src/controls/key-value-control.tsx +57 -46
- package/src/controls/link-control.tsx +25 -20
- package/src/controls/linked-dimensions-control.tsx +1 -1
- package/src/controls/repeatable-control.tsx +100 -21
- package/src/controls/select-control.tsx +22 -2
- package/src/controls/size-control.tsx +25 -12
- package/src/controls/switch-control.tsx +9 -1
- package/src/controls/text-control.tsx +33 -18
- package/src/controls/transform-control/functions/axis-row.tsx +32 -0
- package/src/controls/transform-control/functions/move.tsx +44 -0
- package/src/controls/transform-control/transform-content.tsx +36 -0
- package/src/controls/transform-control/transform-icon.tsx +12 -0
- package/src/controls/transform-control/transform-label.tsx +27 -0
- package/src/controls/transform-control/transform-repeater-control.tsx +42 -0
- package/src/hooks/use-repeatable-control-context.ts +6 -1
- package/src/index.ts +1 -0
- 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
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
const contextValue = useMemo(
|
|
52
|
+
() => ( {
|
|
53
|
+
...childControlConfig,
|
|
54
|
+
placeholder: placeholder || '',
|
|
55
|
+
patternLabel: patternLabel || '',
|
|
56
|
+
} ),
|
|
57
|
+
[ childControlConfig, placeholder, patternLabel ]
|
|
58
|
+
);
|
|
58
59
|
|
|
59
|
-
|
|
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={
|
|
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:
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
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 {
|
|
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 ??
|
|
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 =
|
|
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
|
|
108
|
-
const mergedStates = {
|
|
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(
|
|
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
|
|
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(
|
|
10
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
}
|