@elementor/editor-controls 4.0.0-manual → 4.0.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 (37) hide show
  1. package/dist/index.d.mts +137 -64
  2. package/dist/index.d.ts +137 -64
  3. package/dist/index.js +890 -202
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +872 -188
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +15 -15
  8. package/src/components/promotions/attributes-control.tsx +2 -1
  9. package/src/components/promotions/display-conditions-control.tsx +2 -1
  10. package/src/components/promotions/promotion-trigger.tsx +14 -4
  11. package/src/controls/chips-control.tsx +1 -1
  12. package/src/controls/email-form-action-control.tsx +5 -5
  13. package/src/controls/number-control.tsx +8 -2
  14. package/src/controls/size-control/hooks/use-size-unit-keyboard.tsx +66 -0
  15. package/src/controls/size-control/hooks/use-size-value.ts +71 -0
  16. package/src/controls/size-control/size-component.tsx +94 -0
  17. package/src/controls/size-control/size-field.tsx +113 -0
  18. package/src/controls/size-control/sync/get-units.ts +17 -0
  19. package/src/controls/size-control/types.ts +17 -0
  20. package/src/controls/size-control/ui/size-input.tsx +68 -0
  21. package/src/controls/size-control/ui/text-field-popover.tsx +78 -0
  22. package/src/controls/size-control/ui/unit-selector.tsx +80 -0
  23. package/src/controls/size-control/unstable-size-control.tsx +86 -0
  24. package/src/controls/size-control/utils/has-size-value.ts +5 -0
  25. package/src/controls/size-control/utils/is-extended-unit.ts +8 -0
  26. package/src/controls/size-control/utils/resolve-bound-prop-value.ts +72 -0
  27. package/src/controls/size-control/utils/resolve-size-value.ts +85 -0
  28. package/src/controls/size-control/utils/settings/get-default-unit.ts +7 -0
  29. package/src/controls/size-control/utils/settings/get-prop-type-settings.ts +12 -0
  30. package/src/controls/size-control/utils/settings/get-size-units.ts +23 -0
  31. package/src/controls/size-control/utils/should-nullify-value.ts +15 -0
  32. package/src/controls/transition-control/data.ts +3 -3
  33. package/src/controls/transition-control/transition-repeater-control.tsx +8 -2
  34. package/src/controls/transition-control/transition-selector.tsx +7 -0
  35. package/src/hooks/use-font-families.ts +22 -25
  36. package/src/index.ts +4 -0
  37. package/src/utils/tracking.ts +61 -0
