@elementor/editor-editing-panel 1.6.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/index.js +652 -401
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.mjs +665 -409
  5. package/dist/index.mjs.map +1 -1
  6. package/package.json +7 -6
  7. package/src/components/add-or-remove-content.tsx +2 -2
  8. package/src/components/css-class-selector.tsx +198 -51
  9. package/src/components/editable-field.tsx +158 -0
  10. package/src/components/editing-panel.tsx +17 -14
  11. package/src/components/multi-combobox.tsx +184 -0
  12. package/src/components/settings-tab.tsx +28 -25
  13. package/src/components/style-sections/border-section/border-field.tsx +14 -17
  14. package/src/components/style-sections/position-section/position-field.tsx +1 -0
  15. package/src/components/style-tab.tsx +32 -29
  16. package/src/controls-registry/create-top-level-object-type.ts +14 -0
  17. package/src/controls-registry/settings-field.tsx +12 -14
  18. package/src/controls-registry/styles-field.tsx +17 -5
  19. package/src/css-classes.ts +15 -7
  20. package/src/dynamics/components/dynamic-selection-control.tsx +1 -1
  21. package/src/dynamics/components/dynamic-selection.tsx +3 -4
  22. package/src/dynamics/dynamic-control.tsx +16 -11
  23. package/src/dynamics/hooks/use-dynamic-tag.ts +2 -3
  24. package/src/dynamics/hooks/use-prop-dynamic-action.tsx +1 -4
  25. package/src/dynamics/hooks/use-prop-dynamic-tags.ts +3 -6
  26. package/src/dynamics/utils.ts +1 -1
  27. package/src/hooks/use-styles-fields.ts +1 -0
  28. package/src/hooks/use-unapply-class.ts +25 -0
  29. package/src/components/multi-combobox/index.ts +0 -3
  30. package/src/components/multi-combobox/multi-combobox.tsx +0 -122
  31. package/src/components/multi-combobox/types.ts +0 -29
  32. package/src/components/multi-combobox/use-combobox-actions.ts +0 -62
@@ -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
+ }
@@ -1,6 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { ControlLabel } from '@elementor/editor-controls';
3
3
  import { type Control } from '@elementor/editor-elements';
4
+ import { SessionStorageProvider } from '@elementor/session';
4
5
 
5
6
  import { useElement } from '../contexts/element-context';
6
7
  import { Control as BaseControl } from '../controls-registry/control';
@@ -11,33 +12,35 @@ import { Section } from './section';
11
12
  import { SectionsList } from './sections-list';
12
13
 
13
14
  export const SettingsTab = () => {
14
- const { elementType } = useElement();
15
+ const { elementType, element } = useElement();
15
16
 
16
17
  return (
17
- <SectionsList>
18
- { elementType.controls.map( ( { type, value }, index ) => {
19
- if ( type === 'control' ) {
20
- return <Control key={ value.bind } control={ value } />;
21
- }
22
-
23
- if ( type === 'section' ) {
24
- return (
25
- <Section title={ value.label } key={ type + '.' + index } defaultExpanded={ true }>
26
- { value.items?.map( ( item ) => {
27
- if ( item.type === 'control' ) {
28
- return <Control key={ item.value.bind } control={ item.value } />;
29
- }
30
-
31
- // TODO: Handle 2nd level sections
32
- return null;
33
- } ) }
34
- </Section>
35
- );
36
- }
37
-
38
- return null;
39
- } ) }
40
- </SectionsList>
18
+ <SessionStorageProvider prefix={ element.id }>
19
+ <SectionsList>
20
+ { elementType.controls.map( ( { type, value }, index ) => {
21
+ if ( type === 'control' ) {
22
+ return <Control key={ value.bind } control={ value } />;
23
+ }
24
+
25
+ if ( type === 'section' ) {
26
+ return (
27
+ <Section title={ value.label } key={ type + '.' + index } defaultExpanded={ true }>
28
+ { value.items?.map( ( item ) => {
29
+ if ( item.type === 'control' ) {
30
+ return <Control key={ item.value.bind } control={ item.value } />;
31
+ }
32
+
33
+ // TODO: Handle 2nd level sections
34
+ return null;
35
+ } ) }
36
+ </Section>
37
+ );
38
+ }
39
+
40
+ return null;
41
+ } ) }
42
+ </SectionsList>
43
+ </SessionStorageProvider>
41
44
  );
42
45
  };
43
46
 
@@ -1,37 +1,34 @@
1
1
  import * as React from 'react';
2
2
  import { __ } from '@wordpress/i18n';
3
3
 
4
- import { useStylesField } from '../../../hooks/use-styles-field';
4
+ import { useStylesFields } from '../../../hooks/use-styles-fields';
5
5
  import { AddOrRemoveContent } from '../../add-or-remove-content';
