@elementor/editor-editing-panel 3.33.0-99 → 3.34.2

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 (69) hide show
  1. package/dist/index.d.mts +419 -88
  2. package/dist/index.d.ts +419 -88
  3. package/dist/index.js +3361 -2421
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +3299 -2347
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +22 -21
  8. package/src/apply-unapply-actions.ts +30 -0
  9. package/src/components/collapsible-content.tsx +1 -0
  10. package/src/components/css-classes/css-class-item.tsx +14 -4
  11. package/src/components/css-classes/css-class-menu.tsx +83 -11
  12. package/src/components/css-classes/css-class-selector.tsx +22 -1
  13. package/src/components/css-classes/use-apply-and-unapply-class.ts +4 -13
  14. package/src/components/custom-css-indicator.tsx +68 -1
  15. package/src/components/editing-panel-tabs.tsx +13 -2
  16. package/src/components/interactions-tab.tsx +15 -0
  17. package/src/components/section-content.tsx +3 -2
  18. package/src/components/section.tsx +2 -1
  19. package/src/components/settings-control.tsx +79 -0
  20. package/src/components/settings-tab.tsx +16 -46
  21. package/src/components/style-sections/border-section/border-section.tsx +6 -4
  22. package/src/components/style-sections/effects-section/blend-mode-field.tsx +44 -0
  23. package/src/components/style-sections/effects-section/effects-section.tsx +4 -1
  24. package/src/components/style-sections/layout-section/flex-size-field.tsx +10 -8
  25. package/src/components/style-sections/typography-section/font-family-field.tsx +5 -1
  26. package/src/components/style-sections/typography-section/font-size-field.tsx +1 -1
  27. package/src/components/style-sections/typography-section/letter-spacing-field.tsx +1 -1
  28. package/src/components/style-sections/typography-section/text-color-field.tsx +1 -1
  29. package/src/components/style-sections/typography-section/word-spacing-field.tsx +1 -1
  30. package/src/components/style-tab-section.tsx +2 -2
  31. package/src/components/style-tab.tsx +12 -17
  32. package/src/components/styles-field-layout.tsx +8 -1
  33. package/src/contexts/interaction-context.tsx +0 -0
  34. package/src/controls-registry/conditional-field.tsx +1 -1
  35. package/src/controls-registry/control-type-container.tsx +17 -5
  36. package/src/controls-registry/controls-registry.tsx +18 -5
  37. package/src/controls-registry/element-controls/get-element-by-type.ts +21 -0
  38. package/src/controls-registry/element-controls/registry.ts +16 -0
  39. package/src/controls-registry/element-controls/tabs-control/tabs-control.tsx +229 -0
  40. package/src/controls-registry/element-controls/tabs-control/use-actions.ts +248 -0
  41. package/src/controls-registry/settings-field.tsx +54 -10
  42. package/src/controls-registry/styles-field.tsx +2 -9
  43. package/src/dynamics/components/dynamic-conditional-control.tsx +62 -0
  44. package/src/dynamics/components/dynamic-selection-control.tsx +81 -25
  45. package/src/dynamics/components/dynamic-selection.tsx +3 -3
  46. package/src/dynamics/dynamic-control.tsx +10 -1
  47. package/src/dynamics/hooks/use-prop-dynamic-tags.ts +24 -6
  48. package/src/field-indicators-registry.ts +37 -0
  49. package/src/hooks/use-computed-style.ts +1 -4
  50. package/src/index.ts +16 -3
  51. package/src/init.ts +7 -0
  52. package/src/reset-style-props.tsx +21 -4
  53. package/src/styles-inheritance/components/infotip/value-component.tsx +2 -0
  54. package/src/styles-inheritance/components/styles-inheritance-indicator.tsx +1 -13
  55. package/src/styles-inheritance/components/styles-inheritance-infotip.tsx +5 -1
  56. package/src/styles-inheritance/hooks/use-normalized-inheritance-chain-items.tsx +18 -2
  57. package/src/styles-inheritance/init-styles-inheritance-transformers.ts +25 -4
  58. package/src/styles-inheritance/init.ts +9 -0
  59. package/src/styles-inheritance/transformers/{background-overlay-transformer.tsx → array-transformer.tsx} +2 -2
  60. package/src/styles-inheritance/transformers/background-color-overlay-transformer.tsx +0 -6
  61. package/src/styles-inheritance/transformers/box-shadow-transformer.tsx +32 -0
  62. package/src/styles-inheritance/transformers/color-transformer.tsx +32 -0
  63. package/src/styles-inheritance/transformers/repeater-to-items-transformer.tsx +27 -0
  64. package/src/utils/is-equal.ts +53 -0
  65. package/src/utils/prop-dependency-utils.ts +107 -19
  66. package/src/utils/tracking/subscribe.ts +7 -0
  67. package/src/components/custom-css-field.tsx +0 -20
  68. package/src/components/custom-css.tsx +0 -36
  69. package/src/components/style-sections/border-section/border-field.tsx +0 -54
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elementor/editor-editing-panel",
3
- "version": "3.33.0-99",
3
+ "version": "3.34.2",
4
4
  "private": false,
