@elementor/editor-controls 1.5.0 → 3.32.0-21

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 (71) hide show
  1. package/CHANGELOG.md +0 -22
  2. package/dist/index.d.mts +95 -25
  3. package/dist/index.d.ts +95 -25
  4. package/dist/index.js +2045 -1041
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +1962 -964
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +18 -18
  9. package/src/components/control-toggle-button-group.tsx +78 -14
  10. package/src/components/floating-bar.tsx +45 -0
  11. package/src/components/{font-family-selector.tsx → item-selector.tsx} +62 -50
  12. package/src/components/repeater.tsx +1 -1
  13. package/src/components/restricted-link-infotip.tsx +76 -0
  14. package/src/components/size-control/size-input.tsx +8 -7
  15. package/src/components/size-control/text-field-inner-selection.tsx +60 -14
  16. package/src/components/text-field-popover.tsx +30 -7
  17. package/src/components/unstable-repeater/actions/add-item-action.tsx +50 -0
  18. package/src/components/unstable-repeater/actions/disable-item-action.tsx +39 -0
  19. package/src/components/unstable-repeater/actions/duplicate-item-action.tsx +32 -0
  20. package/src/components/unstable-repeater/actions/remove-item-action.tsx +27 -0
  21. package/src/components/unstable-repeater/context/repeater-context.tsx +137 -0
  22. package/src/components/unstable-repeater/header/header.tsx +23 -0
  23. package/src/components/unstable-repeater/index.ts +5 -0
  24. package/src/components/unstable-repeater/items/edit-item-popover.tsx +28 -0
  25. package/src/components/unstable-repeater/items/item.tsx +71 -0
  26. package/src/components/unstable-repeater/items/items-container.tsx +49 -0
  27. package/src/components/unstable-repeater/items/use-popover.tsx +26 -0
  28. package/src/{locations.ts → components/unstable-repeater/locations.ts} +9 -1
  29. package/src/components/unstable-repeater/types.ts +26 -0
  30. package/src/components/unstable-repeater/unstable-repeater.tsx +24 -0
  31. package/src/control-actions/control-actions.tsx +3 -20
  32. package/src/control-replacements.tsx +41 -0
  33. package/src/controls/background-control/background-control.tsx +1 -8
  34. package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +17 -16
  35. package/src/controls/equal-unequal-sizes-control.tsx +2 -9
  36. package/src/controls/filter-control/drop-shadow-item-content.tsx +4 -6
  37. package/src/controls/filter-control/drop-shadow-item-label.tsx +2 -2
  38. package/src/controls/filter-repeater-control.tsx +149 -110
  39. package/src/controls/font-family-control/font-family-control.tsx +22 -10
  40. package/src/controls/key-value-control.tsx +9 -6
  41. package/src/controls/link-control.tsx +8 -91
  42. package/src/controls/linked-dimensions-control.tsx +3 -16
  43. package/src/controls/number-control.tsx +10 -1
  44. package/src/controls/position-control.tsx +4 -16
  45. package/src/controls/repeatable-control.tsx +8 -5
  46. package/src/controls/select-control.tsx +7 -2
  47. package/src/controls/selection-size-control.tsx +74 -0
  48. package/src/controls/size-control.tsx +181 -126
  49. package/src/controls/stroke-control.tsx +2 -2
  50. package/src/controls/toggle-control.tsx +3 -2
  51. package/src/controls/transform-control/functions/axis-row.tsx +4 -2
  52. package/src/controls/transform-control/functions/move.tsx +2 -1
  53. package/src/controls/transform-control/functions/rotate.tsx +48 -0
  54. package/src/controls/transform-control/functions/scale-axis-row.tsx +32 -0
  55. package/src/controls/transform-control/functions/scale.tsx +45 -0
  56. package/src/controls/transform-control/functions/skew.tsx +43 -0
  57. package/src/controls/transform-control/transform-content.tsx +60 -23
  58. package/src/controls/transform-control/transform-icon.tsx +10 -2
  59. package/src/controls/transform-control/transform-label.tsx +39 -2
  60. package/src/controls/transform-control/transform-repeater-control.tsx +2 -10
  61. package/src/controls/transform-control/types.ts +58 -0
  62. package/src/controls/transform-control/use-transform-tabs-history.tsx +107 -0
  63. package/src/controls/transition-control/data.ts +34 -0
  64. package/src/controls/transition-control/transition-repeater-control.tsx +63 -0
  65. package/src/controls/transition-control/transition-selector.tsx +88 -0
  66. package/src/controls/unstable-transform-control/unstable-transform-repeater-control.tsx +35 -0
  67. package/src/hooks/use-filtered-items-list.ts +24 -0
  68. package/src/hooks/use-size-extended-options.ts +1 -6
  69. package/src/index.ts +13 -3
  70. package/src/utils/size-control.ts +12 -6
  71. package/src/hooks/use-filtered-font-families.ts +0 -24
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": "1.5.0",
4
+ "version": "3.32.0-21",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -19,11 +19,11 @@
19
19
  },
