@elementor/editor-controls 0.28.2 → 0.30.0
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 +21 -0
- package/dist/index.d.mts +13 -3
- package/dist/index.d.ts +13 -3
- package/dist/index.js +388 -245
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +321 -177
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/src/components/conditional-tooltip.tsx +16 -0
- package/src/components/control-toggle-button-group.tsx +173 -28
- package/src/components/repeater.tsx +11 -2
- package/src/controls/box-shadow-repeater-control.tsx +1 -1
- package/src/controls/font-family-control/font-family-control.tsx +10 -8
- package/src/controls/toggle-control.tsx +3 -0
- package/src/index.ts +2 -0
- package/src/locations.ts +11 -0
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": "0.
|
|
4
|
+
"version": "0.30.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Elementor Team",
|
|
7
7
|
"homepage": "https://elementor.com/",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"@elementor/env": "0.3.5",
|
|
48
48
|
"@elementor/http-client": "0.3.0",
|
|
49
49
|
"@elementor/icons": "1.40.1",
|
|
50
|
+
"@elementor/locations": "0.8.0",
|
|
50
51
|
"@elementor/query": "0.2.4",
|
|
51
52
|
"@elementor/session": "0.1.0",
|
|
52
53
|
"@elementor/ui": "1.34.2",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Tooltip } from '@elementor/ui';
|
|
3
|
+
|
|
4
|
+
export const ConditionalTooltip = ( {
|
|
5
|
+
showTooltip,
|
|
6
|
+
children,
|
|
7
|
+
label,
|
|
8
|
+
}: React.PropsWithChildren< { showTooltip: boolean; label: string } > ) => {
|
|
9
|
+
return showTooltip && label ? (
|
|
10
|
+
<Tooltip title={ label } disableFocusListener={ true } placement="top">
|
|
11
|
+
{ children }
|
|
12
|
+
</Tooltip>
|
|
13
|
+
) : (
|
|
14
|
+
children
|
|
15
|
+
);
|
|
16
|
+
};
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { ChevronDownIcon } from '@elementor/icons';
|
|
2
4
|
import {
|
|
5
|
+
ListItemText,
|
|
6
|
+
Menu,
|
|
7
|
+
MenuItem,
|
|
3
8
|
type StackProps,
|
|
4
9
|
styled,
|
|
5
10
|
ToggleButton,
|
|
6
11
|
ToggleButtonGroup,
|
|
7
12
|
type ToggleButtonProps,
|
|
8
|
-
|
|
13
|
+
Typography,
|
|
9
14
|
useTheme,
|
|
10
15
|
} from '@elementor/ui';
|
|
11
16
|
|
|
17
|
+
import { ConditionalTooltip } from './conditional-tooltip';
|
|
18
|
+
|
|
12
19
|
type RenderContentProps = { size: ToggleButtonProps[ 'size' ] };
|
|
13
20
|
|
|
14
21
|
export type ToggleButtonGroupItem< TValue > = {
|
|
@@ -20,6 +27,18 @@ export type ToggleButtonGroupItem< TValue > = {
|
|
|
20
27
|
|
|
21
28
|
const StyledToggleButtonGroup = styled( ToggleButtonGroup )`
|
|
22
29
|
${ ( { justify } ) => `justify-content: ${ justify };` }
|
|
30
|
+
button:not( :last-of-type ) {
|
|
31
|
+
border-start-end-radius: 0;
|
|
32
|
+
border-end-end-radius: 0;
|
|
33
|
+
}
|
|
34
|
+
button:not( :first-of-type ) {
|
|
35
|
+
border-start-start-radius: 0;
|
|
36
|
+
border-end-start-radius: 0;
|
|
37
|
+
}
|
|
38
|
+
button:last-of-type {
|
|
39
|
+
border-start-end-radius: 8px;
|
|
40
|
+
border-end-end-radius: 8px;
|
|
41
|
+
}
|
|
23
42
|
`;
|
|
24
43
|
|
|
25
44
|
type ExclusiveValue< TValue > = TValue;
|
|
@@ -29,6 +48,7 @@ type Props< TValue > = {
|
|
|
29
48
|
justify?: StackProps[ 'justifyContent' ];
|
|
30
49
|
size?: ToggleButtonProps[ 'size' ];
|
|
31
50
|
items: ToggleButtonGroupItem< TValue | null >[];
|
|
51
|
+
maxItems?: number;
|
|
32
52
|
fullWidth?: boolean;
|
|
33
53
|
} & (
|
|
34
54
|
| {
|
|
@@ -49,11 +69,15 @@ export const ControlToggleButtonGroup = < TValue, >( {
|
|
|
49
69
|
value,
|
|
50
70
|
onChange,
|
|
51
71
|
items,
|
|
72
|
+
maxItems,
|
|
52
73
|
exclusive = false,
|
|
53
74
|
fullWidth = false,
|
|
54
75
|
}: Props< TValue > ) => {
|
|
55
|
-
const
|
|
76
|
+
const shouldSliceItems = exclusive && maxItems !== undefined && items.length > maxItems;
|
|
77
|
+
const menuItems = shouldSliceItems ? items.slice( maxItems - 1 ) : [];
|
|
78
|
+
const fixedItems = shouldSliceItems ? items.slice( 0, maxItems - 1 ) : items;
|
|
56
79
|
|
|
80
|
+
const isRtl = 'rtl' === useTheme().direction;
|
|
57
81
|
const handleChange = (
|
|
58
82
|
_: React.MouseEvent< HTMLElement >,
|
|
59
83
|
newValue: typeof exclusive extends true ? ExclusiveValue< TValue > : NonExclusiveValue< TValue >
|
|
@@ -61,38 +85,159 @@ export const ControlToggleButtonGroup = < TValue, >( {
|
|
|
61
85
|
onChange( newValue as never );
|
|
62
86
|
};
|
|
63
87
|
|
|
88
|
+
const getGridTemplateColumns = useMemo( () => {
|
|
89
|
+
const isOffLimits = menuItems?.length;
|
|
90
|
+
const itemsCount = isOffLimits ? fixedItems.length + 1 : fixedItems.length;
|
|
91
|
+
const templateColumnsSuffix = isOffLimits ? 'auto' : '';
|
|
92
|
+
|
|
93
|
+
return `repeat(${ itemsCount }, minmax(0, 25%)) ${ templateColumnsSuffix }`;
|
|
94
|
+
}, [ menuItems?.length, fixedItems.length ] );
|
|
95
|
+
|
|
64
96
|
return (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
showTooltip
|
|
79
|
-
<
|
|
97
|
+
<>
|
|
98
|
+
<StyledToggleButtonGroup
|
|
99
|
+
justify={ justify }
|
|
100
|
+
value={ value }
|
|
101
|
+
onChange={ handleChange }
|
|
102
|
+
exclusive={ exclusive }
|
|
103
|
+
sx={ {
|
|
104
|
+
direction: isRtl ? 'rtl /* @noflip */' : 'ltr /* @noflip */',
|
|
105
|
+
display: 'grid',
|
|
106
|
+
gridTemplateColumns: getGridTemplateColumns,
|
|
107
|
+
width: `100%`,
|
|
108
|
+
} }
|
|
109
|
+
>
|
|
110
|
+
{ fixedItems.map( ( { label, value: buttonValue, renderContent: Content, showTooltip } ) => (
|
|
111
|
+
<ConditionalTooltip
|
|
112
|
+
key={ buttonValue as string }
|
|
113
|
+
label={ label }
|
|
114
|
+
showTooltip={ showTooltip || false }
|
|
115
|
+
>
|
|
80
116
|
<ToggleButton value={ buttonValue } aria-label={ label } size={ size } fullWidth={ fullWidth }>
|
|
81
117
|
<Content size={ size } />
|
|
82
118
|
</ToggleButton>
|
|
83
|
-
</
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
aria-label={ label }
|
|
119
|
+
</ConditionalTooltip>
|
|
120
|
+
) ) }
|
|
121
|
+
|
|
122
|
+
{ menuItems.length && exclusive && (
|
|
123
|
+
<SplitButtonGroup
|
|
89
124
|
size={ size }
|
|
125
|
+
value={ ( value as ExclusiveValue< TValue > ) || null }
|
|
126
|
+
onChange={ onChange as ( v: ExclusiveValue< TValue > ) => void }
|
|
127
|
+
items={ menuItems }
|
|
90
128
|
fullWidth={ fullWidth }
|
|
129
|
+
/>
|
|
130
|
+
) }
|
|
131
|
+
</StyledToggleButtonGroup>
|
|
132
|
+
</>
|
|
133
|
+
);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
type SplitButtonGroup< TValue > = {
|
|
137
|
+
size: ToggleButtonProps[ 'size' ];
|
|
138
|
+
items: ToggleButtonGroupItem< TValue | null >[];
|
|
139
|
+
fullWidth: boolean;
|
|
140
|
+
value: ExclusiveValue< TValue > | null;
|
|
141
|
+
onChange: ( value: ExclusiveValue< TValue > ) => void;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const SplitButtonGroup = < TValue, >( {
|
|
145
|
+
size = 'tiny',
|
|
146
|
+
onChange,
|
|
147
|
+
items,
|
|
148
|
+
fullWidth,
|
|
149
|
+
value,
|
|
150
|
+
}: SplitButtonGroup< TValue > ) => {
|
|
151
|
+
const previewButton = usePreviewButton( items, value );
|
|
152
|
+
const [ isMenuOpen, setIsMenuOpen ] = useState( false );
|
|
153
|
+
const menuButtonRef = useRef( null );
|
|
154
|
+
|
|
155
|
+
const onMenuToggle = ( ev: React.MouseEvent ) => {
|
|
156
|
+
setIsMenuOpen( ( prev ) => ! prev );
|
|
157
|
+
ev.preventDefault();
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const onMenuItemClick = ( newValue: TValue | null ) => {
|
|
161
|
+
setIsMenuOpen( false );
|
|
162
|
+
onToggleItem( newValue );
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const onToggleItem = ( newValue: TValue | null ) => {
|
|
166
|
+
const shouldRemove = newValue === value;
|
|
167
|
+
|
|
168
|
+
onChange( ( shouldRemove ? null : newValue ) as never );
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<>
|
|
173
|
+
<ToggleButton
|
|
174
|
+
value={ previewButton.value }
|
|
175
|
+
aria-label={ previewButton.label }
|
|
176
|
+
size={ size }
|
|
177
|
+
fullWidth={ fullWidth }
|
|
178
|
+
onClick={ ( ev: React.MouseEvent ) => {
|
|
179
|
+
ev.preventDefault();
|
|
180
|
+
onMenuItemClick( previewButton.value );
|
|
181
|
+
} }
|
|
182
|
+
ref={ menuButtonRef }
|
|
183
|
+
>
|
|
184
|
+
{ previewButton.renderContent( { size } ) }
|
|
185
|
+
</ToggleButton>
|
|
186
|
+
<ToggleButton
|
|
187
|
+
size={ size }
|
|
188
|
+
aria-expanded={ isMenuOpen ? 'true' : undefined }
|
|
189
|
+
aria-haspopup="menu"
|
|
190
|
+
aria-pressed={ undefined }
|
|
191
|
+
onClick={ onMenuToggle }
|
|
192
|
+
ref={ menuButtonRef }
|
|
193
|
+
value={ '__chevron-icon-button__' }
|
|
194
|
+
>
|
|
195
|
+
<ChevronDownIcon fontSize={ size } />
|
|
196
|
+
</ToggleButton>
|
|
197
|
+
<Menu
|
|
198
|
+
open={ isMenuOpen }
|
|
199
|
+
onClose={ () => setIsMenuOpen( false ) }
|
|
200
|
+
anchorEl={ menuButtonRef.current }
|
|
201
|
+
anchorOrigin={ {
|
|
202
|
+
vertical: 'bottom',
|
|
203
|
+
horizontal: 'right',
|
|
204
|
+
} }
|
|
205
|
+
transformOrigin={ {
|
|
206
|
+
vertical: 'top',
|
|
207
|
+
horizontal: 'right',
|
|
208
|
+
} }
|
|
209
|
+
sx={ {
|
|
210
|
+
mt: 0.5,
|
|
211
|
+
} }
|
|
212
|
+
>
|
|
213
|
+
{ items.map( ( { label, value: buttonValue } ) => (
|
|
214
|
+
<MenuItem
|
|
215
|
+
key={ buttonValue }
|
|
216
|
+
selected={ buttonValue === value }
|
|
217
|
+
onClick={ () => onMenuItemClick( buttonValue ) }
|
|
91
218
|
>
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
219
|
+
<ListItemText>
|
|
220
|
+
<Typography sx={ { fontSize: '14px' } }>{ label }</Typography>
|
|
221
|
+
</ListItemText>
|
|
222
|
+
</MenuItem>
|
|
223
|
+
) ) }
|
|
224
|
+
</Menu>
|
|
225
|
+
</>
|
|
226
|
+
);
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const usePreviewButton = < TValue, >( items: ToggleButtonGroupItem< TValue >[], value: TValue ) => {
|
|
230
|
+
const [ previewButton, setPreviewButton ] = useState(
|
|
231
|
+
items.find( ( item ) => item.value === value ) ?? items[ 0 ]
|
|
97
232
|
);
|
|
233
|
+
|
|
234
|
+
useEffect( () => {
|
|
235
|
+
const selectedButton = items.find( ( item ) => item.value === value );
|
|
236
|
+
|
|
237
|
+
if ( selectedButton ) {
|
|
238
|
+
setPreviewButton( selectedButton );
|
|
239
|
+
}
|
|
240
|
+
}, [ items, value ] );
|
|
241
|
+
|
|
242
|
+
return previewButton;
|
|
98
243
|
};
|
|
@@ -19,6 +19,7 @@ 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';
|
|
22
23
|
import { SectionContent } from './section-content';
|
|
23
24
|
import { SortableItem, SortableProvider } from './sortable';
|
|
24
25
|
|
|
@@ -175,8 +176,16 @@ export const Repeater = < T, >( {
|
|
|
175
176
|
<SortableItem id={ key } key={ `sortable-${ key }` }>
|
|
176
177
|
<RepeaterItem
|
|
177
178
|
disabled={ value?.disabled }
|
|
178
|
-
label={
|
|
179
|
-
|
|
179
|
+
label={
|
|
180
|
+
<RepeaterItemLabelSlot value={ value }>
|
|
181
|
+
<itemSettings.Label value={ value } />
|
|
182
|
+
</RepeaterItemLabelSlot>
|
|
183
|
+
}
|
|
184
|
+
startIcon={
|
|
185
|
+
<RepeaterItemIconSlot value={ value }>
|
|
186
|
+
<itemSettings.Icon value={ value } />
|
|
187
|
+
</RepeaterItemIconSlot>
|
|
188
|
+
}
|
|
180
189
|
removeItem={ () => removeRepeaterItem( index ) }
|
|
181
190
|
duplicateItem={ () => duplicateRepeaterItem( index ) }
|
|
182
191
|
toggleDisableItem={ () => toggleDisableRepeaterItem( index ) }
|
|
@@ -34,7 +34,7 @@ export const BoxShadowRepeaterControl = createControl( () => {
|
|
|
34
34
|
} );
|
|
35
35
|
|
|
36
36
|
const ItemIcon = ( { value }: { value: ShadowPropValue } ) => (
|
|
37
|
-
<UnstableColorIndicator size="inherit" component="span" value={ value.value.color
|
|
37
|
+
<UnstableColorIndicator size="inherit" component="span" value={ value.value.color?.value } />
|
|
38
38
|
);
|
|
39
39
|
|
|
40
40
|
const ItemContent = ( { anchorEl, bind }: { anchorEl: HTMLElement | null; bind: PropKey } ) => {
|
|
@@ -25,6 +25,7 @@ import { useVirtualizer } from '@tanstack/react-virtual';
|
|
|
25
25
|
import { __ } from '@wordpress/i18n';
|
|
26
26
|
|
|
27
27
|
import { useBoundProp } from '../../bound-prop-context';
|
|
28
|
+
import ControlActions from '../../control-actions/control-actions';
|
|
28
29
|
import { createControl } from '../../create-control';
|
|
29
30
|
import { type FontListItem, useFilteredFontFamilies } from '../../hooks/use-filtered-font-families';
|
|
30
31
|
import { enqueueFont } from './enqueue-font';
|
|
@@ -60,14 +61,15 @@ export const FontFamilyControl = createControl( ( { fontFamilies }: FontFamilyCo
|
|
|
60
61
|
|
|
61
62
|
return (
|
|
62
63
|
<>
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
<ControlActions>
|
|
65
|
+
<UnstableTag
|
|
66
|
+
variant="outlined"
|
|
67
|
+
label={ fontFamily }
|
|
68
|
+
endIcon={ <ChevronDownIcon fontSize="tiny" /> }
|
|
69
|
+
{ ...bindTrigger( popoverState ) }
|
|
70
|
+
fullWidth
|
|
71
|
+
/>
|
|
72
|
+
</ControlActions>
|
|
71
73
|
<Popover
|
|
72
74
|
disablePortal
|
|
73
75
|
disableScrollLock
|
|
@@ -11,6 +11,7 @@ export type ToggleControlProps< T extends PropValue > = {
|
|
|
11
11
|
fullWidth?: boolean;
|
|
12
12
|
size?: ToggleButtonProps[ 'size' ];
|
|
13
13
|
exclusive?: boolean;
|
|
14
|
+
maxItems?: number;
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
export const ToggleControl = createControl(
|
|
@@ -19,6 +20,7 @@ export const ToggleControl = createControl(
|
|
|
19
20
|
fullWidth = false,
|
|
20
21
|
size = 'tiny',
|
|
21
22
|
exclusive = true,
|
|
23
|
+
maxItems,
|
|
22
24
|
}: ToggleControlProps< StringPropValue[ 'value' ] > ) => {
|
|
23
25
|
const { value, setValue, placeholder } = useBoundProp( stringPropTypeUtil );
|
|
24
26
|
|
|
@@ -37,6 +39,7 @@ export const ToggleControl = createControl(
|
|
|
37
39
|
|
|
38
40
|
const toggleButtonGroupProps = {
|
|
39
41
|
items: options,
|
|
42
|
+
maxItems,
|
|
40
43
|
fullWidth,
|
|
41
44
|
size,
|
|
42
45
|
};
|
package/src/index.ts
CHANGED
|
@@ -40,5 +40,7 @@ export { useBoundProp, PropProvider, PropKeyProvider } from './bound-prop-contex
|
|
|
40
40
|
export { ControlAdornmentsProvider } from './control-adornments/control-adornments-context';
|
|
41
41
|
export { ControlAdornments } from './control-adornments/control-adornments';
|
|
42
42
|
|
|
43
|
+
export { injectIntoRepeaterItemIcon, injectIntoRepeaterItemLabel } from './locations';
|
|
44
|
+
|
|
43
45
|
// hooks
|
|
44
46
|
export { useSyncExternalState } from './hooks/use-sync-external-state';
|
package/src/locations.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type PropValue } from '@elementor/editor-props';
|
|
2
|
+
import { createReplaceableLocation } from '@elementor/locations';
|
|
3
|
+
|
|
4
|
+
// Repeaters
|
|
5
|
+
export const { Slot: RepeaterItemIconSlot, inject: injectIntoRepeaterItemIcon } = createReplaceableLocation< {
|
|
6
|
+
value: PropValue;
|
|
7
|
+
} >();
|
|
8
|
+
|
|
9
|
+
export const { Slot: RepeaterItemLabelSlot, inject: injectIntoRepeaterItemLabel } = createReplaceableLocation< {
|
|
10
|
+
value: PropValue;
|
|
11
|
+
} >();
|