@@ -0,0 +1,78 @@
1
+ import * as React from 'react';
2
+ import { type RefObject, useEffect, useRef } from 'react';
3
+ import { PopoverHeader } from '@elementor/editor-ui';
4
+ import { MathFunctionIcon } from '@elementor/icons';
5
+ import { bindPopover, Popover, type PopupState, TextField } from '@elementor/ui';
6
+ import { __ } from '@wordpress/i18n';
7
+
8
+ type Props = {
9
+ popupState: PopupState;
10
+ anchorRef: RefObject< HTMLDivElement | null >;
11
+ onClose?: () => void;
12
+ value: string;
13
+ onChange: ( event: React.ChangeEvent< HTMLInputElement > ) => void;
14
+ };
15
+
16
+ const SIZE = 'tiny';
17
+
18
+ export const TextFieldPopover = ( { popupState, anchorRef, value, onChange, onClose }: Props ) => {
19
+ const inputRef = useRef< HTMLInputElement >( null );
20
+
21
+ useEffect( () => {
22
+ if ( popupState.isOpen ) {
23
+ requestAnimationFrame( () => {
24
+ if ( inputRef.current ) {
25
+ inputRef.current.focus();
26
+ }
27
+ } );
28
+ }
29
+ }, [ popupState.isOpen ] );
30
+
31
+ const handleKeyDown = ( event: React.KeyboardEvent< HTMLInputElement > ) => {
32
+ if ( event.key.toLowerCase() === 'enter' ) {
33
+ handleClose();
34
+ }
35
+ };
36
+
37
+ const handleClose = () => {
38
+ onClose?.();
39
+
40
+ popupState.close();
41
+ };
42
+
43
+ return (
44
+ <Popover
45
+ disablePortal
46
+ slotProps={ {
47
+ paper: {
48
+ sx: {
49
+ borderRadius: 2,
50
+ width: anchorRef.current?.offsetWidth + 'px',
51
+ },
52
+ },
53
+ } }
54
+ { ...bindPopover( popupState ) }
55
+ anchorOrigin={ { vertical: 'bottom', horizontal: 'center' } }
56
+ transformOrigin={ { vertical: 'top', horizontal: 'center' } }
57
+ onClose={ handleClose }
58
+ >
59
+ <PopoverHeader
60
+ title={ __( 'CSS function', 'elementor' ) }
61
+ onClose={ handleClose }
62
+ icon={ <MathFunctionIcon fontSize={ SIZE } /> }
63
+ />
64
+ <TextField
65
+ value={ value }
66
+ onChange={ onChange }
67
+ onKeyDown={ handleKeyDown }
68
+ size="tiny"
69
+ type="text"
70
+ fullWidth
71
+ inputProps={ {
72
+ ref: inputRef,
73
+ } }
74
+ sx={ { pt: 0, pr: 1.5, pb: 1.5, pl: 1.5 } }
75
+ />
76
+ </Popover>
77
+ );
78
+ };
@@ -0,0 +1,80 @@
1
+ import * as React from 'react';
2
+ import { useId } from 'react';
3
+ import { MenuListItem } from '@elementor/editor-ui';
4
+ import { bindMenu, bindTrigger, Button, Menu, styled, usePopupState } from '@elementor/ui';
5
+
6
+ export type UnitSelectorProps< T extends string > = {
7
+ options: T[];
8
+ value: T;
9
+ onSelect: ( value: T ) => void;
10
+ isActive: boolean;
11
+ menuItemsAttributes?: { [ key in T ]?: Record< string, unknown > };
12
+ optionLabelOverrides?: { [ key in T ]?: React.ReactNode };
13
+ disabled?: boolean;
14
+ };
15
+
16
+ const menuItemContentStyles = {
17
+ display: 'flex',
18
+ flexDirection: 'column',
19
+ justifyContent: 'center',
20
+ };
21
+
22
+ export const UnitSelector = < T extends string >( {
23
+ value,
24
+ isActive,
25
+ onSelect,
26
+ options,
27
+ disabled,
28
+ menuItemsAttributes = {},
29
+ optionLabelOverrides = {},
30
+ }: UnitSelectorProps< T > ) => {
31
+ const popupState = usePopupState( {
32
+ variant: 'popover',
33
+ popupId: useId(),
34
+ } );
35
+
36
+ const handleMenuItemClick = ( option: T ) => {
37
+ onSelect( option );
38
+
39
+ popupState.close();
40
+ };
41
+
42
+ return (
43
+ <>
44
+ <StyledButton isActive={ isActive } disabled={ disabled } size="small" { ...bindTrigger( popupState ) }>
45
+ { optionLabelOverrides[ value ] ?? value }
46
+ </StyledButton>
47
+
48
+ <Menu MenuListProps={ { dense: true } } { ...bindMenu( popupState ) }>
49
+ { options.map( ( option ) => (
50
+ <MenuListItem
51
+ key={ option }
52
+ onClick={ () => handleMenuItemClick( option ) }
53
+ { ...menuItemsAttributes?.[ option ] }
54
+ primaryTypographyProps={ {
55
+ variant: 'caption',
56
+ sx: {
57
+ ...menuItemContentStyles,
58
+ lineHeight: '1',
59
+ },
60
+ } }
61
+ menuItemTextProps={ {
62
+ sx: menuItemContentStyles,
63
+ } }
64
+ >
65
+ { optionLabelOverrides[ option ] ?? option.toUpperCase() }
66
+ </MenuListItem>
67
+ ) ) }
68
+ </Menu>
69
+ </>
70
+ );
71
+ };
72
+
73
+ const StyledButton = styled( Button, {
74
+ shouldForwardProp: ( prop ) => prop !== 'isActive',
75
+ } )( ( { isActive, theme } ) => ( {
76
+ color: isActive ? theme.palette.text.primary : theme.palette.text.tertiary,
77
+ font: 'inherit',
78
+ minWidth: 'initial',
79
+ textTransform: 'uppercase',
80
+ } ) );
@@ -0,0 +1,86 @@
1
+ import * as React from 'react';
2
+ import type { RefObject } from 'react';
3
+ import { type CreateOptions, sizePropTypeUtil, type SizePropValue } from '@elementor/editor-props';
4
+
5
+ import { type SetValueMeta, useBoundProp } from '../../bound-prop-context';
6
+ import ControlActions from '../../control-actions/control-actions';
7
+ import { createControl } from '../../create-control';
8
+ import { SizeComponent } from './size-component';
9
+ import { type SizeVariant } from './types';
10
+ import { resolveBoundPropValue } from './utils/resolve-bound-prop-value';
11
+ import { getDefaultUnit } from './utils/settings/get-default-unit';
12
+ import { getSizeUnits } from './utils/settings/get-size-units';
13
+ import { shouldNullifyValue } from './utils/should-nullify-value';
14
+
15
+ type Props = {
16
+ placeholder?: string;
17
+ variant?: SizeVariant;
18
+ anchorRef?: RefObject< HTMLDivElement | null >;
19
+ startIcon?: React.ReactNode;
20
+ ariaLabel?: string;
21
+ min?: number;
22
+ id?: string;
23
+ };
24
+
25
+ export const UnstableSizeControl = createControl(
26
+ ( { variant = 'length', placeholder: propPlaceholder, anchorRef, startIcon, ariaLabel, min }: Props ) => {
27
+ const {
28
+ value,
29
+ setValue,
30
+ propType,
31
+ placeholder: boundPropPlaceholder,
32
+ restoreValue,
33
+ } = useBoundProp( sizePropTypeUtil );
34
+
35
+ const { sizeValue, isUnitHighlighted, placeholder } = resolveBoundPropValue(
36
+ value,
37
+ boundPropPlaceholder,
38
+ propPlaceholder
39
+ );
40
+
41
+ const units = getSizeUnits( propType, variant );
42
+ const defaultUnit = getDefaultUnit( propType );
43
+
44
+ const handleBlur = () => {
45
+ const isRequired = propType.settings.required;
46
+
47
+ if ( shouldNullifyValue( value ) && ! isRequired ) {
48
+ setValue( null );
49
+ }
50
+
51
+ if ( isRequired ) {
52
+ restoreValue();
53
+ }
54
+ };
55
+
56
+ const handleChange = ( newValue: SizePropValue[ 'value' ], options?: CreateOptions, meta?: SetValueMeta ) => {
57
+ setValue( newValue, options, {
58
+ ...meta,
59
+ validation: () => {
60
+ if ( propType.settings.required ) {
61
+ return newValue.size !== '';
62
+ }
63
+
64
+ return meta?.validation ? meta.validation( newValue ) : true;
65
+ },
66
+ } );
67
+ };
68
+
69
+ return (
70
+ <SizeComponent
71
+ units={ units }
72
+ value={ sizeValue }
73
+ anchorRef={ anchorRef }
74
+ placeholder={ placeholder }
75
+ defaultUnit={ defaultUnit }
76
+ isUnitActive={ isUnitHighlighted }
77
+ onBlur={ handleBlur }
78
+ setValue={ handleChange }
79
+ SizeFieldWrapper={ ControlActions }
80
+ startIcon={ startIcon }
81
+ ariaLabel={ ariaLabel }
82
+ min={ min }
83
+ />
84
+ );
85
+ }
86
+ );
@@ -0,0 +1,5 @@
1
+ import { type SizePropValue } from '@elementor/editor-props';
2
+
3
+ export const hasSizeValue = ( size: SizePropValue[ 'value' ][ 'size' ] ): boolean => {
4
+ return Boolean( size ) || size === 0;
5
+ };
@@ -0,0 +1,8 @@
1
+ import { getExtendedUnits } from '../sync/get-units';
2
+ import { type SizeUnit } from '../types';
3
+
4
+ export const isExtendedUnit = ( unit: SizeUnit ) => {
5
+ const extendedUnits = getExtendedUnits();
6
+
7
+ return extendedUnits.includes( unit );
8
+ };
@@ -0,0 +1,72 @@
1
+ import { sizePropTypeUtil, type SizePropValue } from '@elementor/editor-props';
2
+
3
+ import { hasSizeValue } from './has-size-value';
4
+ import { EXTENDED_UNITS } from './resolve-size-value';
5
+
6
+ type SizeValue = SizePropValue[ 'value' ] | null;
7
+
8
+ export type ResolvedBoundProp = {
9
+ sizeValue: SizeValue;
10
+ isUnitHighlighted: boolean;
11
+ placeholder: string | undefined;
12
+ };
13
+
14
+ export const resolveBoundPropValue = < T extends SizeValue >(
15
+ value?: T | null,
16
+ boundPropPlaceholder?: T | null,
17
+ propPlaceholder?: string
18
+ ): ResolvedBoundProp => {
19
+ let sizeValue: T | null = null;
20
+
21
+ if ( validateSizeValue( value ) ) {
22
+ sizeValue = value;
23
+ } else if ( validateSizeValue( boundPropPlaceholder ) ) {
24
+ sizeValue = { size: '', unit: boundPropPlaceholder?.unit } as T;
25
+ }
26
+
27
+ return {
28
+ sizeValue,
29
+ isUnitHighlighted: shouldHighlightUnit( value ),
30
+ placeholder: resolvePlaceholder( propPlaceholder, boundPropPlaceholder ),
31
+ };
32
+ };
33
+
34
+ const validateSizeValue = ( value?: SizeValue | null ): value is SizeValue => {
35
+ if ( ! value ) {
36
+ return false;
37
+ }
38
+
39
+ const sizePropValue = sizePropTypeUtil.create( value );
40
+
41
+ return sizePropTypeUtil.isValid( sizePropValue );
42
+ };
43
+
44
+ const resolvePlaceholder = ( propPlaceholder?: string, boundPropPlaceholder?: SizeValue ): string | undefined => {
45
+ if ( propPlaceholder ) {
46
+ return propPlaceholder;
47
+ }
48
+
49
+ const size = boundPropPlaceholder?.size;
50
+
51
+ if ( size === undefined ) {
52
+ return undefined;
53
+ }
54
+
55
+ if ( typeof size === 'number' ) {
56
+ return size.toString();
57
+ }
58
+
59
+ return size;
60
+ };
61
+
62
+ const shouldHighlightUnit = ( value?: SizePropValue[ 'value' ] | null ) => {
63
+ if ( ! value ) {
64
+ return false;
65
+ }
66
+
67
+ if ( value.unit === EXTENDED_UNITS.auto ) {
68
+ return true;
69
+ }
70
+
71
+ return hasSizeValue( value.size );
72
+ };
@@ -0,0 +1,85 @@
1
+ import { type SizePropValue } from '@elementor/editor-props';
2
+
3
+ import { type SizeUnit } from '../types';
4
+ import { isExtendedUnit } from './is-extended-unit';
5
+
6
+ type SizeValue = SizePropValue[ 'value' ];
7
+
8
+ type ResolverContext< U > = {
9
+ units: U[];
10
+ defaultUnit?: U;
11
+ };
12
+
13
+ const DEFAULT_SIZE = '';
14
+
15
+ export const EXTENDED_UNITS = {
16
+ auto: 'auto',
17
+ custom: 'custom',
18
+ } as const;
19
+
20
+ export const resolveSizeValue = < TValue extends SizeValue | null, TUnit extends SizeValue[ 'unit' ] >(
21
+ value: TValue,
22
+ context: ResolverContext< TUnit >
23
+ ) => {
24
+ if ( ! value ) {
25
+ return value;
26
+ }
27
+
28
+ const { units, defaultUnit } = context;
29
+
30
+ const unit = resolveFallbackUnit( value.unit as TUnit, units, defaultUnit );
31
+
32
+ if ( unit === EXTENDED_UNITS.auto ) {
33
+ return { size: DEFAULT_SIZE, unit };
34
+ }
35
+
36
+ if ( unit === EXTENDED_UNITS.custom ) {
37
+ return { size: String( value.size ?? DEFAULT_SIZE ), unit };
38
+ }
39
+
40
+ return {
41
+ size: sanitizeSize( value.size ) ?? DEFAULT_SIZE,
42
+ unit,
43
+ };
44
+ };
45
+
46
+ export const resolveSizeOnUnitChange = (
47
+ size: SizeValue[ 'size' ],
48
+ unit: SizeValue[ 'unit' ]
49
+ ): SizeValue[ 'size' ] => {
50
+ return isExtendedUnit( unit ) ? DEFAULT_SIZE : size;
51
+ };
52
+
53
+ export const createDefaultSizeValue = < T extends SizeValue >( units: SizeUnit[], defaultUnit?: SizeUnit ): T => {
54
+ let [ unit ] = units;
55
+
56
+ if ( defaultUnit !== undefined ) {
57
+ unit = resolveFallbackUnit( defaultUnit, units );
58
+ }
59
+
60
+ return { size: DEFAULT_SIZE, unit } as T;
61
+ };
62
+
63
+ const resolveFallbackUnit = < TUnit extends SizeUnit >(
64
+ unit: TUnit,
65
+ units: readonly TUnit[],
66
+ defaultUnit?: TUnit
67
+ ): TUnit => {
68
+ if ( units.includes( unit ) ) {
69
+ return unit;
70
+ }
71
+
72
+ if ( defaultUnit && units.includes( defaultUnit ) ) {
73
+ return defaultUnit;
74
+ }
75
+
76
+ return units[ 0 ] ?? '';
77
+ };
78
+
79
+ const sanitizeSize = ( size: SizeValue[ 'size' ] ): SizeValue[ 'size' ] => {
80
+ if ( typeof size === 'number' && isNaN( size ) ) {
81
+ return DEFAULT_SIZE;
82
+ }
83
+
84
+ return size;
85
+ };
@@ -0,0 +1,7 @@
1
+ import type { PropType } from '@elementor/editor-props';
2
+
3
+ import { getPropTypeSettings } from './get-prop-type-settings';
4
+
5
+ export const getDefaultUnit = ( propType: PropType ) => {
6
+ return getPropTypeSettings( propType )?.default_unit;
7
+ };
@@ -0,0 +1,12 @@
1
+ import { type PropType } from '@elementor/editor-props';
2
+
3
+ import { type SizeUnit } from '../../types';
4
+
5
+ type Settings = {
6
+ units?: SizeUnit[];
7
+ default_unit?: SizeUnit;
8
+ };
9
+
10
+ export const getPropTypeSettings = ( propType: PropType ) => {
11
+ return propType.settings as Settings;
12
+ };
@@ -0,0 +1,23 @@
1
+ import { type PropType } from '@elementor/editor-props';
2
+
3
+ import { getAngleUnits, getLengthUnits, getTimeUnits } from '../../sync/get-units';
4
+ import { type SizeUnit, type SizeVariant } from '../../types';
5
+ import { getPropTypeSettings } from './get-prop-type-settings';
6
+
7
+ const getVariantUnits = ( variant: SizeVariant ): SizeUnit[] => {
8
+ const map: Record< SizeVariant, () => SizeUnit[] > = {
9
+ length: getLengthUnits,
10
+ angle: getAngleUnits,
11
+ time: getTimeUnits,
12
+ };
13
+
14
+ return map[ variant ]();
15
+ };
16
+
17
+ const getSettingsUnits = ( propType: PropType ) => {
18
+ return getPropTypeSettings( propType )?.units;
19
+ };
20
+
21
+ export const getSizeUnits = ( propType: PropType, variant: SizeVariant ): SizeUnit[] => {
22
+ return getSettingsUnits( propType ) ?? getVariantUnits( variant );
23
+ };
@@ -0,0 +1,15 @@
1
+ import { type SizePropValue } from '@elementor/editor-props';
2
+
3
+ import { EXTENDED_UNITS } from './resolve-size-value';
4
+
5
+ type SizeValue = SizePropValue[ 'value' ] | null;
6
+
7
+ const conditions: Array< ( value: SizeValue ) => boolean > = [
8
+ ( value ) => value?.size === null || value?.size === undefined || value?.size === '',
9
+ ( value ) => value?.unit !== EXTENDED_UNITS.auto,
10
+ ( value ) => value?.unit !== EXTENDED_UNITS.custom,
11
+ ];
12
+
13
+ export const shouldNullifyValue = ( value: SizeValue ): boolean => {
14
+ return conditions.every( ( condition ) => condition( value ) );
15
+ };
@@ -46,9 +46,9 @@ const getIsSiteRtl = () => {
46
46
  };
