@elementor/editor-controls 4.2.0-beta1 → 4.2.3

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": "4.2.0-beta1",
4
+ "version": "4.2.3",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -40,23 +40,23 @@
40
40
  "dev": "tsup --config=../../tsup.dev.ts"
41
41
  },
42
42
  "dependencies": {
43
- "@elementor/editor-current-user": "4.2.0-beta1",
44
- "@elementor/editor-elements": "4.2.0-beta1",
45
- "@elementor/editor-props": "4.2.0-beta1",
46
- "@elementor/editor-responsive": "4.2.0-beta1",
47
- "@elementor/editor-ui": "4.2.0-beta1",
48
- "@elementor/editor-v1-adapters": "4.2.0-beta1",
49
- "@elementor/env": "4.2.0-beta1",
50
- "@elementor/events": "4.2.0-beta1",
51
- "@elementor/http-client": "4.2.0-beta1",
43
+ "@elementor/editor-current-user": "4.2.3",
44
+ "@elementor/editor-elements": "4.2.3",
45
+ "@elementor/editor-props": "4.2.3",
46
+ "@elementor/editor-responsive": "4.2.3",
47
+ "@elementor/editor-ui": "4.2.3",
48
+ "@elementor/editor-v1-adapters": "4.2.3",
49
+ "@elementor/env": "4.2.3",
50
+ "@elementor/events": "4.2.3",
51
+ "@elementor/http-client": "4.2.3",
52
52
  "@elementor/icons": "~1.75.1",
53
- "@elementor/locations": "4.2.0-beta1",
54
- "@elementor/query": "4.2.0-beta1",
55
- "@elementor/schema": "4.2.0-beta1",
56
- "@elementor/session": "4.2.0-beta1",
53
+ "@elementor/locations": "4.2.3",
54
+ "@elementor/query": "4.2.3",
55
+ "@elementor/schema": "4.2.3",
56
+ "@elementor/session": "4.2.3",
57
57
  "@elementor/ui": "1.37.5",
58
- "@elementor/utils": "4.2.0-beta1",
59
- "@elementor/wp-media": "4.2.0-beta1",
58
+ "@elementor/utils": "4.2.3",
59
+ "@elementor/wp-media": "4.2.3",
60
60
  "@monaco-editor/react": "^4.7.0",
61
61
  "@tiptap/extension-bold": "^3.11.1",
62
62
  "@tiptap/extension-document": "^3.11.1",
@@ -4,19 +4,19 @@ import { Box, InputAdornment, type PopupState } from '@elementor/ui';
4
4
 
5
5
  import ControlActions from '../../control-actions/control-actions';
6
6
  import { useTypingBuffer } from '../../hooks/use-typing-buffer';
7
- import { type ExtendedOption, isUnitExtendedOption, type Unit } from '../../utils/size-control';
7
+ import { isUnitExtendedOption, type SizeUnit } from '../../utils/size-control';
8
8
  import { SelectionEndAdornment, TextFieldInnerSelection } from '../size-control/text-field-inner-selection';
9
9
 
10
10
  type SizeInputProps = {
11
- unit: Unit | ExtendedOption;
11
+ unit: SizeUnit;
12
12
  size: number | string;
13
13
  placeholder?: string;
14
14
  startIcon?: React.ReactNode;
15
- units: ( Unit | ExtendedOption )[];
15
+ units: SizeUnit[];
16
16
  onBlur?: ( event: React.FocusEvent< HTMLInputElement > ) => void;
17
17
  onFocus?: ( event: React.FocusEvent< HTMLInputElement > ) => void;
18
18
  onClick?: ( event: React.MouseEvent< HTMLInputElement > ) => void;
19
- handleUnitChange: ( unit: Unit | ExtendedOption ) => void;
19
+ handleUnitChange: ( unit: SizeUnit ) => void;
20
20
  handleSizeChange: ( event: React.ChangeEvent< HTMLInputElement > ) => void;
21
21
  popupState: PopupState;
22
22
  disabled?: boolean;
@@ -1,21 +1,35 @@
1
1
  import * as React from 'react';
2
- import { type KeyboardEvent, type SyntheticEvent, useState } from 'react';
2
+ import { type KeyboardEvent, type SyntheticEvent, useMemo, useState } from 'react';
3
3
  import { stringArrayPropTypeUtil, stringPropTypeUtil } from '@elementor/editor-props';
4
4
  import { Autocomplete, Grid, TextField } from '@elementor/ui';
5
5
 
6
6
  import { useBoundProp } from '../../bound-prop-context';
7
7
  import { ChipsList } from '../../components/chips-list';
8
8
  import { ControlFormLabel } from '../../components/control-form-label';
9
- import { CHIP_TRIGGER_KEYS, isValidEmail } from './utils';
9
+ import ControlActions from '../../control-actions/control-actions';
10
+ import { createControl } from '../../create-control';
11
+ import { type Suggestion } from '../../hooks/use-form-field-suggestions';
12
+ import { createMentionPattern } from '../mention-text-area-control';
13
+ import { CHIP_TRIGGER_KEYS, isFormFieldShortcode, isValidEmail } from './utils';
10
14
 
11
- // type EmailChip = { label: string; value: string };
15
+ const isValidRecipient = ( address: string ) => isValidEmail( address ) || isFormFieldShortcode( address );
12
16
 
13
- type EmailChipsFieldProps = {
14
- fieldLabel: string;
17
+ function resolveMention( raw: string, suggestions: Suggestion[] ): string {
18
+ if ( ! raw.startsWith( '@' ) ) {
19
+ return raw;
20
+ }
21
+
22
+ const match = suggestions.find( ( suggestion ) => createMentionPattern( suggestion.value, 'start' ).test( raw ) );
23
+
24
+ return match ? `[${ match.value }]` : raw;
25
+ }
26
+
27
+ type EmailChipsControlProps = {
15
28
  placeholder?: string;
29
+ suggestions?: Suggestion[];
16
30
  };
17
31
 
18
- export const EmailChipsField = ( { fieldLabel, placeholder }: EmailChipsFieldProps ) => {
32
+ export const EmailChipsControl = createControl( ( { placeholder, suggestions = [] }: EmailChipsControlProps ) => {
19
33
  const { value, setValue, disabled } = useBoundProp( stringArrayPropTypeUtil );
20
34
  const [ inputValue, setInputValue ] = useState( '' );
21
35
 
@@ -25,10 +39,15 @@ export const EmailChipsField = ( { fieldLabel, placeholder }: EmailChipsFieldPro
25
39
  .map( ( item ) => stringPropTypeUtil.extract( item ) )
26
40
  .filter( ( val ): val is string => val !== null );
27
41
 
42
+ const suggestionOptions = useMemo(
43
+ () => suggestions.map( ( suggestion ) => `[${ suggestion.value }]` ),
44
+ [ suggestions ]
45
+ );
46
+
28
47
  const tryAddChip = ( raw: string ) => {
29
- const address = raw.trim();
48
+ const address = resolveMention( raw.trim(), suggestions );
30
49
 
31
- if ( ! address || selectedValues.includes( address ) || ! isValidEmail( address ) ) {
50
+ if ( ! address || selectedValues.includes( address ) || ! isValidRecipient( address ) ) {
32
51
  return;
33
52
  }
34
53
 
@@ -40,9 +59,9 @@ export const EmailChipsField = ( { fieldLabel, placeholder }: EmailChipsFieldPro
40
59
  const updated = [];
41
60
 
42
61
  for ( const entry of newValue ) {
43
- const address = entry.trim();
62
+ const address = resolveMention( entry.trim(), suggestions );
44
63
 
45
- if ( ! address || ! isValidEmail( address ) ) {
64
+ if ( ! address || ! isValidRecipient( address ) ) {
46
65
  continue;
47
66
  }
48
67
 
@@ -68,37 +87,55 @@ export const EmailChipsField = ( { fieldLabel, placeholder }: EmailChipsFieldPro
68
87
  };
69
88
 
70
89
  return (
71
- <Grid container direction="column" gap={ 0.5 }>
72
- <Grid item>
73
- <ControlFormLabel>{ fieldLabel }</ControlFormLabel>
74
- </Grid>
75
- <Grid item>
76
- <Autocomplete
77
- fullWidth
78
- multiple
79
- freeSolo
80
- size="tiny"
81
- disabled={ disabled }
82
- inputValue={ inputValue }
83
- onInputChange={ ( _, val, reason ) => {
84
- if ( reason !== 'reset' ) {
85
- setInputValue( val );
86
- }
87
- } }
88
- value={ selectedValues }
89
- onChange={ handleChange }
90
- options={ [] }
91
- onBlur={ handleBlur }
92
- getOptionLabel={ ( option ) => option }
93
- isOptionEqualToValue={ ( option, val ) => option === val }
94
- renderInput={ ( params ) => (
95
- <TextField { ...params } placeholder={ placeholder } onKeyDown={ handleKeyDown } />
96
- ) }
97
- renderTags={ ( tagValues, getTagProps ) => (
98
- <ChipsList getLabel={ ( option ) => option } getTagProps={ getTagProps } values={ tagValues } />
99
- ) }
100
- />
101
- </Grid>
102
- </Grid>
90
+ <ControlActions>
91
+ <Autocomplete
92
+ fullWidth
93
+ multiple
94
+ freeSolo
95
+ size="tiny"
96
+ disabled={ disabled }
97
+ inputValue={ inputValue }
98
+ onInputChange={ ( _, val, reason ) => {
99
+ if ( reason !== 'reset' ) {
100
+ setInputValue( val );
101
+ }
102
+ } }
103
+ value={ selectedValues }
104
+ onChange={ handleChange }
105
+ options={ suggestionOptions }
106
+ filterOptions={ ( options, state ) => {
107
+ const query = state.inputValue.trim().replace( /^@/, '' ).toLowerCase();
108
+
109
+ return query ? options.filter( ( option ) => option.toLowerCase().includes( query ) ) : options;
110
+ } }
111
+ filterSelectedOptions
112
+ onBlur={ handleBlur }
113
+ getOptionLabel={ ( option ) => option }
114
+ isOptionEqualToValue={ ( option, val ) => option === val }
115
+ renderInput={ ( params ) => (
116
+ <TextField { ...params } placeholder={ placeholder } onKeyDown={ handleKeyDown } />
117
+ ) }
118
+ renderTags={ ( tagValues, getTagProps ) => (
119
+ <ChipsList getLabel={ ( option ) => option } getTagProps={ getTagProps } values={ tagValues } />
120
+ ) }
121
+ />
122
+ </ControlActions>
103
123
  );
124
+ } );
125
+
126
+ type EmailChipsFieldProps = {
127
+ fieldLabel: string;
128
+ placeholder?: string;
129
+ suggestions?: Suggestion[];
104
130
  };
131
+
132
+ export const EmailChipsField = ( { fieldLabel, placeholder, suggestions }: EmailChipsFieldProps ) => (
133
+ <Grid container direction="column" gap={ 0.5 }>
134
+ <Grid item>
135
+ <ControlFormLabel>{ fieldLabel }</ControlFormLabel>
136
+ </Grid>
137
+ <Grid item>
138
+ <EmailChipsControl placeholder={ placeholder } suggestions={ suggestions } />
139
+ </Grid>
140
+ </Grid>
141
+ );
@@ -13,9 +13,26 @@ import { EmailChipsField } from './email-chips-field';
13
13
  import { EmailField } from './email-field';
14
14
  import { shouldShowMentionsInfo } from './utils';
15
15
 
16
- export const SendToField = ( { placeholder }: { placeholder?: string } ) => (
17
- <EmailChipsField fieldLabel={ __( 'Send to', 'elementor' ) } placeholder={ placeholder } />
18
- );
16
+ export const SendToField = ( { placeholder }: { placeholder?: string } ) => {
17
+ const suggestions = useFormFieldSuggestions( { inputType: 'email' } );
18
+
19
+ return (
20
+ <PropKeyProvider bind="to">
21
+ <Stack gap={ 0.5 }>
22
+ <EmailChipsField
23
+ fieldLabel={ __( 'Send to', 'elementor' ) }
24
+ placeholder={ placeholder }
25
+ suggestions={ suggestions }
26
+ />
27
+ { shouldShowMentionsInfo() && (
28
+ <InfoAlert>
29
+ { __( 'Type @ or an email field name to insert its submitted value.', 'elementor' ) }
30
+ </InfoAlert>
31
+ ) }
32
+ </Stack>
33
+ </PropKeyProvider>
34
+ );
35
+ };
19
36
 
20
37
  export const SubjectField = () => (
21
38
  <EmailField
@@ -90,17 +107,25 @@ export const ReplyToField = () => {
90
107
  );
91
108
  };
92
109
 
93
- export const CcField = () => (
94
- <PropKeyProvider bind="cc">
95
- <EmailChipsField fieldLabel={ __( 'Cc', 'elementor' ) } />
96
- </PropKeyProvider>
97
- );
110
+ export const CcField = () => {
111
+ const suggestions = useFormFieldSuggestions( { inputType: 'email' } );
98
112
 
99
- export const BccField = () => (
100
- <PropKeyProvider bind="bcc">
101
- <EmailChipsField fieldLabel={ __( 'Bcc', 'elementor' ) } />
102
- </PropKeyProvider>
103
- );
113
+ return (
114
+ <PropKeyProvider bind="cc">
115
+ <EmailChipsField fieldLabel={ __( 'Cc', 'elementor' ) } suggestions={ suggestions } />
116
+ </PropKeyProvider>
117
+ );
118
+ };
119
+
120
+ export const BccField = () => {
121
+ const suggestions = useFormFieldSuggestions( { inputType: 'email' } );
122
+
123
+ return (
124
+ <PropKeyProvider bind="bcc">
125
+ <EmailChipsField fieldLabel={ __( 'Bcc', 'elementor' ) } suggestions={ suggestions } />
126
+ </PropKeyProvider>
127
+ );
128
+ };
104
129
 
105
130
  export const MetaDataField = () => (
106
131
  <PropKeyProvider bind="meta-data">
@@ -4,7 +4,7 @@ import { CollapsibleContent } from '@elementor/editor-ui';
4
4
  import { Box, Divider, Stack } from '@elementor/ui';
5
5
  import { __ } from '@wordpress/i18n';
6
6
 
7
- import { PropKeyProvider, PropProvider, useBoundProp } from '../../bound-prop-context';
7
+ import { PropProvider, useBoundProp } from '../../bound-prop-context';
8
8
  import { ControlLabel } from '../../components/control-label';
9
9
  import { createControl } from '../../create-control';
10
10
  import {
@@ -30,9 +30,7 @@ export const EmailFormActionControl = createControl(
30
30
  <ControlLabel>
31
31
  { label ? label + ' ' + __( 'settings', 'elementor' ) : __( 'Email settings', 'elementor' ) }
32
32
  </ControlLabel>
33
- <PropKeyProvider bind="to">
34
- <SendToField placeholder={ toPlaceholder } />
35
- </PropKeyProvider>
33
+ <SendToField placeholder={ toPlaceholder } />
36
34
  <SubjectField />
37
35
  <MessageField />
38
36
  <FromEmailField />
@@ -9,6 +9,12 @@ export function isValidEmail( email: string ): boolean {
9
9
  return z.string().email().safeParse( email ).success;
10
10
  }
11
11
 
12
+ const FORM_FIELD_SHORTCODE_PATTERN = /^\[[^[\]]+]$/;
13
+
14
+ export function isFormFieldShortcode( value: string ): boolean {
15
+ return FORM_FIELD_SHORTCODE_PATTERN.test( value );
16
+ }
17
+
12
18
  export const shouldShowMentionsInfo = (): boolean => {
13
19
  if ( ! hasProInstalled() ) {
14
20
  return true;
@@ -94,7 +94,9 @@ export const GapControl = ( { label }: { label: string } ) => {
94
94
 
95
95
  const propProviderProps = {
96
96
  propType,
97
- value: directionValue ?? ( ! isLinked ? { row: masterPlaceholder, column: masterPlaceholder } : null ),
97
+ value:
98
+ directionValue ??
99
+ ( ! isLinked ? { row: directionPlaceholder?.row, column: directionPlaceholder?.column } : null ),
98
100
  setValue: ( directions: PropValue ) => {
99
101
  const entries = Object.entries( directions as LayoutDirectionPropValue );
100
102
  const filtered = entries.filter( ( [ , value ] ) => Boolean( value ) );
@@ -81,9 +81,9 @@ const MentionWrapper = styled( 'div' )( ( { theme } ) => ( {
81
81
  },
82
82
  } ) );
83
83
 
84
- type TriggerPosition = 'start' | 'auto';
84
+ export type TriggerPosition = 'start' | 'auto';
85
85
 
86
- function createMentionPattern( value: string, triggerPosition: TriggerPosition ): RegExp {
86
+ export function createMentionPattern( value: string, triggerPosition: TriggerPosition ): RegExp {
87
87
  const escaped = value.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
88
88
  const prefix = 'start' === triggerPosition ? '^' : '';
89
89
 
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { stringPropTypeUtil, type StringPropValue } from '@elementor/editor-props';
3
3
  import { MenuListItem } from '@elementor/editor-ui';
4
- import { Select, type SelectChangeEvent, type SelectProps, Typography } from '@elementor/ui';
4
+ import { MenuSubheader, Select, type SelectChangeEvent, type SelectProps, Typography } from '@elementor/ui';
5
5
 
6
6
  import { useBoundProp } from '../bound-prop-context';
7
7
  import ControlActions from '../control-actions/control-actions';
@@ -13,8 +13,14 @@ export type SelectOption = {
13
13
  disabled?: boolean;
14
14
  };
15
15
 
16
- type SelectControlProps = {
16
+ export type SelectOptionGroup = {
17
+ label: string;
17
18
  options: SelectOption[];
19
+ };
20
+
21
+ type SelectControlProps = {
22
+ options?: SelectOption[];
23
+ groups?: SelectOptionGroup[];
18
24
  onChange?: ( newValue: string | null, previousValue: string | null | undefined ) => void;
19
25
  MenuProps?: SelectProps[ 'MenuProps' ];
20
26
  ariaLabel?: string;
@@ -29,7 +35,7 @@ const DEFAULT_MENU_PROPS = {
29
35
  };
30
36
 
31
37
  export const SelectControl = createControl(
32
- ( { options, onChange, MenuProps = DEFAULT_MENU_PROPS, ariaLabel }: SelectControlProps ) => {
38
+ ( { options = [], groups = [], onChange, MenuProps = DEFAULT_MENU_PROPS, ariaLabel }: SelectControlProps ) => {
33
39
  const { value, setValue, disabled, placeholder } = useBoundProp( stringPropTypeUtil );
34
40
  const handleChange = ( event: SelectChangeEvent< StringPropValue[ 'value' ] > ) => {
35
41
  const newValue = event.target.value || null;
@@ -37,7 +43,8 @@ export const SelectControl = createControl(
37
43
  onChange?.( newValue, value );
38
44
  setValue( newValue );
39
45
  };
40
- const isDisabled = disabled || options.length === 0;
46
+ const flatOptions = flattenGroupedOptions( options, groups );
47
+ const isDisabled = disabled || flatOptions.length === 0;
41
48
 
42
49
  return (
43
50
  <ControlActions>
@@ -48,24 +55,54 @@ export const SelectControl = createControl(
48
55
  MenuProps={ MenuProps }
49
56
  aria-label={ ariaLabel || placeholder }
50
57
  renderValue={ ( selectedValue: string | null ) =>
51
- getSelectRenderValue( options, placeholder, selectedValue )
58
+ getSelectRenderValue( flatOptions, placeholder, selectedValue )
52
59
  }
53
60
  value={ value ?? '' }
54
61
  onChange={ handleChange }
55
62
  disabled={ isDisabled }
56
63
  fullWidth
57
64
  >
58
- { options.map( ( { label, ...props } ) => (
59
- <MenuListItem key={ props.value } { ...props } value={ props.value ?? '' }>
60
- { label }
61
- </MenuListItem>
62
- ) ) }
65
+ { groups.length
66
+ ? groups.flatMap( ( group ) => renderGroup( group ) )
67
+ : options.map( ( option ) => renderOption( option ) ) }
63
68
  </Select>
64
69
  </ControlActions>
65
70
  );
66
71
  }
67
72
  );
68
73
 
74
+ const GROUPED_OPTION_INDENT = 3.5;
75
+
76
+ function renderGroup( group: SelectOptionGroup ): React.ReactNode[] {
77
+ return [
78
+ <MenuSubheader key={ `group-${ group.label }` } sx={ { fontWeight: 400, color: 'text.tertiary' } }>
79
+ { group.label }
80
+ </MenuSubheader>,
81
+ ...group.options.map( ( option ) => renderOption( option, true ) ),
82
+ ];
83
+ }
84
+
85
+ function renderOption( { label, ...props }: SelectOption, isGrouped = false ): React.ReactNode {
86
+ return (
87
+ <MenuListItem
88
+ key={ props.value }
89
+ { ...props }
90
+ value={ props.value ?? '' }
91
+ sx={ isGrouped ? { pl: GROUPED_OPTION_INDENT } : undefined }
92
+ >
93
+ { label }
94
+ </MenuListItem>
95
+ );
96
+ }
97
+
98
+ function flattenGroupedOptions( options: SelectOption[], groups: SelectOptionGroup[] ): SelectOption[] {
99
+ if ( ! groups.length ) {
100
+ return options;
101
+ }
102
+
103
+ return groups.flatMap( ( group ) => group.options );
104
+ }
105
+
69
106
  export function getSelectRenderValue(
70
107
  options: SelectOption[],
71
108
  placeholder: string | null | undefined,
@@ -1,17 +1,10 @@
1
1
  import type { CreateOptions } from '@elementor/editor-props';
2
2
 
3
3
  import { type SetValueMeta } from '../../bound-prop-context';
4
+ import { type ExtendedOption } from '../../utils/size-control';
4
5
 
5
- type LengthUnit = 'px' | '%' | 'em' | 'rem' | 'vw' | 'vh' | 'ch';
6
- type AngleUnit = 'deg' | 'rad' | 'grad' | 'turn';
7
- type TimeUnit = 's' | 'ms';
8
-
9
- type Unit = LengthUnit | AngleUnit | TimeUnit;
10
-
11
- export type ExtendedSizeOption = 'auto' | 'custom';
12
-
13
- export type SizeUnit = Unit | ExtendedSizeOption;
14
-
6
+ export type ExtendedSizeOption = ExtendedOption;
7
+ export type { SizeUnit } from '../../utils/size-control';
15
8
  export type SizeVariant = 'length' | 'angle' | 'time';
16
9
 
17
10
  export type SetSizeValue< T > = ( value: T, options?: CreateOptions, meta?: SetValueMeta ) => void;
@@ -19,10 +19,12 @@ import {
19
19
  isUnitExtendedOption,
20
20
  type LengthUnit,
21
21
  lengthUnits,
22
+ type SizeUnit,
22
23
  type TimeUnit,
23
24
  timeUnits,
24
25
  type Unit,
25
26
  } from '../utils/size-control';
27
+ import { getPropTypeSettings } from './size-control/utils/settings/get-prop-type-settings';
26
28
 
27
29
  type SizeValue = SizePropValue[ 'value' ];
28
30
 
@@ -65,7 +67,7 @@ export type SizeControlProps = LengthSizeControlProps | AngleSizeControlProps |
65
67
  type State = {
66
68
  numeric: number;
67
69
  custom: string;
68
- unit: Unit | ExtendedOption;
70
+ unit: SizeUnit;
69
71
  };
70
72
 
71
73
  const defaultSelectedUnit: Record< SizeControlProps[ 'variant' ], Unit > = {
@@ -133,7 +135,7 @@ export const SizeControl = createControl(
133
135
  const { size: controlSize = DEFAULT_SIZE, unit: controlUnit = actualDefaultUnit } =
134
136
  extractValueFromState( state, true ) || {};
135
137
 
136
- const handleUnitChange = ( newUnit: Unit | ExtendedOption ) => {
138
+ const handleUnitChange = ( newUnit: SizeUnit ) => {
137
139
  if ( newUnit === 'custom' ) {
138
140
  popupState.open( anchorRef?.current );
139
141
  }
@@ -217,17 +219,17 @@ function resolveUnits(
217
219
  variant: SizeVariant,
218
220
  externalUnits?: Unit[],
219
221
  actualExtendedOptions?: ExtendedOption[]
220
- ) {
222
+ ): SizeUnit[] {
221
223
  const fallback = [ ...defaultUnits[ variant ] ];
222
224
 
223
225
  if ( ! enablePropTypeUnits ) {
224
226
  return [ ...( externalUnits ?? fallback ), ...( actualExtendedOptions || [] ) ];
225
227
  }
226
228
 
227
- return ( propType.settings?.available_units as Unit[] ) ?? fallback;
229
+ return getPropTypeSettings( propType )?.available_units ?? fallback;
228
230
  }
229
231
 
230
- function formatSize< TSize extends string | number >( size: TSize, unit: Unit | ExtendedOption ): TSize {
232
+ function formatSize< TSize extends string | number >( size: TSize, unit: SizeUnit ): TSize {
231
233
  if ( isUnitExtendedOption( unit ) ) {
232
234
  return unit === 'auto' ? ( '' as TSize ) : ( String( size ?? '' ) as TSize );
233
235
  }
@@ -237,7 +239,7 @@ function formatSize< TSize extends string | number >( size: TSize, unit: Unit |
237
239
 
238
240
  function createStateFromSizeProp(
239
241
  sizeValue: SizeValue | null,
240
- defaultUnit: Unit | ExtendedOption,
242
+ defaultUnit: SizeUnit,
241
243
  defaultSize: string | number = '',
242
244
  customState: string = ''
243
245
  ): State {
@@ -1,18 +1,23 @@
1
- export const lengthUnits = [ 'px', '%', 'em', 'rem', 'vw', 'vh', 'ch' ] as const;
2
- export const angleUnits = [ 'deg', 'rad', 'grad', 'turn' ] as const;
3
- export const timeUnits = [ 's', 'ms' ] as const;
4
- const defaultExtendedOptions = [ 'auto', 'custom' ] as const;
1
+ import type { SizePropValue } from '@elementor/editor-props';
5
2
 
6
- export const DEFAULT_UNIT = 'px';
3
+ export type SizeUnit = SizePropValue[ 'value' ][ 'unit' ];
4
+
5
+ export type ExtendedOption = Extract< SizeUnit, 'auto' | 'custom' >;
6
+ export type Unit = Exclude< SizeUnit, ExtendedOption >;
7
+
8
+ export const lengthUnits = [ 'px', '%', 'em', 'rem', 'vw', 'vh', 'ch' ] as const satisfies readonly Unit[];
9
+ export const angleUnits = [ 'deg', 'rad', 'grad', 'turn' ] as const satisfies readonly Unit[];
10
+ export const timeUnits = [ 's', 'ms' ] as const satisfies readonly Unit[];
11
+
12
+ export const DEFAULT_UNIT = 'px' satisfies Unit;
7
13
  export const DEFAULT_SIZE = NaN;
8
14
 
9
- export type LengthUnit = ( typeof lengthUnits )[ number ];
15
+ export type LengthUnit = Extract< Unit, ( typeof lengthUnits )[ number ] | 'fr' >;
10
16
  export type AngleUnit = ( typeof angleUnits )[ number ];
11
17
  export type TimeUnit = ( typeof timeUnits )[ number ];
12
- export type ExtendedOption = ( typeof defaultExtendedOptions )[ number ];
13
18
 
14
- export type Unit = LengthUnit | AngleUnit | TimeUnit;
19
+ const extendedOptions = [ 'auto', 'custom' ] as const satisfies readonly ExtendedOption[];
15
20
 
16
- export function isUnitExtendedOption( unit: Unit | ExtendedOption ): unit is ExtendedOption {
17
- return defaultExtendedOptions.includes( unit as ExtendedOption );
21
+ export function isUnitExtendedOption( unit: SizeUnit ): unit is ExtendedOption {
22
+ return extendedOptions.includes( unit as ExtendedOption );
18
23
  }