@elementor/editor-controls 1.0.0 → 1.2.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 +60 -0
- package/dist/index.d.mts +78 -41
- package/dist/index.d.ts +78 -41
- package/dist/index.js +875 -617
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +713 -467
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -11
- package/src/components/font-family-selector.tsx +50 -174
- package/src/components/popover-content.tsx +3 -11
- package/src/components/repeater.tsx +27 -11
- package/src/components/text-field-popover.tsx +3 -3
- package/src/controls/aspect-ratio-control.tsx +20 -2
- package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-position.tsx +2 -2
- package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-size.tsx +2 -2
- package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +9 -4
- package/src/controls/box-shadow-repeater-control.tsx +2 -2
- package/src/controls/equal-unequal-sizes-control.tsx +3 -9
- package/src/controls/filter-repeater-control.tsx +186 -0
- package/src/controls/font-family-control/font-family-control.tsx +6 -2
- package/src/controls/gap-control.tsx +3 -3
- package/src/controls/image-control.tsx +22 -35
- package/src/controls/key-value-control.tsx +119 -0
- package/src/controls/link-control.tsx +3 -1
- package/src/controls/linked-dimensions-control.tsx +3 -3
- package/src/controls/number-control.tsx +3 -3
- package/src/controls/position-control.tsx +109 -0
- package/src/controls/repeatable-control.tsx +119 -0
- package/src/controls/size-control.tsx +11 -9
- package/src/controls/stroke-control.tsx +2 -2
- package/src/controls/svg-media-control.tsx +0 -2
- package/src/hooks/use-repeatable-control-context.ts +24 -0
- package/src/index.ts +6 -1
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useRef } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
blurFilterPropTypeUtil,
|
|
5
|
+
brightnessFilterPropTypeUtil,
|
|
6
|
+
type FilterItemPropValue,
|
|
7
|
+
filterPropTypeUtil,
|
|
8
|
+
type PropKey,
|
|
9
|
+
type PropTypeUtil,
|
|
10
|
+
type SizePropValue,
|
|
11
|
+
} from '@elementor/editor-props';
|
|
12
|
+
import { MenuListItem } from '@elementor/editor-ui';
|
|
13
|
+
import { Box, Grid, Select, type SelectChangeEvent } from '@elementor/ui';
|
|
14
|
+
import { __ } from '@wordpress/i18n';
|
|
15
|
+
|
|
16
|
+
import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-context';
|
|
17
|
+
import { ControlLabel } from '../components/control-label';
|
|
18
|
+
import { PopoverContent } from '../components/popover-content';
|
|
19
|
+
import { PopoverGridContainer } from '../components/popover-grid-container';
|
|
20
|
+
import { Repeater } from '../components/repeater';
|
|
21
|
+
import { createControl } from '../create-control';
|
|
22
|
+
import { defaultUnits } from '../utils/size-control';
|
|
23
|
+
import { SizeControl } from './size-control';
|
|
24
|
+
|
|
25
|
+
type FilterType = FilterItemPropValue[ '$$type' ];
|
|
26
|
+
type FilterValue = FilterItemPropValue[ 'value' ];
|
|
27
|
+
|
|
28
|
+
const DEFAULT_FILTER_KEY: FilterType = 'blur';
|
|
29
|
+
|
|
30
|
+
type FilterItemConfig = {
|
|
31
|
+
defaultValue: FilterValue;
|
|
32
|
+
name: string;
|
|
33
|
+
valueName: string;
|
|
34
|
+
propType: PropTypeUtil< FilterValue, FilterValue >;
|
|
35
|
+
units?: Exclude< SizePropValue[ 'value' ][ 'unit' ], 'custom' | 'auto' >[];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const filterConfig: Record< FilterType, FilterItemConfig > = {
|
|
39
|
+
blur: {
|
|
40
|
+
defaultValue: { $$type: 'radius', radius: { $$type: 'size', value: { size: 0, unit: 'px' } } },
|
|
41
|
+
name: __( 'Blur', 'elementor' ),
|
|
42
|
+
valueName: __( 'Radius', 'elementor' ),
|
|
43
|
+
propType: blurFilterPropTypeUtil,
|
|
44
|
+
units: defaultUnits.filter( ( unit ) => unit !== '%' ),
|
|
45
|
+
},
|
|
46
|
+
brightness: {
|
|
47
|
+
defaultValue: { $$type: 'amount', amount: { $$type: 'size', value: { size: 100, unit: '%' } } },
|
|
48
|
+
name: __( 'Brightness', 'elementor' ),
|
|
49
|
+
valueName: __( 'Amount', 'elementor' ),
|
|
50
|
+
propType: brightnessFilterPropTypeUtil,
|
|
51
|
+
units: [ '%' ],
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const filterKeys = Object.keys( filterConfig ) as FilterType[];
|
|
56
|
+
|
|
57
|
+
const singleSizeFilterNames = filterKeys.filter( ( name ) => {
|
|
58
|
+
const filter = filterConfig[ name as FilterType ].defaultValue;
|
|
59
|
+
|
|
60
|
+
return filter[ filter.$$type ].$$type === 'size';
|
|
61
|
+
} ) as FilterType[];
|
|
62
|
+
|
|
63
|
+
export const FilterRepeaterControl = createControl( () => {
|
|
64
|
+
const { propType, value: filterValues, setValue, disabled } = useBoundProp( filterPropTypeUtil );
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<PropProvider propType={ propType } value={ filterValues } setValue={ setValue }>
|
|
68
|
+
<Repeater
|
|
69
|
+
openOnAdd
|
|
70
|
+
disabled={ disabled }
|
|
71
|
+
values={ filterValues ?? [] }
|
|
72
|
+
setValues={ setValue }
|
|
73
|
+
label={ __( 'Filter', 'elementor' ) }
|
|
74
|
+
itemSettings={ {
|
|
75
|
+
Icon: ItemIcon,
|
|
76
|
+
Label: ItemLabel,
|
|
77
|
+
Content: ItemContent,
|
|
78
|
+
initialValues: {
|
|
79
|
+
$$type: DEFAULT_FILTER_KEY,
|
|
80
|
+
value: filterConfig[ DEFAULT_FILTER_KEY ].defaultValue,
|
|
81
|
+
} as FilterItemPropValue,
|
|
82
|
+
} }
|
|
83
|
+
/>
|
|
84
|
+
</PropProvider>
|
|
85
|
+
);
|
|
86
|
+
} );
|
|
87
|
+
|
|
88
|
+
const ItemIcon = () => <></>;
|
|
89
|
+
|
|
90
|
+
const ItemLabel = ( props: { value: FilterItemPropValue } ) => {
|
|
91
|
+
const { $$type } = props.value;
|
|
92
|
+
|
|
93
|
+
return singleSizeFilterNames.includes( $$type ) && <SingleSizeItemLabel value={ props.value } />;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const SingleSizeItemLabel = ( { value }: { value: FilterItemPropValue } ) => {
|
|
97
|
+
const { $$type, value: sizeValue } = value;
|
|
98
|
+
const { $$type: key } = filterConfig[ $$type ].defaultValue;
|
|
99
|
+
const defaultUnit = filterConfig[ $$type ].defaultValue[ key ].value.unit;
|
|
100
|
+
const { unit, size } = sizeValue[ key ]?.value ?? { unit: defaultUnit, size: 0 };
|
|
101
|
+
|
|
102
|
+
const label = (
|
|
103
|
+
<Box component="span" style={ { textTransform: 'capitalize' } }>
|
|
104
|
+
{ value.$$type }:
|
|
105
|
+
</Box>
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<Box component="span">
|
|
110
|
+
{ label }
|
|
111
|
+
{ unit !== 'custom' ? ` ${ size ?? 0 }${ unit ?? defaultUnit }` : size }
|
|
112
|
+
</Box>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const ItemContent = ( { bind }: { bind: PropKey } ) => {
|
|
117
|
+
const { value: filterValues, setValue } = useBoundProp( filterPropTypeUtil );
|
|
118
|
+
const itemIndex = parseInt( bind, 10 );
|
|
119
|
+
const item = filterValues?.[ itemIndex ];
|
|
120
|
+
|
|
121
|
+
const handleChange = ( e: SelectChangeEvent< string > ) => {
|
|
122
|
+
const newFilterValues = [ ...filterValues ];
|
|
123
|
+
const filterType = e.target.value as FilterType;
|
|
124
|
+
|
|
125
|
+
newFilterValues[ itemIndex ] = {
|
|
126
|
+
$$type: filterType,
|
|
127
|
+
value: filterConfig[ filterType ].defaultValue,
|
|
128
|
+
} as FilterItemPropValue;
|
|
129
|
+
|
|
130
|
+
setValue( newFilterValues );
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<PropKeyProvider bind={ bind }>
|
|
135
|
+
<PopoverContent p={ 1.5 }>
|
|
136
|
+
<PopoverGridContainer>
|
|
137
|
+
<Grid item xs={ 6 }>
|
|
138
|
+
<ControlLabel>{ __( 'Filter', 'elementor' ) }</ControlLabel>
|
|
139
|
+
</Grid>
|
|
140
|
+
<Grid item xs={ 6 }>
|
|
141
|
+
<Select
|
|
142
|
+
sx={ { overflow: 'hidden' } }
|
|
143
|
+
size="tiny"
|
|
144
|
+
value={ item?.$$type ?? DEFAULT_FILTER_KEY }
|
|
145
|
+
onChange={ handleChange }
|
|
146
|
+
fullWidth
|
|
147
|
+
>
|
|
148
|
+
{ filterKeys.map( ( filterKey ) => (
|
|
149
|
+
<MenuListItem key={ filterKey } value={ filterKey }>
|
|
150
|
+
{ filterConfig[ filterKey ].name }
|
|
151
|
+
</MenuListItem>
|
|
152
|
+
) ) }
|
|
153
|
+
</Select>
|
|
154
|
+
</Grid>
|
|
155
|
+
</PopoverGridContainer>
|
|
156
|
+
<Content filterType={ item?.$$type } />
|
|
157
|
+
</PopoverContent>
|
|
158
|
+
</PropKeyProvider>
|
|
159
|
+
);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const Content = ( { filterType }: { filterType: FilterType } ) => {
|
|
163
|
+
return singleSizeFilterNames.includes( filterType ) && <SingleSizeItemContent filterType={ filterType } />;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const SingleSizeItemContent = ( { filterType }: { filterType: FilterType } ) => {
|
|
167
|
+
const { propType, valueName, defaultValue, units } = filterConfig[ filterType ];
|
|
168
|
+
const { $$type } = defaultValue;
|
|
169
|
+
const context = useBoundProp( propType );
|
|
170
|
+
const rowRef = useRef< HTMLDivElement >( null );
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<PropProvider { ...context }>
|
|
174
|
+
<PropKeyProvider bind={ $$type }>
|
|
175
|
+
<PopoverGridContainer ref={ rowRef }>
|
|
176
|
+
<Grid item xs={ 6 }>
|
|
177
|
+
<ControlLabel>{ valueName }</ControlLabel>
|
|
178
|
+
</Grid>
|
|
179
|
+
<Grid item xs={ 6 }>
|
|
180
|
+
<SizeControl anchorRef={ rowRef } units={ units } />
|
|
181
|
+
</Grid>
|
|
182
|
+
</PopoverGridContainer>
|
|
183
|
+
</PropKeyProvider>
|
|
184
|
+
</PropProvider>
|
|
185
|
+
);
|
|
186
|
+
};
|
|
@@ -15,11 +15,12 @@ export type FontCategory = {
|
|
|
15
15
|
|
|
16
16
|
type FontFamilyControlProps = {
|
|
17
17
|
fontFamilies: FontCategory[];
|
|
18
|
+
sectionWidth: number;
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
const SIZE = 'tiny';
|
|
21
22
|
|
|
22
|
-
export const FontFamilyControl = createControl( ( { fontFamilies }: FontFamilyControlProps ) => {
|
|
23
|
+
export const FontFamilyControl = createControl( ( { fontFamilies, sectionWidth }: FontFamilyControlProps ) => {
|
|
23
24
|
const { value: fontFamily, setValue: setFontFamily, disabled } = useBoundProp( stringPropTypeUtil );
|
|
24
25
|
|
|
25
26
|
const popoverState = usePopupState( { variant: 'popover' } );
|
|
@@ -39,7 +40,9 @@ export const FontFamilyControl = createControl( ( { fontFamilies }: FontFamilyCo
|
|
|
39
40
|
<Popover
|
|
40
41
|
disablePortal
|
|
41
42
|
disableScrollLock
|
|
42
|
-
anchorOrigin={ { vertical: 'bottom', horizontal: '
|
|
43
|
+
anchorOrigin={ { vertical: 'bottom', horizontal: 'right' } }
|
|
44
|
+
transformOrigin={ { vertical: 'top', horizontal: 'right' } }
|
|
45
|
+
sx={ { my: 1.5 } }
|
|
43
46
|
{ ...bindPopover( popoverState ) }
|
|
44
47
|
>
|
|
45
48
|
<FontFamilySelector
|
|
@@ -47,6 +50,7 @@ export const FontFamilyControl = createControl( ( { fontFamilies }: FontFamilyCo
|
|
|
47
50
|
fontFamily={ fontFamily }
|
|
48
51
|
onFontFamilyChange={ setFontFamily }
|
|
49
52
|
onClose={ popoverState.close }
|
|
53
|
+
sectionWidth={ sectionWidth }
|
|
50
54
|
/>
|
|
51
55
|
</Popover>
|
|
52
56
|
</>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { type
|
|
2
|
+
import { type RefObject, useRef } from 'react';
|
|
3
3
|
import { layoutDirectionPropTypeUtil, type PropKey, sizePropTypeUtil } from '@elementor/editor-props';
|
|
4
4
|
import { DetachIcon, LinkIcon } from '@elementor/icons';
|
|
5
5
|
import { Grid, Stack, ToggleButton, Tooltip } from '@elementor/ui';
|
|
@@ -19,7 +19,7 @@ export const GapControl = createControl( ( { label }: { label: string } ) => {
|
|
|
19
19
|
disabled: directionDisabled,
|
|
20
20
|
} = useBoundProp( layoutDirectionPropTypeUtil );
|
|
21
21
|
|
|
22
|
-
const stackRef
|
|
22
|
+
const stackRef = useRef< HTMLDivElement >( null );
|
|
23
23
|
|
|
24
24
|
const { value: sizeValue, setValue: setSizeValue, disabled: sizeDisabled } = useBoundProp( sizePropTypeUtil );
|
|
25
25
|
|
|
@@ -96,7 +96,7 @@ const Control = ( {
|
|
|
96
96
|
}: {
|
|
97
97
|
bind: PropKey;
|
|
98
98
|
isLinked: boolean;
|
|
99
|
-
anchorRef:
|
|
99
|
+
anchorRef: RefObject< HTMLDivElement >;
|
|
100
100
|
} ) => {
|
|
101
101
|
if ( isLinked ) {
|
|
102
102
|
return <SizeControl anchorRef={ anchorRef } />;
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { imagePropTypeUtil } from '@elementor/editor-props';
|
|
3
|
-
import {
|
|
3
|
+
import { Stack } from '@elementor/ui';
|
|
4
4
|
import { type MediaType } from '@elementor/wp-media';
|
|
5
|
-
import { __ } from '@wordpress/i18n';
|
|
6
5
|
|
|
7
6
|
import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-context';
|
|
8
|
-
import { ControlFormLabel } from '../components/control-form-label';
|
|
9
7
|
import { createControl } from '../create-control';
|
|
10
8
|
import { useUnfilteredFilesUpload } from '../hooks/use-unfiltered-files-upload';
|
|
11
9
|
import { ImageMediaControl } from './image-media-control';
|
|
@@ -13,40 +11,29 @@ import { SelectControl } from './select-control';
|
|
|
13
11
|
|
|
14
12
|
type ImageControlProps = {
|
|
15
13
|
sizes: { label: string; value: string }[];
|
|
16
|
-
resolutionLabel?: string;
|
|
17
14
|
showMode?: 'all' | 'media' | 'sizes';
|
|
18
15
|
};
|
|
19
16
|
|
|
20
|
-
export const ImageControl = createControl(
|
|
21
|
-
|
|
22
|
-
const propContext = useBoundProp( imagePropTypeUtil );
|
|
17
|
+
export const ImageControl = createControl( ( { sizes, showMode = 'all' }: ImageControlProps ) => {
|
|
18
|
+
const propContext = useBoundProp( imagePropTypeUtil );
|
|
23
19
|
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
const { data: allowSvgUpload } = useUnfilteredFilesUpload();
|
|
21
|
+
const mediaTypes: MediaType[] = allowSvgUpload ? [ 'image', 'svg' ] : [ 'image' ];
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
{
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
</Grid>
|
|
45
|
-
</Grid>
|
|
46
|
-
</PropKeyProvider>
|
|
47
|
-
) : null }
|
|
48
|
-
</Stack>
|
|
49
|
-
</PropProvider>
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
);
|
|
23
|
+
return (
|
|
24
|
+
<PropProvider { ...propContext }>
|
|
25
|
+
<Stack gap={ 1.5 }>
|
|
26
|
+
{ [ 'all', 'media' ].includes( showMode ) ? (
|
|
27
|
+
<PropKeyProvider bind={ 'src' }>
|
|
28
|
+
<ImageMediaControl mediaTypes={ mediaTypes } />
|
|
29
|
+
</PropKeyProvider>
|
|
30
|
+
) : null }
|
|
31
|
+
{ [ 'all', 'sizes' ].includes( showMode ) ? (
|
|
32
|
+
<PropKeyProvider bind={ 'size' }>
|
|
33
|
+
<SelectControl options={ sizes } />
|
|
34
|
+
</PropKeyProvider>
|
|
35
|
+
) : null }
|
|
36
|
+
</Stack>
|
|
37
|
+
</PropProvider>
|
|
38
|
+
);
|
|
39
|
+
} );
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type ChangeEvent, useMemo, useState } from 'react';
|
|
3
|
+
import { keyValuePropTypeUtil } from '@elementor/editor-props';
|
|
4
|
+
import { FormHelperText, FormLabel, Grid, TextField } from '@elementor/ui';
|
|
5
|
+
import { __ } from '@wordpress/i18n';
|
|
6
|
+
|
|
7
|
+
import { useBoundProp } from '../bound-prop-context';
|
|
8
|
+
import ControlActions from '../control-actions/control-actions';
|
|
9
|
+
import { createControl } from '../create-control';
|
|
10
|
+
|
|
11
|
+
type FieldType = 'key' | 'value';
|
|
12
|
+
|
|
13
|
+
type KeyValueControlProps = {
|
|
14
|
+
keyName?: string;
|
|
15
|
+
valueName?: string;
|
|
16
|
+
regexKey?: string;
|
|
17
|
+
regexValue?: string;
|
|
18
|
+
validationErrorMessage?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const KeyValueControl = createControl( ( props: KeyValueControlProps = {} ) => {
|
|
22
|
+
const { value, setValue } = useBoundProp( keyValuePropTypeUtil );
|
|
23
|
+
const [ keyError, setKeyError ] = useState< string | null >( null );
|
|
24
|
+
const [ valueError, setValueError ] = useState< string | null >( null );
|
|
25
|
+
|
|
26
|
+
const [ sessionState, setSessionState ] = useState( {
|
|
27
|
+
key: value?.key?.value || '',
|
|
28
|
+
value: value?.value?.value || '',
|
|
29
|
+
} );
|
|
30
|
+
|
|
31
|
+
const keyLabel = props.keyName || __( 'Key', 'elementor' );
|
|
32
|
+
const valueLabel = props.valueName || __( 'Value', 'elementor' );
|
|
33
|
+
|
|
34
|
+
const [ keyRegex, valueRegex, errMsg ] = useMemo< [ RegExp | undefined, RegExp | undefined, string ] >(
|
|
35
|
+
() => [
|
|
36
|
+
props.regexKey ? new RegExp( props.regexKey ) : undefined,
|
|
37
|
+
props.regexValue ? new RegExp( props.regexValue ) : undefined,
|
|
38
|
+
props.validationErrorMessage || __( 'Invalid Format', 'elementor' ),
|
|
39
|
+
],
|
|
40
|
+
[ props.regexKey, props.regexValue, props.validationErrorMessage ]
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const validate = ( newValue: string, fieldType: string ): boolean => {
|
|
44
|
+
if ( fieldType === 'key' && keyRegex ) {
|
|
45
|
+
const isValid = keyRegex.test( newValue );
|
|
46
|
+
setKeyError( isValid ? null : errMsg );
|
|
47
|
+
return isValid;
|
|
48
|
+
} else if ( fieldType === 'value' && valueRegex ) {
|
|
49
|
+
const isValid = valueRegex.test( newValue );
|
|
50
|
+
setValueError( isValid ? null : errMsg );
|
|
51
|
+
return isValid;
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const handleChange = ( event: ChangeEvent< HTMLInputElement >, fieldType: FieldType ) => {
|
|
57
|
+
const newValue = event.target.value;
|
|
58
|
+
|
|
59
|
+
setSessionState( ( prev ) => ( {
|
|
60
|
+
...prev,
|
|
61
|
+
[ fieldType ]: newValue,
|
|
62
|
+
} ) );
|
|
63
|
+
|
|
64
|
+
if ( validate( newValue, fieldType ) ) {
|
|
65
|
+
setValue( {
|
|
66
|
+
...value,
|
|
67
|
+
[ fieldType ]: {
|
|
68
|
+
value: newValue,
|
|
69
|
+
$$type: 'string',
|
|
70
|
+
},
|
|
71
|
+
} );
|
|
72
|
+
} else {
|
|
73
|
+
setValue( {
|
|
74
|
+
...value,
|
|
75
|
+
[ fieldType ]: {
|
|
76
|
+
value: '',
|
|
77
|
+
$$type: 'string',
|
|
78
|
+
},
|
|
79
|
+
} );
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const isKeyInvalid = keyError !== null;
|
|
84
|
+
const isValueInvalid = valueError !== null;
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<ControlActions>
|
|
88
|
+
<Grid container gap={ 1.5 }>
|
|
89
|
+
<Grid item xs={ 12 }>
|
|
90
|
+
<FormLabel size="tiny">{ keyLabel }</FormLabel>
|
|
91
|
+
<TextField
|
|
92
|
+
// eslint-disable-next-line jsx-a11y/no-autofocus
|
|
93
|
+
autoFocus
|
|
94
|
+
sx={ { pt: 1 } }
|
|
95
|
+
size="tiny"
|
|
96
|
+
fullWidth
|
|
97
|
+
value={ sessionState.key }
|
|
98
|
+
onChange={ ( e: ChangeEvent< HTMLInputElement > ) => handleChange( e, 'key' ) }
|
|
99
|
+
error={ isKeyInvalid }
|
|
100
|
+
/>
|
|
101
|
+
{ isKeyInvalid && <FormHelperText error>{ keyError }</FormHelperText> }
|
|
102
|
+
</Grid>
|
|
103
|
+
<Grid item xs={ 12 }>
|
|
104
|
+
<FormLabel size="tiny">{ valueLabel }</FormLabel>
|
|
105
|
+
<TextField
|
|
106
|
+
sx={ { pt: 1 } }
|
|
107
|
+
size="tiny"
|
|
108
|
+
fullWidth
|
|
109
|
+
value={ sessionState.value }
|
|
110
|
+
onChange={ ( e: ChangeEvent< HTMLInputElement > ) => handleChange( e, 'value' ) }
|
|
111
|
+
disabled={ isKeyInvalid }
|
|
112
|
+
error={ isValueInvalid }
|
|
113
|
+
/>
|
|
114
|
+
{ isValueInvalid && <FormHelperText error>{ valueError }</FormHelperText> }
|
|
115
|
+
</Grid>
|
|
116
|
+
</Grid>
|
|
117
|
+
</ControlActions>
|
|
118
|
+
);
|
|
119
|
+
} );
|
|
@@ -38,6 +38,7 @@ type Props = ControlProps< {
|
|
|
38
38
|
allowCustomValues?: boolean;
|
|
39
39
|
minInputLength?: number;
|
|
40
40
|
placeholder?: string;
|
|
41
|
+
label?: string;
|
|
41
42
|
} >;
|
|
42
43
|
|
|
43
44
|
type LinkSessionValue = {
|
|
@@ -66,6 +67,7 @@ export const LinkControl = createControl( ( props: Props ) => {
|
|
|
66
67
|
placeholder,
|
|
67
68
|
minInputLength = 2,
|
|
68
69
|
context: { elementId },
|
|
70
|
+
label = __( 'Link', 'elementor' ),
|
|
69
71
|
} = props || {};
|
|
70
72
|
|
|
71
73
|
const [ linkInLinkRestriction, setLinkInLinkRestriction ] = useState( getLinkInLinkRestriction( elementId ) );
|
|
@@ -163,7 +165,7 @@ export const LinkControl = createControl( ( props: Props ) => {
|
|
|
163
165
|
marginInlineEnd: -0.75,
|
|
164
166
|
} }
|
|
165
167
|
>
|
|
166
|
-
<ControlFormLabel>{
|
|
168
|
+
<ControlFormLabel>{ label }</ControlFormLabel>
|
|
167
169
|
<ConditionalInfoTip isVisible={ ! isActive } linkInLinkRestriction={ linkInLinkRestriction }>
|
|
168
170
|
<ToggleIconControl
|
|
169
171
|
disabled={ shouldDisableAddingLink }
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { type
|
|
2
|
+
import { type RefObject, useRef } from 'react';
|
|
3
3
|
import { dimensionsPropTypeUtil, type PropKey, sizePropTypeUtil } from '@elementor/editor-props';
|
|
4
4
|
import { isExperimentActive } from '@elementor/editor-v1-adapters';
|
|
5
5
|
import { DetachIcon, LinkIcon, SideBottomIcon, SideLeftIcon, SideRightIcon, SideTopIcon } from '@elementor/icons';
|
|
@@ -24,7 +24,7 @@ export const LinkedDimensionsControl = createControl(
|
|
|
24
24
|
extendedOptions?: ExtendedOption[];
|
|
25
25
|
} ) => {
|
|
26
26
|
const { value: sizeValue, setValue: setSizeValue, disabled: sizeDisabled } = useBoundProp( sizePropTypeUtil );
|
|
27
|
-
const gridRowRefs:
|
|
27
|
+
const gridRowRefs: RefObject< HTMLDivElement >[] = [ useRef( null ), useRef( null ) ];
|
|
28
28
|
|
|
29
29
|
const {
|
|
30
30
|
value: dimensionsValue,
|
|
@@ -127,7 +127,7 @@ const Control = ( {
|
|
|
127
127
|
startIcon: React.ReactNode;
|
|
128
128
|
isLinked: boolean;
|
|
129
129
|
extendedOptions?: ExtendedOption[];
|
|
130
|
-
anchorRef:
|
|
130
|
+
anchorRef: RefObject< HTMLDivElement >;
|
|
131
131
|
} ) => {
|
|
132
132
|
if ( isLinked ) {
|
|
133
133
|
return <SizeControl startIcon={ startIcon } extendedOptions={ extendedOptions } anchorRef={ anchorRef } />;
|
|
@@ -13,7 +13,7 @@ const RESTRICTED_INPUT_KEYS = [ 'e', 'E', '+', '-' ];
|
|
|
13
13
|
|
|
14
14
|
export const NumberControl = createControl(
|
|
15
15
|
( {
|
|
16
|
-
placeholder,
|
|
16
|
+
placeholder: labelPlaceholder,
|
|
17
17
|
max = Number.MAX_VALUE,
|
|
18
18
|
min = -Number.MAX_VALUE,
|
|
19
19
|
step = 1,
|
|
@@ -25,7 +25,7 @@ export const NumberControl = createControl(
|
|
|
25
25
|
step?: number;
|
|
26
26
|
shouldForceInt?: boolean;
|
|
27
27
|
} ) => {
|
|
28
|
-
const { value, setValue, disabled } = useBoundProp( numberPropTypeUtil );
|
|
28
|
+
const { value, setValue, placeholder, disabled } = useBoundProp( numberPropTypeUtil );
|
|
29
29
|
|
|
30
30
|
const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
|
|
31
31
|
const eventValue: string = event.target.value;
|
|
@@ -50,7 +50,7 @@ export const NumberControl = createControl(
|
|
|
50
50
|
disabled={ disabled }
|
|
51
51
|
value={ isEmptyOrNaN( value ) ? '' : value }
|
|
52
52
|
onChange={ handleChange }
|
|
53
|
-
placeholder={ placeholder }
|
|
53
|
+
placeholder={ labelPlaceholder ?? ( placeholder ? String( placeholder ) : '' ) }
|
|
54
54
|
inputProps={ { step } }
|
|
55
55
|
onKeyDown={ ( event: KeyboardEvent ) => {
|
|
56
56
|
if ( RESTRICTED_INPUT_KEYS.includes( event.key ) ) {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { positionPropTypeUtil, stringPropTypeUtil } from '@elementor/editor-props';
|
|
4
|
+
import { MenuListItem } from '@elementor/editor-ui';
|
|
5
|
+
import { isExperimentActive } from '@elementor/editor-v1-adapters';
|
|
6
|
+
import { LetterXIcon, LetterYIcon } from '@elementor/icons';
|
|
7
|
+
import { Grid, Select, type SelectChangeEvent } from '@elementor/ui';
|
|
8
|
+
import { __ } from '@wordpress/i18n';
|
|
9
|
+
|
|
10
|
+
import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-context';
|
|
11
|
+
import { ControlFormLabel } from '../components/control-form-label';
|
|
12
|
+
import { SizeControl } from './size-control';
|
|
13
|
+
|
|
14
|
+
type Positions =
|
|
15
|
+
| 'center center'
|
|
16
|
+
| 'center left'
|
|
17
|
+
| 'center right'
|
|
18
|
+
| 'top center'
|
|
19
|
+
| 'top left'
|
|
20
|
+
| 'top right'
|
|
21
|
+
| 'bottom center'
|
|
22
|
+
| 'bottom left'
|
|
23
|
+
| 'bottom right'
|
|
24
|
+
| 'custom';
|
|
25
|
+
|
|
26
|
+
const positionOptions = [
|
|
27
|
+
{ label: __( 'Center center', 'elementor' ), value: 'center center' },
|
|
28
|
+
{ label: __( 'Center left', 'elementor' ), value: 'center left' },
|
|
29
|
+
{ label: __( 'Center right', 'elementor' ), value: 'center right' },
|
|
30
|
+
{ label: __( 'Top center', 'elementor' ), value: 'top center' },
|
|
31
|
+
{ label: __( 'Top left', 'elementor' ), value: 'top left' },
|
|
32
|
+
{ label: __( 'Top right', 'elementor' ), value: 'top right' },
|
|
33
|
+
{ label: __( 'Bottom center', 'elementor' ), value: 'bottom center' },
|
|
34
|
+
{ label: __( 'Bottom left', 'elementor' ), value: 'bottom left' },
|
|
35
|
+
{ label: __( 'Bottom right', 'elementor' ), value: 'bottom right' },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
export const PositionControl = () => {
|
|
39
|
+
const positionContext = useBoundProp( positionPropTypeUtil );
|
|
40
|
+
const stringPropContext = useBoundProp( stringPropTypeUtil );
|
|
41
|
+
|
|
42
|
+
const isVersion331Active = isExperimentActive( 'e_v_3_31' );
|
|
43
|
+
const isCustom = !! positionContext.value && isVersion331Active;
|
|
44
|
+
|
|
45
|
+
const availablePositionOptions = useMemo( () => {
|
|
46
|
+
const options = [ ...positionOptions ];
|
|
47
|
+
|
|
48
|
+
if ( isVersion331Active ) {
|
|
49
|
+
options.push( { label: __( 'Custom', 'elementor' ), value: 'custom' } );
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return options;
|
|
53
|
+
}, [ isVersion331Active ] );
|
|
54
|
+
|
|
55
|
+
const handlePositionChange = ( event: SelectChangeEvent< Positions > ) => {
|
|
56
|
+
const value = event.target.value || null;
|
|
57
|
+
|
|
58
|
+
if ( value === 'custom' && isVersion331Active ) {
|
|
59
|
+
positionContext.setValue( { x: null, y: null } );
|
|
60
|
+
} else {
|
|
61
|
+
stringPropContext.setValue( value );
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<Grid container spacing={ 1.5 }>
|
|
67
|
+
<Grid item xs={ 12 }>
|
|
68
|
+
<Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
|
|
69
|
+
<Grid item xs={ 6 }>
|
|
70
|
+
<ControlFormLabel>{ __( 'Object position', 'elementor' ) }</ControlFormLabel>
|
|
71
|
+
</Grid>
|
|
72
|
+
<Grid item xs={ 6 } sx={ { overflow: 'hidden' } }>
|
|
73
|
+
<Select
|
|
74
|
+
size="tiny"
|
|
75
|
+
disabled={ stringPropContext.disabled }
|
|
76
|
+
value={ ( positionContext.value ? 'custom' : stringPropContext.value ) ?? '' }
|
|
77
|
+
onChange={ handlePositionChange }
|
|
78
|
+
fullWidth
|
|
79
|
+
>
|
|
80
|
+
{ availablePositionOptions.map( ( { label, value } ) => (
|
|
81
|
+
<MenuListItem key={ value } value={ value ?? '' }>
|
|
82
|
+
{ label }
|
|
83
|
+
</MenuListItem>
|
|
84
|
+
) ) }
|
|
85
|
+
</Select>
|
|
86
|
+
</Grid>
|
|
87
|
+
</Grid>
|
|
88
|
+
</Grid>
|
|
89
|
+
{ isCustom && (
|
|
90
|
+
<PropProvider { ...positionContext }>
|
|
91
|
+
<Grid item xs={ 12 }>
|
|
92
|
+
<Grid container spacing={ 1.5 }>
|
|
93
|
+
<Grid item xs={ 6 }>
|
|
94
|
+
<PropKeyProvider bind={ 'x' }>
|
|
95
|
+
<SizeControl startIcon={ <LetterXIcon fontSize={ 'tiny' } /> } />
|
|
96
|
+
</PropKeyProvider>
|
|
97
|
+
</Grid>
|
|
98
|
+
<Grid item xs={ 6 }>
|
|
99
|
+
<PropKeyProvider bind={ 'y' }>
|
|
100
|
+
<SizeControl startIcon={ <LetterYIcon fontSize={ 'tiny' } /> } />
|
|
101
|
+
</PropKeyProvider>
|
|
102
|
+
</Grid>
|
|
103
|
+
</Grid>
|
|
104
|
+
</Grid>
|
|
105
|
+
</PropProvider>
|
|
106
|
+
) }
|
|
107
|
+
</Grid>
|
|
108
|
+
);
|
|
109
|
+
};
|