@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.
- package/dist/index.d.mts +276 -85
- package/dist/index.d.ts +276 -85
- package/dist/index.js +2491 -1783
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2304 -1592
- package/dist/index.mjs.map +1 -1
- package/package.json +31 -17
- package/src/bound-prop-context/prop-context.tsx +7 -1
- package/src/bound-prop-context/use-bound-prop.ts +19 -5
- package/src/components/autocomplete.tsx +34 -3
- package/src/components/conditional-control-infotip.tsx +64 -0
- package/src/components/{unstable-repeater → control-repeater}/actions/disable-item-action.tsx +2 -2
- package/src/components/{unstable-repeater → control-repeater}/actions/duplicate-item-action.tsx +10 -4
- package/src/components/{unstable-repeater → control-repeater}/actions/remove-item-action.tsx +2 -2
- package/src/components/control-repeater/context/item-context.tsx +8 -0
- package/src/components/{unstable-repeater → control-repeater}/context/repeater-context.tsx +24 -15
- package/src/components/control-repeater/control-repeater.tsx +29 -0
- package/src/components/{unstable-repeater → control-repeater}/index.ts +1 -2
- package/src/components/{unstable-repeater → control-repeater}/items/edit-item-popover.tsx +6 -20
- package/src/components/control-repeater/items/item.tsx +75 -0
- package/src/components/{unstable-repeater → control-repeater}/items/items-container.tsx +8 -13
- package/src/components/{unstable-repeater → control-repeater}/locations.ts +0 -4
- package/src/components/{unstable-repeater → control-repeater}/types.ts +1 -2
- package/src/components/control-toggle-button-group.tsx +79 -69
- package/src/components/enable-unfiltered-modal.tsx +1 -26
- package/src/components/icon-buttons/clear-icon-button.tsx +23 -0
- package/src/components/inline-editor-toolbar.tsx +137 -0
- package/src/components/inline-editor.tsx +111 -0
- package/src/components/item-selector.tsx +10 -4
- package/src/components/{unstable-repeater/header/header.tsx → repeater/repeater-header.tsx} +4 -12
- package/src/components/repeater/repeater-popover.tsx +19 -0
- package/src/components/repeater/repeater-tag.tsx +16 -0
- package/src/components/repeater/repeater.tsx +405 -0
- package/src/components/{sortable.tsx → repeater/sortable.tsx} +1 -1
- package/src/components/size-control/size-input.tsx +20 -14
- package/src/components/size-control/text-field-inner-selection.tsx +15 -2
- package/src/control-adornments/control-adornments-context.tsx +5 -4
- package/src/control-replacements.tsx +12 -47
- package/src/controls/background-control/background-control.tsx +43 -12
- package/src/controls/background-control/background-gradient-color-control.tsx +5 -8
- package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-position.tsx +18 -13
- package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +25 -16
- package/src/controls/box-shadow-repeater-control.tsx +38 -21
- package/src/controls/color-control.tsx +3 -1
- package/src/controls/date-time-control.tsx +108 -0
- package/src/controls/filter-control/drop-shadow/drop-shadow-item-content.tsx +1 -0
- package/src/controls/filter-control/drop-shadow/drop-shadow-item-label.tsx +10 -6
- package/src/controls/filter-control/filter-content.tsx +1 -1
- package/src/controls/filter-control/filter-repeater-control.tsx +24 -21
- package/src/controls/filter-control/single-size/single-size-item-content.tsx +1 -1
- package/src/controls/filter-control/single-size/single-size-item-label.tsx +2 -1
- package/src/controls/font-family-control/font-family-control.tsx +66 -55
- package/src/controls/html-tag-control.tsx +90 -0
- package/src/controls/image-media-control.tsx +2 -2
- package/src/controls/inline-editing-control.tsx +18 -0
- package/src/controls/key-value-control.tsx +8 -2
- package/src/controls/link-control.tsx +23 -123
- package/src/controls/query-control.tsx +168 -0
- package/src/controls/repeatable-control.tsx +62 -27
- package/src/controls/select-control-wrapper.tsx +57 -0
- package/src/controls/select-control.tsx +9 -5
- package/src/controls/selection-size-control.tsx +13 -2
- package/src/controls/size-control.tsx +43 -25
- package/src/controls/svg-media-control.tsx +33 -10
- package/src/controls/text-area-control.tsx +5 -1
- package/src/controls/text-control.tsx +5 -0
- package/src/controls/toggle-control.tsx +11 -2
- package/src/controls/transform-control/functions/axis-row.tsx +1 -0
- package/src/controls/transform-control/transform-icon.tsx +2 -2
- package/src/controls/transform-control/transform-label.tsx +15 -32
- package/src/controls/transform-control/transform-repeater-control.tsx +42 -36
- package/src/controls/transform-control/{transform-base-control.tsx → transform-settings-control.tsx} +2 -2
- package/src/controls/transform-control/use-transform-tabs-history.tsx +1 -1
- package/src/controls/transition-control/data.ts +16 -1
- package/src/controls/transition-control/trainsition-events.ts +2 -2
- package/src/controls/transition-control/transition-repeater-control.tsx +137 -13
- package/src/controls/transition-control/transition-selector.tsx +37 -14
- package/src/controls/url-control.tsx +21 -16
- package/src/create-control.tsx +3 -2
- package/src/hooks/use-filtered-items-list.ts +3 -2
- package/src/hooks/use-repeatable-control-context.ts +3 -0
- package/src/hooks/use-sync-external-state.tsx +0 -1
- package/src/index.ts +21 -5
- package/src/utils/convert-toggle-options-to-atomic.tsx +33 -0
- package/src/utils/escape-html-attr.ts +11 -0
- package/src/components/css-code-editor/css-editor.styles.ts +0 -52
- package/src/components/css-code-editor/css-editor.tsx +0 -142
- package/src/components/css-code-editor/css-validation.ts +0 -75
- package/src/components/css-code-editor/resize-handle.tsx +0 -55
- package/src/components/css-code-editor/visual-content-change-protection.ts +0 -69
- package/src/components/repeater.tsx +0 -343
- package/src/components/unstable-repeater/items/item.tsx +0 -77
- package/src/components/unstable-repeater/unstable-repeater.tsx +0 -26
- /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
|
-
|
|
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
|
|
5
|
+
export type AdornmentComponent = ComponentType< { customContext?: { path: string[]; propType: PropType } } >;
|
|
6
|
+
type ControlAdornmentsItem = {
|
|
6
7
|
id: string;
|
|
7
|
-
Adornment:
|
|
8
|
-
}
|
|
8
|
+
Adornment: AdornmentComponent;
|
|
9
|
+
};
|
|
9
10
|
|
|
10
11
|
type ControlAdornmentsContext = {
|
|
11
|
-
items?:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
};
|