@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.
Files changed (30) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/index.js +1038 -754
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.mjs +1014 -725
  5. package/dist/index.mjs.map +1 -1
  6. package/package.json +10 -9
  7. package/src/components/creatable-autocomplete/autocomplete-option-internal-properties.ts +3 -6
  8. package/src/components/creatable-autocomplete/creatable-autocomplete.tsx +25 -2
  9. package/src/components/creatable-autocomplete/types.ts +13 -4
  10. package/src/components/creatable-autocomplete/use-autocomplete-change.ts +59 -46
  11. package/src/components/creatable-autocomplete/use-create-option.ts +4 -4
  12. package/src/components/css-classes/css-class-context.tsx +30 -0
  13. package/src/components/css-classes/css-class-item.tsx +8 -19
  14. package/src/components/css-classes/css-class-menu.tsx +78 -78
  15. package/src/components/css-classes/css-class-selector.tsx +46 -32
  16. package/src/components/css-classes/use-apply-and-unapply-class.ts +178 -0
  17. package/src/components/editing-panel-tabs.tsx +7 -1
  18. package/src/components/settings-tab.tsx +14 -1
  19. package/src/components/style-indicator.tsx +1 -1
  20. package/src/components/style-sections/size-section/object-fit-field.tsx +1 -1
  21. package/src/components/style-sections/size-section/object-position-field.tsx +1 -1
  22. package/src/components/style-sections/size-section/size-section.tsx +13 -5
  23. package/src/components/style-sections/typography-section/typography-section.tsx +1 -1
  24. package/src/components/style-tab.tsx +82 -24
  25. package/src/controls-registry/controls-registry.tsx +2 -0
  26. package/src/hooks/use-active-style-def-id.ts +5 -2
  27. package/src/hooks/use-default-panel-settings.ts +33 -0
  28. package/src/hooks/use-state-by-element.ts +2 -1
  29. package/src/sync/experiments-flags.ts +4 -0
  30. package/src/hooks/use-unapply-class.ts +0 -29
@@ -13,9 +13,9 @@ import {
13
13
  validateStyleLabel,
14
14
  } from '@elementor/editor-styles-repository';
15
15
  import { WarningInfotip } from '@elementor/editor-ui';
16
- import { MapPinIcon } from '@elementor/icons';
16
+ import { ColorSwatchIcon, MapPinIcon } from '@elementor/icons';
17
17
  import { createLocation } from '@elementor/locations';
18
- import { Chip, FormLabel, Stack } from '@elementor/ui';
18
+ import { type AutocompleteChangeReason, Box, Chip, FormLabel, Link, Stack, Typography } from '@elementor/ui';
19
19
  import { __ } from '@wordpress/i18n';
20
20
 
21
21
  import { useClassesProp } from '../../contexts/classes-prop-context';
@@ -29,6 +29,7 @@ import {
29
29
  type ValidationResult,
30
30
  } from '../creatable-autocomplete';
31
31
  import { CssClassItem } from './css-class-item';
32
+ import { useApplyClass, useUnapplyClass } from './use-apply-and-unapply-class';
32
33
 
33
34
  const ID = 'elementor-css-class-selector';
34
35
  const TAGS_LIMIT = 50;
