@elementor/editor-editing-panel 1.3.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -1
- package/dist/index.js +650 -635
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +543 -528
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -5
- package/src/components/conditional-tooltip-wrapper.tsx +52 -0
- package/src/components/css-class-selector.tsx +59 -19
- package/src/components/multi-combobox/types.ts +1 -0
- package/src/components/style-sections/layout-section/gap-control-field.tsx +18 -0
- package/src/components/style-sections/layout-section/layout-section.tsx +2 -0
- package/src/components/style-sections/position-section/position-field.tsx +6 -2
- package/src/components/style-sections/position-section/position-section.tsx +51 -30
- package/src/components/style-tab.tsx +2 -11
- package/src/contexts/style-context.tsx +2 -2
- package/src/dynamics/components/dynamic-selection-control.tsx +3 -5
- package/src/dynamics/components/dynamic-selection.tsx +6 -6
- package/src/hooks/use-persist-dynamic-value.ts +11 -0
- package/src/hooks/use-session-storage.ts +46 -0
- package/src/hooks/use-styles-field.ts +8 -42
- package/src/hooks/use-styles-fields.ts +32 -0
- package/src/hooks/use-prop-value-history.ts +0 -45
- package/src/hooks/use-style-prop-history.ts +0 -75
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-editing-panel",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "Elementor Team",
|
|
6
6
|
"homepage": "https://elementor.com/",
|
|
@@ -40,13 +40,14 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@elementor/editor": "0.17.2",
|
|
43
|
-
"@elementor/editor-controls": "0.
|
|
44
|
-
"@elementor/editor-elements": "0.3.
|
|
43
|
+
"@elementor/editor-controls": "0.3.1",
|
|
44
|
+
"@elementor/editor-elements": "0.3.3",
|
|
45
45
|
"@elementor/menus": "0.1.2",
|
|
46
|
-
"@elementor/editor-props": "0.
|
|
46
|
+
"@elementor/editor-props": "0.5.0",
|
|
47
47
|
"@elementor/editor-panels": "0.10.2",
|
|
48
48
|
"@elementor/editor-responsive": "0.12.4",
|
|
49
|
-
"@elementor/editor-styles": "0.3.
|
|
49
|
+
"@elementor/editor-styles": "0.3.2",
|
|
50
|
+
"@elementor/editor-styles-repository": "0.2.0",
|
|
50
51
|
"@elementor/editor-v1-adapters": "0.8.5",
|
|
51
52
|
"@elementor/icons": "^1.20.0",
|
|
52
53
|
"@elementor/schema": "0.1.2",
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { Box, Tooltip } from '@elementor/ui';
|
|
4
|
+
|
|
5
|
+
type ConditionalTooltipWrapperProps = {
|
|
6
|
+
maxWidth: React.CSSProperties[ 'maxWidth' ];
|
|
7
|
+
title: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const ConditionalTooltipWrapper = ( { maxWidth, title }: ConditionalTooltipWrapperProps ) => {
|
|
11
|
+
const elRef = useRef< HTMLElement >( null );
|
|
12
|
+
const [ isOverflown, setIsOverflown ] = useState( false );
|
|
13
|
+
|
|
14
|
+
useEffect( () => {
|
|
15
|
+
const onResize = () => {
|
|
16
|
+
const element = elRef.current;
|
|
17
|
+
|
|
18
|
+
if ( element ) {
|
|
19
|
+
setIsOverflown( element.scrollWidth > element.clientWidth );
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
onResize();
|
|
24
|
+
|
|
25
|
+
window.addEventListener( 'resize', onResize );
|
|
26
|
+
|
|
27
|
+
return () => {
|
|
28
|
+
window.removeEventListener( 'resize', onResize );
|
|
29
|
+
};
|
|
30
|
+
}, [] );
|
|
31
|
+
|
|
32
|
+
if ( isOverflown ) {
|
|
33
|
+
return (
|
|
34
|
+
<Tooltip title={ title } placement="top">
|
|
35
|
+
<Content maxWidth={ maxWidth } ref={ elRef } title={ title } />
|
|
36
|
+
</Tooltip>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return <Content maxWidth={ maxWidth } ref={ elRef } title={ title } />;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const Content = React.forwardRef( ( { maxWidth, title, ...rest }: ConditionalTooltipWrapperProps, ref ) => (
|
|
44
|
+
<Box
|
|
45
|
+
ref={ ref }
|
|
46
|
+
position="relative"
|
|
47
|
+
sx={ { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth } }
|
|
48
|
+
{ ...rest }
|
|
49
|
+
>
|
|
50
|
+
{ title }
|
|
51
|
+
</Box>
|
|
52
|
+
) );
|
|
@@ -1,32 +1,44 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { updateSettings, useElementSetting
|
|
2
|
+
import { updateSettings, useElementSetting } from '@elementor/editor-elements';
|
|
3
3
|
import { classesPropTypeUtil, type ClassesPropValue } from '@elementor/editor-props';
|
|
4
4
|
import { type StyleDefinitionID } from '@elementor/editor-styles';
|
|
5
|
+
import { ELEMENTS_STYLES_PROVIDER_KEY, useAllStylesByProvider } from '@elementor/editor-styles-repository';
|
|
5
6
|
import { Chip, Stack, Typography } from '@elementor/ui';
|
|
6
7
|
import { __ } from '@wordpress/i18n';
|
|
7
8
|
|
|
8
9
|
import { useClassesProp } from '../contexts/classes-prop-context';
|
|
9
10
|
import { useElement } from '../contexts/element-context';
|
|
10
11
|
import { useStyle } from '../contexts/style-context';
|
|
12
|
+
import { ConditionalTooltipWrapper } from './conditional-tooltip-wrapper';
|
|
11
13
|
import { MultiCombobox, type Option } from './multi-combobox';
|
|
12
14
|
|
|
13
15
|
const ID = 'elementor-css-class-selector';
|
|
14
16
|
const TAGS_LIMIT = 8;
|
|
15
17
|
|
|
18
|
+
const EMPTY_OPTION = {
|
|
19
|
+
label: __( 'local', 'elementor' ),
|
|
20
|
+
value: '',
|
|
21
|
+
fixed: true,
|
|
22
|
+
color: 'primary',
|
|
23
|
+
provider: ELEMENTS_STYLES_PROVIDER_KEY,
|
|
24
|
+
} satisfies Option;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Applied - Classes applied to an element.
|
|
28
|
+
* Active - Class that is currently on edit mode.
|
|
29
|
+
*/
|
|
30
|
+
|
|
16
31
|
export function CssClassSelector() {
|
|
17
32
|
const options = useOptions();
|
|
33
|
+
const [ appliedIds, setAppliedIds ] = useAppliedClassesIds();
|
|
18
34
|
|
|
19
35
|
const { id: activeId, setId: setActiveId } = useStyle();
|
|
20
|
-
const [ appliedIds ] = useAppliedClassesIds();
|
|
21
36
|
|
|
22
|
-
const handleApply = useHandleApply();
|
|
37
|
+
const handleApply = useHandleApply( appliedIds, setAppliedIds );
|
|
23
38
|
const handleActivate = ( { value }: Option ) => setActiveId( value );
|
|
24
39
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
const applied = appliedIds
|
|
28
|
-
.map( ( id ) => options.find( ( option ) => option.value === id ) )
|
|
29
|
-
.filter( ( option ) => !! option );
|
|
40
|
+
const applied = useAppliedOptions( options, appliedIds );
|
|
41
|
+
const active = applied.find( ( option ) => option.value === activeId ) ?? EMPTY_OPTION;
|
|
30
42
|
|
|
31
43
|
return (
|
|
32
44
|
<Stack gap={ 1 } p={ 2 }>
|
|
@@ -51,11 +63,12 @@ export function CssClassSelector() {
|
|
|
51
63
|
{ ...chipProps }
|
|
52
64
|
key={ chipProps.key }
|
|
53
65
|
size="small"
|
|
54
|
-
label={ value.label }
|
|
66
|
+
label={ <ConditionalTooltipWrapper maxWidth="10ch" title={ value.label } /> }
|
|
55
67
|
variant={ isActive ? 'filled' : 'standard' }
|
|
56
68
|
color={ isActive && value.color ? value.color : 'default' }
|
|
57
69
|
onClick={ () => handleActivate( value ) }
|
|
58
70
|
onDelete={ null }
|
|
71
|
+
aria-pressed={ isActive }
|
|
59
72
|
/>
|
|
60
73
|
);
|
|
61
74
|
} )
|
|
@@ -68,14 +81,40 @@ export function CssClassSelector() {
|
|
|
68
81
|
function useOptions() {
|
|
69
82
|
const { element } = useElement();
|
|
70
83
|
|
|
71
|
-
|
|
84
|
+
return useAllStylesByProvider( { elementId: element.id } ).flatMap< Option >( ( [ providerKey, styleDefs ] ) => {
|
|
85
|
+
const isElements = providerKey === ELEMENTS_STYLES_PROVIDER_KEY;
|
|
86
|
+
|
|
87
|
+
// Add empty local option for elements, as fallback.
|
|
88
|
+
if ( isElements && styleDefs.length === 0 ) {
|
|
89
|
+
return [ EMPTY_OPTION ];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return styleDefs.map( ( styleDef ) => {
|
|
93
|
+
return {
|
|
94
|
+
label: styleDef.label,
|
|
95
|
+
value: styleDef.id,
|
|
96
|
+
fixed: isElements,
|
|
97
|
+
color: isElements ? 'primary' : 'global',
|
|
98
|
+
provider: providerKey,
|
|
99
|
+
};
|
|
100
|
+
} );
|
|
101
|
+
} );
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function useAppliedOptions( options: Option[], appliedIds: StyleDefinitionID[] ) {
|
|
105
|
+
const applied = appliedIds
|
|
106
|
+
.map( ( id ) => options.find( ( option ) => option.value === id ) )
|
|
107
|
+
.filter( ( option ) => !! option );
|
|
108
|
+
|
|
109
|
+
const hasElementsProviderStyleApplied = applied.some(
|
|
110
|
+
( option ) => option.provider === ELEMENTS_STYLES_PROVIDER_KEY
|
|
111
|
+
);
|
|
72
112
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
} ) );
|
|
113
|
+
if ( ! hasElementsProviderStyleApplied ) {
|
|
114
|
+
applied.unshift( EMPTY_OPTION );
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return applied;
|
|
79
118
|
}
|
|
80
119
|
|
|
81
120
|
function useAppliedClassesIds() {
|
|
@@ -96,12 +135,13 @@ function useAppliedClassesIds() {
|
|
|
96
135
|
return [ value, setValue ] as const;
|
|
97
136
|
}
|
|
98
137
|
|
|
99
|
-
function useHandleApply() {
|
|
138
|
+
function useHandleApply( appliedIds: StyleDefinitionID[], setAppliedIds: ( ids: StyleDefinitionID[] ) => void ) {
|
|
100
139
|
const { id: activeId, setId: setActiveId } = useStyle();
|
|
101
|
-
const [ appliedIds, setAppliedIds ] = useAppliedClassesIds();
|
|
102
140
|
|
|
103
141
|
return ( selectedOptions: Option[] ) => {
|
|
104
|
-
const selectedValues = selectedOptions
|
|
142
|
+
const selectedValues = selectedOptions
|
|
143
|
+
.map( ( { value } ) => value )
|
|
144
|
+
.filter( ( value ) => value !== EMPTY_OPTION.value );
|
|
105
145
|
|
|
106
146
|
const isSameClassesAlreadyApplied =
|
|
107
147
|
selectedValues.length === appliedIds.length &&
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { GapControl } from '@elementor/editor-controls';
|
|
3
|
+
import { Stack } from '@elementor/ui';
|
|
4
|
+
import { __ } from '@wordpress/i18n';
|
|
5
|
+
|
|
6
|
+
import { StylesField } from '../../../controls-registry/styles-field';
|
|
7
|
+
|
|
8
|
+
export type Gap = 'column' | 'row';
|
|
9
|
+
|
|
10
|
+
export const GapControlField = () => {
|
|
11
|
+
return (
|
|
12
|
+
<Stack gap={ 1 }>
|
|
13
|
+
<StylesField bind={ 'gap' }>
|
|
14
|
+
<GapControl label={ __( 'Gaps', 'elementor' ) } />
|
|
15
|
+
</StylesField>
|
|
16
|
+
</Stack>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
@@ -11,6 +11,7 @@ import { DisplayField } from './display-field';
|
|
|
11
11
|
import { FlexDirectionField } from './flex-direction-field';
|
|
12
12
|
import { FlexOrderField } from './flex-order-field';
|
|
13
13
|
import { FlexSizeField } from './flex-size-field';
|
|
14
|
+
import { GapControlField } from './gap-control-field';
|
|
14
15
|
import { JustifyContentField } from './justify-content-field';
|
|
15
16
|
import { WrapField } from './wrap-field';
|
|
16
17
|
|
|
@@ -31,6 +32,7 @@ const FlexFields = () => (
|
|
|
31
32
|
<JustifyContentField />
|
|
32
33
|
<AlignItemsField />
|
|
33
34
|
<Divider />
|
|
35
|
+
<GapControlField />
|
|
34
36
|
<WrapField />
|
|
35
37
|
<Divider />
|
|
36
38
|
<ControlLabel>{ __( 'Flex child', 'elementor' ) }</ControlLabel>
|
|
@@ -12,7 +12,11 @@ const positionOptions = [
|
|
|
12
12
|
{ label: __( 'Fixed', 'elementor' ), value: 'fixed' },
|
|
13
13
|
];
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
type Props = {
|
|
16
|
+
onChange?: ( newValue: string | null, previousValue: string | null | undefined ) => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const PositionField = ( { onChange }: Props ) => {
|
|
16
20
|
return (
|
|
17
21
|
<StylesField bind="position">
|
|
18
22
|
<Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
|
|
@@ -20,7 +24,7 @@ export const PositionField = () => {
|
|
|
20
24
|
<ControlLabel>{ __( 'Position', 'elementor' ) }</ControlLabel>
|
|
21
25
|
</Grid>
|
|
22
26
|
<Grid item xs={ 6 }>
|
|
23
|
-
<SelectControl options={ positionOptions } />
|
|
27
|
+
<SelectControl options={ positionOptions } onChange={ onChange } />
|
|
24
28
|
</Grid>
|
|
25
29
|
</Grid>
|
|
26
30
|
</StylesField>
|
|
@@ -1,25 +1,65 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import type { PropValue, StringPropValue } from '@elementor/editor-props';
|
|
2
|
+
import { type StringPropValue } from '@elementor/editor-props';
|
|
4
3
|
import { Stack } from '@elementor/ui';
|
|
5
4
|
|
|
6
|
-
import {
|
|
5
|
+
import { useStyle } from '../../../contexts/style-context';
|
|
6
|
+
import { useSessionStorage } from '../../../hooks/use-session-storage';
|
|
7
7
|
import { useStylesField } from '../../../hooks/use-styles-field';
|
|
8
|
+
import { useStylesFields } from '../../../hooks/use-styles-fields';
|
|
8
9
|
import { DimensionsField } from './dimensions-field';
|
|
9
10
|
import { PositionField } from './position-field';
|
|
10
11
|
import { ZIndexField } from './z-index-field';
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
type DimensionValue =
|
|
14
|
+
| {
|
|
15
|
+
$$type: 'size';
|
|
16
|
+
value: number;
|
|
17
|
+
}
|
|
18
|
+
| undefined
|
|
19
|
+
| null;
|
|
20
|
+
|
|
21
|
+
type DimensionsValues = {
|
|
22
|
+
top: DimensionValue;
|
|
23
|
+
bottom: DimensionValue;
|
|
24
|
+
left: DimensionValue;
|
|
25
|
+
right: DimensionValue;
|
|
26
|
+
};
|
|
13
27
|
|
|
14
28
|
export const PositionSection = () => {
|
|
15
29
|
const [ positionValue ] = useStylesField< StringPropValue >( 'position' );
|
|
16
|
-
|
|
30
|
+
const [ dimensionsValues, setDimensionsValues ] = useStylesFields< DimensionsValues >( [
|
|
31
|
+
'top',
|
|
32
|
+
'bottom',
|
|
33
|
+
'left',
|
|
34
|
+
'right',
|
|
35
|
+
] );
|
|
36
|
+
|
|
37
|
+
const [ dimensionsValuesFromHistory, updateDimensionsHistory, clearDimensionsHistory ] = usePersistDimensions();
|
|
38
|
+
|
|
39
|
+
const onPositionChange = ( newPosition: string | null, previousPosition: string | null | undefined ) => {
|
|
40
|
+
if ( newPosition === 'static' ) {
|
|
41
|
+
if ( dimensionsValues ) {
|
|
42
|
+
updateDimensionsHistory( dimensionsValues );
|
|
43
|
+
setDimensionsValues( {
|
|
44
|
+
top: undefined,
|
|
45
|
+
bottom: undefined,
|
|
46
|
+
left: undefined,
|
|
47
|
+
right: undefined,
|
|
48
|
+
} );
|
|
49
|
+
}
|
|
50
|
+
} else if ( previousPosition === 'static' ) {
|
|
51
|
+
if ( dimensionsValuesFromHistory ) {
|
|
52
|
+
setDimensionsValues( dimensionsValuesFromHistory );
|
|
53
|
+
clearDimensionsHistory();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
17
57
|
|
|
18
58
|
const isNotStatic = positionValue && positionValue?.value !== 'static';
|
|
19
59
|
|
|
20
60
|
return (
|
|
21
61
|
<Stack gap={ 1.5 }>
|
|
22
|
-
<PositionField />
|
|
62
|
+
<PositionField onChange={ onPositionChange } />
|
|
23
63
|
{ isNotStatic ? (
|
|
24
64
|
<>
|
|
25
65
|
<DimensionsField />
|
|
@@ -30,29 +70,10 @@ export const PositionSection = () => {
|
|
|
30
70
|
);
|
|
31
71
|
};
|
|
32
72
|
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
( newPositionValue: PropValue, previousPosition: PropValue ) => {
|
|
38
|
-
if ( ! dimensionsHistory ) {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const { saveStylePropsHistory, updateStylePropsFromHistory, clearCurrentStyleProps } = dimensionsHistory;
|
|
43
|
-
|
|
44
|
-
if ( newPositionValue === 'static' ) {
|
|
45
|
-
saveStylePropsHistory();
|
|
46
|
-
clearCurrentStyleProps();
|
|
47
|
-
} else if ( previousPosition === 'static' ) {
|
|
48
|
-
updateStylePropsFromHistory();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
previousPosition = newPositionValue;
|
|
52
|
-
},
|
|
53
|
-
[ dimensionsHistory ]
|
|
54
|
-
);
|
|
73
|
+
const usePersistDimensions = () => {
|
|
74
|
+
const { id: styleDefID, meta } = useStyle();
|
|
75
|
+
const styleVariantPath = `styles/${ styleDefID }/${ meta.breakpoint || 'desktop' }/${ meta.state || 'null' }`;
|
|
76
|
+
const dimensionsPath = `${ styleVariantPath }/dimensions`;
|
|
55
77
|
|
|
56
|
-
|
|
57
|
-
registerChangeListener?.( onPositionChange );
|
|
78
|
+
return useSessionStorage< DimensionsValues >( dimensionsPath );
|
|
58
79
|
};
|
|
@@ -3,7 +3,7 @@ import { useState } from 'react';
|
|
|
3
3
|
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
|
-
import {
|
|
6
|
+
import { type StyleDefinition } from '@elementor/editor-styles';
|
|
7
7
|
import { Divider } from '@elementor/ui';
|
|
8
8
|
import { __ } from '@wordpress/i18n';
|
|
9
9
|
|
|
@@ -70,9 +70,7 @@ function useActiveStyleDefId( currentClassesProp: PropKey ) {
|
|
|
70
70
|
|
|
71
71
|
const fallback = useFirstElementStyleDef( currentClassesProp );
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return [ activeStyledDefId || fallback?.id || newId, setActiveStyledDefId ] as const;
|
|
73
|
+
return [ activeStyledDefId || fallback?.id || null, setActiveStyledDefId ] as const;
|
|
76
74
|
}
|
|
77
75
|
|
|
78
76
|
function useFirstElementStyleDef( currentClassesProp: PropKey ) {
|
|
@@ -84,13 +82,6 @@ function useFirstElementStyleDef( currentClassesProp: PropKey ) {
|
|
|
84
82
|
return Object.values( stylesDefs ).find( ( styleDef ) => classesIds.includes( styleDef.id ) );
|
|
85
83
|
}
|
|
86
84
|
|
|
87
|
-
function useGeneratedId() {
|
|
88
|
-
const { element } = useElement();
|
|
89
|
-
const stylesDefs = useElementStyles( element.id );
|
|
90
|
-
|
|
91
|
-
return generateId( `e-${ element.id }-`, Object.keys( stylesDefs ) );
|
|
92
|
-
}
|
|
93
|
-
|
|
94
85
|
function useCurrentClassesProp(): string {
|
|
95
86
|
const { elementType } = useElement();
|
|
96
87
|
|
|
@@ -3,8 +3,8 @@ import { createContext, type Dispatch, type PropsWithChildren, useContext } from
|
|
|
3
3
|
import { type StyleDefinition, type StyleVariant } from '@elementor/editor-styles';
|
|
4
4
|
|
|
5
5
|
type ContextValue = {
|
|
6
|
-
id: StyleDefinition[ 'id' ];
|
|
7
|
-
setId: Dispatch< StyleDefinition[ 'id' ] >;
|
|
6
|
+
id: StyleDefinition[ 'id' ] | null;
|
|
7
|
+
setId: Dispatch< StyleDefinition[ 'id' ] | null >;
|
|
8
8
|
meta: StyleVariant[ 'meta' ];
|
|
9
9
|
};
|
|
10
10
|
|
|
@@ -24,7 +24,7 @@ import { __ } from '@wordpress/i18n';
|
|
|
24
24
|
|
|
25
25
|
import { Control as BaseControl } from '../../controls-registry/control';
|
|
26
26
|
import { type ControlType, getControlByType } from '../../controls-registry/controls-registry';
|
|
27
|
-
import {
|
|
27
|
+
import { usePersistDynamicValue } from '../../hooks/use-persist-dynamic-value';
|
|
28
28
|
import { DynamicControl } from '../dynamic-control';
|
|
29
29
|
import { useDynamicTag } from '../hooks/use-dynamic-tag';
|
|
30
30
|
import { type DynamicTag } from '../types';
|
|
@@ -36,7 +36,7 @@ const SIZE = 'tiny';
|
|
|
36
36
|
export const DynamicSelectionControl = () => {
|
|
37
37
|
const { setValue: setAnyValue } = useBoundProp();
|
|
38
38
|
const { bind, value } = useBoundProp( dynamicPropTypeUtil );
|
|
39
|
-
const
|
|
39
|
+
const [ propValueFromHistory ] = usePersistDynamicValue( bind );
|
|
40
40
|
const { name: tagName = '' } = value;
|
|
41
41
|
|
|
42
42
|
const selectionPopoverId = useId();
|
|
@@ -45,9 +45,7 @@ export const DynamicSelectionControl = () => {
|
|
|
45
45
|
const dynamicTag = useDynamicTag( bind, tagName );
|
|
46
46
|
|
|
47
47
|
const removeDynamicTag = () => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
setAnyValue( propValue ?? null );
|
|
48
|
+
setAnyValue( propValueFromHistory ?? null );
|
|
51
49
|
};
|
|
52
50
|
|
|
53
51
|
if ( ! dynamicTag ) {
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
} from '@elementor/ui';
|
|
18
18
|
import { __ } from '@wordpress/i18n';
|
|
19
19
|
|
|
20
|
-
import {
|
|
20
|
+
import { usePersistDynamicValue } from '../../hooks/use-persist-dynamic-value';
|
|
21
21
|
import { usePropDynamicTags } from '../hooks/use-prop-dynamic-tags';
|
|
22
22
|
import { getAtomicDynamicTags } from '../sync/get-atomic-dynamic-tags';
|
|
23
23
|
import { dynamicPropTypeUtil } from '../utils';
|
|
@@ -40,11 +40,11 @@ export const DynamicSelection = ( { onSelect }: DynamicSelectionProps ) => {
|
|
|
40
40
|
const { groups: dynamicGroups } = getAtomicDynamicTags() || {};
|
|
41
41
|
|
|
42
42
|
const { value: anyValue } = useBoundProp();
|
|
43
|
-
const { bind, value:
|
|
43
|
+
const { bind, value: dynamicValue, setValue } = useBoundProp( dynamicPropTypeUtil );
|
|
44
44
|
|
|
45
|
-
const
|
|
45
|
+
const [ , updatePropValueHistory ] = usePersistDynamicValue( bind );
|
|
46
46
|
|
|
47
|
-
const isCurrentValueDynamic = !!
|
|
47
|
+
const isCurrentValueDynamic = !! dynamicValue;
|
|
48
48
|
|
|
49
49
|
const options = useFilteredOptions( bind, searchValue );
|
|
50
50
|
|
|
@@ -54,7 +54,7 @@ export const DynamicSelection = ( { onSelect }: DynamicSelectionProps ) => {
|
|
|
54
54
|
|
|
55
55
|
const handleSetDynamicTag = ( value: string ) => {
|
|
56
56
|
if ( ! isCurrentValueDynamic ) {
|
|
57
|
-
updatePropValueHistory(
|
|
57
|
+
updatePropValueHistory( anyValue );
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
setValue( { name: value, settings: {} } );
|
|
@@ -90,7 +90,7 @@ export const DynamicSelection = ( { onSelect }: DynamicSelectionProps ) => {
|
|
|
90
90
|
{ dynamicGroups?.[ category ]?.title || category }
|
|
91
91
|
</ListSubheader>
|
|
92
92
|
{ items.map( ( { value, label: tagLabel } ) => {
|
|
93
|
-
const isSelected = isCurrentValueDynamic && value ===
|
|
93
|
+
const isSelected = isCurrentValueDynamic && value === dynamicValue?.name;
|
|
94
94
|
|
|
95
95
|
return (
|
|
96
96
|
<MenuItem
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type PropValue } from '@elementor/editor-props';
|
|
2
|
+
|
|
3
|
+
import { useElement } from '../contexts/element-context';
|
|
4
|
+
import { type DynamicPropValue } from '../dynamics/types';
|
|
5
|
+
import { useSessionStorage } from './use-session-storage';
|
|
6
|
+
|
|
7
|
+
export const usePersistDynamicValue = < T extends DynamicPropValue | PropValue >( propKey: string ) => {
|
|
8
|
+
const { element } = useElement();
|
|
9
|
+
const prefixedKey = `dynamic/non-dynamic-values-history/${ element.id }/${ propKey }`;
|
|
10
|
+
return useSessionStorage< T >( prefixedKey );
|
|
11
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { getSessionStorageItem, removeSessionStorageItem, setSessionStorageItem } from '@elementor/utils';
|
|
3
|
+
|
|
4
|
+
export const useSessionStorage = < T >( key: string ) => {
|
|
5
|
+
const prefixedKey = `elementor/${ key }`;
|
|
6
|
+
|
|
7
|
+
const [ value, setValue ] = useState< T | null >();
|
|
8
|
+
|
|
9
|
+
useEffect( () => {
|
|
10
|
+
return subscribeToSessionStorage< T | null >( prefixedKey, ( newValue ) => {
|
|
11
|
+
setValue( newValue ?? null );
|
|
12
|
+
} );
|
|
13
|
+
}, [ prefixedKey ] );
|
|
14
|
+
|
|
15
|
+
const saveValue = ( newValue: T ) => {
|
|
16
|
+
setSessionStorageItem( prefixedKey, newValue );
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const removeValue = () => {
|
|
20
|
+
removeSessionStorageItem( prefixedKey );
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
return [ value, saveValue, removeValue ] as const;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const subscribeToSessionStorage = < T >( key: string, subscriber: ( value: T ) => void ) => {
|
|
27
|
+
subscriber( getSessionStorageItem( key ) as T );
|
|
28
|
+
|
|
29
|
+
const abortController = new AbortController();
|
|
30
|
+
|
|
31
|
+
window.addEventListener(
|
|
32
|
+
'storage',
|
|
33
|
+
( e ) => {
|
|
34
|
+
if ( e.key !== key || e.storageArea !== sessionStorage ) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
subscriber( getSessionStorageItem( key ) as T );
|
|
39
|
+
},
|
|
40
|
+
{ signal: abortController.signal }
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
return () => {
|
|
44
|
+
abortController.abort();
|
|
45
|
+
};
|
|
46
|
+
};
|
|
@@ -1,51 +1,17 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react';
|
|
2
|
-
import { updateStyle, useElementStyleProp } from '@elementor/editor-elements';
|
|
3
1
|
import type { PropKey, PropValue } from '@elementor/editor-props';
|
|
4
2
|
|
|
5
|
-
import {
|
|
6
|
-
import { useElement } from '../contexts/element-context';
|
|
7
|
-
import { useStyle } from '../contexts/style-context';
|
|
3
|
+
import { useStylesFields } from './use-styles-fields';
|
|
8
4
|
|
|
9
|
-
export
|
|
10
|
-
propName:
|
|
11
|
-
): [
|
|
12
|
-
T | null,
|
|
13
|
-
( newValue: T ) => void,
|
|
14
|
-
( callback: ( newValue: T | null, previousValue: T | null ) => void ) => void,
|
|
15
|
-
] => {
|
|
16
|
-
const { element } = useElement();
|
|
17
|
-
const { id, meta } = useStyle();
|
|
18
|
-
const classesProp = useClassesProp();
|
|
19
|
-
const previousValue = useRef< T | null >( null );
|
|
20
|
-
const onChangeCallbacks = useRef< Set< ( newValue: T | null, previousValue: T | null ) => void > >( new Set() );
|
|
5
|
+
export function useStylesField< T extends PropValue >( propName: PropKey ): [ T | null, ( newValue: T ) => void ] {
|
|
6
|
+
const [ values, setValues ] = useStylesFields< { [ k: typeof propName ]: T } >( [ propName ] );
|
|
21
7
|
|
|
22
|
-
const value =
|
|
23
|
-
elementID: element.id,
|
|
24
|
-
styleDefID: id,
|
|
25
|
-
meta,
|
|
26
|
-
propName,
|
|
27
|
-
} );
|
|
8
|
+
const value = values?.[ propName ] ?? null;
|
|
28
9
|
|
|
29
10
|
const setValue = ( newValue: T ) => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
styleDefID: id,
|
|
33
|
-
props: { [ propName ]: newValue },
|
|
34
|
-
meta,
|
|
35
|
-
bind: classesProp,
|
|
11
|
+
setValues( {
|
|
12
|
+
[ propName ]: newValue,
|
|
36
13
|
} );
|
|
37
14
|
};
|
|
38
15
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
useEffect( () => {
|
|
44
|
-
onChangeCallbacks.current.forEach( ( cb ) => {
|
|
45
|
-
cb( value, previousValue.current );
|
|
46
|
-
} );
|
|
47
|
-
previousValue.current = value;
|
|
48
|
-
}, [ value ] );
|
|
49
|
-
|
|
50
|
-
return [ value, setValue, registerChangeListener ];
|
|
51
|
-
};
|
|
16
|
+
return [ value, setValue ];
|
|
17
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { updateStyle, useElementStyleProps } from '@elementor/editor-elements';
|
|
2
|
+
import type { Props } from '@elementor/editor-props';
|
|
3
|
+
import { __ } from '@wordpress/i18n';
|
|
4
|
+
|
|
5
|
+
import { useClassesProp } from '../contexts/classes-prop-context';
|
|
6
|
+
import { useElement } from '../contexts/element-context';
|
|
7
|
+
import { useStyle } from '../contexts/style-context';
|
|
8
|
+
|
|
9
|
+
export function useStylesFields< T extends Props >( propNames: ( keyof T & string )[] ) {
|
|
10
|
+
const { element } = useElement();
|
|
11
|
+
const { id, meta } = useStyle();
|
|
12
|
+
const classesProp = useClassesProp();
|
|
13
|
+
|
|
14
|
+
const value = useElementStyleProps( {
|
|
15
|
+
elementID: element.id,
|
|
16
|
+
styleDefID: id,
|
|
17
|
+
meta,
|
|
18
|
+
propNames,
|
|
19
|
+
} );
|
|
20
|
+
|
|
21
|
+
const setValue = ( newValues: T ) => {
|
|
22
|
+
updateStyle( {
|
|
23
|
+
elementID: element.id,
|
|
24
|
+
styleDefID: id,
|
|
25
|
+
props: newValues,
|
|
26
|
+
meta,
|
|
27
|
+
bind: classesProp,
|
|
28
|
+
label: __( 'local', 'elementor' ),
|
|
29
|
+
} );
|
|
30
|
+
};
|
|
31
|
+
return [ value, setValue ] as const;
|
|
32
|
+
}
|