@elementor/editor-controls 1.3.0 → 3.32.0-20

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 (74) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/index.d.mts +104 -26
  3. package/dist/index.d.ts +104 -26
  4. package/dist/index.js +2271 -1119
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +2147 -990
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +18 -18
  9. package/src/components/control-toggle-button-group.tsx +78 -14
  10. package/src/components/floating-bar.tsx +45 -0
  11. package/src/components/item-selector.tsx +168 -0
  12. package/src/components/repeater.tsx +23 -12
  13. package/src/components/restricted-link-infotip.tsx +76 -0
  14. package/src/components/size-control/size-input.tsx +4 -3
  15. package/src/components/size-control/text-field-inner-selection.tsx +60 -14
  16. package/src/components/text-field-popover.tsx +30 -7
  17. package/src/components/unstable-repeater/actions/add-item-action.tsx +50 -0
  18. package/src/components/unstable-repeater/actions/disable-item-action.tsx +39 -0
  19. package/src/components/unstable-repeater/actions/duplicate-item-action.tsx +32 -0
  20. package/src/components/unstable-repeater/actions/remove-item-action.tsx +27 -0
  21. package/src/components/unstable-repeater/context/repeater-context.tsx +137 -0
  22. package/src/components/unstable-repeater/header/header.tsx +23 -0
  23. package/src/components/unstable-repeater/index.ts +5 -0
  24. package/src/components/unstable-repeater/items/edit-item-popover.tsx +28 -0
  25. package/src/components/unstable-repeater/items/item.tsx +71 -0
  26. package/src/components/unstable-repeater/items/items-container.tsx +49 -0
  27. package/src/components/unstable-repeater/items/use-popover.tsx +26 -0
  28. package/src/{locations.ts → components/unstable-repeater/locations.ts} +9 -1
  29. package/src/components/unstable-repeater/types.ts +26 -0
  30. package/src/components/unstable-repeater/unstable-repeater.tsx +24 -0
  31. package/src/control-actions/control-actions.tsx +3 -20
  32. package/src/control-replacements.tsx +41 -0
  33. package/src/controls/background-control/background-control.tsx +1 -8
  34. package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +17 -16
  35. package/src/controls/color-control.tsx +12 -1
  36. package/src/controls/equal-unequal-sizes-control.tsx +2 -9
  37. package/src/controls/filter-control/drop-shadow-item-content.tsx +67 -0
  38. package/src/controls/filter-control/drop-shadow-item-label.tsx +20 -0
  39. package/src/controls/filter-repeater-control.tsx +214 -88
  40. package/src/controls/font-family-control/font-family-control.tsx +22 -10
  41. package/src/controls/key-value-control.tsx +64 -50
  42. package/src/controls/link-control.tsx +8 -91
  43. package/src/controls/linked-dimensions-control.tsx +3 -16
  44. package/src/controls/number-control.tsx +10 -1
  45. package/src/controls/position-control.tsx +4 -16
  46. package/src/controls/repeatable-control.tsx +56 -34
  47. package/src/controls/select-control.tsx +7 -2
  48. package/src/controls/selection-size-control.tsx +74 -0
  49. package/src/controls/size-control.tsx +189 -121
  50. package/src/controls/stroke-control.tsx +2 -2
  51. package/src/controls/text-control.tsx +33 -18
  52. package/src/controls/toggle-control.tsx +3 -2
  53. package/src/controls/transform-control/functions/axis-row.tsx +4 -2
  54. package/src/controls/transform-control/functions/move.tsx +2 -1
  55. package/src/controls/transform-control/functions/rotate.tsx +48 -0
  56. package/src/controls/transform-control/functions/scale-axis-row.tsx +32 -0
  57. package/src/controls/transform-control/functions/scale.tsx +45 -0
  58. package/src/controls/transform-control/functions/skew.tsx +43 -0
  59. package/src/controls/transform-control/transform-content.tsx +60 -23
  60. package/src/controls/transform-control/transform-icon.tsx +10 -2
  61. package/src/controls/transform-control/transform-label.tsx +39 -2
  62. package/src/controls/transform-control/transform-repeater-control.tsx +2 -10
  63. package/src/controls/transform-control/types.ts +58 -0
  64. package/src/controls/transform-control/use-transform-tabs-history.tsx +107 -0
  65. package/src/controls/transition-control/data.ts +34 -0
  66. package/src/controls/transition-control/transition-repeater-control.tsx +63 -0
  67. package/src/controls/transition-control/transition-selector.tsx +88 -0
  68. package/src/controls/unstable-transform-control/unstable-transform-repeater-control.tsx +35 -0
  69. package/src/hooks/use-filtered-items-list.ts +24 -0
  70. package/src/hooks/use-size-extended-options.ts +1 -6
  71. package/src/index.ts +13 -3
  72. package/src/utils/size-control.ts +10 -2
  73. package/src/components/font-family-selector.tsx +0 -158
  74. package/src/hooks/use-filtered-font-families.ts +0 -24
