@elementor/editor-controls 3.33.0-98 → 3.35.0-324

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 (94) hide show
  1. package/dist/index.d.mts +276 -85
  2. package/dist/index.d.ts +276 -85
  3. package/dist/index.js +2491 -1783
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +2304 -1592
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +31 -17
  8. package/src/bound-prop-context/prop-context.tsx +7 -1
  9. package/src/bound-prop-context/use-bound-prop.ts +19 -5
  10. package/src/components/autocomplete.tsx +34 -3
  11. package/src/components/conditional-control-infotip.tsx +64 -0
  12. package/src/components/{unstable-repeater → control-repeater}/actions/disable-item-action.tsx +2 -2
  13. package/src/components/{unstable-repeater → control-repeater}/actions/duplicate-item-action.tsx +10 -4
  14. package/src/components/{unstable-repeater → control-repeater}/actions/remove-item-action.tsx +2 -2
  15. package/src/components/control-repeater/context/item-context.tsx +8 -0
  16. package/src/components/{unstable-repeater → control-repeater}/context/repeater-context.tsx +24 -15
  17. package/src/components/control-repeater/control-repeater.tsx +29 -0
  18. package/src/components/{unstable-repeater → control-repeater}/index.ts +1 -2
  19. package/src/components/{unstable-repeater → control-repeater}/items/edit-item-popover.tsx +6 -20
  20. package/src/components/control-repeater/items/item.tsx +75 -0
  21. package/src/components/{unstable-repeater → control-repeater}/items/items-container.tsx +8 -13
  22. package/src/components/{unstable-repeater → control-repeater}/locations.ts +0 -4
  23. package/src/components/{unstable-repeater → control-repeater}/types.ts +1 -2
  24. package/src/components/control-toggle-button-group.tsx +79 -69
  25. package/src/components/enable-unfiltered-modal.tsx +1 -26
  26. package/src/components/icon-buttons/clear-icon-button.tsx +23 -0
  27. package/src/components/inline-editor-toolbar.tsx +137 -0
  28. package/src/components/inline-editor.tsx +111 -0
  29. package/src/components/item-selector.tsx +10 -4
  30. package/src/components/{unstable-repeater/header/header.tsx → repeater/repeater-header.tsx} +4 -12
  31. package/src/components/repeater/repeater-popover.tsx +19 -0
  32. package/src/components/repeater/repeater-tag.tsx +16 -0
  33. package/src/components/repeater/repeater.tsx +405 -0
  34. package/src/components/{sortable.tsx → repeater/sortable.tsx} +1 -1
  35. package/src/components/size-control/size-input.tsx +20 -14
  36. package/src/components/size-control/text-field-inner-selection.tsx +15 -2
  37. package/src/control-adornments/control-adornments-context.tsx +5 -4
  38. package/src/control-replacements.tsx +12 -47
  39. package/src/controls/background-control/background-control.tsx +43 -12
  40. package/src/controls/background-control/background-gradient-color-control.tsx +5 -8
  41. package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-position.tsx +18 -13
  42. package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +25 -16
  43. package/src/controls/box-shadow-repeater-control.tsx +38 -21
  44. package/src/controls/color-control.tsx +3 -1
  45. package/src/controls/date-time-control.tsx +108 -0
  46. package/src/controls/filter-control/drop-shadow/drop-shadow-item-content.tsx +1 -0
  47. package/src/controls/filter-control/drop-shadow/drop-shadow-item-label.tsx +10 -6
  48. package/src/controls/filter-control/filter-content.tsx +1 -1
  49. package/src/controls/filter-control/filter-repeater-control.tsx +24 -21
  50. package/src/controls/filter-control/single-size/single-size-item-content.tsx +1 -1
  51. package/src/controls/filter-control/single-size/single-size-item-label.tsx +2 -1
  52. package/src/controls/font-family-control/font-family-control.tsx +66 -55
  53. package/src/controls/html-tag-control.tsx +90 -0
  54. package/src/controls/image-media-control.tsx +2 -2
  55. package/src/controls/inline-editing-control.tsx +18 -0
  56. package/src/controls/key-value-control.tsx +8 -2
  57. package/src/controls/link-control.tsx +23 -123
  58. package/src/controls/query-control.tsx +168 -0
  59. package/src/controls/repeatable-control.tsx +62 -27
  60. package/src/controls/select-control-wrapper.tsx +57 -0
  61. package/src/controls/select-control.tsx +9 -5
  62. package/src/controls/selection-size-control.tsx +13 -2
  63. package/src/controls/size-control.tsx +43 -25
  64. package/src/controls/svg-media-control.tsx +33 -10
  65. package/src/controls/text-area-control.tsx +5 -1
  66. package/src/controls/text-control.tsx +5 -0
  67. package/src/controls/toggle-control.tsx +11 -2
  68. package/src/controls/transform-control/functions/axis-row.tsx +1 -0
  69. package/src/controls/transform-control/transform-icon.tsx +2 -2
  70. package/src/controls/transform-control/transform-label.tsx +15 -32
  71. package/src/controls/transform-control/transform-repeater-control.tsx +42 -36
  72. package/src/controls/transform-control/{transform-base-control.tsx → transform-settings-control.tsx} +2 -2
  73. package/src/controls/transform-control/use-transform-tabs-history.tsx +1 -1
  74. package/src/controls/transition-control/data.ts +16 -1
  75. package/src/controls/transition-control/trainsition-events.ts +2 -2
  76. package/src/controls/transition-control/transition-repeater-control.tsx +137 -13
  77. package/src/controls/transition-control/transition-selector.tsx +37 -14
  78. package/src/controls/url-control.tsx +21 -16
  79. package/src/create-control.tsx +3 -2
  80. package/src/hooks/use-filtered-items-list.ts +3 -2
  81. package/src/hooks/use-repeatable-control-context.ts +3 -0
  82. package/src/hooks/use-sync-external-state.tsx +0 -1
  83. package/src/index.ts +21 -5
  84. package/src/utils/convert-toggle-options-to-atomic.tsx +33 -0
  85. package/src/utils/escape-html-attr.ts +11 -0
  86. package/src/components/css-code-editor/css-editor.styles.ts +0 -52
  87. package/src/components/css-code-editor/css-editor.tsx +0 -142
  88. package/src/components/css-code-editor/css-validation.ts +0 -75
  89. package/src/components/css-code-editor/resize-handle.tsx +0 -55
  90. package/src/components/css-code-editor/visual-content-change-protection.ts +0 -69
  91. package/src/components/repeater.tsx +0 -343
  92. package/src/components/unstable-repeater/items/item.tsx +0 -77
  93. package/src/components/unstable-repeater/unstable-repeater.tsx +0 -26
  94. /package/src/components/{unstable-repeater → control-repeater}/actions/tooltip-add-item-action.tsx +0 -0
