@elementor/editor-editing-panel 1.6.0 → 1.7.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 +20 -0
- package/dist/index.js +354 -314
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +215 -171
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
- package/src/components/css-class-selector.tsx +87 -26
- package/src/components/multi-combobox.tsx +184 -0
- package/src/components/style-sections/border-section/border-field.tsx +1 -5
- package/src/components/style-sections/position-section/position-field.tsx +1 -0
- package/src/components/style-tab.tsx +1 -1
- package/src/controls-registry/create-top-level-object-type.ts +14 -0
- package/src/controls-registry/settings-field.tsx +12 -14
- package/src/controls-registry/styles-field.tsx +17 -5
- package/src/dynamics/components/dynamic-selection-control.tsx +1 -1
- package/src/dynamics/components/dynamic-selection.tsx +3 -4
- package/src/dynamics/dynamic-control.tsx +16 -11
- package/src/dynamics/hooks/use-dynamic-tag.ts +2 -3
- package/src/dynamics/hooks/use-prop-dynamic-action.tsx +1 -4
- package/src/dynamics/hooks/use-prop-dynamic-tags.ts +3 -6
- package/src/dynamics/utils.ts +1 -1
- package/src/hooks/use-styles-fields.ts +1 -0
- package/src/components/multi-combobox/index.ts +0 -3
- package/src/components/multi-combobox/multi-combobox.tsx +0 -122
- package/src/components/multi-combobox/types.ts +0 -29
- package/src/components/multi-combobox/use-combobox-actions.ts +0 -62
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-editing-panel",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "Elementor Team",
|
|
6
6
|
"homepage": "https://elementor.com/",
|
|
@@ -40,14 +40,14 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@elementor/editor": "0.17.2",
|
|
43
|
-
"@elementor/editor-controls": "0.
|
|
44
|
-
"@elementor/editor-elements": "0.
|
|
43
|
+
"@elementor/editor-controls": "0.5.0",
|
|
44
|
+
"@elementor/editor-elements": "0.4.0",
|
|
45
45
|
"@elementor/menus": "0.1.2",
|
|
46
|
-
"@elementor/editor-props": "0.
|
|
46
|
+
"@elementor/editor-props": "0.6.0",
|
|
47
47
|
"@elementor/editor-panels": "0.10.2",
|
|
48
48
|
"@elementor/editor-responsive": "0.12.4",
|
|
49
|
-
"@elementor/editor-styles": "0.
|
|
50
|
-
"@elementor/editor-styles-repository": "0.3.
|
|
49
|
+
"@elementor/editor-styles": "0.5.0",
|
|
50
|
+
"@elementor/editor-styles-repository": "0.3.3",
|
|
51
51
|
"@elementor/editor-v1-adapters": "0.8.5",
|
|
52
52
|
"@elementor/icons": "^1.20.0",
|
|
53
53
|
"@elementor/schema": "0.1.2",
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { useId, useRef } from 'react';
|
|
3
|
-
import { updateSettings, useElementSetting } from '@elementor/editor-elements';
|
|
3
|
+
import { getElementSetting, updateSettings, useElementSetting } from '@elementor/editor-elements';
|
|
4
4
|
import { classesPropTypeUtil, type ClassesPropValue } from '@elementor/editor-props';
|
|
5
5
|
import { type StyleDefinitionID } from '@elementor/editor-styles';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
ELEMENTS_STYLES_PROVIDER_KEY,
|
|
8
|
+
useAllStylesByProvider,
|
|
9
|
+
useCreateActionsByProvider,
|
|
10
|
+
} from '@elementor/editor-styles-repository';
|
|
7
11
|
import { DotsVerticalIcon } from '@elementor/icons';
|
|
8
12
|
import {
|
|
9
13
|
type AutocompleteRenderGetTagProps,
|
|
@@ -23,18 +27,23 @@ import { useElement } from '../contexts/element-context';
|
|
|
23
27
|
import { useStyle } from '../contexts/style-context';
|
|
24
28
|
import { ConditionalTooltipWrapper } from './conditional-tooltip-wrapper';
|
|
25
29
|
import { CssClassMenu } from './css-class-menu';
|
|
26
|
-
import { MultiCombobox, type Option } from './multi-combobox';
|
|
30
|
+
import { type Action, MultiCombobox, type Option } from './multi-combobox';
|
|
27
31
|
|
|
28
32
|
const ID = 'elementor-css-class-selector';
|
|
29
33
|
const TAGS_LIMIT = 8;
|
|
30
34
|
|
|
35
|
+
type StyleDefOption = Option & {
|
|
36
|
+
color: 'primary' | 'global';
|
|
37
|
+
provider: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
31
40
|
const EMPTY_OPTION = {
|
|
32
41
|
label: __( 'local', 'elementor' ),
|
|
33
42
|
value: '',
|
|
34
43
|
fixed: true,
|
|
35
44
|
color: 'primary',
|
|
36
45
|
provider: ELEMENTS_STYLES_PROVIDER_KEY,
|
|
37
|
-
} satisfies
|
|
46
|
+
} satisfies StyleDefOption;
|
|
38
47
|
|
|
39
48
|
/**
|
|
40
49
|
* Applied - Classes applied to an element.
|
|
@@ -43,10 +52,12 @@ const EMPTY_OPTION = {
|
|
|
43
52
|
|
|
44
53
|
export function CssClassSelector() {
|
|
45
54
|
const options = useOptions();
|
|
46
|
-
const [ appliedIds, setAppliedIds ] = useAppliedClassesIds();
|
|
47
55
|
|
|
56
|
+
const { value: appliedIds, setValue: setAppliedIds, pushValue: pushAppliedId } = useAppliedClassesIds();
|
|
48
57
|
const { id: activeId, setId: setActiveId } = useStyle();
|
|
49
58
|
|
|
59
|
+
const actions = useCreateActions( { pushAppliedId, setActiveId } );
|
|
60
|
+
|
|
50
61
|
const handleApply = useHandleApply( appliedIds, setAppliedIds );
|
|
51
62
|
const handleActivate = ( { value }: Option ) => setActiveId( value );
|
|
52
63
|
|
|
@@ -65,7 +76,10 @@ export function CssClassSelector() {
|
|
|
65
76
|
selected={ applied }
|
|
66
77
|
onSelect={ handleApply }
|
|
67
78
|
limitTags={ TAGS_LIMIT }
|
|
68
|
-
|
|
79
|
+
actions={ actions }
|
|
80
|
+
getLimitTagsText={ ( more ) => (
|
|
81
|
+
<Chip size="tiny" variant="standard" label={ `+${ more }` } clickable />
|
|
82
|
+
) }
|
|
69
83
|
renderTags={ ( values, getTagProps ) =>
|
|
70
84
|
values.map( ( value, index ) => {
|
|
71
85
|
const chipProps = getTagProps( { index } );
|
|
@@ -113,7 +127,7 @@ function CssClassItem( { id, label, isActive, isGlobal, color, chipProps, onClic
|
|
|
113
127
|
<UnstableChipGroup ref={ chipRef } { ...chipGroupProps } aria-label={ `Edit ${ label }` } role="group">
|
|
114
128
|
<Chip
|
|
115
129
|
key={ chipProps.key }
|
|
116
|
-
size="
|
|
130
|
+
size="tiny"
|
|
117
131
|
label={ <ConditionalTooltipWrapper maxWidth="10ch" title={ label } /> }
|
|
118
132
|
variant={ isActive && ! meta.state ? 'filled' : 'standard' }
|
|
119
133
|
color={ color }
|
|
@@ -122,7 +136,7 @@ function CssClassItem( { id, label, isActive, isGlobal, color, chipProps, onClic
|
|
|
122
136
|
/>
|
|
123
137
|
<Chip
|
|
124
138
|
key={ `${ chipProps.key }-menu` }
|
|
125
|
-
size="
|
|
139
|
+
size="tiny"
|
|
126
140
|
label={
|
|
127
141
|
<Stack direction="row" gap={ 0.5 } alignItems="center">
|
|
128
142
|
{ isActive && meta.state && <Typography variant="inherit">{ meta.state }</Typography> }
|
|
@@ -143,27 +157,64 @@ function CssClassItem( { id, label, isActive, isGlobal, color, chipProps, onClic
|
|
|
143
157
|
function useOptions() {
|
|
144
158
|
const { element } = useElement();
|
|
145
159
|
|
|
146
|
-
return useAllStylesByProvider( { elementId: element.id } ).flatMap<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
160
|
+
return useAllStylesByProvider( { elementId: element.id } ).flatMap< StyleDefOption >(
|
|
161
|
+
( [ provider, styleDefs ] ) => {
|
|
162
|
+
const isElements = provider.key === ELEMENTS_STYLES_PROVIDER_KEY;
|
|
163
|
+
|
|
164
|
+
// Add empty local option for elements, as fallback.
|
|
165
|
+
if ( isElements && styleDefs.length === 0 ) {
|
|
166
|
+
return [ EMPTY_OPTION ];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return styleDefs.map( ( styleDef ) => {
|
|
170
|
+
return {
|
|
171
|
+
label: styleDef.label,
|
|
172
|
+
value: styleDef.id,
|
|
173
|
+
fixed: isElements,
|
|
174
|
+
color: isElements ? 'primary' : 'global',
|
|
175
|
+
provider: provider.key,
|
|
176
|
+
group: provider.labels?.plural,
|
|
177
|
+
};
|
|
178
|
+
} );
|
|
152
179
|
}
|
|
180
|
+
);
|
|
181
|
+
}
|
|
153
182
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
183
|
+
function useCreateActions( {
|
|
184
|
+
pushAppliedId,
|
|
185
|
+
setActiveId,
|
|
186
|
+
}: {
|
|
187
|
+
pushAppliedId: ( id: StyleDefinitionID ) => void;
|
|
188
|
+
setActiveId: ( id: StyleDefinitionID ) => void;
|
|
189
|
+
} ) {
|
|
190
|
+
return useCreateActionsByProvider().map( ( [ provider, create ] ): Action< StyleDefOption > => {
|
|
191
|
+
return {
|
|
192
|
+
// translators: %s is the label of the new class.
|
|
193
|
+
label: ( value ) => __( 'Create new "%s"', 'elementor' ).replace( '%s', value ),
|
|
194
|
+
apply: async ( value ) => {
|
|
195
|
+
const created = await create( { label: value } );
|
|
196
|
+
|
|
197
|
+
if ( ! created ) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
pushAppliedId( created.id );
|
|
202
|
+
setActiveId( created.id );
|
|
203
|
+
},
|
|
204
|
+
condition: ( options, inputValue ) => {
|
|
205
|
+
const isUniqueLabel = ! options.some(
|
|
206
|
+
( option ) => option.label.toLowerCase() === inputValue.toLowerCase()
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
return !! inputValue && isUniqueLabel;
|
|
210
|
+
},
|
|
211
|
+
// translators: %s is the singular label of css class provider (e.g "Global CSS Class").
|
|
212
|
+
group: __( 'Create New %s', 'elementor' ).replace( '%s', provider.labels?.singular ?? '' ),
|
|
213
|
+
};
|
|
163
214
|
} );
|
|
164
215
|
}
|
|
165
216
|
|
|
166
|
-
function useAppliedOptions( options:
|
|
217
|
+
function useAppliedOptions( options: StyleDefOption[], appliedIds: StyleDefinitionID[] ) {
|
|
167
218
|
const applied = options.filter( ( option ) => appliedIds.includes( option.value ) );
|
|
168
219
|
|
|
169
220
|
const hasElementsProviderStyleApplied = applied.some(
|
|
@@ -192,13 +243,23 @@ function useAppliedClassesIds() {
|
|
|
192
243
|
} );
|
|
193
244
|
};
|
|
194
245
|
|
|
195
|
-
|
|
246
|
+
const pushValue = ( id: StyleDefinitionID ) => {
|
|
247
|
+
const ids = getElementSetting< ClassesPropValue >( element.id, currentClassesProp )?.value || [];
|
|
248
|
+
|
|
249
|
+
setValue( [ ...ids, id ] );
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
value,
|
|
254
|
+
setValue,
|
|
255
|
+
pushValue,
|
|
256
|
+
};
|
|
196
257
|
}
|
|
197
258
|
|
|
198
259
|
function useHandleApply( appliedIds: StyleDefinitionID[], setAppliedIds: ( ids: StyleDefinitionID[] ) => void ) {
|
|
199
260
|
const { id: activeId, setId: setActiveId } = useStyle();
|
|
200
261
|
|
|
201
|
-
return ( selectedOptions:
|
|
262
|
+
return ( selectedOptions: StyleDefOption[] ) => {
|
|
202
263
|
const selectedValues = selectedOptions
|
|
203
264
|
.map( ( { value } ) => value )
|
|
204
265
|
.filter( ( value ) => value !== EMPTY_OPTION.value );
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useId, useState } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
Autocomplete,
|
|
5
|
+
type AutocompleteProps,
|
|
6
|
+
type AutocompleteRenderGroupParams,
|
|
7
|
+
Box,
|
|
8
|
+
createFilterOptions,
|
|
9
|
+
styled,
|
|
10
|
+
TextField,
|
|
11
|
+
} from '@elementor/ui';
|
|
12
|
+
|
|
13
|
+
export type Option = {
|
|
14
|
+
label: string;
|
|
15
|
+
value: string;
|
|
16
|
+
fixed?: boolean;
|
|
17
|
+
group?: string;
|
|
18
|
+
key?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type Action< TOption extends Option > = {
|
|
22
|
+
label: ( value: string ) => string;
|
|
23
|
+
apply: ( value: string ) => void | Promise< void >;
|
|
24
|
+
condition: ( options: TOption[], value: string ) => boolean;
|
|
25
|
+
group?: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type ActionAsOption< TOption extends Option > = TOption & {
|
|
29
|
+
apply: Action< TOption >[ 'apply' ];
|
|
30
|
+
condition: Action< TOption >[ 'condition' ];
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type Props< TOption extends Option > = Omit<
|
|
34
|
+
AutocompleteProps< TOption, true, true, true >,
|
|
35
|
+
'renderInput' | 'onSelect'
|
|
36
|
+
> & {
|
|
37
|
+
actions?: Action< TOption >[];
|
|
38
|
+
selected: TOption[];
|
|
39
|
+
options: TOption[];
|
|
40
|
+
onSelect?: ( value: TOption[] ) => void;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export function MultiCombobox< TOption extends Option >( {
|
|
44
|
+
actions = [],
|
|
45
|
+
selected,
|
|
46
|
+
options,
|
|
47
|
+
onSelect,
|
|
48
|
+
...props
|
|
49
|
+
}: Props< TOption > ) {
|
|
50
|
+
const filter = useFilterOptions< TOption >();
|
|
51
|
+
const { run, loading } = useActionRunner< TOption >();
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Autocomplete
|
|
55
|
+
{ ...props }
|
|
56
|
+
freeSolo
|
|
57
|
+
multiple
|
|
58
|
+
clearOnBlur
|
|
59
|
+
selectOnFocus
|
|
60
|
+
disableClearable
|
|
61
|
+
handleHomeEndKeys
|
|
62
|
+
disabled={ loading }
|
|
63
|
+
value={ selected }
|
|
64
|
+
options={ options }
|
|
65
|
+
renderGroup={ ( params ) => <Group { ...params } /> }
|
|
66
|
+
renderInput={ ( params ) => <TextField { ...params } /> }
|
|
67
|
+
onChange={ ( _, selectedOrInputValue, reason ) => {
|
|
68
|
+
const inputValue = selectedOrInputValue.find( ( option ) => typeof option === 'string' );
|
|
69
|
+
const optionsAndActions = selectedOrInputValue.filter( ( option ) => typeof option !== 'string' );
|
|
70
|
+
|
|
71
|
+
// Handles user input when Enter is pressed
|
|
72
|
+
if ( reason === 'createOption' ) {
|
|
73
|
+
const [ firstAction ] = filterActions( actions, { options, inputValue: inputValue ?? '' } );
|
|
74
|
+
|
|
75
|
+
if ( firstAction ) {
|
|
76
|
+
return run( firstAction.apply, firstAction.value );
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Handles the user's action selection when triggered.
|
|
81
|
+
const action = optionsAndActions.find( ( value ) => isAction( value ) );
|
|
82
|
+
|
|
83
|
+
if ( reason === 'selectOption' && action ) {
|
|
84
|
+
return run( action.apply, action.value );
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Every other case, we update the selected values.
|
|
88
|
+
const fixedValues = options.filter( ( option ) => !! option.fixed );
|
|
89
|
+
|
|
90
|
+
onSelect?.( [ ...new Set( [ ...optionsAndActions, ...fixedValues ] ) ] );
|
|
91
|
+
} }
|
|
92
|
+
getOptionLabel={ ( option ) => ( typeof option === 'string' ? option : option.label ) }
|
|
93
|
+
getOptionKey={ ( option ) => {
|
|
94
|
+
if ( typeof option === 'string' ) {
|
|
95
|
+
return option;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return option.key ?? option.value;
|
|
99
|
+
} }
|
|
100
|
+
filterOptions={ ( optionList, params ) => {
|
|
101
|
+
const selectedValues = selected.map( ( option ) => option.value );
|
|
102
|
+
|
|
103
|
+
return [
|
|
104
|
+
...filterActions( actions, { options: optionList, inputValue: params.inputValue } ),
|
|
105
|
+
...filter(
|
|
106
|
+
optionList.filter( ( option ) => ! selectedValues.includes( option.value ) ),
|
|
107
|
+
params
|
|
108
|
+
),
|
|
109
|
+
];
|
|
110
|
+
} }
|
|
111
|
+
groupBy={ ( option ) => option.group ?? '' }
|
|
112
|
+
/>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const Group = ( params: Omit< AutocompleteRenderGroupParams, 'key' > ) => {
|
|
117
|
+
const id = `combobox-group-${ useId().replace( /:/g, '_' ) }`;
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<StyledGroup role="group" aria-labelledby={ id }>
|
|
121
|
+
<StyledGroupHeader id={ id }> { params.group }</StyledGroupHeader>
|
|
122
|
+
<StyledGroupItems role="listbox">{ params.children }</StyledGroupItems>
|
|
123
|
+
</StyledGroup>
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const StyledGroup = styled( 'li' )`
|
|
128
|
+
&:not( :last-of-type ) {
|
|
129
|
+
border-bottom: 1px solid ${ ( { theme } ) => theme.palette.divider };
|
|
130
|
+
}
|
|
131
|
+
`;
|
|
132
|
+
|
|
133
|
+
const StyledGroupHeader = styled( Box )( ( { theme } ) => ( {
|
|
134
|
+
position: 'sticky',
|
|
135
|
+
top: '-8px',
|
|
136
|
+
padding: theme.spacing( 1, 2 ),
|
|
137
|
+
color: theme.palette.text.tertiary,
|
|
138
|
+
} ) );
|
|
139
|
+
|
|
140
|
+
const StyledGroupItems = styled( 'ul' )`
|
|
141
|
+
padding: 0;
|
|
142
|
+
`;
|
|
143
|
+
|
|
144
|
+
function useFilterOptions< TOption extends Option >() {
|
|
145
|
+
return useState( () => createFilterOptions< TOption >() )[ 0 ];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function useActionRunner< TOption extends Option >() {
|
|
149
|
+
const [ loading, setLoading ] = useState( false );
|
|
150
|
+
|
|
151
|
+
const run = async ( apply: Action< TOption >[ 'apply' ], value: string ) => {
|
|
152
|
+
setLoading( true );
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
await apply( value );
|
|
156
|
+
} catch {
|
|
157
|
+
// TODO: Do something with the error.
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
setLoading( false );
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
return { run, loading };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function filterActions< TOption extends Option >(
|
|
167
|
+
actions: Action< TOption >[],
|
|
168
|
+
{ options, inputValue }: { options: TOption[]; inputValue: string }
|
|
169
|
+
) {
|
|
170
|
+
return actions
|
|
171
|
+
.filter( ( action ) => action.condition( options, inputValue ) )
|
|
172
|
+
.map( ( action, index ) => ( {
|
|
173
|
+
label: action.label( inputValue ),
|
|
174
|
+
value: inputValue,
|
|
175
|
+
group: action.group,
|
|
176
|
+
apply: action.apply,
|
|
177
|
+
condition: action.condition,
|
|
178
|
+
key: index.toString(),
|
|
179
|
+
} ) ) as ActionAsOption< TOption >[];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function isAction< TOption extends Option >( option: TOption ): option is ActionAsOption< TOption > {
|
|
183
|
+
return 'apply' in option && 'condition' in option;
|
|
184
|
+
}
|
|
@@ -7,11 +7,7 @@ import { BorderColorField } from './border-color-field';
|
|
|
7
7
|
import { BorderStyleField } from './border-style-field';
|
|
8
8
|
import { BorderWidthField } from './border-width-field';
|
|
9
9
|
|
|
10
|
-
const
|
|
11
|
-
const initialBorderWidth = {
|
|
12
|
-
$$type: 'border-width',
|
|
13
|
-
value: { top: initialSize, right: initialSize, bottom: initialSize, left: initialSize },
|
|
14
|
-
};
|
|
10
|
+
const initialBorderWidth = { $$type: 'size', value: { size: 1, unit: 'px' } };
|
|
15
11
|
const initialBorderColor = { $$type: 'color', value: '#000000' };
|
|
16
12
|
const initialBorderStyle = 'solid';
|
|
17
13
|
|
|
@@ -10,6 +10,7 @@ const positionOptions = [
|
|
|
10
10
|
{ label: __( 'Relative', 'elementor' ), value: 'relative' },
|
|
11
11
|
{ label: __( 'Absolute', 'elementor' ), value: 'absolute' },
|
|
12
12
|
{ label: __( 'Fixed', 'elementor' ), value: 'fixed' },
|
|
13
|
+
{ label: __( 'Sticky', 'elementor' ), value: 'sticky' },
|
|
13
14
|
];
|
|
14
15
|
|
|
15
16
|
type Props = {
|
|
@@ -95,7 +95,7 @@ function useCurrentClassesProp(): string {
|
|
|
95
95
|
const { elementType } = useElement();
|
|
96
96
|
|
|
97
97
|
const prop = Object.entries( elementType.propsSchema ).find(
|
|
98
|
-
( [ , propType ] ) => propType.kind === '
|
|
98
|
+
( [ , propType ] ) => propType.kind === 'plain' && propType.key === CLASSES_PROP_KEY
|
|
99
99
|
);
|
|
100
100
|
|
|
101
101
|
if ( ! prop ) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ObjectPropType, type PropsSchema } from '@elementor/editor-props';
|
|
2
|
+
|
|
3
|
+
export const createTopLevelOjectType = ( { schema }: { schema: PropsSchema } ) => {
|
|
4
|
+
const schemaPropType: ObjectPropType = {
|
|
5
|
+
key: '',
|
|
6
|
+
kind: 'object',
|
|
7
|
+
meta: {},
|
|
8
|
+
settings: {},
|
|
9
|
+
default: null,
|
|
10
|
+
shape: schema,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
return schemaPropType;
|
|
14
|
+
};
|
|
@@ -1,36 +1,34 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { PropKeyProvider, PropProvider } from '@elementor/editor-controls';
|
|
3
3
|
import { updateSettings, useElementSetting } from '@elementor/editor-elements';
|
|
4
4
|
import { type PropKey, type PropValue } from '@elementor/editor-props';
|
|
5
5
|
|
|
6
6
|
import { useElement } from '../contexts/element-context';
|
|
7
|
+
import { createTopLevelOjectType } from './create-top-level-object-type';
|
|
7
8
|
|
|
8
9
|
type Props = {
|
|
9
10
|
bind: PropKey;
|
|
10
11
|
children: React.ReactNode;
|
|
11
12
|
};
|
|
12
13
|
|
|
13
|
-
const SettingsField = ( { bind, children }: Props ) => {
|
|
14
|
+
export const SettingsField = ( { bind, children }: Props ) => {
|
|
14
15
|
const { element, elementType } = useElement();
|
|
15
16
|
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const value = settingsValue ?? defaultValue ?? null;
|
|
17
|
+
const settingsValue = useElementSetting< PropValue >( element.id, bind );
|
|
18
|
+
const value = { [ bind ]: settingsValue };
|
|
19
19
|
|
|
20
|
-
const
|
|
20
|
+
const propType = createTopLevelOjectType( { schema: elementType.propsSchema } );
|
|
21
|
+
|
|
22
|
+
const setValue = ( newValue: Record< string, PropValue > ) => {
|
|
21
23
|
updateSettings( {
|
|
22
24
|
id: element.id,
|
|
23
|
-
props: {
|
|
24
|
-
[ bind ]: newValue,
|
|
25
|
-
},
|
|
25
|
+
props: { ...newValue },
|
|
26
26
|
} );
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
return (
|
|
30
|
-
<
|
|
31
|
-
{ children }
|
|
32
|
-
</
|
|
30
|
+
<PropProvider propType={ propType } value={ value } setValue={ setValue }>
|
|
31
|
+
<PropKeyProvider bind={ bind }>{ children }</PropKeyProvider>
|
|
32
|
+
</PropProvider>
|
|
33
33
|
);
|
|
34
34
|
};
|
|
35
|
-
|
|
36
|
-
export { SettingsField };
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import { type PropKey } from '@elementor/editor-props';
|
|
2
|
+
import { PropKeyProvider, PropProvider } from '@elementor/editor-controls';
|
|
3
|
+
import { type PropKey, type PropValue } from '@elementor/editor-props';
|
|
4
|
+
import { getStylesSchema } from '@elementor/editor-styles';
|
|
4
5
|
|
|
5
6
|
import { useStylesField } from '../hooks/use-styles-field';
|
|
7
|
+
import { createTopLevelOjectType } from './create-top-level-object-type';
|
|
6
8
|
|
|
7
9
|
export type StylesFieldProps = {
|
|
8
10
|
bind: PropKey;
|
|
@@ -12,9 +14,19 @@ export type StylesFieldProps = {
|
|
|
12
14
|
export const StylesField = ( { bind, children }: StylesFieldProps ) => {
|
|
13
15
|
const [ value, setValue ] = useStylesField( bind );
|
|
14
16
|
|
|
17
|
+
const stylesSchema = getStylesSchema();
|
|
18
|
+
|
|
19
|
+
const propType = createTopLevelOjectType( { schema: stylesSchema } );
|
|
20
|
+
|
|
21
|
+
const values = { [ bind ]: value };
|
|
22
|
+
|
|
23
|
+
const setValues = ( newValue: Record< string, PropValue > ) => {
|
|
24
|
+
setValue( newValue[ bind ] );
|
|
25
|
+
};
|
|
26
|
+
|
|
15
27
|
return (
|
|
16
|
-
<
|
|
17
|
-
{ children }
|
|
18
|
-
</
|
|
28
|
+
<PropProvider propType={ propType } value={ values } setValue={ setValues }>
|
|
29
|
+
<PropKeyProvider bind={ bind }>{ children }</PropKeyProvider>
|
|
30
|
+
</PropProvider>
|
|
19
31
|
);
|
|
20
32
|
};
|
|
@@ -42,7 +42,7 @@ export const DynamicSelectionControl = () => {
|
|
|
42
42
|
const selectionPopoverId = useId();
|
|
43
43
|
const selectionPopoverState = usePopupState( { variant: 'popover', popupId: selectionPopoverId } );
|
|
44
44
|
|
|
45
|
-
const dynamicTag = useDynamicTag(
|
|
45
|
+
const dynamicTag = useDynamicTag( tagName );
|
|
46
46
|
|
|
47
47
|
const removeDynamicTag = () => {
|
|
48
48
|
setAnyValue( propValueFromHistory ?? null );
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { Fragment, useState } from 'react';
|
|
3
3
|
import { useBoundProp } from '@elementor/editor-controls';
|
|
4
|
-
import { type PropKey } from '@elementor/editor-props';
|
|
5
4
|
import { PhotoIcon, SearchIcon } from '@elementor/icons';
|
|
6
5
|
import {
|
|
7
6
|
Box,
|
|
@@ -46,7 +45,7 @@ export const DynamicSelection = ( { onSelect }: DynamicSelectionProps ) => {
|
|
|
46
45
|
|
|
47
46
|
const isCurrentValueDynamic = !! dynamicValue;
|
|
48
47
|
|
|
49
|
-
const options = useFilteredOptions(
|
|
48
|
+
const options = useFilteredOptions( searchValue );
|
|
50
49
|
|
|
51
50
|
const handleSearch = ( event: React.ChangeEvent< HTMLInputElement > ) => {
|
|
52
51
|
setSearchValue( event.target.value );
|
|
@@ -135,8 +134,8 @@ export const DynamicSelection = ( { onSelect }: DynamicSelectionProps ) => {
|
|
|
135
134
|
);
|
|
136
135
|
};
|
|
137
136
|
|
|
138
|
-
const useFilteredOptions = (
|
|
139
|
-
const dynamicTags = usePropDynamicTags(
|
|
137
|
+
const useFilteredOptions = ( searchValue: string ): OptionEntry[] => {
|
|
138
|
+
const dynamicTags = usePropDynamicTags();
|
|
140
139
|
|
|
141
140
|
const options = dynamicTags.reduce< Map< string, Option[] > >( ( categories, { name, label, group } ) => {
|
|
142
141
|
const isVisible = label.toLowerCase().includes( searchValue.trim().toLowerCase() );
|
|
@@ -1,40 +1,45 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import { type PropKey
|
|
2
|
+
import { PropKeyProvider, PropProvider, type SetValue, useBoundProp } from '@elementor/editor-controls';
|
|
3
|
+
import { type PropKey } from '@elementor/editor-props';
|
|
4
4
|
|
|
5
|
+
import { createTopLevelOjectType } from '../controls-registry/create-top-level-object-type';
|
|
5
6
|
import { useDynamicTag } from './hooks/use-dynamic-tag';
|
|
6
|
-
import { dynamicPropTypeUtil } from './utils';
|
|
7
|
+
import { dynamicPropTypeUtil, type DynamicPropValue } from './utils';
|
|
7
8
|
|
|
8
9
|
export type DynamicControlProps = React.PropsWithChildren< {
|
|
9
10
|
bind: PropKey;
|
|
10
11
|
} >;
|
|
11
12
|
|
|
12
13
|
export const DynamicControl = ( { bind, children }: DynamicControlProps ) => {
|
|
13
|
-
const { value, setValue
|
|
14
|
+
const { value, setValue } = useBoundProp( dynamicPropTypeUtil );
|
|
14
15
|
const { name = '', settings } = value ?? {};
|
|
15
16
|
|
|
16
|
-
const dynamicTag = useDynamicTag(
|
|
17
|
+
const dynamicTag = useDynamicTag( name );
|
|
17
18
|
|
|
18
19
|
if ( ! dynamicTag ) {
|
|
19
20
|
throw new Error( `Dynamic tag ${ name } not found` );
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
const
|
|
23
|
+
const dynamicPropType = dynamicTag.props_schema[ bind ];
|
|
24
|
+
|
|
25
|
+
const defaultValue = dynamicPropType?.default;
|
|
23
26
|
const dynamicValue = settings?.[ bind ] ?? defaultValue;
|
|
24
27
|
|
|
25
|
-
const setDynamicValue = (
|
|
28
|
+
const setDynamicValue: SetValue< Record< string, DynamicPropValue > > = ( newValues ) => {
|
|
26
29
|
setValue( {
|
|
27
30
|
name,
|
|
28
31
|
settings: {
|
|
29
32
|
...settings,
|
|
30
|
-
|
|
33
|
+
...newValues,
|
|
31
34
|
},
|
|
32
35
|
} );
|
|
33
36
|
};
|
|
34
37
|
|
|
38
|
+
const propType = createTopLevelOjectType( { schema: dynamicTag.props_schema } );
|
|
39
|
+
|
|
35
40
|
return (
|
|
36
|
-
<
|
|
37
|
-
{ children }
|
|
38
|
-
</
|
|
41
|
+
<PropProvider propType={ propType } setValue={ setDynamicValue } value={ { [ bind ]: dynamicValue } }>
|
|
42
|
+
<PropKeyProvider bind={ bind }>{ children }</PropKeyProvider>
|
|
43
|
+
</PropProvider>
|
|
39
44
|
);
|
|
40
45
|
};
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
|
-
import { type PropKey } from '@elementor/editor-props';
|
|
3
2
|
|
|
4
3
|
import { type DynamicTag } from '../types';
|
|
5
4
|
import { usePropDynamicTags } from './use-prop-dynamic-tags';
|
|
6
5
|
|
|
7
|
-
export const useDynamicTag = (
|
|
8
|
-
const dynamicTags = usePropDynamicTags(
|
|
6
|
+
export const useDynamicTag = ( tagName: string ): DynamicTag | null => {
|
|
7
|
+
const dynamicTags = usePropDynamicTags();
|
|
9
8
|
|
|
10
9
|
return useMemo( () => dynamicTags.find( ( tag ) => tag.name === tagName ) ?? null, [ dynamicTags, tagName ] );
|
|
11
10
|
};
|
|
@@ -3,16 +3,13 @@ import { useBoundProp } from '@elementor/editor-controls';
|
|
|
3
3
|
import { DatabaseIcon } from '@elementor/icons';
|
|
4
4
|
import { __ } from '@wordpress/i18n';
|
|
5
5
|
|
|
6
|
-
import { useElement } from '../../contexts/element-context';
|
|
7
6
|
import { type PopoverActionProps } from '../../popover-action';
|
|
8
7
|
import { DynamicSelection } from '../components/dynamic-selection';
|
|
9
8
|
import { supportsDynamic } from '../utils';
|
|
10
9
|
|
|
11
10
|
export const usePropDynamicAction = (): PopoverActionProps => {
|
|
12
|
-
const {
|
|
13
|
-
const { elementType } = useElement();
|
|
11
|
+
const { propType } = useBoundProp();
|
|
14
12
|
|
|
15
|
-
const propType = elementType.propsSchema[ bind ];
|
|
16
13
|
const visible = !! propType && supportsDynamic( propType );
|
|
17
14
|
|
|
18
15
|
return {
|