@elementor/editor-editing-panel 1.8.1 → 1.10.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elementor/editor-editing-panel",
3
- "version": "1.8.1",
3
+ "version": "1.10.0",
4
4
  "private": false,
5
5
  "author": "Elementor Team",
6
6
  "homepage": "https://elementor.com/",
@@ -39,20 +39,22 @@
39
39
  "dev": "tsup --config=../../tsup.dev.ts"
40
40
  },
41
41
  "dependencies": {
42
- "@elementor/editor": "0.17.3",
43
- "@elementor/editor-controls": "0.6.1",
44
- "@elementor/editor-elements": "0.4.2",
45
- "@elementor/menus": "0.1.2",
46
- "@elementor/editor-props": "0.7.1",
47
- "@elementor/editor-panels": "0.10.3",
48
- "@elementor/editor-responsive": "0.12.5",
49
- "@elementor/editor-styles": "0.5.2",
50
- "@elementor/editor-styles-repository": "0.4.1",
51
- "@elementor/editor-v1-adapters": "0.9.0",
52
- "@elementor/icons": "^1.20.0",
42
+ "@elementor/editor": "0.17.5",
43
+ "@elementor/editor-controls": "0.8.0",
44
+ "@elementor/editor-elements": "0.5.1",
45
+ "@elementor/menus": "0.1.3",
46
+ "@elementor/editor-props": "0.8.0",
47
+ "@elementor/editor-panels": "0.10.5",
48
+ "@elementor/editor-responsive": "0.12.6",
49
+ "@elementor/editor-styles": "0.5.4",
50
+ "@elementor/editor-styles-repository": "0.6.0",
51
+ "@elementor/editor-ui": "0.1.0",
52
+ "@elementor/editor-v1-adapters": "0.9.1",
53
+ "@elementor/icons": "1.26.0",
54
+ "@elementor/locations": "0.7.6",
53
55
  "@elementor/schema": "0.1.2",
54
56
  "@elementor/session": "0.1.0",
55
- "@elementor/ui": "^1.22.0",
57
+ "@elementor/ui": "1.24.1",
56
58
  "@elementor/utils": "0.3.0",
57
59
  "@wordpress/i18n": "^5.13.0"
58
60
  },
@@ -1,11 +1,9 @@
1
1
  import * as React from 'react';
2
- import { type RefObject } from 'react';
3
- import { type StyleState } from '@elementor/editor-styles';
2
+ import { type StyleDefinitionState } from '@elementor/editor-styles';
4
3
  import { CheckIcon } from '@elementor/icons';
