@elementor/editor-editing-panel 1.24.0 → 1.27.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.24.0",
3
+ "version": "1.27.0",
4
4
  "private": false,
5
5
  "author": "Elementor Team",
6
6
  "homepage": "https://elementor.com/",
@@ -40,15 +40,16 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@elementor/editor": "0.18.5",
43
- "@elementor/editor-controls": "0.21.0",
43
+ "@elementor/editor-canvas": "0.17.0",
44
+ "@elementor/editor-controls": "0.24.0",
44
45
  "@elementor/editor-current-user": "0.3.0",
45
- "@elementor/editor-elements": "0.7.0",
46
+ "@elementor/editor-elements": "0.8.0",
46
47
  "@elementor/editor-panels": "0.14.0",
47
48
  "@elementor/editor-props": "0.11.1",
48
- "@elementor/editor-responsive": "0.13.3",
49
- "@elementor/editor-styles": "0.6.4",
50
- "@elementor/editor-styles-repository": "0.8.1",
51
- "@elementor/editor-ui": "0.5.1",
49
+ "@elementor/editor-responsive": "0.13.4",
50
+ "@elementor/editor-styles": "0.6.5",
51
+ "@elementor/editor-styles-repository": "0.8.3",
52
+ "@elementor/editor-ui": "0.7.0",
52
53
  "@elementor/editor-v1-adapters": "0.11.0",
53
54
  "@elementor/icons": "1.37.0",
54
55
  "@elementor/locations": "0.7.7",