5
5
  "author": "Elementor Team",
6
6
  "homepage": "https://elementor.com/",
@@ -39,26 +39,27 @@
39
39
  "dev": "tsup --config=../../tsup.dev.ts"
40
40
  },
41
41
  "dependencies": {
42
- "@elementor/editor": "3.33.0-99",
43
- "@elementor/editor-canvas": "3.33.0-99",
44
- "@elementor/editor-controls": "3.33.0-99",
45
- "@elementor/editor-documents": "3.33.0-99",
46
- "@elementor/editor-elements": "3.33.0-99",
47
- "@elementor/editor-panels": "3.33.0-99",
48
- "@elementor/editor-props": "3.33.0-99",
49
- "@elementor/editor-responsive": "3.33.0-99",
50
- "@elementor/editor-styles": "3.33.0-99",
51
- "@elementor/editor-styles-repository": "3.33.0-99",
52
- "@elementor/editor-ui": "3.33.0-99",
53
- "@elementor/editor-v1-adapters": "3.33.0-99",
54
- "@elementor/icons": "1.46.0",
55
- "@elementor/locations": "3.33.0-99",
56
- "@elementor/menus": "3.33.0-99",
57
- "@elementor/schema": "3.33.0-99",
58
- "@elementor/session": "3.33.0-99",
59
- "@elementor/ui": "1.36.12",
60
- "@elementor/utils": "3.33.0-99",
61
- "@elementor/wp-media": "3.33.0-99",
42
+ "@elementor/editor": "3.34.2",
43
+ "@elementor/editor-canvas": "3.34.2",
44
+ "@elementor/editor-controls": "3.34.2",
45
+ "@elementor/editor-documents": "3.34.2",
46
+ "@elementor/editor-elements": "3.34.2",
47
+ "@elementor/editor-interactions": "3.34.2",
48
+ "@elementor/editor-panels": "3.34.2",
49
+ "@elementor/editor-props": "3.34.2",
50
+ "@elementor/editor-responsive": "3.34.2",
51
+ "@elementor/editor-styles": "3.34.2",
52
+ "@elementor/editor-styles-repository": "3.34.2",
53
+ "@elementor/editor-ui": "3.34.2",
54
+ "@elementor/editor-v1-adapters": "3.34.2",
55
+ "@elementor/icons": "^1.61.0",
56
+ "@elementor/locations": "3.34.2",
57
+ "@elementor/menus": "3.34.2",
58
+ "@elementor/schema": "3.34.2",
59
+ "@elementor/session": "3.34.2",
60
+ "@elementor/ui": "1.36.17",
61
+ "@elementor/utils": "3.34.2",
62
+ "@elementor/wp-media": "3.34.2",
62
63
  "@wordpress/i18n": "^5.13.0"
63
64
  },
