@elementor/editor-controls 3.33.0-99 → 3.35.0-325

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
@@ -0,0 +1,16 @@
1
+ import * as React from 'react';
2
+ import { forwardRef } from 'react';
3
+ import { UnstableTag, type UnstableTagProps } from '@elementor/ui';
4
+
5
+ export const RepeaterTag = forwardRef< HTMLDivElement, UnstableTagProps >( ( props, ref ) => {
6
+ return (
7
+ <UnstableTag
8
+ ref={ ref }
9
+ fullWidth
10
+ showActionsOnHover
11
+ variant="outlined"
12
+ sx={ { minHeight: ( theme ) => theme.spacing( 3.5 ) } }
13
+ { ...props }
14
+ />
15
+ );
16
+ } );
@@ -0,0 +1,405 @@
1
+ import * as React from 'react';
2
+ import { useEffect, useState } from 'react';
3
+ import { type CreateOptions, type PropKey } from '@elementor/editor-props';
4
+ import { CopyIcon, EyeIcon, EyeOffIcon, PlusIcon, XIcon } from '@elementor/icons';
5
+ import {
6
+ bindPopover,
7
+ bindTrigger,
8
+ Box,
9
+ IconButton,
10
+ Infotip,
11
+ Tooltip,
12
+ type UnstableTagProps,
13
+ usePopupState,
14
+ } from '@elementor/ui';
15
+ import { __ } from '@wordpress/i18n';
16
+
17
+ import { type SetValueMeta } from '../../bound-prop-context';
18
+ import { ControlAdornments } from '../../control-adornments/control-adornments';
19
+ import { RepeaterItemIconSlot, RepeaterItemLabelSlot } from '../control-repeater/locations';
20
+ import { SectionContent } from '../section-content';
21
+ import { RepeaterHeader } from './repeater-header';
22
+ import { RepeaterPopover } from './repeater-popover';
23
+ import { RepeaterTag } from './repeater-tag';
24
+ import { SortableItem, SortableProvider } from './sortable';
25
+
26
+ const SIZE = 'tiny';
27
+
28
+ type AnchorEl = HTMLElement | null;
29
+
30
+ export type RepeaterItem< T > = {
31
+ disabled?: boolean;
32
+ } & T;
33
+
34
+ type RepeaterItemContentProps< T > = {
35
+ anchorEl: AnchorEl;
36
+ bind: PropKey;
37
+ value: T;
38
+ index: number;
39
+ };
40
+
41
+ type RepeaterItemContent< T > = React.ComponentType< RepeaterItemContentProps< T > >;
42
+
43
+ export type ItemsActionPayload< T > = Array< { index: number; item: T } >;
44
+
45
+ type AddItemMeta< T > = {
46
+ type: 'add';
47
+ payload: ItemsActionPayload< T >;
48
+ };
49
+
50
+ type RemoveItemMeta< T > = {
51
+ type: 'remove';
52
+ payload: ItemsActionPayload< T >;
53
+ };
54
+
55
+ type DuplicateItemMeta< T > = {
56
+ type: 'duplicate';
57
+ payload: ItemsActionPayload< T >;
58
+ };
59
+
60
+ type ReorderItemMeta = {
61
+ type: 'reorder';
62
+ payload: { from: number; to: number };
63
+ };
64
+
65
+ type ToggleDisableItemMeta = {
66
+ type: 'toggle-disable';
67
+ };
68
+
69
+ export type SetRepeaterValuesMeta< T > =
70
+ | SetValueMeta< AddItemMeta< T > >
71
+ | SetValueMeta< RemoveItemMeta< T > >
72
+ | SetValueMeta< DuplicateItemMeta< T > >
73
+ | SetValueMeta< ReorderItemMeta >
74
+ | SetValueMeta< ToggleDisableItemMeta >;
75
+
76
+ type BaseItemSettings< T > = {
77
+ initialValues: T;
78
+ Label: React.ComponentType< { value: T; index: number } >;
79
+ Icon: React.ComponentType< { value: T } >;
80
+ Content: RepeaterItemContent< T >;
81
+ actions?: ( value: T ) => React.ReactNode;
82
+ };
83
+
84
+ type SortableItemSettings< T > = BaseItemSettings< T > & {
85
+ getId: ( { item, index }: { item: T; index: number } ) => string;
86
+ };
87
+
88
+ type RepeaterProps< T > =
89
+ | {
90
+ label: string;
91
+ values?: T[];
92
+ openOnAdd?: boolean;
93
+ setValues: ( newValue: T[], _: CreateOptions, meta?: SetRepeaterValuesMeta< T > ) => void;
94
+ disabled?: boolean;
95
+ disableAddItemButton?: boolean;
96
+ addButtonInfotipContent?: React.ReactNode;
97
+ showDuplicate?: boolean;
98
+ showToggle?: boolean;
99
+ showRemove?: boolean;
100
+ openItem?: number;
101
+ isSortable: false;
102
+ itemSettings: BaseItemSettings< T >;
103
+ }
104
+ | {
105
+ label: string;
106
+ values?: T[];
107
+ openOnAdd?: boolean;
108
+ setValues: ( newValue: T[], _: CreateOptions, meta?: SetRepeaterValuesMeta< T > ) => void;
109
+ disabled?: boolean;
110
+ disableAddItemButton?: boolean;
111
+ addButtonInfotipContent?: React.ReactNode;
112
+ showDuplicate?: boolean;
113
+ showToggle?: boolean;
114
+ showRemove?: boolean;
115
+ openItem?: number;
116
+ isSortable?: true;
117
+ itemSettings: SortableItemSettings< T >;
118
+ };
119
+
120
+ const EMPTY_OPEN_ITEM = -1;
121
+
122
+ export const Repeater = < T, >( {
123
+ label,
124
+ itemSettings,
125
+ disabled = false,
126
+ openOnAdd = false,
127
+ values: items = [],
128
+ setValues: setItems,
129
+ showDuplicate = true,
130
+ showToggle = true,
131
+ showRemove = true,
132
+ disableAddItemButton = false,
133
+ addButtonInfotipContent,
134
+ openItem: initialOpenItem = EMPTY_OPEN_ITEM,
135
+ isSortable = true,
136
+ }: RepeaterProps< RepeaterItem< T > > ) => {
137
+ const [ openItem, setOpenItem ] = useState( initialOpenItem );
138
+
139
+ const uniqueKeys = items.map( ( item, index ) =>
140
+ isSortable && 'getId' in itemSettings ? itemSettings.getId( { item, index } ) : String( index )
141
+ );
142
+
143
+ const addRepeaterItem = () => {
144
+ const newItem = structuredClone( itemSettings.initialValues );
145
+ const newIndex = items.length;
146
+ setItems(
147
+ [ ...items, newItem ],
148
+ {},
149
+ {
150
+ action: { type: 'add', payload: [ { index: newIndex, item: newItem } ] },
151
+ }
152
+ );
153
+
154
+ if ( openOnAdd ) {
155
+ setOpenItem( newIndex );
156
+ }
157
+ };
158
+
159
+ const duplicateRepeaterItem = ( index: number ) => {
160
+ const newItem = structuredClone( items[ index ] );
161
+
162
+ // Insert the new (cloned item) at the next spot (after the current index)
163
+ const atPosition = 1 + index;
164
+
165
+ setItems(
166
+ [ ...items.slice( 0, atPosition ), newItem, ...items.slice( atPosition ) ],
167
+ {},
168
+ {
169
+ action: { type: 'duplicate', payload: [ { index, item: newItem } ] },
170
+ }
171
+ );
172
+ };
173
+
174
+ const removeRepeaterItem = ( index: number ) => {
175
+ const removedItem = items[ index ];
176
+
177
+ setItems(
178
+ items.filter( ( _, pos ) => {
179
+ return pos !== index;
180
+ } ),
181
+ {},
182
+ { action: { type: 'remove', payload: [ { index, item: removedItem } ] } }
183
+ );
184
+ };
185
+
186
+ const toggleDisableRepeaterItem = ( index: number ) => {
187
+ setItems(
188
+ items.map( ( value, pos ) => {
189
+ if ( pos === index ) {
190
+ const { disabled: propDisabled, ...rest } = value;
191
+
192
+ // If the items should not be disabled, remove the disabled property.
193
+ return { ...rest, ...( propDisabled ? {} : { disabled: true } ) } as RepeaterItem< T >;
194
+ }
195
+
196
+ return value;
197
+ } ),
198
+ {},
199
+ { action: { type: 'toggle-disable' } }
200
+ );
201
+ };
202
+
203
+ const onChangeOrder = ( reorderedKeys: string[], meta: { from: number; to: number } ) => {
204
+ setItems(
205
+ reorderedKeys.map( ( id ) => {
206
+ return items[ uniqueKeys.indexOf( id ) ];
207
+ } ),
208
+ {},
209
+ { action: { type: 'reorder', payload: { ...meta } } }
210
+ );
211
+ };
212
+
213
+ const isButtonDisabled = disabled || disableAddItemButton;
214
+ const shouldShowInfotip = isButtonDisabled && addButtonInfotipContent;
215
+
216
+ const addButton = (
217
+ <IconButton
218
+ size={ SIZE }
219
+ sx={ {
220
+ ml: 'auto',
221
+ } }
222
+ disabled={ isButtonDisabled }
223
+ onClick={ addRepeaterItem }
224
+ aria-label={ __( 'Add item', 'elementor' ) }
225
+ >
226
+ <PlusIcon fontSize={ SIZE } />
227
+ </IconButton>
228
+ );
229
+
230
+ return (
231
+ <SectionContent gap={ 2 }>
232
+ <RepeaterHeader label={ label } adornment={ ControlAdornments }>
233
+ { shouldShowInfotip ? (
234
+ <Infotip
235
+ placement="right"
236
+ content={ addButtonInfotipContent }
237
+ color="secondary"
238
+ slotProps={ { popper: { sx: { width: 300 } } } }
239
+ >
240
+ <Box sx={ { ...( isButtonDisabled ? { cursor: 'not-allowed' } : {} ) } }>{ addButton }</Box>
241
+ </Infotip>
242
+ ) : (
243
+ addButton
244
+ ) }
245
+ </RepeaterHeader>
246
+ { 0 < uniqueKeys.length && (
247
+ <SortableProvider value={ uniqueKeys } onChange={ onChangeOrder }>
248
+ { uniqueKeys.map( ( key ) => {
249
+ const index = uniqueKeys.indexOf( key );
250
+ const value = items[ index ];
251
+
252
+ if ( ! value ) {
253
+ return null;
254
+ }
255
+
256
+ return (
257
+ <SortableItem id={ key } key={ `sortable-${ key }` } disabled={ ! isSortable }>
258
+ <RepeaterItem
259
+ disabled={ disabled }
260
+ propDisabled={ value?.disabled }
261
+ label={
262
+ <RepeaterItemLabelSlot value={ value }>
263
+ <itemSettings.Label value={ value } index={ index } />
264
+ </RepeaterItemLabelSlot>
265
+ }
266
+ startIcon={
267
+ <RepeaterItemIconSlot value={ value }>
268
+ <itemSettings.Icon value={ value } />
269
+ </RepeaterItemIconSlot>
270
+ }
271
+ removeItem={ () => removeRepeaterItem( index ) }
272
+ duplicateItem={ () => duplicateRepeaterItem( index ) }
273
+ toggleDisableItem={ () => toggleDisableRepeaterItem( index ) }
274
+ openOnMount={ openOnAdd && openItem === index }
275
+ onOpen={ () => setOpenItem( EMPTY_OPEN_ITEM ) }
276
+ showDuplicate={ showDuplicate }
277
+ showToggle={ showToggle }
278
+ showRemove={ showRemove }
279
+ actions={ itemSettings.actions }
280
+ value={ value }
281
+ >
282
+ { ( props ) => (
283
+ <itemSettings.Content
284
+ { ...props }
285
+ value={ value }
286
+ bind={ String( index ) }
287
+ index={ index }
288
+ />
289
+ ) }
290
+ </RepeaterItem>
291
+ </SortableItem>
292
+ );
293
+ } ) }
294
+ </SortableProvider>
295
+ ) }
296
+ </SectionContent>
297
+ );
298
+ };
299
+
300
+ type RepeaterItemProps< T > = {
301
+ label: React.ReactNode;
302
+ propDisabled?: boolean;
303
+ startIcon: UnstableTagProps[ 'startIcon' ];
304
+ removeItem: () => void;
305
+ duplicateItem: () => void;
306
+ toggleDisableItem: () => void;
307
+ children: ( props: Pick< RepeaterItemContentProps< T >, 'anchorEl' > ) => React.ReactNode;
308
+ openOnMount: boolean;
309
+ onOpen: () => void;
310
+ showDuplicate: boolean;
311
+ showToggle: boolean;
312
+ showRemove: boolean;
313
+ disabled?: boolean;
314
+ actions?: ( value: T ) => React.ReactNode;
315
+ value: T;
316
+ };
317
+
318
+ const RepeaterItem = < T, >( {
319
+ label,
320
+ propDisabled,
321
+ startIcon,
322
+ children,
323
+ removeItem,
324
+ duplicateItem,
325
+ toggleDisableItem,
326
+ openOnMount,
327
+ onOpen,
328
+ showDuplicate,
329
+ showToggle,
330
+ showRemove,
331
+ disabled,
332
+ actions,
333
+ value,
334
+ }: RepeaterItemProps< T > ) => {
335
+ const { popoverState, popoverProps, ref, setRef } = usePopover( openOnMount, onOpen );
336
+
337
+ const duplicateLabel = __( 'Duplicate', 'elementor' );
338
+ const toggleLabel = propDisabled ? __( 'Show', 'elementor' ) : __( 'Hide', 'elementor' );
339
+ const removeLabel = __( 'Remove', 'elementor' );
340
+
341
+ return (
342
+ <>
343
+ <RepeaterTag
344
+ disabled={ disabled }
345
+ label={ label }
346
+ ref={ setRef }
347
+ aria-label={ __( 'Open item', 'elementor' ) }
348
+ { ...bindTrigger( popoverState ) }
349
+ startIcon={ startIcon }
350
+ actions={
351
+ <>
352
+ { showDuplicate && (
353
+ <Tooltip title={ duplicateLabel } placement="top">
354
+ <IconButton size={ SIZE } onClick={ duplicateItem } aria-label={ duplicateLabel }>
355
+ <CopyIcon fontSize={ SIZE } />
356
+ </IconButton>
357
+ </Tooltip>
358
+ ) }
359
+ { showToggle && (
360
+ <Tooltip title={ toggleLabel } placement="top">
361
+ <IconButton size={ SIZE } onClick={ toggleDisableItem } aria-label={ toggleLabel }>
362
+ { propDisabled ? <EyeOffIcon fontSize={ SIZE } /> : <EyeIcon fontSize={ SIZE } /> }
363
+ </IconButton>
364
+ </Tooltip>
365
+ ) }
366
+ { actions?.( value ) }
367
+ { showRemove && (
368
+ <Tooltip title={ removeLabel } placement="top">
369
+ <IconButton size={ SIZE } onClick={ removeItem } aria-label={ removeLabel }>
370
+ <XIcon fontSize={ SIZE } />
371
+ </IconButton>
372
+ </Tooltip>
373
+ ) }
374
+ </>
375
+ }
376
+ />
377
+ <RepeaterPopover width={ ref?.getBoundingClientRect().width } { ...popoverProps } anchorEl={ ref }>
378
+ <Box>{ children( { anchorEl: ref } ) }</Box>
379
+ </RepeaterPopover>
380
+ </>
381
+ );
382
+ };
383
+
384
+ const usePopover = ( openOnMount: boolean, onOpen: () => void ) => {
385
+ const [ ref, setRef ] = useState< HTMLElement | null >( null );
386
+
387
+ const popoverState = usePopupState( { variant: 'popover' } );
388
+
389
+ const popoverProps = bindPopover( popoverState );
390
+
391
+ useEffect( () => {
392
+ if ( openOnMount && ref ) {
393
+ popoverState.open( ref );
394
+ onOpen?.();
395
+ }
396
+ // eslint-disable-next-line react-hooks/exhaustive-deps
397
+ }, [ ref ] );
398
+
399
+ return {
400
+ popoverState,
401
+ ref,
402
+ setRef,
403
+ popoverProps,
404
+ };
405
+ };
@@ -13,7 +13,7 @@ import {
13
13
  } from '@elementor/ui';