@@ -1,26 +1,27 @@
1
1
  import * as React from 'react';
2
2
  import { useMemo } from 'react';
3
- import { createArrayPropUtils } from '@elementor/editor-props';
3
+ import { createArrayPropUtils, type SizePropValue } from '@elementor/editor-props';
4
4
  import { Box } from '@elementor/ui';
5
5
 
6
6
  import { PropProvider, useBoundProp } from '../bound-prop-context';
7
+ import { ControlRepeater, Item, TooltipAddItemAction } from '../components/control-repeater';
8
+ import { DisableItemAction } from '../components/control-repeater/actions/disable-item-action';
9
+ import { DuplicateItemAction } from '../components/control-repeater/actions/duplicate-item-action';
10
+ import { RemoveItemAction } from '../components/control-repeater/actions/remove-item-action';
11
+ import { type TooltipAddItemActionProps } from '../components/control-repeater/actions/tooltip-add-item-action';
12
+ import { EditItemPopover } from '../components/control-repeater/items/edit-item-popover';
13
+ import { ItemsContainer } from '../components/control-repeater/items/items-container';
14
+ import { type CollectionPropUtil, type RepeatablePropValue } from '../components/control-repeater/types';
7
15
  import { PopoverContent } from '../components/popover-content';
8
16
  import { PopoverGridContainer } from '../components/popover-grid-container';
9
- import { type CollectionPropUtil } from '../components/repeater';
10
- import { Header, Item, TooltipAddItemAction, UnstableRepeater } from '../components/unstable-repeater';
11
- import { DisableItemAction } from '../components/unstable-repeater/actions/disable-item-action';
12
- import { DuplicateItemAction } from '../components/unstable-repeater/actions/duplicate-item-action';
13
- import { RemoveItemAction } from '../components/unstable-repeater/actions/remove-item-action';
14
- import { type TooltipAddItemActionProps } from '../components/unstable-repeater/actions/tooltip-add-item-action';
15
- import { EditItemPopover } from '../components/unstable-repeater/items/edit-item-popover';
16
- import { ItemsContainer } from '../components/unstable-repeater/items/items-container';
17
- import { type RepeatablePropValue } from '../components/unstable-repeater/types';
17
+ import { RepeaterHeader } from '../components/repeater/repeater-header';
18
18
  import { createControl } from '../create-control';
19
19
  import {
20
20
  type ChildControlConfig,
21
21
  RepeatableControlContext,
22
22
  useRepeatableControlContext,
23
23
  } from '../hooks/use-repeatable-control-context';
24
+ import { CUSTOM_SIZE_LABEL } from './size-control';
24
25
 