64
65
  "peerDependencies": {
@@ -0,0 +1,30 @@
1
+ import { setDocumentModifiedStatus } from '@elementor/editor-documents';
2
+ import { getElementSetting, updateElementSettings } from '@elementor/editor-elements';
3
+ import { classesPropTypeUtil, type ClassesPropValue } from '@elementor/editor-props';
4
+ import { type StyleDefinitionID } from '@elementor/editor-styles';
5
+
6
+ // Externalized for use outside of Hooks
7
+
8
+ export function doGetAppliedClasses( elementId: string, classesPropType = 'classes' ) {
9
+ return getElementSetting< ClassesPropValue >( elementId, classesPropType )?.value || [];
10
+ }
11
+
12
+ export function doApplyClasses( elementId: string, classIds: StyleDefinitionID[], classesPropType = 'classes' ) {
13
+ updateElementSettings( {
14
+ id: elementId,
15
+ props: { [ classesPropType ]: classesPropTypeUtil.create( classIds ) },
16
+ withHistory: false,
17
+ } );
18
+ setDocumentModifiedStatus( true );
19
+ }
20
+
21
+ export function doUnapplyClass( elementId: string, classId: StyleDefinitionID, classesPropType = 'classes' ) {
22
+ const appliedClasses = getElementSetting< ClassesPropValue >( elementId, classesPropType )?.value || [];
23
+ if ( ! appliedClasses.includes( classId ) ) {
24
+ return false;
25
+ }
26
+
27
+ const updatedClassIds = appliedClasses.filter( ( id ) => id !== classId );
28
+ doApplyClasses( elementId, updatedClassIds, classesPropType );
29
+ return true;
30
+ }
@@ -44,6 +44,7 @@ export const CollapsibleContent = ( { children, defaultOpen = false, titleEnd =
44
44
  onClick={ handleToggle }
45
45
  endIcon={ <CollapseIcon open={ open } /> }
46
46
  sx={ { my: 0.5 } }
47
+ aria-label={ open ? 'Show less' : 'Show more' }
47
48
  >
48
49
  { open ? __( 'Show less', 'elementor' ) : __( 'Show more', 'elementor' ) }
49
50
  </Button>
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
- import { type ReactElement, useEffect, useState } from 'react';
2
+ import { type ReactElement, useEffect, useMemo, useState } from 'react';
3
+ import { isClassState } from '@elementor/editor-styles';
3
4
  import { stylesRepository, useUserStylesCapability, validateStyleLabel } from '@elementor/editor-styles-repository';
4
5
  import { EditableField, EllipsisWithTooltip, useEditable } from '@elementor/editor-ui';
5
6
  import { DotsVerticalIcon } from '@elementor/icons';
@@ -20,7 +21,7 @@ import { __ } from '@wordpress/i18n';
20
21
 
21
22
  import { useStyle } from '../../contexts/style-context';
22
23
  import { CssClassProvider } from './css-class-context';
23
- import { CssClassMenu } from './css-class-menu';
24
+ import { CssClassMenu, useElementStates } from './css-class-menu';
24
25
 
25
26
  type CssClassItemProps = {
26
27
  id: string | null;
@@ -43,6 +44,8 @@ export function CssClassItem( props: CssClassItemProps ) {
43
44
  const { chipProps, icon, color: colorProp, fixed, ...classProps } = props;
44
45
  const { id, provider, label, isActive, onClickActive, renameLabel, setError } = classProps;
45
46
 
47
+ const { elementStates } = useElementStates();
48
+
46
49
  const { meta, setMetaState } = useStyle();
47
50
  const popupState = usePopupState( { variant: 'popover' } );
48
51
  const [ chipRef, setChipRef ] = useState< HTMLElement | null >( null );
@@ -75,12 +78,19 @@ export function CssClassItem( props: CssClassItemProps ) {
75
78
 
76
79
  const isShowingState = isActive && meta.state;
77
80
 
81
+ const stateLabel = useMemo( () => {
82
+ if ( isClassState( meta.state ) ) {
83
+ return elementStates.find( ( state ) => state.value === meta.state )?.label;
84
+ }
85
+
86
+ return meta.state;
87
+ }, [ meta.state, elementStates ] );
88
+
78
89
  useEffect( () => {
79
90
  if ( convertedFromLocalId && id === convertedFromLocalId ) {
80
91
  clearConvertedFromLocalId();
81
92
  openEditMode();
82
93
  }
83
- // eslint-disable-next-line react-compiler/react-compiler
84
94
  // eslint-disable-next-line react-hooks/exhaustive-deps
85
95
  }, [ id, convertedFromLocalId ] );
86
96
 
@@ -138,7 +148,7 @@ export function CssClassItem( props: CssClassItemProps ) {
138
148
  label={
139
149
  isShowingState ? (
140
150
  <Stack direction="row" gap={ 0.5 } alignItems="center">
141
- <Typography variant="inherit">{ meta.state }</Typography>
151
+ <Typography variant="inherit">{ stateLabel }</Typography>
142
152
  <DotsVerticalIcon fontSize="tiny" />
143
153
  </Stack>
144
154
  ) : undefined
@@ -9,9 +9,11 @@ import { MenuItemInfotip, MenuListItem } from '@elementor/editor-ui';
9
9
  import { bindMenu, Divider, Menu, MenuSubheader, type PopupState, Stack } from '@elementor/ui';
10
10
  import { __ } from '@wordpress/i18n';
11
11
 
12
+ import { useElement } from '../../contexts/element-context';
12
13
  import { useStyle } from '../../contexts/style-context';
13
14
  import { type StyleDefinitionStateWithNormal } from '../../styles-inheritance/types';
14
15
  import { getTempStylesProviderThemeColor } from '../../utils/get-styles-provider-color';
16
+ import { trackStyles } from '../../utils/tracking/subscribe';
15
17
  import { StyleIndicator } from '../style-indicator';
16
18
  import { useCssClass } from './css-class-context';
17
19
  import { LocalClassSubMenu } from './local-class-sub-menu';
@@ -20,13 +22,14 @@ import { useUnapplyClass } from './use-apply-and-unapply-class';
20
22
  type State = {
21
23
  key: StyleDefinitionStateWithNormal;
22
24
  value: StyleDefinitionState | null;
25
+ label: string;
23
26
  };
24
27
 
25
28
  const STATES: State[] = [
26
- { key: 'normal', value: null },
27
- { key: 'hover', value: 'hover' },
28
- { key: 'focus', value: 'focus' },
29
- { key: 'active', value: 'active' },
29
+ { key: 'normal', value: null, label: __( 'normal', 'elementor' ) },
30
+ { key: 'hover', value: 'hover', label: __( 'hover', 'elementor' ) },
31
+ { key: 'focus', value: 'focus', label: __( 'focus', 'elementor' ) },
32
+ { key: 'active', value: 'active', label: __( 'active', 'elementor' ) },
30
33
  ];
31
34
 
32
35
  type CssClassMenuProps = {
@@ -67,15 +70,75 @@ export function CssClassMenu( { popupState, anchorEl, fixed }: CssClassMenuProps
67
70
  { __( 'States', 'elementor' ) }
68
71
  </MenuSubheader>
69
72
  { STATES.map( ( state ) => {
70
- return <StateMenuItem key={ state.key } state={ state.value } closeMenu={ popupState.close } />;
73
+ return (
74
+ <StateMenuItem
75
+ key={ state.key }
76
+ state={ state.value }
77
+ label={ state.label }
78
+ closeMenu={ popupState.close }
79
+ />
80
+ );
71
81
  } ) }
82
+ <ClassStatesMenu closeMenu={ popupState.close } />
72
83
  </Menu>
73
84
  );
74
85
  }
75
86
 
87
+ function ClassStatesMenu( { closeMenu }: { closeMenu: () => void } ) {
88
+ const { elementStates, elementTitle } = useElementStates();
89
+
90
+ if ( ! elementStates.length ) {
91
+ return null;
92
+ }
93
+
94
+ /* translators: %s: Element type title. */
95
+ const customTitle = __( '%s States', 'elementor' ).replace( '%s', elementTitle );
96
+
97
+ return (
98
+ <>
99
+ <Divider />
100
+ <MenuSubheader sx={ { typography: 'caption', color: 'text.secondary', pb: 0.5, pt: 1 } }>
101
+ { customTitle }
102
+ </MenuSubheader>
103
+ { elementStates.map( ( state ) => {
104
+ return (
105
+ <StateMenuItem
106
+ key={ state.key }
107
+ state={ state.value }
108
+ label={ state.label }
109
+ closeMenu={ closeMenu }
110
+ />
111
+ );
112
+ } ) }
113
+ </>
114
+ );
115
+ }
116
+
117
+ const CLASS_STATES_MAP: Record< string, { label: string } > = {
118
+ selected: {
119
+ label: __( 'selected', 'elementor' ),
120
+ },
121
+ };
122
+
123
+ export function useElementStates() {
124
+ const { elementType } = useElement();
125
+
126
+ const { styleStates = [] } = elementType;
127
+
128
+ const elementStates = styleStates.map( ( { value, name } ) => ( {
129
+ key: value,
130
+ value,
131
+ label: CLASS_STATES_MAP[ value ]?.label ?? name,
132
+ } ) );
133
+
134
+ return {
135
+ elementStates,
136
+ elementTitle: elementType.title,
137
+ };
138
+ }
139
+
76
140
  function useModifiedStates( styleId: string | null ): Partial< Record< StyleDefinitionStateWithNormal, true > > {
77
141
  const { meta } = useStyle();
78
-
79
142
  const styleDef = stylesRepository.all().find( ( style ) => style.id === styleId );
80
143
 
81
144
  return Object.fromEntries(
@@ -126,10 +189,11 @@ function getMenuItemsByProvider( {
126
189
 
127
190
  type StateMenuItemProps = {
128
191
  state: StyleDefinitionState;
192
+ label: string;
129
193
  closeMenu: () => void;
130
194
  };
131
195
 
132
- function StateMenuItem( { state, closeMenu, ...props }: StateMenuItemProps ) {
196
+ function StateMenuItem( { state, label, closeMenu, ...props }: StateMenuItemProps ) {
133
197
  const { id: styleId, provider } = useCssClass();
134
198
  const { id: activeId, setId: setActiveId, setMetaState: setActiveMetaState, meta } = useStyle();
135
199
  const { state: activeState } = meta;
@@ -154,9 +218,12 @@ function StateMenuItem( { state, closeMenu, ...props }: StateMenuItemProps ) {
154
218
  if ( ! isActive ) {
155
219
  setActiveId( styleId );
156
220
  }
157
-
221
+ trackStyles( provider ?? '', 'classStateClicked', {
222
+ classId: styleId,
223
+ type: label,
224
+ source: styleId ? 'global' : 'local',
225
+ } );
158
226
  setActiveMetaState( state );
159
-
160
227
  closeMenu();
161
228
  } }
162
229
  >
@@ -171,7 +238,7 @@ function StateMenuItem( { state, closeMenu, ...props }: StateMenuItemProps ) {
171
238
  getColor={ getTempStylesProviderThemeColor( provider ?? '' ) }
172
239
  />
173
240
  ) }
174
- { state ?? 'normal' }
241
+ { label }
175
242
  </Stack>
176
243
  </MenuItemInfotip>
177
244
  </MenuListItem>
@@ -179,7 +246,7 @@ function StateMenuItem( { state, closeMenu, ...props }: StateMenuItemProps ) {
179
246
  }
180
247
 
181
248
  function UnapplyClassMenuItem( { closeMenu, ...props }: { closeMenu: () => void } ) {
182
- const { id: classId, label: classLabel } = useCssClass();
249
+ const { id: classId, label: classLabel, provider } = useCssClass();
183
250
  const unapplyClass = useUnapplyClass();
184
251
 
185
252
  return classId ? (
@@ -187,6 +254,11 @@ function UnapplyClassMenuItem( { closeMenu, ...props }: { closeMenu: () => void
187
254
  { ...props }
188
255
  onClick={ () => {
189
256
  unapplyClass( { classId, classLabel } );
257
+ trackStyles( provider ?? '', 'classRemoved', {
258
+ classId,
259
+ classTitle: classLabel,
260
+ source: 'style-tab',
261
+ } );
190
262
  closeMenu();
191
263
  } }
192
264
  >
@@ -30,6 +30,7 @@ import { useClassesProp } from '../../contexts/classes-prop-context';
30
30
  import { useElement } from '../../contexts/element-context';
31
31
  import { useStyle } from '../../contexts/style-context';
32
32
  import { getStylesProviderColorName } from '../../utils/get-styles-provider-color';
33
+ import { trackStyles } from '../../utils/tracking/subscribe';
33
34
  import {
34
35
  CreatableAutocomplete,
35
36
  type CreatableAutocompleteProps,
@@ -127,6 +128,13 @@ export function CssClassSelector() {
127
128
  if ( ! value.value ) {
128
129
  throw new Error( `Cannot rename a class without style id` );
129
130
  }
131
+ trackStyles( value.provider ?? '', 'classRenamed', {
132
+ classId: value.value,
133
+ newValue: newLabel,
134
+ oldValue: value.label,
135
+ source: 'style-tab',
136
+ } );
137
+
130
138
  return updateClassByProvider( value.provider, { label: newLabel, id: value.value } );
131
139
  };
132
140
 
@@ -244,7 +252,12 @@ function useCreateAction() {
244
252
  }
245
253
 
246
254
  const create = ( classLabel: string ) => {
247
- createAction( { classLabel } );
255
+ const { createdId } = createAction( { classLabel } );
256
+ trackStyles( provider.getKey() ?? '', 'classCreated', {
257
+ source: 'created',
258
+ classTitle: classLabel,
259
+ classId: createdId,
260
+ } );
248
261
  };
249
262
 
250
263
  const validate = ( newClassLabel: string, event: ValidationEvent ): ValidationResult => {
@@ -302,10 +315,18 @@ function useHandleSelect() {
302
315
  switch ( reason ) {
303
316
  case 'selectOption':
304
317
  apply( { classId: option.value, classLabel: option.label } );
318
+ trackStyles( option.provider ?? '', 'classApplied', {
319
+ classId: option.value,
320
+ source: 'style-tab',
321
+ } );
305
322
  break;
306
323
 
307
324
  case 'removeOption':
308
325
  unapply( { classId: option.value, classLabel: option.label } );
326
+ trackStyles( option.provider ?? '', 'classRemoved', {
327
+ classId: option.value,
328
+ source: 'style-tab',
329
+ } );
309
330
  break;
310
331
  }
311
332
  };
@@ -1,12 +1,11 @@
1
1
  import { useCallback, useMemo } from 'react';
2
- import { setDocumentModifiedStatus } from '@elementor/editor-documents';
3
- import { getElementLabel, getElementSetting, updateElementSettings } from '@elementor/editor-elements';
4
- import { classesPropTypeUtil, type ClassesPropValue } from '@elementor/editor-props';
2
+ import { getElementLabel } from '@elementor/editor-elements';
5
3
  import { type StyleDefinitionID } from '@elementor/editor-styles';
6
4
  import { useGetStylesRepositoryCreateAction } from '@elementor/editor-styles-repository';
7
5
  import { undoable } from '@elementor/editor-v1-adapters';
8
6
  import { __ } from '@wordpress/i18n';
9
7
 
8
+ import { doApplyClasses, doGetAppliedClasses } from '../../apply-unapply-actions';
10
9
  import { useClassesProp } from '../../contexts/classes-prop-context';
11
10
  import { useElement } from '../../contexts/element-context';
12
11
  import { useStyle } from '../../contexts/style-context';
@@ -112,7 +111,6 @@ export function useCreateAndApplyClass() {
112
111
 
113
112
  const createdId = createAction( classLabel );
114
113
  applyClass( createdId );
115
-
116
114
  return { prevActiveId, createdId };
117
115
  },
118
116
  undo: ( _: CreateAndApplyClassPayload, { prevActiveId, createdId }: CreateAndApplyClassUndoData ) => {
@@ -194,17 +192,10 @@ function useClasses() {
194
192
 
195
193
  return useMemo( () => {
196
194
  const setClasses = ( ids: StyleDefinitionID[] ) => {
197
- updateElementSettings( {
198
- id: element.id,
199
- props: { [ currentClassesProp ]: classesPropTypeUtil.create( ids ) },
200
- withHistory: false,
201
- } );
202
-
203
- setDocumentModifiedStatus( true );
195
+ doApplyClasses( element.id, ids, currentClassesProp );
204
196
  };
205
197
 
206
- const getAppliedClasses = () =>
207
- getElementSetting< ClassesPropValue >( element.id, currentClassesProp )?.value || [];
198
+ const getAppliedClasses = () => doGetAppliedClasses( element.id, currentClassesProp ) || [];
208
199
 
209
200
  return { setClasses, getAppliedClasses };
210
201
  }, [ currentClassesProp, element.id ] );
@@ -1,16 +1,83 @@
1
1
  import * as React from 'react';
2
+ import { type BreakpointId, type BreakpointNode, getBreakpointsTree } from '@elementor/editor-responsive';
3
+ import { getVariantByMeta, type StyleDefinition, type StyleDefinitionVariant } from '@elementor/editor-styles';
2
4
 
5
+ import { useElement } from '../contexts/element-context';
6
+ import { useStyle } from '../contexts/style-context';
3
7
  import { useCustomCss } from '../hooks/use-custom-css';
8
+ import { getStylesProviderThemeColor } from '../utils/get-styles-provider-color';
4
9
  import { StyleIndicator } from './style-indicator';
5
10
 
6
11
  export const CustomCssIndicator = () => {
7
12
  const { customCss } = useCustomCss();
13
+ const { id: styleId, provider, meta } = useStyle();
14
+ const {
15
+ element: { id: elementId },
16
+ } = useElement();
17
+
18
+ const style = React.useMemo(
19
+ () => ( styleId && provider ? provider.actions.get( styleId, { elementId } ) : null ),
20
+ [ styleId, provider, elementId ]
21
+ );
8
22
 
9
23
  const hasContent = Boolean( customCss?.raw?.trim() );
10
24
 
25
+ const hasInheritedContent = React.useMemo( () => {
26
+ if ( hasContent ) {
27
+ return false;
28
+ }
29
+
30
+ return hasInheritedCustomCss( style, meta );
31
+ }, [ hasContent, style, meta ] );
32
+
11
33
  if ( ! hasContent ) {
34
+ if ( hasInheritedContent ) {
35
+ return <StyleIndicator />;
36
+ }
12
37
  return null;
13
38
  }
14
39
 
15
- return <StyleIndicator getColor={ ( theme ) => theme.palette.accent.main } />;
40
+ return <StyleIndicator getColor={ provider ? getStylesProviderThemeColor( provider.getKey() ) : undefined } />;
41
+ };
42
+
43
+ const hasInheritedCustomCss = (
44
+ style: StyleDefinition | null,
45
+ meta: StyleDefinitionVariant[ 'meta' ] | null
46
+ ): boolean => {
47
+ if ( ! style || ! meta ) {
48
+ return false;
49
+ }
50
+
51
+ const target = ( meta.breakpoint ?? 'desktop' ) as BreakpointId;
52
+ const root = getBreakpointsTree();
53
+ const state = meta.state;
54
+
55
+ function search( node: BreakpointNode, ancestorHasCss: boolean ): boolean | undefined {
56
+ if ( ! style ) {
57
+ return undefined;
58
+ }
59
+
60
+ const hasHere = Boolean(
61
+ getVariantByMeta( style, {
62
+ breakpoint: node.id as BreakpointId,
63
+ state,
64
+ } )?.custom_css?.raw?.trim()
65
+ );
66
+
67
+ if ( node.id === target ) {
68
+ return ancestorHasCss;
69
+ }
70
+
71
+ for ( const child of node.children ?? [] ) {
72
+ const res = search( child, ancestorHasCss || hasHere );
73
+
74
+ if ( res !== undefined ) {
75
+ return res;
76
+ }
77
+ }
78
+
79
+ return undefined;
80
+ }
81
+
82
+ return Boolean( search( root, false ) );
16
83
  };
@@ -1,5 +1,6 @@
1
- import * as React from 'react';
2
1
  import { Fragment } from 'react';
2
+ import * as React from 'react';
3
+ import { isExperimentActive } from '@elementor/editor-v1-adapters';
3
4
  import { Divider, Stack, Tab, TabPanel, Tabs, useTabs } from '@elementor/ui';
4
5
  import { __ } from '@wordpress/i18n';
5
6
 
@@ -7,10 +8,11 @@ import { useElement } from '../contexts/element-context';
7
8
  import { ScrollProvider } from '../contexts/scroll-context';
8
9
  import { useDefaultPanelSettings } from '../hooks/use-default-panel-settings';
9
10
  import { useStateByElement } from '../hooks/use-state-by-element';
11
+ import { InteractionsTab } from './interactions-tab';
10
12
  import { SettingsTab } from './settings-tab';
11
13
  import { stickyHeaderStyles, StyleTab } from './style-tab';
12
14
 
13
- type TabValue = 'settings' | 'style';
15
+ type TabValue = 'settings' | 'style' | 'interactions';
14
16
 
15
17
  export const EditingPanelTabs = () => {
16
18
  const { element } = useElement();
@@ -26,6 +28,7 @@ export const EditingPanelTabs = () => {
26
28
  const PanelTabContent = () => {
27
29
  const editorDefaults = useDefaultPanelSettings();
28
30
  const defaultComponentTab = editorDefaults.defaultTab as TabValue;
31
+ const isInteractionsActive = isExperimentActive( 'e_interactions' );
29
32
 
30
33
  const [ currentTab, setCurrentTab ] = useStateByElement< TabValue >( 'tab', defaultComponentTab );
31
34
  const { getTabProps, getTabPanelProps, getTabsProps } = useTabs< TabValue >( currentTab );
@@ -45,6 +48,9 @@ const PanelTabContent = () => {
45
48
  >
46
49
  <Tab label={ __( 'General', 'elementor' ) } { ...getTabProps( 'settings' ) } />
47
50
  <Tab label={ __( 'Style', 'elementor' ) } { ...getTabProps( 'style' ) } />
51
+ { isInteractionsActive && (
52
+ <Tab label={ __( 'Interactions', 'elementor' ) } { ...getTabProps( 'interactions' ) } />
53
+ ) }
48
54
  </Tabs>
49
55
  <Divider />
50
56
  </Stack>
@@ -54,6 +60,11 @@ const PanelTabContent = () => {
54
60
  <TabPanel { ...getTabPanelProps( 'style' ) } disablePadding>
55
61
  <StyleTab />
56
62
  </TabPanel>
63
+ { isInteractionsActive && (
64
+ <TabPanel { ...getTabPanelProps( 'interactions' ) } disablePadding>
65
+ <InteractionsTab />
66
+ </TabPanel>
67
+ ) }
57
68
  </Stack>
58
69
  </ScrollProvider>
59
70
  );
@@ -0,0 +1,15 @@
1
+ import * as React from 'react';
2
+ import { InteractionsTab as InteractionsTabContent } from '@elementor/editor-interactions';
3
+
4
+ import { useElement } from '../contexts/element-context';
5
+ import { SectionsList } from './sections-list';
6
+
7
+ export const InteractionsTab = () => {
8
+ const { element } = useElement();
9
+
10
+ return (
11
+ <SectionsList>
12
+ <InteractionsTabContent elementId={ element.id } />
13
+ </SectionsList>
14
+ );
15
+ };
@@ -7,10 +7,11 @@ type SectionContentProps = PropsWithChildren< {
7
7
  sx?: {
8
8
  pt?: number;
9
9
  };
10
+ 'aria-label'?: string;
10
11
  } >;
11
12
 
12
- export const SectionContent: FC< SectionContentProps > = ( { gap = 2, sx, children } ) => (
13
- <Stack gap={ gap } sx={ { ...sx } }>
13
+ export const SectionContent: FC< SectionContentProps > = ( { gap = 2, sx, children, 'aria-label': ariaLabel } ) => (
14
+ <Stack gap={ gap } sx={ { ...sx } } aria-label={ ariaLabel }>
14
15
  { children }
15
16
  </Stack>
16
17
  );
@@ -31,6 +31,7 @@ export function Section( { title, children, defaultExpanded = false, titleEnd, u
31
31
  <ListItemButton
32
32
  id={ labelId }
33
33
  aria-controls={ contentId }
34
+ aria-label={ `${ title } section` }
34
35
  onClick={ handleClick }
35
36
  sx={ { '&:hover': { backgroundColor: 'transparent' } } }
36
37
  >
@@ -52,7 +53,7 @@ export function Section( { title, children, defaultExpanded = false, titleEnd, u
52
53
  unmountOnExit={ unmountOnExit }
53
54
  >
54
55
  <SectionRefContext.Provider value={ ref }>
55
- <Stack ref={ ref } gap={ 2.5 } p={ 2 }>
56
+ <Stack ref={ ref } gap={ 2.5 } p={ 2 } aria-label={ `${ title } section content` }>
56
57
  { children }
57
58
  </Stack>
58
59
  </SectionRefContext.Provider>