14
14
  import { __ } from '@wordpress/i18n';
15
15
 
16
- export const SortableProvider = < T extends number >( props: UnstableSortableProviderProps< T > ) => {
16
+ export const SortableProvider = < T extends string | number >( props: UnstableSortableProviderProps< T > ) => {
17
17
  return (
18
18
  <List sx={ { p: 0, my: -0.5, mx: 0 } }>
19
19
  <UnstableSortableProvider restrictAxis disableDragOverlay={ false } variant={ 'static' } { ...props } />
@@ -7,6 +7,8 @@ import ControlActions from '../../control-actions/control-actions';
7
7
  import { type ExtendedOption, isUnitExtendedOption, type Unit } from '../../utils/size-control';
8
8
  import { SelectionEndAdornment, TextFieldInnerSelection } from '../size-control/text-field-inner-selection';
9
9
 
10
+ const RESTRICTED_KEYBOARD_SHORTCUT_UNITS = [ 'auto' ];
11
+
10
12
  type SizeInputProps = {
11
13
  unit: Unit | ExtendedOption;
12
14
  size: number | string;
@@ -22,6 +24,7 @@ type SizeInputProps = {
22
24
  disabled?: boolean;
23
25
  min?: number;
24
26
  id?: string;
27
+ ariaLabel?: string;
25
28
  };
26
29
 
27
30
  export const SizeInput = ( {
@@ -39,6 +42,7 @@ export const SizeInput = ( {
39
42
  disabled,
40
43
  min,
41
44
  id,
45
+ ariaLabel,
42
46
  }: SizeInputProps ) => {
43
47
  const unitInputBufferRef = useRef( '' );
44
48
  const inputType = isUnitExtendedOption( unit ) ? 'text' : 'number';
@@ -58,9 +62,9 @@ export const SizeInput = ( {
58
62
  unitInputBufferRef.current = updatedBuffer;
59
63
 
60
64
  const matchedUnit =
61
- units.find( ( u ) => u.includes( updatedBuffer ) ) ||
62
- units.find( ( u ) => u.startsWith( newChar ) ) ||
63
- units.find( ( u ) => u.includes( newChar ) );
65
+ units.find( ( u ) => ! RESTRICTED_KEYBOARD_SHORTCUT_UNITS.includes( u ) && u.includes( updatedBuffer ) ) ||
66
+ units.find( ( u ) => ! RESTRICTED_KEYBOARD_SHORTCUT_UNITS.includes( u ) && u.startsWith( newChar ) ) ||
67
+ units.find( ( u ) => ! RESTRICTED_KEYBOARD_SHORTCUT_UNITS.includes( u ) && u.includes( newChar ) );
64
68
 
65
69
  if ( matchedUnit ) {
66
70
  handleUnitChange( matchedUnit );
@@ -72,6 +76,16 @@ export const SizeInput = ( {
72
76
  'aria-haspopup': true,
73
77
  };
74
78
 
79
+ const menuItemsAttributes = units.includes( 'custom' )
80
+ ? {
81
+ custom: popupAttributes,
82
+ }
83
+ : undefined;
84
+
85
+ const alternativeOptionLabels = {
86
+ custom: <MathFunctionIcon fontSize="tiny" />,
87
+ };
88
+
75
89
  const InputProps = {
76
90
  ...popupAttributes,
77
91
  readOnly: isUnitExtendedOption( unit ),
@@ -89,16 +103,8 @@ export const SizeInput = ( {
89
103
  options={ units }
90
104
  onClick={ handleUnitChange }
91
105
  value={ unit }
92
- alternativeOptionLabels={ {
93
- custom: <MathFunctionIcon fontSize="tiny" />,
94
- } }
95
- menuItemsAttributes={
96
- units.includes( 'custom' )
97
- ? {
98
- custom: popupAttributes,
99
- }
100
- : undefined
101
- }
106
+ alternativeOptionLabels={ alternativeOptionLabels }
107
+ menuItemsAttributes={ menuItemsAttributes }
102
108
  />
103
109
  ),
104
110
  };
@@ -115,7 +121,7 @@ export const SizeInput = ( {
115
121
  onKeyUp={ handleKeyUp }
116
122
  onBlur={ onBlur }
117
123
  InputProps={ InputProps }
118
- inputProps={ { min, step: 'any' } }
124
+ inputProps={ { min, step: 'any', 'aria-label': ariaLabel } }
119
125
  isPopoverOpen={ popupState.isOpen }
120
126
  id={ id }
121
127
  />
@@ -109,7 +109,11 @@ export const SelectionEndAdornment = < T extends string >( {
109
109
  };
110
110
 
111
111
  const { placeholder, showPrimaryColor } = useUnitPlaceholder( value );
112
-
112
+ const itemStyles = {
113
+ display: 'flex',
114
+ flexDirection: 'column',
115
+ justifyContent: 'center',
116
+ };
113
117
  return (
114
118
  <InputAdornment position="end">
115
119
  <StyledButton
@@ -120,13 +124,22 @@ export const SelectionEndAdornment = < T extends string >( {
120
124
  >
121
125
  { placeholder ?? alternativeOptionLabels[ value ] ?? value }
122
126
  </StyledButton>
123
-
124
127
  <Menu MenuListProps={ { dense: true } } { ...bindMenu( popupState ) }>
125
128
  { options.map( ( option, index ) => (
126
129
  <MenuListItem
127
130
  key={ option }
128
131
  onClick={ () => handleMenuItemClick( index ) }
129
132
  { ...menuItemsAttributes?.[ option ] }
133
+ primaryTypographyProps={ {
134
+ variant: 'caption',
135
+ sx: {
136
+ ...itemStyles,
137
+ lineHeight: '1',
138
+ },
139
+ } }
140
+ menuItemTextProps={ {
141
+ sx: itemStyles,
142
+ } }
130
143
  >
131
144
  { alternativeOptionLabels[ option ] ?? option.toUpperCase() }
132
145
  </MenuListItem>
@@ -2,13 +2,14 @@ import * as React from 'react';
2
2
  import { type ComponentType, createContext, type PropsWithChildren, useContext } from 'react';
3
3
  import { type PropType } from '@elementor/editor-props';
4
4
 
5
- type ControlAdornmentsItems = Array< {
5
+ export type AdornmentComponent = ComponentType< { customContext?: { path: string[]; propType: PropType } } >;
6
+ type ControlAdornmentsItem = {
6
7
  id: string;
7
- Adornment: ComponentType< { customContext?: { path: string[]; propType: PropType } } >;
8
- } >;
8
+ Adornment: AdornmentComponent;
9
+ };
9
10
 
10
11
  type ControlAdornmentsContext = {
11
- items?: ControlAdornmentsItems;
12
+ items?: ControlAdornmentsItem[];
12
13
  };
13
14
 
14
15
  const Context = createContext< ControlAdornmentsContext | null >( null );
@@ -4,13 +4,15 @@ import { type PropValue } from '@elementor/editor-props';
4
4
 
5
5
  import { useBoundProp } from './bound-prop-context';
6
6
 
7
+ type ControlComponent = ComponentType< object & { OriginalControl: ComponentType } >;
7
8
  type ControlReplacement = {
8
- component: ComponentType;
9
+ component: ControlComponent;
9
10
  condition: ( { value }: ConditionArgs ) => boolean;
10
11
  };
11
12
 
12
13
  type ConditionArgs = {
13
14
  value: PropValue;
15
+ placeholder?: PropValue;
14
16
  };
15
17
 
16
18
  type Props = PropsWithChildren< { replacements: ControlReplacement[] } >;
@@ -21,16 +23,20 @@ export const ControlReplacementsProvider = ( { replacements, children }: Props )
21
23
  return <ControlReplacementContext.Provider value={ replacements }>{ children }</ControlReplacementContext.Provider>;
22
24
  };
23
25
 
24
- export const useControlReplacement = ( OriginalComponent: ComponentType ) => {
25
- const { value } = useBoundProp();
26
+ export const useControlReplacement = ( OriginalComponent: ControlComponent ) => {
27
+ const { value, placeholder } = useBoundProp();
26
28
  const replacements = useContext( ControlReplacementContext );
27
29
 
28
30
  try {
29
- const replacement = replacements.find( ( r ) => r.condition( { value } ) );
31
+ const replacement = replacements.find( ( r ) => r.condition( { value, placeholder } ) );
30
32
 
31
- return replacement?.component ?? OriginalComponent;
33
+ return {
34
+ ControlToRender: replacement?.component ?? OriginalComponent,
35
+ OriginalControl: OriginalComponent,
36
+ isReplaced: !! replacement,
37
+ };
32
38
  } catch {
33
- return OriginalComponent;
39
+ return { ControlToRender: OriginalComponent, OriginalControl: OriginalComponent };
34
40
  }
35
41
  };
36
42
 
@@ -47,44 +53,3 @@ export const createControlReplacementsRegistry = () => {
47
53
 
48
54
  return { registerControlReplacement, getControlReplacements };
49
55
  };
50
-
51
- export const SlotChildren = ( {
52
- children,
53
- whitelist = [],
54
- sorted = false,
55
- props = {},
56
- }: {
57
- children: React.ReactNode;
58
- whitelist: React.FC[];
59
- sorted?: boolean;
60
- props?: Record< string, unknown >;
61
- } ) => {
62
- const filtered = (
63
- ! whitelist.length
64
- ? React.Children.toArray( children )
65
- : React.Children.toArray( children ).filter(
66
- ( child ) => React.isValidElement( child ) && whitelist.includes( child.type as React.FC )
67
- )
68
- ) as React.ReactElement[];
69
-
70
- if ( sorted ) {
71
- sort( filtered, whitelist );
72
- }
73
-
74
- return filtered.map( ( child, index ) => (
75
- <React.Fragment key={ index }>{ React.cloneElement( child, props ) }</React.Fragment>
76
- ) );
77
- };
78
-
79
- const sort = ( childrenArray: React.ReactElement[], whitelist: unknown[] ) => {
80
- childrenArray.sort( ( a, b ) => {
81
- const aIndex = whitelist.indexOf( a.type );
82
- const bIndex = whitelist.indexOf( b.type );
83
-
84
- if ( aIndex === -1 || bIndex === -1 ) {
85
- return 0;
86
- }
87
-
88
- return aIndex - bIndex;
89
- } );
90
- };