@elementor/editor-editing-panel 1.6.0 → 1.8.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.
Files changed (32) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/index.js +652 -401
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.mjs +665 -409
  5. package/dist/index.mjs.map +1 -1
  6. package/package.json +7 -6
  7. package/src/components/add-or-remove-content.tsx +2 -2
  8. package/src/components/css-class-selector.tsx +198 -51
  9. package/src/components/editable-field.tsx +158 -0
  10. package/src/components/editing-panel.tsx +17 -14
  11. package/src/components/multi-combobox.tsx +184 -0
  12. package/src/components/settings-tab.tsx +28 -25
  13. package/src/components/style-sections/border-section/border-field.tsx +14 -17
  14. package/src/components/style-sections/position-section/position-field.tsx +1 -0
  15. package/src/components/style-tab.tsx +32 -29
  16. package/src/controls-registry/create-top-level-object-type.ts +14 -0
  17. package/src/controls-registry/settings-field.tsx +12 -14
  18. package/src/controls-registry/styles-field.tsx +17 -5
  19. package/src/css-classes.ts +15 -7
  20. package/src/dynamics/components/dynamic-selection-control.tsx +1 -1
  21. package/src/dynamics/components/dynamic-selection.tsx +3 -4
  22. package/src/dynamics/dynamic-control.tsx +16 -11
  23. package/src/dynamics/hooks/use-dynamic-tag.ts +2 -3
  24. package/src/dynamics/hooks/use-prop-dynamic-action.tsx +1 -4
  25. package/src/dynamics/hooks/use-prop-dynamic-tags.ts +3 -6
  26. package/src/dynamics/utils.ts +1 -1
  27. package/src/hooks/use-styles-fields.ts +1 -0
  28. package/src/hooks/use-unapply-class.ts +25 -0
  29. package/src/components/multi-combobox/index.ts +0 -3
  30. package/src/components/multi-combobox/multi-combobox.tsx +0 -122
  31. package/src/components/multi-combobox/types.ts +0 -29
  32. package/src/components/multi-combobox/use-combobox-actions.ts +0 -62
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elementor/editor-editing-panel",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "private": false,
5
5
  "author": "Elementor Team",
6
6
  "homepage": "https://elementor.com/",
@@ -40,17 +40,18 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@elementor/editor": "0.17.2",
43
- "@elementor/editor-controls": "0.4.1",
44
- "@elementor/editor-elements": "0.3.5",
43
+ "@elementor/editor-controls": "0.6.0",
44
+ "@elementor/editor-elements": "0.4.1",
45
45
  "@elementor/menus": "0.1.2",
46
- "@elementor/editor-props": "0.5.1",
46
+ "@elementor/editor-props": "0.7.0",
47
47
  "@elementor/editor-panels": "0.10.2",
48
48
  "@elementor/editor-responsive": "0.12.4",
49
- "@elementor/editor-styles": "0.4.0",
50
- "@elementor/editor-styles-repository": "0.3.2",
49
+ "@elementor/editor-styles": "0.5.1",
50
+ "@elementor/editor-styles-repository": "0.4.0",
51
51
  "@elementor/editor-v1-adapters": "0.8.5",
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
  ) }
@@ -1,9 +1,15 @@
1
1
  import * as React from 'react';
2
2
  import { useId, useRef } from 'react';
3
- import { updateSettings, useElementSetting } from '@elementor/editor-elements';
3
+ import { getElementSetting, updateSettings, useElementSetting } from '@elementor/editor-elements';
4
4
  import { classesPropTypeUtil, type ClassesPropValue } from '@elementor/editor-props';
5
5
  import { type StyleDefinitionID } from '@elementor/editor-styles';
6
- import { ELEMENTS_STYLES_PROVIDER_KEY, useAllStylesByProvider } from '@elementor/editor-styles-repository';
6
+ import {
7
+ ELEMENTS_STYLES_PROVIDER_KEY,
8
+ stylesRepository,
9
+ type UpdateActionPayload,
10
+ useAllStylesByProvider,
11
+ useCreateActionsByProvider,
12
+ } from '@elementor/editor-styles-repository';
7
13
  import { DotsVerticalIcon } from '@elementor/icons';
8
14
  import {
9
15
  type AutocompleteRenderGetTagProps,
@@ -23,18 +29,24 @@ import { useElement } from '../contexts/element-context';
23
29
  import { useStyle } from '../contexts/style-context';
24
30
  import { ConditionalTooltipWrapper } from './conditional-tooltip-wrapper';
25
31
  import { CssClassMenu } from './css-class-menu';
26
- import { MultiCombobox, type Option } from './multi-combobox';
32
+ import { EditableField, EditableFieldProvider, useEditableField } from './editable-field';
33
+ import { type Action, MultiCombobox, type Option } from './multi-combobox';
27
34
 
28
35
  const ID = 'elementor-css-class-selector';
29
36
  const TAGS_LIMIT = 8;
30
37
 
38
+ type StyleDefOption = Option & {
39
+ color: 'primary' | 'global';
40
+ provider: string;
41
+ };
42
+
31
43
  const EMPTY_OPTION = {
32
44
  label: __( 'local', 'elementor' ),
33
45
  value: '',
34
46
  fixed: true,
35
47
  color: 'primary',
36
48
  provider: ELEMENTS_STYLES_PROVIDER_KEY,
37
- } satisfies Option;
49
+ } satisfies StyleDefOption;
38
50
 
