@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elementor/editor-editing-panel",
3
- "version": "1.7.0",
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.2",
43
- "@elementor/editor-controls": "0.5.0",
44
- "@elementor/editor-elements": "0.4.0",
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.6.0",
47
- "@elementor/editor-panels": "0.10.2",
48
- "@elementor/editor-responsive": "0.12.4",
49
- "@elementor/editor-styles": "0.5.0",
50
- "@elementor/editor-styles-repository": "0.3.3",
51
- "@elementor/editor-v1-adapters": "0.8.5",
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
- <CssClassItem
96
+ <EditableFieldProvider
90
97
  key={ chipProps.key }
91
- label={ value.label }
92
- id={ value.value }
93
- isActive={ isActive }
94
- isGlobal={ value.color === 'global' }
95
- color={ isActive && value.color ? value.color : 'default' }
96
- chipProps={ chipProps }
97
- onClickActive={ () => handleActivate( value ) }
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
- function CssClassItem( { id, label, isActive, isGlobal, color, chipProps, onClickActive }: CssClassItemProps ) {
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
- key={ chipProps.key }
130
- size="tiny"
131
- label={ <ConditionalTooltipWrapper maxWidth="10ch" title={ 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
- <Chip
138
- key={ `${ chipProps.key }-menu` }
139
- size="tiny"
140
- label={
141
- <Stack direction="row" gap={ 0.5 } alignItems="center">
142
- { isActive && meta.state && <Typography variant="inherit">{ meta.state }</Typography> }
143
- <DotsVerticalIcon fontSize="inherit" />
144
- </Stack>
145
- }
146
- variant="filled"
147
- color={ color }
148
- { ...bindTrigger( popupState ) }
149
- aria-label={ __( 'Open CSS Class Menu', 'elementor' ) }
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
- <Panel>
31
- <PanelHeader>
32
- <PanelHeaderTitle>{ panelTitle }</PanelHeaderTitle>
33
- </PanelHeader>
34
- <PanelBody>
35
- <ControlActionsProvider items={ menuItems }>
36
- <ControlReplacementProvider { ...controlReplacement }>
37
- <ElementProvider element={ element } elementType={ elementType }>
38
- <EditingPanelTabs />
39
- </ElementProvider>
40
- </ControlReplacementProvider>
41
- </ControlActionsProvider>
42
- </PanelBody>
43
- </Panel>
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
- <SectionsList>
18
- { elementType.controls.map( ( { type, value }, index ) => {
19
- if ( type === 'control' ) {
20
- return <Control key={ value.bind } control={ value } />;
21
- }
22
-
23
- if ( type === 'section' ) {
24
- return (
25
- <Section title={ value.label } key={ type + '.' + index } defaultExpanded={ true }>
26
- { value.items?.map( ( item ) => {
27
- if ( item.type === 'control' ) {
28
- return <Control key={ item.value.bind } control={ item.value } />;
29
- }
30
-
31
- // TODO: Handle 2nd level sections
32
- return null;
33
- } ) }
34
- </Section>
35
- );
36
- }
37
-
38
- return null;
39
- } ) }
40
- </SectionsList>
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 { useStylesField } from '../../../hooks/use-styles-field';
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 initialBorderWidth = { $$type: 'size', value: { size: 1, unit: 'px' } };
11
- const initialBorderColor = { $$type: 'color', value: '#000000' };
12
- const initialBorderStyle = 'solid';
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 [ borderWidth, setBorderWidth ] = useStylesField( 'border-width' );
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
- setBorderWidth( initialBorderWidth );
21
- setBorderColor( initialBorderColor );
22
- setBorderStyle( initialBorderStyle );
20
+ setBorder( initialBorder );
23
21
  };
22
+
24
23
  const removeBorder = () => {
25
- setBorderWidth( null );
26
- setBorderColor( null );
27
- setBorderStyle( null );
24
+ setBorder( {
25
+ 'border-width': null,
26
+ 'border-color': null,
27
+ 'border-style': null,
28
+ } );
28
29
  };
29
30
 
30
- const hasBorder = Boolean( borderWidth || borderColor || borderStyle );
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
- <CssClassSelector />
45
- <Divider />
46
- <SectionsList>
47
- <Section title={ __( 'Layout', 'elementor' ) }>
48
- <LayoutSection />
49
- </Section>
50
- <Section title={ __( 'Spacing', 'elementor' ) }>
51
- <SpacingSection />
52
- </Section>
53
- <Section title={ __( 'Size', 'elementor' ) }>
54
- <SizeSection />
55
- </Section>
56
- <Section title={ __( 'Position', 'elementor' ) }>
57
- <PositionSection />
58
- </Section>
59
- <Section title={ __( 'Typography', 'elementor' ) }>
60
- <TypographySection />
61
- </Section>
62
- <Section title={ __( 'Background', 'elementor' ) }>
63
- <BackgroundSection />
64
- </Section>
65
- <Section title={ __( 'Border', 'elementor' ) }>
66
- <BorderSection />
67
- </Section>
68
- <Section title={ __( 'Effects', 'elementor' ) }>
69
- <EffectsSection />
70
- </Section>
71
- </SectionsList>
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
  );
@@ -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
- * TODO - register the relevant global classes here
33
- * change the import statement from 'css-class-menu' to -
34
- * import { registerGlobalClassMenuItem, registerStateMenuItem } from './components/css-class-menu';
35
- * and use registerGlobalClassMenuItem
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
  }