@@ -154,11 +154,11 @@ export function CssClassItem( {
154
154
 
155
155
  const validateLabel = ( newLabel: string ) => {
156
156
  if ( ! stylesRepository.isLabelValid( newLabel ) ) {
157
- return __( 'Format is not valid', 'elementor' );
157
+ return __( 'Invalid format', 'elementor' );
158
158
  }
159
159
 
160
160
  if ( stylesRepository.isLabelExist( newLabel ) ) {
161
- return __( 'Existing name', 'elementor' );
161
+ return __( 'Name exists', 'elementor' );
162
162
  }
163
163
 
164
164
  return null;
@@ -1,12 +1,14 @@
1
1
  import * as React from 'react';
2
2
  import { type StyleDefinitionState } from '@elementor/editor-styles';
3
- import { stylesRepository } from '@elementor/editor-styles-repository';
3
+ import { ELEMENTS_STYLES_PROVIDER_KEY, stylesRepository } from '@elementor/editor-styles-repository';
4
4
  import { MenuListItem } from '@elementor/editor-ui';
5
- import { bindMenu, Divider, Menu, MenuSubheader, type PopupState } from '@elementor/ui';
5
+ import { bindMenu, Divider, Menu, MenuSubheader, type PopupState, Stack } from '@elementor/ui';
6
6
  import { __ } from '@wordpress/i18n';
7
7
 
8
8
  import { useStyle } from '../../contexts/style-context';
9
9
  import { useUnapplyClass } from '../../hooks/use-unapply-class';
10
+ import { type StyleDefinitionStateWithNormal } from '../../styles-inheritance/types';
11
+ import { StyleIndicator, type StyleIndicatorVariant } from '../style-indicator';
10
12
 
11
13
  const STATES: NonNullable< StyleDefinitionState >[] = [ 'hover', 'focus', 'active' ];
12
14
 
@@ -19,13 +21,17 @@ type CssClassMenuProps = {
19
21
  };
20
22
 
21
23
  export function CssClassMenu( { styleId, provider, popupState, handleRename, anchorEl }: CssClassMenuProps ) {
24
+ const styledStates = useStyledStates( styleId );
25
+
26
+ const indicatorVariant = provider === ELEMENTS_STYLES_PROVIDER_KEY ? 'local' : 'global';
27
+
22
28
  const handleKeyDown = ( e: React.KeyboardEvent< HTMLElement > ) => {
23
29
  e.stopPropagation();
24
30
  };
25
31
 
26
32
  return (
27
33
  <Menu
28
- MenuListProps={ { dense: true } }
34
+ MenuListProps={ { dense: true, sx: { minWidth: '160px' } } }
29
35
  { ...bindMenu( popupState ) }
30
36
  anchorEl={ anchorEl }
31
37
  anchorOrigin={ {
@@ -41,18 +47,44 @@ export function CssClassMenu( { styleId, provider, popupState, handleRename, anc
41
47
  { /* 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 */ }
42
48
  { getMenuItemsByProvider( { provider, styleId, handleRename, closeMenu: popupState.close } ) }
43
49
  <MenuSubheader sx={ { typography: 'caption', color: 'text.secondary', pb: 0.5, pt: 1 } }>
44
- { __( 'Pseudo classes', 'elementor' ) }
50
+ { __( 'States', 'elementor' ) }
45
51
  </MenuSubheader>
46
- <StateMenuItem key="normal" state={ null } styleId={ styleId } closeMenu={ popupState.close } />
52
+ <StateMenuItem
53
+ key="normal"
54
+ state={ null }
55
+ styleId={ styleId }
56
+ closeMenu={ popupState.close }
57
+ isStyled={ styledStates.normal }
58
+ indicatorVariant={ indicatorVariant }
59
+ />
47
60
  { STATES.map( ( state ) => {
48
61
  return (
49
- <StateMenuItem key={ state } state={ state } styleId={ styleId } closeMenu={ popupState.close } />
62
+ <StateMenuItem
63
+ key={ state }
64
+ state={ state }
65
+ styleId={ styleId }
66
+ closeMenu={ popupState.close }
67
+ isStyled={ styledStates[ state ] }
68
+ indicatorVariant={ indicatorVariant }
69
+ />
50
70
  );
51
71
  } ) }
52
72
  </Menu>
53
73
  );
54
74
  }
55
75
 
76
+ function useStyledStates( styleId: string | null ): Partial< Record< StyleDefinitionStateWithNormal, true > > {
77
+ const { meta } = useStyle();
78
+
79
+ const styleDef = stylesRepository.all().find( ( style ) => style.id === styleId );
80
+
81
+ return Object.fromEntries(
82
+ styleDef?.variants
83
+ .filter( ( variant ) => meta.breakpoint === variant.meta.breakpoint )
84
+ .map( ( variant ) => [ variant.meta.state ?? 'normal', true ] ) ?? []
85
+ );
86
+ }
87
+
56
88
  function getMenuItemsByProvider( {
57
89
  provider,
58
90
  styleId,
@@ -82,7 +114,7 @@ function getMenuItemsByProvider( {
82
114
  actions.unshift(
83
115
  <MenuSubheader
84
116
  key="provider-label"
85
- sx={ { typography: 'caption', color: 'text.secondary', pb: 0.5, pt: 1 } }
117
+ sx={ { typography: 'caption', color: 'text.secondary', pb: 0.5, pt: 1, textTransform: 'capitalize' } }
86
118
  >
87
119
  { providerInstance?.labels?.singular }
88
120
  </MenuSubheader>
@@ -97,9 +129,18 @@ type StateMenuItemProps = {
97
129
  state: StyleDefinitionState;
98
130
  styleId: string | null;
99
131
  closeMenu: () => void;
132
+ isStyled?: boolean;
133
+ indicatorVariant: StyleIndicatorVariant;
100
134
  };
101
135
 
102
- function StateMenuItem( { state, styleId, closeMenu, ...props }: StateMenuItemProps ) {
136
+ function StateMenuItem( {
137
+ state,
138
+ styleId,
139
+ closeMenu,
140
+ isStyled = false,
141
+ indicatorVariant,
142
+ ...props
143
+ }: StateMenuItemProps ) {
103
144
  const { id: activeId, setId: setActiveId, setMetaState: setActiveMetaState, meta } = useStyle();
104
145
  const { state: activeState } = meta;
105
146
 
@@ -121,7 +162,12 @@ function StateMenuItem( { state, styleId, closeMenu, ...props }: StateMenuItemPr
121
162
  closeMenu();
122
163
  } }
123
164
  >
124
- { state ? state : 'Normal' }
165
+ <Stack gap={ 0.75 } direction="row" alignItems="center">
166
+ { isStyled && (
167
+ <StyleIndicator aria-label={ __( 'Has style', 'elementor' ) } variant={ indicatorVariant } />
168
+ ) }
169
+ { state ?? 'normal' }
170
+ </Stack>
125
171
  </MenuListItem>
126
172
  );
127
173
  }
@@ -64,7 +64,7 @@ export function CssClassSelector() {
64
64
  <Stack gap={ 1 } p={ 2 }>
65
65
  <Stack direction="row" gap={ 1 } alignItems="center" justifyContent="space-between">
66
66
  <Typography component="label" variant="caption" htmlFor={ ID }>
67
- { __( 'CSS classes', 'elementor' ) }
67
+ { __( 'Classes', 'elementor' ) }
68
68
  </Typography>
69
69
  <Stack direction="row" gap={ 1 }>
70
70
  <ClassSelectorActionsSlot />
@@ -148,7 +148,8 @@ function useOptions() {
148
148
  color: isElements ? 'accent' : 'global',
149
149
  icon: isElements ? <MapPinIcon /> : null,
150
150
  provider: provider.key,
151
- group: provider.labels?.plural,
151
+ // translators: %s is the plural label of the provider (e.g "Existing classes").
152
+ group: __( 'Existing %s', 'elementor' ).replace( '%s', provider.labels?.plural ?? '' ),
152
153
  };
153
154
  } );
154
155
  } );
@@ -164,9 +165,9 @@ function useCreateActions( {
164
165
  return useCreateActionsByProvider().map( ( [ provider, create ] ): Action< StyleDefOption > => {
165
166
  return {
166
167
  // translators: %s is the label of the new class.
167
- label: ( value ) => __( 'Create new "%s"', 'elementor' ).replace( '%s', value ),
168
- // translators: %s is the singular label of css class provider (e.g "Global CSS Class").
169
- group: __( 'Create New %s', 'elementor' ).replace( '%s', provider.labels?.singular ?? '' ),
168
+ label: ( value ) => __( 'Create "%s"', 'elementor' ).replace( '%s', value ),
169
+ // translators: %s is the singular label of css class provider (e.g "CSS Class").
170
+ group: __( 'Create a new %s', 'elementor' ).replace( '%s', provider.labels?.singular ?? '' ),
170
171
  condition: ( _, inputValue ) => isLabelValid( inputValue ) && ! hasReachedLimit( provider ),
171
172
  apply: ( label ) => {
172
173
  const createdId = create( label );
@@ -19,14 +19,7 @@ export const EditingPanelTabs = () => {
19
19
  // Reference: https://react.dev/learn/preserving-and-resetting-state#resetting-a-form-with-a-key
20
20
  <Fragment key={ element.id }>
21
21
  <Stack direction="column" sx={ { width: '100%' } }>
22
- <Tabs
23
- variant="fullWidth"
24
- indicatorColor="secondary"
25
- textColor="inherit"
26
- size="small"
27
- sx={ { mt: 0.5 } }
28
- { ...getTabsProps() }
29
- >
22
+ <Tabs variant="fullWidth" size="small" sx={ { mt: 0.5 } } { ...getTabsProps() }>
30
23
  <Tab label={ __( 'General', 'elementor' ) } { ...getTabProps( 'settings' ) } />
31
24
  <Tab label={ __( 'Style', 'elementor' ) } { ...getTabProps( 'style' ) } />
32
25
  </Tabs>
@@ -0,0 +1,23 @@
1
+ import { styled } from '@elementor/ui';
2
+
3
+ export type StyleIndicatorVariant = 'overridden' | 'local' | 'global';
4
+
5
+ export const StyleIndicator = styled( 'div', {
6
+ shouldForwardProp: ( prop ) => prop !== 'variant',
7
+ } )< { variant?: StyleIndicatorVariant } >`
8
+ width: 5px;
9
+ height: 5px;
10
+ border-radius: 50%;
11
+ background-color: ${ ( { theme, variant } ) => {
12
+ switch ( variant ) {
13
+ case 'overridden':
14
+ return theme.palette.warning.light;
15
+ case 'global':
16
+ return theme.palette.global.dark;
17
+ case 'local':
18
+ return theme.palette.accent.main;
19
+ default:
20
+ return theme.palette.text.disabled;
21
+ }
22
+ } };
23
+ `;
@@ -13,7 +13,6 @@ import { __ } from '@wordpress/i18n';
13
13
 
14
14
  import { StylesField } from '../../../controls-registry/styles-field';
15
15
  import { useDirection } from '../../../hooks/use-direction';
16
- import { RotatedIcon } from '../layout-section/utils/rotated-icon';
17
16
 
18
17
  const StartStartIcon = withDirection( RadiusTopLeftIcon );
19
18
  const StartEndIcon = withDirection( RadiusTopRightIcon );
@@ -32,22 +31,22 @@ const getEndEndLabel = ( isSiteRtl: boolean ) =>
32
31
  const getCorners = ( isSiteRtl: boolean ): EqualUnequalItems => [
33
32
  {
34
33
  label: getStartStartLabel( isSiteRtl ),
35
- icon: <RotatedIcon icon={ StartStartIcon } size="tiny" />,
34
+ icon: <StartStartIcon fontSize={ 'tiny' } />,
36
35
  bind: 'start-start',
37
36
  },
38
37
  {
39
38
  label: getStartEndLabel( isSiteRtl ),
40
- icon: <RotatedIcon icon={ StartEndIcon } size="tiny" />,
39
+ icon: <StartEndIcon fontSize={ 'tiny' } />,
41
40
  bind: 'start-end',
42
41
  },
43
42
  {
44
43
  label: getEndStartLabel( isSiteRtl ),
45
- icon: <RotatedIcon icon={ EndStartIcon } size="tiny" />,
44
+ icon: <EndStartIcon fontSize={ 'tiny' } />,
46
45
  bind: 'end-start',
47
46
  },
48
47
  {
49
48
  label: getEndEndLabel( isSiteRtl ),
50
- icon: <RotatedIcon icon={ EndEndIcon } size="tiny" />,
49
+ icon: <EndEndIcon fontSize={ 'tiny' } />,
51
50
  bind: 'end-end',
52
51
  },
53
52
  ];
@@ -7,7 +7,6 @@ import { __ } from '@wordpress/i18n';
7
7
 
8
8
  import { StylesField } from '../../../controls-registry/styles-field';
9
9
  import { useDirection } from '../../../hooks/use-direction';
10
- import { RotatedIcon } from '../layout-section/utils/rotated-icon';
11
10
 
12
11
  const InlineStartIcon = withDirection( SideRightIcon );
13
12
  const InlineEndIcon = withDirection( SideLeftIcon );
@@ -20,7 +19,7 @@ const getEdges = ( isSiteRtl: boolean ): EqualUnequalItems => [
20
19
  },
21
20
  {
22
21
  label: isSiteRtl ? __( 'Left', 'elementor' ) : __( 'Right', 'elementor' ),
23
- icon: <RotatedIcon icon={ InlineStartIcon } size="tiny" />,
22
+ icon: <InlineStartIcon fontSize={ 'tiny' } />,
24
23
  bind: 'inline-end',
25
24
  },
26
25
  {
@@ -30,7 +29,7 @@ const getEdges = ( isSiteRtl: boolean ): EqualUnequalItems => [
30
29
  },
31
30
  {
32
31
  label: isSiteRtl ? __( 'Right', 'elementor' ) : __( 'Left', 'elementor' ),
33
- icon: <RotatedIcon icon={ InlineEndIcon } size="tiny" />,
32
+ icon: <InlineEndIcon fontSize={ 'tiny' } />,
34
33
  bind: 'inline-start',
35
34
  },
36
35
  ];
@@ -6,14 +6,14 @@ import {
6
6
  SizeControl,
7
7
  type ToggleButtonGroupItem,
8
8
  } from '@elementor/editor-controls';
9
- import type { NumberPropValue, SizePropValue } from '@elementor/editor-props';
9
+ import { numberPropTypeUtil, type NumberPropValue, type SizePropValue } from '@elementor/editor-props';
10
10
  import { ExpandIcon, PencilIcon, ShrinkIcon } from '@elementor/icons';
11
- import { DirectionProvider, Grid, ThemeProvider, Typography } from '@elementor/ui';
11
+ import { DirectionProvider, Grid, ThemeProvider } from '@elementor/ui';
12
12
  import { __ } from '@wordpress/i18n';
13
13
 
14
14
  import { StylesField } from '../../../controls-registry/styles-field';
15
15
  import { useDirection } from '../../../hooks/use-direction';
16
- import { useStylesField } from '../../../hooks/use-styles-field';
16
+ import { useStylesFields } from '../../../hooks/use-styles-fields';
17
17
  import { ControlLabel } from '../../control-label';
18
18
  import { SectionContent } from '../../section-content';
19
19
 
@@ -43,45 +43,55 @@ const items: ToggleButtonGroupItem< GroupItem >[] = [
43
43
  ];
44
44
 
45
45
  export const FlexSizeField = () => {
46
- const { isSiteRtl } = useDirection(),
47
- [ growField, setGrowField ] = useStylesField< NumberPropValue | null >( 'flex-grow' ),
48
- [ shrinkField, setShrinkField ] = useStylesField< NumberPropValue | null >( 'flex-shrink' ),
49
- [ basisField, setBasisField ] = useStylesField< SizePropValue | null >( 'flex-basis' );
46
+ const { isSiteRtl } = useDirection();
50
47
 
51
- const grow = growField?.value || null,
52
- shrink = shrinkField?.value || null,
53
- basis = basisField?.value || null;
48
+ const [ fields, setFields ] = useStylesFields< {
49
+ 'flex-grow': NumberPropValue | null;
50
+ 'flex-shrink': NumberPropValue | null;
51
+ 'flex-basis': SizePropValue | null;
52
+ } >( [ 'flex-grow', 'flex-shrink', 'flex-basis' ] );
53
+
54
+ const grow = fields?.[ 'flex-grow' ]?.value || null;
55
+ const shrink = fields?.[ 'flex-shrink' ]?.value || null;
56
+ const basis = fields?.[ 'flex-basis' ]?.value || null;
54
57
 
55
58
  const currentGroup = useMemo( () => getActiveGroup( { grow, shrink, basis } ), [ grow, shrink, basis ] ),
56
59
  [ activeGroup, setActiveGroup ] = useState( currentGroup );
57
60
 
58
61
  const onChangeGroup = ( group: GroupItem | null = null ) => {
59
62
  setActiveGroup( group );
60
- setBasisField( null );
61
63
 
62
64
  if ( ! group || group === 'custom' ) {
63
- setGrowField( null );
64
- setShrinkField( null );
65
+ setFields( {
66
+ 'flex-basis': null,
67
+ 'flex-grow': null,
68
+ 'flex-shrink': null,
69
+ } );
65
70
 
66
71
  return;
67
72
  }
68
73
 
69
74
  if ( group === 'flex-grow' ) {
70
- setGrowField( { $$type: 'number', value: DEFAULT } );
71
- setShrinkField( null );
75
+ setFields( {
76
+ 'flex-basis': null,
77
+ 'flex-grow': numberPropTypeUtil.create( DEFAULT ),
78
+ 'flex-shrink': null,
79
+ } );
72
80
 
73
81
  return;
74
82
  }
75
83
 
76
- setGrowField( null );
77
- setShrinkField( { $$type: 'number', value: DEFAULT } );
84
+ setFields( {
85
+ 'flex-basis': null,
86
+ 'flex-grow': null,
87
+ 'flex-shrink': numberPropTypeUtil.create( DEFAULT ),
88
+ } );
78
89
  };
79
90
 
80
91
  return (
81
92
  <DirectionProvider rtl={ isSiteRtl }>
82
93
  <ThemeProvider>
83
94
  <SectionContent>
84
- <Typography fontSize="large">{ activeGroup }</Typography>
85
95
  <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
86
96
  <Grid item xs={ 6 }>
87
97
  <StylesField bind={ activeGroup ?? '' }>
@@ -6,18 +6,17 @@ import { __ } from '@wordpress/i18n';
6
6
 
7
7
  import { StylesField } from '../../../controls-registry/styles-field';
8
8
  import { ControlLabel } from '../../control-label';
9
- import { RotatedIcon } from '../layout-section/utils/rotated-icon';
10
9
 
11
10
  type Alignments = 'start' | 'center' | 'end' | 'justify';
12
11
 
13
- const StartIcon = withDirection( AlignLeftIcon );
14
- const EndIcon = withDirection( AlignRightIcon );
12
+ const AlignStartIcon = withDirection( AlignLeftIcon );
13
+ const AlignEndIcon = withDirection( AlignRightIcon );
15
14
 
16
15
  const options: ToggleButtonGroupItem< Alignments >[] = [
17
16
  {
18
17
  value: 'start',
19
18
  label: __( 'Start', 'elementor' ),
20
- renderContent: () => <RotatedIcon icon={ StartIcon } size="tiny" />,
19
+ renderContent: ( { size } ) => <AlignStartIcon fontSize={ size } />,
21
20
  showTooltip: true,
22
21
  },
23
22
  {
@@ -29,7 +28,7 @@ const options: ToggleButtonGroupItem< Alignments >[] = [
29
28
  {
30
29
  value: 'end',
31
30
  label: __( 'End', 'elementor' ),
32
- renderContent: () => <RotatedIcon icon={ EndIcon } size="tiny" />,
31
+ renderContent: ( { size } ) => <AlignEndIcon fontSize={ size } />,
33
32
  showTooltip: true,
34
33
  },
35
34
  {
@@ -45,7 +44,7 @@ export const TextAlignmentField = () => {
45
44
  <StylesField bind={ 'text-align' }>
46
45
  <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
47
46
  <Grid item xs={ 6 }>
48
- <ControlLabel>{ __( 'Alignment', 'elementor' ) }</ControlLabel>
47
+ <ControlLabel>{ __( 'Text align', 'elementor' ) }</ControlLabel>
49
48
  </Grid>
50
49
  <Grid item xs={ 6 } display="flex" justifyContent="end">
51
50
  <ToggleControl options={ options } />
@@ -50,7 +50,7 @@ export function useStyle() {
50
50
  return context;
51
51
  }
52
52
 
53
- function getProviderByStyleId( styleId: StyleDefinitionID ) {
53
+ export function getProviderByStyleId( styleId: StyleDefinitionID ) {
54
54
  const styleProvider = stylesRepository.getProviders().find( ( provider ) => {
55
55
  return provider.actions.get().find( ( style ) => style.id === styleId );
56
56
  } );
@@ -23,7 +23,7 @@ const StyledContainer = styled( Box, {
23
23
  const getGridLayout = ( layout: ControlLayout ) => ( {
24
24
  justifyContent: 'space-between',
25
25
  gridTemplateColumns: {
26
- full: '1fr',
27
- 'two-columns': 'repeat(2, 1fr)',
26
+ full: 'minmax(0, 1fr)',
27
+ 'two-columns': 'repeat(2, minmax(0, 1fr))',
28
28
  }[ layout ],
29
29
  } );
@@ -0,0 +1,61 @@
1
+ import { createTransformer } from '@elementor/editor-canvas';
2
+ import { isTransformable, type Props } from '@elementor/editor-props';
3
+
4
+ import { DynamicTagsManagerNotFoundError } from './errors';
5
+ import { type ExtendedWindow } from './types';
6
+
7
+ type Dynamic = {
8
+ name?: string;
9
+ settings?: Props;
10
+ };
11
+
12
+ export const dynamicTransformer = createTransformer( ( value: Dynamic ) => {
13
+ if ( ! value.name ) {
14
+ return null;
15
+ }
16
+
17
+ return getDynamicValue( value.name, simpleTransform( value.settings ?? {} ) );
18
+ } );
19
+
20
+ // Temporary naive transformation until we'll have a `backendTransformer` that
21
+ // will replace the `dynamicTransformer` client implementation.
22
+ function simpleTransform( props: Props ) {
23
+ const transformed = Object.entries( props ).map( ( [ settingKey, settingValue ] ) => {
24
+ const value = isTransformable( settingValue ) ? settingValue.value : settingValue;
25
+
26
+ return [ settingKey, value ] as const;
27
+ } );
28
+
29
+ return Object.fromEntries( transformed );
30
+ }
31
+
32
+ function getDynamicValue( name: string, settings: Record< string, unknown > ) {
33
+ const extendedWindow = window as unknown as ExtendedWindow;
34
+ const { dynamicTags } = extendedWindow.elementor ?? {};
35
+
36
+ if ( ! dynamicTags ) {
37
+ throw new DynamicTagsManagerNotFoundError();
38
+ }
39
+
40
+ const getTagValue = () => {
41
+ const tag = dynamicTags.createTag( 'v4-dynamic-tag', name, settings );
42
+
43
+ if ( ! tag ) {
44
+ return null;
45
+ }
46
+
47
+ return dynamicTags.loadTagDataFromCache( tag ) ?? null;
48
+ };
49
+
50
+ const tagValue = getTagValue();
51
+
52
+ if ( tagValue !== null ) {
53
+ return tagValue;
54
+ }
55
+
56
+ return new Promise( ( resolve ) => {
57
+ dynamicTags.refreshCacheFromServer( () => {
58
+ resolve( getTagValue() );
59
+ } );
60
+ } );
61
+ }
@@ -0,0 +1,6 @@
1
+ import { createError } from '@elementor/utils';
2
+
3
+ export const DynamicTagsManagerNotFoundError = createError( {
4
+ code: 'dynamic_tags_manager_not_found',
5
+ message: 'Dynamic tags manager not found',
6
+ } );
@@ -1,6 +1,9 @@
1
+ import { settingsTransformersRegistry, styleTransformersRegistry } from '@elementor/editor-canvas';
2
+
1
3
  import { replaceControl } from '../control-replacement';
2
4
  import { controlActionsMenu } from '../controls-actions';
3
5
  import { DynamicSelectionControl } from './components/dynamic-selection-control';
6
+ import { dynamicTransformer } from './dynamic-transformer';
4
7
  import { usePropDynamicAction } from './hooks/use-prop-dynamic-action';
5
8
  import { isDynamicPropValue } from './utils';
6
9
 
@@ -16,4 +19,7 @@ export const init = () => {
16
19
  id: 'dynamic-tags',
17
20
  useProps: usePropDynamicAction,
18
21
  } );
22
+
23
+ styleTransformersRegistry.register( 'dynamic', dynamicTransformer );
24
+ settingsTransformersRegistry.register( 'dynamic', dynamicTransformer );
19
25
  };
@@ -9,6 +9,7 @@ export type ExtendedWindow = Window & {
9
9
  groups: Record< DynamicTag[ 'group' ], { title: string } >;
10
10
  };
11
11
  };
12
+ dynamicTags?: DynamicTagsManager;
12
13
  };
13
14
  };
14
15
 
@@ -34,3 +35,19 @@ export type DynamicPropValue = TransformablePropValue<
34
35
  'dynamic',
35
36
  { name: string; settings?: Record< string, unknown > }
36
37
  >;
38
+
39
+ export type DynamicTagsManager = {
40
+ createTag: ( id: string, name: string, settings: Record< string, unknown > ) => TagInstance;
41
+ loadTagDataFromCache: ( tag: TagInstance ) => unknown;
42
+ refreshCacheFromServer: ( callback: () => void ) => void;
43
+ };
44
+
45
+ export type TagInstance = {
46
+ options: {
47
+ id: string;
48
+ name: string;
49
+ };
50
+ model: {
51
+ toJSON: () => Record< string, unknown >;
52
+ };
53
+ };
@@ -10,12 +10,12 @@ import {
10
10
  type StylesInheritanceSnapshot,
11
11
  type StylesInheritanceSnapshotGetter,
12
12
  type StylesInheritanceSnapshotsSlot,
13
- type StyleVariantWithId,
13
+ type StyleVariantDetails,
14
14
  } from './types';
15
15
  import { DEFAULT_STATE, getBreakpointKey, getStateKey } from './utils';
16
16
 
17
17
  export function createSnapshotsManager(
18
- getStylesByMeta: ( meta: StyleInheritanceMetaProps ) => StyleVariantWithId[],
18
+ getStylesByMeta: ( meta: StyleInheritanceMetaProps ) => StyleVariantDetails[],
19
19
  breakpointsRoot: BreakpointNode
20
20
  ): StylesInheritanceSnapshotGetter {
21
21
  const breakpointsInheritancePaths = makeBreakpointsInheritancePaths( breakpointsRoot );
@@ -104,7 +104,7 @@ function makeBreakpointsInheritancePaths( root: BreakpointNode ): BreakpointsInh
104
104
 
105
105
  // creates a snapshot slot for a specific breakpoint and state
106
106
  function buildStateSnapshotSlot(
107
- styles: StyleVariantWithId[],
107
+ styles: StyleVariantDetails[],
108
108
  parentBreakpoint: BreakpointStatesSlotsMapping | undefined,
109
109
  currentBreakpoint: BreakpointStatesSlotsMapping,
110
110
  state: StyleDefinitionState
@@ -132,13 +132,13 @@ function buildStateSnapshotSlot(
132
132
  }
133
133
 
134
134
  // creates an initial snapshot based on the passed style variants only
135
- function buildInitialSnapshotFromStyles( styles: StyleVariantWithId[] ): StylesInheritanceSnapshotsSlot {
135
+ function buildInitialSnapshotFromStyles( styles: StyleVariantDetails[] ): StylesInheritanceSnapshotsSlot {
136
136
  const snapshot: StylesInheritanceSnapshot = {};
137
137
 
138
- styles.forEach( ( styleVariantWithId ) => {
138
+ styles.forEach( ( styleData ) => {
139
139
  const {
140
- styleVariant: { props },
141
- } = styleVariantWithId;
140
+ variant: { props },
141
+ } = styleData;
142
142
 
143
143
  Object.entries( props ).forEach( ( [ key, value ] ) => {
144
144
  if ( ! snapshot[ key ] ) {
@@ -146,7 +146,7 @@ function buildInitialSnapshotFromStyles( styles: StyleVariantWithId[] ): StylesI
146
146
  }
147
147
 
148
148
  const snapshotPropValue: SnapshotPropValue = {
149
- ...styleVariantWithId,
149
+ ...styleData,
150
150
  value,
151
151
  };
152
152
 
@@ -1,6 +1,7 @@
1
1
  import { type BreakpointNode } from '@elementor/editor-responsive';
2
2
  import { type StyleDefinition } from '@elementor/editor-styles';
3
3
 
4
+ import { getProviderByStyleId } from '../contexts/style-context';
4
5
  import { createSnapshotsManager } from './create-snapshots-manager';
5
6
  import { type BreakpointsStatesStyles, type StyleInheritanceMetaProps, type StylesInheritanceSnapshot } from './types';
6
7
  import { getBreakpointKey, getStateKey } from './utils';
@@ -21,9 +22,11 @@ function buildStyleVariantsByMetaMapping( styleDefs: StyleDefinition[] ): Breakp
21
22
  const breakpointStateSlots: BreakpointsStatesStyles = {};
22
23
 
23
24
  styleDefs.forEach( ( styleDef ) => {
25
+ const provider = getProviderByStyleId( styleDef.id )?.key ?? null;
26
+
24
27
  // iterate over each style definition's variants and place them in the corresponding breakpoint's base or state styles
25
- styleDef.variants.forEach( ( styleVariant ) => {
26
- const { meta } = styleVariant;
28
+ styleDef.variants.forEach( ( variant ) => {
29
+ const { meta } = variant;
27
30
  const { state, breakpoint } = meta;
28
31
 
29
32
  const breakpointKey = getBreakpointKey( breakpoint );
@@ -40,8 +43,9 @@ function buildStyleVariantsByMetaMapping( styleDefs: StyleDefinition[] ): Breakp
40
43
  }
41
44
 
42
45
  breakpointNode[ stateKey ].push( {
43
- styleId: styleDef.id,
44
- styleVariant,
46
+ style: styleDef,
47
+ variant,
48
+ provider,
45
49
  } );
46
50
  } );
47
51
  } );