47
47
 
48
48
  // TODO: Remove this after version 4.01 is released
49
- const shouldExtendTransitionProperties = (): boolean => {
49
+ const shouldShowAllTransitionProperties = (): boolean => {
50
50
  if ( ! hasProInstalled() ) {
51
- return false;
51
+ return true;
52
52
  }
53
53
 
54
54
  const proVersion = window.elementorPro?.config?.version;
@@ -191,7 +191,7 @@ const createTransitionPropertiesList = (): TransitionCategory[] => {
191
191
  },
192
192
  ];
193
193
 
194
- return shouldExtendTransitionProperties() ? baseProperties : [ baseProperties[ 0 ] ];
194
+ return shouldShowAllTransitionProperties() ? baseProperties : [ baseProperties[ 0 ] ];
195
195
  };
196
196
 
197
197
  export const transitionProperties: TransitionCategory[] = createTransitionPropertiesList();
@@ -9,6 +9,7 @@ import {
9
9
  import { type StyleDefinitionState } from '@elementor/editor-styles';
10
10
  import { InfoCircleFilledIcon } from '@elementor/icons';
11
11
  import { Alert, AlertTitle, Box, Typography } from '@elementor/ui';
12
+ import { hasProInstalled } from '@elementor/utils';
12
13
  import { __ } from '@wordpress/i18n';
13
14
 
14
15
  import { useBoundProp } from '../../bound-prop-context';
@@ -181,6 +182,7 @@ export const TransitionRepeaterControl = createControl(
181
182
  } ) => {
182
183
  const currentStyleIsNormal = currentStyleState === null;
183
184
  const [ recentlyUsedList, setRecentlyUsedList ] = useState< string[] >( [] );
185
+ const proInstalled = hasProInstalled();
184
186
 
185
187
  const { value, setValue } = useBoundProp( childArrayPropTypeUtil );
186
188
  const { allDisabled: disabledItems, proDisabled: proDisabledItems } = useMemo(
@@ -191,10 +193,14 @@ export const TransitionRepeaterControl = createControl(
191
193
  const allowedTransitionSet = useMemo( () => {
192
194
  const set = new Set< string >();
193
195
  transitionProperties.forEach( ( category ) => {
194
- category.properties.forEach( ( prop ) => set.add( prop.value ) );
196
+ category.properties.forEach( ( prop ) => {
197
+ if ( ! prop.isDisabled || proInstalled ) {
198
+ set.add( prop.value );
199
+ }
200
+ } );
195
201
  } );
196
202
  return set;
197
- }, [] );
203
+ }, [ proInstalled ] );
198
204
 
199
205
  useEffect( () => {
200
206
  if ( ! value || value.length === 0 ) {
@@ -9,6 +9,7 @@ import { __ } from '@wordpress/i18n';
9
9
  import { useBoundProp } from '../../bound-prop-context';
10
10
  import { ItemSelector } from '../../components/item-selector';
11
11
  import ControlActions from '../../control-actions/control-actions';
12
+ import { trackUpgradePromotionClick } from '../../utils/tracking';
12
13
  import { transitionProperties, transitionsItemsList } from './data';
13
14
 
14
15
  const toTransitionSelectorValue = ( label: string ) => {
@@ -170,6 +171,12 @@ export const TransitionSelector = ( {
170
171
  'elementor'
171
172
  ) }
172
173
  upgradeUrl={ PRO_UPGRADE_URL }
174
+ onCtaClick={ () =>
175
+ trackUpgradePromotionClick( {
176
+ target_name: 'transition_property',
177
+ location_l2: 'style',
178
+ } )
179
+ }
173
180
  />
174
181
  ) : null
175
182
  }
@@ -1,52 +1,49 @@
1
1
  import { useMemo } from 'react';
2
- import { getElementorConfig, type SupportedFonts } from '@elementor/editor-v1-adapters';
3
- import { __ } from '@wordpress/i18n';
2
+ import { getElementorConfig } from '@elementor/editor-v1-adapters';
4
3
 
5
4
  import { type FontCategory } from '../controls/font-family-control/font-family-control';
6
5
 
7
- const supportedCategories: Record< SupportedFonts, string > = {
8
- system: __( 'System', 'elementor' ),
9
- custom: __( 'Custom Fonts', 'elementor' ),
10
- googlefonts: __( 'Google Fonts', 'elementor' ),
6
+ type FontControlConfig = {
7
+ groups?: Record< string, string >;
8
+ options?: Record< string, string >;
11
9
  };
12
10
 
13
- const getFontFamilies = () => {
11
+ const getFontControlConfig = (): FontControlConfig => {
14
12
  const { controls } = getElementorConfig();
15
13
 
16
- const options = controls?.font?.options;
17
-
18
- if ( ! options ) {
19
- return null;
20
- }
21
-
22
- return options;
14
+ return controls?.font ?? {};
23
15
  };
24
16
 
25
17
  export const useFontFamilies = () => {
26
- const fontFamilies = getFontFamilies();
18
+ const { groups, options } = getFontControlConfig();
27
19
 
28
20
  return useMemo( () => {
29
- const categoriesOrder: SupportedFonts[] = [ 'system', 'custom', 'googlefonts' ];
21
+ if ( ! groups || ! options ) {
22
+ return [];
23
+ }
24
+
25
+ const groupKeys = Object.keys( groups );
26
+ const groupIndexMap = new Map( groupKeys.map( ( key, index ) => [ key, index ] ) );
30
27
 
31
- return Object.entries( fontFamilies || {} )
28
+ return Object.entries( options )
32
29
  .reduce< FontCategory[] >( ( acc, [ font, category ] ) => {
33
- if ( ! supportedCategories[ category as SupportedFonts ] ) {
30
+ const groupIndex = groupIndexMap.get( category );
31
+
32
+ if ( groupIndex === undefined ) {
34
33
  return acc;
35
34
  }
36
35
 
37
- const categoryIndex = categoriesOrder.indexOf( category );
38
-
39
- if ( ! acc[ categoryIndex ] ) {
40
- acc[ categoryIndex ] = {
41
- label: supportedCategories[ category as SupportedFonts ],
36
+ if ( ! acc[ groupIndex ] ) {
37
+ acc[ groupIndex ] = {
38
+ label: groups[ category ],
42
39
  fonts: [],
43
40
  };
44
41
  }
45
42
 
46
- acc[ categoryIndex ].fonts.push( font );
43
+ acc[ groupIndex ].fonts.push( font );
47
44
 
48
45
  return acc;
49
46
  }, [] )
50
47
  .filter( Boolean );
51
- }, [ fontFamilies ] );
48
+ }, [ groups, options ] );
52
49
  };
package/src/index.ts CHANGED
@@ -38,6 +38,7 @@ export { transitionProperties, transitionsItemsList } from './controls/transitio
38
38
  export { DateTimeControl } from './controls/date-time-control';
39
39
  export { InlineEditingControl } from './controls/inline-editing-control';
40
40
  export { EmailFormActionControl } from './controls/email-form-action-control';
41
+ export { UnstableSizeControl } from './controls/size-control/unstable-size-control';
41
42
 
42
43
  // components
43
44
  export { ControlFormLabel } from './components/control-form-label';
@@ -58,6 +59,7 @@ export { InlineEditor } from './components/inline-editor';
58
59
  export { InlineEditorToolbar } from './components/inline-editor-toolbar';
59
60
  export { UnstableSizeField } from './components/size/unstable-size-field';
60
61
  export { NumberInput } from './components/number-input';
62
+ export { SizeComponent } from './controls/size-control/size-component';
61
63
 
62
64
  // types
63
65
  export type { ControlComponent } from './create-control';
@@ -80,6 +82,8 @@ export type { FontCategory } from './controls/font-family-control/font-family-co
80
82
  export type { InlineEditorToolbarProps } from './components/inline-editor-toolbar';
81
83
  export type { V4PromotionData, V4PromotionKey } from './components/promotions/types';
82
84
  export type { PromotionTriggerRef } from './components/promotions/promotion-trigger';
85
+ export { trackViewPromotion, trackUpgradePromotionClick } from './utils/tracking';
86
+ export type { PromotionTrackingData } from './utils/tracking';
83
87
 
84
88
  // providers
85
89
  export {