@elementor/editor-controls 0.20.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/index.d.mts +2 -2
  3. package/dist/index.d.ts +2 -2
  4. package/dist/index.js +373 -290
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +307 -224
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +3 -3
  9. package/src/components/control-form-label.tsx +6 -0
  10. package/src/components/control-label.tsx +12 -3
  11. package/src/components/repeater.tsx +18 -8
  12. package/src/components/sortable.tsx +6 -6
  13. package/src/components/text-field-inner-selection.tsx +3 -3
  14. package/src/controls/background-control/background-control.tsx +2 -2
  15. package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-attachment.tsx +2 -2
  16. package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-position.tsx +2 -2
  17. package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-repeat.tsx +2 -2
  18. package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-size.tsx +2 -2
  19. package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +30 -5
  20. package/src/controls/color-control.tsx +1 -1
  21. package/src/controls/equal-unequal-sizes-control.tsx +2 -1
  22. package/src/controls/font-family-control/font-family-control.tsx +2 -0
  23. package/src/controls/gap-control.tsx +3 -2
  24. package/src/controls/image-control.tsx +3 -3
  25. package/src/controls/link-control.tsx +99 -27
  26. package/src/controls/linked-dimensions-control.tsx +7 -6
  27. package/src/controls/stroke-control.tsx +2 -2
  28. package/src/controls/svg-media-control.tsx +2 -2
  29. package/src/index.ts +2 -1
  30. package/src/control-adornments/control-label-with-adornments.tsx +0 -15
@@ -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
 
@@ -24,7 +25,7 @@ import {
24
25
  type FlatOption,
25
26
  isCategorizedOptionPool,
26
27
  } from '../components/autocomplete';
27
- import { ControlLabel } from '../components/control-label';
28
+ import { ControlFormLabel } from '../components/control-form-label';
28
29
  import ControlActions from '../control-actions/control-actions';
29
30
  import { createControl } from '../create-control';
30
31
  import { type ControlProps } from '../utils/types';