5
- import { createMenu } from '@elementor/menus';
6
4
  import {
7
5
  bindMenu,
8
- Box,
6
+ Divider,
9
7
  ListItemIcon,
10
8
  ListItemText,
11
9
  ListSubheader,
@@ -16,31 +14,17 @@ import {
16
14
  } from '@elementor/ui';
17
15
  import { __ } from '@wordpress/i18n';
18
16
 
19
- import { useCssClassItem } from '../contexts/css-class-item-context';
20
17
  import { useStyle } from '../contexts/style-context';
18
+ import { useUnapplyClass } from '../hooks/use-unapply-class';
21
19
 
22
- export const { useMenuItems: useStateMenuItems, registerStateMenuItem } = createMenu( {
23
- components: {
24
- StateMenuItem,
25
- },
26
- } );
20
+ const STATES: NonNullable< StyleDefinitionState >[] = [ 'hover', 'focus', 'active' ];
27
21
 
28
- export const { useMenuItems: useGlobalClassMenuItems, registerGlobalClassMenuItem } = createMenu( {
29
- components: {
30
- GlobalClassMenuItem,
31
- },
32
- } );
22
+ type CssClassMenuProps = { styleId: string; isFixed: boolean; popupState: PopupState };
33
23
 
34
- export function CssClassMenu( {
35
- popupState,
36
- containerRef,
37
- }: {
38
- popupState: PopupState;
39
- containerRef: RefObject< Element >;
40
- } ) {
41
- const { isGlobal } = useCssClassItem();
42
- const { default: globalClassMenuItems } = useGlobalClassMenuItems();
43
- const { default: stateMenuItems } = useStateMenuItems();
24
+ export function CssClassMenu( { styleId, isFixed = false, popupState }: CssClassMenuProps ) {
25
+ const handleKeyDown = ( e: React.KeyboardEvent< HTMLElement > ) => {
26
+ e.stopPropagation();
27
+ };
44
28
 
45
29
  return (
46
30
  <Menu
@@ -50,39 +34,38 @@ export function CssClassMenu( {
50
34
  vertical: 'top',
51
35
  horizontal: 'right',
52
36
  } }
53
- anchorEl={ containerRef.current }
37
+ onKeyDown={ handleKeyDown }
54
38
  >
55
- { isGlobal && (
56
- <GlobalClassMenuSection>
57
- { globalClassMenuItems.map( ( { id, MenuItem: MenuItemComponent } ) => (
58
- <MenuItemComponent key={ id } />
59
- ) ) }
60
- </GlobalClassMenuSection>
61
- ) }
39
+ { /* It has to be an array since MUI menu doesn't accept a Fragment as a child, and wrapping the items with an HTML element disrupts keyboard navigation */ }
40
+ { ! isFixed && [
41
+ <UnapplyClassMenuItem key="unapply-class" styleId={ styleId } />,
42
+ <Divider key="global-class-items-divider" />,
43
+ ] }
62
44
  <ListSubheader>{ __( 'Add a pseudo selector', 'elementor' ) }</ListSubheader>
63
- { stateMenuItems.map( ( { id, MenuItem: MenuItemComponent } ) => (
64
- <MenuItemComponent key={ id } />
65
- ) ) }
45
+ <StateMenuItem key="normal" state={ null } styleId={ styleId } />
46
+ { STATES.map( ( state ) => {
47
+ return <StateMenuItem key={ state } state={ state } styleId={ styleId } />;
48
+ } ) }
66
49
  </Menu>
67
50
  );
68
51
  }
69
52
 
70
53
  type StateMenuItemProps = {
71
- state: StyleState;
72
- disabled?: boolean;
54
+ state: StyleDefinitionState;
55
+ styleId: string;
73
56
  };
74
57
 
75
- export function StateMenuItem( { state, disabled }: StateMenuItemProps ) {
76
- const { isActive, styleId } = useCssClassItem();
77
- const { setId: setActiveId, setMetaState: setActiveMetaState, meta } = useStyle();
58
+ function StateMenuItem( { state, styleId, ...props }: StateMenuItemProps ) {
59
+ const { id: activeId, setId: setActiveId, setMetaState: setActiveMetaState, meta } = useStyle();
78
60
  const { state: activeState } = meta;
79
61
 
62
+ const isActive = styleId === activeId;
80
63
  const isSelected = state === activeState && isActive;
81
64
 
82
65
  return (
83
66
  <StyledMenuItem
84
- selected={ state === activeState && isActive }
85
- disabled={ disabled }
67
+ { ...props }
68
+ selected={ isSelected }
86
69
  onClick={ () => {
87
70
  if ( ! isActive ) {
88
71
  setActiveId( styleId );
@@ -101,23 +84,16 @@ export function StateMenuItem( { state, disabled }: StateMenuItemProps ) {
101
84
  );
102
85
  }
103
86
 
104
- type GlobalClassMenuItemProps = {
105
- text: string;
106
- onClick: () => void;
107
- };
87
+ function UnapplyClassMenuItem( { styleId, ...props }: { styleId: string } ) {
88
+ const unapplyClass = useUnapplyClass( styleId );
108
89
 
109
- export function GlobalClassMenuItem( { text, onClick }: GlobalClassMenuItemProps ) {
110
90
  return (
111
- <StyledMenuItem onClick={ onClick }>
112
- <ListItemText primary={ text } />
91
+ <StyledMenuItem { ...props } onClick={ unapplyClass }>
92
+ <ListItemText primary="Remove" />
113
93
  </StyledMenuItem>
114
94
  );
115
95
  }
116
96
 
117
- const GlobalClassMenuSection = styled( Box )( ( { theme } ) => ( {
118
- borderBottom: `1px solid ${ theme?.palette.divider }`,
119
- } ) );
120
-
121
97
  const StyledMenuItem = styled( MenuItem )( {
122
98
  '&:hover': {
123
99
  color: 'text.primary', // Overriding global CSS from the editor.
@@ -1,16 +1,19 @@
1
1
  import * as React from 'react';
2
2
  import { useId, useRef } from 'react';
3
- import { getElementSetting, updateSettings, useElementSetting } from '@elementor/editor-elements';
3
+ import { getElementSetting, updateElementSettings, 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
6
  import {
7
7
  ELEMENTS_STYLES_PROVIDER_KEY,
8
+ type StylesProvider,
8
9
  stylesRepository,
9
10
  type UpdateActionPayload,
10
- useAllStylesByProvider,
11
11
  useCreateActionsByProvider,
12
+ useProviders,
12
13
  } from '@elementor/editor-styles-repository';
14
+ import { EllipsisWithTooltip } from '@elementor/editor-ui';
13
15
  import { DotsVerticalIcon } from '@elementor/icons';
16
+ import { createLocation } from '@elementor/locations';
14
17
  import {
15
18
  type AutocompleteRenderGetTagProps,
16
19
  bindTrigger,
@@ -24,10 +27,8 @@ import {
24
27
  import { __ } from '@wordpress/i18n';
25
28
 
26
29
  import { useClassesProp } from '../contexts/classes-prop-context';
27
- import { CssClassItemProvider } from '../contexts/css-class-item-context';
28
30
  import { useElement } from '../contexts/element-context';
29
31
  import { useStyle } from '../contexts/style-context';
30
- import { ConditionalTooltipWrapper } from './conditional-tooltip-wrapper';
31
32
  import { CssClassMenu } from './css-class-menu';
32
33
  import { EditableField, EditableFieldProvider, useEditableField } from './editable-field';
33
34
  import { type Action, MultiCombobox, type Option } from './multi-combobox';
@@ -48,6 +49,8 @@ const EMPTY_OPTION = {
48
49
  provider: ELEMENTS_STYLES_PROVIDER_KEY,
49
50
  } satisfies StyleDefOption;
50
51
 
52
+ export const { Slot: ClassSelectorActionsSlot, inject: injectIntoClassSelectorActions } = createLocation();
53
+
51
54
  /**
52
55
  * Applied - Classes applied to an element.
53
56
  * Active - Class that is currently on edit mode.
@@ -62,16 +65,20 @@ export function CssClassSelector() {
62
65
  const actions = useCreateActions( { pushAppliedId, setActiveId } );
63
66
 
64
67
  const handleApply = useHandleApply( appliedIds, setAppliedIds );
65
- const handleActivate = ( { value }: Option ) => setActiveId( value );
66
68
 
67
69
  const applied = useAppliedOptions( options, appliedIds );
68
70
  const active = applied.find( ( option ) => option.value === activeId ) ?? EMPTY_OPTION;
69
71
 
70
72
  return (
71
73
  <Stack gap={ 1 } p={ 2 }>
72
- <Typography component="label" variant="caption" htmlFor={ ID }>
73
- { __( 'CSS Classes', 'elementor' ) }
74
- </Typography>
74
+ <Stack direction="row" gap={ 1 } alignItems="center" justifyContent="space-between">
75
+ <Typography component="label" variant="caption" htmlFor={ ID }>
76
+ { __( 'CSS Classes', 'elementor' ) }
77
+ </Typography>
78
+ <Stack direction="row" gap={ 1 }>
79
+ <ClassSelectorActionsSlot />
80
+ </Stack>
81
+ </Stack>
75
82
  <MultiCombobox
76
83
  id={ ID }
77
84
  size="tiny"
@@ -87,6 +94,7 @@ export function CssClassSelector() {
87
94
  values.map( ( value, index ) => {
88
95
  const chipProps = getTagProps( { index } );
89
96
  const isActive = value.value === active?.value;
97
+ const isElementsProvider = value.provider === ELEMENTS_STYLES_PROVIDER_KEY;
90
98
 
91
99
  const renameLabel = ( newLabel: string ) => {
92
100
  return updateClassByProvider( value.provider, { label: newLabel, id: value.value } );
@@ -97,7 +105,7 @@ export function CssClassSelector() {
97
105
  key={ chipProps.key }
98
106
  value={ value.label }
99
107
  onSubmit={ renameLabel }
100
- editable={ value.provider !== ELEMENTS_STYLES_PROVIDER_KEY }
108
+ editable={ ! value.fixed }
101
109
  validation={ ( newLabel ) =>
102
110
  renameValidation(
103
111
  newLabel,
@@ -109,10 +117,12 @@ export function CssClassSelector() {
109
117
  label={ value.label }
110
118
  id={ value.value }
111
119
  isActive={ isActive }
112
- isGlobal={ value.color === 'global' }
120
+ isFixed={ value.fixed }
113
121
  color={ isActive && value.color ? value.color : 'default' }
114
122
  chipProps={ chipProps }
115
- onClickActive={ () => handleActivate( value ) }
123
+ // There is only a single local style, which might not exist, so setting it to
124
+ // `null` will either return the actual style or the fallback one.
125
+ onClickActive={ () => setActiveId( isElementsProvider ? null : value.value ) }
116
126
  />
117
127
  </EditableFieldProvider>
118
128
  );
@@ -127,7 +137,7 @@ type CssClassItemProps = {
127
137
  id: string;
128
138
  label: string;
129
139
  isActive: boolean;
130
- isGlobal: boolean;
140
+ isFixed?: boolean;
131
141
  color: ChipOwnProps[ 'color' ];
132
142
  chipProps: ReturnType< AutocompleteRenderGetTagProps >;
133
143
  onClickActive: ( id: string ) => void;
@@ -139,7 +149,7 @@ export function CssClassItem( {
139
149
  id,
140
150
  label,
141
151
  isActive,
142
- isGlobal,
152
+ isFixed = false,
143
153
  color: colorProp,
144
154
  chipProps,
145
155
  onClickActive,
@@ -155,7 +165,7 @@ export function CssClassItem( {
155
165
  const color = error ? 'error' : colorProp;
156
166
 
157
167
  return (
158
- <CssClassItemProvider styleId={ id } isActive={ isActive } isGlobal={ isGlobal }>
168
+ <>
159
169
  <UnstableChipGroup ref={ chipRef } { ...chipGroupProps } aria-label={ `Edit ${ label }` } role="group">
160
170
  <Chip
161
171
  disabled={ submitting }
@@ -173,7 +183,7 @@ export function CssClassItem( {
173
183
  }
174
184
  } }
175
185
  >
176
- <ConditionalTooltipWrapper maxWidth="10ch" title={ label } />
186
+ <EllipsisWithTooltip maxWidth="10ch" title={ label } />
177
187
  </EditableField>
178
188
  }
179
189
  variant={ isActive && ! meta.state ? 'filled' : 'standard' }
@@ -203,8 +213,8 @@ export function CssClassItem( {
203
213
  />
204
214
  ) }
205
215
  </UnstableChipGroup>
206
- <CssClassMenu popupState={ popupState } containerRef={ chipRef } />
207
- </CssClassItemProvider>
216
+ <CssClassMenu styleId={ id } popupState={ popupState } isFixed={ isFixed } />
217
+ </>
208
218
  );
209
219
  }
210
220
 
@@ -243,9 +253,13 @@ const isCharactersNotSupported = ( newLabel: string ) => ! VALID_SELECTOR_REGEX.
243
253
  function useOptions() {
244
254
  const { element } = useElement();
245
255
 
246
- return useAllStylesByProvider( { elementId: element.id } ).flatMap< StyleDefOption >(
247
- ( [ provider, styleDefs ] ) => {
256
+ const isProviderEditable = ( provider: StylesProvider ) => !! provider.actions.updateProps;
257
+
258
+ return useProviders()
259
+ .filter( isProviderEditable )
260
+ .flatMap< StyleDefOption >( ( provider ) => {
248
261
  const isElements = provider.key === ELEMENTS_STYLES_PROVIDER_KEY;
262
+ const styleDefs = provider.actions.get( { elementId: element.id } );
249
263
 
250
264
  // Add empty local option for elements, as fallback.
251
265
  if ( isElements && styleDefs.length === 0 ) {
@@ -262,8 +276,7 @@ function useOptions() {
262
276
  group: provider.labels?.plural,
263
277
  };
264
278
  } );
265
- }
266
- );
279
+ } );
267
280
  }
268
281
 
269
282
  function useCreateActions( {
@@ -321,7 +334,7 @@ function useAppliedClassesIds() {
321
334
  const value = useElementSetting< ClassesPropValue >( element.id, currentClassesProp )?.value || [];
322
335
 
323
336
  const setValue = ( ids: StyleDefinitionID[] ) => {
324
- updateSettings( {
337
+ updateElementSettings( {
325
338
  id: element.id,
326
339
  props: {
327
340
  [ currentClassesProp ]: classesPropTypeUtil.create( ids ),
@@ -86,7 +86,7 @@ export const EditableFieldProvider = ( {
86
86
 
87
87
  type EditableFieldProps = ComponentProps< 'div' >;
88
88
 
89
- export const EditableField = ( { children, ...props }: EditableFieldProps ) => {
89
+ export const EditableField = ( { children, onClick, ...props }: EditableFieldProps ) => {
90
90
  const ref = useRef< HTMLElement >( null );
91
91
  const { isEditing, closeEditMode, value, onChange, error, submit, editable } = useEditableField();
92
92
 
@@ -110,6 +110,14 @@ export const EditableField = ( { children, ...props }: EditableFieldProps ) => {
110
110
  }
111
111
  };
112
112
 
113
+ const handleClick = ( event: React.MouseEvent< HTMLDivElement > ) => {
114
+ if ( isEditing ) {
115
+ event.stopPropagation();
116
+ }
117
+
118
+ onClick?.( event );
119
+ };
120
+
113
121
  const selectAll = () => {
114
122
  const selection = getSelection();
115
123
 
@@ -131,7 +139,7 @@ export const EditableField = ( { children, ...props }: EditableFieldProps ) => {
131
139
  return (
132
140
  <Tooltip open={ !! error } title={ error } placement="top">
133
141
  { /* eslint-disable-next-line jsx-a11y/no-static-element-interactions */ }
134
- <div onKeyDown={ handleKeyDown } { ...props }>
142
+ <div onKeyDown={ handleKeyDown } onClick={ handleClick } { ...props }>
135
143
  <span
136
144
  ref={ ref }
137
145
  role="textbox"
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { useMemo, useState } from 'react';
2
3
  import {
3
4
  ControlLabel,
4
5
  ControlToggleButtonGroup,
@@ -50,8 +51,8 @@ export const FlexSizeField = () => {
50
51
  shrink = shrinkField?.value || null,
51
52
  basis = basisField?.value || null;
52
53
 
53
- const currentGroup = React.useMemo( () => getActiveGroup( { grow, shrink, basis } ), [ grow, shrink, basis ] ),
54
- [ activeGroup, setActiveGroup ] = React.useState( currentGroup );
54
+ const currentGroup = useMemo( () => getActiveGroup( { grow, shrink, basis } ), [ grow, shrink, basis ] ),
55
+ [ activeGroup, setActiveGroup ] = useState( currentGroup );
55
56
 
56
57
  const onChangeGroup = ( group: GroupItem | null = null ) => {
57
58
  setActiveGroup( group );
@@ -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 { type StyleDefinitionID, type StyleState } from '@elementor/editor-styles';
6
+ import { type StyleDefinitionID, type StyleDefinitionState } from '@elementor/editor-styles';
7
7
  import { SessionStorageProvider } from '@elementor/session';
8
8
  import { Divider } from '@elementor/ui';
9
9
  import { __ } from '@wordpress/i18n';
@@ -28,7 +28,7 @@ const CLASSES_PROP_KEY = 'classes';
28
28
  export const StyleTab = () => {
29
29
  const currentClassesProp = useCurrentClassesProp();
30
30
  const [ activeStyleDefId, setActiveStyleDefId ] = useActiveStyleDefId( currentClassesProp );
31
- const [ activeStyleState, setActiveStyleState ] = useState< StyleState | null >( null );
31
+ const [ activeStyleState, setActiveStyleState ] = useState< StyleDefinitionState | null >( null );
32
32
  const breakpoint = useActiveBreakpoint();
33
33
 
34
34
  return (
@@ -1,20 +1,43 @@
1
1
  import * as React from 'react';
2
2
  import { createContext, type Dispatch, type PropsWithChildren, useContext } from 'react';
3
- import { type StyleDefinition, type StyleState, type StyleVariant } from '@elementor/editor-styles';
3
+ import {
4
+ type StyleDefinition,
5
+ type StyleDefinitionID,
6
+ type StyleDefinitionState,
7
+ type StyleDefinitionVariant,
8
+ } from '@elementor/editor-styles';
9
+ import { type StylesProvider, stylesRepository } from '@elementor/editor-styles-repository';
10
+
11
+ import { StylesProviderNotFoundError } from '../errors';
4
12
 
5
13
  type ContextValue = {
6
- id: StyleDefinition[ 'id' ] | null;
7
14
  setId: Dispatch< StyleDefinition[ 'id' ] | null >;
8
- meta: StyleVariant[ 'meta' ];
9
- setMetaState: Dispatch< StyleState >;
15
+ meta: StyleDefinitionVariant[ 'meta' ];
16
+ setMetaState: Dispatch< StyleDefinitionState >;
17
+ } & ( ContextValueWithProvider | ContextValueWithoutProvider );
18
+
19
+ type ContextValueWithProvider = {
20
+ id: StyleDefinitionID;
21
+ provider: StylesProvider;
22
+ };
23
+
24
+ type ContextValueWithoutProvider = {
25
+ id: null;
26
+ provider: null;
10
27
  };
11
28
 
12
29
  const Context = createContext< ContextValue | null >( null );
13
30
 
14
- type Props = PropsWithChildren< ContextValue >;
31
+ type Props = PropsWithChildren< Omit< ContextValue, 'provider' > >;
32
+
33
+ export function StyleProvider( { children, ...props }: Props ) {
34
+ const provider = props.id === null ? null : getProviderByStyleId( props.id );
15
35
 
16
- export function StyleProvider( { children, id, setId, meta, setMetaState }: Props ) {
17
- return <Context.Provider value={ { id, setId, meta, setMetaState } }>{ children }</Context.Provider>;
36
+ if ( props.id && ! provider ) {
37
+ throw new StylesProviderNotFoundError( { context: { styleId: props.id } } );
38
+ }
39
+
40
+ return <Context.Provider value={ { ...props, provider } as ContextValue }>{ children }</Context.Provider>;
18
41
  }
19
42
 
20
43
  export function useStyle() {
@@ -26,3 +49,11 @@ export function useStyle() {
26
49
 
27
50
  return context;
28
51
  }
52
+
53
+ function getProviderByStyleId( styleId: StyleDefinitionID ) {
54
+ const styleProvider = stylesRepository.getProviders().find( ( provider ) => {
55
+ return provider.actions.get().find( ( style ) => style.id === styleId );
56
+ } );
57
+
58
+ return styleProvider ?? null;
59
+ }
@@ -1,18 +1,9 @@
1
1
  import * as React from 'react';
2
2
  import type { ComponentProps } from 'react';
3
- import { createError } from '@elementor/utils';
4
3
 
4
+ import { ControlTypeNotFoundError } from '../errors';
5
5
  import { type ControlType, type ControlTypes, getControlByType } from './controls-registry';
6
6
 
7
- export type ControlTypeErrorContext = {
8
- type: string;
9
- };
10
-
11
- const ControlTypeError = createError< ControlTypeErrorContext >( {
12
- code: 'CONTROL_TYPE_NOT_FOUND',
13
- message: `Control type not found.`,
14
- } );
15
-
16
7
  type IsRequired< T, K extends keyof T > = object extends Pick< T, K > ? false : true;
17
8
 
18
9
  type AnyPropertyRequired< T > = {
@@ -35,8 +26,8 @@ export const Control = < T extends ControlType >( { props, type }: ControlProps<
35
26
  const ControlByType = getControlByType( type );
36
27
 
37
28
  if ( ! ControlByType ) {
38
- throw new ControlTypeError( {
39
- context: { type },
29
+ throw new ControlTypeNotFoundError( {
30
+ context: { controlType: type },
40
31
  } );
41
32
  }
42
33
 
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { PropKeyProvider, PropProvider } from '@elementor/editor-controls';
3
- import { updateSettings, useElementSetting } from '@elementor/editor-elements';
3
+ import { updateElementSettings, 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';
@@ -20,7 +20,7 @@ export const SettingsField = ( { bind, children }: Props ) => {
20
20
  const propType = createTopLevelOjectType( { schema: elementType.propsSchema } );
21
21
 
22
22
  const setValue = ( newValue: Record< string, PropValue > ) => {
23
- updateSettings( {
23
+ updateElementSettings( {
24
24
  id: element.id,
25
25
  props: { ...newValue },
26
26
  } );
package/src/errors.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { type StyleDefinitionID } from '@elementor/editor-styles';
2
+ import { createError } from '@elementor/utils';
3
+
4
+ export const ControlTypeNotFoundError = createError< { controlType: string } >( {
5
+ code: 'control_type_not_found',
6
+ message: 'Control type not found.',
7
+ } );
8
+
9
+ export const StylesProviderNotFoundError = createError< { styleId: StyleDefinitionID } >( {
10
+ code: 'provider_not_found',
11
+ message: 'Styles provider not found.',
12
+ } );
13
+
14
+ export const StylesProviderCannotUpdatePropsError = createError< { providerKey: string } >( {
15
+ code: 'provider_cannot_update_props',
16
+ message: "Styles provider doesn't support updating props.",
17
+ } );
18
+
19
+ export const StyleNotFoundUnderProviderError = createError< { styleId: StyleDefinitionID; providerKey: string } >( {
20
+ code: 'style_not_found_under_provider',
21
+ message: 'Style not found under the provider.',
22
+ } );
@@ -1,33 +1,81 @@
1
- import { updateStyle, useElementStyleProps } from '@elementor/editor-elements';
1
+ import { useEffect, useReducer } from 'react';
2
+ import { createElementStyle, type ElementID } from '@elementor/editor-elements';
2
3
  import type { Props } from '@elementor/editor-props';
4
+ import { getVariantByMeta, type StyleDefinition, type StyleDefinitionVariant } from '@elementor/editor-styles';
5
+ import { type StylesProvider } from '@elementor/editor-styles-repository';
3
6
  import { __ } from '@wordpress/i18n';
4
7
 
5
8
  import { useClassesProp } from '../contexts/classes-prop-context';
6
9
  import { useElement } from '../contexts/element-context';
7
10
  import { useStyle } from '../contexts/style-context';
11
+ import { StyleNotFoundUnderProviderError, StylesProviderCannotUpdatePropsError } from '../errors';
8
12
 
9
13
  export function useStylesFields< T extends Props >( propNames: ( keyof T & string )[] ) {
10
14
  const { element } = useElement();
11
- const { id, meta } = useStyle();
15
+ const { id, meta, provider } = useStyle();
12
16
  const classesProp = useClassesProp();
13
17
 
14
- const value = useElementStyleProps( {
15
- elementID: element.id,
16
- styleDefID: id,
18
+ const [ , reRender ] = useReducer( ( p ) => ! p, false );
19
+
20
+ useEffect( () => provider?.subscribe( reRender ), [ provider ] );
21
+
22
+ const value = getProps< T >( {
23
+ elementId: element.id,
24
+ styleId: id,
25
+ provider,
17
26
  meta,
18
27
  propNames,
19
28
  } );
20
29
 
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
+ const setValue = ( props: T ) => {
31
+ if ( id === null ) {
32
+ createElementStyle( {
33
+ elementId: element.id,
34
+ classesProp,
35
+ meta,
36
+ props,
37
+ label: __( 'local', 'elementor' ),
38
+ } );
39
+
40
+ return;
41
+ }
42
+
43
+ if ( ! provider.actions.updateProps ) {
44
+ throw new StylesProviderCannotUpdatePropsError( { context: { providerKey: provider.key } } );
45
+ }
46
+
47
+ provider.actions.updateProps( { id, meta, props }, { elementId: element.id } );
30
48
  };
31
49
 
32
50
  return [ value, setValue ] as const;
33
51
  }
52
+
53
+ type GetPropsArgs = {
54
+ provider: StylesProvider | null;
55
+ styleId: StyleDefinition[ 'id' ] | null;
56
+ elementId: ElementID;
57
+ meta: StyleDefinitionVariant[ 'meta' ];
58
+ propNames: string[];
59
+ };
60
+
61
+ type NullableValues< T extends Props > = {
62
+ [ K in keyof T ]: T[ K ] | null;
63
+ };
64
+
65
+ function getProps< T extends Props >( { styleId, elementId, provider, meta, propNames }: GetPropsArgs ) {
66
+ if ( ! provider || ! styleId ) {
67
+ return null;
68
+ }
69
+
70
+ const style = provider.actions.getById?.( styleId, { elementId } );
71
+
72
+ if ( ! style ) {
73
+ throw new StyleNotFoundUnderProviderError( { context: { styleId, providerKey: provider.key } } );
74
+ }
75
+
76
+ const variant = getVariantByMeta( style, meta );
77
+
78
+ return Object.fromEntries(
79
+ propNames.map( ( key ) => [ key, variant?.props[ key ] ?? null ] )
80
+ ) as NullableValues< T >;
81
+ }