6
6
  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 initialSize = { $$type: 'size', value: { size: 1, unit: 'px' } };
11
- const initialBorderWidth = {
12
- $$type: 'border-width',
13
- value: { top: initialSize, right: initialSize, bottom: initialSize, left: initialSize },
10
+ const initialBorder = {
11
+ 'border-width': { $$type: 'size', value: { size: 1, unit: 'px' } },
12
+ 'border-color': { $$type: 'color', value: '#000000' },
13
+ 'border-style': { $$type: 'string', value: 'solid' },
14
14
  };
15
- const initialBorderColor = { $$type: 'color', value: '#000000' };
16
- const initialBorderStyle = 'solid';
17
15
 
18
16
  export const BorderField = () => {
19
- const [ borderWidth, setBorderWidth ] = useStylesField( 'border-width' );
20
- const [ borderColor, setBorderColor ] = useStylesField( 'border-color' );
21
- const [ borderStyle, setBorderStyle ] = useStylesField( 'border-style' );
17
+ const [ border, setBorder ] = useStylesFields( Object.keys( initialBorder ) );
22
18
 
23
19
  const addBorder = () => {
24
- setBorderWidth( initialBorderWidth );
25
- setBorderColor( initialBorderColor );
26
- setBorderStyle( initialBorderStyle );
20
+ setBorder( initialBorder );
27
21
  };
22
+
28
23
  const removeBorder = () => {
29
- setBorderWidth( null );
30
- setBorderColor( null );
31
- setBorderStyle( null );
24
+ setBorder( {
25
+ 'border-width': null,
26
+ 'border-color': null,
27
+ 'border-style': null,
28
+ } );
32
29
  };
33
30
 
34
- const hasBorder = Boolean( borderWidth || borderColor || borderStyle );
31
+ const hasBorder = Object.values( border ?? {} ).some( Boolean );
35
32
 
36
33
  return (
37
34
  <AddOrRemoveContent
@@ -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 = {
@@ -4,6 +4,7 @@ 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
6
  import { type StyleDefinitionID, type StyleState } from '@elementor/editor-styles';
7
+ import { SessionStorageProvider } from '@elementor/session';
7
8
  import { Divider } from '@elementor/ui';
8
9
  import { __ } from '@wordpress/i18n';
9
10
 
@@ -41,34 +42,36 @@ export const StyleTab = () => {
41
42
  } }
42
43
  setMetaState={ setActiveStyleState }
43
44
  >
44
- <CssClassSelector />
45
- <Divider />
46
- <SectionsList>
47
- <Section title={ __( 'Layout', 'elementor' ) }>
48
- <LayoutSection />
49
- </Section>
50
- <Section title={ __( 'Spacing', 'elementor' ) }>
51
- <SpacingSection />
52
- </Section>
53
- <Section title={ __( 'Size', 'elementor' ) }>
54
- <SizeSection />
55
- </Section>
56
- <Section title={ __( 'Position', 'elementor' ) }>
57
- <PositionSection />
58
- </Section>
59
- <Section title={ __( 'Typography', 'elementor' ) }>
60
- <TypographySection />
61
- </Section>
62
- <Section title={ __( 'Background', 'elementor' ) }>
63
- <BackgroundSection />
64
- </Section>
65
- <Section title={ __( 'Border', 'elementor' ) }>
66
- <BorderSection />
67
- </Section>
68
- <Section title={ __( 'Effects', 'elementor' ) }>
69
- <EffectsSection />
70
- </Section>
71
- </SectionsList>
45
+ <SessionStorageProvider prefix={ activeStyleDefId ?? '' }>
46
+ <CssClassSelector />
47
+ <Divider />
48
+ <SectionsList>
49
+ <Section title={ __( 'Layout', 'elementor' ) }>
50
+ <LayoutSection />
51
+ </Section>
52
+ <Section title={ __( 'Spacing', 'elementor' ) }>
53
+ <SpacingSection />
54
+ </Section>
55
+ <Section title={ __( 'Size', 'elementor' ) }>
56
+ <SizeSection />
57
+ </Section>
58
+ <Section title={ __( 'Position', 'elementor' ) }>
59
+ <PositionSection />
60
+ </Section>
61
+ <Section title={ __( 'Typography', 'elementor' ) }>
62
+ <TypographySection />
63
+ </Section>
64
+ <Section title={ __( 'Background', 'elementor' ) }>
65
+ <BackgroundSection />
66
+ </Section>
67
+ <Section title={ __( 'Border', 'elementor' ) }>
68
+ <BorderSection />
69
+ </Section>
70
+ <Section title={ __( 'Effects', 'elementor' ) }>
71
+ <EffectsSection />
72
+ </Section>
73
+ </SectionsList>
74
+ </SessionStorageProvider>
72
75
  </StyleProvider>
73
76
  </ClassesPropProvider>
74
77
  );
@@ -95,7 +98,7 @@ function useCurrentClassesProp(): string {
95
98
  const { elementType } = useElement();
96
99
 
97
100
  const prop = Object.entries( elementType.propsSchema ).find(
98
- ( [ , propType ] ) => propType.kind === 'array' && propType.key === CLASSES_PROP_KEY
101
+ ( [ , propType ] ) => propType.kind === 'plain' && propType.key === CLASSES_PROP_KEY
99
102
  );
100
103
 
101
104
  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 { BoundPropProvider } from '@elementor/editor-controls';
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 defaultValue = elementType.propsSchema[ bind ]?.default;
17
- const settingsValue = useElementSetting( element.id, bind );
18
- const value = settingsValue ?? defaultValue ?? null;
17
+ const settingsValue = useElementSetting< PropValue >( element.id, bind );
18
+ const value = { [ bind ]: settingsValue };
19
19
 
20
- const setValue = ( newValue: PropValue ) => {
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
- <BoundPropProvider setValue={ setValue } value={ value } bind={ bind }>
31
- { children }
32
- </BoundPropProvider>
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 { BoundPropProvider } from '@elementor/editor-controls';
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
- <BoundPropProvider setValue={ setValue } value={ value } bind={ bind }>
17
- { children }
18
- </BoundPropProvider>
28
+ <PropProvider propType={ propType } value={ values } setValue={ setValues }>
29
+ <PropKeyProvider bind={ bind }>{ children }</PropKeyProvider>
30
+ </PropProvider>
19
31
  );
20
32
  };
@@ -1,6 +1,8 @@
1
1
  import { type StyleState } from '@elementor/editor-styles';
2
2
 
3
- import { registerStateMenuItem } from './components/css-class-menu';
3
+ import { registerGlobalClassMenuItem, registerStateMenuItem } from './components/css-class-menu';
4
+ import { useCssClassItem } from './contexts/css-class-item-context';
5
+ import { useUnapplyClass } from './hooks/use-unapply-class';
4
6
 
5
7
  const STATES: NonNullable< StyleState >[] = [ 'hover', 'focus', 'active' ];
6
8
 
@@ -28,10 +30,16 @@ function registerStateItems() {
28
30
  }
29
31
 
30
32
  function registerGlobalClassItems() {
31
- /**
32
- * TODO - register the relevant global classes here
33
- * change the import statement from 'css-class-menu' to -
34
- * import { registerGlobalClassMenuItem, registerStateMenuItem } from './components/css-class-menu';
35
- * and use registerGlobalClassMenuItem
36
- */
33
+ registerGlobalClassMenuItem( {
34
+ id: 'unapply-class',
35
+ useProps: () => {
36
+ const { styleId: currentClass } = useCssClassItem();
37
+ const unapplyClass = useUnapplyClass( currentClass );
38
+
39
+ return {
40
+ text: 'Remove',
41
+ onClick: unapplyClass,
42
+ };
43
+ },
44
+ } );
37
45
  }
@@ -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( bind, tagName );
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( bind, searchValue );
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 = ( bind: PropKey, searchValue: string ): OptionEntry[] => {
139
- const dynamicTags = usePropDynamicTags( bind );
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 { BoundPropProvider, useBoundProp } from '@elementor/editor-controls';
3
- import { type PropKey, type PropValue } from '@elementor/editor-props';
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, bind: propName } = useBoundProp( dynamicPropTypeUtil );
14
+ const { value, setValue } = useBoundProp( dynamicPropTypeUtil );
14
15
  const { name = '', settings } = value ?? {};
15
16
 
16
- const dynamicTag = useDynamicTag( propName, name );
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 defaultValue = dynamicTag.props_schema[ bind ]?.default;
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 = ( newValue: PropValue ) => {
28
+ const setDynamicValue: SetValue< Record< string, DynamicPropValue > > = ( newValues ) => {
26
29
  setValue( {
27
30
  name,
28
31
  settings: {
29
32
  ...settings,
30
- [ bind ]: newValue,
33
+ ...newValues,
31
34
  },
32
35
  } );
33
36
  };
34
37
 
38
+ const propType = createTopLevelOjectType( { schema: dynamicTag.props_schema } );
39
+
35
40
  return (
36
- <BoundPropProvider setValue={ setDynamicValue } value={ dynamicValue } bind={ bind }>
37
- { children }
38
- </BoundPropProvider>
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 = ( propName: PropKey, tagName: string ): DynamicTag | null => {
8
- const dynamicTags = usePropDynamicTags( propName );
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 { bind } = useBoundProp();
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 {