@elementor/editor-controls 0.21.0 → 0.25.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@elementor/editor-controls",
3
3
  "description": "This package contains the controls model and utils for the Elementor editor",
4
- "version": "0.21.0",
4
+ "version": "0.25.0",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -41,9 +41,9 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@elementor/editor-current-user": "0.3.0",
44
- "@elementor/editor-elements": "0.7.0",
45
- "@elementor/editor-props": "0.11.1",
46
- "@elementor/editor-ui": "0.5.1",
44
+ "@elementor/editor-elements": "0.8.1",
45
+ "@elementor/editor-props": "0.12.0",
46
+ "@elementor/editor-ui": "0.7.1",
47
47
  "@elementor/env": "0.3.5",
48
48
  "@elementor/http": "0.1.4",
49
49
  "@elementor/icons": "1.37.0",
@@ -14,6 +14,7 @@ type PropContext< T extends PropValue, P extends PropType > = {
14
14
  setValue: SetValue< T >;
15
15
  value: T | null;
16
16
  propType: P;
17
+ placeholder?: T;
17
18
  };
18
19
 
19
20
  const PropContext = createContext< PropContext< PropValue, PropType > | null >( null );
@@ -27,6 +28,7 @@ export const PropProvider = < T extends PropValue, P extends PropType >( {
27
28
  value,
28
29
  setValue,
29
30
  propType,
31
+ placeholder,
30
32
  }: PropProviderProps< T, P > ) => {
31
33
  return (
32
34
  <PropContext.Provider
@@ -34,6 +36,7 @@ export const PropProvider = < T extends PropValue, P extends PropType >( {
34
36
  value,
35
37
  propType,
36
38
  setValue: setValue as SetValue< PropValue >,
39
+ placeholder,
37
40
  } }
38
41
  >
39
42
  { children }
@@ -19,6 +19,7 @@ export type PropKeyContextValue< T, P > = {
19
19
  setValue: SetValue< T >;
20
20
  value: T;
21
21
  propType: P;
22
+ placeholder?: T;
22
23
  path: PropKey[];
23
24
  };
24
25
 
@@ -60,12 +61,13 @@ const ObjectPropKeyProvider = ( { children, bind }: PropKeyProviderProps ) => {
60
61
  };
61
62
 
62
63
  const value = context.value?.[ bind ];
64
+ const placeholder = context.placeholder?.[ bind ];
63
65
 
64
66
  const propType = context.propType.shape[ bind ];
65
67
 
66
68
  return (
67
69
  <PropKeyContext.Provider
68
- value={ { ...context, value, setValue, bind, propType, path: [ ...( path ?? [] ), bind ] } }
70
+ value={ { ...context, value, setValue, placeholder, bind, propType, path: [ ...( path ?? [] ), bind ] } }
69
71
  >
70
72
  { children }
71
73
  </PropKeyContext.Provider>
@@ -1,3 +1,4 @@
1
+ import { useState } from 'react';
1
2
  import {
2
3
  type CreateOptions,
3
4
  type PropKey,
@@ -15,7 +16,9 @@ type UseBoundProp< TValue extends PropValue > = {
15
16
  setValue: SetValue< TValue | null >;
16
17
  value: TValue;
17
18
  propType: PropType;
19
+ placeholder?: TValue;
18
20
  path: PropKey[];
21
+ restoreValue: () => void;
19
22
  };
20
23
 
21
24
  export function useBoundProp< T extends PropValue = PropValue >(): PropKeyContextValue< T, PropType >;
@@ -29,12 +32,18 @@ export function useBoundProp< TKey extends string, TValue extends PropValue >(
29
32
  ) {
30
33
  const propKeyContext = usePropKeyContext();
31
34
 
35
+ const { isValid, validate, restoreValue } = useValidation( propKeyContext.propType );
36
+
32
37
  // allow using the hook without a propTypeUtil, with no modifications or validations.
33
38
  if ( ! propTypeUtil ) {
34
39
  return propKeyContext;
35
40
  }
36
41
 
37
42
  function setValue( value: TValue | null, options: CreateOptions, meta: { bind?: PropKey } ) {
43
+ if ( ! validate( value ) ) {
44
+ return;
45
+ }
46
+
38
47
  if ( value === null ) {
39
48
  return propKeyContext?.setValue( null, options, meta );
40
49
  }
@@ -45,15 +54,45 @@ export function useBoundProp< TKey extends string, TValue extends PropValue >(
45
54
  const propType = resolveUnionPropType( propKeyContext.propType, propTypeUtil.key );
46
55
 
47
56
  const value = propTypeUtil.extract( propKeyContext.value ?? propType.default ?? null );
57
+ const placeholder = propTypeUtil.extract( propKeyContext.placeholder ?? null );
48
58
 
49
59
  return {
50
60
  ...propKeyContext,
51
- setValue,
52
- value,
53
61
  propType,
62
+ setValue,
63
+ value: isValid ? value : null,
64
+ restoreValue,
65
+ placeholder,
54
66
  };
55
67
  }
56
68
 
69
+ const useValidation = ( propType: PropType ) => {
70
+ const [ isValid, setIsValid ] = useState( true );
71
+
72
+ // If the value does not pass the prop type validation, set the isValid state to false.
73
+ // This will prevent the value from being set in the model, and its fallback will be used instead.
74
+ const validate = ( value: PropValue | null ) => {
75
+ let valid = true;
76
+
77
+ if ( propType.settings.required && value === null ) {
78
+ valid = false;
79
+ }
80
+
81
+ setIsValid( valid );
82
+
83
+ return valid;
84
+ };
85
+
86
+ const restoreValue = () => setIsValid( true );
87
+
88
+ return {
89
+ isValid,
90
+ setIsValid,
91
+ validate,
92
+ restoreValue,
93
+ };
94
+ };
95
+
57
96
  // utils
58
97
  const resolveUnionPropType = ( propType: PropType, key: string ): PropType => {
59
98
  let resolvedPropType = propType;
@@ -48,6 +48,8 @@ type RepeaterProps< T > = {
48
48
  };
49
49
  };
50
50
 
51
+ const EMPTY_OPEN_ITEM = -1;
52
+
51
53
  export const Repeater = < T, >( {
52
54
  label,
53
55
  itemSettings,
@@ -56,7 +58,7 @@ export const Repeater = < T, >( {
56
58
  values: repeaterValues = [],
57
59
  setValues: setRepeaterValues,
58
60
  }: RepeaterProps< Item< T > > ) => {
59
- const [ openItem, setOpenItem ] = useState( -1 );
61
+ const [ openItem, setOpenItem ] = useState( EMPTY_OPEN_ITEM );
60
62
 
61
63
  const [ items, setItems ] = useSyncExternalState( {
62
64
  external: repeaterValues,
@@ -166,7 +168,6 @@ export const Repeater = < T, >( {
166
168
  return (
167
169
  <SortableItem id={ key } key={ `sortable-${ key }` }>
168
170
  <RepeaterItem
169
- bind={ String( index ) }
170
171
  disabled={ value?.disabled }
171
172
  label={ <itemSettings.Label value={ value } /> }
172
173
  startIcon={ <itemSettings.Icon value={ value } /> }
@@ -174,6 +175,7 @@ export const Repeater = < T, >( {
174
175
  duplicateItem={ () => duplicateRepeaterItem( index ) }
175
176
  toggleDisableItem={ () => toggleDisableRepeaterItem( index ) }
176
177
  openOnMount={ openOnAdd && openItem === key }
178
+ onOpen={ () => setOpenItem( EMPTY_OPEN_ITEM ) }
177
179
  >
178
180
  { ( props ) => (
179
181
  <itemSettings.Content { ...props } value={ value } bind={ String( index ) } />
@@ -190,7 +192,6 @@ export const Repeater = < T, >( {
190
192
 
191
193
  type RepeaterItemProps = {
192
194
  label: React.ReactNode;
193
- bind: string;
194
195
  disabled?: boolean;
195
196
  startIcon: UnstableTagProps[ 'startIcon' ];
196
197
  removeItem: () => void;
@@ -198,6 +199,7 @@ type RepeaterItemProps = {
198
199
  toggleDisableItem: () => void;
199
200
  children: ( { anchorEl }: { anchorEl: AnchorEl } ) => React.ReactNode;
200
201
  openOnMount: boolean;
202
+ onOpen: () => void;
201
203
  };
202
204
 
203
205
  const RepeaterItem = ( {
@@ -209,9 +211,10 @@ const RepeaterItem = ( {
209
211
  duplicateItem,
210
212
  toggleDisableItem,
211
213
  openOnMount,
214
+ onOpen,
212
215
  }: RepeaterItemProps ) => {
213
216
  const [ anchorEl, setAnchorEl ] = useState< AnchorEl >( null );
214
- const { popoverState, popoverProps, ref, setRef } = usePopover( openOnMount );
217
+ const { popoverState, popoverProps, ref, setRef } = usePopover( openOnMount, onOpen );
215
218
 
216
219
  const duplicateLabel = __( 'Duplicate', 'elementor' );
217
220
  const toggleLabel = disabled ? __( 'Show', 'elementor' ) : __( 'Hide', 'elementor' );
@@ -247,7 +250,6 @@ const RepeaterItem = ( {
247
250
  </Tooltip>
248
251
  </>
249
252
  }
250
- sx={ { backgroundColor: 'background.paper' } }
251
253
  />
252
254
  <Popover
253
255
  disablePortal
@@ -267,7 +269,7 @@ const RepeaterItem = ( {
267
269
  );
268
270
  };
269
271
 
270
- const usePopover = ( openOnMount: boolean ) => {
272
+ const usePopover = ( openOnMount: boolean, onOpen: () => void ) => {
271
273
  const [ ref, setRef ] = useState< HTMLElement | null >( null );
272
274
 
273
275
  const popoverState = usePopupState( { variant: 'popover' } );
@@ -277,6 +279,7 @@ const usePopover = ( openOnMount: boolean ) => {
277
279
  useEffect( () => {
278
280
  if ( openOnMount && ref ) {
279
281
  popoverState.open( ref );
282
+ onOpen?.();
280
283
  }
281
284
  // eslint-disable-next-line react-compiler/react-compiler
282
285
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -34,16 +34,11 @@ export const SortableItem = ( { id, children }: SortableItemProps ): React.React
34
34
  triggerProps,
35
35
  itemStyle,
36
36
  triggerStyle,
37
- isDragOverlay,
38
37
  showDropIndication,
39
38
  dropIndicationStyle,
40
39
  }: UnstableSortableItemRenderProps ) => {
41
40
  return (
42
- <StyledListItem
43
- { ...itemProps }
44
- style={ itemStyle }
45
- sx={ { backgroundColor: isDragOverlay ? 'background.paper' : undefined } }
46
- >
41
+ <StyledListItem { ...itemProps } style={ itemStyle }>
47
42
  <SortableTrigger { ...triggerProps } style={ triggerStyle } />
48
43
  { children }
49
44
  { showDropIndication && <StyledDivider style={ dropIndicationStyle } /> }
@@ -72,6 +67,11 @@ const StyledListItem = styled( ListItem )`
72
67
  transform: translate( -75%, -50% );
73
68
  }
74
69
 
70
+ &[aria-describedby=''] > .MuiTag-root {
71
+ background-color: ${ ( { theme } ) => theme.palette.background.paper };
72
+ box-shadow: ${ ( { theme } ) => theme.shadows[ 3 ] };
73
+ }
74
+
75
75
  &:hover {
76
76
  & .class-item-sortable-trigger {
77
77
  visibility: visible;
@@ -9,25 +9,41 @@ type TextFieldInnerSelectionProps = {
9
9
  type: string;
10
10
  value: PropValue;
11
11
  onChange: ( event: React.ChangeEvent< HTMLInputElement > ) => void;
12
+ onBlur?: ( event: React.FocusEvent< HTMLInputElement > ) => void;
13
+ onKeyDown?: ( event: React.KeyboardEvent< HTMLInputElement > ) => void;
12
14
  endAdornment: React.ReactNode;
13
15
  startAdornment?: React.ReactNode;
14
16
  };
15
17
 
16
18
  export const TextFieldInnerSelection = forwardRef(
17
- ( { placeholder, type, value, onChange, endAdornment, startAdornment }: TextFieldInnerSelectionProps, ref ) => {
19
+ (
20
+ {
21
+ placeholder,
22
+ type,
23
+ value,
24
+ onChange,
25
+ onBlur,
26
+ onKeyDown,
27
+ endAdornment,
28
+ startAdornment,
29
+ }: TextFieldInnerSelectionProps,
30
+ ref
31
+ ) => {
18
32
  return (
19
33
  <TextField
34
+ ref={ ref }
20
35
  size="tiny"
21
36
  fullWidth
22
37
  type={ type }
23
38
  value={ value }
24
39
  onChange={ onChange }
40
+ onKeyDown={ onKeyDown }
41
+ onBlur={ onBlur }
25
42
  placeholder={ placeholder }
26
43
  InputProps={ {
27
44
  endAdornment,
28
45
  startAdornment,
29
46
  } }
30
- ref={ ref }
31
47
  />
32
48
  );
33
49
  }
@@ -58,11 +74,11 @@ export const SelectionEndAdornment = < T extends string >( {
58
74
  <InputAdornment position="end">
59
75
  <Button
60
76
  size="small"
61
- color="inherit"
62
- sx={ { font: 'inherit', minWidth: 'initial' } }
77
+ color="secondary"
78
+ sx={ { font: 'inherit', minWidth: 'initial', textTransform: 'uppercase' } }
63
79
  { ...bindTrigger( popupState ) }
64
80
  >
65
- { value.toUpperCase() }
81
+ { value }
66
82
  </Button>
67
83
 
68
84
  <Menu MenuListProps={ { dense: true } } { ...bindMenu( popupState ) }>
@@ -271,7 +271,7 @@ const useImage = ( image: BackgroundImageOverlay ) => {
271
271
  const { data: attachment } = useWpMediaAttachment( imageSrc.id?.value || null );
272
272
 
273
273
  if ( imageSrc.id ) {
274
- const imageFileTypeExtension = attachment?.subtype ? `.${ attachment.subtype }` : '';
274
+ const imageFileTypeExtension = getFileExtensionFromFilename( attachment?.filename );
275
275
  imageTitle = `${ attachment?.title }${ imageFileTypeExtension }` || null;
276
276
  imageUrl = attachment?.url || null;
277
277
  } else if ( imageSrc.url ) {
@@ -282,6 +282,17 @@ const useImage = ( image: BackgroundImageOverlay ) => {
282
282
  return { imageTitle, imageUrl };
283
283
  };
284
284
 
285
+ const getFileExtensionFromFilename = ( filename?: string ) => {
286
+ if ( ! filename ) {
287
+ return '';
288
+ }
289
+
290
+ // get the substring after the last . in the filename
291
+ const extension = filename.substring( filename.lastIndexOf( '.' ) + 1 );
292
+
293
+ return `.${ extension }`;
294
+ };
295
+
285
296
  const getGradientValue = ( value: BackgroundOverlayItemPropValue ) => {
286
297
  const gradient = value.value;
287
298
 
@@ -86,6 +86,8 @@ export const FontFamilyControl = createControl( ( { fontFamilies }: FontFamilyCo
86
86
 
87
87
  <Box px={ 1.5 } pb={ 1 }>
88
88
  <TextField
89
+ // eslint-disable-next-line jsx-a11y/no-autofocus
90
+ autoFocus
89
91
  fullWidth
90
92
  size={ SIZE }
91
93
  value={ searchValue }
@@ -280,6 +282,8 @@ const StyledMenuList = styled( MenuList )( ( { theme } ) => ( {
280
282
  top: 0,
281
283
  left: 0,
282
284
  width: '100%',
285
+ display: 'flex',
286
+ alignItems: 'center',
283
287
  },
284
288
  '& > [role="option"]': {
285
289
  ...theme.typography.caption,
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
- import { useMemo, useState } from 'react';
3
- import { getAncestorWithAnchorTag, getDescendantWithAnchorTag } from '@elementor/editor-elements';
2
+ import { type PropsWithChildren, useMemo, useState } from 'react';
3
+ import { getLinkInLinkRestriction, type LinkInLinkRestriction, selectElement } from '@elementor/editor-elements';
4
4
  import {
5
5
  booleanPropTypeUtil,
6
6
  linkPropTypeUtil,
@@ -9,10 +9,11 @@ import {
9
9
  stringPropTypeUtil,
10
10
  urlPropTypeUtil,
11
11
  } from '@elementor/editor-props';
12
+ import { InfoTipCard } from '@elementor/editor-ui';
12
13
  import { type HttpResponse, httpService } from '@elementor/http';
13
- import { MinusIcon, PlusIcon } from '@elementor/icons';
14
+ import { AlertTriangleIcon, MinusIcon, PlusIcon } from '@elementor/icons';
14
15
  import { useSessionStorage } from '@elementor/session';
15
- import { Collapse, Divider, Grid, IconButton, Stack, Switch } from '@elementor/ui';
16
+ import { Box, Collapse, Divider, Grid, IconButton, Infotip, Stack, Switch } from '@elementor/ui';
16
17
  import { debounce } from '@elementor/utils';
17
18
  import { __ } from '@wordpress/i18n';
18
19
 
@@ -49,11 +50,15 @@ type LinkSessionValue = {
49
50
  type Response = HttpResponse< { value: FlatOption[] | CategorizedOption[] } >;
50
51
 
51
52
  const SIZE = 'tiny';
53
+ const learnMoreButton = {
54
+ label: __( 'Learn More', 'elementor' ),
55
+ href: 'https://go.elementor.com/element-link-inside-link-infotip',
56
+ };
52
57
 
53
58
  export const LinkControl = createControl( ( props: Props ) => {
54
59
  const { value, path, setValue, ...propContext } = useBoundProp( linkPropTypeUtil );
55
60
  const [ linkSessionValue, setLinkSessionValue ] = useSessionStorage< LinkSessionValue >( path.join( '/' ) );
56
- const [ isEnabled, setIsEnabled ] = useState( !! value );
61
+ const [ isActive, setIsActive ] = useState( !! value );
57
62
 
58
63
  const {
59
64
  allowCustomValues,
@@ -63,20 +68,22 @@ export const LinkControl = createControl( ( props: Props ) => {
63
68
  context: { elementId },
64
69
  } = props || {};
65
70
 
71
+ const [ linkInLinkRestriction, setLinkInLinkRestriction ] = useState( getLinkInLinkRestriction( elementId ) );
66
72
  const [ options, setOptions ] = useState< FlatOption[] | CategorizedOption[] >(
67
73
  generateFirstLoadedOption( value )
68
74
  );
75
+ const shouldDisableAddingLink = ! isActive && linkInLinkRestriction.shouldRestrict;
69
76
 
70
77
  const onEnabledChange = () => {
71
- const shouldRestrict = getAncestorWithAnchorTag( elementId ) || getDescendantWithAnchorTag( elementId );
78
+ setLinkInLinkRestriction( getLinkInLinkRestriction( elementId ) );
72
79
 
73
- if ( shouldRestrict && ! isEnabled ) {
80
+ if ( linkInLinkRestriction.shouldRestrict && ! isActive ) {
74
81
  return;
75
82
  }
76
83
 
77
- setIsEnabled( ( prevState ) => ! prevState );
78
- setValue( isEnabled ? null : linkSessionValue?.value ?? null );
79
- setLinkSessionValue( { value, meta: { isEnabled: ! isEnabled } } );
84
+ setIsActive( ( prevState ) => ! prevState );
85
+ setValue( isActive ? null : linkSessionValue?.value ?? null );
86
+ setLinkSessionValue( { value, meta: { isEnabled: ! isActive } } );
80
87
  };
81
88
 
82
89
  const onOptionChange = ( newValue: number | null ) => {
@@ -145,13 +152,16 @@ export const LinkControl = createControl( ( props: Props ) => {
145
152
  } }
146
153
  >
147
154
  <ControlFormLabel>{ __( 'Link', 'elementor' ) }</ControlFormLabel>
148
- <ToggleIconControl
149
- enabled={ isEnabled }
150
- onIconClick={ onEnabledChange }
151
- label={ __( 'Toggle link', 'elementor' ) }
152
- />
155
+ <ConditionalInfoTip isVisible={ ! isActive } linkInLinkRestriction={ linkInLinkRestriction }>
156
+ <ToggleIconControl
157
+ disabled={ shouldDisableAddingLink }
158
+ active={ isActive }
159
+ onIconClick={ onEnabledChange }
160
+ label={ __( 'Toggle link', 'elementor' ) }
161
+ />
162
+ </ConditionalInfoTip>
153
163
  </Stack>
154
- <Collapse in={ isEnabled } timeout="auto" unmountOnExit>
164
+ <Collapse in={ isActive } timeout="auto" unmountOnExit>
155
165
  <Stack gap={ 1.5 }>
156
166
  <PropKeyProvider bind={ 'destination' }>
157
167
  <ControlActions>
@@ -167,7 +177,7 @@ export const LinkControl = createControl( ( props: Props ) => {
167
177
  </ControlActions>
168
178
  </PropKeyProvider>
169
179
  <PropKeyProvider bind={ 'isTargetBlank' }>
170
- <SwitchControl />
180
+ <SwitchControl disabled={ ! value } />
171
181
  </PropKeyProvider>
172
182
  </Stack>
173
183
  </Collapse>
@@ -177,34 +187,43 @@ export const LinkControl = createControl( ( props: Props ) => {
177
187
  } );
178
188
 
179
189
  type ToggleIconControlProps = {
180
- enabled: boolean;
190
+ disabled: boolean;
191
+ active: boolean;
181
192
  onIconClick: () => void;
182
193
  label?: string;
183
194
  };
184
195
 
185
- const ToggleIconControl = ( { enabled, onIconClick, label }: ToggleIconControlProps ) => {
196
+ const ToggleIconControl = ( { disabled, active, onIconClick, label }: ToggleIconControlProps ) => {
186
197
  return (
187
- <IconButton size={ SIZE } onClick={ onIconClick } aria-label={ label }>
188
- { enabled ? <MinusIcon fontSize={ SIZE } /> : <PlusIcon fontSize={ SIZE } /> }
198
+ <IconButton size={ SIZE } onClick={ onIconClick } aria-label={ label } disabled={ disabled }>
199
+ { active ? <MinusIcon fontSize={ SIZE } /> : <PlusIcon fontSize={ SIZE } /> }
189
200
  </IconButton>
190
201
  );
191
202
  };
192
203
 
193
204
  // @TODO Should be refactored in ED-16323
194
- const SwitchControl = () => {
205
+ const SwitchControl = ( { disabled }: { disabled: boolean } ) => {
195
206
  const { value = false, setValue } = useBoundProp( booleanPropTypeUtil );
196
207
 
197
208
  const onClick = () => {
198
209
  setValue( ! value );
199
210
  };
200
211
 
212
+ const inputProps = disabled
213
+ ? {
214
+ style: {
215
+ opacity: 0,
216
+ },
217
+ }
218
+ : {};
219
+
201
220
  return (
202
221
  <Grid container alignItems="center" flexWrap="nowrap" justifyContent="space-between">
203
222
  <Grid item>
204
223
  <ControlFormLabel>{ __( 'Open in a new tab', 'elementor' ) }</ControlFormLabel>
205
224
  </Grid>
206
225
  <Grid item>
207
- <Switch checked={ value } onClick={ onClick } />
226
+ <Switch checked={ value } onClick={ onClick } disabled={ disabled } inputProps={ inputProps } />
208
227
  </Grid>
209
228
  </Grid>
210
229
  );
@@ -248,3 +267,56 @@ function generateFirstLoadedOption( unionValue: LinkPropValue[ 'value' ] | null
248
267
  ]
249
268
  : [];
250
269
  }
270
+
271
+ interface ConditionalInfoTipType extends PropsWithChildren {
272
+ linkInLinkRestriction: LinkInLinkRestriction;
273
+ isVisible: boolean;
274
+ }
275
+
276
+ const ConditionalInfoTip: React.FC< ConditionalInfoTipType > = ( { linkInLinkRestriction, isVisible, children } ) => {
277
+ const { shouldRestrict, reason, elementId } = linkInLinkRestriction;
278
+
279
+ const handleTakeMeClick = () => {
280
+ if ( elementId ) {
281
+ selectElement( elementId );
282
+ }
283
+ };
284
+
285
+ return shouldRestrict && isVisible ? (
286
+ <Infotip
287
+ placement="right"
288
+ content={
289
+ <InfoTipCard
290
+ content={ INFOTIP_CONTENT[ reason ] }
291
+ svgIcon={ <AlertTriangleIcon /> }
292
+ learnMoreButton={ learnMoreButton }
293
+ ctaButton={ {
294
+ label: __( 'Take me there', 'elementor' ),
295
+ onClick: handleTakeMeClick,
296
+ } }
297
+ />
298
+ }
299
+ >
300
+ <Box>{ children }</Box>
301
+ </Infotip>
302
+ ) : (
303
+ <>{ children }</>
304
+ );
305
+ };
306
+
307
+ const INFOTIP_CONTENT = {
308
+ descendant: (
309
+ <>
310
+ { __( 'To add a link to this container,', 'elementor' ) }
311
+ <br />
312
+ { __( 'first remove the link from the elements inside of it.', 'elementor' ) }
313
+ </>
314
+ ),
315
+ ancestor: (
316
+ <>
317
+ { __( 'To add a link to this element,', 'elementor' ) }
318
+ <br />
319
+ { __( 'first remove the link from its parent container.', 'elementor' ) }
320
+ </>
321
+ ),
322
+ };
@@ -9,6 +9,8 @@ import { createControl } from '../create-control';
9
9
  const isEmptyOrNaN = ( value?: string | number | null ) =>
10
10
  value === null || value === undefined || value === '' || Number.isNaN( Number( value ) );
11
11
 
12
+ const RESTRICTED_INPUT_KEYS = [ 'e', 'E', '+', '-' ];
13
+
12
14
  export const NumberControl = createControl(
13
15
  ( {
14
16
  placeholder,
@@ -49,6 +51,11 @@ export const NumberControl = createControl(
49
51
  onChange={ handleChange }
50
52
  placeholder={ placeholder }
51
53
  inputProps={ { step } }
54
+ onKeyDown={ ( event: KeyboardEvent ) => {
55
+ if ( RESTRICTED_INPUT_KEYS.includes( event.key ) ) {
56
+ event.preventDefault();
57
+ }
58
+ } }
52
59
  />
53
60
  </ControlActions>
54
61
  );
@@ -25,7 +25,7 @@ type SizeControlProps = {
25
25
 
26
26
  export const SizeControl = createControl(
27
27
  ( { units = defaultUnits, extendedValues = [], placeholder, startIcon }: SizeControlProps ) => {
28
- const { value: sizeValue, setValue: setSizeValue } = useBoundProp( sizePropTypeUtil );
28
+ const { value: sizeValue, setValue: setSizeValue, restoreValue } = useBoundProp( sizePropTypeUtil );
29
29
 
30
30
  const [ state, setState ] = useSyncExternalState( {
31
31
  external: sizeValue,
@@ -50,21 +50,21 @@ export const SizeControl = createControl(
50
50
  } ) );
51
51
  };
52
52
 
53
- const inputProps = {
54
- size: state.size,
55
- unit: state.unit,
56
- placeholder,
57
- startIcon,
58
- units,
59
- extendedValues,
60
- handleSizeChange,
61
- handleUnitChange,
62
- };
53
+ const Input = extendedValues?.length ? ExtendedSizeInput : SizeInput;
63
54
 
64
- if ( extendedValues?.length ) {
65
- return <ExtendedSizeInput { ...inputProps } />;
66
- }
67
- return <SizeInput { ...inputProps } />;
55
+ return (
56
+ <Input
57
+ size={ state.size }
58
+ unit={ state.unit }
59
+ placeholder={ placeholder }
60
+ startIcon={ startIcon }
61
+ units={ units }
62
+ extendedValues={ extendedValues }
63
+ handleSizeChange={ handleSizeChange }
64
+ handleUnitChange={ handleUnitChange }
65
+ onBlur={ restoreValue }
66
+ />
67
+ );
68
68
  }
69
69
  );
70
70
 
@@ -99,16 +99,20 @@ type SizeInputProps = {
99
99
  startIcon?: React.ReactNode;
100
100
  units: Unit[];
101
101
  extendedValues?: ExtendedValue[];
102
+ onBlur?: ( event: React.FocusEvent< HTMLInputElement > ) => void;
102
103
  handleUnitChange: ( unit: Unit ) => void;
103
104
  handleSizeChange: ( event: React.ChangeEvent< HTMLInputElement > ) => void;
104
105
  };
105
106
 
107
+ const RESTRICTED_INPUT_KEYS = [ 'e', 'E', '+', '-' ];
108
+
106
109
  const SizeInput = ( {
107
110
  units,
108
111
  handleUnitChange,
109
112
  handleSizeChange,
110
113
  placeholder,
111
114
  startIcon,
115
+ onBlur,
112
116
  size,
113
117
  unit,
114
118
  }: SizeInputProps ) => {
@@ -129,6 +133,12 @@ const SizeInput = ( {
129
133
  type="number"
130
134
  value={ Number.isNaN( size ) ? '' : size }
131
135
  onChange={ handleSizeChange }
136
+ onBlur={ onBlur }
137
+ onKeyDown={ ( event ) => {
138
+ if ( RESTRICTED_INPUT_KEYS.includes( event.key ) ) {
139
+ event.preventDefault();
140
+ }
141
+ } }
132
142
  />
133
143
  </ControlActions>
134
144
  );