@@ -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 ) => {
@@ -144,14 +151,17 @@ export const LinkControl = createControl( ( props: Props ) => {
144
151
  alignItems: 'center',
145
152
  } }
146
153
  >
147
- <ControlLabel>{ __( 'Link', 'elementor' ) }</ControlLabel>
148
- <ToggleIconControl
149
- enabled={ isEnabled }
150
- onIconClick={ onEnabledChange }
151
- label={ __( 'Toggle link', 'elementor' ) }
152
- />
154
+ <ControlFormLabel>{ __( 'Link', 'elementor' ) }</ControlFormLabel>
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>
@@ -159,7 +169,7 @@ export const LinkControl = createControl( ( props: Props ) => {
159
169
  options={ options }
160
170
  allowCustomValues={ allowCustomValues }
161
171
  placeholder={ placeholder }
162
- value={ value?.destination?.value }
172
+ value={ value?.destination?.value?.settings?.label || value?.destination?.value }
163
173
  onOptionChange={ onOptionChange }
164
174
  onTextChange={ onTextChange }
165
175
  minInputLength={ minInputLength }
@@ -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
- <ControlLabel>{ __( 'Open in a new tab', 'elementor' ) }</ControlLabel>
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
+ };
@@ -5,6 +5,7 @@ import { Grid, Stack, ToggleButton, Tooltip } from '@elementor/ui';
5
5
  import { __ } from '@wordpress/i18n';
6
6
 
7
7
  import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-context';
8
+ import { ControlFormLabel } from '../components/control-form-label';
8
9
  import { ControlLabel } from '../components/control-label';
9
10
  import { createControl } from '../create-control';
10
11
  import { type ExtendedValue, SizeControl } from './size-control';
@@ -72,7 +73,7 @@ export const LinkedDimensionsControl = createControl(
72
73
  <Stack direction="row" gap={ 2 } flexWrap="nowrap">
73
74
  <Grid container gap={ 0.75 } alignItems="center">
74
75
  <Grid item xs={ 12 }>
75
- <ControlLabel>{ __( 'Top', 'elementor' ) }</ControlLabel>
76
+ <ControlFormLabel>{ __( 'Top', 'elementor' ) }</ControlFormLabel>
76
77
  </Grid>
77
78
  <Grid item xs={ 12 }>
78
79
  <Control
@@ -85,9 +86,9 @@ export const LinkedDimensionsControl = createControl(
85
86
  </Grid>
86
87
  <Grid container gap={ 0.75 } alignItems="center">
87
88
  <Grid item xs={ 12 }>
88
- <ControlLabel>
89
+ <ControlFormLabel>
89
90
  { isSiteRtl ? __( 'Left', 'elementor' ) : __( 'Right', 'elementor' ) }
90
- </ControlLabel>
91
+ </ControlFormLabel>
91
92
  </Grid>
92
93
  <Grid item xs={ 12 }>
93
94
  <Control
@@ -108,7 +109,7 @@ export const LinkedDimensionsControl = createControl(
108
109
  <Stack direction="row" gap={ 2 } flexWrap="nowrap">
109
110
  <Grid container gap={ 0.75 } alignItems="center">
110
111
  <Grid item xs={ 12 }>
111
- <ControlLabel>{ __( 'Bottom', 'elementor' ) }</ControlLabel>
112
+ <ControlFormLabel>{ __( 'Bottom', 'elementor' ) }</ControlFormLabel>
112
113
  </Grid>
113
114
  <Grid item xs={ 12 }>
114
115
  <Control
@@ -121,9 +122,9 @@ export const LinkedDimensionsControl = createControl(
121
122
  </Grid>
122
123
  <Grid container gap={ 0.75 } alignItems="center">
123
124
  <Grid item xs={ 12 }>
124
- <ControlLabel>
125
+ <ControlFormLabel>
125
126
  { isSiteRtl ? __( 'Right', 'elementor' ) : __( 'Left', 'elementor' ) }
126
- </ControlLabel>
127
+ </ControlFormLabel>
127
128
  </Grid>
128
129
  <Grid item xs={ 12 }>
129
130
  <Control
@@ -4,7 +4,7 @@ import { Grid } from '@elementor/ui';
4
4
  import { __ } from '@wordpress/i18n';
5
5
 
6
6
  import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-context';
7
- import { ControlLabel } from '../components/control-label';
7
+ import { ControlFormLabel } from '../components/control-form-label';
8
8
  import { SectionContent } from '../components/section-content';
9
9
  import { createControl } from '../create-control';
10
10
  import { ColorControl } from './color-control';
@@ -39,7 +39,7 @@ const Control = ( { bind, label, children }: StrokeProps ) => (
39
39
  <PropKeyProvider bind={ bind }>
40
40
  <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
41
41
  <Grid item xs={ 6 }>
42
- <ControlLabel>{ label }</ControlLabel>
42
+ <ControlFormLabel>{ label }</ControlFormLabel>
43
43
  </Grid>
44
44
  <Grid item xs={ 6 }>
45
45
  { children }
@@ -7,7 +7,7 @@ import { type OpenOptions, useWpMediaAttachment, useWpMediaFrame } from '@elemen
7
7
  import { __ } from '@wordpress/i18n';
8
8
 
9
9
  import { useBoundProp } from '../bound-prop-context';
10
- import { ControlLabel } from '../components/control-label';
10
+ import { ControlFormLabel } from '../components/control-form-label';
11
11
  import { EnableUnfilteredModal } from '../components/enable-unfiltered-modal';
12
12
  import ControlActions from '../control-actions/control-actions';
13
13
  import { createControl } from '../create-control';
@@ -83,7 +83,7 @@ export const SvgMediaControl = createControl( () => {
83
83
  return (
84
84
  <Stack gap={ 1 }>
85
85
  <EnableUnfilteredModal open={ unfilteredModalOpenState } onClose={ onCloseUnfilteredModal } />
86
- <ControlLabel> { __( 'SVG', 'elementor' ) } </ControlLabel>
86
+ <ControlFormLabel> { __( 'SVG', 'elementor' ) } </ControlFormLabel>
87
87
  <ControlActions>
88
88
  <StyledCard variant="outlined">
89
89
  <StyledCardMediaContainer>
package/src/index.ts CHANGED
@@ -19,7 +19,7 @@ export { SvgMediaControl } from './controls/svg-media-control';
19
19
  export { BackgroundControl } from './controls/background-control/background-control';
20
20
 
21
21
  // components
22
- export { ControlLabel } from './components/control-label';
22
+ export { ControlFormLabel } from './components/control-form-label';
23
23
  export { ControlToggleButtonGroup } from './components/control-toggle-button-group';
24
24
 
25
25
  // types
@@ -39,5 +39,6 @@ export { ControlActionsProvider, useControlActions } from './control-actions/con
39
39
  export { useBoundProp, PropProvider, PropKeyProvider } from './bound-prop-context';
40
40
  export { ControlAdornmentsProvider } from './control-adornments/control-adornments-context';
41
41
  export { ControlAdornments } from './control-adornments/control-adornments';
42
+
42
43
  // hooks
43
44
  export { useSyncExternalState } from './hooks/use-sync-external-state';
@@ -1,15 +0,0 @@
1
- import * as React from 'react';
2
- import { type PropsWithChildren } from 'react';
3
- import { Stack } from '@elementor/ui';
4
-
5
- import { ControlLabel } from '../components/control-label';
6
- import { ControlAdornments } from './control-adornments';
7
-
8
- export const ControlLabelWithAdornments = ( { children }: PropsWithChildren< object > ) => {
9
- return (
10
- <Stack direction="row" alignItems="center" justifyItems="start" gap={ 1 }>
11
- <ControlLabel>{ children }</ControlLabel>
12
- <ControlAdornments />
13
- </Stack>
14
- );
15
- };