@elementor/editor-controls 1.3.0 → 3.32.0-20
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/CHANGELOG.md +18 -0
- package/dist/index.d.mts +104 -26
- package/dist/index.d.ts +104 -26
- package/dist/index.js +2271 -1119
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2147 -990
- package/dist/index.mjs.map +1 -1
- package/package.json +18 -18
- package/src/components/control-toggle-button-group.tsx +78 -14
- package/src/components/floating-bar.tsx +45 -0
- package/src/components/item-selector.tsx +168 -0
- package/src/components/repeater.tsx +23 -12
- package/src/components/restricted-link-infotip.tsx +76 -0
- package/src/components/size-control/size-input.tsx +4 -3
- package/src/components/size-control/text-field-inner-selection.tsx +60 -14
- package/src/components/text-field-popover.tsx +30 -7
- package/src/components/unstable-repeater/actions/add-item-action.tsx +50 -0
- package/src/components/unstable-repeater/actions/disable-item-action.tsx +39 -0
- package/src/components/unstable-repeater/actions/duplicate-item-action.tsx +32 -0
- package/src/components/unstable-repeater/actions/remove-item-action.tsx +27 -0
- package/src/components/unstable-repeater/context/repeater-context.tsx +137 -0
- package/src/components/unstable-repeater/header/header.tsx +23 -0
- package/src/components/unstable-repeater/index.ts +5 -0
- package/src/components/unstable-repeater/items/edit-item-popover.tsx +28 -0
- package/src/components/unstable-repeater/items/item.tsx +71 -0
- package/src/components/unstable-repeater/items/items-container.tsx +49 -0
- package/src/components/unstable-repeater/items/use-popover.tsx +26 -0
- package/src/{locations.ts → components/unstable-repeater/locations.ts} +9 -1
- package/src/components/unstable-repeater/types.ts +26 -0
- package/src/components/unstable-repeater/unstable-repeater.tsx +24 -0
- package/src/control-actions/control-actions.tsx +3 -20
- package/src/control-replacements.tsx +41 -0
- package/src/controls/background-control/background-control.tsx +1 -8
- package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +17 -16
- package/src/controls/color-control.tsx +12 -1
- package/src/controls/equal-unequal-sizes-control.tsx +2 -9
- package/src/controls/filter-control/drop-shadow-item-content.tsx +67 -0
- package/src/controls/filter-control/drop-shadow-item-label.tsx +20 -0
- package/src/controls/filter-repeater-control.tsx +214 -88
- package/src/controls/font-family-control/font-family-control.tsx +22 -10
- package/src/controls/key-value-control.tsx +64 -50
- package/src/controls/link-control.tsx +8 -91
- package/src/controls/linked-dimensions-control.tsx +3 -16
- package/src/controls/number-control.tsx +10 -1
- package/src/controls/position-control.tsx +4 -16
- package/src/controls/repeatable-control.tsx +56 -34
- package/src/controls/select-control.tsx +7 -2
- package/src/controls/selection-size-control.tsx +74 -0
- package/src/controls/size-control.tsx +189 -121
- package/src/controls/stroke-control.tsx +2 -2
- package/src/controls/text-control.tsx +33 -18
- package/src/controls/toggle-control.tsx +3 -2
- package/src/controls/transform-control/functions/axis-row.tsx +4 -2
- package/src/controls/transform-control/functions/move.tsx +2 -1
- package/src/controls/transform-control/functions/rotate.tsx +48 -0
- package/src/controls/transform-control/functions/scale-axis-row.tsx +32 -0
- package/src/controls/transform-control/functions/scale.tsx +45 -0
- package/src/controls/transform-control/functions/skew.tsx +43 -0
- package/src/controls/transform-control/transform-content.tsx +60 -23
- package/src/controls/transform-control/transform-icon.tsx +10 -2
- package/src/controls/transform-control/transform-label.tsx +39 -2
- package/src/controls/transform-control/transform-repeater-control.tsx +2 -10
- package/src/controls/transform-control/types.ts +58 -0
- package/src/controls/transform-control/use-transform-tabs-history.tsx +107 -0
- package/src/controls/transition-control/data.ts +34 -0
- package/src/controls/transition-control/transition-repeater-control.tsx +63 -0
- package/src/controls/transition-control/transition-selector.tsx +88 -0
- package/src/controls/unstable-transform-control/unstable-transform-repeater-control.tsx +35 -0
- package/src/hooks/use-filtered-items-list.ts +24 -0
- package/src/hooks/use-size-extended-options.ts +1 -6
- package/src/index.ts +13 -3
- package/src/utils/size-control.ts +10 -2
- package/src/components/font-family-selector.tsx +0 -158
- 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": "
|
|
4
|
+
"version": "3.32.0-20",
|
|
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
|
|
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
|
|
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": "
|
|
44
|
-
"@elementor/editor-elements": "
|
|
45
|
-
"@elementor/editor-props": "
|
|
46
|
-
"@elementor/editor-responsive": "
|
|
47
|
-
"@elementor/editor-ui": "
|
|
48
|
-
"@elementor/editor-v1-adapters": "
|
|
49
|
-
"@elementor/env": "
|
|
50
|
-
"@elementor/http-client": "
|
|
51
|
-
"@elementor/icons": "1.
|
|
52
|
-
"@elementor/locations": "
|
|
53
|
-
"@elementor/query": "
|
|
54
|
-
"@elementor/session": "
|
|
55
|
-
"@elementor/ui": "1.36.
|
|
56
|
-
"@elementor/utils": "
|
|
57
|
-
"@elementor/wp-media": "
|
|
43
|
+
"@elementor/editor-current-user": "3.32.0-20",
|
|
44
|
+
"@elementor/editor-elements": "3.32.0-20",
|
|
45
|
+
"@elementor/editor-props": "3.32.0-20",
|
|
46
|
+
"@elementor/editor-responsive": "3.32.0-20",
|
|
47
|
+
"@elementor/editor-ui": "3.32.0-20",
|
|
48
|
+
"@elementor/editor-v1-adapters": "3.32.0-20",
|
|
49
|
+
"@elementor/env": "3.32.0-20",
|
|
50
|
+
"@elementor/http-client": "3.32.0-20",
|
|
51
|
+
"@elementor/icons": "^1.51.1",
|
|
52
|
+
"@elementor/locations": "3.32.0-20",
|
|
53
|
+
"@elementor/query": "3.32.0-20",
|
|
54
|
+
"@elementor/session": "3.32.0-20",
|
|
55
|
+
"@elementor/ui": "1.36.2",
|
|
56
|
+
"@elementor/utils": "3.32.0-20",
|
|
57
|
+
"@elementor/wp-media": "3.32.0-20",
|
|
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
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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=
|
|
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
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
3
|
+
import { PopoverBody, PopoverHeader, PopoverMenuList, PopoverSearch } from '@elementor/editor-ui';
|
|
4
|
+
import { Box, Divider, Link, Stack, Typography } from '@elementor/ui';
|
|
5
|
+
import { debounce } from '@elementor/utils';
|
|
6
|
+
import { __ } from '@wordpress/i18n';
|
|
7
|
+
|
|
8
|
+
import { type SelectableItem, useFilteredItemsList } from '../hooks/use-filtered-items-list';
|
|
9
|
+
|
|
10
|
+
export type Category = {
|
|
11
|
+
label: string;
|
|
12
|
+
items: string[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type ItemSelectorProps = {
|
|
16
|
+
itemsList: Category[];
|
|
17
|
+
selectedItem: string | null;
|
|
18
|
+
onItemChange: ( item: string ) => void;
|
|
19
|
+
onClose: () => void;
|
|
20
|
+
sectionWidth: number;
|
|
21
|
+
title: string;
|
|
22
|
+
itemStyle?: ( item: SelectableItem ) => React.CSSProperties;
|
|
23
|
+
onDebounce?: ( name: string ) => void;
|
|
24
|
+
icon: React.ElementType< { fontSize: string } >;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const ItemSelector = ( {
|
|
28
|
+
itemsList,
|
|
29
|
+
selectedItem,
|
|
30
|
+
onItemChange,
|
|
31
|
+
onClose,
|
|
32
|
+
sectionWidth,
|
|
33
|
+
title,
|
|
34
|
+
itemStyle = () => ( {} ),
|
|
35
|
+
onDebounce = () => {},
|
|
36
|
+
icon,
|
|
37
|
+
}: ItemSelectorProps ) => {
|
|
38
|
+
const [ searchValue, setSearchValue ] = useState( '' );
|
|
39
|
+
|
|
40
|
+
const filteredItemsList = useFilteredItemsList( itemsList, searchValue );
|
|
41
|
+
|
|
42
|
+
const IconComponent = icon;
|
|
43
|
+
|
|
44
|
+
const handleSearch = ( value: string ) => {
|
|
45
|
+
setSearchValue( value );
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const handleClose = () => {
|
|
49
|
+
setSearchValue( '' );
|
|
50
|
+
onClose();
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<PopoverBody width={ sectionWidth }>
|
|
55
|
+
<PopoverHeader title={ title } onClose={ handleClose } icon={ <IconComponent fontSize="tiny" /> } />
|
|
56
|
+
<PopoverSearch
|
|
57
|
+
value={ searchValue }
|
|
58
|
+
onSearch={ handleSearch }
|
|
59
|
+
placeholder={ __( 'Search', 'elementor' ) }
|
|
60
|
+
/>
|
|
61
|
+
|
|
62
|
+
<Divider />
|
|
63
|
+
|
|
64
|
+
{ filteredItemsList.length > 0 ? (
|
|
65
|
+
<ItemList
|
|
66
|
+
itemListItems={ filteredItemsList }
|
|
67
|
+
setSelectedItem={ onItemChange }
|
|
68
|
+
handleClose={ handleClose }
|
|
69
|
+
selectedItem={ selectedItem }
|
|
70
|
+
itemStyle={ itemStyle }
|
|
71
|
+
onDebounce={ onDebounce }
|
|
72
|
+
/>
|
|
73
|
+
) : (
|
|
74
|
+
<Stack
|
|
75
|
+
alignItems="center"
|
|
76
|
+
justifyContent="center"
|
|
77
|
+
height="100%"
|
|
78
|
+
p={ 2.5 }
|
|
79
|
+
gap={ 1.5 }
|
|
80
|
+
overflow="hidden"
|
|
81
|
+
>
|
|
82
|
+
<IconComponent fontSize="large" />
|
|
83
|
+
<Box sx={ { maxWidth: 160, overflow: 'hidden' } }>
|
|
84
|
+
<Typography align="center" variant="subtitle2" color="text.secondary">
|
|
85
|
+
{ __( 'Sorry, nothing matched', 'elementor' ) }
|
|
86
|
+
</Typography>
|
|
87
|
+
<Typography
|
|
88
|
+
variant="subtitle2"
|
|
89
|
+
color="text.secondary"
|
|
90
|
+
sx={ { display: 'flex', width: '100%', justifyContent: 'center' } }
|
|
91
|
+
>
|
|
92
|
+
<span>“</span>
|
|
93
|
+
<span style={ { maxWidth: '80%', overflow: 'hidden', textOverflow: 'ellipsis' } }>
|
|
94
|
+
{ searchValue }
|
|
95
|
+
</span>
|
|
96
|
+
<span>”.</span>
|
|
97
|
+
</Typography>
|
|
98
|
+
</Box>
|
|
99
|
+
<Typography
|
|
100
|
+
align="center"
|
|
101
|
+
variant="caption"
|
|
102
|
+
color="text.secondary"
|
|
103
|
+
sx={ { display: 'flex', flexDirection: 'column' } }
|
|
104
|
+
>
|
|
105
|
+
{ __( 'Try something else.', 'elementor' ) }
|
|
106
|
+
<Link
|
|
107
|
+
color="secondary"
|
|
108
|
+
variant="caption"
|
|
109
|
+
component="button"
|
|
110
|
+
onClick={ () => setSearchValue( '' ) }
|
|
111
|
+
>
|
|
112
|
+
{ __( 'Clear & try again', 'elementor' ) }
|
|
113
|
+
</Link>
|
|
114
|
+
</Typography>
|
|
115
|
+
</Stack>
|
|
116
|
+
) }
|
|
117
|
+
</PopoverBody>
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
type ItemListProps = {
|
|
122
|
+
itemListItems: SelectableItem[];
|
|
123
|
+
setSelectedItem: ( item: string ) => void;
|
|
124
|
+
handleClose: () => void;
|
|
125
|
+
selectedItem: string | null;
|
|
126
|
+
itemStyle?: ( item: SelectableItem ) => React.CSSProperties;
|
|
127
|
+
onDebounce?: ( name: string ) => void;
|
|
128
|
+
};
|
|
129
|
+
|
|
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 );
|
|
139
|
+
|
|
140
|
+
const debouncedVirtualizeChange = useDebounce( ( { getVirtualIndexes }: { getVirtualIndexes: () => number[] } ) => {
|
|
141
|
+
getVirtualIndexes().forEach( ( index ) => {
|
|
142
|
+
const item = itemListItems[ index ];
|
|
143
|
+
if ( item && item.type === 'item' ) {
|
|
144
|
+
onDebounce( item.value );
|
|
145
|
+
}
|
|
146
|
+
} );
|
|
147
|
+
}, 100 );
|
|
148
|
+
|
|
149
|
+
const memoizedItemStyle = useCallback( ( item: SelectableItem ) => itemStyle( item ), [ itemStyle ] );
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<PopoverMenuList
|
|
153
|
+
items={ itemListItems }
|
|
154
|
+
selectedValue={ selectedItemFound?.value }
|
|
155
|
+
onChange={ debouncedVirtualizeChange }
|
|
156
|
+
onSelect={ setSelectedItem }
|
|
157
|
+
onClose={ handleClose }
|
|
158
|
+
itemStyle={ memoizedItemStyle }
|
|
159
|
+
data-testid="item-list"
|
|
160
|
+
/>
|
|
161
|
+
);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const useDebounce = < TArgs extends unknown[] >( fn: ( ...args: TArgs ) => void, delay: number ) => {
|
|
165
|
+
const [ debouncedFn ] = useState( () => debounce( fn, delay ) );
|
|
166
|
+
useEffect( () => () => debouncedFn.cancel(), [ debouncedFn ] );
|
|
167
|
+
return debouncedFn;
|
|
168
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
|
-
import { type PropKey } from '@elementor/editor-props';
|
|
3
|
+
import { type PropKey, type PropTypeUtil } from '@elementor/editor-props';
|
|
4
4
|
import { CopyIcon, EyeIcon, EyeOffIcon, PlusIcon, XIcon } from '@elementor/icons';
|
|
5
5
|
import {
|
|
6
6
|
bindPopover,
|
|
@@ -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
|
|
|
@@ -30,6 +30,16 @@ type AnchorEl = HTMLElement | null;
|
|
|
30
30
|
type Item< T > = {
|
|
31
31
|
disabled?: boolean;
|
|
32
32
|
} & T;
|
|
33
|
+
export type CollectionPropUtil< T > = PropTypeUtil< PropKey, T[] >;
|
|
34
|
+
|
|
35
|
+
type RepeaterItemContentProps< T > = {
|
|
36
|
+
anchorEl: AnchorEl;
|
|
37
|
+
bind: PropKey;
|
|
38
|
+
value: T;
|
|
39
|
+
collectionPropUtil?: CollectionPropUtil< T >;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type RepeaterItemContent< T > = React.ComponentType< RepeaterItemContentProps< T > >;
|
|
33
43
|
|
|
34
44
|
type RepeaterProps< T > = {
|
|
35
45
|
label: string;
|
|
@@ -42,15 +52,12 @@ type RepeaterProps< T > = {
|
|
|
42
52
|
initialValues: T;
|
|
43
53
|
Label: React.ComponentType< { value: T } >;
|
|
44
54
|
Icon: React.ComponentType< { value: T } >;
|
|
45
|
-
Content:
|
|
46
|
-
anchorEl: AnchorEl;
|
|
47
|
-
bind: PropKey;
|
|
48
|
-
value: T;
|
|
49
|
-
} >;
|
|
55
|
+
Content: RepeaterItemContent< T >;
|
|
50
56
|
};
|
|
51
57
|
showDuplicate?: boolean;
|
|
52
58
|
showToggle?: boolean;
|
|
53
59
|
isSortable?: boolean;
|
|
60
|
+
collectionPropUtil?: CollectionPropUtil< T >;
|
|
54
61
|
};
|
|
55
62
|
|
|
56
63
|
const EMPTY_OPEN_ITEM = -1;
|
|
@@ -66,6 +73,7 @@ export const Repeater = < T, >( {
|
|
|
66
73
|
showDuplicate = true,
|
|
67
74
|
showToggle = true,
|
|
68
75
|
isSortable = true,
|
|
76
|
+
collectionPropUtil,
|
|
69
77
|
}: RepeaterProps< Item< T > > ) => {
|
|
70
78
|
const [ openItem, setOpenItem ] = useState( EMPTY_OPEN_ITEM );
|
|
71
79
|
|
|
@@ -203,6 +211,7 @@ export const Repeater = < T, >( {
|
|
|
203
211
|
onOpen={ () => setOpenItem( EMPTY_OPEN_ITEM ) }
|
|
204
212
|
showDuplicate={ showDuplicate }
|
|
205
213
|
showToggle={ showToggle }
|
|
214
|
+
collectionPropUtil={ collectionPropUtil }
|
|
206
215
|
>
|
|
207
216
|
{ ( props ) => (
|
|
208
217
|
<itemSettings.Content { ...props } value={ value } bind={ String( index ) } />
|
|
@@ -217,22 +226,23 @@ export const Repeater = < T, >( {
|
|
|
217
226
|
);
|
|
218
227
|
};
|
|
219
228
|
|
|
220
|
-
type RepeaterItemProps = {
|
|
229
|
+
type RepeaterItemProps< T > = {
|
|
221
230
|
label: React.ReactNode;
|
|
222
231
|
propDisabled?: boolean;
|
|
223
232
|
startIcon: UnstableTagProps[ 'startIcon' ];
|
|
224
233
|
removeItem: () => void;
|
|
225
234
|
duplicateItem: () => void;
|
|
226
235
|
toggleDisableItem: () => void;
|
|
227
|
-
children: (
|
|
236
|
+
children: ( props: Pick< RepeaterItemContentProps< T >, 'anchorEl' | 'collectionPropUtil' > ) => React.ReactNode;
|
|
228
237
|
openOnMount: boolean;
|
|
229
238
|
onOpen: () => void;
|
|
230
239
|
showDuplicate: boolean;
|
|
231
240
|
showToggle: boolean;
|
|
232
241
|
disabled?: boolean;
|
|
242
|
+
collectionPropUtil?: CollectionPropUtil< T >;
|
|
233
243
|
};
|
|
234
244
|
|
|
235
|
-
const RepeaterItem = ( {
|
|
245
|
+
const RepeaterItem = < T, >( {
|
|
236
246
|
label,
|
|
237
247
|
propDisabled,
|
|
238
248
|
startIcon,
|
|
@@ -245,7 +255,8 @@ const RepeaterItem = ( {
|
|
|
245
255
|
showDuplicate,
|
|
246
256
|
showToggle,
|
|
247
257
|
disabled,
|
|
248
|
-
|
|
258
|
+
collectionPropUtil,
|
|
259
|
+
}: RepeaterItemProps< T > ) => {
|
|
249
260
|
const [ anchorEl, setAnchorEl ] = useState< AnchorEl >( null );
|
|
250
261
|
const { popoverState, popoverProps, ref, setRef } = usePopover( openOnMount, onOpen );
|
|
251
262
|
|
|
@@ -301,7 +312,7 @@ const RepeaterItem = ( {
|
|
|
301
312
|
{ ...popoverProps }
|
|
302
313
|
anchorEl={ ref }
|
|
303
314
|
>
|
|
304
|
-
<Box>{ children( { anchorEl } ) }</Box>
|
|
315
|
+
<Box>{ children( { anchorEl, collectionPropUtil } ) }</Box>
|
|
305
316
|
</Popover>
|
|
306
317
|
</>
|
|
307
318
|
);
|
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { useRef } from 'react';
|
|
3
|
-
import {
|
|
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';
|
|
@@ -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: <
|
|
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>
|