@elementor/editor-editing-panel 1.38.1 → 1.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/dist/index.js +1038 -754
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1014 -725
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -9
- package/src/components/creatable-autocomplete/autocomplete-option-internal-properties.ts +3 -6
- package/src/components/creatable-autocomplete/creatable-autocomplete.tsx +25 -2
- package/src/components/creatable-autocomplete/types.ts +13 -4
- package/src/components/creatable-autocomplete/use-autocomplete-change.ts +59 -46
- package/src/components/creatable-autocomplete/use-create-option.ts +4 -4
- package/src/components/css-classes/css-class-context.tsx +30 -0
- package/src/components/css-classes/css-class-item.tsx +8 -19
- package/src/components/css-classes/css-class-menu.tsx +78 -78
- package/src/components/css-classes/css-class-selector.tsx +46 -32
- package/src/components/css-classes/use-apply-and-unapply-class.ts +178 -0
- package/src/components/editing-panel-tabs.tsx +7 -1
- package/src/components/settings-tab.tsx +14 -1
- package/src/components/style-indicator.tsx +1 -1
- package/src/components/style-sections/size-section/object-fit-field.tsx +1 -1
- package/src/components/style-sections/size-section/object-position-field.tsx +1 -1
- package/src/components/style-sections/size-section/size-section.tsx +13 -5
- package/src/components/style-sections/typography-section/typography-section.tsx +1 -1
- package/src/components/style-tab.tsx +82 -24
- package/src/controls-registry/controls-registry.tsx +2 -0
- package/src/hooks/use-active-style-def-id.ts +5 -2
- package/src/hooks/use-default-panel-settings.ts +33 -0
- package/src/hooks/use-state-by-element.ts +2 -1
- package/src/sync/experiments-flags.ts +4 -0
- package/src/hooks/use-unapply-class.ts +0 -29
|
@@ -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,
|
|
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
|
|
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={
|
|
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
|
+
“{ searchValue }”.
|
|
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
|
|
272
|
-
const
|
|
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
|
-
|
|
308
|
+
switch ( reason ) {
|
|
309
|
+
case 'selectOption':
|
|
310
|
+
apply( { classId: option.value, classLabel: option.label } );
|
|
311
|
+
break;
|
|
299
312
|
|
|
300
|
-
|
|
301
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
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 ] =
|
|
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(
|
|
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 );
|