@elementor/editor-controls 0.14.0 → 0.16.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.14.0",
4
+ "version": "0.16.0",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -40,15 +40,17 @@
40
40
  "dev": "tsup --config=../../tsup.dev.ts"
41
41
  },
42
42
  "dependencies": {
43
- "@elementor/editor-props": "0.9.3",
43
+ "@elementor/editor-props": "0.10.0",
44
44
  "@elementor/env": "0.3.5",
45
- "@elementor/http": "0.1.3",
46
- "@elementor/icons": "1.31.0",
45
+ "@elementor/http": "0.1.4",
46
+ "@elementor/icons": "1.37.0",
47
+ "@elementor/query": "0.2.4",
47
48
  "@elementor/session": "0.1.0",
48
49
  "@elementor/ui": "1.26.0",
49
50
  "@elementor/utils": "0.4.0",
50
- "@elementor/wp-media": "0.4.2",
51
- "@wordpress/i18n": "^5.13.0"
51
+ "@elementor/wp-media": "0.5.0",
52
+ "@wordpress/i18n": "^5.13.0",
53
+ "@tanstack/react-virtual": "3.13.3"
52
54
  },
53
55
  "devDependencies": {
54
56
  "tsup": "^8.3.5"
package/src/api.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { httpService } from '@elementor/http';
2
+
3
+ const ELEMENTOR_SETTING_URL = 'elementor/v1/settings';
4
+
5
+ type Response< T > = { data: { value: T }; success: boolean };
6
+
7
+ export const apiClient = {
8
+ getElementorSetting: < T >( key: string ) =>
9
+ httpService()
10
+ .get< Response< T > >( `${ ELEMENTOR_SETTING_URL }/${ key }` )
11
+ .then( ( res ) => formatSettingResponse( res.data ) ),
12
+ updateElementorSetting: < T >( key: string, value: T ) =>
13
+ httpService().put( `${ ELEMENTOR_SETTING_URL }/${ key }`, { value } ),
14
+ };
15
+
16
+ const formatSettingResponse = < T >( response: Response< T > ) => response.data.value;
@@ -9,6 +9,7 @@ import {
9
9
  IconButton,
10
10
  Popover,
11
11
  Stack,
12
+ Tooltip,
12
13
  Typography,
13
14
  UnstableTag,
14
15
  type UnstableTagProps,
@@ -200,6 +201,10 @@ const RepeaterItem = ( {
200
201
 
201
202
  const popoverProps = bindPopover( popoverState );
202
203
 
204
+ const duplicateLabel = __( 'Duplicate', 'elementor' );
205
+ const toggleLabel = disabled ? __( 'Show', 'elementor' ) : __( 'Hide', 'elementor' );
206
+ const removeLabel = __( 'Remove', 'elementor' );
207
+
203
208
  return (
204
209
  <>
205
210
  <UnstableTag
@@ -213,29 +218,21 @@ const RepeaterItem = ( {
213
218
  startIcon={ startIcon }
214
219
  actions={
215
220
  <>
216
- <IconButton
217
- size={ SIZE }
218
- onClick={ duplicateItem }
219
- aria-label={ __( 'Duplicate item', 'elementor' ) }
220
- >
221
- <CopyIcon fontSize={ SIZE } />
222
- </IconButton>
223
- <IconButton
224
- size={ SIZE }
225
- onClick={ toggleDisableItem }
226
- aria-label={
227
- disabled ? __( 'Enable item', 'elementor' ) : __( 'Disable item', 'elementor' )
228
- }
229
- >
230
- { disabled ? <EyeOffIcon fontSize={ SIZE } /> : <EyeIcon fontSize={ SIZE } /> }
231
- </IconButton>
232
- <IconButton
233
- size={ SIZE }
234
- onClick={ removeItem }
235
- aria-label={ __( 'Remove item', 'elementor' ) }
236
- >
237
- <XIcon fontSize={ SIZE } />
238
- </IconButton>
221
+ <Tooltip title={ duplicateLabel } placement="top">
222
+ <IconButton size={ SIZE } onClick={ duplicateItem } aria-label={ duplicateLabel }>
223
+ <CopyIcon fontSize={ SIZE } />
224
+ </IconButton>
225
+ </Tooltip>
226
+ <Tooltip title={ toggleLabel } placement="top">
227
+ <IconButton size={ SIZE } onClick={ toggleDisableItem } aria-label={ toggleLabel }>
228
+ { disabled ? <EyeOffIcon fontSize={ SIZE } /> : <EyeIcon fontSize={ SIZE } /> }
229
+ </IconButton>
230
+ </Tooltip>
231
+ <Tooltip title={ removeLabel } placement="top">
232
+ <IconButton size={ SIZE } onClick={ removeItem } aria-label={ removeLabel }>
233
+ <XIcon fontSize={ SIZE } />
234
+ </IconButton>
235
+ </Tooltip>
239
236
  </>
240
237
  }
241
238
  sx={ { backgroundColor: 'background.paper' } }
@@ -0,0 +1,101 @@
1
+ import * as React from 'react';
2
+ import {
3
+ backgroundGradientOverlayPropTypeUtil,
4
+ type BackgroundGradientOverlayPropValue,
5
+ type BackgroundOverlayItemPropValue,
6
+ colorPropTypeUtil,
7
+ type ColorPropValue,
8
+ colorStopPropTypeUtil,
9
+ gradientColorStopPropTypeUtil,
10
+ numberPropTypeUtil,
11
+ type NumberPropValue,
12
+ stringPropTypeUtil,
13
+ type TransformablePropValue,
14
+ } from '@elementor/editor-props';
15
+ import { UnstableGradientBox } from '@elementor/ui';
16
+
17
+ import { useBoundProp } from '../../bound-prop-context';
18
+ import ControlActions from '../../control-actions/control-actions';
19
+ import { createControl } from '../../create-control';
20
+
21
+ export type ColorStop = TransformablePropValue<
22
+ 'color-stop',
23
+ {
24
+ color: ColorPropValue;
25
+ offset: NumberPropValue;
26
+ }
27
+ >;
28
+
29
+ export const BackgroundGradientColorControl = createControl( () => {
30
+ const { value, setValue } = useBoundProp( backgroundGradientOverlayPropTypeUtil );
31
+
32
+ const handleChange = ( newValue: BackgroundGradientOverlayPropValue[ 'value' ] ) => {
33
+ const transformedValue = createTransformableValue( newValue );
34
+
35
+ if ( transformedValue.positions ) {
36
+ transformedValue.positions = stringPropTypeUtil.create( newValue.positions.join( ' ' ) );
37
+ }
38
+
39
+ setValue( transformedValue );
40
+ };
41
+
42
+ // TODO: To support Global variables this won't be needed when we have a flexible Gradient Box
43
+ const createTransformableValue = ( newValue: BackgroundGradientOverlayPropValue[ 'value' ] ) => ( {
44
+ ...newValue,
45
+ type: stringPropTypeUtil.create( newValue.type ),
46
+ angle: numberPropTypeUtil.create( newValue.angle ),
47
+ stops: gradientColorStopPropTypeUtil.create(
48
+ newValue.stops.map( ( { color, offset }: { color: string; offset: number } ) =>
49
+ colorStopPropTypeUtil.create( {
50
+ color: colorPropTypeUtil.create( color ),
51
+ offset: numberPropTypeUtil.create( offset ),
52
+ } )
53
+ )
54
+ ),
55
+ } );
56
+
57
+ // TODO: To support Global variables this won't be needed when we have a flexible Gradient Box
58
+ const normalizeValue = () => {
59
+ if ( ! value ) {
60
+ return;
61
+ }
62
+
63
+ const { type, angle, stops, positions } = value;
64
+
65
+ return {
66
+ type: type.value,
67
+ angle: angle.value,
68
+ stops: stops.value.map( ( { value: { color, offset } }: ColorStop ) => ( {
69
+ color: color.value,
70
+ offset: offset.value,
71
+ } ) ),
72
+ positions: positions?.value.split( ' ' ),
73
+ };
74
+ };
75
+
76
+ return (
77
+ <ControlActions>
78
+ <UnstableGradientBox
79
+ sx={ { width: 'auto', padding: 1.5 } }
80
+ value={ normalizeValue() }
81
+ onChange={ handleChange }
82
+ />
83
+ </ControlActions>
84
+ );
85
+ } );
86
+
87
+ export const initialBackgroundGradientOverlay: BackgroundOverlayItemPropValue =
88
+ backgroundGradientOverlayPropTypeUtil.create( {
89
+ type: stringPropTypeUtil.create( 'linear' ),
90
+ angle: numberPropTypeUtil.create( 180 ),
91
+ stops: gradientColorStopPropTypeUtil.create( [
92
+ colorStopPropTypeUtil.create( {
93
+ color: stringPropTypeUtil.create( 'var(--primary-color)' ),
94
+ offset: numberPropTypeUtil.create( 0 ),
95
+ } ),
96
+ colorStopPropTypeUtil.create( {
97
+ color: colorPropTypeUtil.create( 'rgb(255,255,255)' ),
98
+ offset: numberPropTypeUtil.create( 100 ),
99
+ } ),
100
+ ] ),
101
+ } );
@@ -17,6 +17,11 @@ import { createControl } from '../../../create-control';
17
17
  import { env } from '../../../env';
18
18
  import { ColorControl } from '../../color-control';
19
19
  import { ImageControl } from '../../image-control';
20
+ import {
21
+ BackgroundGradientColorControl,
22
+ type ColorStop,
23
+ initialBackgroundGradientOverlay,
24
+ } from '../background-gradient-color-control';
20
25
  import { BackgroundImageOverlayAttachment } from './background-image-overlay/background-image-overlay-attachment';
21
26
  import { BackgroundImageOverlayPosition } from './background-image-overlay/background-image-overlay-position';
22
27
  import { BackgroundImageOverlayRepeat } from './background-image-overlay/background-image-overlay-repeat';
@@ -93,6 +98,7 @@ const Content = () => {
93
98
  const { getTabsProps, getTabProps, getTabPanelProps } = useBackgroundTabsHistory( {
94
99
  image: getInitialBackgroundOverlay().value,
95
100
  color: initialBackgroundColorOverlay.value,
101
+ gradient: initialBackgroundGradientOverlay.value,
96
102
  } );
97
103
 
98
104
  return (
@@ -100,6 +106,7 @@ const Content = () => {
100
106
  <Box sx={ { borderBottom: 1, borderColor: 'divider' } }>
101
107
  <Tabs { ...getTabsProps() } aria-label={ __( 'Background Overlay', 'elementor' ) }>
102
108
  <Tab label={ __( 'Image', 'elementor' ) } { ...getTabProps( 'image' ) } />
109
+ <Tab label={ __( 'Gradient', 'elementor' ) } { ...getTabProps( 'gradient' ) } />
103
110
  <Tab label={ __( 'Color', 'elementor' ) } { ...getTabProps( 'color' ) } />
104
111
  </Tabs>
105
112
  </Box>
@@ -108,12 +115,11 @@ const Content = () => {
108
115
  <ImageOverlayContent />
109
116
  </PopoverContent>
110
117
  </TabPanel>
118
+ <TabPanel sx={ { p: 1.5 } } { ...getTabPanelProps( 'gradient' ) }>
119
+ <BackgroundGradientColorControl />
120
+ </TabPanel>
111
121
  <TabPanel { ...getTabPanelProps( 'color' ) } sx={ { p: 1.5 } }>
112
- <Grid container spacing={ 1 } alignItems="center">
113
- <Grid item xs={ 12 }>
114
- <ColorControl propTypeUtil={ backgroundColorOverlayPropTypeUtil } />
115
- </Grid>
116
- </Grid>
122
+ <ColorControl propTypeUtil={ backgroundColorOverlayPropTypeUtil } />
117
123
  </TabPanel>
118
124
  </Box>
119
125
  );
@@ -125,6 +131,8 @@ const ItemIcon = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
125
131
  return <ItemIconImage value={ value as BackgroundImageOverlay } />;
126
132
  case 'background-color-overlay':
127
133
  return <ItemIconColor value={ value } />;
134
+ case 'background-gradient-overlay':
135
+ return <ItemIconGradient value={ value } />;
128
136
  default:
129
137
  return null;
130
138
  }
@@ -140,12 +148,20 @@ const ItemIconImage = ( { value }: { value: BackgroundImageOverlay } ) => {
140
148
  return <CardMedia image={ imageUrl } sx={ { height: 13, width: 13, borderRadius: '4px' } } />;
141
149
  };
142
150
 
151
+ const ItemIconGradient = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
152
+ const gradient = getGradientValue( value );
153
+
154
+ return <UnstableColorIndicator size="inherit" component="span" value={ gradient } />;
155
+ };
156
+
143
157
  const ItemLabel = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
144
158
  switch ( value.$$type ) {
145
159
  case 'background-image-overlay':
146
160
  return <ItemLabelImage value={ value as BackgroundImageOverlay } />;
147
161
  case 'background-color-overlay':
148
162
  return <ItemLabelColor value={ value } />;
163
+ case 'background-gradient-overlay':
164
+ return <ItemLabelGradient value={ value } />;
149
165
  default:
150
166
  return null;
151
167
  }
@@ -161,6 +177,14 @@ const ItemLabelImage = ( { value }: { value: BackgroundImageOverlay } ) => {
161
177
  return <span>{ imageTitle }</span>;
162
178
  };
163
179
 
180
+ const ItemLabelGradient = ( { value }: { value: BackgroundOverlayItemPropValue } ) => {
181
+ if ( value.value.type.value === 'linear' ) {
182
+ return <span>{ __( 'Linear Gradient', 'elementor' ) }</span>;
183
+ }
184
+
185
+ return <span>{ __( 'Radial Gradient', 'elementor' ) }</span>;
186
+ };
187
+
164
188
  const ImageOverlayContent = () => {
165
189
  const propContext = useBoundProp( backgroundImageOverlayPropTypeUtil );
166
190
 
@@ -210,3 +234,17 @@ const useImage = ( image: BackgroundImageOverlay ) => {
210
234
 
211
235
  return { imageTitle, imageUrl };
212
236
  };
237
+
238
+ const getGradientValue = ( value: BackgroundOverlayItemPropValue ) => {
239
+ const gradient = value.value;
240
+
241
+ const stops = gradient.stops.value
242
+ ?.map( ( { value: { color, offset } }: ColorStop ) => `${ color.value } ${ offset.value ?? 0 }%` )
243
+ ?.join( ',' );
244
+
245
+ if ( gradient.type.value === 'linear' ) {
246
+ return `linear-gradient(${ gradient.angle.value }deg, ${ stops })`;
247
+ }
248
+
249
+ return `radial-gradient(circle at ${ gradient.positions.value }, ${ stops })`;
250
+ };
@@ -1,6 +1,7 @@
1
1
  import { useRef } from 'react';
2
2
  import {
3
3
  backgroundColorOverlayPropTypeUtil,
4
+ backgroundGradientOverlayPropTypeUtil,
4
5
  backgroundImageOverlayPropTypeUtil,
5
6
  type BackgroundOverlayItemPropValue,
6
7
  } from '@elementor/editor-props';
@@ -9,25 +10,41 @@ import { useTabs } from '@elementor/ui';
9
10
  import { useBoundProp } from '../../../bound-prop-context';
10
11
  import { type BackgroundImageOverlay } from './types';
11
12
 
12
- type OverlayType = 'image' | 'color';
13
+ type OverlayType = 'image' | 'gradient' | 'color';
13
14
 
14
15
  type InitialBackgroundValues = {
15
16
  color: BackgroundOverlayItemPropValue[ 'value' ];
16
17
  image: BackgroundImageOverlay[ 'value' ];
18
+ gradient: BackgroundOverlayItemPropValue[ 'value' ];
17
19
  };
18
20
 
19
21
  export const useBackgroundTabsHistory = ( {
20
22
  color: initialBackgroundColorOverlay,
21
23
  image: initialBackgroundImageOverlay,
24
+ gradient: initialBackgroundGradientOverlay,
22
25
  }: InitialBackgroundValues ) => {
23
26
  const { value: imageValue, setValue: setImageValue } = useBoundProp( backgroundImageOverlayPropTypeUtil );
24
27
  const { value: colorValue, setValue: setColorValue } = useBoundProp( backgroundColorOverlayPropTypeUtil );
28
+ const { value: gradientValue, setValue: setGradientValue } = useBoundProp( backgroundGradientOverlayPropTypeUtil );
25
29
 
26
- const { getTabsProps, getTabProps, getTabPanelProps } = useTabs< OverlayType >( colorValue ? 'color' : 'image' );
30
+ const getCurrentOverlayType = (): OverlayType => {
31
+ if ( colorValue ) {
32
+ return 'color';
33
+ }
34
+
35
+ if ( gradientValue ) {
36
+ return 'gradient';
37
+ }
38
+
39
+ return 'image';
40
+ };
41
+
42
+ const { getTabsProps, getTabProps, getTabPanelProps } = useTabs< OverlayType >( getCurrentOverlayType() );
27
43
 
28
44
  const valuesHistory = useRef< InitialBackgroundValues >( {
29
45
  image: initialBackgroundImageOverlay,
30
46
  color: initialBackgroundColorOverlay,
47
+ gradient: initialBackgroundGradientOverlay,
31
48
  } );
32
49
 
33
50
  const saveToHistory = ( key: keyof InitialBackgroundValues, value: BackgroundOverlayItemPropValue[ 'value' ] ) => {
@@ -42,6 +59,15 @@ export const useBackgroundTabsHistory = ( {
42
59
  setImageValue( valuesHistory.current.image );
43
60
 
44
61
  saveToHistory( 'color', colorValue );
62
+ saveToHistory( 'gradient', gradientValue );
63
+
64
+ break;
65
+
66
+ case 'gradient':
67
+ setGradientValue( valuesHistory.current.gradient );
68
+
69
+ saveToHistory( 'color', colorValue );
70
+ saveToHistory( 'image', imageValue );
45
71
 
46
72
  break;
47
73
 
@@ -49,6 +75,7 @@ export const useBackgroundTabsHistory = ( {
49
75
  setColorValue( valuesHistory.current.color );
50
76
 
51
77
  saveToHistory( 'image', imageValue );
78
+ saveToHistory( 'gradient', gradientValue );
52
79
  }
53
80
 
54
81
  return getTabsProps().onChange( e, tabName );
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { type ReactNode, useId, useRef } from 'react';
3
3
  import { type PropKey, type PropTypeUtil, sizePropTypeUtil, type SizePropValue } from '@elementor/editor-props';
4
- import { bindPopover, bindToggle, Grid, Popover, Stack, ToggleButton, usePopupState } from '@elementor/ui';
4
+ import { bindPopover, bindToggle, Grid, Popover, Stack, ToggleButton, Tooltip, usePopupState } from '@elementor/ui';
5
5
  import { __ } from '@wordpress/i18n';
6
6
 
7
7
  import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-context';
@@ -23,6 +23,7 @@ export type EqualUnequalItems = [ Item, Item, Item, Item ];
23
23
  type Props< TMultiPropType extends string, TPropValue extends MultiSizePropValue > = {
24
24
  label: string;
25
25
  icon: ReactNode;
26
+ tooltipLabel: string;
26
27
  items: EqualUnequalItems;
27
28
  multiSizePropTypeUtil: PropTypeUtil< TMultiPropType, TPropValue >;
28
29
  };
@@ -44,6 +45,7 @@ const isEqualSizes = ( propValue: MultiSizePropValue, items: EqualUnequalItems )
44
45
  export function EqualUnequalSizesControl< TMultiPropType extends string, TPropValue extends MultiSizePropValue >( {
45
46
  label,
46
47
  icon,
48
+ tooltipLabel,
47
49
  items,
48
50
  multiSizePropTypeUtil,
49
51
  }: Props< TMultiPropType, TPropValue > ) {
@@ -104,15 +106,18 @@ export function EqualUnequalSizesControl< TMultiPropType extends string, TPropVa
104
106
  <Grid item xs={ 6 }>
105
107
  <Stack direction="row" alignItems="center" gap={ 1 }>
106
108
  <SizeControl placeholder={ isMixed ? __( 'Mixed', 'elementor' ) : undefined } />
107
- <ToggleButton
108
- size={ 'tiny' }
109
- value={ 'check' }
110
- sx={ { marginLeft: 'auto' } }
111
- { ...bindToggle( popupState ) }
112
- selected={ popupState.isOpen }
113
- >
114
- { icon }
115
- </ToggleButton>
109
+ <Tooltip title={ tooltipLabel } placement="top">
110
+ <ToggleButton
111
+ size={ 'tiny' }
112
+ value={ 'check' }
113
+ sx={ { marginLeft: 'auto' } }
114
+ { ...bindToggle( popupState ) }
115
+ selected={ popupState.isOpen }
116
+ aria-label={ tooltipLabel }
117
+ >
118
+ { icon }
119
+ </ToggleButton>
120
+ </Tooltip>
116
121
  </Stack>
117
122
  </Grid>
118
123
  </Grid>
@@ -0,0 +1,15 @@
1
+ type EnqueueFont = ( fontFamily: string, context?: 'preview' | 'editor' ) => void;
2
+
3
+ type ExtendedWindow = Window & {
4
+ elementor?: {
5
+ helpers?: {
6
+ enqueueFont?: EnqueueFont;
7
+ };
8
+ };
9
+ };
10
+
11
+ export const enqueueFont: EnqueueFont = ( fontFamily, context = 'editor' ) => {
12
+ const extendedWindow = window as unknown as ExtendedWindow;
13
+
14
+ return extendedWindow.elementor?.helpers?.enqueueFont?.( fontFamily, context ) ?? null;
15
+ };