@@ -1,67 +1,172 @@
1
1
  import * as React from 'react';
2
2
  import { useRef } from 'react';
3
3
  import {
4
- blurFilterPropTypeUtil,
5
- brightnessFilterPropTypeUtil,
4
+ type CreateOptions,
5
+ cssFilterFunctionPropUtil,
6
6
  type FilterItemPropValue,
7
7
  filterPropTypeUtil,
8
8
  type PropKey,
9
- type PropTypeUtil,
10
9
  type SizePropValue,
11
10
  } from '@elementor/editor-props';
12
- import { MenuListItem } from '@elementor/editor-ui';
13
- import { Box, Grid, Select, type SelectChangeEvent } from '@elementor/ui';
11
+ import { backdropFilterPropTypeUtil } from '@elementor/editor-props';
12
+ import { Box, Grid } from '@elementor/ui';
14
13
  import { __ } from '@wordpress/i18n';
15
14
 
16
15
  import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-context';
17
- import { ControlLabel } from '../components/control-label';
16
+ import { ControlFormLabel } from '../components/control-form-label';
18
17
  import { PopoverContent } from '../components/popover-content';
19
18
  import { PopoverGridContainer } from '../components/popover-grid-container';
20
- import { Repeater } from '../components/repeater';
19
+ import { type CollectionPropUtil, Repeater } from '../components/repeater';
21
20
  import { createControl } from '../create-control';
22
- import { defaultUnits } from '../utils/size-control';
23
- import { SizeControl } from './size-control';
21
+ import { type LengthUnit, lengthUnits, type Unit } from '../utils/size-control';
22
+ import { DropShadowItemContent } from './filter-control/drop-shadow-item-content';
23
+ import { DropShadowItemLabel } from './filter-control/drop-shadow-item-label';
24
+ import { SelectControl } from './select-control';
25
+ import { SizeControl, type SizeControlProps } from './size-control';
24
26
 
25
- type FilterType = FilterItemPropValue[ '$$type' ];
26
- type FilterValue = FilterItemPropValue[ 'value' ];
27
+ type FilterType = FilterItemPropValue[ 'value' ][ 'func' ];
27
28
 
28
- const DEFAULT_FILTER_KEY: FilterType = 'blur';
29
+ const DEFAULT_FILTER = 'blur';
29
30
 
30
31
  type FilterItemConfig = {
31
- defaultValue: FilterValue;
32
+ defaultValue: FilterItemPropValue;
32
33
  name: string;
33
34
  valueName: string;
34
- propType: PropTypeUtil< FilterValue, FilterValue >;
35
35
  units?: Exclude< SizePropValue[ 'value' ][ 'unit' ], 'custom' | 'auto' >[];
36
+ sizeVariant?: SizeControlProps[ 'variant' ];
36
37
  };
37
38
 