25
26
  type RepeatableControlProps = {
26
27
  label: string;
@@ -49,7 +50,7 @@ export const RepeatableControl = createControl(
49
50
  propKey,
50
51
  addItemTooltipProps,
51
52
  }: RepeatableControlProps ) => {
52
- const { propTypeUtil: childPropTypeUtil } = childControlConfig;
53
+ const { propTypeUtil: childPropTypeUtil, isItemDisabled } = childControlConfig;
53
54
 
54
55
  if ( ! childPropTypeUtil ) {
55
56
  return null;
@@ -74,29 +75,35 @@ export const RepeatableControl = createControl(
74
75
  return (
75
76
  <PropProvider propType={ propType } value={ value } setValue={ setValue }>
76
77
  <RepeatableControlContext.Provider value={ contextValue }>
77
- <UnstableRepeater
78
+ <ControlRepeater
78
79
  initial={ childPropTypeUtil.create( initialValues || null ) }
79
80
  propTypeUtil={ childArrayPropTypeUtil as CollectionPropUtil< RepeatablePropValue > }
81
+ isItemDisabled={ isItemDisabled }
80
82
  >
81
- <Header label={ repeaterLabel }>
83
+ <RepeaterHeader label={ repeaterLabel }>
82
84
  <TooltipAddItemAction
83
85
  { ...addItemTooltipProps }
84
86
  newItemIndex={ 0 }
85
87
  ariaLabel={ repeaterLabel }
86
88
  />
87
- </Header>
88
- <ItemsContainer
89
- isSortable={ false }
90
- itemTemplate={ <Item Icon={ ItemIcon } Label={ ItemLabel } /> }
91
- >
92
- { showDuplicate && <DuplicateItemAction /> }
93
- { showToggle && <DisableItemAction /> }
94
- <RemoveItemAction />
89
+ </RepeaterHeader>
90
+ <ItemsContainer isSortable={ false }>
91
+ <Item
92
+ Icon={ ItemIcon }
93
+ Label={ ItemLabel }
94
+ actions={
95
+ <>
96
+ { showDuplicate && <DuplicateItemAction /> }
97
+ { showToggle && <DisableItemAction /> }
98
+ <RemoveItemAction />
99
+ </>
100
+ }
101
+ />
95
102
  </ItemsContainer>
96
103
  <EditItemPopover>
97
104
  <Content />
98
105
  </EditItemPopover>
99
- </UnstableRepeater>
106
+ </ControlRepeater>
100
107
  </RepeatableControlContext.Provider>
101
108
  </PropProvider>
102
109
  );
@@ -126,7 +133,7 @@ const interpolate = ( template: string, data: Record< string, unknown > ) => {
126
133
  const value = getNestedValue( data, path );
127
134
 
128
135
  if ( typeof value === 'object' && value !== null && ! Array.isArray( value ) ) {
129
- if ( value.name ) {
136
+ if ( 'name' in value && value.name ) {
130
137
  return value.name as string;
131
138
  }
132
139
 
@@ -142,12 +149,32 @@ const interpolate = ( template: string, data: Record< string, unknown > ) => {
142
149
  };
143
150
 
144
151
  const getNestedValue = ( obj: Record< string, unknown >, path: string ) => {
145
- return path.split( '.' ).reduce( ( current: Record< string, unknown >, key ) => {
152
+ let parentObj: Record< string, unknown > = {};
153
+ const pathKeys = path.split( '.' );
154
+ const key = pathKeys.slice( -1 )[ 0 ];
155
+
156
+ let value: unknown = pathKeys.reduce( ( current: Record< string, unknown >, currentKey, currentIndex ) => {
157
+ if ( currentIndex === pathKeys.length - 2 ) {
158
+ parentObj = current;
159
+ }
160
+
146
161
  if ( current && typeof current === 'object' ) {
147
- return current[ key ] as Record< string, unknown >;
162
+ return current[ currentKey ] as Record< string, unknown >;
148
163
  }
164
+
149
165
  return {};
150
166
  }, obj );
167
+
168
+ value = !! value ? value : '';
169
+ const propType = parentObj?.$$type;
170
+ const propValue = parentObj?.value as SizePropValue[ 'value' ];
171
+ const doesValueRepresentCustomSize = key === 'unit' && propType === 'size' && propValue?.unit === 'custom';
172
+
173
+ if ( ! doesValueRepresentCustomSize ) {
174
+ return value;
175
+ }
176
+
177
+ return propValue?.size ? '' : CUSTOM_SIZE_LABEL;
151
178
  };
152
179
 
153
180
  const isEmptyValue = ( val: unknown ) => {
@@ -190,11 +217,19 @@ const shouldShowPlaceholder = ( pattern: string, data: Record< string, unknown >
190
217
  return false;
191
218
  };
192
219
 
220
+ const getTextColor = ( isReadOnly: boolean, showPlaceholder: boolean ): string => {
221
+ if ( isReadOnly ) {
222
+ return 'text.disabled';
223
+ }
224
+ return showPlaceholder ? 'text.tertiary' : 'text.primary';
225
+ };
226
+
193
227
  const ItemLabel = ( { value }: { value: Record< string, unknown > } ) => {
194
- const { placeholder, patternLabel } = useRepeatableControlContext();
228
+ const { placeholder, patternLabel, props: childProps } = useRepeatableControlContext();
195
229
  const showPlaceholder = shouldShowPlaceholder( patternLabel, value );
196
230
  const label = showPlaceholder ? placeholder : interpolate( patternLabel, value );
197
- const color = showPlaceholder ? 'text.tertiary' : 'text.primary';
231
+ const isReadOnly = !! childProps?.readOnly;
232
+ const color = getTextColor( isReadOnly, showPlaceholder );
198
233
 
199
234
  return (
200
235
  <Box component="span" color={ color }>
@@ -0,0 +1,57 @@
1
+ import * as React from 'react';
2
+ import { useEffect, useState } from 'react';
3
+
4
+ import { createControl } from '../create-control';
5
+ import { SelectControl, type SelectOption } from './select-control';
6
+
7
+ type ExtendedWindow = Window & {
8
+ elementor: { $previewContents: [ HTMLIFrameElement ]; config: { document: { id: string } } };
9
+ };
10
+
11
+ const getOffCanvasElements = () => {
12
+ const extendedWindow = window as unknown as ExtendedWindow;
13
+ const documentId = extendedWindow.elementor.config.document.id;
14
+ const offCanvasElements = extendedWindow.elementor.$previewContents[ 0 ].querySelectorAll(
15
+ `[data-elementor-id="${ documentId }"] .elementor-widget-off-canvas.elementor-element-edit-mode`
16
+ );
17
+
18
+ return Array.from( offCanvasElements as unknown as HTMLElement[] ).map( ( offCanvasElement ) => {
19
+ return {
20
+ label: offCanvasElement.querySelector( '.e-off-canvas' )?.getAttribute( 'aria-label' ) ?? '',
21
+ value: offCanvasElement.dataset.id,
22
+ } as SelectOption;
23
+ } );
24
+ };
25
+
26
+ const collectionMethods = {
27
+ 'off-canvas': getOffCanvasElements,
28
+ } as const;
29
+
30
+ type SelectControlWrapperProps = Parameters< typeof SelectControl >[ 0 ] & {
31
+ collectionId?: keyof typeof collectionMethods;
32
+ };
33
+
34
+ const useDynamicOptions = (
35
+ collectionId?: keyof typeof collectionMethods,
36
+ initialOptions?: SelectControlWrapperProps[ 'options' ]
37
+ ) => {
38
+ const [ options, setOptions ] = useState< SelectControlWrapperProps[ 'options' ] >( initialOptions ?? [] );
39
+
40
+ useEffect( () => {
41
+ if ( ! collectionId || ! collectionMethods[ collectionId ] ) {
42
+ setOptions( initialOptions ?? [] );
43
+ return;
44
+ }
45
+ setOptions( collectionMethods[ collectionId ]() );
46
+ }, [ collectionId, initialOptions ] );
47
+
48
+ return options;
49
+ };
50
+
51
+ export const SelectControlWrapper = createControl(
52
+ ( { collectionId, options, ...props }: SelectControlWrapperProps ) => {
53
+ const actualOptions = useDynamicOptions( collectionId, options );
54
+
55
+ return <SelectControl options={ actualOptions } { ...props } />;
56
+ }
57
+ );
@@ -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, Typography } from '@elementor/ui';
4
+ import { 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,12 +13,13 @@ export type SelectOption = {
13
13
  disabled?: boolean;
14
14
  };
15
15
 
16
- type Props = {
16
+ type SelectControlProps = {
17
17
  options: SelectOption[];
18
18
  onChange?: ( newValue: string | null, previousValue: string | null | undefined ) => void;
19
+ MenuProps?: SelectProps[ 'MenuProps' ];
20
+ ariaLabel?: string;
19
21
  };
20
-
21
- export const SelectControl = createControl( ( { options, onChange }: Props ) => {
22
+ export const SelectControl = createControl( ( { options, onChange, MenuProps, ariaLabel }: SelectControlProps ) => {
22
23
  const { value, setValue, disabled, placeholder } = useBoundProp( stringPropTypeUtil );
23
24
  const handleChange = ( event: SelectChangeEvent< StringPropValue[ 'value' ] > ) => {
24
25
  const newValue = event.target.value || null;
@@ -26,6 +27,7 @@ export const SelectControl = createControl( ( { options, onChange }: Props ) =>
26
27
  onChange?.( newValue, value );
27
28
  setValue( newValue );
28
29
  };
30
+ const isDisabled = disabled || options.length === 0;
29
31
 
30
32
  return (
31
33
  <ControlActions>
@@ -33,6 +35,8 @@ export const SelectControl = createControl( ( { options, onChange }: Props ) =>
33
35
  sx={ { overflow: 'hidden' } }
34
36
  displayEmpty
35
37
  size="tiny"
38
+ MenuProps={ MenuProps }
39
+ aria-label={ ariaLabel || placeholder }
36
40
  renderValue={ ( selectedValue: string | null ) => {
37
41
  const findOptionByValue = ( searchValue: string | null ) =>
38
42
  options.find( ( opt ) => opt.value === searchValue );
@@ -55,7 +59,7 @@ export const SelectControl = createControl( ( { options, onChange }: Props ) =>
55
59
  } }
56
60
  value={ value ?? '' }
57
61
  onChange={ handleChange }
58
- disabled={ disabled }
62
+ disabled={ isDisabled }
59
63
  fullWidth
60
64
  >
61
65
  { options.map( ( { label, ...props } ) => (
@@ -20,13 +20,22 @@ type SelectionSizeControlProps = {
20
20
  sizeLabel: string;
21
21
  selectionConfig: SelectionComponentConfig;
22
22
  sizeConfigMap: Record< string, SizeControlConfig >;
23
+ isRepeaterControl?: boolean;
23
24
  };
24
25
 
25
26
  export const SelectionSizeControl = createControl(
26
- ( { selectionLabel, sizeLabel, selectionConfig, sizeConfigMap }: SelectionSizeControlProps ) => {
27
+ ( {
28
+ selectionLabel,
29
+ sizeLabel,
30
+ selectionConfig,
31
+ sizeConfigMap,
32
+ isRepeaterControl = false,
33
+ }: SelectionSizeControlProps ) => {
27
34
  const { value, setValue, propType } = useBoundProp( selectionSizePropTypeUtil );
28
35
  const rowRef = useRef< HTMLDivElement >( null );
29
36
 
37
+ const sizeFieldId = sizeLabel.replace( /\s+/g, '-' ).toLowerCase();
38
+
30
39
  const currentSizeConfig = useMemo( () => {
31
40
  switch ( value.selection.$$type ) {
32
41
  case 'key-value':
@@ -53,7 +62,7 @@ export const SelectionSizeControl = createControl(
53
62
  { currentSizeConfig && (
54
63
  <>
55
64
  <Grid item xs={ 6 } sx={ { display: 'flex', alignItems: 'center' } }>
56
- <ControlFormLabel>{ sizeLabel }</ControlFormLabel>
65
+ <ControlFormLabel htmlFor={ sizeFieldId }>{ sizeLabel }</ControlFormLabel>
57
66
  </Grid>
58
67
  <Grid item xs={ 6 }>
59
68
  <PropKeyProvider bind="size">
@@ -62,6 +71,8 @@ export const SelectionSizeControl = createControl(
62
71
  variant={ currentSizeConfig.variant }
63
72
  units={ currentSizeConfig.units }
64
73
  defaultUnit={ currentSizeConfig.defaultUnit }
74
+ id={ sizeFieldId }
75
+ isRepeaterControl={ isRepeaterControl }
65
76
  />
66
77
  </PropKeyProvider>
67
78
  </Grid>
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { type RefObject, useEffect, useState } from 'react';
2
+ import { type RefObject, useEffect, useMemo } from 'react';
3
3
  import { type PropType, sizePropTypeUtil, type SizePropValue } from '@elementor/editor-props';
4
4
  import { useActiveBreakpoint } from '@elementor/editor-responsive';
5
5
  import { usePopupState } from '@elementor/ui';
@@ -42,6 +42,8 @@ type BaseSizeControlProps = {
42
42
  min?: number;
43
43
  enablePropTypeUnits?: boolean;
44
44
  id?: string;
45
+ ariaLabel?: string;
46
+ isRepeaterControl?: boolean;
45
47
  };
46
48
 
47
49
  type LengthSizeControlProps = BaseSizeControlProps &
@@ -79,6 +81,8 @@ const defaultUnits: Record< SizeControlProps[ 'variant' ], Unit[] > = {
79
81
  time: [ ...timeUnits ] as TimeUnit[],
80
82
  } as const;
81
83
 
84
+ export const CUSTOM_SIZE_LABEL = 'fx';
85
+
82
86
  export const SizeControl = createControl(
83
87
  ( {
84
88
  variant = 'length' as SizeControlProps[ 'variant' ],
@@ -92,6 +96,8 @@ export const SizeControl = createControl(
92
96
  min = 0,
93
97
  enablePropTypeUnits = false,
94
98
  id,
99
+ ariaLabel,
100
+ isRepeaterControl = false,
95
101
  }: Omit< SizeControlProps, 'variant' > & { variant?: SizeVariant } ) => {
96
102
  const {
97
103
  value: sizeValue,
@@ -102,28 +108,22 @@ export const SizeControl = createControl(
102
108
  propType,
103
109
  } = useBoundProp( sizePropTypeUtil );
104
110
  const actualDefaultUnit = defaultUnit ?? externalPlaceholder?.unit ?? defaultSelectedUnit[ variant ];
105
- const [ internalState, setInternalState ] = useState( createStateFromSizeProp( sizeValue, actualDefaultUnit ) );
106
111
  const activeBreakpoint = useActiveBreakpoint();
107
- const actualUnits = resolveUnits( propType, enablePropTypeUnits, variant, units );
108
-
109
112
  const actualExtendedOptions = useSizeExtendedOptions( extendedOptions || [], disableCustom ?? false );
113
+ const actualUnits = resolveUnits( propType, enablePropTypeUnits, variant, units, actualExtendedOptions );
114
+
110
115
  const popupState = usePopupState( { variant: 'popover' } );
111
116
 
117
+ const memorizedExternalState = useMemo(
118
+ () => createStateFromSizeProp( sizeValue, actualDefaultUnit ),
119
+ [ sizeValue, actualDefaultUnit ]
120
+ );
121
+
112
122
  const [ state, setState ] = useSyncExternalState( {
113
- external: internalState,
123
+ external: memorizedExternalState,
114
124
  setExternal: ( newState: State | null, options, meta ) =>
115
125
  setSizeValue( extractValueFromState( newState ), options, meta ),
116
- persistWhen: ( newState ) => {
117
- if ( ! newState?.unit ) {
118
- return false;
119
- }
120
-
121
- if ( isUnitExtendedOption( newState.unit ) ) {
122
- return newState.unit === 'auto' ? true : !! newState.custom;
123
- }
124
-
125
- return !! newState?.numeric || newState?.numeric === 0;
126
- },
126
+ persistWhen: ( newState ) => !! extractValueFromState( newState ),
127
127
  fallback: ( newState ) => ( {
128
128
  unit: newState?.unit ?? actualDefaultUnit,
129
129
  numeric: newState?.numeric ?? DEFAULT_SIZE,
@@ -132,7 +132,7 @@ export const SizeControl = createControl(
132
132
  } );
133
133
 
134
134
  const { size: controlSize = DEFAULT_SIZE, unit: controlUnit = actualDefaultUnit } =
135
- extractValueFromState( state ) || {};
135
+ extractValueFromState( state, true ) || {};
136
136
 
137
137
  const handleUnitChange = ( newUnit: Unit | ExtendedOption ) => {
138
138
  if ( newUnit === 'custom' ) {
@@ -169,7 +169,7 @@ export const SizeControl = createControl(
169
169
  }
170
170
  };
171
171
 
172
- useEffect( () => {
172
+ const handleLinkedSizeControlChanges = () => {
173
173
  const newState = createStateFromSizeProp(
174
174
  sizeValue,
175
175
  state.unit === 'custom' ? state.unit : actualDefaultUnit,
@@ -188,12 +188,18 @@ export const SizeControl = createControl(
188
188
  }
189
189
 
190
190
  if ( state.unit === newState.unit ) {
191
- setInternalState( mergedStates );
191
+ setState( mergedStates );
192
192
 
193
193
  return;
194
194
  }
195
195
 
196
196
  setState( newState );
197
+ };
198
+
199
+ useEffect( () => {
200
+ if ( ! isRepeaterControl ) {
201
+ handleLinkedSizeControlChanges();
202
+ }
197
203
  // eslint-disable-next-line react-hooks/exhaustive-deps
198
204
  }, [ sizeValue ] );
199
205
 
@@ -212,7 +218,7 @@ export const SizeControl = createControl(
212
218
  disabled={ disabled }
213
219
  size={ controlSize }
214
220
  unit={ controlUnit }
215
- units={ [ ...actualUnits, ...( actualExtendedOptions || [] ) ] }
221
+ units={ [ ...actualUnits ] }
216
222
  placeholder={ placeholder }
217
223
  startIcon={ startIcon }
218
224
  handleSizeChange={ handleSizeChange }
@@ -222,6 +228,7 @@ export const SizeControl = createControl(
222
228
  popupState={ popupState }
223
229
  min={ min }
224
230
  id={ id }
231
+ ariaLabel={ ariaLabel }
225
232
  />
226
233
  { anchorRef?.current && popupState.isOpen && (
227
234
  <TextFieldPopover
@@ -241,12 +248,13 @@ function resolveUnits(
241
248
  propType: PropType,
242
249
  enablePropTypeUnits: boolean,
243
250
  variant: SizeVariant,
244
- externalUnits?: Unit[]
251
+ externalUnits?: Unit[],
252
+ actualExtendedOptions?: ExtendedOption[]
245
253
  ) {
246
254
  const fallback = [ ...defaultUnits[ variant ] ];
247
255
 
248
256
  if ( ! enablePropTypeUnits ) {
249
- return externalUnits ?? fallback;
257
+ return [ ...( externalUnits ?? fallback ), ...( actualExtendedOptions || [] ) ];
250
258
  }
251
259
 
252
260
  return ( propType.settings?.available_units as Unit[] ) ?? fallback;
@@ -279,7 +287,7 @@ function createStateFromSizeProp(
279
287
  };
280
288
  }
281
289
 
282
- function extractValueFromState( state: State | null ): SizeValue | null {
290
+ function extractValueFromState( state: State | null, allowEmpty: boolean = false ): SizeValue | null {
283
291
  if ( ! state ) {
284
292
  return null;
285
293
  }
@@ -294,10 +302,20 @@ function extractValueFromState( state: State | null ): SizeValue | null {
294
302
  return { size: '', unit };
295
303
  }
296
304
 
305
+ if ( unit === 'custom' ) {
306
+ return { size: state.custom ?? '', unit: 'custom' };
307
+ }
308
+
309
+ const numeric = state.numeric;
310
+
311
+ if ( ! allowEmpty && ( numeric === undefined || numeric === null || Number.isNaN( numeric ) ) ) {
312
+ return null;
313
+ }
314
+
297
315
  return {
298
- size: state[ unit === 'custom' ? 'custom' : 'numeric' ],
316
+ size: numeric,
299
317
  unit,
300
- } as SizeValue;
318
+ };
301
319
  }
302
320
 
303
321
  function areStatesEqual( state1: State, state2: State ): boolean {
@@ -1,12 +1,14 @@
1
1
  import * as React from 'react';
2
2
  import { useState } from 'react';
3
+ import { useCurrentUserCapabilities } from '@elementor/editor-current-user';
3
4
  import { imageSrcPropTypeUtil } from '@elementor/editor-props';
4
5
  import { UploadIcon } from '@elementor/icons';
5
- import { Button, Card, CardMedia, CardOverlay, CircularProgress, Stack, styled } from '@elementor/ui';
6
+ import { Button, Card, CardMedia, CardOverlay, CircularProgress, Stack, styled, ThemeProvider } from '@elementor/ui';
6
7
  import { type OpenOptions, useWpMediaAttachment, useWpMediaFrame } from '@elementor/wp-media';
7
8
  import { __ } from '@wordpress/i18n';
8
9
 
9
10
  import { useBoundProp } from '../bound-prop-context';
11
+ import { ConditionalControlInfotip } from '../components/conditional-control-infotip';
10
12
  import { EnableUnfilteredModal } from '../components/enable-unfiltered-modal';
11
13
  import ControlActions from '../control-actions/control-actions';
12
14
  import { createControl } from '../create-control';
@@ -47,6 +49,8 @@ export const SvgMediaControl = createControl( () => {
47
49
  const src = attachment?.url ?? url?.value ?? null;
48
50
  const { data: allowSvgUpload } = useUnfilteredFilesUpload();
49
51
  const [ unfilteredModalOpenState, setUnfilteredModalOpenState ] = useState( false );
52
+ const { canUser } = useCurrentUserCapabilities();
53
+ const canManageOptions = canUser( 'manage_options' );
50
54
 
51
55
  const { open } = useWpMediaFrame( {
52
56
  mediaTypes: [ 'svg' ],
@@ -79,6 +83,18 @@ export const SvgMediaControl = createControl( () => {
79
83
  }
80
84
  };
81
85
 
86
+ const infotipProps = {
87
+ title: __( "Sorry, you can't upload that file yet.", 'elementor' ),
88
+ description: (
89
+ <>
90
+ { __( 'To upload them anyway, ask the site administrator to enable unfiltered', 'elementor' ) }
91
+ <br />
92
+ { __( 'file uploads.', 'elementor' ) }
93
+ </>
94
+ ),
95
+ isEnabled: ! canManageOptions,
96
+ };
97
+
82
98
  return (
83
99
  <Stack gap={ 1 }>
84
100
  <EnableUnfilteredModal open={ unfilteredModalOpenState } onClose={ onCloseUnfilteredModal } />
@@ -112,15 +128,22 @@ export const SvgMediaControl = createControl( () => {
112
128
  >
113
129
  { __( 'Select SVG', 'elementor' ) }
114
130
  </Button>
115
- <Button
116
- size="tiny"
117
- variant="text"
118
- color="inherit"
119
- startIcon={ <UploadIcon /> }
120
- onClick={ () => handleClick( MODE_UPLOAD ) }
121
- >
122
- { __( 'Upload', 'elementor' ) }
123
- </Button>
131
+ <ConditionalControlInfotip { ...infotipProps }>
132
+ <span>
133
+ <ThemeProvider colorScheme={ canManageOptions ? 'light' : 'dark' }>
134
+ <Button
135
+ size="tiny"
136
+ variant="text"
137
+ color="inherit"
138
+ startIcon={ <UploadIcon /> }
139
+ disabled={ canManageOptions ? false : true }
140
+ onClick={ () => canManageOptions && handleClick( MODE_UPLOAD ) }
141
+ >
142
+ { __( 'Upload', 'elementor' ) }
143
+ </Button>
144
+ </ThemeProvider>
145
+ </span>
146
+ </ConditionalControlInfotip>
124
147
  </Stack>
125
148
  </CardOverlay>
126
149
  </StyledCard>
@@ -8,9 +8,10 @@ import { createControl } from '../create-control';
8
8
 
9
9
  type Props = {
10
10
  placeholder?: string;
11
+ ariaLabel?: string;
11
12
  };
12
13
 
13
- export const TextAreaControl = createControl( ( { placeholder }: Props ) => {
14
+ export const TextAreaControl = createControl( ( { placeholder, ariaLabel }: Props ) => {
14
15
  const { value, setValue, disabled } = useBoundProp( stringPropTypeUtil );
15
16
 
16
17
  const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
@@ -28,6 +29,9 @@ export const TextAreaControl = createControl( ( { placeholder }: Props ) => {
28
29
  value={ value ?? '' }
29
30
  onChange={ handleChange }
30
31
  placeholder={ placeholder }
32
+ inputProps={ {
33
+ ...( ariaLabel ? { 'aria-label': ariaLabel } : {} ),
34
+ } }
31
35
  />
32
36
  </ControlActions>
33
37
  );
@@ -14,6 +14,7 @@ export const TextControl = createControl(
14
14
  inputDisabled,
15
15
  helperText,
16
16
  sx,
17
+ ariaLabel,
17
18
  }: {
18
19
  placeholder?: string;
19
20
  error?: boolean;
@@ -21,6 +22,7 @@ export const TextControl = createControl(
21
22
  inputDisabled?: boolean;
22
23
  helperText?: string;
23
24
  sx?: SxProps;
25
+ ariaLabel?: string;
24
26
  } ) => {
25
27
  const { value, setValue, disabled } = useBoundProp( stringPropTypeUtil );
26
28
  const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => setValue( event.target.value );
@@ -37,6 +39,9 @@ export const TextControl = createControl(
37
39
  error={ error }
38
40
  helperText={ helperText }
39
41
  sx={ sx }
42
+ inputProps={ {
43
+ ...( ariaLabel ? { 'aria-label': ariaLabel } : {} ),
44
+ } }
40
45
  />
41
46
  </ControlActions>
42
47
  );
@@ -5,6 +5,7 @@ import { type ToggleButtonProps } from '@elementor/ui';
5
5
  import { useBoundProp } from '../bound-prop-context';
6
6
  import { ControlToggleButtonGroup, type ToggleButtonGroupItem } from '../components/control-toggle-button-group';
7
7
  import { createControl } from '../create-control';
8
+ import { convertToggleOptionsToAtomic, type DynamicToggleOption } from '../utils/convert-toggle-options-to-atomic';
8
9
 
9
10
  export type ToggleControlProps< T extends PropValue > = {
10
11
  options: Array< ToggleButtonGroupItem< T > & { exclusive?: boolean } >;
@@ -12,6 +13,7 @@ export type ToggleControlProps< T extends PropValue > = {
12
13
  size?: ToggleButtonProps[ 'size' ];
13
14
  exclusive?: boolean;
14
15
  maxItems?: number;
16
+ convertOptions?: boolean;
15
17
  };
16
18
 
17
19
  export const ToggleControl = createControl(
@@ -21,10 +23,17 @@ export const ToggleControl = createControl(
21
23
  size = 'tiny',
22
24
  exclusive = true,
23
25
  maxItems,
26
+ convertOptions = false,
24
27
  }: ToggleControlProps< StringPropValue[ 'value' ] > ) => {
25
28
  const { value, setValue, placeholder, disabled } = useBoundProp( stringPropTypeUtil );
26
29
 
27
- const exclusiveValues = options.filter( ( option ) => option.exclusive ).map( ( option ) => option.value );
30
+ const processedOptions = convertOptions
31
+ ? convertToggleOptionsToAtomic( options as DynamicToggleOption[] )
32
+ : ( options as Array< ToggleButtonGroupItem< StringPropValue[ 'value' ] > & { exclusive?: boolean } > );
33
+
34
+ const exclusiveValues = processedOptions
35
+ .filter( ( option ) => option.exclusive )
36
+ .map( ( option ) => option.value );
28
37
 
29
38
  const handleNonExclusiveToggle = ( selectedValues: StringPropValue[ 'value' ][] ) => {
30
39
  const newSelectedValue = selectedValues[ selectedValues.length - 1 ];
@@ -38,7 +47,7 @@ export const ToggleControl = createControl(
38
47
  };
39
48
 
40
49
  const toggleButtonGroupProps = {
41
- items: options,
50
+ items: processedOptions,
42
51
  maxItems,
43
52
  fullWidth,
44
53
  size,
@@ -33,6 +33,7 @@ export const AxisRow = ( { label, bind, startIcon, anchorRef, units, variant = '
33
33
  units={ units }
34
34
  variant={ variant }
35
35
  min={ -Number.MAX_SAFE_INTEGER }
36
+ isRepeaterControl
36
37
  id={ safeId }
37
38
  />
38
39
  </PropKeyProvider>
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { type TransformFunctionsItemPropValue } from '@elementor/editor-props';
3
- import { ArrowsMaximizeIcon, ExpandIcon, RotateClockwise2Icon, SkewXIcon } from '@elementor/icons';
3
+ import { ArrowAutofitHeightIcon, ArrowsMaximizeIcon, RotateClockwise2Icon, SkewXIcon } from '@elementor/icons';
4
4
 
5
5
  import { TransformFunctionKeys } from './initial-values';
6
6
 
@@ -9,7 +9,7 @@ export const TransformIcon = ( { value }: { value: TransformFunctionsItemPropVal
9
9
  case TransformFunctionKeys.move:
10
10
  return <ArrowsMaximizeIcon fontSize="tiny" />;
11
11
  case TransformFunctionKeys.scale:
12
- return <ExpandIcon fontSize="tiny" />;
12
+ return <ArrowAutofitHeightIcon fontSize="tiny" />;
13
13
  case TransformFunctionKeys.rotate:
14
14
  return <RotateClockwise2Icon fontSize="tiny" />;
15
15
  case TransformFunctionKeys.skew: