@elementor/editor-editing-panel 1.48.0 → 3.32.0-20
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 +21 -0
- package/dist/index.d.mts +78 -47
- package/dist/index.d.ts +78 -47
- package/dist/index.js +1770 -1406
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1550 -1157
- package/dist/index.mjs.map +1 -1
- package/package.json +22 -22
- package/src/components/css-classes/css-class-convert-local.tsx +77 -0
- package/src/components/css-classes/css-class-item.tsx +18 -3
- package/src/components/css-classes/css-class-menu.tsx +10 -3
- package/src/components/css-classes/css-class-selector.tsx +10 -2
- package/src/components/css-classes/local-class-sub-menu.tsx +23 -0
- package/src/components/css-classes/use-apply-and-unapply-class.ts +7 -50
- package/src/components/css-classes/use-can-convert-local-class-to-global.ts +22 -0
- package/src/components/custom-css.tsx +21 -0
- package/src/components/editing-panel-tabs.tsx +1 -5
- package/src/components/popover-body.tsx +12 -0
- package/src/components/section.tsx +1 -5
- package/src/components/settings-tab.tsx +6 -15
- package/src/components/style-sections/effects-section/effects-section.tsx +32 -19
- package/src/components/style-sections/layout-section/display-field.tsx +11 -20
- package/src/components/style-sections/layout-section/flex-order-field.tsx +6 -1
- package/src/components/style-sections/layout-section/flex-size-field.tsx +86 -52
- package/src/components/style-sections/position-section/offset-field.tsx +2 -2
- package/src/components/style-sections/position-section/position-section.tsx +2 -8
- package/src/components/style-sections/size-section/size-section.tsx +16 -31
- package/src/components/style-sections/typography-section/typography-section.tsx +2 -19
- package/src/components/style-tab-collapsible-content.tsx +1 -5
- package/src/components/style-tab-section.tsx +1 -5
- package/src/components/style-tab.tsx +15 -2
- package/src/controls-actions.ts +1 -1
- package/src/controls-registry/conditional-field.tsx +26 -0
- package/src/controls-registry/control.tsx +2 -2
- package/src/controls-registry/controls-registry.tsx +44 -3
- package/src/controls-registry/settings-field.tsx +33 -45
- package/src/controls-registry/styles-field.tsx +14 -14
- package/src/dynamics/components/dynamic-selection-control.tsx +24 -16
- package/src/dynamics/components/dynamic-selection.tsx +32 -36
- package/src/errors.ts +10 -0
- package/src/hooks/use-custom-css.ts +184 -0
- package/src/hooks/use-state-by-element.ts +1 -4
- package/src/hooks/use-styles-fields.ts +129 -106
- package/src/index.ts +9 -10
- package/src/init.ts +2 -5
- package/src/popover-action.tsx +36 -15
- package/src/reset-style-props.tsx +2 -6
- package/src/styles-inheritance/components/infotip/value-component.tsx +1 -0
- package/src/styles-inheritance/components/styles-inheritance-indicator.tsx +6 -23
- package/src/styles-inheritance/components/styles-inheritance-infotip.tsx +18 -9
- package/src/styles-inheritance/consts.ts +0 -4
- package/src/styles-inheritance/init.ts +1 -4
- package/src/styles-inheritance/transformers/background-color-overlay-transformer.tsx +5 -1
- package/src/styles-inheritance/transformers/background-gradient-overlay-transformer.tsx +3 -3
- package/src/styles-inheritance/transformers/background-image-overlay-transformer.tsx +2 -1
- package/src/utils/get-styles-provider-color.ts +8 -0
- package/src/utils/prop-dependency-utils.ts +156 -0
- package/src/components/popover-scrollable-content.tsx +0 -12
- package/src/components/style-sections/size-section/object-position-field.tsx +0 -15
- package/src/sync/experiments-flags.ts +0 -5
- /package/src/components/style-sections/{layout-section → effects-section}/opacity-control-field.tsx +0 -0
|
@@ -3,7 +3,7 @@ import type { ComponentProps } from 'react';
|
|
|
3
3
|
|
|
4
4
|
import { useElement } from '../contexts/element-context';
|
|
5
5
|
import { ControlTypeNotFoundError } from '../errors';
|
|
6
|
-
import { type ControlType, type ControlTypes
|
|
6
|
+
import { controlsRegistry, type ControlType, type ControlTypes } from './controls-registry';
|
|
7
7
|
|
|
8
8
|
type IsRequired< T, K extends keyof T > = object extends Pick< T, K > ? false : true;
|
|
9
9
|
|
|
@@ -24,7 +24,7 @@ type ControlProps< T extends ControlType > = AnyPropertyRequired< ComponentProps
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
export const Control = < T extends ControlType >( { props, type }: ControlProps< T > ) => {
|
|
27
|
-
const ControlByType =
|
|
27
|
+
const ControlByType = controlsRegistry.get( type );
|
|
28
28
|
const { element } = useElement();
|
|
29
29
|
|
|
30
30
|
if ( ! ControlByType ) {
|
|
@@ -24,6 +24,8 @@ import {
|
|
|
24
24
|
stringPropTypeUtil,
|
|
25
25
|
} from '@elementor/editor-props';
|
|
26
26
|
|
|
27
|
+
import { ControlTypeAlreadyRegisteredError, ControlTypeNotRegisteredError } from '../errors';
|
|
28
|
+
|
|
27
29
|
type ControlRegistry = Record<
|
|
28
30
|
string,
|
|
29
31
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -50,8 +52,47 @@ export type ControlTypes = {
|
|
|
50
52
|
[ key in ControlType ]: ( typeof controlTypes )[ key ][ 'component' ];
|
|
51
53
|
};
|
|
52
54
|
|
|
53
|
-
|
|
55
|
+
class ControlsRegistry {
|
|
56
|
+
constructor( private readonly controlsRegistry: ControlRegistry = controlTypes ) {
|
|
57
|
+
this.controlsRegistry = controlsRegistry;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get( type: ControlType ): ControlComponent {
|
|
61
|
+
return this.controlsRegistry[ type ]?.component;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
getLayout( type: ControlType ) {
|
|
65
|
+
return this.controlsRegistry[ type ]?.layout;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
getPropTypeUtil( type: ControlType ) {
|
|
69
|
+
return this.controlsRegistry[ type ]?.propTypeUtil;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
registry(): ControlRegistry {
|
|
73
|
+
return this.controlsRegistry;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
register(
|
|
77
|
+
type: string,
|
|
78
|
+
component: ControlComponent,
|
|
79
|
+
layout: ControlLayout,
|
|
80
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
81
|
+
propTypeUtil?: PropTypeUtil< string, any >
|
|
82
|
+
) {
|
|
83
|
+
if ( this.controlsRegistry[ type ] ) {
|
|
84
|
+
throw new ControlTypeAlreadyRegisteredError( { context: { controlType: type } } );
|
|
85
|
+
}
|
|
86
|
+
this.controlsRegistry[ type ] = { component, layout, propTypeUtil };
|
|
87
|
+
}
|
|
54
88
|
|
|
55
|
-
|
|
89
|
+
unregister( type: string ) {
|
|
90
|
+
if ( ! this.controlsRegistry[ type ] ) {
|
|
91
|
+
throw new ControlTypeNotRegisteredError( { context: { controlType: type } } );
|
|
92
|
+
}
|
|
93
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
94
|
+
delete this.controlsRegistry[ type ];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
56
97
|
|
|
57
|
-
export const
|
|
98
|
+
export const controlsRegistry = new ControlsRegistry();
|
|
@@ -5,50 +5,57 @@ import { setDocumentModifiedStatus } from '@elementor/editor-documents';
|
|
|
5
5
|
import {
|
|
6
6
|
type ElementID,
|
|
7
7
|
getElementLabel,
|
|
8
|
-
|
|
8
|
+
getElementSettings,
|
|
9
9
|
updateElementSettings,
|
|
10
10
|
useElementSettings,
|
|
11
11
|
} from '@elementor/editor-elements';
|
|
12
|
-
import { type PropKey, type PropType, type PropValue
|
|
13
|
-
import {
|
|
12
|
+
import { isDependencyMet, type PropKey, type Props, type PropType, type PropValue } from '@elementor/editor-props';
|
|
13
|
+
import { undoable } from '@elementor/editor-v1-adapters';
|
|
14
14
|
import { __ } from '@wordpress/i18n';
|
|
15
15
|
|
|
16
16
|
import { useElement } from '../contexts/element-context';
|
|
17
|
-
import {
|
|
17
|
+
import { extractOrderedDependencies, updateValues, type Values } from '../utils/prop-dependency-utils';
|
|
18
18
|
import { createTopLevelOjectType } from './create-top-level-object-type';
|
|
19
19
|
|
|
20
|
-
type
|
|
20
|
+
type SettingsFieldProps = {
|
|
21
21
|
bind: PropKey;
|
|
22
22
|
propDisplayName: string;
|
|
23
23
|
children: React.ReactNode;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
const { element, elementType } = useElement();
|
|
26
|
+
const HISTORY_DEBOUNCE_WAIT = 800;
|
|
28
27
|
|
|
29
|
-
|
|
28
|
+
export const SettingsField = ( { bind, children, propDisplayName }: SettingsFieldProps ) => {
|
|
29
|
+
const {
|
|
30
|
+
element: { id: elementId },
|
|
31
|
+
elementType: { propsSchema, dependenciesPerTargetMapping = {} },
|
|
32
|
+
} = useElement();
|
|
30
33
|
|
|
31
|
-
const
|
|
34
|
+
const elementSettingValues = useElementSettings< PropValue >( elementId, Object.keys( propsSchema ) ) as Values;
|
|
32
35
|
|
|
33
|
-
const
|
|
36
|
+
const value = { [ bind ]: elementSettingValues?.[ bind ] ?? null };
|
|
37
|
+
|
|
38
|
+
const propType = createTopLevelOjectType( { schema: propsSchema } );
|
|
34
39
|
|
|
35
40
|
const undoableUpdateElementProp = useUndoableUpdateElementProp( {
|
|
36
|
-
|
|
37
|
-
elementId: element.id,
|
|
41
|
+
elementId,
|
|
38
42
|
propDisplayName,
|
|
39
43
|
} );
|
|
40
44
|
|
|
41
|
-
const setValue = ( newValue:
|
|
42
|
-
const
|
|
45
|
+
const setValue = ( newValue: Values ) => {
|
|
46
|
+
const dependents = extractOrderedDependencies(
|
|
47
|
+
bind,
|
|
48
|
+
propsSchema,
|
|
49
|
+
elementSettingValues,
|
|
50
|
+
dependenciesPerTargetMapping
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const settings = updateValues( newValue, dependents, propsSchema, elementSettingValues );
|
|
43
54
|
|
|
44
|
-
|
|
45
|
-
undoableUpdateElementProp( { newValue } );
|
|
46
|
-
} else {
|
|
47
|
-
updateElementSettings( { id: element.id, props: newValue } );
|
|
48
|
-
}
|
|
55
|
+
undoableUpdateElementProp( settings );
|
|
49
56
|
};
|
|
50
57
|
|
|
51
|
-
const isDisabled = ( prop: PropType ) =>
|
|
58
|
+
const isDisabled = ( prop: PropType ) => ! isDependencyMet( prop?.dependencies, elementSettingValues );
|
|
52
59
|
|
|
53
60
|
return (
|
|
54
61
|
<PropProvider propType={ propType } value={ value } setValue={ setValue } isDisabled={ isDisabled }>
|
|
@@ -57,43 +64,23 @@ export const SettingsField = ( { bind, children, propDisplayName }: Props ) => {
|
|
|
57
64
|
);
|
|
58
65
|
};
|
|
59
66
|
|
|
60
|
-
function getDisableState( propType: PropType, elementValues: PropValue ): boolean | undefined {
|
|
61
|
-
const disablingDependencies = propType.dependencies?.filter( ( { effect } ) => effect === 'disable' ) || [];
|
|
62
|
-
|
|
63
|
-
if ( ! disablingDependencies.length ) {
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if ( disablingDependencies.length > 1 ) {
|
|
68
|
-
throw new Error( 'Multiple disabling dependencies are not supported.' );
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return shouldApplyEffect( disablingDependencies[ 0 ], elementValues );
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
type UndoableUpdateElementSettingsArgs = {
|
|
75
|
-
newValue: Record< string, PropValue >;
|
|
76
|
-
};
|
|
77
|
-
|
|
78
67
|
function useUndoableUpdateElementProp( {
|
|
79
|
-
propKey,
|
|
80
68
|
elementId,
|
|
81
69
|
propDisplayName,
|
|
82
70
|
}: {
|
|
83
|
-
propKey: PropKey;
|
|
84
71
|
elementId: ElementID;
|
|
85
72
|
propDisplayName: string;
|
|
86
73
|
} ) {
|
|
87
74
|
return useMemo( () => {
|
|
88
75
|
return undoable(
|
|
89
76
|
{
|
|
90
|
-
do: (
|
|
91
|
-
const prevPropValue =
|
|
77
|
+
do: ( newSettings: Props ) => {
|
|
78
|
+
const prevPropValue = getElementSettings( elementId, Object.keys( newSettings ) ) as Props;
|
|
92
79
|
|
|
93
|
-
updateElementSettings( { id: elementId, props:
|
|
80
|
+
updateElementSettings( { id: elementId, props: newSettings as Props, withHistory: false } );
|
|
94
81
|
setDocumentModifiedStatus( true );
|
|
95
82
|
|
|
96
|
-
return
|
|
83
|
+
return prevPropValue;
|
|
97
84
|
},
|
|
98
85
|
|
|
99
86
|
undo: ( {}, prevProps ) => {
|
|
@@ -104,7 +91,8 @@ function useUndoableUpdateElementProp( {
|
|
|
104
91
|
title: getElementLabel( elementId ),
|
|
105
92
|
// translators: %s is the name of the property that was edited.
|
|
106
93
|
subtitle: __( '%s edited', 'elementor' ).replace( '%s', propDisplayName ),
|
|
94
|
+
debounce: { wait: HISTORY_DEBOUNCE_WAIT },
|
|
107
95
|
}
|
|
108
96
|
);
|
|
109
|
-
}, [
|
|
97
|
+
}, [ elementId, propDisplayName ] );
|
|
110
98
|
}
|
|
@@ -2,11 +2,11 @@ import * as React from 'react';
|
|
|
2
2
|
import { ControlAdornmentsProvider, PropKeyProvider, PropProvider } from '@elementor/editor-controls';
|
|
3
3
|
import { type PropKey, type PropValue } from '@elementor/editor-props';
|
|
4
4
|
import { getStylesSchema } from '@elementor/editor-styles';
|
|
5
|
-
import { isExperimentActive } from '@elementor/editor-v1-adapters';
|
|
6
5
|
|
|
7
6
|
import { useStylesInheritanceChain } from '../contexts/styles-inheritance-context';
|
|
8
7
|
import { useStylesField } from '../hooks/use-styles-field';
|
|
9
8
|
import { StylesInheritanceIndicator } from '../styles-inheritance/components/styles-inheritance-indicator';
|
|
9
|
+
import { ConditionalField } from './conditional-field';
|
|
10
10
|
import { createTopLevelOjectType } from './create-top-level-object-type';
|
|
11
11
|
|
|
12
12
|
export type StylesFieldProps = {
|
|
@@ -16,25 +16,23 @@ export type StylesFieldProps = {
|
|
|
16
16
|
propDisplayName: string;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
export const StylesField = ( { bind,
|
|
20
|
-
const
|
|
21
|
-
history: { propDisplayName },
|
|
22
|
-
} );
|
|
19
|
+
export const StylesField = ( { bind, propDisplayName, children }: StylesFieldProps ) => {
|
|
20
|
+
const stylesSchema = getStylesSchema();
|
|
23
21
|
|
|
24
|
-
const isVersion331Active = isExperimentActive( 'e_v_3_31' );
|
|
25
22
|
const stylesInheritanceChain = useStylesInheritanceChain( [ bind ] );
|
|
26
23
|
|
|
27
|
-
const
|
|
24
|
+
const { value, canEdit, ...fields } = useStylesField( bind, { history: { propDisplayName } } );
|
|
28
25
|
|
|
29
26
|
const propType = createTopLevelOjectType( { schema: stylesSchema } );
|
|
30
27
|
|
|
31
|
-
const values = { [ bind ]: value };
|
|
32
28
|
const [ actualValue ] = stylesInheritanceChain;
|
|
29
|
+
|
|
33
30
|
const placeholderValues = {
|
|
34
|
-
[ bind ]:
|
|
31
|
+
[ bind ]: actualValue?.value,
|
|
35
32
|
};
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
|
|
34
|
+
const setValue = ( newValue: Record< string, PropValue > ) => {
|
|
35
|
+
fields.setValue( newValue[ bind ] );
|
|
38
36
|
};
|
|
39
37
|
|
|
40
38
|
return (
|
|
@@ -48,12 +46,14 @@ export const StylesField = ( { bind, placeholder, propDisplayName, children }: S
|
|
|
48
46
|
>
|
|
49
47
|
<PropProvider
|
|
50
48
|
propType={ propType }
|
|
51
|
-
value={
|
|
52
|
-
setValue={
|
|
49
|
+
value={ { [ bind ]: value } }
|
|
50
|
+
setValue={ setValue }
|
|
53
51
|
placeholder={ placeholderValues }
|
|
54
52
|
isDisabled={ () => ! canEdit }
|
|
55
53
|
>
|
|
56
|
-
<PropKeyProvider bind={ bind }>
|
|
54
|
+
<PropKeyProvider bind={ bind }>
|
|
55
|
+
<ConditionalField>{ children }</ConditionalField>
|
|
56
|
+
</PropKeyProvider>
|
|
57
57
|
</PropProvider>
|
|
58
58
|
</ControlAdornmentsProvider>
|
|
59
59
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { ControlFormLabel, useBoundProp } from '@elementor/editor-controls';
|
|
3
3
|
import type { Control, ControlsSection } from '@elementor/editor-elements';
|
|
4
|
-
import { PopoverHeader
|
|
4
|
+
import { PopoverHeader } from '@elementor/editor-ui';
|
|
5
5
|
import { DatabaseIcon, SettingsIcon, XIcon } from '@elementor/icons';
|
|
6
6
|
import {
|
|
7
7
|
bindPopover,
|
|
@@ -21,8 +21,9 @@ import {
|
|
|
21
21
|
} from '@elementor/ui';
|
|
22
22
|
import { __ } from '@wordpress/i18n';
|
|
23
23
|
|
|
24
|
+
import { PopoverBody } from '../../components/popover-body';
|
|
24
25
|
import { Control as BaseControl } from '../../controls-registry/control';
|
|
25
|
-
import { type ControlType
|
|
26
|
+
import { controlsRegistry, type ControlType } from '../../controls-registry/controls-registry';
|
|
26
27
|
import { usePersistDynamicValue } from '../../hooks/use-persist-dynamic-value';
|
|
27
28
|
import { DynamicControl } from '../dynamic-control';
|
|
28
29
|
import { useDynamicTag } from '../hooks/use-dynamic-tag';
|
|
@@ -82,9 +83,9 @@ export const DynamicSelectionControl = () => {
|
|
|
82
83
|
} }
|
|
83
84
|
{ ...bindPopover( selectionPopoverState ) }
|
|
84
85
|
>
|
|
85
|
-
<
|
|
86
|
+
<PopoverBody>
|
|
86
87
|
<DynamicSelection close={ selectionPopoverState.close } />
|
|
87
|
-
</
|
|
88
|
+
</PopoverBody>
|
|
88
89
|
</Popover>
|
|
89
90
|
</Box>
|
|
90
91
|
);
|
|
@@ -107,18 +108,21 @@ export const DynamicSettingsPopover = ( { dynamicTag }: { dynamicTag: DynamicTag
|
|
|
107
108
|
<Popover
|
|
108
109
|
disablePortal
|
|
109
110
|
disableScrollLock
|
|
110
|
-
anchorOrigin={ { vertical: 'bottom', horizontal: '
|
|
111
|
+
anchorOrigin={ { vertical: 'bottom', horizontal: 'right' } }
|
|
112
|
+
transformOrigin={ { vertical: 'top', horizontal: 'right' } }
|
|
111
113
|
PaperProps={ {
|
|
112
|
-
sx: { my:
|
|
114
|
+
sx: { my: 1 },
|
|
113
115
|
} }
|
|
114
116
|
{ ...bindPopover( popupState ) }
|
|
115
117
|
>
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
118
|
+
<PopoverBody>
|
|
119
|
+
<PopoverHeader
|
|
120
|
+
title={ dynamicTag.label }
|
|
121
|
+
onClose={ popupState.close }
|
|
122
|
+
icon={ <DatabaseIcon fontSize={ SIZE } /> }
|
|
123
|
+
/>
|
|
124
|
+
<DynamicSettings controls={ dynamicTag.atomic_controls } />
|
|
125
|
+
</PopoverBody>
|
|
122
126
|
</Popover>
|
|
123
127
|
</>
|
|
124
128
|
);
|
|
@@ -134,7 +138,7 @@ const DynamicSettings = ( { controls }: { controls: DynamicTag[ 'atomic_controls
|
|
|
134
138
|
}
|
|
135
139
|
|
|
136
140
|
return (
|
|
137
|
-
|
|
141
|
+
<>
|
|
138
142
|
<Tabs size="small" variant="fullWidth" { ...getTabsProps() }>
|
|
139
143
|
{ tabs.map( ( { value }, index ) => (
|
|
140
144
|
<Tab key={ index } label={ value.label } sx={ { px: 1, py: 0.5 } } { ...getTabProps( index ) } />
|
|
@@ -144,7 +148,11 @@ const DynamicSettings = ( { controls }: { controls: DynamicTag[ 'atomic_controls
|
|
|
144
148
|
|
|
145
149
|
{ tabs.map( ( { value }, index ) => {
|
|
146
150
|
return (
|
|
147
|
-
<TabPanel
|
|
151
|
+
<TabPanel
|
|
152
|
+
key={ index }
|
|
153
|
+
sx={ { flexGrow: 1, py: 0, overflowY: 'auto' } }
|
|
154
|
+
{ ...getTabPanelProps( index ) }
|
|
155
|
+
>
|
|
148
156
|
<Stack p={ 2 } gap={ 2 }>
|
|
149
157
|
{ value.items.map( ( item ) => {
|
|
150
158
|
if ( item.type === 'control' ) {
|
|
@@ -156,12 +164,12 @@ const DynamicSettings = ( { controls }: { controls: DynamicTag[ 'atomic_controls
|
|
|
156
164
|
</TabPanel>
|
|
157
165
|
);
|
|
158
166
|
} ) }
|
|
159
|
-
|
|
167
|
+
</>
|
|
160
168
|
);
|
|
161
169
|
};
|
|
162
170
|
|
|
163
171
|
const Control = ( { control }: { control: Control[ 'value' ] } ) => {
|
|
164
|
-
if ( !
|
|
172
|
+
if ( ! controlsRegistry.get( control.type as ControlType ) ) {
|
|
165
173
|
return null;
|
|
166
174
|
}
|
|
167
175
|
|
|
@@ -3,10 +3,10 @@ import * as React from 'react';
|
|
|
3
3
|
import { useBoundProp } from '@elementor/editor-controls';
|
|
4
4
|
import { PopoverHeader, PopoverMenuList, PopoverSearch } from '@elementor/editor-ui';
|
|
5
5
|
import { DatabaseIcon } from '@elementor/icons';
|
|
6
|
-
import {
|
|
6
|
+
import { Divider, Link, Stack, Typography, useTheme } from '@elementor/ui';
|
|
7
7
|
import { __ } from '@wordpress/i18n';
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { PopoverBody } from '../../components/popover-body';
|
|
10
10
|
import { usePersistDynamicValue } from '../../hooks/use-persist-dynamic-value';
|
|
11
11
|
import { usePropDynamicTags } from '../hooks/use-prop-dynamic-tags';
|
|
12
12
|
import { getAtomicDynamicTags } from '../sync/get-atomic-dynamic-tags';
|
|
@@ -76,43 +76,39 @@ export const DynamicSelection = ( { close: closePopover }: DynamicSelectionProps
|
|
|
76
76
|
] );
|
|
77
77
|
|
|
78
78
|
return (
|
|
79
|
-
|
|
79
|
+
<PopoverBody>
|
|
80
80
|
<PopoverHeader
|
|
81
81
|
title={ __( 'Dynamic tags', 'elementor' ) }
|
|
82
82
|
onClose={ closePopover }
|
|
83
83
|
icon={ <DatabaseIcon fontSize={ SIZE } /> }
|
|
84
84
|
/>
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
</Fragment>
|
|
113
|
-
) }
|
|
114
|
-
</Stack>
|
|
115
|
-
</>
|
|
85
|
+
{ hasNoDynamicTags ? (
|
|
86
|
+
<NoDynamicTags />
|
|
87
|
+
) : (
|
|
88
|
+
<Fragment>
|
|
89
|
+
<PopoverSearch
|
|
90
|
+
value={ searchValue }
|
|
91
|
+
onSearch={ handleSearch }
|
|
92
|
+
placeholder={ __( 'Search dynamic tags…', 'elementor' ) }
|
|
93
|
+
/>
|
|
94
|
+
|
|
95
|
+
<Divider />
|
|
96
|
+
|
|
97
|
+
<PopoverMenuList
|
|
98
|
+
items={ virtualizedItems }
|
|
99
|
+
onSelect={ handleSetDynamicTag }
|
|
100
|
+
onClose={ closePopover }
|
|
101
|
+
selectedValue={ dynamicValue?.name }
|
|
102
|
+
itemStyle={ ( item ) =>
|
|
103
|
+
item.type === 'item' ? { paddingInlineStart: theme.spacing( 3.5 ) } : {}
|
|
104
|
+
}
|
|
105
|
+
noResultsComponent={
|
|
106
|
+
<NoResults searchValue={ searchValue } onClear={ () => setSearchValue( '' ) } />
|
|
107
|
+
}
|
|
108
|
+
/>
|
|
109
|
+
</Fragment>
|
|
110
|
+
) }
|
|
111
|
+
</PopoverBody>
|
|
116
112
|
);
|
|
117
113
|
};
|
|
118
114
|
|
|
@@ -142,7 +138,7 @@ const NoResults = ( { searchValue, onClear }: NoResultsProps ) => (
|
|
|
142
138
|
);
|
|
143
139
|
|
|
144
140
|
const NoDynamicTags = () => (
|
|
145
|
-
|
|
141
|
+
<>
|
|
146
142
|
<Divider />
|
|
147
143
|
<Stack
|
|
148
144
|
gap={ 1 }
|
|
@@ -161,7 +157,7 @@ const NoDynamicTags = () => (
|
|
|
161
157
|
{ __( "You'll need Elementor Pro to use this feature.", 'elementor' ) }
|
|
162
158
|
</Typography>
|
|
163
159
|
</Stack>
|
|
164
|
-
|
|
160
|
+
</>
|
|
165
161
|
);
|
|
166
162
|
|
|
167
163
|
const useFilteredOptions = ( searchValue: string ): OptionEntry[] => {
|
package/src/errors.ts
CHANGED
|
@@ -6,6 +6,16 @@ export const ControlTypeNotFoundError = createError< { controlType: string } >(
|
|
|
6
6
|
message: 'Control type not found.',
|
|
7
7
|
} );
|
|
8
8
|
|
|
9
|
+
export const ControlTypeAlreadyRegisteredError = createError< { controlType: string } >( {
|
|
10
|
+
code: 'control_type_already_registered',
|
|
11
|
+
message: 'Control type is already registered.',
|
|
12
|
+
} );
|
|
13
|
+
|
|
14
|
+
export const ControlTypeNotRegisteredError = createError< { controlType: string } >( {
|
|
15
|
+
code: 'control_type_not_registered',
|
|
16
|
+
message: 'Control type is not registered.',
|
|
17
|
+
} );
|
|
18
|
+
|
|
9
19
|
export const StylesProviderNotFoundError = createError< { styleId: StyleDefinitionID } >( {
|
|
10
20
|
code: 'provider_not_found',
|
|
11
21
|
message: 'Styles provider not found.',
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
createElementStyle,
|
|
4
|
+
deleteElementStyle,
|
|
5
|
+
type ElementID,
|
|
6
|
+
shouldCreateNewLocalStyle,
|
|
7
|
+
} from '@elementor/editor-elements';
|
|
8
|
+
import { stringPropTypeUtil } from '@elementor/editor-props';
|
|
9
|
+
import {
|
|
10
|
+
type CustomCss,
|
|
11
|
+
getVariantByMeta,
|
|
12
|
+
type StyleDefinition,
|
|
13
|
+
type StyleDefinitionVariant,
|
|
14
|
+
} from '@elementor/editor-styles';
|
|
15
|
+
import { ELEMENTS_STYLES_RESERVED_LABEL, type StylesProvider } from '@elementor/editor-styles-repository';
|
|
16
|
+
import { undoable } from '@elementor/editor-v1-adapters';
|
|
17
|
+
import { decodeString, encodeString } from '@elementor/utils';
|
|
18
|
+
|
|
19
|
+
import { useClassesProp } from '../contexts/classes-prop-context';
|
|
20
|
+
import { useElement } from '../contexts/element-context';
|
|
21
|
+
import { useStyle } from '../contexts/style-context';
|
|
22
|
+
import { StylesProviderCannotUpdatePropsError } from '../errors';
|
|
23
|
+
import { getSubtitle, getTitle, HISTORY_DEBOUNCE_WAIT } from './use-styles-fields';
|
|
24
|
+
import { useStylesRerender } from './use-styles-rerender';
|
|
25
|
+
|
|
26
|
+
type UpdateStyleArgs = {
|
|
27
|
+
styleId: StyleDefinition[ 'id' ];
|
|
28
|
+
provider: StylesProvider;
|
|
29
|
+
customCss: CustomCss;
|
|
30
|
+
propDisplayName: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type CreateStyleArgs = {
|
|
34
|
+
styleId: null;
|
|
35
|
+
provider: null;
|
|
36
|
+
customCss: CustomCss | null;
|
|
37
|
+
propDisplayName: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type UpdateStyleReturn = {
|
|
41
|
+
styleId: StyleDefinition[ 'id' ];
|
|
42
|
+
provider: StylesProvider;
|
|
43
|
+
prevCustomCss: CustomCss | null;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type CreateStyleReturn = {
|
|
47
|
+
createdStyleId: StyleDefinition[ 'id' ];
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
type UndoableUpdateStylePayload = UpdateStyleArgs | CreateStyleArgs;
|
|
51
|
+
type UndoableUpdateStyleReturn = UpdateStyleReturn | CreateStyleReturn;
|
|
52
|
+
|
|
53
|
+
export const useCustomCss = () => {
|
|
54
|
+
const {
|
|
55
|
+
element: { id: elementId },
|
|
56
|
+
} = useElement();
|
|
57
|
+
const { id: styleId, meta, provider } = useStyle();
|
|
58
|
+
const style = provider?.actions.get( styleId, { elementId } );
|
|
59
|
+
const undoableUpdateStyle = useUndoableActions( { elementId, meta } );
|
|
60
|
+
|
|
61
|
+
const currentStyleId = styleId ? styleId : null;
|
|
62
|
+
const currentProvider = styleId ? provider : null;
|
|
63
|
+
|
|
64
|
+
useStylesRerender();
|
|
65
|
+
|
|
66
|
+
const variant = style ? getVariantByMeta( style, meta ) : null;
|
|
67
|
+
|
|
68
|
+
const setCustomCss = (
|
|
69
|
+
raw: string,
|
|
70
|
+
{ history: { propDisplayName } }: { history: { propDisplayName: string } }
|
|
71
|
+
) => {
|
|
72
|
+
const newValue = { raw: encodeString( sanitize( raw ) ) };
|
|
73
|
+
|
|
74
|
+
undoableUpdateStyle( {
|
|
75
|
+
styleId: currentStyleId,
|
|
76
|
+
provider: currentProvider,
|
|
77
|
+
customCss: newValue,
|
|
78
|
+
propDisplayName,
|
|
79
|
+
} as UndoableUpdateStylePayload );
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const customCss = variant?.custom_css?.raw ? { raw: decodeString( variant.custom_css.raw ) } : null;
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
customCss,
|
|
86
|
+
setCustomCss,
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
function useUndoableActions( {
|
|
91
|
+
elementId,
|
|
92
|
+
meta: { breakpoint, state },
|
|
93
|
+
}: {
|
|
94
|
+
elementId: ElementID;
|
|
95
|
+
meta: StyleDefinitionVariant[ 'meta' ];
|
|
96
|
+
} ) {
|
|
97
|
+
const classesProp = useClassesProp();
|
|
98
|
+
|
|
99
|
+
return useMemo( () => {
|
|
100
|
+
const meta = { breakpoint, state };
|
|
101
|
+
|
|
102
|
+
const createStyleArgs = { elementId, classesProp, meta, label: ELEMENTS_STYLES_RESERVED_LABEL };
|
|
103
|
+
|
|
104
|
+
return undoable(
|
|
105
|
+
{
|
|
106
|
+
do: ( payload: UndoableUpdateStylePayload ): UndoableUpdateStyleReturn => {
|
|
107
|
+
if ( shouldCreateNewLocalStyle< StylesProvider >( payload ) ) {
|
|
108
|
+
return create( payload as CreateStyleArgs );
|
|
109
|
+
}
|
|
110
|
+
return update( payload as UpdateStyleArgs );
|
|
111
|
+
},
|
|
112
|
+
undo: ( payload: UndoableUpdateStylePayload, doReturn: UndoableUpdateStyleReturn ) => {
|
|
113
|
+
if ( shouldCreateNewLocalStyle< StylesProvider >( payload ) ) {
|
|
114
|
+
return undoCreate( payload as CreateStyleArgs, doReturn as CreateStyleReturn );
|
|
115
|
+
}
|
|
116
|
+
return undoUpdate( payload as UpdateStyleArgs, doReturn as UpdateStyleReturn );
|
|
117
|
+
},
|
|
118
|
+
redo: ( payload: UndoableUpdateStylePayload, doReturn: UndoableUpdateStyleReturn ) => {
|
|
119
|
+
if ( shouldCreateNewLocalStyle< StylesProvider >( payload ) ) {
|
|
120
|
+
return create( payload as CreateStyleArgs, doReturn as CreateStyleReturn );
|
|
121
|
+
}
|
|
122
|
+
return update( payload as UpdateStyleArgs );
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
title: ( { provider, styleId } ) => getTitle( { provider, styleId, elementId } ),
|
|
127
|
+
subtitle: ( { provider, styleId, propDisplayName } ) =>
|
|
128
|
+
getSubtitle( { provider, styleId, elementId, propDisplayName } ),
|
|
129
|
+
debounce: { wait: HISTORY_DEBOUNCE_WAIT },
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
function create( { customCss }: CreateStyleArgs, redoArgs?: CreateStyleReturn ): CreateStyleReturn {
|
|
134
|
+
const createdStyle = createElementStyle( {
|
|
135
|
+
...createStyleArgs,
|
|
136
|
+
props: {},
|
|
137
|
+
custom_css: customCss ?? null,
|
|
138
|
+
styleId: redoArgs?.createdStyleId,
|
|
139
|
+
} );
|
|
140
|
+
|
|
141
|
+
return { createdStyleId: createdStyle };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function undoCreate( _: UndoableUpdateStylePayload, { createdStyleId }: CreateStyleReturn ) {
|
|
145
|
+
deleteElementStyle( elementId, createdStyleId );
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function update( { provider, styleId, customCss }: UpdateStyleArgs ): UpdateStyleReturn {
|
|
149
|
+
if ( ! provider.actions.updateCustomCss ) {
|
|
150
|
+
throw new StylesProviderCannotUpdatePropsError( {
|
|
151
|
+
context: { providerKey: provider.getKey() },
|
|
152
|
+
} );
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const style = provider.actions.get( styleId, { elementId } );
|
|
156
|
+
const prevCustomCss = getCurrentCustomCss( style, meta );
|
|
157
|
+
|
|
158
|
+
provider.actions.updateCustomCss( { id: styleId, meta, custom_css: customCss }, { elementId } );
|
|
159
|
+
|
|
160
|
+
return { styleId, provider, prevCustomCss };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function undoUpdate( _: UndoableUpdateStylePayload, { styleId, provider, prevCustomCss }: UpdateStyleReturn ) {
|
|
164
|
+
provider.actions.updateCustomCss?.(
|
|
165
|
+
{ id: styleId, meta, custom_css: prevCustomCss ?? { raw: '' } },
|
|
166
|
+
{ elementId }
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}, [ elementId, breakpoint, state, classesProp ] );
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function getCurrentCustomCss( style: StyleDefinition | null, meta: StyleDefinitionVariant[ 'meta' ] ) {
|
|
173
|
+
if ( ! style ) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const variant = getVariantByMeta( style, meta );
|
|
178
|
+
|
|
179
|
+
return variant?.custom_css ?? null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function sanitize( raw: string ) {
|
|
183
|
+
return stringPropTypeUtil.schema.safeParse( stringPropTypeUtil.create( raw ) ).data?.value?.trim() ?? '';
|
|
184
|
+
}
|