@elementor/editor-editing-panel 1.7.0 → 1.8.1
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 +46 -0
- package/dist/index.js +562 -351
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +475 -263
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -10
- package/src/components/add-or-remove-content.tsx +2 -2
- package/src/components/css-class-selector.tsx +113 -27
- package/src/components/editable-field.tsx +158 -0
- package/src/components/editing-panel.tsx +17 -14
- package/src/components/settings-tab.tsx +28 -25
- package/src/components/style-sections/border-section/border-field.tsx +15 -14
- package/src/components/style-tab.tsx +31 -28
- package/src/css-classes.ts +15 -7
- package/src/hooks/use-unapply-class.ts +25 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-editing-panel",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "Elementor Team",
|
|
6
6
|
"homepage": "https://elementor.com/",
|
|
@@ -39,18 +39,19 @@
|
|
|
39
39
|
"dev": "tsup --config=../../tsup.dev.ts"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@elementor/editor": "0.17.
|
|
43
|
-
"@elementor/editor-controls": "0.
|
|
44
|
-
"@elementor/editor-elements": "0.4.
|
|
42
|
+
"@elementor/editor": "0.17.3",
|
|
43
|
+
"@elementor/editor-controls": "0.6.1",
|
|
44
|
+
"@elementor/editor-elements": "0.4.2",
|
|
45
45
|
"@elementor/menus": "0.1.2",
|
|
46
|
-
"@elementor/editor-props": "0.
|
|
47
|
-
"@elementor/editor-panels": "0.10.
|
|
48
|
-
"@elementor/editor-responsive": "0.12.
|
|
49
|
-
"@elementor/editor-styles": "0.5.
|
|
50
|
-
"@elementor/editor-styles-repository": "0.
|
|
51
|
-
"@elementor/editor-v1-adapters": "0.
|
|
46
|
+
"@elementor/editor-props": "0.7.1",
|
|
47
|
+
"@elementor/editor-panels": "0.10.3",
|
|
48
|
+
"@elementor/editor-responsive": "0.12.5",
|
|
49
|
+
"@elementor/editor-styles": "0.5.2",
|
|
50
|
+
"@elementor/editor-styles-repository": "0.4.1",
|
|
51
|
+
"@elementor/editor-v1-adapters": "0.9.0",
|
|
52
52
|
"@elementor/icons": "^1.20.0",
|
|
53
53
|
"@elementor/schema": "0.1.2",
|
|
54
|
+
"@elementor/session": "0.1.0",
|
|
54
55
|
"@elementor/ui": "^1.22.0",
|
|
55
56
|
"@elementor/utils": "0.3.0",
|
|
56
57
|
"@wordpress/i18n": "^5.13.0"
|
|
@@ -25,11 +25,11 @@ export const AddOrRemoveContent = ( { isAdded, label, onAdd, onRemove, children
|
|
|
25
25
|
>
|
|
26
26
|
<ControlLabel>{ label }</ControlLabel>
|
|
27
27
|
{ isAdded ? (
|
|
28
|
-
<IconButton size={ SIZE } onClick={ onRemove }>
|
|
28
|
+
<IconButton size={ SIZE } onClick={ onRemove } aria-label="Remove">
|
|
29
29
|
<MinusIcon fontSize={ SIZE } />
|
|
30
30
|
</IconButton>
|
|
31
31
|
) : (
|
|
32
|
-
<IconButton size={ SIZE } onClick={ onAdd }>
|
|
32
|
+
<IconButton size={ SIZE } onClick={ onAdd } aria-label="Add">
|
|
33
33
|
<PlusIcon fontSize={ SIZE } />
|
|
34
34
|
</IconButton>
|
|
35
35
|
) }
|
|
@@ -5,6 +5,8 @@ import { classesPropTypeUtil, type ClassesPropValue } from '@elementor/editor-pr
|
|
|
5
5
|
import { type StyleDefinitionID } from '@elementor/editor-styles';
|
|
6
6
|
import {
|
|
7
7
|
ELEMENTS_STYLES_PROVIDER_KEY,
|
|
8
|
+
stylesRepository,
|
|
9
|
+
type UpdateActionPayload,
|
|
8
10
|
useAllStylesByProvider,
|
|
9
11
|
useCreateActionsByProvider,
|
|
10
12
|
} from '@elementor/editor-styles-repository';
|
|
@@ -27,6 +29,7 @@ import { useElement } from '../contexts/element-context';
|
|
|
27
29
|
import { useStyle } from '../contexts/style-context';
|
|
28
30
|
import { ConditionalTooltipWrapper } from './conditional-tooltip-wrapper';
|
|
29
31
|
import { CssClassMenu } from './css-class-menu';
|
|
32
|
+
import { EditableField, EditableFieldProvider, useEditableField } from './editable-field';
|
|
30
33
|
import { type Action, MultiCombobox, type Option } from './multi-combobox';
|
|
31
34
|
|
|
32
35
|
const ID = 'elementor-css-class-selector';
|
|
@@ -85,17 +88,33 @@ export function CssClassSelector() {
|
|
|
85
88
|
const chipProps = getTagProps( { index } );
|
|
86
89
|
const isActive = value.value === active?.value;
|
|
87
90
|
|
|
91
|
+
const renameLabel = ( newLabel: string ) => {
|
|
92
|
+
return updateClassByProvider( value.provider, { label: newLabel, id: value.value } );
|
|
93
|
+
};
|
|
94
|
+
|
|
88
95
|
return (
|
|
89
|
-
<
|
|
96
|
+
<EditableFieldProvider
|
|
90
97
|
key={ chipProps.key }
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
value={ value.label }
|
|
99
|
+
onSubmit={ renameLabel }
|
|
100
|
+
editable={ value.provider !== ELEMENTS_STYLES_PROVIDER_KEY }
|
|
101
|
+
validation={ ( newLabel ) =>
|
|
102
|
+
renameValidation(
|
|
103
|
+
newLabel,
|
|
104
|
+
options.filter( ( option ) => option.value !== value.value )
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
>
|
|
108
|
+
<CssClassItem
|
|
109
|
+
label={ value.label }
|
|
110
|
+
id={ value.value }
|
|
111
|
+
isActive={ isActive }
|
|
112
|
+
isGlobal={ value.color === 'global' }
|
|
113
|
+
color={ isActive && value.color ? value.color : 'default' }
|
|
114
|
+
chipProps={ chipProps }
|
|
115
|
+
onClickActive={ () => handleActivate( value ) }
|
|
116
|
+
/>
|
|
117
|
+
</EditableFieldProvider>
|
|
99
118
|
);
|
|
100
119
|
} )
|
|
101
120
|
}
|
|
@@ -114,46 +133,113 @@ type CssClassItemProps = {
|
|
|
114
133
|
onClickActive: ( id: string ) => void;
|
|
115
134
|
};
|
|
116
135
|
|
|
117
|
-
|
|
136
|
+
const CHIP_SIZE = 'tiny';
|
|
137
|
+
|
|
138
|
+
export function CssClassItem( {
|
|
139
|
+
id,
|
|
140
|
+
label,
|
|
141
|
+
isActive,
|
|
142
|
+
isGlobal,
|
|
143
|
+
color: colorProp,
|
|
144
|
+
chipProps,
|
|
145
|
+
onClickActive,
|
|
146
|
+
}: CssClassItemProps ) {
|
|
118
147
|
const { meta } = useStyle();
|
|
119
148
|
// TODO - resolve the useId issue with invalid characters upon CSS selectors (EDS-1089)
|
|
120
149
|
const popupId = useId().replace( /:/g, '_' );
|
|
121
150
|
const popupState = usePopupState( { variant: 'popover', popupId } );
|
|
122
151
|
const chipRef = useRef< Element >( null );
|
|
123
152
|
const { onDelete, ...chipGroupProps } = chipProps;
|
|
153
|
+
const { isEditing, openEditMode, error, submitting } = useEditableField();
|
|
154
|
+
|
|
155
|
+
const color = error ? 'error' : colorProp;
|
|
124
156
|
|
|
125
157
|
return (
|
|
126
158
|
<CssClassItemProvider styleId={ id } isActive={ isActive } isGlobal={ isGlobal }>
|
|
127
159
|
<UnstableChipGroup ref={ chipRef } { ...chipGroupProps } aria-label={ `Edit ${ label }` } role="group">
|
|
128
160
|
<Chip
|
|
129
|
-
|
|
130
|
-
size=
|
|
131
|
-
label={
|
|
161
|
+
disabled={ submitting }
|
|
162
|
+
size={ CHIP_SIZE }
|
|
163
|
+
label={
|
|
164
|
+
<EditableField
|
|
165
|
+
onDoubleClick={ () => {
|
|
166
|
+
if ( ! isActive ) {
|
|
167
|
+
openEditMode();
|
|
168
|
+
}
|
|
169
|
+
} }
|
|
170
|
+
onClick={ () => {
|
|
171
|
+
if ( isActive ) {
|
|
172
|
+
openEditMode();
|
|
173
|
+
}
|
|
174
|
+
} }
|
|
175
|
+
>
|
|
176
|
+
<ConditionalTooltipWrapper maxWidth="10ch" title={ label } />
|
|
177
|
+
</EditableField>
|
|
178
|
+
}
|
|
132
179
|
variant={ isActive && ! meta.state ? 'filled' : 'standard' }
|
|
133
180
|
color={ color }
|
|
134
181
|
onClick={ () => onClickActive( id ) }
|
|
135
182
|
aria-pressed={ isActive }
|
|
183
|
+
sx={ {
|
|
184
|
+
'&.Mui-focusVisible': {
|
|
185
|
+
boxShadow: 'none !important',
|
|
186
|
+
},
|
|
187
|
+
} }
|
|
136
188
|
/>
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
189
|
+
{ ! isEditing && (
|
|
190
|
+
<Chip
|
|
191
|
+
disabled={ submitting }
|
|
192
|
+
size={ CHIP_SIZE }
|
|
193
|
+
label={
|
|
194
|
+
<Stack direction="row" gap={ 0.5 } alignItems="center">
|
|
195
|
+
{ isActive && meta.state && <Typography variant="inherit">{ meta.state }</Typography> }
|
|
196
|
+
<DotsVerticalIcon fontSize="inherit" />
|
|
197
|
+
</Stack>
|
|
198
|
+
}
|
|
199
|
+
variant="filled"
|
|
200
|
+
color={ color }
|
|
201
|
+
{ ...bindTrigger( popupState ) }
|
|
202
|
+
aria-label={ __( 'Open CSS Class Menu', 'elementor' ) }
|
|
203
|
+
/>
|
|
204
|
+
) }
|
|
151
205
|
</UnstableChipGroup>
|
|
152
206
|
<CssClassMenu popupState={ popupState } containerRef={ chipRef } />
|
|
153
207
|
</CssClassItemProvider>
|
|
154
208
|
);
|
|
155
209
|
}
|
|
156
210
|
|
|
211
|
+
const updateClassByProvider = ( provider: string, data: UpdateActionPayload ) => {
|
|
212
|
+
const providerInstance = stylesRepository.getProviderByKey( provider );
|
|
213
|
+
|
|
214
|
+
if ( ! providerInstance ) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return providerInstance.actions.update?.( data );
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const VALID_SELECTOR_REGEX = /^[a-zA-Z0-9_-]+$/;
|
|
222
|
+
|
|
223
|
+
const renameValidation = ( newLabel: string, options: Option[] ) => {
|
|
224
|
+
if ( isNameExist( newLabel, options ) ) {
|
|
225
|
+
return __( 'Existing name', 'elementor' );
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if ( isCharactersNotSupported( newLabel ) ) {
|
|
229
|
+
return __( 'Format is not valid', 'elementor' );
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const isNameExist = ( newLabel: string, options: Option[] ) => {
|
|
234
|
+
if ( ! options?.length ) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return options.some( ( option ) => option.label.toLowerCase() === newLabel.toLowerCase() );
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const isCharactersNotSupported = ( newLabel: string ) => ! VALID_SELECTOR_REGEX.test( newLabel );
|
|
242
|
+
|
|
157
243
|
function useOptions() {
|
|
158
244
|
const { element } = useElement();
|
|
159
245
|
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type ComponentProps, createContext, useContext, useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { Tooltip } from '@elementor/ui';
|
|
4
|
+
|
|
5
|
+
type EditableFieldContext = {
|
|
6
|
+
isEditing: boolean;
|
|
7
|
+
openEditMode: () => void;
|
|
8
|
+
closeEditMode: () => void;
|
|
9
|
+
onChange: ( event: React.ChangeEvent< HTMLInputElement > ) => void;
|
|
10
|
+
value: string;
|
|
11
|
+
error?: string | null;
|
|
12
|
+
submit: ( value: string ) => Promise< void >;
|
|
13
|
+
editable?: boolean;
|
|
14
|
+
submitting: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const Context = createContext< EditableFieldContext | null >( null );
|
|
18
|
+
|
|
19
|
+
export type EditableFieldProviderProps = React.PropsWithChildren< {
|
|
20
|
+
value: string;
|
|
21
|
+
onSubmit: ( value: string ) => unknown | Promise< unknown >;
|
|
22
|
+
validation?: ( value: string ) => string | undefined | null;
|
|
23
|
+
editable?: boolean;
|
|
24
|
+
} >;
|
|
25
|
+
|
|
26
|
+
export const EditableFieldProvider = ( {
|
|
27
|
+
children,
|
|
28
|
+
value,
|
|
29
|
+
onSubmit,
|
|
30
|
+
validation,
|
|
31
|
+
editable,
|
|
32
|
+
}: EditableFieldProviderProps ) => {
|
|
33
|
+
const [ isEditing, setIsEditing ] = useState( false );
|
|
34
|
+
const [ submitting, setSubmitting ] = useState( false );
|
|
35
|
+
const [ error, setError ] = useState< string | null | undefined >( null );
|
|
36
|
+
|
|
37
|
+
const openEditMode = () => {
|
|
38
|
+
setIsEditing( true );
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const closeEditMode = () => {
|
|
42
|
+
setError( null );
|
|
43
|
+
setIsEditing( false );
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const submit = async ( newValue: string ) => {
|
|
47
|
+
if ( ! error ) {
|
|
48
|
+
setSubmitting( true );
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
await onSubmit( newValue );
|
|
52
|
+
} finally {
|
|
53
|
+
setSubmitting( false );
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
closeEditMode();
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const onChange = ( event: React.ChangeEvent< HTMLSpanElement > ) => {
|
|
61
|
+
const { innerText: newValue } = event.target;
|
|
62
|
+
|
|
63
|
+
if ( validation ) {
|
|
64
|
+
setError( validation( newValue ) );
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<Context.Provider
|
|
70
|
+
value={ {
|
|
71
|
+
isEditing,
|
|
72
|
+
openEditMode,
|
|
73
|
+
closeEditMode,
|
|
74
|
+
onChange,
|
|
75
|
+
value,
|
|
76
|
+
error,
|
|
77
|
+
submit,
|
|
78
|
+
editable,
|
|
79
|
+
submitting,
|
|
80
|
+
} }
|
|
81
|
+
>
|
|
82
|
+
{ children }
|
|
83
|
+
</Context.Provider>
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
type EditableFieldProps = ComponentProps< 'div' >;
|
|
88
|
+
|
|
89
|
+
export const EditableField = ( { children, ...props }: EditableFieldProps ) => {
|
|
90
|
+
const ref = useRef< HTMLElement >( null );
|
|
91
|
+
const { isEditing, closeEditMode, value, onChange, error, submit, editable } = useEditableField();
|
|
92
|
+
|
|
93
|
+
useEffect( () => {
|
|
94
|
+
if ( isEditing ) {
|
|
95
|
+
ref.current?.focus();
|
|
96
|
+
selectAll();
|
|
97
|
+
}
|
|
98
|
+
}, [ isEditing ] );
|
|
99
|
+
|
|
100
|
+
const handleKeyDown = ( event: React.KeyboardEvent ) => {
|
|
101
|
+
event.stopPropagation();
|
|
102
|
+
|
|
103
|
+
if ( [ 'Escape' ].includes( event.key ) ) {
|
|
104
|
+
return closeEditMode();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if ( [ 'Enter' ].includes( event.key ) ) {
|
|
108
|
+
event.preventDefault();
|
|
109
|
+
return submit( ( event.target as HTMLElement ).innerText );
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const selectAll = () => {
|
|
114
|
+
const selection = getSelection();
|
|
115
|
+
|
|
116
|
+
if ( ! selection || ! ref.current ) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const range = document.createRange();
|
|
121
|
+
range.selectNodeContents( ref.current );
|
|
122
|
+
|
|
123
|
+
selection.removeAllRanges();
|
|
124
|
+
selection.addRange( range );
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
if ( ! editable ) {
|
|
128
|
+
return children;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<Tooltip open={ !! error } title={ error } placement="top">
|
|
133
|
+
{ /* eslint-disable-next-line jsx-a11y/no-static-element-interactions */ }
|
|
134
|
+
<div onKeyDown={ handleKeyDown } { ...props }>
|
|
135
|
+
<span
|
|
136
|
+
ref={ ref }
|
|
137
|
+
role="textbox"
|
|
138
|
+
onInput={ onChange }
|
|
139
|
+
contentEditable={ isEditing }
|
|
140
|
+
suppressContentEditableWarning
|
|
141
|
+
onBlur={ closeEditMode }
|
|
142
|
+
>
|
|
143
|
+
{ isEditing ? value : children }
|
|
144
|
+
</span>
|
|
145
|
+
</div>
|
|
146
|
+
</Tooltip>
|
|
147
|
+
);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export const useEditableField = () => {
|
|
151
|
+
const contextValue = useContext( Context );
|
|
152
|
+
|
|
153
|
+
if ( ! contextValue ) {
|
|
154
|
+
throw new Error( 'useEditableField must be used within a EditableFieldProvider' );
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return contextValue;
|
|
158
|
+
};
|
|
@@ -2,6 +2,7 @@ import * as React from 'react';
|
|
|
2
2
|
import { ControlActionsProvider, ControlReplacementProvider } from '@elementor/editor-controls';
|
|
3
3
|
import { useSelectedElement } from '@elementor/editor-elements';
|
|
4
4
|
import { Panel, PanelBody, PanelHeader, PanelHeaderTitle } from '@elementor/editor-panels';
|
|
5
|
+
import { SessionStorageProvider } from '@elementor/session';
|
|
5
6
|
import { ErrorBoundary } from '@elementor/ui';
|
|
6
7
|
import { __ } from '@wordpress/i18n';
|
|
7
8
|
|
|
@@ -27,20 +28,22 @@ export const EditingPanel = () => {
|
|
|
27
28
|
|
|
28
29
|
return (
|
|
29
30
|
<ErrorBoundary fallback={ <EditorPanelErrorFallback /> }>
|
|
30
|
-
<
|
|
31
|
-
<
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
<
|
|
36
|
-
<
|
|
37
|
-
<
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
31
|
+
<SessionStorageProvider prefix={ 'elementor' }>
|
|
32
|
+
<Panel>
|
|
33
|
+
<PanelHeader>
|
|
34
|
+
<PanelHeaderTitle>{ panelTitle }</PanelHeaderTitle>
|
|
35
|
+
</PanelHeader>
|
|
36
|
+
<PanelBody>
|
|
37
|
+
<ControlActionsProvider items={ menuItems }>
|
|
38
|
+
<ControlReplacementProvider { ...controlReplacement }>
|
|
39
|
+
<ElementProvider element={ element } elementType={ elementType }>
|
|
40
|
+
<EditingPanelTabs />
|
|
41
|
+
</ElementProvider>
|
|
42
|
+
</ControlReplacementProvider>
|
|
43
|
+
</ControlActionsProvider>
|
|
44
|
+
</PanelBody>
|
|
45
|
+
</Panel>
|
|
46
|
+
</SessionStorageProvider>
|
|
44
47
|
</ErrorBoundary>
|
|
45
48
|
);
|
|
46
49
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { ControlLabel } from '@elementor/editor-controls';
|
|
3
3
|
import { type Control } from '@elementor/editor-elements';
|
|
4
|
+
import { SessionStorageProvider } from '@elementor/session';
|
|
4
5
|
|
|
5
6
|
import { useElement } from '../contexts/element-context';
|
|
6
7
|
import { Control as BaseControl } from '../controls-registry/control';
|
|
@@ -11,33 +12,35 @@ import { Section } from './section';
|
|
|
11
12
|
import { SectionsList } from './sections-list';
|
|
12
13
|
|
|
13
14
|
export const SettingsTab = () => {
|
|
14
|
-
const { elementType } = useElement();
|
|
15
|
+
const { elementType, element } = useElement();
|
|
15
16
|
|
|
16
17
|
return (
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
{ value.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
18
|
+
<SessionStorageProvider prefix={ element.id }>
|
|
19
|
+
<SectionsList>
|
|
20
|
+
{ elementType.controls.map( ( { type, value }, index ) => {
|
|
21
|
+
if ( type === 'control' ) {
|
|
22
|
+
return <Control key={ value.bind } control={ value } />;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if ( type === 'section' ) {
|
|
26
|
+
return (
|
|
27
|
+
<Section title={ value.label } key={ type + '.' + index } defaultExpanded={ true }>
|
|
28
|
+
{ value.items?.map( ( item ) => {
|
|
29
|
+
if ( item.type === 'control' ) {
|
|
30
|
+
return <Control key={ item.value.bind } control={ item.value } />;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// TODO: Handle 2nd level sections
|
|
34
|
+
return null;
|
|
35
|
+
} ) }
|
|
36
|
+
</Section>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return null;
|
|
41
|
+
} ) }
|
|
42
|
+
</SectionsList>
|
|
43
|
+
</SessionStorageProvider>
|
|
41
44
|
);
|
|
42
45
|
};
|
|
43
46
|
|
|
@@ -1,33 +1,34 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { __ } from '@wordpress/i18n';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { useStylesFields } from '../../../hooks/use-styles-fields';
|
|
5
5
|
import { AddOrRemoveContent } from '../../add-or-remove-content';
|
|
6
6
|
import { BorderColorField } from './border-color-field';
|
|
7
7
|
import { BorderStyleField } from './border-style-field';
|
|
8
8
|
import { BorderWidthField } from './border-width-field';
|
|
9
9
|
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
const initialBorder = {
|
|
11
|
+
'border-width': { $$type: 'size', value: { size: 1, unit: 'px' } },
|
|
12
|
+
'border-color': { $$type: 'color', value: '#000000' },
|
|
13
|
+
'border-style': { $$type: 'string', value: 'solid' },
|
|
14
|
+
};
|
|
13
15
|
|
|
14
16
|
export const BorderField = () => {
|
|
15
|
-
const [
|
|
16
|
-
const [ borderColor, setBorderColor ] = useStylesField( 'border-color' );
|
|
17
|
-
const [ borderStyle, setBorderStyle ] = useStylesField( 'border-style' );
|
|
17
|
+
const [ border, setBorder ] = useStylesFields( Object.keys( initialBorder ) );
|
|
18
18
|
|
|
19
19
|
const addBorder = () => {
|
|
20
|
-
|
|
21
|
-
setBorderColor( initialBorderColor );
|
|
22
|
-
setBorderStyle( initialBorderStyle );
|
|
20
|
+
setBorder( initialBorder );
|
|
23
21
|
};
|
|
22
|
+
|
|
24
23
|
const removeBorder = () => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
setBorder( {
|
|
25
|
+
'border-width': null,
|
|
26
|
+
'border-color': null,
|
|
27
|
+
'border-style': null,
|
|
28
|
+
} );
|
|
28
29
|
};
|
|
29
30
|
|
|
30
|
-
const hasBorder =
|
|
31
|
+
const hasBorder = Object.values( border ?? {} ).some( Boolean );
|
|
31
32
|
|
|
32
33
|
return (
|
|
33
34
|
<AddOrRemoveContent
|
|
@@ -4,6 +4,7 @@ import { useElementSetting, useElementStyles } from '@elementor/editor-elements'
|
|
|
4
4
|
import { type ClassesPropValue, type PropKey } from '@elementor/editor-props';
|
|
5
5
|
import { useActiveBreakpoint } from '@elementor/editor-responsive';
|
|
6
6
|
import { type StyleDefinitionID, type StyleState } from '@elementor/editor-styles';
|
|
7
|
+
import { SessionStorageProvider } from '@elementor/session';
|
|
7
8
|
import { Divider } from '@elementor/ui';
|
|
8
9
|
import { __ } from '@wordpress/i18n';
|
|
9
10
|
|
|
@@ -41,34 +42,36 @@ export const StyleTab = () => {
|
|
|
41
42
|
} }
|
|
42
43
|
setMetaState={ setActiveStyleState }
|
|
43
44
|
>
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
<
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
<
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
45
|
+
<SessionStorageProvider prefix={ activeStyleDefId ?? '' }>
|
|
46
|
+
<CssClassSelector />
|
|
47
|
+
<Divider />
|
|
48
|
+
<SectionsList>
|
|
49
|
+
<Section title={ __( 'Layout', 'elementor' ) }>
|
|
50
|
+
<LayoutSection />
|
|
51
|
+
</Section>
|
|
52
|
+
<Section title={ __( 'Spacing', 'elementor' ) }>
|
|
53
|
+
<SpacingSection />
|
|
54
|
+
</Section>
|
|
55
|
+
<Section title={ __( 'Size', 'elementor' ) }>
|
|
56
|
+
<SizeSection />
|
|
57
|
+
</Section>
|
|
58
|
+
<Section title={ __( 'Position', 'elementor' ) }>
|
|
59
|
+
<PositionSection />
|
|
60
|
+
</Section>
|
|
61
|
+
<Section title={ __( 'Typography', 'elementor' ) }>
|
|
62
|
+
<TypographySection />
|
|
63
|
+
</Section>
|
|
64
|
+
<Section title={ __( 'Background', 'elementor' ) }>
|
|
65
|
+
<BackgroundSection />
|
|
66
|
+
</Section>
|
|
67
|
+
<Section title={ __( 'Border', 'elementor' ) }>
|
|
68
|
+
<BorderSection />
|
|
69
|
+
</Section>
|
|
70
|
+
<Section title={ __( 'Effects', 'elementor' ) }>
|
|
71
|
+
<EffectsSection />
|
|
72
|
+
</Section>
|
|
73
|
+
</SectionsList>
|
|
74
|
+
</SessionStorageProvider>
|
|
72
75
|
</StyleProvider>
|
|
73
76
|
</ClassesPropProvider>
|
|
74
77
|
);
|
package/src/css-classes.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type StyleState } from '@elementor/editor-styles';
|
|
2
2
|
|
|
3
|
-
import { registerStateMenuItem } from './components/css-class-menu';
|
|
3
|
+
import { registerGlobalClassMenuItem, registerStateMenuItem } from './components/css-class-menu';
|
|
4
|
+
import { useCssClassItem } from './contexts/css-class-item-context';
|
|
5
|
+
import { useUnapplyClass } from './hooks/use-unapply-class';
|
|
4
6
|
|
|
5
7
|
const STATES: NonNullable< StyleState >[] = [ 'hover', 'focus', 'active' ];
|
|
6
8
|
|
|
@@ -28,10 +30,16 @@ function registerStateItems() {
|
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
function registerGlobalClassItems() {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
registerGlobalClassMenuItem( {
|
|
34
|
+
id: 'unapply-class',
|
|
35
|
+
useProps: () => {
|
|
36
|
+
const { styleId: currentClass } = useCssClassItem();
|
|
37
|
+
const unapplyClass = useUnapplyClass( currentClass );
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
text: 'Remove',
|
|
41
|
+
onClick: unapplyClass,
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
} );
|
|
37
45
|
}
|