20
20
  "repository": {
21
21
  "type": "git",
22
- "url": "git+https://github.com/elementor/elementor-packages.git",
22
+ "url": "git+https://github.com/elementor/elementor.git",
23
23
  "directory": "packages/libs/editor-controls"
24
24
  },
25
25
  "bugs": {
26
- "url": "https://github.com/elementor/elementor-packages/issues"
26
+ "url": "https://github.com/elementor/elementor/issues"
27
27
  },
28
28
  "publishConfig": {
29
29
  "access": "public"
@@ -40,21 +40,21 @@
40
40
  "dev": "tsup --config=../../tsup.dev.ts"
41
41
  },
42
42
  "dependencies": {
43
- "@elementor/editor-current-user": "0.6.1",
44
- "@elementor/editor-elements": "0.9.2",
45
- "@elementor/editor-props": "0.18.0",
46
- "@elementor/editor-responsive": "0.13.6",
47
- "@elementor/editor-ui": "0.14.2",
48
- "@elementor/editor-v1-adapters": "0.12.1",
49
- "@elementor/env": "0.3.5",
50
- "@elementor/http-client": "0.3.0",
51
- "@elementor/icons": "1.46.0",
52
- "@elementor/locations": "0.8.0",
53
- "@elementor/query": "0.2.4",
54
- "@elementor/session": "0.1.0",
55
- "@elementor/ui": "1.36.0",
56
- "@elementor/utils": "0.5.0",
57
- "@elementor/wp-media": "0.6.1",
43
+ "@elementor/editor-current-user": "3.32.0-21",
44
+ "@elementor/editor-elements": "3.32.0-21",
45
+ "@elementor/editor-props": "3.32.0-21",
46
+ "@elementor/editor-responsive": "3.32.0-21",
47
+ "@elementor/editor-ui": "3.32.0-21",
48
+ "@elementor/editor-v1-adapters": "3.32.0-21",
49
+ "@elementor/env": "3.32.0-21",
50
+ "@elementor/http-client": "3.32.0-21",
51
+ "@elementor/icons": "^1.51.1",
52
+ "@elementor/locations": "3.32.0-21",
53
+ "@elementor/query": "3.32.0-21",
54
+ "@elementor/session": "3.32.0-21",
55
+ "@elementor/ui": "1.36.2",
56
+ "@elementor/utils": "3.32.0-21",
57
+ "@elementor/wp-media": "3.32.0-21",
58
58
  "@wordpress/i18n": "^5.13.0"
59
59
  },