@@ -58,13 +59,13 @@ export const { Slot: ClassSelectorActionsSlot, inject: injectIntoClassSelectorAc
58
59
  export function CssClassSelector() {
59
60
  const options = useOptions();
60
61
 
61
- const { value: appliedIds, setValue: setAppliedIds, pushValue: pushAppliedId } = useAppliedClassesIds();
62
+ const { value: appliedIds, pushValue: pushAppliedId } = useAppliedClassesIds();
62
63
  const { id: activeId, setId: setActiveId } = useStyle();
63
64
 
64
65
  const autocompleteRef = useRef< HTMLElement | null >( null );
65
66
  const [ renameError, setRenameError ] = useState< string | null >( null );
66
67
 
67
- const handleApply = useHandleApply( appliedIds, setAppliedIds );
68
+ const handleSelect = useHandleSelect();
68
69
  const { create, validate, entityName } = useCreateAction( { pushAppliedId, setActiveId } );
69
70
 
70
71
  const applied = useAppliedOptions( options, appliedIds );
@@ -97,10 +98,11 @@ export function CssClassSelector() {
97
98
  options={ options }
98
99
  selected={ applied }
99
100
  entityName={ entityName }
100
- onSelect={ handleApply }
101
+ onSelect={ handleSelect }
101
102
  onCreate={ create ?? undefined }
102
103
  validate={ validate ?? undefined }
103
104
  limitTags={ TAGS_LIMIT }
105
+ renderEmptyState={ EmptyState }
104
106
  getLimitTagsText={ ( more ) => (
105
107
  <Chip size="tiny" variant="standard" label={ `+${ more }` } clickable />
106
108
  ) }
@@ -139,6 +141,33 @@ export function CssClassSelector() {
139
141
  );
140
142
  }
141
143
 
144
+ const EmptyState = ( { searchValue, onClear }: { searchValue: string; onClear: () => void } ) => (
145
+ <Box sx={ { py: 4 } }>
146
+ <Stack
147
+ gap={ 1 }
148
+ alignItems="center"
149
+ color="text.secondary"
150
+ justifyContent="center"
151
+ sx={ { px: 2, m: 'auto', maxWidth: '236px' } }
152
+ >
153
+ <ColorSwatchIcon sx={ { transform: 'rotate(90deg)' } } fontSize="large" />
154
+ <Typography align="center" variant="subtitle2">
155
+ { __( 'Sorry, nothing matched', 'elementor' ) }
156
+ <br />
157
+ &ldquo;{ searchValue }&rdquo;.
158
+ </Typography>
159
+ <Typography align="center" variant="caption" sx={ { mb: 2 } }>
160
+ { __( 'With your current role,', 'elementor' ) }
161
+ <br />
162
+ { __( 'you can only use existing classes.', 'elementor' ) }
163
+ </Typography>
164
+ <Link color="text.secondary" variant="caption" component="button" onClick={ onClear }>
165
+ { __( 'Clear & try again', 'elementor' ) }
166
+ </Link>
167
+ </Stack>
168
+ </Box>
169
+ );
170
+
142
171
  const updateClassByProvider = ( provider: string | null, data: UpdateActionPayload ) => {
143
172
  if ( ! provider ) {
144
173
  return;
@@ -263,42 +292,27 @@ function useAppliedClassesIds() {
263
292
 
264
293
  return {
265
294
  value,
266
- setValue,
267
295
  pushValue,
268
296
  };
269
297
  }
270
298
 
271
- function useHandleApply( appliedIds: StyleDefinitionID[], setAppliedIds: ( ids: StyleDefinitionID[] ) => void ) {
272
- const { id: activeId, setId: setActiveId } = useStyle();
273
-
274
- return ( selectedOptions: StyleDefOption[] ) => {
275
- const selectedValues = selectedOptions
276
- .map( ( { value } ) => value )
277
- .filter( ( value ) => value !== EMPTY_OPTION.value );
278
-
279
- const isSameClassesAlreadyApplied =
280
- selectedValues.length === appliedIds.length &&
281
- selectedValues.every( ( value ) => appliedIds.includes( value ) );
282
-
283
- // Should not trigger to avoid register an undo step.
284
- if ( isSameClassesAlreadyApplied ) {
285
- return;
286
- }
287
-
288
- setAppliedIds( selectedValues );
289
-
290
- const addedValue = selectedValues.find( ( id ) => ! appliedIds.includes( id ) );
291
-
292
- if ( addedValue ) {
293
- setActiveId( addedValue );
299
+ function useHandleSelect() {
300
+ const apply = useApplyClass();
301
+ const unapply = useUnapplyClass();
294
302
 
303
+ return ( _selectedOptions: StyleDefOption[], reason: AutocompleteChangeReason, option: StyleDefOption ) => {
304
+ if ( ! option.value ) {
295
305
  return;
296
306
  }
297
307
 
298
- const removedValue = appliedIds.find( ( id ) => ! selectedValues.includes( id ) );
308
+ switch ( reason ) {
309
+ case 'selectOption':
310
+ apply( { classId: option.value, classLabel: option.label } );
311
+ break;
299
312
 
300
- if ( removedValue && removedValue === activeId ) {
301
- setActiveId( selectedValues[ 0 ] ?? null );
313
+ case 'removeOption':
314
+ unapply( { classId: option.value, classLabel: option.label } );
315
+ break;
302
316
  }
303
317
  };
304
318
  }
@@ -0,0 +1,178 @@
1
+ import { useCallback, useMemo } from 'react';
2
+ import { setDocumentModifiedStatus } from '@elementor/editor-documents';
3
+ import { getElementLabel, getElementSetting, updateElementSettings } from '@elementor/editor-elements';
4
+ import { classesPropTypeUtil, type ClassesPropValue } from '@elementor/editor-props';
5
+ import { type StyleDefinitionID } from '@elementor/editor-styles';
6
+ import { isExperimentActive, undoable } from '@elementor/editor-v1-adapters';
7
+ import { __ } from '@wordpress/i18n';
8
+
9
+ import { useClassesProp } from '../../contexts/classes-prop-context';
10
+ import { useElement } from '../../contexts/element-context';
11
+ import { useStyle } from '../../contexts/style-context';
12
+
13
+ type UndoableClassActionPayload = {
14
+ classId: StyleDefinitionID;
15
+ classLabel: string;
16
+ };
17
+
18
+ export function useApplyClass() {
19
+ const { id: activeId, setId: setActiveId } = useStyle();
20
+ const { element } = useElement();
21
+
22
+ const isVersion330Active = isExperimentActive( 'e_v_3_30' );
23
+
24
+ const applyClass = useApply();
25
+ const unapplyClass = useUnapply();
26
+
27
+ const undoableApply = useMemo( () => {
28
+ return undoable(
29
+ {
30
+ do: ( { classId }: UndoableClassActionPayload ) => {
31
+ const prevActiveId = activeId;
32
+
33
+ applyClass( classId );
34
+ setDocumentModifiedStatus( true );
35
+
36
+ return prevActiveId;
37
+ },
38
+ undo: ( { classId }: UndoableClassActionPayload, prevActiveId: string | null ) => {
39
+ unapplyClass( classId );
40
+ setActiveId( prevActiveId );
41
+ },
42
+ },
43
+ {
44
+ title: getElementLabel( element.id ),
45
+ subtitle: ( { classLabel } ) => {
46
+ /* translators: %s is the class name. */
47
+ return __( `class %s applied`, 'elementor' ).replace( '%s', classLabel );
48
+ },
49
+ }
50
+ );
51
+ }, [ activeId, applyClass, element.id, unapplyClass, setActiveId ] );
52
+
53
+ const applyWithoutHistory = useCallback(
54
+ ( { classId }: UndoableClassActionPayload ) => {
55
+ applyClass( classId );
56
+ },
57
+ [ applyClass ]
58
+ );
59
+
60
+ return isVersion330Active ? undoableApply : applyWithoutHistory;
61
+ }
62
+
63
+ export function useUnapplyClass() {
64
+ const { id: activeId, setId: setActiveId } = useStyle();
65
+ const { element } = useElement();
66
+
67
+ const isVersion330Active = isExperimentActive( 'e_v_3_30' );
68
+
69
+ const applyClass = useApply();
70
+ const unapplyClass = useUnapply();
71
+
72
+ const undoableUnapply = useMemo( () => {
73
+ return undoable(
74
+ {
75
+ do: ( { classId }: UndoableClassActionPayload ) => {
76
+ const prevActiveId = activeId;
77
+
78
+ unapplyClass( classId );
79
+ setDocumentModifiedStatus( true );
80
+
81
+ return prevActiveId;
82
+ },
83
+ undo: ( { classId }: UndoableClassActionPayload, prevActiveId: string | null ) => {
84
+ applyClass( classId );
85
+ setActiveId( prevActiveId );
86
+ },
87
+ },
88
+ {
89
+ title: getElementLabel( element.id ),
90
+ subtitle: ( { classLabel } ) => {
91
+ /* translators: %s is the class name. */
92
+ return __( `class %s removed`, 'elementor' ).replace( '%s', classLabel );
93
+ },
94
+ }
95
+ );
96
+ }, [ activeId, applyClass, element.id, unapplyClass, setActiveId ] );
97
+
98
+ const unapplyWithoutHistory = useCallback(
99
+ ( { classId }: UndoableClassActionPayload ) => {
100
+ unapplyClass( classId );
101
+ },
102
+ [ unapplyClass ]
103
+ );
104
+
105
+ return isVersion330Active ? undoableUnapply : unapplyWithoutHistory;
106
+ }
107
+
108
+ function useApply() {
109
+ const { element } = useElement();
110
+ const { setId: setActiveId } = useStyle();
111
+ const { setClasses, getAppliedClasses } = useSetClasses();
112
+
113
+ return useCallback(
114
+ ( classIDToApply: StyleDefinitionID ) => {
115
+ const appliedClasses = getAppliedClasses();
116
+
117
+ if ( appliedClasses.includes( classIDToApply ) ) {
118
+ throw new Error(
119
+ `Class ${ classIDToApply } is already applied to element ${ element.id }, cannot re-apply.`
120
+ );
121
+ }
122
+
123
+ const updatedClassesIds = [ ...appliedClasses, classIDToApply ];
124
+ setClasses( updatedClassesIds );
125
+ setActiveId( classIDToApply );
126
+ },
127
+ [ element.id, getAppliedClasses, setActiveId, setClasses ]
128
+ );
129
+ }
130
+
131
+ function useUnapply() {
132
+ const { element } = useElement();
133
+ const { id: activeId, setId: setActiveId } = useStyle();
134
+ const { setClasses, getAppliedClasses } = useSetClasses();
135
+
136
+ return useCallback(
137
+ ( classIDToUnapply: StyleDefinitionID ) => {
138
+ const appliedClasses = getAppliedClasses();
139
+
140
+ if ( ! appliedClasses.includes( classIDToUnapply ) ) {
141
+ throw new Error(
142
+ `Class ${ classIDToUnapply } is not applied to element ${ element.id }, cannot unapply it.`
143
+ );
144
+ }
145
+
146
+ const updatedClassesIds = appliedClasses.filter( ( id ) => id !== classIDToUnapply );
147
+ setClasses( updatedClassesIds );
148
+
149
+ if ( activeId === classIDToUnapply ) {
150
+ setActiveId( updatedClassesIds[ 0 ] ?? null );
151
+ }
152
+ },
153
+ [ activeId, element.id, getAppliedClasses, setActiveId, setClasses ]
154
+ );
155
+ }
156
+
157
+ function useSetClasses() {
158
+ const { element } = useElement();
159
+ const currentClassesProp = useClassesProp();
160
+
161
+ return useMemo( () => {
162
+ const setClasses = ( ids: StyleDefinitionID[] ) => {
163
+ updateElementSettings( {
164
+ id: element.id,
165
+ props: { [ currentClassesProp ]: classesPropTypeUtil.create( ids ) },
166
+ withHistory: false,
167
+ } );
168
+ };
169
+
170
+ const getAppliedClasses = () =>
171
+ getElementSetting< ClassesPropValue >( element.id, currentClassesProp )?.value || [];
172
+
173
+ return {
174
+ setClasses,
175
+ getAppliedClasses,
176
+ };
177
+ }, [ currentClassesProp, element.id ] );
178
+ }
@@ -1,11 +1,14 @@
1
1
  import * as React from 'react';
2
2
  import { Fragment } from 'react';
3
+ import { isExperimentActive } from '@elementor/editor-v1-adapters';
3
4
  import { Divider, Stack, Tab, TabPanel, Tabs, useTabs } from '@elementor/ui';
4
5
  import { __ } from '@wordpress/i18n';
5
6
 
6
7
  import { useElement } from '../contexts/element-context';
7
8
  import { ScrollProvider } from '../contexts/scroll-context';
9
+ import { useDefaultPanelSettings } from '../hooks/use-default-panel-settings';
8
10
  import { useStateByElement } from '../hooks/use-state-by-element';
11
+ import { EXPERIMENTAL_FEATURES } from '../sync/experiments-flags';
9
12
  import { SettingsTab } from './settings-tab';
10
13
  import { stickyHeaderStyles, StyleTab } from './style-tab';
11
14
 
@@ -23,7 +26,10 @@ export const EditingPanelTabs = () => {
23
26
  };
24
27
 
25
28
  const PanelTabContent = () => {
26
- const defaultComponentTab = 'settings';
29
+ const editorDefaults = useDefaultPanelSettings();
30
+ const defaultComponentTab = isExperimentActive( EXPERIMENTAL_FEATURES.V_3_30 )
31
+ ? ( editorDefaults.defaultTab as TabValue )
32
+ : 'settings';
27
33
 
28
34
  const [ currentTab, setCurrentTab ] = useStateByElement< TabValue >( 'tab', defaultComponentTab );
29
35
  const { getTabProps, getTabPanelProps, getTabsProps } = useTabs< TabValue >( currentTab );
@@ -1,6 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { ControlFormLabel } from '@elementor/editor-controls';
3
3
  import { type Control } from '@elementor/editor-elements';
4
+ import { isExperimentActive } from '@elementor/editor-v1-adapters';
4
5
  import { SessionStorageProvider } from '@elementor/session';
5
6
  import { Divider } from '@elementor/ui';
6
7
 
@@ -9,11 +10,19 @@ import { Control as BaseControl } from '../controls-registry/control';
9
10
  import { ControlTypeContainer } from '../controls-registry/control-type-container';
10
11
  import { type ControlType, getControl, getDefaultLayout } from '../controls-registry/controls-registry';
11
12
  import { SettingsField } from '../controls-registry/settings-field';
13
+ import { useDefaultPanelSettings } from '../hooks/use-default-panel-settings';
14
+ import { EXPERIMENTAL_FEATURES } from '../sync/experiments-flags';
12
15
  import { Section } from './section';
13
16
  import { SectionsList } from './sections-list';
14
17
 
15
18
  export const SettingsTab = () => {
16
19
  const { elementType, element } = useElement();
20
+ const settingsDefault = useDefaultPanelSettings();
21
+
22
+ const isDefaultExpanded = ( sectionId: string ) =>
23
+ isExperimentActive( EXPERIMENTAL_FEATURES.V_3_30 )
24
+ ? settingsDefault.defaultSectionsExpanded.settings?.includes( sectionId )
25
+ : true;
17
26
 
18
27
  return (
19
28
  <SessionStorageProvider prefix={ element.id }>
@@ -25,7 +34,11 @@ export const SettingsTab = () => {
25
34
 
26
35
  if ( type === 'section' ) {
27
36
  return (
28
- <Section title={ value.label } key={ type + '.' + index } defaultExpanded={ true }>
37
+ <Section
38
+ title={ value.label }
39
+ key={ type + '.' + index }
40
+ defaultExpanded={ isDefaultExpanded( value.label ) }
41
+ >
29
42
  { value.items?.map( ( item ) => {
30
43
  if ( item.type === 'control' ) {
31
44
  return <Control key={ item.value.bind } control={ item.value } />;
@@ -1,6 +1,6 @@
1
1
  import { styled } from '@elementor/ui';
2
2
 
3
- export type StyleIndicatorVariant = 'overridden' | 'local' | 'global';
3
+ type StyleIndicatorVariant = 'overridden' | 'local' | 'global';
4
4
 
5
5
  export const StyleIndicator = styled( 'div', {
6
6
  shouldForwardProp: ( prop ) => prop !== 'variant',
@@ -21,7 +21,7 @@ type Props = {
21
21
  export const ObjectFitField = ( { onChange }: Props ) => {
22
22
  return (
23
23
  <StylesField bind="object-fit">
24
- <Grid container pt={ 2 } gap={ 2 } alignItems="center" flexWrap="nowrap">
24
+ <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
25
25
  <Grid item xs={ 6 }>
26
26
  <ControlLabel>{ __( 'Object fit', 'elementor' ) }</ControlLabel>
27
27
  </Grid>
@@ -25,7 +25,7 @@ type Props = {
25
25
  export const ObjectPositionField = ( { onChange }: Props ) => {
26
26
  return (
27
27
  <StylesField bind="object-position">
28
- <Grid container pt={ 2 } gap={ 2 } alignItems="center" flexWrap="nowrap">
28
+ <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
29
29
  <Grid item xs={ 6 }>
30
30
  <ControlLabel>{ __( 'Object position', 'elementor' ) }</ControlLabel>
31
31
  </Grid>
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { type ExtendedValue, SizeControl } from '@elementor/editor-controls';
2
+ import { AspectRatioControl, type ExtendedValue, SizeControl } from '@elementor/editor-controls';
3
3
  import type { StringPropValue } from '@elementor/editor-props';
4
4
  import { isExperimentActive } from '@elementor/editor-v1-adapters';
5
5
  import { Grid, Stack } from '@elementor/ui';
@@ -68,10 +68,18 @@ export const SizeSection = () => {
68
68
  </Stack>
69
69
  { isVersion330Active && (
70
70
  <CollapsibleContent>
71
- <ObjectFitField onChange={ onFitChange } />
72
- <Grid item xs={ 6 }>
73
- { isNotFill && <ObjectPositionField /> }
74
- </Grid>
71
+ <Stack gap={ 2 }>
72
+ <StylesField bind={ 'aspect-ratio' }>
73
+ <AspectRatioControl label={ __( 'Aspect Ratio', 'elementor' ) } />
74
+ </StylesField>
75
+ <PanelDivider />
76
+ <ObjectFitField onChange={ onFitChange } />
77
+ { isNotFill && (
78
+ <Grid item xs={ 6 }>
79
+ <ObjectPositionField />
80
+ </Grid>
81
+ ) }
82
+ </Stack>
75
83
  </CollapsibleContent>
76
84
  ) }
77
85
  </SectionContent>
@@ -25,7 +25,7 @@ import { WordSpacingField } from './word-spacing-field';
25
25
  export const TypographySection = () => {
26
26
  const [ columnCount ] = useStylesField< NumberPropValue >( 'column-count' );
27
27
  const isVersion330Active = isExperimentActive( 'e_v_3_30' );
28
- const hasMultiColumns = columnCount?.value && columnCount?.value > 1;
28
+ const hasMultiColumns = !! ( columnCount?.value && columnCount?.value > 1 );
29
29
  return (
30
30
  <SectionContent>
31
31
  <FontFamilyField />
@@ -3,6 +3,7 @@ import { useState } from 'react';
3
3
  import { CLASSES_PROP_KEY } from '@elementor/editor-props';
4
4
  import { useActiveBreakpoint } from '@elementor/editor-responsive';
5
5
  import { type StyleDefinitionID, type StyleDefinitionState } from '@elementor/editor-styles';
6
+ import { isExperimentActive } from '@elementor/editor-v1-adapters';
6
7
  import { SessionStorageProvider } from '@elementor/session';
7
8
  import { Divider, Stack } from '@elementor/ui';
8
9
  import { __ } from '@wordpress/i18n';
@@ -13,6 +14,8 @@ import { useScrollDirection } from '../contexts/scroll-context';
13
14
  import { StyleProvider } from '../contexts/style-context';
14
15
  import { StyleInheritanceProvider } from '../contexts/styles-inheritance-context';
15
16
  import { useActiveStyleDefId } from '../hooks/use-active-style-def-id';
17
+ import { useDefaultPanelSettings } from '../hooks/use-default-panel-settings';
18
+ import { EXPERIMENTAL_FEATURES } from '../sync/experiments-flags';
16
19
  import { CssClassSelector } from './css-classes/css-class-selector';
17
20
  import { Section } from './section';
18
21
  import { SectionsList } from './sections-list';
@@ -35,6 +38,27 @@ export const stickyHeaderStyles = {
35
38
  transition: 'top 300ms ease',
36
39
  };
37
40
 
41
+ type Section = {
42
+ component: () => React.JSX.Element;
43
+ name: string;
44
+ title: string;
45
+ };
46
+
47
+ const PanelSection = ( { section }: { section: Section } ) => {
48
+ const { component, name, title } = section;
49
+ const tabDefaults = useDefaultPanelSettings();
50
+ const SectionComponent = component;
51
+ const isExpanded = isExperimentActive( EXPERIMENTAL_FEATURES.V_3_30 )
52
+ ? tabDefaults.defaultSectionsExpanded.style?.includes( name )
53
+ : true;
54
+
55
+ return (
56
+ <Section title={ title } defaultExpanded={ isExpanded }>
57
+ <SectionComponent />
58
+ </Section>
59
+ );
60
+ };
61
+
38
62
  export const StyleTab = () => {
39
63
  const currentClassesProp = useCurrentClassesProp();
40
64
  const [ activeStyleDefId, setActiveStyleDefId ] = useActiveStyleDefId( currentClassesProp );
@@ -59,30 +83,62 @@ export const StyleTab = () => {
59
83
  <Divider />
60
84
  </ClassesHeader>
61
85
  <SectionsList>
62
- <Section title={ __( 'Layout', 'elementor' ) }>
63
- <LayoutSection />
64
- </Section>
65
- <Section title={ __( 'Spacing', 'elementor' ) }>
66
- <SpacingSection />
67
- </Section>
68
- <Section title={ __( 'Size', 'elementor' ) }>
69
- <SizeSection />
70
- </Section>
71
- <Section title={ __( 'Position', 'elementor' ) }>
72
- <PositionSection />
73
- </Section>
74
- <Section title={ __( 'Typography', 'elementor' ) }>
75
- <TypographySection />
76
- </Section>
77
- <Section title={ __( 'Background', 'elementor' ) }>
78
- <BackgroundSection />
79
- </Section>
80
- <Section title={ __( 'Border', 'elementor' ) }>
81
- <BorderSection />
82
- </Section>
83
- <Section title={ __( 'Effects', 'elementor' ) }>
84
- <EffectsSection />
85
- </Section>
86
+ <PanelSection
87
+ section={ {
88
+ component: LayoutSection,
89
+ name: 'Layout',
90
+ title: __( 'Layout', 'elementor' ),
91
+ } }
92
+ />
93
+ <PanelSection
94
+ section={ {
95
+ component: SpacingSection,
96
+ name: 'Spacing',
97
+ title: __( 'Spacing', 'elementor' ),
98
+ } }
99
+ />
100
+ <PanelSection
101
+ section={ {
102
+ component: SizeSection,
103
+ name: 'Size',
104
+ title: __( 'Size', 'elementor' ),
105
+ } }
106
+ />
107
+ <PanelSection
108
+ section={ {
109
+ component: PositionSection,
110
+ name: 'Position',
111
+ title: __( 'Position', 'elementor' ),
112
+ } }
113
+ />
114
+ <PanelSection
115
+ section={ {
116
+ component: TypographySection,
117
+ name: 'Typography',
118
+ title: __( 'Typography', 'elementor' ),
119
+ } }
120
+ />
121
+ <PanelSection
122
+ section={ {
123
+ component: BackgroundSection,
124
+ name: 'Background',
125
+ title: __( 'Background', 'elementor' ),
126
+ } }
127
+ />
128
+ <PanelSection
129
+ section={ {
130
+ component: BorderSection,
131
+ name: 'Border',
132
+ title: __( 'Border', 'elementor' ),
133
+ } }
134
+ />
135
+ <PanelSection
136
+ section={ {
137
+ component: EffectsSection,
138
+ name: 'Effects',
139
+ title: __( 'Effects', 'elementor' ),
140
+ } }
141
+ />
86
142
  </SectionsList>
87
143
  </StyleInheritanceProvider>
88
144
  </SessionStorageProvider>
@@ -114,3 +170,5 @@ function useCurrentClassesProp(): string {
114
170
 
115
171
  return prop[ 0 ];
116
172
  }
173
+
174
+ export { PanelSection as StyleTabSection };
@@ -5,6 +5,7 @@ import {
5
5
  SelectControl,
6
6
  SizeControl,
7
7
  SvgMediaControl,
8
+ SwitchControl,
8
9
  TextAreaControl,
9
10
  TextControl,
10
11
  UrlControl,
@@ -22,6 +23,7 @@ const controlTypes = {
22
23
  select: { component: SelectControl, layout: 'two-columns' },
23
24
  link: { component: LinkControl, layout: 'full' },
24
25
  url: { component: UrlControl, layout: 'full' },
26
+ switch: { component: SwitchControl, layout: 'two-columns' },
25
27
  } as const satisfies ControlRegistry;
26
28
 
27
29
  export type ControlType = keyof typeof controlTypes;
@@ -1,12 +1,15 @@
1
- import { useState } from 'react';
2
1
  import { getElementStyles, useElementSetting } from '@elementor/editor-elements';
3
2
  import { type ClassesPropValue, type PropKey } from '@elementor/editor-props';
4
3
  import { type StyleDefinitionID } from '@elementor/editor-styles';
5
4
 
6
5
  import { useElement } from '../contexts/element-context';
6
+ import { useStateByElement } from './use-state-by-element';
7
7
 
8
8
  export function useActiveStyleDefId( classProp: PropKey ) {
9
- const [ activeStyledDefId, setActiveStyledDefId ] = useState< StyleDefinitionID | null >( null );
9
+ const [ activeStyledDefId, setActiveStyledDefId ] = useStateByElement< StyleDefinitionID | null >(
10
+ 'active-style-id',
11
+ null
12
+ );
10
13
 
11
14
  const appliedClassesIds = useAppliedClassesIds( classProp )?.value || [];
12
15
 
@@ -0,0 +1,33 @@
1
+ import { createContext, useContext } from 'react';
2
+
3
+ import { useElement } from '../contexts/element-context';
4
+
5
+ type Defaults = {
6
+ defaultSectionsExpanded: Record< string, string[] >;
7
+ defaultTab: string;
8
+ };
9
+
10
+ const fallbackEditorSettings: Defaults = {
11
+ defaultSectionsExpanded: {
12
+ settings: [ 'Content', 'Settings' ],
13
+ style: [],
14
+ },
15
+ defaultTab: 'settings',
16
+ };
17
+
18
+ const defaultPanelSettingsContext = createContext< Record< string, Defaults | undefined > >( {
19
+ 'e-div-block': {
20
+ defaultSectionsExpanded: fallbackEditorSettings.defaultSectionsExpanded,
21
+ defaultTab: 'style',
22
+ },
23
+ 'e-flexbox': {
24
+ defaultSectionsExpanded: fallbackEditorSettings.defaultSectionsExpanded,
25
+ defaultTab: 'style',
26
+ },
27
+ } );
28
+
29
+ export const useDefaultPanelSettings = () => {
30
+ const { element } = useElement();
31
+ const defaults = useContext( defaultPanelSettingsContext )[ element.type ];
32
+ return defaults || fallbackEditorSettings;
33
+ };
@@ -3,10 +3,11 @@ import { isExperimentActive } from '@elementor/editor-v1-adapters';
3
3
  import { getSessionStorageItem, setSessionStorageItem } from '@elementor/session';
4
4
 
5
5
  import { useElement } from '../contexts/element-context';
6
+ import { EXPERIMENTAL_FEATURES } from '../sync/experiments-flags';
6
7
 
7
8
  export const useStateByElement = < T >( key: string, initialValue: T ) => {
8
9
  const { element } = useElement();
9
- const isFeatureActive = isExperimentActive( 'e_v_3_30' );
10
+ const isFeatureActive = isExperimentActive( EXPERIMENTAL_FEATURES.V_3_30 );
10
11
  const lookup = `elementor/editor-state/${ element.id }/${ key }`;
11
12
  const storedValue = isFeatureActive ? getSessionStorageItem< T >( lookup ) : initialValue;
12
13
  const [ value, setValue ] = useState( storedValue ?? initialValue );