@elementor/editor-controls 3.33.0-98 → 3.35.0-324
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
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { useMemo } from 'react';
|
|
3
|
-
import { createArrayPropUtils } from '@elementor/editor-props';
|
|
3
|
+
import { createArrayPropUtils, type SizePropValue } from '@elementor/editor-props';
|
|
4
4
|
import { Box } from '@elementor/ui';
|
|
5
5
|
|
|
6
6
|
import { PropProvider, useBoundProp } from '../bound-prop-context';
|
|
7
|
+
import { ControlRepeater, Item, TooltipAddItemAction } from '../components/control-repeater';
|
|
8
|
+
import { DisableItemAction } from '../components/control-repeater/actions/disable-item-action';
|
|
9
|
+
import { DuplicateItemAction } from '../components/control-repeater/actions/duplicate-item-action';
|
|
10
|
+
import { RemoveItemAction } from '../components/control-repeater/actions/remove-item-action';
|
|
11
|
+
import { type TooltipAddItemActionProps } from '../components/control-repeater/actions/tooltip-add-item-action';
|
|
12
|
+
import { EditItemPopover } from '../components/control-repeater/items/edit-item-popover';
|
|
13
|
+
import { ItemsContainer } from '../components/control-repeater/items/items-container';
|
|
14
|
+
import { type CollectionPropUtil, type RepeatablePropValue } from '../components/control-repeater/types';
|
|
7
15
|
import { PopoverContent } from '../components/popover-content';
|
|
8
16
|
import { PopoverGridContainer } from '../components/popover-grid-container';
|
|
9
|
-
import {
|
|
10
|
-
import { Header, Item, TooltipAddItemAction, UnstableRepeater } from '../components/unstable-repeater';
|
|
11
|
-
import { DisableItemAction } from '../components/unstable-repeater/actions/disable-item-action';
|
|
12
|
-
import { DuplicateItemAction } from '../components/unstable-repeater/actions/duplicate-item-action';
|
|
13
|
-
import { RemoveItemAction } from '../components/unstable-repeater/actions/remove-item-action';
|
|
14
|
-
import { type TooltipAddItemActionProps } from '../components/unstable-repeater/actions/tooltip-add-item-action';
|
|
15
|
-
import { EditItemPopover } from '../components/unstable-repeater/items/edit-item-popover';
|
|
16
|
-
import { ItemsContainer } from '../components/unstable-repeater/items/items-container';
|
|
17
|
-
import { type RepeatablePropValue } from '../components/unstable-repeater/types';
|
|
17
|
+
import { RepeaterHeader } from '../components/repeater/repeater-header';
|
|
18
18
|
import { createControl } from '../create-control';
|
|
19
19
|
import {
|
|
20
20
|
type ChildControlConfig,
|
|
21
21
|
RepeatableControlContext,
|
|
22
22
|
useRepeatableControlContext,
|
|
23
23
|
} from '../hooks/use-repeatable-control-context';
|
|
24
|
+
import { CUSTOM_SIZE_LABEL } from './size-control';
|
|
24
25
|
|
|
25
26
|
type RepeatableControlProps = {
|
|
26
27
|
label: string;
|
|
@@ -49,7 +50,7 @@ export const RepeatableControl = createControl(
|
|
|
49
50
|
propKey,
|
|
50
51
|
addItemTooltipProps,
|
|
51
52
|
}: RepeatableControlProps ) => {
|
|
52
|
-
const { propTypeUtil: childPropTypeUtil } = childControlConfig;
|
|
53
|
+
const { propTypeUtil: childPropTypeUtil, isItemDisabled } = childControlConfig;
|
|
53
54
|
|
|
54
55
|
if ( ! childPropTypeUtil ) {
|
|
55
56
|
return null;
|
|
@@ -74,29 +75,35 @@ export const RepeatableControl = createControl(
|
|
|
74
75
|
return (
|
|
75
76
|
<PropProvider propType={ propType } value={ value } setValue={ setValue }>
|
|
76
77
|
<RepeatableControlContext.Provider value={ contextValue }>
|
|
77
|
-
<
|
|
78
|
+
<ControlRepeater
|
|
78
79
|
initial={ childPropTypeUtil.create( initialValues || null ) }
|
|
79
80
|
propTypeUtil={ childArrayPropTypeUtil as CollectionPropUtil< RepeatablePropValue > }
|
|
81
|
+
isItemDisabled={ isItemDisabled }
|
|
80
82
|
>
|
|
81
|
-
<
|
|
83
|
+
<RepeaterHeader label={ repeaterLabel }>
|
|
82
84
|
<TooltipAddItemAction
|
|
83
85
|
{ ...addItemTooltipProps }
|
|
84
86
|
newItemIndex={ 0 }
|
|
85
87
|
ariaLabel={ repeaterLabel }
|
|
86
88
|
/>
|
|
87
|
-
</
|
|
88
|
-
<ItemsContainer
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
89
|
+
</RepeaterHeader>
|
|
90
|
+
<ItemsContainer isSortable={ false }>
|
|
91
|
+
<Item
|
|
92
|
+
Icon={ ItemIcon }
|
|
93
|
+
Label={ ItemLabel }
|
|
94
|
+
actions={
|
|
95
|
+
<>
|
|
96
|
+
{ showDuplicate && <DuplicateItemAction /> }
|
|
97
|
+
{ showToggle && <DisableItemAction /> }
|
|
98
|
+
<RemoveItemAction />
|
|
99
|
+
</>
|
|
100
|
+
}
|
|
101
|
+
/>
|
|
95
102
|
</ItemsContainer>
|
|
96
103
|
<EditItemPopover>
|
|
97
104
|
<Content />
|
|
98
105
|
</EditItemPopover>
|
|
99
|
-
</
|
|
106
|
+
</ControlRepeater>
|
|
100
107
|
</RepeatableControlContext.Provider>
|
|
101
108
|
</PropProvider>
|
|
102
109
|
);
|
|
@@ -126,7 +133,7 @@ const interpolate = ( template: string, data: Record< string, unknown > ) => {
|
|
|
126
133
|
const value = getNestedValue( data, path );
|
|
127
134
|
|
|
128
135
|
if ( typeof value === 'object' && value !== null && ! Array.isArray( value ) ) {
|
|
129
|
-
if ( value.name ) {
|
|
136
|
+
if ( 'name' in value && value.name ) {
|
|
130
137
|
return value.name as string;
|
|
131
138
|
}
|
|
132
139
|
|
|
@@ -142,12 +149,32 @@ const interpolate = ( template: string, data: Record< string, unknown > ) => {
|
|
|
142
149
|
};
|
|
143
150
|
|
|
144
151
|
const getNestedValue = ( obj: Record< string, unknown >, path: string ) => {
|
|
145
|
-
|
|
152
|
+
let parentObj: Record< string, unknown > = {};
|
|
153
|
+
const pathKeys = path.split( '.' );
|
|
154
|
+
const key = pathKeys.slice( -1 )[ 0 ];
|
|
155
|
+
|
|
156
|
+
let value: unknown = pathKeys.reduce( ( current: Record< string, unknown >, currentKey, currentIndex ) => {
|
|
157
|
+
if ( currentIndex === pathKeys.length - 2 ) {
|
|
158
|
+
parentObj = current;
|
|
159
|
+
}
|
|
160
|
+
|
|
146
161
|
if ( current && typeof current === 'object' ) {
|
|
147
|
-
return current[
|
|
162
|
+
return current[ currentKey ] as Record< string, unknown >;
|
|
148
163
|
}
|
|
164
|
+
|
|
149
165
|
return {};
|
|
150
166
|
}, obj );
|
|
167
|
+
|
|
168
|
+
value = !! value ? value : '';
|
|
169
|
+
const propType = parentObj?.$$type;
|
|
170
|
+
const propValue = parentObj?.value as SizePropValue[ 'value' ];
|
|
171
|
+
const doesValueRepresentCustomSize = key === 'unit' && propType === 'size' && propValue?.unit === 'custom';
|
|
172
|
+
|
|
173
|
+
if ( ! doesValueRepresentCustomSize ) {
|
|
174
|
+
return value;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return propValue?.size ? '' : CUSTOM_SIZE_LABEL;
|
|
151
178
|
};
|
|
152
179
|
|
|
153
180
|
const isEmptyValue = ( val: unknown ) => {
|
|
@@ -190,11 +217,19 @@ const shouldShowPlaceholder = ( pattern: string, data: Record< string, unknown >
|
|
|
190
217
|
return false;
|
|
191
218
|
};
|
|
192
219
|
|
|
220
|
+
const getTextColor = ( isReadOnly: boolean, showPlaceholder: boolean ): string => {
|
|
221
|
+
if ( isReadOnly ) {
|
|
222
|
+
return 'text.disabled';
|
|
223
|
+
}
|
|
224
|
+
return showPlaceholder ? 'text.tertiary' : 'text.primary';
|
|
225
|
+
};
|
|
226
|
+
|
|
193
227
|
const ItemLabel = ( { value }: { value: Record< string, unknown > } ) => {
|
|
194
|
-
const { placeholder, patternLabel } = useRepeatableControlContext();
|
|
228
|
+
const { placeholder, patternLabel, props: childProps } = useRepeatableControlContext();
|
|
195
229
|
const showPlaceholder = shouldShowPlaceholder( patternLabel, value );
|
|
196
230
|
const label = showPlaceholder ? placeholder : interpolate( patternLabel, value );
|
|
197
|
-
const
|
|
231
|
+
const isReadOnly = !! childProps?.readOnly;
|
|
232
|
+
const color = getTextColor( isReadOnly, showPlaceholder );
|
|
198
233
|
|
|
199
234
|
return (
|
|
200
235
|
<Box component="span" color={ color }>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
import { createControl } from '../create-control';
|
|
5
|
+
import { SelectControl, type SelectOption } from './select-control';
|
|
6
|
+
|
|
7
|
+
type ExtendedWindow = Window & {
|
|
8
|
+
elementor: { $previewContents: [ HTMLIFrameElement ]; config: { document: { id: string } } };
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const getOffCanvasElements = () => {
|
|
12
|
+
const extendedWindow = window as unknown as ExtendedWindow;
|
|
13
|
+
const documentId = extendedWindow.elementor.config.document.id;
|
|
14
|
+
const offCanvasElements = extendedWindow.elementor.$previewContents[ 0 ].querySelectorAll(
|
|
15
|
+
`[data-elementor-id="${ documentId }"] .elementor-widget-off-canvas.elementor-element-edit-mode`
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
return Array.from( offCanvasElements as unknown as HTMLElement[] ).map( ( offCanvasElement ) => {
|
|
19
|
+
return {
|
|
20
|
+
label: offCanvasElement.querySelector( '.e-off-canvas' )?.getAttribute( 'aria-label' ) ?? '',
|
|
21
|
+
value: offCanvasElement.dataset.id,
|
|
22
|
+
} as SelectOption;
|
|
23
|
+
} );
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const collectionMethods = {
|
|
27
|
+
'off-canvas': getOffCanvasElements,
|
|
28
|
+
} as const;
|
|
29
|
+
|
|
30
|
+
type SelectControlWrapperProps = Parameters< typeof SelectControl >[ 0 ] & {
|
|
31
|
+
collectionId?: keyof typeof collectionMethods;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const useDynamicOptions = (
|
|
35
|
+
collectionId?: keyof typeof collectionMethods,
|
|
36
|
+
initialOptions?: SelectControlWrapperProps[ 'options' ]
|
|
37
|
+
) => {
|
|
38
|
+
const [ options, setOptions ] = useState< SelectControlWrapperProps[ 'options' ] >( initialOptions ?? [] );
|
|
39
|
+
|
|
40
|
+
useEffect( () => {
|
|
41
|
+
if ( ! collectionId || ! collectionMethods[ collectionId ] ) {
|
|
42
|
+
setOptions( initialOptions ?? [] );
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
setOptions( collectionMethods[ collectionId ]() );
|
|
46
|
+
}, [ collectionId, initialOptions ] );
|
|
47
|
+
|
|
48
|
+
return options;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const SelectControlWrapper = createControl(
|
|
52
|
+
( { collectionId, options, ...props }: SelectControlWrapperProps ) => {
|
|
53
|
+
const actualOptions = useDynamicOptions( collectionId, options );
|
|
54
|
+
|
|
55
|
+
return <SelectControl options={ actualOptions } { ...props } />;
|
|
56
|
+
}
|
|
57
|
+
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { stringPropTypeUtil, type StringPropValue } from '@elementor/editor-props';
|
|
3
3
|
import { MenuListItem } from '@elementor/editor-ui';
|
|
4
|
-
import { Select, type SelectChangeEvent, Typography } from '@elementor/ui';
|
|
4
|
+
import { Select, type SelectChangeEvent, type SelectProps, Typography } from '@elementor/ui';
|
|
5
5
|
|
|
6
6
|
import { useBoundProp } from '../bound-prop-context';
|
|
7
7
|
import ControlActions from '../control-actions/control-actions';
|
|
@@ -13,12 +13,13 @@ export type SelectOption = {
|
|
|
13
13
|
disabled?: boolean;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
type
|
|
16
|
+
type SelectControlProps = {
|
|
17
17
|
options: SelectOption[];
|
|
18
18
|
onChange?: ( newValue: string | null, previousValue: string | null | undefined ) => void;
|
|
19
|
+
MenuProps?: SelectProps[ 'MenuProps' ];
|
|
20
|
+
ariaLabel?: string;
|
|
19
21
|
};
|
|
20
|
-
|
|
21
|
-
export const SelectControl = createControl( ( { options, onChange }: Props ) => {
|
|
22
|
+
export const SelectControl = createControl( ( { options, onChange, MenuProps, ariaLabel }: SelectControlProps ) => {
|
|
22
23
|
const { value, setValue, disabled, placeholder } = useBoundProp( stringPropTypeUtil );
|
|
23
24
|
const handleChange = ( event: SelectChangeEvent< StringPropValue[ 'value' ] > ) => {
|
|
24
25
|
const newValue = event.target.value || null;
|
|
@@ -26,6 +27,7 @@ export const SelectControl = createControl( ( { options, onChange }: Props ) =>
|
|
|
26
27
|
onChange?.( newValue, value );
|
|
27
28
|
setValue( newValue );
|
|
28
29
|
};
|
|
30
|
+
const isDisabled = disabled || options.length === 0;
|
|
29
31
|
|
|
30
32
|
return (
|
|
31
33
|
<ControlActions>
|
|
@@ -33,6 +35,8 @@ export const SelectControl = createControl( ( { options, onChange }: Props ) =>
|
|
|
33
35
|
sx={ { overflow: 'hidden' } }
|
|
34
36
|
displayEmpty
|
|
35
37
|
size="tiny"
|
|
38
|
+
MenuProps={ MenuProps }
|
|
39
|
+
aria-label={ ariaLabel || placeholder }
|
|
36
40
|
renderValue={ ( selectedValue: string | null ) => {
|
|
37
41
|
const findOptionByValue = ( searchValue: string | null ) =>
|
|
38
42
|
options.find( ( opt ) => opt.value === searchValue );
|
|
@@ -55,7 +59,7 @@ export const SelectControl = createControl( ( { options, onChange }: Props ) =>
|
|
|
55
59
|
} }
|
|
56
60
|
value={ value ?? '' }
|
|
57
61
|
onChange={ handleChange }
|
|
58
|
-
disabled={
|
|
62
|
+
disabled={ isDisabled }
|
|
59
63
|
fullWidth
|
|
60
64
|
>
|
|
61
65
|
{ options.map( ( { label, ...props } ) => (
|
|
@@ -20,13 +20,22 @@ type SelectionSizeControlProps = {
|
|
|
20
20
|
sizeLabel: string;
|
|
21
21
|
selectionConfig: SelectionComponentConfig;
|
|
22
22
|
sizeConfigMap: Record< string, SizeControlConfig >;
|
|
23
|
+
isRepeaterControl?: boolean;
|
|
23
24
|
};
|
|
24
25
|
|
|
25
26
|
export const SelectionSizeControl = createControl(
|
|
26
|
-
( {
|
|
27
|
+
( {
|
|
28
|
+
selectionLabel,
|
|
29
|
+
sizeLabel,
|
|
30
|
+
selectionConfig,
|
|
31
|
+
sizeConfigMap,
|
|
32
|
+
isRepeaterControl = false,
|
|
33
|
+
}: SelectionSizeControlProps ) => {
|
|
27
34
|
const { value, setValue, propType } = useBoundProp( selectionSizePropTypeUtil );
|
|
28
35
|
const rowRef = useRef< HTMLDivElement >( null );
|
|
29
36
|
|
|
37
|
+
const sizeFieldId = sizeLabel.replace( /\s+/g, '-' ).toLowerCase();
|
|
38
|
+
|
|
30
39
|
const currentSizeConfig = useMemo( () => {
|
|
31
40
|
switch ( value.selection.$$type ) {
|
|
32
41
|
case 'key-value':
|
|
@@ -53,7 +62,7 @@ export const SelectionSizeControl = createControl(
|
|
|
53
62
|
{ currentSizeConfig && (
|
|
54
63
|
<>
|
|
55
64
|
<Grid item xs={ 6 } sx={ { display: 'flex', alignItems: 'center' } }>
|
|
56
|
-
<ControlFormLabel>{ sizeLabel }</ControlFormLabel>
|
|
65
|
+
<ControlFormLabel htmlFor={ sizeFieldId }>{ sizeLabel }</ControlFormLabel>
|
|
57
66
|
</Grid>
|
|
58
67
|
<Grid item xs={ 6 }>
|
|
59
68
|
<PropKeyProvider bind="size">
|
|
@@ -62,6 +71,8 @@ export const SelectionSizeControl = createControl(
|
|
|
62
71
|
variant={ currentSizeConfig.variant }
|
|
63
72
|
units={ currentSizeConfig.units }
|
|
64
73
|
defaultUnit={ currentSizeConfig.defaultUnit }
|
|
74
|
+
id={ sizeFieldId }
|
|
75
|
+
isRepeaterControl={ isRepeaterControl }
|
|
65
76
|
/>
|
|
66
77
|
</PropKeyProvider>
|
|
67
78
|
</Grid>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { type RefObject, useEffect,
|
|
2
|
+
import { type RefObject, useEffect, useMemo } from 'react';
|
|
3
3
|
import { type PropType, sizePropTypeUtil, type SizePropValue } from '@elementor/editor-props';
|
|
4
4
|
import { useActiveBreakpoint } from '@elementor/editor-responsive';
|
|
5
5
|
import { usePopupState } from '@elementor/ui';
|
|
@@ -42,6 +42,8 @@ type BaseSizeControlProps = {
|
|
|
42
42
|
min?: number;
|
|
43
43
|
enablePropTypeUnits?: boolean;
|
|
44
44
|
id?: string;
|
|
45
|
+
ariaLabel?: string;
|
|
46
|
+
isRepeaterControl?: boolean;
|
|
45
47
|
};
|
|
46
48
|
|
|
47
49
|
type LengthSizeControlProps = BaseSizeControlProps &
|
|
@@ -79,6 +81,8 @@ const defaultUnits: Record< SizeControlProps[ 'variant' ], Unit[] > = {
|
|
|
79
81
|
time: [ ...timeUnits ] as TimeUnit[],
|
|
80
82
|
} as const;
|
|
81
83
|
|
|
84
|
+
export const CUSTOM_SIZE_LABEL = 'fx';
|
|
85
|
+
|
|
82
86
|
export const SizeControl = createControl(
|
|
83
87
|
( {
|
|
84
88
|
variant = 'length' as SizeControlProps[ 'variant' ],
|
|
@@ -92,6 +96,8 @@ export const SizeControl = createControl(
|
|
|
92
96
|
min = 0,
|
|
93
97
|
enablePropTypeUnits = false,
|
|
94
98
|
id,
|
|
99
|
+
ariaLabel,
|
|
100
|
+
isRepeaterControl = false,
|
|
95
101
|
}: Omit< SizeControlProps, 'variant' > & { variant?: SizeVariant } ) => {
|
|
96
102
|
const {
|
|
97
103
|
value: sizeValue,
|
|
@@ -102,28 +108,22 @@ export const SizeControl = createControl(
|
|
|
102
108
|
propType,
|
|
103
109
|
} = useBoundProp( sizePropTypeUtil );
|
|
104
110
|
const actualDefaultUnit = defaultUnit ?? externalPlaceholder?.unit ?? defaultSelectedUnit[ variant ];
|
|
105
|
-
const [ internalState, setInternalState ] = useState( createStateFromSizeProp( sizeValue, actualDefaultUnit ) );
|
|
106
111
|
const activeBreakpoint = useActiveBreakpoint();
|
|
107
|
-
const actualUnits = resolveUnits( propType, enablePropTypeUnits, variant, units );
|
|
108
|
-
|
|
109
112
|
const actualExtendedOptions = useSizeExtendedOptions( extendedOptions || [], disableCustom ?? false );
|
|
113
|
+
const actualUnits = resolveUnits( propType, enablePropTypeUnits, variant, units, actualExtendedOptions );
|
|
114
|
+
|
|
110
115
|
const popupState = usePopupState( { variant: 'popover' } );
|
|
111
116
|
|
|
117
|
+
const memorizedExternalState = useMemo(
|
|
118
|
+
() => createStateFromSizeProp( sizeValue, actualDefaultUnit ),
|
|
119
|
+
[ sizeValue, actualDefaultUnit ]
|
|
120
|
+
);
|
|
121
|
+
|
|
112
122
|
const [ state, setState ] = useSyncExternalState( {
|
|
113
|
-
external:
|
|
123
|
+
external: memorizedExternalState,
|
|
114
124
|
setExternal: ( newState: State | null, options, meta ) =>
|
|
115
125
|
setSizeValue( extractValueFromState( newState ), options, meta ),
|
|
116
|
-
persistWhen: ( newState ) =>
|
|
117
|
-
if ( ! newState?.unit ) {
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if ( isUnitExtendedOption( newState.unit ) ) {
|
|
122
|
-
return newState.unit === 'auto' ? true : !! newState.custom;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return !! newState?.numeric || newState?.numeric === 0;
|
|
126
|
-
},
|
|
126
|
+
persistWhen: ( newState ) => !! extractValueFromState( newState ),
|
|
127
127
|
fallback: ( newState ) => ( {
|
|
128
128
|
unit: newState?.unit ?? actualDefaultUnit,
|
|
129
129
|
numeric: newState?.numeric ?? DEFAULT_SIZE,
|
|
@@ -132,7 +132,7 @@ export const SizeControl = createControl(
|
|
|
132
132
|
} );
|
|
133
133
|
|
|
134
134
|
const { size: controlSize = DEFAULT_SIZE, unit: controlUnit = actualDefaultUnit } =
|
|
135
|
-
extractValueFromState( state ) || {};
|
|
135
|
+
extractValueFromState( state, true ) || {};
|
|
136
136
|
|
|
137
137
|
const handleUnitChange = ( newUnit: Unit | ExtendedOption ) => {
|
|
138
138
|
if ( newUnit === 'custom' ) {
|
|
@@ -169,7 +169,7 @@ export const SizeControl = createControl(
|
|
|
169
169
|
}
|
|
170
170
|
};
|
|
171
171
|
|
|
172
|
-
|
|
172
|
+
const handleLinkedSizeControlChanges = () => {
|
|
173
173
|
const newState = createStateFromSizeProp(
|
|
174
174
|
sizeValue,
|
|
175
175
|
state.unit === 'custom' ? state.unit : actualDefaultUnit,
|
|
@@ -188,12 +188,18 @@ export const SizeControl = createControl(
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
if ( state.unit === newState.unit ) {
|
|
191
|
-
|
|
191
|
+
setState( mergedStates );
|
|
192
192
|
|
|
193
193
|
return;
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
setState( newState );
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
useEffect( () => {
|
|
200
|
+
if ( ! isRepeaterControl ) {
|
|
201
|
+
handleLinkedSizeControlChanges();
|
|
202
|
+
}
|
|
197
203
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
198
204
|
}, [ sizeValue ] );
|
|
199
205
|
|
|
@@ -212,7 +218,7 @@ export const SizeControl = createControl(
|
|
|
212
218
|
disabled={ disabled }
|
|
213
219
|
size={ controlSize }
|
|
214
220
|
unit={ controlUnit }
|
|
215
|
-
units={ [ ...actualUnits
|
|
221
|
+
units={ [ ...actualUnits ] }
|
|
216
222
|
placeholder={ placeholder }
|
|
217
223
|
startIcon={ startIcon }
|
|
218
224
|
handleSizeChange={ handleSizeChange }
|
|
@@ -222,6 +228,7 @@ export const SizeControl = createControl(
|
|
|
222
228
|
popupState={ popupState }
|
|
223
229
|
min={ min }
|
|
224
230
|
id={ id }
|
|
231
|
+
ariaLabel={ ariaLabel }
|
|
225
232
|
/>
|
|
226
233
|
{ anchorRef?.current && popupState.isOpen && (
|
|
227
234
|
<TextFieldPopover
|
|
@@ -241,12 +248,13 @@ function resolveUnits(
|
|
|
241
248
|
propType: PropType,
|
|
242
249
|
enablePropTypeUnits: boolean,
|
|
243
250
|
variant: SizeVariant,
|
|
244
|
-
externalUnits?: Unit[]
|
|
251
|
+
externalUnits?: Unit[],
|
|
252
|
+
actualExtendedOptions?: ExtendedOption[]
|
|
245
253
|
) {
|
|
246
254
|
const fallback = [ ...defaultUnits[ variant ] ];
|
|
247
255
|
|
|
248
256
|
if ( ! enablePropTypeUnits ) {
|
|
249
|
-
return externalUnits ?? fallback;
|
|
257
|
+
return [ ...( externalUnits ?? fallback ), ...( actualExtendedOptions || [] ) ];
|
|
250
258
|
}
|
|
251
259
|
|
|
252
260
|
return ( propType.settings?.available_units as Unit[] ) ?? fallback;
|
|
@@ -279,7 +287,7 @@ function createStateFromSizeProp(
|
|
|
279
287
|
};
|
|
280
288
|
}
|
|
281
289
|
|
|
282
|
-
function extractValueFromState( state: State | null ): SizeValue | null {
|
|
290
|
+
function extractValueFromState( state: State | null, allowEmpty: boolean = false ): SizeValue | null {
|
|
283
291
|
if ( ! state ) {
|
|
284
292
|
return null;
|
|
285
293
|
}
|
|
@@ -294,10 +302,20 @@ function extractValueFromState( state: State | null ): SizeValue | null {
|
|
|
294
302
|
return { size: '', unit };
|
|
295
303
|
}
|
|
296
304
|
|
|
305
|
+
if ( unit === 'custom' ) {
|
|
306
|
+
return { size: state.custom ?? '', unit: 'custom' };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const numeric = state.numeric;
|
|
310
|
+
|
|
311
|
+
if ( ! allowEmpty && ( numeric === undefined || numeric === null || Number.isNaN( numeric ) ) ) {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
|
|
297
315
|
return {
|
|
298
|
-
size:
|
|
316
|
+
size: numeric,
|
|
299
317
|
unit,
|
|
300
|
-
}
|
|
318
|
+
};
|
|
301
319
|
}
|
|
302
320
|
|
|
303
321
|
function areStatesEqual( state1: State, state2: State ): boolean {
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { useState } from 'react';
|
|
3
|
+
import { useCurrentUserCapabilities } from '@elementor/editor-current-user';
|
|
3
4
|
import { imageSrcPropTypeUtil } from '@elementor/editor-props';
|
|
4
5
|
import { UploadIcon } from '@elementor/icons';
|
|
5
|
-
import { Button, Card, CardMedia, CardOverlay, CircularProgress, Stack, styled } from '@elementor/ui';
|
|
6
|
+
import { Button, Card, CardMedia, CardOverlay, CircularProgress, Stack, styled, ThemeProvider } from '@elementor/ui';
|
|
6
7
|
import { type OpenOptions, useWpMediaAttachment, useWpMediaFrame } from '@elementor/wp-media';
|
|
7
8
|
import { __ } from '@wordpress/i18n';
|
|
8
9
|
|
|
9
10
|
import { useBoundProp } from '../bound-prop-context';
|
|
11
|
+
import { ConditionalControlInfotip } from '../components/conditional-control-infotip';
|
|
10
12
|
import { EnableUnfilteredModal } from '../components/enable-unfiltered-modal';
|
|
11
13
|
import ControlActions from '../control-actions/control-actions';
|
|
12
14
|
import { createControl } from '../create-control';
|
|
@@ -47,6 +49,8 @@ export const SvgMediaControl = createControl( () => {
|
|
|
47
49
|
const src = attachment?.url ?? url?.value ?? null;
|
|
48
50
|
const { data: allowSvgUpload } = useUnfilteredFilesUpload();
|
|
49
51
|
const [ unfilteredModalOpenState, setUnfilteredModalOpenState ] = useState( false );
|
|
52
|
+
const { canUser } = useCurrentUserCapabilities();
|
|
53
|
+
const canManageOptions = canUser( 'manage_options' );
|
|
50
54
|
|
|
51
55
|
const { open } = useWpMediaFrame( {
|
|
52
56
|
mediaTypes: [ 'svg' ],
|
|
@@ -79,6 +83,18 @@ export const SvgMediaControl = createControl( () => {
|
|
|
79
83
|
}
|
|
80
84
|
};
|
|
81
85
|
|
|
86
|
+
const infotipProps = {
|
|
87
|
+
title: __( "Sorry, you can't upload that file yet.", 'elementor' ),
|
|
88
|
+
description: (
|
|
89
|
+
<>
|
|
90
|
+
{ __( 'To upload them anyway, ask the site administrator to enable unfiltered', 'elementor' ) }
|
|
91
|
+
<br />
|
|
92
|
+
{ __( 'file uploads.', 'elementor' ) }
|
|
93
|
+
</>
|
|
94
|
+
),
|
|
95
|
+
isEnabled: ! canManageOptions,
|
|
96
|
+
};
|
|
97
|
+
|
|
82
98
|
return (
|
|
83
99
|
<Stack gap={ 1 }>
|
|
84
100
|
<EnableUnfilteredModal open={ unfilteredModalOpenState } onClose={ onCloseUnfilteredModal } />
|
|
@@ -112,15 +128,22 @@ export const SvgMediaControl = createControl( () => {
|
|
|
112
128
|
>
|
|
113
129
|
{ __( 'Select SVG', 'elementor' ) }
|
|
114
130
|
</Button>
|
|
115
|
-
<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
131
|
+
<ConditionalControlInfotip { ...infotipProps }>
|
|
132
|
+
<span>
|
|
133
|
+
<ThemeProvider colorScheme={ canManageOptions ? 'light' : 'dark' }>
|
|
134
|
+
<Button
|
|
135
|
+
size="tiny"
|
|
136
|
+
variant="text"
|
|
137
|
+
color="inherit"
|
|
138
|
+
startIcon={ <UploadIcon /> }
|
|
139
|
+
disabled={ canManageOptions ? false : true }
|
|
140
|
+
onClick={ () => canManageOptions && handleClick( MODE_UPLOAD ) }
|
|
141
|
+
>
|
|
142
|
+
{ __( 'Upload', 'elementor' ) }
|
|
143
|
+
</Button>
|
|
144
|
+
</ThemeProvider>
|
|
145
|
+
</span>
|
|
146
|
+
</ConditionalControlInfotip>
|
|
124
147
|
</Stack>
|
|
125
148
|
</CardOverlay>
|
|
126
149
|
</StyledCard>
|
|
@@ -8,9 +8,10 @@ import { createControl } from '../create-control';
|
|
|
8
8
|
|
|
9
9
|
type Props = {
|
|
10
10
|
placeholder?: string;
|
|
11
|
+
ariaLabel?: string;
|
|
11
12
|
};
|
|
12
13
|
|
|
13
|
-
export const TextAreaControl = createControl( ( { placeholder }: Props ) => {
|
|
14
|
+
export const TextAreaControl = createControl( ( { placeholder, ariaLabel }: Props ) => {
|
|
14
15
|
const { value, setValue, disabled } = useBoundProp( stringPropTypeUtil );
|
|
15
16
|
|
|
16
17
|
const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
|
|
@@ -28,6 +29,9 @@ export const TextAreaControl = createControl( ( { placeholder }: Props ) => {
|
|
|
28
29
|
value={ value ?? '' }
|
|
29
30
|
onChange={ handleChange }
|
|
30
31
|
placeholder={ placeholder }
|
|
32
|
+
inputProps={ {
|
|
33
|
+
...( ariaLabel ? { 'aria-label': ariaLabel } : {} ),
|
|
34
|
+
} }
|
|
31
35
|
/>
|
|
32
36
|
</ControlActions>
|
|
33
37
|
);
|
|
@@ -14,6 +14,7 @@ export const TextControl = createControl(
|
|
|
14
14
|
inputDisabled,
|
|
15
15
|
helperText,
|
|
16
16
|
sx,
|
|
17
|
+
ariaLabel,
|
|
17
18
|
}: {
|
|
18
19
|
placeholder?: string;
|
|
19
20
|
error?: boolean;
|
|
@@ -21,6 +22,7 @@ export const TextControl = createControl(
|
|
|
21
22
|
inputDisabled?: boolean;
|
|
22
23
|
helperText?: string;
|
|
23
24
|
sx?: SxProps;
|
|
25
|
+
ariaLabel?: string;
|
|
24
26
|
} ) => {
|
|
25
27
|
const { value, setValue, disabled } = useBoundProp( stringPropTypeUtil );
|
|
26
28
|
const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => setValue( event.target.value );
|
|
@@ -37,6 +39,9 @@ export const TextControl = createControl(
|
|
|
37
39
|
error={ error }
|
|
38
40
|
helperText={ helperText }
|
|
39
41
|
sx={ sx }
|
|
42
|
+
inputProps={ {
|
|
43
|
+
...( ariaLabel ? { 'aria-label': ariaLabel } : {} ),
|
|
44
|
+
} }
|
|
40
45
|
/>
|
|
41
46
|
</ControlActions>
|
|
42
47
|
);
|
|
@@ -5,6 +5,7 @@ import { type ToggleButtonProps } from '@elementor/ui';
|
|
|
5
5
|
import { useBoundProp } from '../bound-prop-context';
|
|
6
6
|
import { ControlToggleButtonGroup, type ToggleButtonGroupItem } from '../components/control-toggle-button-group';
|
|
7
7
|
import { createControl } from '../create-control';
|
|
8
|
+
import { convertToggleOptionsToAtomic, type DynamicToggleOption } from '../utils/convert-toggle-options-to-atomic';
|
|
8
9
|
|
|
9
10
|
export type ToggleControlProps< T extends PropValue > = {
|
|
10
11
|
options: Array< ToggleButtonGroupItem< T > & { exclusive?: boolean } >;
|
|
@@ -12,6 +13,7 @@ export type ToggleControlProps< T extends PropValue > = {
|
|
|
12
13
|
size?: ToggleButtonProps[ 'size' ];
|
|
13
14
|
exclusive?: boolean;
|
|
14
15
|
maxItems?: number;
|
|
16
|
+
convertOptions?: boolean;
|
|
15
17
|
};
|
|
16
18
|
|
|
17
19
|
export const ToggleControl = createControl(
|
|
@@ -21,10 +23,17 @@ export const ToggleControl = createControl(
|
|
|
21
23
|
size = 'tiny',
|
|
22
24
|
exclusive = true,
|
|
23
25
|
maxItems,
|
|
26
|
+
convertOptions = false,
|
|
24
27
|
}: ToggleControlProps< StringPropValue[ 'value' ] > ) => {
|
|
25
28
|
const { value, setValue, placeholder, disabled } = useBoundProp( stringPropTypeUtil );
|
|
26
29
|
|
|
27
|
-
const
|
|
30
|
+
const processedOptions = convertOptions
|
|
31
|
+
? convertToggleOptionsToAtomic( options as DynamicToggleOption[] )
|
|
32
|
+
: ( options as Array< ToggleButtonGroupItem< StringPropValue[ 'value' ] > & { exclusive?: boolean } > );
|
|
33
|
+
|
|
34
|
+
const exclusiveValues = processedOptions
|
|
35
|
+
.filter( ( option ) => option.exclusive )
|
|
36
|
+
.map( ( option ) => option.value );
|
|
28
37
|
|
|
29
38
|
const handleNonExclusiveToggle = ( selectedValues: StringPropValue[ 'value' ][] ) => {
|
|
30
39
|
const newSelectedValue = selectedValues[ selectedValues.length - 1 ];
|
|
@@ -38,7 +47,7 @@ export const ToggleControl = createControl(
|
|
|
38
47
|
};
|
|
39
48
|
|
|
40
49
|
const toggleButtonGroupProps = {
|
|
41
|
-
items:
|
|
50
|
+
items: processedOptions,
|
|
42
51
|
maxItems,
|
|
43
52
|
fullWidth,
|
|
44
53
|
size,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { type TransformFunctionsItemPropValue } from '@elementor/editor-props';
|
|
3
|
-
import {
|
|
3
|
+
import { ArrowAutofitHeightIcon, ArrowsMaximizeIcon, RotateClockwise2Icon, SkewXIcon } from '@elementor/icons';
|
|
4
4
|
|
|
5
5
|
import { TransformFunctionKeys } from './initial-values';
|
|
6
6
|
|
|
@@ -9,7 +9,7 @@ export const TransformIcon = ( { value }: { value: TransformFunctionsItemPropVal
|
|
|
9
9
|
case TransformFunctionKeys.move:
|
|
10
10
|
return <ArrowsMaximizeIcon fontSize="tiny" />;
|
|
11
11
|
case TransformFunctionKeys.scale:
|
|
12
|
-
return <
|
|
12
|
+
return <ArrowAutofitHeightIcon fontSize="tiny" />;
|
|
13
13
|
case TransformFunctionKeys.rotate:
|
|
14
14
|
return <RotateClockwise2Icon fontSize="tiny" />;
|
|
15
15
|
case TransformFunctionKeys.skew:
|