39
51
  /**
40
52
  * Applied - Classes applied to an element.
@@ -43,10 +55,12 @@ const EMPTY_OPTION = {
43
55
 
44
56
  export function CssClassSelector() {
45
57
  const options = useOptions();
46
- const [ appliedIds, setAppliedIds ] = useAppliedClassesIds();
47
58
 
59
+ const { value: appliedIds, setValue: setAppliedIds, pushValue: pushAppliedId } = useAppliedClassesIds();
48
60
  const { id: activeId, setId: setActiveId } = useStyle();
49
61
 
62
+ const actions = useCreateActions( { pushAppliedId, setActiveId } );
63
+
50
64
  const handleApply = useHandleApply( appliedIds, setAppliedIds );
51
65
  const handleActivate = ( { value }: Option ) => setActiveId( value );
52
66
 
@@ -65,23 +79,42 @@ export function CssClassSelector() {
65
79
  selected={ applied }
66
80
  onSelect={ handleApply }
67
81
  limitTags={ TAGS_LIMIT }
68
- optionsLabel={ __( 'Global CSS Classes', 'elementor' ) }
82
+ actions={ actions }
83
+ getLimitTagsText={ ( more ) => (
84
+ <Chip size="tiny" variant="standard" label={ `+${ more }` } clickable />
85
+ ) }
69
86
  renderTags={ ( values, getTagProps ) =>
70
87
  values.map( ( value, index ) => {
71
88
  const chipProps = getTagProps( { index } );
72
89
  const isActive = value.value === active?.value;
73
90
 
91
+ const renameLabel = ( newLabel: string ) => {
92
+ return updateClassByProvider( value.provider, { label: newLabel, id: value.value } );
93
+ };
94
+
74
95
  return (
75
- <CssClassItem
96
+ <EditableFieldProvider
76
97
  key={ chipProps.key }
77
- label={ value.label }
78
- id={ value.value }
79
- isActive={ isActive }
80
- isGlobal={ value.color === 'global' }
81
- color={ isActive && value.color ? value.color : 'default' }
82
- chipProps={ chipProps }
83
- onClickActive={ () => handleActivate( value ) }
84
- />
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>
85
118
  );
86
119
  } )
87
120
  }
@@ -100,70 +133,174 @@ type CssClassItemProps = {
100
133
  onClickActive: ( id: string ) => void;
101
134
  };
102
135
 
103
- 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 ) {
104
147
  const { meta } = useStyle();
105
148
  // TODO - resolve the useId issue with invalid characters upon CSS selectors (EDS-1089)
106
149
  const popupId = useId().replace( /:/g, '_' );
107
150
  const popupState = usePopupState( { variant: 'popover', popupId } );
108
151
  const chipRef = useRef< Element >( null );
109
152
  const { onDelete, ...chipGroupProps } = chipProps;
153
+ const { isEditing, openEditMode, error, submitting } = useEditableField();
154
+
155
+ const color = error ? 'error' : colorProp;
110
156
 
111
157
  return (
112
158
  <CssClassItemProvider styleId={ id } isActive={ isActive } isGlobal={ isGlobal }>
113
159
  <UnstableChipGroup ref={ chipRef } { ...chipGroupProps } aria-label={ `Edit ${ label }` } role="group">
114
160
  <Chip
115
- key={ chipProps.key }
116
- size="small"
117
- 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
+ }
118
179
  variant={ isActive && ! meta.state ? 'filled' : 'standard' }
119
180
  color={ color }
120
181
  onClick={ () => onClickActive( id ) }
121
182
  aria-pressed={ isActive }
183
+ sx={ {
184
+ '&.Mui-focusVisible': {
185
+ boxShadow: 'none !important',
186
+ },
187
+ } }
122
188
  />
123
- <Chip
124
- key={ `${ chipProps.key }-menu` }
125
- size="small"
126
- label={
127
- <Stack direction="row" gap={ 0.5 } alignItems="center">
128
- { isActive && meta.state && <Typography variant="inherit">{ meta.state }</Typography> }
129
- <DotsVerticalIcon fontSize="inherit" />
130
- </Stack>
131
- }
132
- variant="filled"
133
- color={ color }
134
- { ...bindTrigger( popupState ) }
135
- aria-label={ __( 'Open CSS Class Menu', 'elementor' ) }
136
- />
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
+ ) }
137
205
  </UnstableChipGroup>
138
206
  <CssClassMenu popupState={ popupState } containerRef={ chipRef } />
139
207
  </CssClassItemProvider>
140
208
  );
141
209
  }
142
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
+
143
243
  function useOptions() {
144
244
  const { element } = useElement();
145
245
 
146
- return useAllStylesByProvider( { elementId: element.id } ).flatMap< Option >( ( [ providerKey, styleDefs ] ) => {
147
- const isElements = providerKey === ELEMENTS_STYLES_PROVIDER_KEY;
148
-
149
- // Add empty local option for elements, as fallback.
150
- if ( isElements && styleDefs.length === 0 ) {
151
- return [ EMPTY_OPTION ];
246
+ return useAllStylesByProvider( { elementId: element.id } ).flatMap< StyleDefOption >(
247
+ ( [ provider, styleDefs ] ) => {
248
+ const isElements = provider.key === ELEMENTS_STYLES_PROVIDER_KEY;
249
+
250
+ // Add empty local option for elements, as fallback.
251
+ if ( isElements && styleDefs.length === 0 ) {
252
+ return [ EMPTY_OPTION ];
253
+ }
254
+
255
+ return styleDefs.map( ( styleDef ) => {
256
+ return {
257
+ label: styleDef.label,
258
+ value: styleDef.id,
259
+ fixed: isElements,
260
+ color: isElements ? 'primary' : 'global',
261
+ provider: provider.key,
262
+ group: provider.labels?.plural,
263
+ };
264
+ } );
152
265
  }
266
+ );
267
+ }
153
268
 
154
- return styleDefs.map( ( styleDef ) => {
155
- return {
156
- label: styleDef.label,
157
- value: styleDef.id,
158
- fixed: isElements,
159
- color: isElements ? 'primary' : 'global',
160
- provider: providerKey,
161
- };
162
- } );
269
+ function useCreateActions( {
270
+ pushAppliedId,
271
+ setActiveId,
272
+ }: {
273
+ pushAppliedId: ( id: StyleDefinitionID ) => void;
274
+ setActiveId: ( id: StyleDefinitionID ) => void;
275
+ } ) {
276
+ return useCreateActionsByProvider().map( ( [ provider, create ] ): Action< StyleDefOption > => {
277
+ return {
278
+ // translators: %s is the label of the new class.
279
+ label: ( value ) => __( 'Create new "%s"', 'elementor' ).replace( '%s', value ),
280
+ apply: async ( value ) => {
281
+ const created = await create( { label: value } );
282
+
283
+ if ( ! created ) {
284
+ return;
285
+ }
286
+
287
+ pushAppliedId( created.id );
288
+ setActiveId( created.id );
289
+ },
290
+ condition: ( options, inputValue ) => {
291
+ const isUniqueLabel = ! options.some(
292
+ ( option ) => option.label.toLowerCase() === inputValue.toLowerCase()
293
+ );
294
+
295
+ return !! inputValue && isUniqueLabel;
296
+ },
297
+ // translators: %s is the singular label of css class provider (e.g "Global CSS Class").
298
+ group: __( 'Create New %s', 'elementor' ).replace( '%s', provider.labels?.singular ?? '' ),
299
+ };
163
300
  } );
164
301
  }
165
302
 
166
- function useAppliedOptions( options: Option[], appliedIds: StyleDefinitionID[] ) {
303
+ function useAppliedOptions( options: StyleDefOption[], appliedIds: StyleDefinitionID[] ) {
167
304
  const applied = options.filter( ( option ) => appliedIds.includes( option.value ) );
168
305
 
169
306
  const hasElementsProviderStyleApplied = applied.some(
@@ -192,13 +329,23 @@ function useAppliedClassesIds() {
192
329
  } );
193
330
  };
194
331
 
195
- return [ value, setValue ] as const;
332
+ const pushValue = ( id: StyleDefinitionID ) => {
333
+ const ids = getElementSetting< ClassesPropValue >( element.id, currentClassesProp )?.value || [];
334
+
335
+ setValue( [ ...ids, id ] );
336
+ };
337
+
338
+ return {
339
+ value,
340
+ setValue,
341
+ pushValue,
342
+ };
196
343
  }
197
344
 
198
345
  function useHandleApply( appliedIds: StyleDefinitionID[], setAppliedIds: ( ids: StyleDefinitionID[] ) => void ) {
199
346
  const { id: activeId, setId: setActiveId } = useStyle();
200
347
 
201
- return ( selectedOptions: Option[] ) => {
348
+ return ( selectedOptions: StyleDefOption[] ) => {
202
349
  const selectedValues = selectedOptions
203
350
  .map( ( { value } ) => value )
204
351
  .filter( ( value ) => value !== EMPTY_OPTION.value );
@@ -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
  };