@elementor/editor-editing-panel 1.38.1 → 1.40.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 +42 -0
- package/dist/index.js +1038 -754
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1014 -725
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -9
- package/src/components/creatable-autocomplete/autocomplete-option-internal-properties.ts +3 -6
- package/src/components/creatable-autocomplete/creatable-autocomplete.tsx +25 -2
- package/src/components/creatable-autocomplete/types.ts +13 -4
- package/src/components/creatable-autocomplete/use-autocomplete-change.ts +59 -46
- package/src/components/creatable-autocomplete/use-create-option.ts +4 -4
- package/src/components/css-classes/css-class-context.tsx +30 -0
- package/src/components/css-classes/css-class-item.tsx +8 -19
- package/src/components/css-classes/css-class-menu.tsx +78 -78
- package/src/components/css-classes/css-class-selector.tsx +46 -32
- package/src/components/css-classes/use-apply-and-unapply-class.ts +178 -0
- package/src/components/editing-panel-tabs.tsx +7 -1
- package/src/components/settings-tab.tsx +14 -1
- package/src/components/style-indicator.tsx +1 -1
- package/src/components/style-sections/size-section/object-fit-field.tsx +1 -1
- package/src/components/style-sections/size-section/object-position-field.tsx +1 -1
- package/src/components/style-sections/size-section/size-section.tsx +13 -5
- package/src/components/style-sections/typography-section/typography-section.tsx +1 -1
- package/src/components/style-tab.tsx +82 -24
- package/src/controls-registry/controls-registry.tsx +2 -0
- package/src/hooks/use-active-style-def-id.ts +5 -2
- package/src/hooks/use-default-panel-settings.ts +33 -0
- package/src/hooks/use-state-by-element.ts +2 -1
- package/src/sync/experiments-flags.ts +4 -0
- package/src/hooks/use-unapply-class.ts +0 -29
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-editing-panel",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.40.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "Elementor Team",
|
|
6
6
|
"homepage": "https://elementor.com/",
|
|
@@ -39,24 +39,25 @@
|
|
|
39
39
|
"dev": "tsup --config=../../tsup.dev.ts"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@elementor/editor": "0.19.
|
|
43
|
-
"@elementor/editor-canvas": "0.22.
|
|
44
|
-
"@elementor/editor-controls": "0.
|
|
45
|
-
"@elementor/editor-current-user": "0.
|
|
42
|
+
"@elementor/editor": "0.19.4",
|
|
43
|
+
"@elementor/editor-canvas": "0.22.1",
|
|
44
|
+
"@elementor/editor-controls": "0.33.0",
|
|
45
|
+
"@elementor/editor-current-user": "0.5.0",
|
|
46
|
+
"@elementor/editor-documents": "0.13.6",
|
|
46
47
|
"@elementor/editor-elements": "0.8.4",
|
|
47
|
-
"@elementor/editor-panels": "0.15.
|
|
48
|
+
"@elementor/editor-panels": "0.15.4",
|
|
48
49
|
"@elementor/editor-props": "0.12.1",
|
|
49
50
|
"@elementor/editor-responsive": "0.13.5",
|
|
50
51
|
"@elementor/editor-styles": "0.6.8",
|
|
51
|
-
"@elementor/editor-styles-repository": "0.
|
|
52
|
-
"@elementor/editor-ui": "0.
|
|
52
|
+
"@elementor/editor-styles-repository": "0.10.0",
|
|
53
|
+
"@elementor/editor-ui": "0.9.0",
|
|
53
54
|
"@elementor/editor-v1-adapters": "0.12.0",
|
|
54
55
|
"@elementor/icons": "1.40.1",
|
|
55
56
|
"@elementor/locations": "0.8.0",
|
|
56
57
|
"@elementor/menus": "0.1.5",
|
|
57
58
|
"@elementor/schema": "0.1.2",
|
|
58
59
|
"@elementor/session": "0.1.0",
|
|
59
|
-
"@elementor/ui": "1.34.
|
|
60
|
+
"@elementor/ui": "1.34.5",
|
|
60
61
|
"@elementor/utils": "0.4.0",
|
|
61
62
|
"@wordpress/i18n": "^5.13.0"
|
|
62
63
|
},
|
|
@@ -12,10 +12,7 @@ export function addGroupToOptions< TOption extends Option >(
|
|
|
12
12
|
} );
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export function
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return rest as unknown as TOption;
|
|
20
|
-
} );
|
|
15
|
+
export function removeInternalKeys< TOption extends Option >( option: InternalOption< TOption > ): TOption {
|
|
16
|
+
const { _group, _action, ...rest } = option;
|
|
17
|
+
return rest as unknown as TOption;
|
|
21
18
|
}
|
|
@@ -19,6 +19,8 @@ import { useInputState, useOpenState } from './use-autocomplete-states';
|
|
|
19
19
|
import { useCreateOption } from './use-create-option';
|
|
20
20
|
import { useFilterOptions } from './use-filter-options';
|
|
21
21
|
|
|
22
|
+
const MIN_INPUT_LENGTH = 2;
|
|
23
|
+
|
|
22
24
|
export const CreatableAutocomplete = React.forwardRef( CreatableAutocompleteInner ) as <
|
|
23
25
|
TOption extends SafeOptionConstraint,
|
|
24
26
|
>(
|
|
@@ -36,6 +38,7 @@ function CreatableAutocompleteInner< TOption extends SafeOptionConstraint >(
|
|
|
36
38
|
placeholder,
|
|
37
39
|
onCreate,
|
|
38
40
|
validate,
|
|
41
|
+
renderEmptyState,
|
|
39
42
|
...props
|
|
40
43
|
}: CreatableAutocompleteProps< TOption >,
|
|
41
44
|
ref: React.ForwardedRef< HTMLElement >
|
|
@@ -56,8 +59,13 @@ function CreatableAutocompleteInner< TOption extends SafeOptionConstraint >(
|
|
|
56
59
|
setInputValue,
|
|
57
60
|
closeDropdown,
|
|
58
61
|
} );
|
|
62
|
+
|
|
59
63
|
const filterOptions = useFilterOptions( { options, selected, onCreate, entityName } );
|
|
60
64
|
|
|
65
|
+
const isCreatable = Boolean( onCreate );
|
|
66
|
+
|
|
67
|
+
const freeSolo = isCreatable || inputValue.length < MIN_INPUT_LENGTH || undefined;
|
|
68
|
+
|
|
61
69
|
return (
|
|
62
70
|
<Autocomplete< InternalOption< TOption >, true, true, true >
|
|
63
71
|
renderTags={ ( tagValue, getTagProps ) => {
|
|
@@ -72,7 +80,8 @@ function CreatableAutocompleteInner< TOption extends SafeOptionConstraint >(
|
|
|
72
80
|
} }
|
|
73
81
|
{ ...( props as AutocompleteProps< InternalOption< TOption >, true, true, true > ) }
|
|
74
82
|
ref={ ref }
|
|
75
|
-
freeSolo
|
|
83
|
+
freeSolo={ freeSolo }
|
|
84
|
+
forcePopupIcon={ false }
|
|
76
85
|
multiple
|
|
77
86
|
clearOnBlur
|
|
78
87
|
selectOnFocus
|
|
@@ -98,8 +107,8 @@ function CreatableAutocompleteInner< TOption extends SafeOptionConstraint >(
|
|
|
98
107
|
return (
|
|
99
108
|
<TextField
|
|
100
109
|
{ ...params }
|
|
101
|
-
placeholder={ placeholder }
|
|
102
110
|
error={ Boolean( error ) }
|
|
111
|
+
placeholder={ placeholder }
|
|
103
112
|
{ ...inputHandlers }
|
|
104
113
|
sx={ ( theme: Theme ) => ( {
|
|
105
114
|
'.MuiAutocomplete-inputRoot.MuiInputBase-adornedStart': {
|
|
@@ -134,6 +143,20 @@ function CreatableAutocompleteInner< TOption extends SafeOptionConstraint >(
|
|
|
134
143
|
</li>
|
|
135
144
|
);
|
|
136
145
|
} }
|
|
146
|
+
noOptionsText={ renderEmptyState?.( {
|
|
147
|
+
searchValue: inputValue,
|
|
148
|
+
onClear: () => {
|
|
149
|
+
setInputValue( '' );
|
|
150
|
+
closeDropdown();
|
|
151
|
+
},
|
|
152
|
+
} ) }
|
|
153
|
+
isOptionEqualToValue={ ( option, value ) => {
|
|
154
|
+
if ( typeof option === 'string' ) {
|
|
155
|
+
return option === value;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return option.value === value.value;
|
|
159
|
+
} }
|
|
137
160
|
/>
|
|
138
161
|
);
|
|
139
162
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AutocompleteProps } from '@elementor/ui';
|
|
1
|
+
import { type AutocompleteChangeReason, type AutocompleteProps } from '@elementor/ui';
|
|
2
2
|
|
|
3
3
|
export type Option = {
|
|
4
4
|
label: string;
|
|
@@ -21,6 +21,14 @@ export type SafeOptionConstraint = Option & {
|
|
|
21
21
|
export type ValidationResult = { isValid: true; errorMessage: null } | { isValid: false; errorMessage: string };
|
|
22
22
|
export type ValidationEvent = 'inputChange' | 'create';
|
|
23
23
|
|
|
24
|
+
export type OnSelect< TOption extends Option > = (
|
|
25
|
+
value: TOption[],
|
|
26
|
+
reason: AutocompleteChangeReason,
|
|
27
|
+
option: TOption
|
|
28
|
+
) => void;
|
|
29
|
+
type OnCreate = ( value: string ) => unknown;
|
|
30
|
+
type Validate = ( value: string, event: ValidationEvent ) => ValidationResult;
|
|
31
|
+
|
|
24
32
|
export type CreatableAutocompleteProps< TOption extends SafeOptionConstraint > = Omit<
|
|
25
33
|
AutocompleteProps< TOption, true, true, true >,
|
|
26
34
|
'renderInput' | 'onSelect' | 'options'
|
|
@@ -32,7 +40,8 @@ export type CreatableAutocompleteProps< TOption extends SafeOptionConstraint > =
|
|
|
32
40
|
singular: string;
|
|
33
41
|
plural: string;
|
|
34
42
|
};
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
43
|
+
renderEmptyState?: ( props: { searchValue: string; onClear: () => void } ) => React.ReactNode;
|
|
44
|
+
onSelect?: OnSelect< TOption >;
|
|
45
|
+
onCreate?: OnCreate;
|
|
46
|
+
validate?: Validate;
|
|
38
47
|
};
|
|
@@ -1,75 +1,88 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { type AutocompleteChangeDetails, type AutocompleteChangeReason } from '@elementor/ui';
|
|
2
|
+
|
|
3
|
+
import { removeInternalKeys } from './autocomplete-option-internal-properties';
|
|
4
|
+
import { type InternalOption, type OnSelect, type Option } from './types';
|
|
3
5
|
|
|
4
6
|
export function useAutocompleteChange< TOption extends Option >( params: {
|
|
5
7
|
options: InternalOption< TOption >[];
|
|
6
|
-
onSelect?:
|
|
7
|
-
createOption: ( value: string ) => Promise< unknown
|
|
8
|
+
onSelect?: OnSelect< TOption >;
|
|
9
|
+
createOption: ( ( value: string ) => Promise< unknown > ) | null;
|
|
8
10
|
setInputValue: ( value: string ) => void;
|
|
9
11
|
closeDropdown: () => void;
|
|
10
12
|
} ) {
|
|
11
13
|
const { options, onSelect, createOption, setInputValue, closeDropdown } = params;
|
|
12
14
|
|
|
15
|
+
if ( ! onSelect && ! createOption ) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
const handleChange = async (
|
|
14
20
|
_: React.SyntheticEvent,
|
|
15
21
|
selectedOrInputValue: Array< InternalOption< TOption > | string >,
|
|
16
|
-
reason:
|
|
22
|
+
reason: AutocompleteChangeReason,
|
|
23
|
+
details?: AutocompleteChangeDetails< InternalOption< TOption > | string >
|
|
17
24
|
) => {
|
|
18
|
-
|
|
25
|
+
const changedOption = details?.option;
|
|
26
|
+
if ( ! changedOption || ( typeof changedOption === 'object' && changedOption.fixed ) ) {
|
|
27
|
+
// If `changedOption` is nullish it means no option was selected, created or removed.
|
|
28
|
+
// The reason is either "blur" which we don't support (can't be "clear" since we disabled it).
|
|
29
|
+
// If the option is fixed, it can't be selected, created or removed.
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
19
33
|
const selectedOptions = selectedOrInputValue.filter( ( option ) => typeof option !== 'string' );
|
|
20
34
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
return acc;
|
|
28
|
-
}, null );
|
|
35
|
+
switch ( reason ) {
|
|
36
|
+
case 'removeOption':
|
|
37
|
+
const removedOption = changedOption as InternalOption< TOption >;
|
|
38
|
+
updateSelectedOptions( selectedOptions, 'removeOption', removedOption );
|
|
39
|
+
break;
|
|
29
40
|
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
// User clicked an option. It's either an existing option, or "Create <new option>".
|
|
42
|
+
case 'selectOption': {
|
|
43
|
+
const selectedOption = changedOption as InternalOption< TOption >;
|
|
32
44
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
45
|
+
if ( selectedOption._action === 'create' ) {
|
|
46
|
+
const newOption = selectedOption.value as string;
|
|
47
|
+
return createOption?.( newOption );
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
updateSelectedOptions( selectedOptions, 'selectOption', selectedOption );
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// User pressed "Enter" after typing input. The input is either matching existing option or a new option to create.
|
|
55
|
+
case 'createOption': {
|
|
56
|
+
const inputValue = changedOption as string;
|
|
40
57
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
58
|
+
const matchingOption = options.find(
|
|
59
|
+
( option ) => option.label.toLocaleLowerCase() === inputValue.toLocaleLowerCase()
|
|
60
|
+
);
|
|
61
|
+
if ( matchingOption ) {
|
|
62
|
+
selectedOptions.push( matchingOption );
|
|
63
|
+
updateSelectedOptions( selectedOptions, 'selectOption', matchingOption );
|
|
64
|
+
} else {
|
|
65
|
+
return createOption?.( inputValue );
|
|
66
|
+
}
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
44
69
|
}
|
|
45
70
|
|
|
46
|
-
updateSelectedOptions( selectedOptions );
|
|
47
71
|
setInputValue( '' );
|
|
48
72
|
closeDropdown();
|
|
49
73
|
};
|
|
50
74
|
|
|
51
75
|
return handleChange;
|
|
52
76
|
|
|
53
|
-
function
|
|
54
|
-
reason: string,
|
|
77
|
+
function updateSelectedOptions(
|
|
55
78
|
selectedOptions: InternalOption< TOption >[],
|
|
56
|
-
|
|
57
|
-
|
|
79
|
+
reason: AutocompleteChangeReason,
|
|
80
|
+
changedOption: InternalOption< TOption >
|
|
58
81
|
) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const createOptionWasDisplayed = ! inputValueMatchesExistingOption;
|
|
65
|
-
|
|
66
|
-
return createOptionWasClicked || ( enterWasPressed && createOptionWasDisplayed );
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function updateSelectedOptions( selectedOptions: InternalOption< TOption >[] ) {
|
|
70
|
-
const fixedOptions = options.filter( ( option ) => !! option.fixed );
|
|
71
|
-
const updatedOptions = [ ...fixedOptions, ...selectedOptions.filter( ( option ) => ! option.fixed ) ];
|
|
72
|
-
|
|
73
|
-
onSelect?.( removeOptionsInternalKeys( updatedOptions ) );
|
|
82
|
+
onSelect?.(
|
|
83
|
+
selectedOptions.map( ( option ) => removeInternalKeys( option ) ),
|
|
84
|
+
reason,
|
|
85
|
+
removeInternalKeys( changedOption )
|
|
86
|
+
);
|
|
74
87
|
}
|
|
75
88
|
}
|
|
@@ -13,11 +13,11 @@ export function useCreateOption( params: {
|
|
|
13
13
|
|
|
14
14
|
const [ loading, setLoading ] = useState( false );
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
16
|
+
if ( ! onCreate ) {
|
|
17
|
+
return { createOption: null, loading: false };
|
|
18
|
+
}
|
|
20
19
|
|
|
20
|
+
const createOption = async ( value: string ) => {
|
|
21
21
|
setLoading( true );
|
|
22
22
|
|
|
23
23
|
if ( validate ) {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { createContext, useContext } from 'react';
|
|
3
|
+
|
|
4
|
+
type CssClassContextType = {
|
|
5
|
+
id: string | null;
|
|
6
|
+
provider: string | null;
|
|
7
|
+
label: string;
|
|
8
|
+
isActive: boolean;
|
|
9
|
+
onClickActive: ( id: string | null ) => void;
|
|
10
|
+
handleRename: () => void;
|
|
11
|
+
setError?: ( error: string | null ) => void;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const CssClassContext = createContext< CssClassContextType | null >( null );
|
|
15
|
+
|
|
16
|
+
export const useCssClass = () => {
|
|
17
|
+
const context = useContext( CssClassContext );
|
|
18
|
+
if ( ! context ) {
|
|
19
|
+
throw new Error( 'useCssClass must be used within a CssClassProvider' );
|
|
20
|
+
}
|
|
21
|
+
return context;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type CssClassProviderProps = CssClassContextType & {
|
|
25
|
+
children: React.ReactNode;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function CssClassProvider( { children, ...contextValue }: CssClassProviderProps ) {
|
|
29
|
+
return <CssClassContext.Provider value={ contextValue }>{ children }</CssClassContext.Provider>;
|
|
30
|
+
}
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import { __ } from '@wordpress/i18n';
|
|
18
18
|
|
|
19
19
|
import { useStyle } from '../../contexts/style-context';
|
|
20
|
+
import { CssClassProvider } from './css-class-context';
|
|
20
21
|
import { CssClassMenu } from './css-class-menu';
|
|
21
22
|
|
|
22
23
|
type CssClassItemProps = {
|
|
@@ -35,18 +36,10 @@ type CssClassItemProps = {
|
|
|
35
36
|
|
|
36
37
|
const CHIP_SIZE = 'tiny';
|
|
37
38
|
|
|
38
|
-
export function CssClassItem( {
|
|
39
|
-
|
|
40
|
-
provider,
|
|
41
|
-
|
|
42
|
-
isActive,
|
|
43
|
-
color: colorProp,
|
|
44
|
-
icon,
|
|
45
|
-
chipProps,
|
|
46
|
-
onClickActive,
|
|
47
|
-
renameLabel,
|
|
48
|
-
setError,
|
|
49
|
-
}: CssClassItemProps ) {
|
|
39
|
+
export function CssClassItem( props: CssClassItemProps ) {
|
|
40
|
+
const { chipProps, icon, color: colorProp, ...classProps } = props;
|
|
41
|
+
const { id, provider, label, isActive, onClickActive, renameLabel, setError } = classProps;
|
|
42
|
+
|
|
50
43
|
const { meta, setMetaState } = useStyle();
|
|
51
44
|
const popupState = usePopupState( { variant: 'popover' } );
|
|
52
45
|
const [ chipRef, setChipRef ] = useState< HTMLElement | null >( null );
|
|
@@ -145,13 +138,9 @@ export function CssClassItem( {
|
|
|
145
138
|
/>
|
|
146
139
|
) }
|
|
147
140
|
</UnstableChipGroup>
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
provider={ provider }
|
|
152
|
-
handleRename={ openEditMode }
|
|
153
|
-
anchorEl={ chipRef }
|
|
154
|
-
/>
|
|
141
|
+
<CssClassProvider { ...classProps } handleRename={ openEditMode }>
|
|
142
|
+
<CssClassMenu popupState={ popupState } anchorEl={ chipRef } />
|
|
143
|
+
</CssClassProvider>
|
|
155
144
|
</>
|
|
156
145
|
);
|
|
157
146
|
}
|
|
@@ -1,29 +1,39 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { type StyleDefinitionState } from '@elementor/editor-styles';
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
isElementsStylesProvider,
|
|
5
|
+
stylesRepository,
|
|
6
|
+
useUserStylesCapability,
|
|
7
|
+
} from '@elementor/editor-styles-repository';
|
|
8
|
+
import { MenuItemInfotip, MenuListItem } from '@elementor/editor-ui';
|
|
5
9
|
import { bindMenu, Divider, Menu, MenuSubheader, type PopupState, Stack } from '@elementor/ui';
|
|
6
10
|
import { __ } from '@wordpress/i18n';
|
|
7
11
|
|
|
8
12
|
import { useStyle } from '../../contexts/style-context';
|
|
9
|
-
import { useUnapplyClass } from '../../hooks/use-unapply-class';
|
|
10
13
|
import { type StyleDefinitionStateWithNormal } from '../../styles-inheritance/types';
|
|
11
|
-
import { StyleIndicator
|
|
14
|
+
import { StyleIndicator } from '../style-indicator';
|
|
15
|
+
import { useCssClass } from './css-class-context';
|
|
16
|
+
import { useUnapplyClass } from './use-apply-and-unapply-class';
|
|
12
17
|
|
|
13
|
-
|
|
18
|
+
type State = {
|
|
19
|
+
key: StyleDefinitionStateWithNormal;
|
|
20
|
+
value: StyleDefinitionState | null;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const STATES: State[] = [
|
|
24
|
+
{ key: 'normal', value: null },
|
|
25
|
+
{ key: 'hover', value: 'hover' },
|
|
26
|
+
{ key: 'focus', value: 'focus' },
|
|
27
|
+
{ key: 'active', value: 'active' },
|
|
28
|
+
];
|
|
14
29
|
|
|
15
30
|
type CssClassMenuProps = {
|
|
16
|
-
styleId: string | null;
|
|
17
|
-
provider: string | null;
|
|
18
31
|
popupState: PopupState;
|
|
19
|
-
handleRename: () => void;
|
|
20
32
|
anchorEl: HTMLElement | null;
|
|
21
33
|
};
|
|
22
34
|
|
|
23
|
-
export function CssClassMenu( {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
const indicatorVariant = ! provider || isElementsStylesProvider( provider ) ? 'local' : 'global';
|
|
35
|
+
export function CssClassMenu( { popupState, anchorEl }: CssClassMenuProps ) {
|
|
36
|
+
const { provider } = useCssClass();
|
|
27
37
|
|
|
28
38
|
const handleKeyDown = ( e: React.KeyboardEvent< HTMLElement > ) => {
|
|
29
39
|
e.stopPropagation();
|
|
@@ -47,35 +57,18 @@ export function CssClassMenu( { styleId, provider, popupState, handleRename, anc
|
|
|
47
57
|
disableAutoFocusItem
|
|
48
58
|
>
|
|
49
59
|
{ /* It has to be an array since MUI menu doesn't accept a Fragment as a child, and wrapping the items with an HTML element disrupts keyboard navigation */ }
|
|
50
|
-
{ getMenuItemsByProvider( { provider,
|
|
60
|
+
{ getMenuItemsByProvider( { provider, closeMenu: popupState.close } ) }
|
|
51
61
|
<MenuSubheader sx={ { typography: 'caption', color: 'text.secondary', pb: 0.5, pt: 1 } }>
|
|
52
62
|
{ __( 'States', 'elementor' ) }
|
|
53
63
|
</MenuSubheader>
|
|
54
|
-
<StateMenuItem
|
|
55
|
-
key="normal"
|
|
56
|
-
state={ null }
|
|
57
|
-
styleId={ styleId }
|
|
58
|
-
closeMenu={ popupState.close }
|
|
59
|
-
isStyled={ styledStates.normal }
|
|
60
|
-
indicatorVariant={ indicatorVariant }
|
|
61
|
-
/>
|
|
62
64
|
{ STATES.map( ( state ) => {
|
|
63
|
-
return
|
|
64
|
-
<StateMenuItem
|
|
65
|
-
key={ state }
|
|
66
|
-
state={ state }
|
|
67
|
-
styleId={ styleId }
|
|
68
|
-
closeMenu={ popupState.close }
|
|
69
|
-
isStyled={ styledStates[ state ] }
|
|
70
|
-
indicatorVariant={ indicatorVariant }
|
|
71
|
-
/>
|
|
72
|
-
);
|
|
65
|
+
return <StateMenuItem key={ state.key } state={ state.value } closeMenu={ popupState.close } />;
|
|
73
66
|
} ) }
|
|
74
67
|
</Menu>
|
|
75
68
|
);
|
|
76
69
|
}
|
|
77
70
|
|
|
78
|
-
function
|
|
71
|
+
function useModifiedStates( styleId: string | null ): Partial< Record< StyleDefinitionStateWithNormal, true > > {
|
|
79
72
|
const { meta } = useStyle();
|
|
80
73
|
|
|
81
74
|
const styleDef = stylesRepository.all().find( ( style ) => style.id === styleId );
|
|
@@ -87,18 +80,8 @@ function useStyledStates( styleId: string | null ): Partial< Record< StyleDefini
|
|
|
87
80
|
);
|
|
88
81
|
}
|
|
89
82
|
|
|
90
|
-
function getMenuItemsByProvider( {
|
|
91
|
-
provider
|
|
92
|
-
styleId,
|
|
93
|
-
handleRename,
|
|
94
|
-
closeMenu,
|
|
95
|
-
}: {
|
|
96
|
-
provider: string | null;
|
|
97
|
-
styleId: string | null;
|
|
98
|
-
handleRename: () => void;
|
|
99
|
-
closeMenu: () => void;
|
|
100
|
-
} ) {
|
|
101
|
-
if ( ! styleId || ! provider ) {
|
|
83
|
+
function getMenuItemsByProvider( { provider, closeMenu }: { provider: string | null; closeMenu: () => void } ) {
|
|
84
|
+
if ( ! provider ) {
|
|
102
85
|
return [];
|
|
103
86
|
}
|
|
104
87
|
|
|
@@ -108,8 +91,8 @@ function getMenuItemsByProvider( {
|
|
|
108
91
|
const [ canUpdate, canDelete ] = [ providerActions?.update, providerActions?.delete ];
|
|
109
92
|
|
|
110
93
|
const actions = [
|
|
111
|
-
canUpdate && <RenameClassMenuItem key="rename-class"
|
|
112
|
-
canDelete && <UnapplyClassMenuItem key="unapply-class"
|
|
94
|
+
canUpdate && <RenameClassMenuItem key="rename-class" closeMenu={ closeMenu } />,
|
|
95
|
+
canDelete && <UnapplyClassMenuItem key="unapply-class" closeMenu={ closeMenu } />,
|
|
113
96
|
].filter( Boolean );
|
|
114
97
|
|
|
115
98
|
if ( actions.length ) {
|
|
@@ -129,23 +112,23 @@ function getMenuItemsByProvider( {
|
|
|
129
112
|
|
|
130
113
|
type StateMenuItemProps = {
|
|
131
114
|
state: StyleDefinitionState;
|
|
132
|
-
styleId: string | null;
|
|
133
115
|
closeMenu: () => void;
|
|
134
|
-
isStyled?: boolean;
|
|
135
|
-
indicatorVariant: StyleIndicatorVariant;
|
|
136
116
|
};
|
|
137
117
|
|
|
138
|
-
function StateMenuItem( {
|
|
139
|
-
|
|
140
|
-
styleId,
|
|
141
|
-
closeMenu,
|
|
142
|
-
isStyled = false,
|
|
143
|
-
indicatorVariant,
|
|
144
|
-
...props
|
|
145
|
-
}: StateMenuItemProps ) {
|
|
118
|
+
function StateMenuItem( { state, closeMenu, ...props }: StateMenuItemProps ) {
|
|
119
|
+
const { id: styleId, provider } = useCssClass();
|
|
146
120
|
const { id: activeId, setId: setActiveId, setMetaState: setActiveMetaState, meta } = useStyle();
|
|
147
121
|
const { state: activeState } = meta;
|
|
122
|
+
const { userCan } = useUserStylesCapability();
|
|
123
|
+
|
|
124
|
+
const modifiedStates = useModifiedStates( styleId );
|
|
148
125
|
|
|
126
|
+
const isUpdateAllowed = userCan( provider ?? '' ).updateProps;
|
|
127
|
+
|
|
128
|
+
const indicatorVariant = ! provider || isElementsStylesProvider( provider ) ? 'local' : 'global';
|
|
129
|
+
|
|
130
|
+
const isStyled = modifiedStates[ state ?? 'normal' ] ?? false;
|
|
131
|
+
const disabled = isUpdateAllowed ? false : ! isStyled;
|
|
149
132
|
const isActive = styleId === activeId;
|
|
150
133
|
const isSelected = state === activeState && isActive;
|
|
151
134
|
|
|
@@ -153,6 +136,7 @@ function StateMenuItem( {
|
|
|
153
136
|
<MenuListItem
|
|
154
137
|
{ ...props }
|
|
155
138
|
selected={ isSelected }
|
|
139
|
+
disabled={ disabled }
|
|
156
140
|
sx={ { textTransform: 'capitalize' } }
|
|
157
141
|
onClick={ () => {
|
|
158
142
|
if ( ! isActive ) {
|
|
@@ -164,49 +148,65 @@ function StateMenuItem( {
|
|
|
164
148
|
closeMenu();
|
|
165
149
|
} }
|
|
166
150
|
>
|
|
167
|
-
<
|
|
168
|
-
{
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
{
|
|
172
|
-
|
|
151
|
+
<MenuItemInfotip
|
|
152
|
+
showInfoTip={ disabled }
|
|
153
|
+
content={ __( 'With your role as an editor, you can only use existing states.', 'elementor' ) }
|
|
154
|
+
>
|
|
155
|
+
<Stack gap={ 0.75 } direction="row" alignItems="center">
|
|
156
|
+
{ isStyled && (
|
|
157
|
+
<StyleIndicator aria-label={ __( 'Has style', 'elementor' ) } variant={ indicatorVariant } />
|
|
158
|
+
) }
|
|
159
|
+
{ state ?? 'normal' }
|
|
160
|
+
</Stack>
|
|
161
|
+
</MenuItemInfotip>
|
|
173
162
|
</MenuListItem>
|
|
174
163
|
);
|
|
175
164
|
}
|
|
176
165
|
|
|
177
|
-
function UnapplyClassMenuItem( {
|
|
178
|
-
const
|
|
166
|
+
function UnapplyClassMenuItem( { closeMenu, ...props }: { closeMenu: () => void } ) {
|
|
167
|
+
const { id: classId, label: classLabel } = useCssClass();
|
|
168
|
+
const unapplyClass = useUnapplyClass();
|
|
179
169
|
|
|
180
|
-
return (
|
|
170
|
+
return classId ? (
|
|
181
171
|
<MenuListItem
|
|
182
172
|
{ ...props }
|
|
183
173
|
onClick={ () => {
|
|
184
|
-
unapplyClass();
|
|
174
|
+
unapplyClass( { classId, classLabel } );
|
|
185
175
|
closeMenu();
|
|
186
176
|
} }
|
|
187
177
|
>
|
|
188
178
|
{ __( 'Remove', 'elementor' ) }
|
|
189
179
|
</MenuListItem>
|
|
190
|
-
);
|
|
180
|
+
) : null;
|
|
191
181
|
}
|
|
192
182
|
|
|
193
|
-
function RenameClassMenuItem( {
|
|
194
|
-
handleRename,
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
183
|
+
function RenameClassMenuItem( { closeMenu }: { closeMenu: () => void } ) {
|
|
184
|
+
const { handleRename, provider } = useCssClass();
|
|
185
|
+
const { userCan } = useUserStylesCapability();
|
|
186
|
+
|
|
187
|
+
if ( ! provider ) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const isAllowed = userCan( provider ).update;
|
|
192
|
+
|
|
201
193
|
return (
|
|
202
194
|
<MenuListItem
|
|
203
|
-
{
|
|
195
|
+
disabled={ ! isAllowed }
|
|
204
196
|
onClick={ () => {
|
|
205
197
|
closeMenu();
|
|
206
198
|
handleRename();
|
|
207
199
|
} }
|
|
208
200
|
>
|
|
209
|
-
|
|
201
|
+
<MenuItemInfotip
|
|
202
|
+
showInfoTip={ ! isAllowed }
|
|
203
|
+
content={ __(
|
|
204
|
+
'With your role as an editor, you can use existing classes but can’t modify them.',
|
|
205
|
+
'elementor'
|
|
206
|
+
) }
|
|
207
|
+
>
|
|
208
|
+
{ __( 'Rename', 'elementor' ) }
|
|
209
|
+
</MenuItemInfotip>
|
|
210
210
|
</MenuListItem>
|
|
211
211
|
);
|
|
212
212
|
}
|