@elementor/editor-controls 1.1.0 → 1.3.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 +57 -0
- package/dist/index.d.mts +26 -13
- package/dist/index.d.ts +26 -13
- package/dist/index.js +979 -575
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +823 -418
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -11
- 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 +30 -13
- package/src/components/popover-content.tsx +3 -11
- package/src/components/repeater.tsx +3 -1
- package/src/components/text-field-popover.tsx +21 -20
- 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 +3 -10
- package/src/controls/box-shadow-repeater-control.tsx +3 -3
- package/src/controls/equal-unequal-sizes-control.tsx +4 -10
- package/src/controls/filter-repeater-control.tsx +186 -0
- package/src/controls/font-family-control/font-family-control.tsx +20 -4
- package/src/controls/gap-control.tsx +3 -3
- package/src/controls/image-control.tsx +46 -30
- package/src/controls/key-value-control.tsx +39 -19
- package/src/controls/link-control.tsx +28 -21
- package/src/controls/linked-dimensions-control.tsx +4 -4
- package/src/controls/number-control.tsx +3 -3
- package/src/controls/repeatable-control.tsx +98 -8
- package/src/controls/select-control.tsx +22 -2
- package/src/controls/size-control.tsx +3 -3
- package/src/controls/stroke-control.tsx +2 -2
- package/src/controls/svg-media-control.tsx +0 -2
- package/src/controls/switch-control.tsx +9 -1
- 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 +4 -1
|
@@ -15,31 +15,46 @@ 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
|
-
const { value: fontFamily, setValue: setFontFamily, disabled } = useBoundProp( stringPropTypeUtil );
|
|
23
|
+
export const FontFamilyControl = createControl( ( { fontFamilies, sectionWidth }: FontFamilyControlProps ) => {
|
|
24
|
+
const { value: fontFamily, setValue: setFontFamily, disabled, placeholder } = useBoundProp( stringPropTypeUtil );
|
|
24
25
|
|
|
25
26
|
const popoverState = usePopupState( { variant: 'popover' } );
|
|
26
27
|
|
|
28
|
+
const isShowingPlaceholder = ! fontFamily && placeholder;
|
|
29
|
+
|
|
27
30
|
return (
|
|
28
31
|
<>
|
|
29
32
|
<ControlActions>
|
|
30
33
|
<UnstableTag
|
|
31
34
|
variant="outlined"
|
|
32
|
-
label={ fontFamily }
|
|
35
|
+
label={ fontFamily || placeholder }
|
|
33
36
|
endIcon={ <ChevronDownIcon fontSize={ SIZE } /> }
|
|
34
37
|
{ ...bindTrigger( popoverState ) }
|
|
35
38
|
fullWidth
|
|
36
39
|
disabled={ disabled }
|
|
40
|
+
sx={
|
|
41
|
+
isShowingPlaceholder
|
|
42
|
+
? {
|
|
43
|
+
'& .MuiTag-label': {
|
|
44
|
+
color: ( theme ) => theme.palette.text.tertiary,
|
|
45
|
+
},
|
|
46
|
+
textTransform: 'capitalize',
|
|
47
|
+
}
|
|
48
|
+
: undefined
|
|
49
|
+
}
|
|
37
50
|
/>
|
|
38
51
|
</ControlActions>
|
|
39
52
|
<Popover
|
|
40
53
|
disablePortal
|
|
41
54
|
disableScrollLock
|
|
42
|
-
anchorOrigin={ { vertical: 'bottom', horizontal: '
|
|
55
|
+
anchorOrigin={ { vertical: 'bottom', horizontal: 'right' } }
|
|
56
|
+
transformOrigin={ { vertical: 'top', horizontal: 'right' } }
|
|
57
|
+
sx={ { my: 1.5 } }
|
|
43
58
|
{ ...bindPopover( popoverState ) }
|
|
44
59
|
>
|
|
45
60
|
<FontFamilySelector
|
|
@@ -47,6 +62,7 @@ export const FontFamilyControl = createControl( ( { fontFamilies }: FontFamilyCo
|
|
|
47
62
|
fontFamily={ fontFamily }
|
|
48
63
|
onFontFamilyChange={ setFontFamily }
|
|
49
64
|
onClose={ popoverState.close }
|
|
65
|
+
sectionWidth={ sectionWidth }
|
|
50
66
|
/>
|
|
51
67
|
</Popover>
|
|
52
68
|
</>
|
|
@@ -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 } />;
|
|
@@ -13,40 +13,56 @@ import { SelectControl } from './select-control';
|
|
|
13
13
|
|
|
14
14
|
type ImageControlProps = {
|
|
15
15
|
sizes: { label: string; value: string }[];
|
|
16
|
-
resolutionLabel?: string;
|
|
17
16
|
showMode?: 'all' | 'media' | 'sizes';
|
|
18
17
|
};
|
|
19
18
|
|
|
20
|
-
export const ImageControl = createControl(
|
|
21
|
-
|
|
22
|
-
const propContext = useBoundProp( imagePropTypeUtil );
|
|
19
|
+
export const ImageControl = createControl( ( { sizes, showMode = 'all' }: ImageControlProps ) => {
|
|
20
|
+
const propContext = useBoundProp( imagePropTypeUtil );
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
let componentToRender;
|
|
23
|
+
switch ( showMode ) {
|
|
24
|
+
case 'media':
|
|
25
|
+
componentToRender = <ImageSrcControl />;
|
|
26
|
+
break;
|
|
27
|
+
case 'sizes':
|
|
28
|
+
componentToRender = <ImageSizeControl sizes={ sizes } />;
|
|
29
|
+
break;
|
|
30
|
+
case 'all':
|
|
31
|
+
default:
|
|
32
|
+
componentToRender = (
|
|
29
33
|
<Stack gap={ 1.5 }>
|
|
30
|
-
{
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
<ControlFormLabel>{ resolutionLabel }</ControlFormLabel>
|
|
41
|
-
</Grid>
|
|
42
|
-
<Grid item xs={ 6 } sx={ { overflow: 'hidden' } }>
|
|
43
|
-
<SelectControl options={ sizes } />
|
|
44
|
-
</Grid>
|
|
45
|
-
</Grid>
|
|
46
|
-
</PropKeyProvider>
|
|
47
|
-
) : null }
|
|
34
|
+
<ControlFormLabel>{ __( 'Image', 'elementor' ) }</ControlFormLabel>
|
|
35
|
+
<ImageSrcControl />
|
|
36
|
+
<Grid container gap={ 1.5 } alignItems="center" flexWrap="nowrap">
|
|
37
|
+
<Grid item xs={ 6 }>
|
|
38
|
+
<ControlFormLabel>{ __( 'Resolution', 'elementor' ) }</ControlFormLabel>
|
|
39
|
+
</Grid>
|
|
40
|
+
<Grid item xs={ 6 } sx={ { overflow: 'hidden' } }>
|
|
41
|
+
<ImageSizeControl sizes={ sizes } />
|
|
42
|
+
</Grid>
|
|
43
|
+
</Grid>
|
|
48
44
|
</Stack>
|
|
49
|
-
|
|
50
|
-
);
|
|
45
|
+
);
|
|
51
46
|
}
|
|
52
|
-
|
|
47
|
+
|
|
48
|
+
return <PropProvider { ...propContext }>{ componentToRender }</PropProvider>;
|
|
49
|
+
} );
|
|
50
|
+
|
|
51
|
+
const ImageSrcControl = () => {
|
|
52
|
+
const { data: allowSvgUpload } = useUnfilteredFilesUpload();
|
|
53
|
+
const mediaTypes: MediaType[] = allowSvgUpload ? [ 'image', 'svg' ] : [ 'image' ];
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<PropKeyProvider bind={ 'src' }>
|
|
57
|
+
<ImageMediaControl mediaTypes={ mediaTypes } />
|
|
58
|
+
</PropKeyProvider>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const ImageSizeControl = ( { sizes }: { sizes: ImageControlProps[ 'sizes' ] } ) => {
|
|
63
|
+
return (
|
|
64
|
+
<PropKeyProvider bind={ 'size' }>
|
|
65
|
+
<SelectControl options={ sizes } />
|
|
66
|
+
</PropKeyProvider>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { type ChangeEvent, useMemo, useState } from 'react';
|
|
3
3
|
import { keyValuePropTypeUtil } from '@elementor/editor-props';
|
|
4
|
-
import { FormHelperText, FormLabel, Grid,
|
|
4
|
+
import { FormHelperText, FormLabel, Grid, TextField } from '@elementor/ui';
|
|
5
5
|
import { __ } from '@wordpress/i18n';
|
|
6
6
|
|
|
7
7
|
import { useBoundProp } from '../bound-prop-context';
|
|
@@ -13,7 +13,6 @@ type FieldType = 'key' | 'value';
|
|
|
13
13
|
type KeyValueControlProps = {
|
|
14
14
|
keyName?: string;
|
|
15
15
|
valueName?: string;
|
|
16
|
-
sx?: SxProps< Theme >;
|
|
17
16
|
regexKey?: string;
|
|
18
17
|
regexValue?: string;
|
|
19
18
|
validationErrorMessage?: string;
|
|
@@ -23,12 +22,15 @@ export const KeyValueControl = createControl( ( props: KeyValueControlProps = {}
|
|
|
23
22
|
const { value, setValue } = useBoundProp( keyValuePropTypeUtil );
|
|
24
23
|
const [ keyError, setKeyError ] = useState< string | null >( null );
|
|
25
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
|
+
|
|
26
31
|
const keyLabel = props.keyName || __( 'Key', 'elementor' );
|
|
27
32
|
const valueLabel = props.valueName || __( 'Value', 'elementor' );
|
|
28
33
|
|
|
29
|
-
const keyValue = value?.key?.value || '';
|
|
30
|
-
const valueValue = value?.value?.value || '';
|
|
31
|
-
|
|
32
34
|
const [ keyRegex, valueRegex, errMsg ] = useMemo< [ RegExp | undefined, RegExp | undefined, string ] >(
|
|
33
35
|
() => [
|
|
34
36
|
props.regexKey ? new RegExp( props.regexKey ) : undefined,
|
|
@@ -38,28 +40,44 @@ export const KeyValueControl = createControl( ( props: KeyValueControlProps = {}
|
|
|
38
40
|
[ props.regexKey, props.regexValue, props.validationErrorMessage ]
|
|
39
41
|
);
|
|
40
42
|
|
|
41
|
-
const validate = ( newValue: string,
|
|
42
|
-
if (
|
|
43
|
+
const validate = ( newValue: string, fieldType: string ): boolean => {
|
|
44
|
+
if ( fieldType === 'key' && keyRegex ) {
|
|
43
45
|
const isValid = keyRegex.test( newValue );
|
|
44
46
|
setKeyError( isValid ? null : errMsg );
|
|
45
|
-
|
|
47
|
+
return isValid;
|
|
48
|
+
} else if ( fieldType === 'value' && valueRegex ) {
|
|
46
49
|
const isValid = valueRegex.test( newValue );
|
|
47
50
|
setValueError( isValid ? null : errMsg );
|
|
51
|
+
return isValid;
|
|
48
52
|
}
|
|
53
|
+
return true;
|
|
49
54
|
};
|
|
50
55
|
|
|
51
56
|
const handleChange = ( event: ChangeEvent< HTMLInputElement >, fieldType: FieldType ) => {
|
|
52
57
|
const newValue = event.target.value;
|
|
53
58
|
|
|
54
|
-
|
|
59
|
+
setSessionState( ( prev ) => ( {
|
|
60
|
+
...prev,
|
|
61
|
+
[ fieldType ]: newValue,
|
|
62
|
+
} ) );
|
|
55
63
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
+
}
|
|
63
81
|
};
|
|
64
82
|
|
|
65
83
|
const isKeyInvalid = keyError !== null;
|
|
@@ -67,14 +85,16 @@ export const KeyValueControl = createControl( ( props: KeyValueControlProps = {}
|
|
|
67
85
|
|
|
68
86
|
return (
|
|
69
87
|
<ControlActions>
|
|
70
|
-
<Grid container gap={ 1.5 }
|
|
88
|
+
<Grid container gap={ 1.5 }>
|
|
71
89
|
<Grid item xs={ 12 }>
|
|
72
90
|
<FormLabel size="tiny">{ keyLabel }</FormLabel>
|
|
73
91
|
<TextField
|
|
92
|
+
// eslint-disable-next-line jsx-a11y/no-autofocus
|
|
93
|
+
autoFocus
|
|
74
94
|
sx={ { pt: 1 } }
|
|
75
95
|
size="tiny"
|
|
76
96
|
fullWidth
|
|
77
|
-
value={
|
|
97
|
+
value={ sessionState.key }
|
|
78
98
|
onChange={ ( e: ChangeEvent< HTMLInputElement > ) => handleChange( e, 'key' ) }
|
|
79
99
|
error={ isKeyInvalid }
|
|
80
100
|
/>
|
|
@@ -86,7 +106,7 @@ export const KeyValueControl = createControl( ( props: KeyValueControlProps = {}
|
|
|
86
106
|
sx={ { pt: 1 } }
|
|
87
107
|
size="tiny"
|
|
88
108
|
fullWidth
|
|
89
|
-
value={
|
|
109
|
+
value={ sessionState.value }
|
|
90
110
|
onChange={ ( e: ChangeEvent< HTMLInputElement > ) => handleChange( e, 'value' ) }
|
|
91
111
|
disabled={ isKeyInvalid }
|
|
92
112
|
error={ isValueInvalid }
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
urlPropTypeUtil,
|
|
11
11
|
} from '@elementor/editor-props';
|
|
12
12
|
import { InfoTipCard } from '@elementor/editor-ui';
|
|
13
|
+
import { isExperimentActive } from '@elementor/editor-v1-adapters';
|
|
13
14
|
import { type HttpResponse, httpService } from '@elementor/http-client';
|
|
14
15
|
import { AlertTriangleIcon, MinusIcon, PlusIcon } from '@elementor/icons';
|
|
15
16
|
import { useSessionStorage } from '@elementor/session';
|
|
@@ -29,6 +30,7 @@ import { ControlFormLabel } from '../components/control-form-label';
|
|
|
29
30
|
import ControlActions from '../control-actions/control-actions';
|
|
30
31
|
import { createControl } from '../create-control';
|
|
31
32
|
import { type ControlProps } from '../utils/types';
|
|
33
|
+
import { SwitchControl } from './switch-control';
|
|
32
34
|
|
|
33
35
|
type Props = ControlProps< {
|
|
34
36
|
queryOptions: {
|
|
@@ -38,6 +40,7 @@ type Props = ControlProps< {
|
|
|
38
40
|
allowCustomValues?: boolean;
|
|
39
41
|
minInputLength?: number;
|
|
40
42
|
placeholder?: string;
|
|
43
|
+
label?: string;
|
|
41
44
|
} >;
|
|
42
45
|
|
|
43
46
|
type LinkSessionValue = {
|
|
@@ -66,6 +69,7 @@ export const LinkControl = createControl( ( props: Props ) => {
|
|
|
66
69
|
placeholder,
|
|
67
70
|
minInputLength = 2,
|
|
68
71
|
context: { elementId },
|
|
72
|
+
label = __( 'Link', 'elementor' ),
|
|
69
73
|
} = props || {};
|
|
70
74
|
|
|
71
75
|
const [ linkInLinkRestriction, setLinkInLinkRestriction ] = useState( getLinkInLinkRestriction( elementId ) );
|
|
@@ -163,7 +167,7 @@ export const LinkControl = createControl( ( props: Props ) => {
|
|
|
163
167
|
marginInlineEnd: -0.75,
|
|
164
168
|
} }
|
|
165
169
|
>
|
|
166
|
-
<ControlFormLabel>{
|
|
170
|
+
<ControlFormLabel>{ label }</ControlFormLabel>
|
|
167
171
|
<ConditionalInfoTip isVisible={ ! isActive } linkInLinkRestriction={ linkInLinkRestriction }>
|
|
168
172
|
<ToggleIconControl
|
|
169
173
|
disabled={ shouldDisableAddingLink }
|
|
@@ -189,7 +193,14 @@ export const LinkControl = createControl( ( props: Props ) => {
|
|
|
189
193
|
</ControlActions>
|
|
190
194
|
</PropKeyProvider>
|
|
191
195
|
<PropKeyProvider bind={ 'isTargetBlank' }>
|
|
192
|
-
<
|
|
196
|
+
<Grid container alignItems="center" flexWrap="nowrap" justifyContent="space-between">
|
|
197
|
+
<Grid item>
|
|
198
|
+
<ControlFormLabel>{ __( 'Open in a new tab', 'elementor' ) }</ControlFormLabel>
|
|
199
|
+
</Grid>
|
|
200
|
+
<Grid item sx={ { marginInlineEnd: -1 } }>
|
|
201
|
+
<SwitchControlComponent disabled={ propContext.disabled || ! value } />
|
|
202
|
+
</Grid>
|
|
203
|
+
</Grid>
|
|
193
204
|
</PropKeyProvider>
|
|
194
205
|
</Stack>
|
|
195
206
|
</Collapse>
|
|
@@ -213,31 +224,27 @@ const ToggleIconControl = ( { disabled, active, onIconClick, label }: ToggleIcon
|
|
|
213
224
|
);
|
|
214
225
|
};
|
|
215
226
|
|
|
216
|
-
|
|
217
|
-
const
|
|
218
|
-
const
|
|
227
|
+
const SwitchControlComponent = ( { disabled }: { disabled: boolean } ) => {
|
|
228
|
+
const { value, setValue } = useBoundProp( booleanPropTypeUtil );
|
|
229
|
+
const isVersion331Active = isExperimentActive( 'e_v_3_31' );
|
|
230
|
+
|
|
231
|
+
if ( isVersion331Active ) {
|
|
232
|
+
return <SwitchControl />;
|
|
233
|
+
}
|
|
219
234
|
|
|
220
235
|
const onClick = () => {
|
|
221
236
|
setValue( ! value );
|
|
222
237
|
};
|
|
223
238
|
|
|
224
|
-
const inputProps = disabled
|
|
225
|
-
? {
|
|
226
|
-
style: {
|
|
227
|
-
opacity: 0,
|
|
228
|
-
},
|
|
229
|
-
}
|
|
230
|
-
: {};
|
|
231
|
-
|
|
232
239
|
return (
|
|
233
|
-
<
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
240
|
+
<Switch
|
|
241
|
+
checked={ value ?? false }
|
|
242
|
+
onClick={ onClick }
|
|
243
|
+
disabled={ disabled }
|
|
244
|
+
inputProps={ {
|
|
245
|
+
...( disabled ? { style: { opacity: 0 } } : {} ),
|
|
246
|
+
} }
|
|
247
|
+
/>
|
|
241
248
|
);
|
|
242
249
|
};
|
|
243
250
|
|
|
@@ -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,
|
|
@@ -68,7 +68,7 @@ export const LinkedDimensionsControl = createControl(
|
|
|
68
68
|
propType={ propType }
|
|
69
69
|
value={ dimensionsValue }
|
|
70
70
|
setValue={ setDimensionsValue }
|
|
71
|
-
|
|
71
|
+
isDisabled={ () => disabled }
|
|
72
72
|
>
|
|
73
73
|
<Stack direction="row" gap={ 2 } flexWrap="nowrap">
|
|
74
74
|
{ isUsingNestedProps ? (
|
|
@@ -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 ) ) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { useMemo } from 'react';
|
|
3
3
|
import { createArrayPropUtils, type PropKey } from '@elementor/editor-props';
|
|
4
|
-
import {
|
|
4
|
+
import { Box } from '@elementor/ui';
|
|
5
5
|
|
|
6
|
+
/* eslint-disable */
|
|
6
7
|
import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-context';
|
|
7
8
|
import { PopoverContent } from '../components/popover-content';
|
|
8
9
|
import { PopoverGridContainer } from '../components/popover-grid-container';
|
|
@@ -16,13 +17,25 @@ import {
|
|
|
16
17
|
|
|
17
18
|
type RepeatableControlProps = {
|
|
18
19
|
label: string;
|
|
20
|
+
repeaterLabel: string;
|
|
19
21
|
childControlConfig: ChildControlConfig;
|
|
20
22
|
showDuplicate?: boolean;
|
|
21
23
|
showToggle?: boolean;
|
|
24
|
+
initialValues?: object;
|
|
25
|
+
patternLabel?: string;
|
|
26
|
+
placeholder?: string;
|
|
22
27
|
};
|
|
23
28
|
|
|
24
29
|
export const RepeatableControl = createControl(
|
|
25
|
-
( {
|
|
30
|
+
( {
|
|
31
|
+
repeaterLabel,
|
|
32
|
+
childControlConfig,
|
|
33
|
+
showDuplicate,
|
|
34
|
+
showToggle,
|
|
35
|
+
initialValues,
|
|
36
|
+
patternLabel,
|
|
37
|
+
placeholder,
|
|
38
|
+
}: RepeatableControlProps ) => {
|
|
26
39
|
const { propTypeUtil: childPropTypeUtil } = childControlConfig;
|
|
27
40
|
|
|
28
41
|
if ( ! childPropTypeUtil ) {
|
|
@@ -34,21 +47,32 @@ export const RepeatableControl = createControl(
|
|
|
34
47
|
[ childPropTypeUtil.key, childPropTypeUtil.schema ]
|
|
35
48
|
);
|
|
36
49
|
|
|
50
|
+
const contextValue = useMemo(
|
|
51
|
+
() => ({
|
|
52
|
+
...childControlConfig,
|
|
53
|
+
placeholder: placeholder || '',
|
|
54
|
+
patternLabel: patternLabel || '',
|
|
55
|
+
}),
|
|
56
|
+
[ childControlConfig, placeholder, patternLabel ]
|
|
57
|
+
);
|
|
58
|
+
|
|
37
59
|
const { propType, value, setValue } = useBoundProp( childArrayPropTypeUtil );
|
|
38
60
|
|
|
61
|
+
|
|
39
62
|
return (
|
|
40
63
|
<PropProvider propType={ propType } value={ value } setValue={ setValue }>
|
|
41
|
-
<RepeatableControlContext.Provider value={
|
|
64
|
+
<RepeatableControlContext.Provider value={ contextValue }>
|
|
42
65
|
<Repeater
|
|
43
66
|
openOnAdd
|
|
44
67
|
values={ value ?? [] }
|
|
45
68
|
setValues={ setValue }
|
|
46
|
-
label={
|
|
69
|
+
label={ repeaterLabel }
|
|
70
|
+
isSortable={ false }
|
|
47
71
|
itemSettings={ {
|
|
48
72
|
Icon: ItemIcon,
|
|
49
73
|
Label: ItemLabel,
|
|
50
74
|
Content: ItemContent,
|
|
51
|
-
initialValues: childPropTypeUtil.create( null ),
|
|
75
|
+
initialValues: childPropTypeUtil.create( initialValues || null ),
|
|
52
76
|
} }
|
|
53
77
|
showDuplicate={ showDuplicate }
|
|
54
78
|
showToggle={ showToggle }
|
|
@@ -82,8 +106,74 @@ const Content = () => {
|
|
|
82
106
|
);
|
|
83
107
|
};
|
|
84
108
|
|
|
85
|
-
const
|
|
86
|
-
|
|
109
|
+
const interpolate = ( template: string, data: object ) => {
|
|
110
|
+
if ( ! data ) {
|
|
111
|
+
return template;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return new Function( ...Object.keys( data ), `return \`${ template }\`;` )( ...Object.values( data ) );
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const getNestedValue = ( obj: any, path: string ) => {
|
|
118
|
+
return path.split( '.' ).reduce( ( current, key ) => current?.[key], obj);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const isEmptyValue = (val: unknown) => {
|
|
122
|
+
if ( typeof val === 'string' ) {
|
|
123
|
+
return val.trim() === '';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if ( Number.isNaN( val ) ) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if ( Array.isArray( val ) ) {
|
|
131
|
+
return val.length === 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if ( typeof val === 'object' && val !== null && Object.keys( val ).length === 0 ) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return false;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const shouldShowPlaceholder = ( pattern: string, data: unknown ): boolean => {
|
|
142
|
+
const propertyPaths = getAllProperties( pattern );
|
|
87
143
|
|
|
88
|
-
|
|
144
|
+
const values = propertyPaths.map( path => getNestedValue( data, path ) );
|
|
145
|
+
|
|
146
|
+
if ( values.length === 0 ) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if ( values.some( ( value ) => value === null || value === undefined ) ) {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if ( values.every( isEmptyValue ) ) {
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return false;
|
|
89
159
|
};
|
|
160
|
+
|
|
161
|
+
const ItemLabel = ( { value }: { value: any } ) => {
|
|
162
|
+
const { placeholder, patternLabel } = useRepeatableControlContext();
|
|
163
|
+
|
|
164
|
+
const label = shouldShowPlaceholder( patternLabel, value ) ? placeholder : interpolate( patternLabel, value );
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<Box component="span" color="text.tertiary">
|
|
168
|
+
{ label }
|
|
169
|
+
</Box>
|
|
170
|
+
);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const getAllProperties = ( pattern: string ) => {
|
|
174
|
+
const properties = pattern.match(/\$\{([^}]+)\}/g)?.map(match =>
|
|
175
|
+
match.slice(2, -1)
|
|
176
|
+
) || [];
|
|
177
|
+
|
|
178
|
+
return properties;
|
|
179
|
+
};
|
|
@@ -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 }
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { type
|
|
2
|
+
import { type RefObject, useEffect, useState } from 'react';
|
|
3
3
|
import { sizePropTypeUtil, type SizePropValue } from '@elementor/editor-props';
|
|
4
4
|
import { useActiveBreakpoint } from '@elementor/editor-responsive';
|
|
5
5
|
import { usePopupState } from '@elementor/ui';
|
|
@@ -23,7 +23,7 @@ type SizeControlProps = {
|
|
|
23
23
|
units?: Unit[];
|
|
24
24
|
extendedOptions?: ExtendedOption[];
|
|
25
25
|
disableCustom?: boolean;
|
|
26
|
-
anchorRef?:
|
|
26
|
+
anchorRef?: RefObject< HTMLDivElement | null >;
|
|
27
27
|
defaultUnit?: Unit;
|
|
28
28
|
};
|
|
29
29
|
|
|
@@ -149,7 +149,7 @@ export const SizeControl = createControl( ( props: SizeControlProps ) => {
|
|
|
149
149
|
{ anchorRef?.current && (
|
|
150
150
|
<TextFieldPopover
|
|
151
151
|
popupState={ popupState }
|
|
152
|
-
anchorRef={ anchorRef
|
|
152
|
+
anchorRef={ anchorRef }
|
|
153
153
|
restoreValue={ restoreValue }
|
|
154
154
|
value={ controlSize as string }
|
|
155
155
|
onChange={ handleSizeChange }
|