38
- const filterConfig: Record< FilterType, FilterItemConfig > = {
39
+ const filterConfig: Record< string, FilterItemConfig > = {
39
40
  blur: {
40
- defaultValue: { $$type: 'radius', radius: { $$type: 'size', value: { size: 0, unit: 'px' } } },
41
+ defaultValue: {
42
+ $$type: 'css-filter-func',
43
+ value: {
44
+ func: { $$type: 'string', value: 'blur' },
45
+ args: { $$type: 'size', value: { size: 0, unit: 'px' } },
46
+ },
47
+ },
41
48
  name: __( 'Blur', 'elementor' ),
42
49
  valueName: __( 'Radius', 'elementor' ),
43
- propType: blurFilterPropTypeUtil,
44
- units: defaultUnits.filter( ( unit ) => unit !== '%' ),
50
+ units: lengthUnits.filter( ( unit ) => unit !== '%' ),
45
51
  },
46
52
  brightness: {
47
- defaultValue: { $$type: 'amount', amount: { $$type: 'size', value: { size: 100, unit: '%' } } },
53
+ defaultValue: {
54
+ $$type: 'css-filter-func',
55
+ value: {
56
+ func: { $$type: 'string', value: 'brightness' },
57
+ args: { $$type: 'size', value: { size: 100, unit: '%' } },
58
+ },
59
+ },
48
60
  name: __( 'Brightness', 'elementor' ),
49
61
  valueName: __( 'Amount', 'elementor' ),
50
- propType: brightnessFilterPropTypeUtil,
51
62
  units: [ '%' ],
52
63
  },
64
+ contrast: {
65
+ defaultValue: {
66
+ $$type: 'css-filter-func',
67
+ value: {
68
+ func: { $$type: 'string', value: 'contrast' },
69
+ args: { $$type: 'size', value: { size: 100, unit: '%' } },
70
+ },
71
+ },
72
+ name: __( 'Contrast', 'elementor' ),
73
+ valueName: __( 'Amount', 'elementor' ),
74
+ units: [ '%' ],
75
+ },
76
+ 'hue-rotate': {
77
+ defaultValue: {
78
+ $$type: 'css-filter-func',
79
+ value: {
80
+ func: { $$type: 'string', value: 'hue-rotate' },
81
+ args: { $$type: 'size', value: { size: 0, unit: 'deg' } },
82
+ },
83
+ },
84
+ name: __( 'Hue Rotate', 'elementor' ),
85
+ valueName: __( 'Angle', 'elementor' ),
86
+ units: [ 'deg', 'rad', 'grad', 'turn' ],
87
+ },
88
+ saturate: {
89
+ defaultValue: {
90
+ $$type: 'css-filter-func',
91
+ value: {
92
+ func: { $$type: 'string', value: 'saturate' },
93
+ args: { $$type: 'size', value: { size: 100, unit: '%' } },
94
+ },
95
+ },
96
+ name: __( 'Saturate', 'elementor' ),
97
+ valueName: __( 'Amount', 'elementor' ),
98
+ units: [ '%' ],
99
+ },
100
+ grayscale: {
101
+ defaultValue: {
102
+ $$type: 'css-filter-func',
103
+ value: {
104
+ func: { $$type: 'string', value: 'grayscale' },
105
+ args: { $$type: 'size', value: { size: 0, unit: '%' } },
106
+ },
107
+ },
108
+ name: __( 'Grayscale', 'elementor' ),
109
+ valueName: __( 'Amount', 'elementor' ),
110
+ units: [ '%' ],
111
+ },
112
+ invert: {
113
+ defaultValue: {
114
+ $$type: 'css-filter-func',
115
+ value: {
116
+ func: { $$type: 'string', value: 'invert' },
117
+ args: { $$type: 'size', value: { size: 0, unit: '%' } },
118
+ },
119
+ },
120
+ name: __( 'Invert', 'elementor' ),
121
+ valueName: __( 'Amount', 'elementor' ),
122
+ units: [ '%' ],
123
+ },
124
+ sepia: {
125
+ defaultValue: {
126
+ $$type: 'css-filter-func',
127
+ value: {
128
+ func: { $$type: 'string', value: 'sepia' },
129
+ args: { $$type: 'size', value: { size: 0, unit: '%' } },
130
+ },
131
+ },
132
+ name: __( 'Sepia', 'elementor' ),
133
+ valueName: __( 'Amount', 'elementor' ),
134
+ units: [ '%' ],
135
+ },
136
+ 'drop-shadow': {
137
+ defaultValue: {
138
+ $$type: 'css-filter-func',
139
+ value: {
140
+ func: { $$type: 'string', value: 'drop-shadow' },
141
+ args: {
142
+ $$type: 'drop-shadow',
143
+ value: {
144
+ xAxis: { $$type: 'size', value: { size: 0, unit: 'px' } },
145
+ yAxis: { $$type: 'size', value: { size: 0, unit: 'px' } },
146
+ blur: { $$type: 'size', value: { size: 10, unit: 'px' } },
147
+ color: { $$type: 'color', value: 'rgba(0, 0, 0, 1)' },
148
+ },
149
+ },
150
+ },
151
+ },
152
+ name: __( 'Drop shadow', 'elementor' ),
153
+ valueName: __( 'Drop-shadow', 'elementor' ),
154
+ units: lengthUnits.filter( ( unit ) => unit !== '%' ),
155
+ },
53
156
  };
54
157
 
55
- const filterKeys = Object.keys( filterConfig ) as FilterType[];
56
-
57
- const singleSizeFilterNames = filterKeys.filter( ( name ) => {
58
- const filter = filterConfig[ name as FilterType ].defaultValue;
158
+ const filterKeys = Object.keys( filterConfig );
59
159
 
60
- return filter[ filter.$$type ].$$type === 'size';
61
- } ) as FilterType[];
160
+ const isSingleSize = ( key: string ): boolean => {
161
+ return ! [ 'drop-shadow' ].includes( key );
162
+ };
62
163
 
63
- export const FilterRepeaterControl = createControl( () => {
64
- const { propType, value: filterValues, setValue, disabled } = useBoundProp( filterPropTypeUtil );
164
+ export const FilterRepeaterControl = createControl( ( { filterPropName = 'filter' }: { filterPropName?: string } ) => {
165
+ const [ propUtil, label ] =
166
+ filterPropName === 'backdrop-filter'
167
+ ? [ backdropFilterPropTypeUtil, __( 'Backdrop Filters', 'elementor' ) ]
168
+ : [ filterPropTypeUtil, __( 'Filters', 'elementor' ) ];
169
+ const { propType, value: filterValues, setValue, disabled } = useBoundProp( propUtil );
65
170
 
66
171
  return (
67
172
  <PropProvider propType={ propType } value={ filterValues } setValue={ setValue }>
@@ -70,15 +175,13 @@ export const FilterRepeaterControl = createControl( () => {
70
175
  disabled={ disabled }
71
176
  values={ filterValues ?? [] }
72
177
  setValues={ setValue }
73
- label={ __( 'Filter', 'elementor' ) }
178
+ label={ label }
179
+ collectionPropUtil={ propUtil }
74
180
  itemSettings={ {
75
181
  Icon: ItemIcon,
76
182
  Label: ItemLabel,
77
183
  Content: ItemContent,
78
- initialValues: {
79
- $$type: DEFAULT_FILTER_KEY,
80
- value: filterConfig[ DEFAULT_FILTER_KEY ].defaultValue,
81
- } as FilterItemPropValue,
184
+ initialValues: filterConfig[ DEFAULT_FILTER ].defaultValue,
82
185
  } }
83
186
  />
84
187
  </PropProvider>
@@ -87,21 +190,23 @@ export const FilterRepeaterControl = createControl( () => {
87
190
 
88
191
  const ItemIcon = () => <></>;
89
192
 
90
- const ItemLabel = ( props: { value: FilterItemPropValue } ) => {
91
- const { $$type } = props.value;
92
-
93
- return singleSizeFilterNames.includes( $$type ) && <SingleSizeItemLabel value={ props.value } />;
193
+ const ItemLabel = ( { value }: { value: FilterItemPropValue } ) => {
194
+ return isSingleSize( value.value.func.value ?? '' ) ? (
195
+ <SingleSizeItemLabel value={ value } />
196
+ ) : (
197
+ <DropShadowItemLabel value={ value } />
198
+ );
94
199
  };
95
200
 
96
201
  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 };
202
+ const { func, args } = value.value;
203
+ const defaultUnit =
204
+ ( filterConfig[ func.value ?? '' ].defaultValue.value.args as SizePropValue ).value.unit ?? lengthUnits[ 0 ];
205
+ const { unit, size } = ( args as SizePropValue ).value ?? { unit: defaultUnit, size: 0 };
101
206
 
102
207
  const label = (
103
208
  <Box component="span" style={ { textTransform: 'capitalize' } }>
104
- { value.$$type }:
209
+ { func.value ?? '' }:
105
210
  </Box>
106
211
  );
107
212
 
@@ -113,74 +218,95 @@ const SingleSizeItemLabel = ( { value }: { value: FilterItemPropValue } ) => {
113
218
  );
114
219
  };
115
220
 
116
- const ItemContent = ( { bind }: { bind: PropKey } ) => {
117
- const { value: filterValues, setValue } = useBoundProp( filterPropTypeUtil );
221
+ const ItemContent = ( {
222
+ bind,
223
+ collectionPropUtil,
224
+ anchorEl,
225
+ }: {
226
+ bind: PropKey;
227
+ collectionPropUtil?: CollectionPropUtil< FilterItemPropValue >;
228
+ anchorEl?: HTMLElement | null;
229
+ } ) => {
230
+ const { value: filterValues = [] } = useBoundProp( collectionPropUtil ?? filterPropTypeUtil );
118
231
  const itemIndex = parseInt( bind, 10 );
119
232
  const item = filterValues?.[ itemIndex ];
233
+ return item ? (
234
+ <PropKeyProvider bind={ bind }>
235
+ <PropContent item={ item } anchorEl={ anchorEl } />
236
+ </PropKeyProvider>
237
+ ) : null;
238
+ };
120
239
 
121
- const handleChange = ( e: SelectChangeEvent< string > ) => {
122
- const newFilterValues = [ ...filterValues ];
123
- const filterType = e.target.value as FilterType;
240
+ const PropContent = ( { item, anchorEl }: { item: FilterItemPropValue; anchorEl?: HTMLElement | null } ) => {
241
+ const propContext = useBoundProp( cssFilterFunctionPropUtil );
124
242
 
125
- newFilterValues[ itemIndex ] = {
126
- $$type: filterType,
127
- value: filterConfig[ filterType ].defaultValue,
128
- } as FilterItemPropValue;
243
+ const handleValueChange = (
244
+ changedValue: FilterItemPropValue[ 'value' ],
245
+ options?: CreateOptions,
246
+ meta?: { bind?: PropKey }
247
+ ) => {
248
+ let newValue = structuredClone( changedValue );
249
+ const newFuncName = newValue?.func.value ?? '';
250
+ if ( meta?.bind === 'func' ) {
251
+ newValue = structuredClone( filterConfig[ newFuncName ].defaultValue.value );
252
+ }
129
253
 
130
- setValue( newFilterValues );
254
+ if ( ! newValue.args ) {
255
+ return;
256
+ }
257
+ propContext.setValue( newValue );
131
258
  };
132
259
 
133
260
  return (
134
- <PropKeyProvider bind={ bind }>
261
+ <PropProvider { ...propContext } setValue={ handleValueChange }>
135
262
  <PopoverContent p={ 1.5 }>
136
263
  <PopoverGridContainer>
137
264
  <Grid item xs={ 6 }>
138
- <ControlLabel>{ __( 'Filter', 'elementor' ) }</ControlLabel>
265
+ <ControlFormLabel>{ __( 'Filter', 'elementor' ) }</ControlFormLabel>
139
266
  </Grid>
140
267
  <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>
268
+ <PropKeyProvider bind="func">
269
+ <SelectControl
270
+ options={ filterKeys.map( ( filterKey ) => ( {
271
+ label: filterConfig[ filterKey ].name,
272
+ value: filterKey,
273
+ } ) ) }
274
+ />
275
+ </PropKeyProvider>
154
276
  </Grid>
155
277
  </PopoverGridContainer>
156
- <Content filterType={ item?.$$type } />
278
+ <PropKeyProvider bind="args">
279
+ <Content filterType={ item?.value.func } anchorEl={ anchorEl } />
280
+ </PropKeyProvider>
157
281
  </PopoverContent>
158
- </PropKeyProvider>
282
+ </PropProvider>
159
283
  );
160
284
  };
161
285
 
162
- const Content = ( { filterType }: { filterType: FilterType } ) => {
163
- return singleSizeFilterNames.includes( filterType ) && <SingleSizeItemContent filterType={ filterType } />;
286
+ const Content = ( { filterType, anchorEl }: { filterType: FilterType; anchorEl?: HTMLElement | null } ) => {
287
+ const filterName = filterType?.value || DEFAULT_FILTER;
288
+ const filterItemConfig = filterConfig[ filterName ];
289
+ const { units = [] } = filterItemConfig;
290
+
291
+ return isSingleSize( filterName ) ? (
292
+ <SingleSizeItemContent filterType={ filterName } />
293
+ ) : (
294
+ <DropShadowItemContent units={ units as LengthUnit[] } anchorEl={ anchorEl } />
295
+ );
164
296
  };
165
297
 
166
- const SingleSizeItemContent = ( { filterType }: { filterType: FilterType } ) => {
167
- const { propType, valueName, defaultValue, units } = filterConfig[ filterType ];
168
- const { $$type } = defaultValue;
169
- const context = useBoundProp( propType );
298
+ const SingleSizeItemContent = ( { filterType }: { filterType: string } ) => {
299
+ const { valueName, defaultValue, units } = filterConfig[ filterType ];
170
300
  const rowRef = useRef< HTMLDivElement >( null );
171
-
301
+ const defaultUnit = ( defaultValue.value.args as SizePropValue ).value.unit;
172
302
  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>
303
+ <PopoverGridContainer ref={ rowRef }>
304
+ <Grid item xs={ 6 }>
305
+ <ControlFormLabel>{ valueName }</ControlFormLabel>
306
+ </Grid>
307
+ <Grid item xs={ 6 }>
308
+ <SizeControl anchorRef={ rowRef } units={ units as LengthUnit[] } defaultUnit={ defaultUnit as Unit } />
309
+ </Grid>
310
+ </PopoverGridContainer>
185
311
  );
186
312
  };
@@ -1,12 +1,15 @@
1
1
  import * as React from 'react';
2
2
  import { stringPropTypeUtil } from '@elementor/editor-props';
3
- import { ChevronDownIcon } from '@elementor/icons';
3
+ import { ChevronDownIcon, TextIcon } from '@elementor/icons';
4
4
  import { bindPopover, bindTrigger, Popover, UnstableTag, usePopupState } from '@elementor/ui';
5
+ import { __ } from '@wordpress/i18n';
5
6
 
6
7
  import { useBoundProp } from '../../bound-prop-context';
7
- import { FontFamilySelector } from '../../components/font-family-selector';
8
+ import { ItemSelector } from '../../components/item-selector';
9
+ import { type Category } from '../../components/item-selector';
8
10
  import ControlActions from '../../control-actions/control-actions';
9
11
  import { createControl } from '../../create-control';
12
+ import { enqueueFont } from './enqueue-font';
10
13
 
11
14
  export type FontCategory = {
12
15
  label: string;
@@ -18,22 +21,26 @@ type FontFamilyControlProps = {
18
21
  sectionWidth: number;
19
22
  };
20
23
 
21
- const SIZE = 'tiny';
22
-
23
24
  export const FontFamilyControl = createControl( ( { fontFamilies, sectionWidth }: FontFamilyControlProps ) => {
24
25
  const { value: fontFamily, setValue: setFontFamily, disabled, placeholder } = useBoundProp( stringPropTypeUtil );
25
26
 
26
27
  const popoverState = usePopupState( { variant: 'popover' } );
27
-
28
28
  const isShowingPlaceholder = ! fontFamily && placeholder;
29
29
 
30
+ const mapFontSubs = React.useMemo< Category[] >( () => {
31
+ return fontFamilies.map( ( { label, fonts } ) => ( {
32
+ label,
33
+ items: fonts,
34
+ } ) );
35
+ }, [ fontFamilies ] );
36
+
30
37
  return (
31
38
  <>
32
39
  <ControlActions>
33
40
  <UnstableTag
34
41
  variant="outlined"
35
42
  label={ fontFamily || placeholder }
36
- endIcon={ <ChevronDownIcon fontSize={ SIZE } /> }
43
+ endIcon={ <ChevronDownIcon fontSize="tiny" /> }
37
44
  { ...bindTrigger( popoverState ) }
38
45
  fullWidth
39
46
  disabled={ disabled }
@@ -49,6 +56,7 @@ export const FontFamilyControl = createControl( ( { fontFamilies, sectionWidth }
49
56
  }
50
57
  />
51
58
  </ControlActions>
59
+
52
60
  <Popover
53
61
  disablePortal
54
62
  disableScrollLock
@@ -57,12 +65,16 @@ export const FontFamilyControl = createControl( ( { fontFamilies, sectionWidth }
57
65
  sx={ { my: 1.5 } }
58
66
  { ...bindPopover( popoverState ) }
59
67
  >
60
- <FontFamilySelector
61
- fontFamilies={ fontFamilies }
62
- fontFamily={ fontFamily }
63
- onFontFamilyChange={ setFontFamily }
68
+ <ItemSelector
69
+ itemsList={ mapFontSubs }
70
+ selectedItem={ fontFamily }
71
+ onItemChange={ setFontFamily }
64
72
  onClose={ popoverState.close }
65
73
  sectionWidth={ sectionWidth }
74
+ title={ __( 'Font Family', 'elementor' ) }
75
+ itemStyle={ ( item ) => ( { fontFamily: item.value } ) }
76
+ onDebounce={ enqueueFont }
77
+ icon={ TextIcon as React.ElementType< { fontSize: string } > }
66
78
  />
67
79
  </Popover>
68
80
  </>
@@ -1,14 +1,19 @@
1
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';
2
+ import { useMemo, useState } from 'react';
3
+ import {
4
+ type CreateOptions,
5
+ isTransformable,
6
+ keyValuePropTypeUtil,
7
+ type PropKey,
8
+ type Props,
9
+ stringPropTypeUtil,
10
+ } from '@elementor/editor-props';
11
+ import { FormHelperText, FormLabel, Grid } from '@elementor/ui';
5
12
  import { __ } from '@wordpress/i18n';
6
13
 
7
- import { useBoundProp } from '../bound-prop-context';
8
- import ControlActions from '../control-actions/control-actions';
14
+ import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-context';
9
15
  import { createControl } from '../create-control';
10
-
11
- type FieldType = 'key' | 'value';
16
+ import { TextControl } from './text-control';
12
17
 
13
18
  type KeyValueControlProps = {
14
19
  keyName?: string;
@@ -19,9 +24,9 @@ type KeyValueControlProps = {
19
24
  };
20
25
 
21
26
  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 );
27
+ const { value, setValue, ...propContext } = useBoundProp( keyValuePropTypeUtil );
28
+ const [ keyError, setKeyError ] = useState< string >( '' );
29
+ const [ valueError, setValueError ] = useState< string >( '' );
25
30
 
26
31
  const [ sessionState, setSessionState ] = useState( {
27
32
  key: value?.key?.value || '',
@@ -43,31 +48,48 @@ export const KeyValueControl = createControl( ( props: KeyValueControlProps = {}
43
48
  const validate = ( newValue: string, fieldType: string ): boolean => {
44
49
  if ( fieldType === 'key' && keyRegex ) {
45
50
  const isValid = keyRegex.test( newValue );
46
- setKeyError( isValid ? null : errMsg );
51
+ setKeyError( isValid ? '' : errMsg );
52
+
47
53
  return isValid;
48
54
  } else if ( fieldType === 'value' && valueRegex ) {
49
55
  const isValid = valueRegex.test( newValue );
50
- setValueError( isValid ? null : errMsg );
56
+ setValueError( isValid ? '' : errMsg );
57
+
51
58
  return isValid;
52
59
  }
60
+
53
61
  return true;
54
62
  };
55
63
 
56
- const handleChange = ( event: ChangeEvent< HTMLInputElement >, fieldType: FieldType ) => {
57
- const newValue = event.target.value;
64
+ const handleChange = ( newValue: Props, options?: CreateOptions, meta?: { bind?: PropKey } ) => {
65
+ const fieldType = meta?.bind;
66
+
67
+ if ( ! fieldType ) {
68
+ return;
69
+ }
70
+
71
+ const newChangedValue = newValue[ fieldType ];
72
+
73
+ if ( isTransformable( newChangedValue ) && newChangedValue.$$type === 'dynamic' ) {
74
+ setValue( {
75
+ ...value,
76
+ [ fieldType ]: newChangedValue,
77
+ } );
78
+
79
+ return;
80
+ }
81
+
82
+ const extractedValue = stringPropTypeUtil.extract( newChangedValue );
58
83
 
59
84
  setSessionState( ( prev ) => ( {
60
85
  ...prev,
61
- [ fieldType ]: newValue,
86
+ [ fieldType ]: extractedValue,
62
87
  } ) );
63
88
 
64
- if ( validate( newValue, fieldType ) ) {
89
+ if ( extractedValue && validate( extractedValue, fieldType ) ) {
65
90
  setValue( {
66
91
  ...value,
67
- [ fieldType ]: {
68
- value: newValue,
69
- $$type: 'string',
70
- },
92
+ [ fieldType ]: newChangedValue,
71
93
  } );
72
94
  } else {
73
95
  setValue( {
@@ -80,40 +102,32 @@ export const KeyValueControl = createControl( ( props: KeyValueControlProps = {}
80
102
  }
81
103
  };
82
104
 
83
- const isKeyInvalid = keyError !== null;
84
- const isValueInvalid = valueError !== null;
85
-
86
105
  return (
87
- <ControlActions>
106
+ <PropProvider { ...propContext } value={ value } setValue={ handleChange }>
88
107
  <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> }
108
+ <Grid item xs={ 12 } display="flex" flexDirection="column">
109
+ <FormLabel size="tiny" sx={ { pb: 1 } }>
110
+ { keyLabel }
111
+ </FormLabel>
112
+ <PropKeyProvider bind={ 'key' }>
113
+ <TextControl inputValue={ sessionState.key } error={ !! keyError } />
114
+ </PropKeyProvider>
115
+ { !! keyError && <FormHelperText error>{ keyError }</FormHelperText> }
102
116
  </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> }
117
+ <Grid item xs={ 12 } display="flex" flexDirection="column">
118
+ <FormLabel size="tiny" sx={ { pb: 1 } }>
119
+ { valueLabel }
120
+ </FormLabel>
121
+ <PropKeyProvider bind={ 'value' }>
122
+ <TextControl
123
+ inputValue={ sessionState.value }
124
+ error={ !! valueError }
125
+ inputDisabled={ !! keyError }
126
+ />
127
+ </PropKeyProvider>
128
+ { !! valueError && <FormHelperText error>{ valueError }</FormHelperText> }
115
129
  </Grid>
116
130
  </Grid>
117
- </ControlActions>
131
+ </PropProvider>
118
132
  );
119
133
  } );