60
60
  "devDependencies": {
@@ -42,6 +42,21 @@ const StyledToggleButtonGroup = styled( ToggleButtonGroup )`
42
42
  }
43
43
  `;
44
44
 
45
+ const StyledToggleButton = styled( ToggleButton, {
46
+ shouldForwardProp: ( prop ) => prop !== 'isPlaceholder',
47
+ } )< { isPlaceholder: boolean } >`
48
+ ${ ( { theme, isPlaceholder } ) =>
49
+ isPlaceholder &&
50
+ `
51
+ color: ${ theme.palette.text.tertiary };
52
+ background-color: ${ theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.02)' };
53
+
54
+ &:hover {
55
+ background-color: ${ theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.04)' };
56
+ }
57
+ ` }
58
+ `;
59
+
45
60
  type ExclusiveValue< TValue > = TValue;
46
61
  type NonExclusiveValue< TValue > = TValue[];
47
62
 
@@ -52,6 +67,7 @@ type Props< TValue > = {
52
67
  items: ToggleButtonGroupItem< TValue | null >[];
53
68
  maxItems?: number;
54
69
  fullWidth?: boolean;
70
+ placeholder?: TValue | TValue[];
55
71
  } & (
56
72
  | {
57
73
  exclusive?: false;
@@ -75,12 +91,15 @@ export const ControlToggleButtonGroup = < TValue, >( {
75
91
  exclusive = false,
76
92
  fullWidth = false,
77
93
  disabled,
94
+ placeholder,
78
95
  }: Props< TValue > ) => {
79
96
  const shouldSliceItems = exclusive && maxItems !== undefined && items.length > maxItems;
80
97
  const menuItems = shouldSliceItems ? items.slice( maxItems - 1 ) : [];
81
98
  const fixedItems = shouldSliceItems ? items.slice( 0, maxItems - 1 ) : items;
82
99
 
83
- const isRtl = 'rtl' === useTheme().direction;
100
+ const theme = useTheme();
101
+ const isRtl = 'rtl' === theme.direction;
102
+
84
103
  const handleChange = (
85
104
  _: React.MouseEvent< HTMLElement >,
86
105
  newValue: typeof exclusive extends true ? ExclusiveValue< TValue > : NonExclusiveValue< TValue >
@@ -92,10 +111,42 @@ export const ControlToggleButtonGroup = < TValue, >( {
92
111
  const isOffLimits = menuItems?.length;
93
112
  const itemsCount = isOffLimits ? fixedItems.length + 1 : fixedItems.length;
94
113
  const templateColumnsSuffix = isOffLimits ? 'auto' : '';
95
-
96
114
  return `repeat(${ itemsCount }, minmax(0, 25%)) ${ templateColumnsSuffix }`;
97
115
  }, [ menuItems?.length, fixedItems.length ] );
98
116
 
117
+ const shouldShowExclusivePlaceholder = exclusive && ( value === null || value === undefined || value === '' );
118
+
119
+ const nonExclusiveSelectedValues =
120
+ ! exclusive && Array.isArray( value )
121
+ ? value
122
+ .map( ( v ) => ( typeof v === 'string' ? v : '' ) )
123
+ .join( ' ' )
124
+ .trim()
125
+ .split( /\s+/ )
126
+ .filter( Boolean )
127
+ : [];
128
+
129
+ const shouldShowNonExclusivePlaceholder = ! exclusive && nonExclusiveSelectedValues.length === 0;
130
+
131
+ const getPlaceholderArray = ( placeholderValue: TValue | TValue[] | undefined ): string[] => {
132
+ if ( Array.isArray( placeholderValue ) ) {
133
+ return placeholderValue.flatMap( ( p ) => {
134
+ if ( typeof p === 'string' ) {
135
+ return p.trim().split( /\s+/ ).filter( Boolean );
136
+ }
137
+ return [];
138
+ } );
139
+ }
140
+
141
+ if ( typeof placeholderValue === 'string' ) {
142
+ return placeholderValue.trim().split( /\s+/ ).filter( Boolean );
143
+ }
144
+
145
+ return [];
146
+ };
147
+
148
+ const placeholderArray = getPlaceholderArray( placeholder );
149
+
99
150
  return (
100
151
  <ControlActions>
101
152
  <StyledToggleButtonGroup
@@ -111,17 +162,30 @@ export const ControlToggleButtonGroup = < TValue, >( {
111
162
  width: `100%`,
112
163
  } }
113
164
  >
114
- { fixedItems.map( ( { label, value: buttonValue, renderContent: Content, showTooltip } ) => (
115
- <ConditionalTooltip
116
- key={ buttonValue as string }
117
- label={ label }
118
- showTooltip={ showTooltip || false }
119
- >
120
- <ToggleButton value={ buttonValue } aria-label={ label } size={ size } fullWidth={ fullWidth }>
121
- <Content size={ size } />
122
- </ToggleButton>
123
- </ConditionalTooltip>
124
- ) ) }
165
+ { fixedItems.map( ( { label, value: buttonValue, renderContent: Content, showTooltip } ) => {
166
+ const isPlaceholder =
167
+ placeholderArray.length > 0 &&
168
+ placeholderArray.includes( buttonValue as string ) &&
169
+ ( shouldShowExclusivePlaceholder || shouldShowNonExclusivePlaceholder );
170
+
171
+ return (
172
+ <ConditionalTooltip
173
+ key={ buttonValue as string }
174
+ label={ label }
175
+ showTooltip={ showTooltip || false }
176
+ >
177
+ <StyledToggleButton
178
+ value={ buttonValue }
179
+ aria-label={ label }
180
+ size={ size }
181
+ fullWidth={ fullWidth }
182
+ isPlaceholder={ isPlaceholder }
183
+ >
184
+ <Content size={ size } />
185
+ </StyledToggleButton>
186
+ </ConditionalTooltip>
187
+ );
188
+ } ) }
125
189
 
126
190
  { menuItems.length && exclusive && (
127
191
  <SplitButtonGroup
@@ -194,7 +258,7 @@ const SplitButtonGroup = < TValue, >( {
194
258
  aria-pressed={ undefined }
195
259
  onClick={ onMenuToggle }
196
260
  ref={ menuButtonRef }
197
- value={ '__chevron-icon-button__' }
261
+ value="__chevron-icon-button__"
198
262
  >
199
263
  <ChevronDownIcon fontSize={ size } />
200
264
  </ToggleButton>
@@ -0,0 +1,45 @@
1
+ import * as React from 'react';
2
+ import { createContext, type PropsWithChildren, type ReactElement, useContext, useState } from 'react';
3
+ import { styled, UnstableFloatingActionBar } from '@elementor/ui';
4
+
5
+ // CSS hack to hide empty floating bars.
6
+ const FloatingBarContainer = styled( 'span' )`
7
+ display: contents;
8
+
9
+ .MuiFloatingActionBar-popper:has( .MuiFloatingActionBar-actions:empty ) {
10
+ display: none;
11
+ }
12
+
13
+ .MuiFloatingActionBar-popper {
14
+ z-index: 1000;
15
+ }
16
+ `;
17
+
18
+ const FloatingActionsContext = createContext< null | {
19
+ open: boolean;
20
+ setOpen: React.Dispatch< React.SetStateAction< boolean > >;
21
+ } >( null );
22
+
23
+ export function FloatingActionsBar( { actions, children }: PropsWithChildren< { actions: ReactElement[] } > ) {
24
+ const [ open, setOpen ] = useState< boolean >( false );
25
+
26
+ return (
27
+ <FloatingActionsContext.Provider value={ { open, setOpen } }>
28
+ <FloatingBarContainer>
29
+ <UnstableFloatingActionBar actions={ actions } open={ open || undefined }>
30
+ { children as ReactElement }
31
+ </UnstableFloatingActionBar>
32
+ </FloatingBarContainer>
33
+ </FloatingActionsContext.Provider>
34
+ );
35
+ }
36
+
37
+ export function useFloatingActionsBar() {
38
+ const context = useContext( FloatingActionsContext );
39
+
40
+ if ( ! context ) {
41
+ throw new Error( 'useFloatingActions must be used within a FloatingActionsBar' );
42
+ }
43
+
44
+ return context;
45
+ }
@@ -1,35 +1,45 @@
1
1
  import * as React from 'react';
2
- import { useEffect, useState } from 'react';
2
+ import { useCallback, useEffect, useState } from 'react';
3
3
  import { PopoverBody, PopoverHeader, PopoverMenuList, PopoverSearch } from '@elementor/editor-ui';
4
- import { TextIcon } from '@elementor/icons';
5
4
  import { Box, Divider, Link, Stack, Typography } from '@elementor/ui';
6
5
  import { debounce } from '@elementor/utils';
7
6
  import { __ } from '@wordpress/i18n';
8
7
 
9
- import { enqueueFont } from '../controls/font-family-control/enqueue-font';
10
- import { type FontCategory } from '../controls/font-family-control/font-family-control';
11
- import { type FontListItem, useFilteredFontFamilies } from '../hooks/use-filtered-font-families';
8
+ import { type SelectableItem, useFilteredItemsList } from '../hooks/use-filtered-items-list';
12
9
 
13
- const SIZE = 'tiny';
10
+ export type Category = {
11
+ label: string;
12
+ items: string[];
13
+ };
14
14
 
15
- type FontFamilySelectorProps = {
16
- fontFamilies: FontCategory[];
17
- fontFamily: string | null;
18
- onFontFamilyChange: ( fontFamily: string ) => void;
15
+ type ItemSelectorProps = {
16
+ itemsList: Category[];
17
+ selectedItem: string | null;
18
+ onItemChange: ( item: string ) => void;
19
19
  onClose: () => void;
20
20
  sectionWidth: number;
21
+ title: string;
22
+ itemStyle?: ( item: SelectableItem ) => React.CSSProperties;
23
+ onDebounce?: ( name: string ) => void;
24
+ icon: React.ElementType< { fontSize: string } >;
21
25
  };
22
26
 
23
- export const FontFamilySelector = ( {
24
- fontFamilies,
25
- fontFamily,
26
- onFontFamilyChange,
27
+ export const ItemSelector = ( {
28
+ itemsList,
29
+ selectedItem,
30
+ onItemChange,
27
31
  onClose,
28
32
  sectionWidth,
29
- }: FontFamilySelectorProps ) => {
33
+ title,
34
+ itemStyle = () => ( {} ),
35
+ onDebounce = () => {},
36
+ icon,
37
+ }: ItemSelectorProps ) => {
30
38
  const [ searchValue, setSearchValue ] = useState( '' );
31
39
 
32
- const filteredFontFamilies = useFilteredFontFamilies( fontFamilies, searchValue );
40
+ const filteredItemsList = useFilteredItemsList( itemsList, searchValue );
41
+
42
+ const IconComponent = icon;
33
43
 
34
44
  const handleSearch = ( value: string ) => {
35
45
  setSearchValue( value );
@@ -42,12 +52,7 @@ export const FontFamilySelector = ( {
42
52
 
43
53
  return (
44
54
  <PopoverBody width={ sectionWidth }>
45
- <PopoverHeader
46
- title={ __( 'Font Family', 'elementor' ) }
47
- onClose={ handleClose }
48
- icon={ <TextIcon fontSize={ SIZE } /> }
49
- />
50
-
55
+ <PopoverHeader title={ title } onClose={ handleClose } icon={ <IconComponent fontSize="tiny" /> } />
51
56
  <PopoverSearch
52
57
  value={ searchValue }
53
58
  onSearch={ handleSearch }
@@ -56,12 +61,14 @@ export const FontFamilySelector = ( {
56
61
 
57
62
  <Divider />
58
63
 
59
- { filteredFontFamilies.length > 0 ? (
60
- <FontList
61
- fontListItems={ filteredFontFamilies }
62
- setFontFamily={ onFontFamilyChange }
64
+ { filteredItemsList.length > 0 ? (
65
+ <ItemList
66
+ itemListItems={ filteredItemsList }
67
+ setSelectedItem={ onItemChange }
63
68
  handleClose={ handleClose }
64
- fontFamily={ fontFamily }
69
+ selectedItem={ selectedItem }
70
+ itemStyle={ itemStyle }
71
+ onDebounce={ onDebounce }
65
72
  />
66
73
  ) : (
67
74
  <Stack
@@ -70,9 +77,9 @@ export const FontFamilySelector = ( {
70
77
  height="100%"
71
78
  p={ 2.5 }
72
79
  gap={ 1.5 }
73
- overflow={ 'hidden' }
80
+ overflow="hidden"
74
81
  >
75
- <TextIcon fontSize="large" />
82
+ <IconComponent fontSize="large" />
76
83
  <Box sx={ { maxWidth: 160, overflow: 'hidden' } }>
77
84
  <Typography align="center" variant="subtitle2" color="text.secondary">
78
85
  { __( 'Sorry, nothing matched', 'elementor' ) }
@@ -80,11 +87,7 @@ export const FontFamilySelector = ( {
80
87
  <Typography
81
88
  variant="subtitle2"
82
89
  color="text.secondary"
83
- sx={ {
84
- display: 'flex',
85
- width: '100%',
86
- justifyContent: 'center',
87
- } }
90
+ sx={ { display: 'flex', width: '100%', justifyContent: 'center' } }
88
91
  >
89
92
  <span>&ldquo;</span>
90
93
  <span style={ { maxWidth: '80%', overflow: 'hidden', textOverflow: 'ellipsis' } }>
@@ -115,42 +118,51 @@ export const FontFamilySelector = ( {
115
118
  );
116
119
  };
117
120
 
118
- type FontListProps = {
119
- fontListItems: FontListItem[];
120
- setFontFamily: ( fontFamily: string ) => void;
121
+ type ItemListProps = {
122
+ itemListItems: SelectableItem[];
123
+ setSelectedItem: ( item: string ) => void;
121
124
  handleClose: () => void;
122
- fontFamily: string | null;
125
+ selectedItem: string | null;
126
+ itemStyle?: ( item: SelectableItem ) => React.CSSProperties;
127
+ onDebounce?: ( name: string ) => void;
123
128
  };
124
129
 
125
- const FontList = ( { fontListItems, setFontFamily, handleClose, fontFamily }: FontListProps ) => {
126
- const selectedItem = fontListItems.find( ( item ) => item.value === fontFamily );
130
+ const ItemList = ( {
131
+ itemListItems,
132
+ setSelectedItem,
133
+ handleClose,
134
+ selectedItem,
135
+ itemStyle = () => ( {} ),
136
+ onDebounce = () => {},
137
+ }: ItemListProps ) => {
138
+ const selectedItemFound = itemListItems.find( ( item ) => item.value === selectedItem );
127
139
 
128
140
  const debouncedVirtualizeChange = useDebounce( ( { getVirtualIndexes }: { getVirtualIndexes: () => number[] } ) => {
129
141
  getVirtualIndexes().forEach( ( index ) => {
130
- const item = fontListItems[ index ];
131
- if ( item && item.type === 'font' ) {
132
- enqueueFont( item.value );
142
+ const item = itemListItems[ index ];
143
+ if ( item && item.type === 'item' ) {
144
+ onDebounce( item.value );
133
145
  }
134
146
  } );
135
147
  }, 100 );
136
148
 
149
+ const memoizedItemStyle = useCallback( ( item: SelectableItem ) => itemStyle( item ), [ itemStyle ] );
150
+
137
151
  return (
138
152
  <PopoverMenuList
139
- items={ fontListItems }
140
- selectedValue={ selectedItem?.value }
153
+ items={ itemListItems }
154
+ selectedValue={ selectedItemFound?.value }
141
155
  onChange={ debouncedVirtualizeChange }
142
- onSelect={ setFontFamily }
156
+ onSelect={ setSelectedItem }
143
157
  onClose={ handleClose }
144
- itemStyle={ ( item ) => ( { fontFamily: item.value } ) }
145
- data-testid="font-list"
158
+ itemStyle={ memoizedItemStyle }
159
+ data-testid="item-list"
146
160
  />
147
161
  );
148
162
  };
149
163
 
150
164
  const useDebounce = < TArgs extends unknown[] >( fn: ( ...args: TArgs ) => void, delay: number ) => {
151
165
  const [ debouncedFn ] = useState( () => debounce( fn, delay ) );
152
-
153
166
  useEffect( () => () => debouncedFn.cancel(), [ debouncedFn ] );
154
-
155
167
  return debouncedFn;
156
168
  };
@@ -19,9 +19,9 @@ import { __ } from '@wordpress/i18n';
19
19
 
20
20
  import { ControlAdornments } from '../control-adornments/control-adornments';
21
21
  import { useSyncExternalState } from '../hooks/use-sync-external-state';
22
- import { RepeaterItemIconSlot, RepeaterItemLabelSlot } from '../locations';
23
22
  import { SectionContent } from './section-content';
24
23
  import { SortableItem, SortableProvider } from './sortable';
24
+ import { RepeaterItemIconSlot, RepeaterItemLabelSlot } from './unstable-repeater/locations';
25
25
 
26
26
  const SIZE = 'tiny';
27
27
 
@@ -0,0 +1,76 @@
1
+ import * as React from 'react';
2
+ import { type PropsWithChildren } from 'react';
3
+ import { type LinkInLinkRestriction, selectElement } from '@elementor/editor-elements';
4
+ import { InfoCircleFilledIcon } from '@elementor/icons';
5
+ import { Alert, AlertAction, AlertTitle, Box, Infotip, Link } from '@elementor/ui';
6
+ import { __ } from '@wordpress/i18n';
7
+
8
+ const learnMoreButton = {
9
+ label: __( 'Learn More', 'elementor' ),
10
+ href: 'https://go.elementor.com/element-link-inside-link-infotip',
11
+ };
12
+
13
+ const INFOTIP_CONTENT = {
14
+ descendant: __(
15
+ 'To add a link to this element, first remove the link from the elements inside of it.',
16
+ 'elementor'
17
+ ),
18
+ ancestor: __( 'To add a link to this element, first remove the link from its parent container.', 'elementor' ),
19
+ };
20
+
21
+ type RestrictedLinkInfotipProps = PropsWithChildren< {
22
+ linkInLinkRestriction: LinkInLinkRestriction;
23
+ isVisible: boolean;
24
+ } >;
25
+
26
+ export const RestrictedLinkInfotip: React.FC< RestrictedLinkInfotipProps > = ( {
27
+ linkInLinkRestriction,
28
+ isVisible,
29
+ children,
30
+ } ) => {
31
+ const { shouldRestrict, reason, elementId } = linkInLinkRestriction;
32
+
33
+ const handleTakeMeClick = () => {
34
+ if ( elementId ) {
35
+ selectElement( elementId );
36
+ }
37
+ };
38
+
39
+ const content = (
40
+ <Alert
41
+ severity="secondary"
42
+ icon={ <InfoCircleFilledIcon /> }
43
+ action={
44
+ <AlertAction
45
+ sx={ { width: 'fit-content' } }
46
+ variant="contained"
47
+ color="secondary"
48
+ onClick={ handleTakeMeClick }
49
+ >
50
+ { __( 'Take me there', 'elementor' ) }
51
+ </AlertAction>
52
+ }
53
+ >
54
+ <AlertTitle>{ __( 'Nested links', 'elementor' ) }</AlertTitle>
55
+ <Box component="span">
56
+ { INFOTIP_CONTENT[ reason ?? 'descendant' ] }{ ' ' }
57
+ <Link href={ learnMoreButton.href } target="_blank" color="info.main">
58
+ { learnMoreButton.label }
59
+ </Link>
60
+ </Box>
61
+ </Alert>
62
+ );
63
+
64
+ return shouldRestrict && isVisible ? (
65
+ <Infotip
66
+ placement="right"
67
+ content={ content }
68
+ color="secondary"
69
+ slotProps={ { popper: { sx: { width: 300 } } } }
70
+ >
71
+ <Box>{ children }</Box>
72
+ </Infotip>
73
+ ) : (
74
+ <>{ children }</>
75
+ );
76
+ };
@@ -1,22 +1,22 @@
1
1
  import * as React from 'react';
2
2
  import { useRef } from 'react';
3
- import { PencilIcon } from '@elementor/icons';
3
+ import { MathFunctionIcon } from '@elementor/icons';
4
4
  import { Box, InputAdornment, type PopupState } from '@elementor/ui';
5
5
 
6
6
  import ControlActions from '../../control-actions/control-actions';
7
- import { type DegreeUnit, type ExtendedOption, isUnitExtendedOption, type Unit } from '../../utils/size-control';
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
10
  type SizeInputProps = {
11
- unit: Unit | DegreeUnit | ExtendedOption;
11
+ unit: Unit | ExtendedOption;
12
12
  size: number | string;
13
13
  placeholder?: string;
14
14
  startIcon?: React.ReactNode;
15
- units: ( Unit | DegreeUnit | ExtendedOption )[];
15
+ units: ( Unit | ExtendedOption )[];
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 | DegreeUnit | ExtendedOption ) => void;
19
+ handleUnitChange: ( unit: Unit | ExtendedOption ) => void;
20
20
  handleSizeChange: ( event: React.ChangeEvent< HTMLInputElement > ) => void;
21
21
  popupState: PopupState;
22
22
  disabled?: boolean;
@@ -72,6 +72,7 @@ export const SizeInput = ( {
72
72
 
73
73
  const inputProps = {
74
74
  ...popupAttributes,
75
+ readOnly: isUnitExtendedOption( unit ),
75
76
  autoComplete: 'off',
76
77
  onClick,
77
78
  onFocus,
@@ -87,7 +88,7 @@ export const SizeInput = ( {
87
88
  onClick={ handleUnitChange }
88
89
  value={ unit }
89
90
  alternativeOptionLabels={ {
90
- custom: <PencilIcon fontSize="small" />,
91
+ custom: <MathFunctionIcon fontSize="tiny" />,
91
92
  } }
92
93
  menuItemsAttributes={
93
94
  units.includes( 'custom' )
@@ -116,8 +117,8 @@ export const SizeInput = ( {
116
117
  } }
117
118
  onKeyUp={ handleKeyUp }
118
119
  onBlur={ onBlur }
119
- shouldBlockInput={ isUnitExtendedOption( unit ) }
120
120
  inputProps={ inputProps }
121
+ isPopoverOpen={ popupState.isOpen }
121
122
  />
122
123
  </Box>
123
124
  </ControlActions>