@elementor/editor-editing-panel 1.5.1 → 1.6.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.5.1",
3
+ "version": "1.6.0",
4
4
  "private": false,
5
5
  "author": "Elementor Team",
6
6
  "homepage": "https://elementor.com/",
@@ -40,14 +40,14 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@elementor/editor": "0.17.2",
43
- "@elementor/editor-controls": "0.4.0",
44
- "@elementor/editor-elements": "0.3.4",
43
+ "@elementor/editor-controls": "0.4.1",
44
+ "@elementor/editor-elements": "0.3.5",
45
45
  "@elementor/menus": "0.1.2",
46
- "@elementor/editor-props": "0.5.0",
46
+ "@elementor/editor-props": "0.5.1",
47
47
  "@elementor/editor-panels": "0.10.2",
48
48
  "@elementor/editor-responsive": "0.12.4",
49
- "@elementor/editor-styles": "0.3.2",
50
- "@elementor/editor-styles-repository": "0.3.1",
49
+ "@elementor/editor-styles": "0.4.0",
50
+ "@elementor/editor-styles-repository": "0.3.2",
51
51
  "@elementor/editor-v1-adapters": "0.8.5",
52
52
  "@elementor/icons": "^1.20.0",
53
53
  "@elementor/schema": "0.1.2",
@@ -0,0 +1,125 @@
1
+ import * as React from 'react';
2
+ import { type RefObject } from 'react';
3
+ import { type StyleState } from '@elementor/editor-styles';
4
+ import { CheckIcon } from '@elementor/icons';
5
+ import { createMenu } from '@elementor/menus';
6
+ import {
7
+ bindMenu,
8
+ Box,
9
+ ListItemIcon,
10
+ ListItemText,
11
+ ListSubheader,
12
+ Menu,
13
+ MenuItem,
14
+ type PopupState,
15
+ styled,
16
+ } from '@elementor/ui';
17
+ import { __ } from '@wordpress/i18n';
18
+
19
+ import { useCssClassItem } from '../contexts/css-class-item-context';
20
+ import { useStyle } from '../contexts/style-context';
21
+
22
+ export const { useMenuItems: useStateMenuItems, registerStateMenuItem } = createMenu( {
23
+ components: {
24
+ StateMenuItem,
25
+ },
26
+ } );
27
+
28
+ export const { useMenuItems: useGlobalClassMenuItems, registerGlobalClassMenuItem } = createMenu( {
29
+ components: {
30
+ GlobalClassMenuItem,
31
+ },
32
+ } );
33
+
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();
44
+
45
+ return (
46
+ <Menu
47
+ MenuListProps={ { dense: true } }
48
+ { ...bindMenu( popupState ) }
49
+ anchorOrigin={ {
50
+ vertical: 'top',
51
+ horizontal: 'right',
52
+ } }
53
+ anchorEl={ containerRef.current }
54
+ >
55
+ { isGlobal && (
56
+ <GlobalClassMenuSection>
57
+ { globalClassMenuItems.map( ( { id, MenuItem: MenuItemComponent } ) => (
58
+ <MenuItemComponent key={ id } />
59
+ ) ) }
60
+ </GlobalClassMenuSection>
61
+ ) }
62
+ <ListSubheader>{ __( 'Add a pseudo selector', 'elementor' ) }</ListSubheader>
63
+ { stateMenuItems.map( ( { id, MenuItem: MenuItemComponent } ) => (
64
+ <MenuItemComponent key={ id } />
65
+ ) ) }
66
+ </Menu>
67
+ );
68
+ }
69
+
70
+ type StateMenuItemProps = {
71
+ state: StyleState;
72
+ disabled?: boolean;
73
+ };
74
+
75
+ export function StateMenuItem( { state, disabled }: StateMenuItemProps ) {
76
+ const { isActive, styleId } = useCssClassItem();
77
+ const { setId: setActiveId, setMetaState: setActiveMetaState, meta } = useStyle();
78
+ const { state: activeState } = meta;
79
+
80
+ const isSelected = state === activeState && isActive;
81
+
82
+ return (
83
+ <StyledMenuItem
84
+ selected={ state === activeState && isActive }
85
+ disabled={ disabled }
86
+ onClick={ () => {
87
+ if ( ! isActive ) {
88
+ setActiveId( styleId );
89
+ }
90
+
91
+ setActiveMetaState( state );
92
+ } }
93
+ >
94
+ { isSelected && (
95
+ <ListItemIcon>
96
+ <CheckIcon />
97
+ </ListItemIcon>
98
+ ) }
99
+ <ListItemText primary={ state ? `:${ state }` : 'Normal' } />
100
+ </StyledMenuItem>
101
+ );
102
+ }
103
+
104
+ type GlobalClassMenuItemProps = {
105
+ text: string;
106
+ onClick: () => void;
107
+ };
108
+
109
+ export function GlobalClassMenuItem( { text, onClick }: GlobalClassMenuItemProps ) {
110
+ return (
111
+ <StyledMenuItem onClick={ onClick }>
112
+ <ListItemText primary={ text } />
113
+ </StyledMenuItem>
114
+ );
115
+ }
116
+
117
+ const GlobalClassMenuSection = styled( Box )( ( { theme } ) => ( {
118
+ borderBottom: `1px solid ${ theme?.palette.divider }`,
119
+ } ) );
120
+
121
+ const StyledMenuItem = styled( MenuItem )( {
122
+ '&:hover': {
123
+ color: 'text.primary', // Overriding global CSS from the editor.
124
+ },
125
+ } );
@@ -1,15 +1,28 @@
1
1
  import * as React from 'react';
2
+ import { useId, useRef } from 'react';
2
3
  import { updateSettings, useElementSetting } from '@elementor/editor-elements';
3
4
  import { classesPropTypeUtil, type ClassesPropValue } from '@elementor/editor-props';
4
5
  import { type StyleDefinitionID } from '@elementor/editor-styles';
5
6
  import { ELEMENTS_STYLES_PROVIDER_KEY, useAllStylesByProvider } from '@elementor/editor-styles-repository';
6
- import { Chip, Stack, Typography } from '@elementor/ui';
7
+ import { DotsVerticalIcon } from '@elementor/icons';
8
+ import {
9
+ type AutocompleteRenderGetTagProps,
10
+ bindTrigger,
11
+ Chip,
12
+ type ChipOwnProps,
13
+ Stack,
14
+ Typography,
15
+ UnstableChipGroup,
16
+ usePopupState,
17
+ } from '@elementor/ui';
7
18
  import { __ } from '@wordpress/i18n';
8
19
 
9
20
  import { useClassesProp } from '../contexts/classes-prop-context';
21
+ import { CssClassItemProvider } from '../contexts/css-class-item-context';
10
22
  import { useElement } from '../contexts/element-context';
11
23
  import { useStyle } from '../contexts/style-context';
12
24
  import { ConditionalTooltipWrapper } from './conditional-tooltip-wrapper';
25
+ import { CssClassMenu } from './css-class-menu';
13
26
  import { MultiCombobox, type Option } from './multi-combobox';
14
27
 
15
28
  const ID = 'elementor-css-class-selector';
@@ -59,16 +72,15 @@ export function CssClassSelector() {
59
72
  const isActive = value.value === active?.value;
60
73
 
61
74
  return (
62
- <Chip
63
- { ...chipProps }
75
+ <CssClassItem
64
76
  key={ chipProps.key }
65
- size="small"
66
- label={ <ConditionalTooltipWrapper maxWidth="10ch" title={ value.label } /> }
67
- variant={ isActive ? 'filled' : 'standard' }
77
+ label={ value.label }
78
+ id={ value.value }
79
+ isActive={ isActive }
80
+ isGlobal={ value.color === 'global' }
68
81
  color={ isActive && value.color ? value.color : 'default' }
69
- onClick={ () => handleActivate( value ) }
70
- onDelete={ null }
71
- aria-pressed={ isActive }
82
+ chipProps={ chipProps }
83
+ onClickActive={ () => handleActivate( value ) }
72
84
  />
73
85
  );
74
86
  } )
@@ -78,6 +90,56 @@ export function CssClassSelector() {
78
90
  );
79
91
  }
80
92
 
93
+ type CssClassItemProps = {
94
+ id: string;
95
+ label: string;
96
+ isActive: boolean;
97
+ isGlobal: boolean;
98
+ color: ChipOwnProps[ 'color' ];
99
+ chipProps: ReturnType< AutocompleteRenderGetTagProps >;
100
+ onClickActive: ( id: string ) => void;
101
+ };
102
+
103
+ function CssClassItem( { id, label, isActive, isGlobal, color, chipProps, onClickActive }: CssClassItemProps ) {
104
+ const { meta } = useStyle();
105
+ // TODO - resolve the useId issue with invalid characters upon CSS selectors (EDS-1089)
106
+ const popupId = useId().replace( /:/g, '_' );
107
+ const popupState = usePopupState( { variant: 'popover', popupId } );
108
+ const chipRef = useRef< Element >( null );
109
+ const { onDelete, ...chipGroupProps } = chipProps;
110
+
111
+ return (
112
+ <CssClassItemProvider styleId={ id } isActive={ isActive } isGlobal={ isGlobal }>
113
+ <UnstableChipGroup ref={ chipRef } { ...chipGroupProps } aria-label={ `Edit ${ label }` } role="group">
114
+ <Chip
115
+ key={ chipProps.key }
116
+ size="small"
117
+ label={ <ConditionalTooltipWrapper maxWidth="10ch" title={ label } /> }
118
+ variant={ isActive && ! meta.state ? 'filled' : 'standard' }
119
+ color={ color }
120
+ onClick={ () => onClickActive( id ) }
121
+ aria-pressed={ isActive }
122
+ />
123
+ <Chip
124
+ key={ `${ chipProps.key }-menu` }
125
+ size="small"
126
+ label={
127
+ <Stack direction="row" gap={ 0.5 } alignItems="center">
128
+ { isActive && meta.state && <Typography variant="inherit">{ meta.state }</Typography> }
129
+ <DotsVerticalIcon fontSize="inherit" />
130
+ </Stack>
131
+ }
132
+ variant="filled"
133
+ color={ color }
134
+ { ...bindTrigger( popupState ) }
135
+ aria-label={ __( 'Open CSS Class Menu', 'elementor' ) }
136
+ />
137
+ </UnstableChipGroup>
138
+ <CssClassMenu popupState={ popupState } containerRef={ chipRef } />
139
+ </CssClassItemProvider>
140
+ );
141
+ }
142
+
81
143
  function useOptions() {
82
144
  const { element } = useElement();
83
145
 
@@ -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 StyleDefinition } from '@elementor/editor-styles';
6
+ import { type StyleDefinitionID, type StyleState } from '@elementor/editor-styles';
7
7
  import { Divider } from '@elementor/ui';
8
8
  import { __ } from '@wordpress/i18n';
9
9
 
@@ -27,11 +27,20 @@ const CLASSES_PROP_KEY = 'classes';
27
27
  export const StyleTab = () => {
28
28
  const currentClassesProp = useCurrentClassesProp();
29
29
  const [ activeStyleDefId, setActiveStyleDefId ] = useActiveStyleDefId( currentClassesProp );
30
+ const [ activeStyleState, setActiveStyleState ] = useState< StyleState | null >( null );
30
31
  const breakpoint = useActiveBreakpoint();
31
32
 
32
33
  return (
33
34
  <ClassesPropProvider prop={ currentClassesProp }>
34
- <StyleProvider meta={ { breakpoint, state: null } } id={ activeStyleDefId } setId={ setActiveStyleDefId }>
35
+ <StyleProvider
36
+ meta={ { breakpoint, state: activeStyleState } }
37
+ id={ activeStyleDefId }
38
+ setId={ ( id: StyleDefinitionID | null ) => {
39
+ setActiveStyleDefId( id );
40
+ setActiveStyleState( null );
41
+ } }
42
+ setMetaState={ setActiveStyleState }
43
+ >
35
44
  <CssClassSelector />
36
45
  <Divider />
37
46
  <SectionsList>
@@ -66,7 +75,7 @@ export const StyleTab = () => {
66
75
  };
67
76
 
68
77
  function useActiveStyleDefId( currentClassesProp: PropKey ) {
69
- const [ activeStyledDefId, setActiveStyledDefId ] = useState< StyleDefinition[ 'id' ] | null >( null );
78
+ const [ activeStyledDefId, setActiveStyledDefId ] = useState< StyleDefinitionID | null >( null );
70
79
 
71
80
  const fallback = useFirstElementStyleDef( currentClassesProp );
72
81
 
@@ -0,0 +1,31 @@
1
+ import * as React from 'react';
2
+ import { createContext, type PropsWithChildren } from 'react';
3
+
4
+ type ClassItemContextType = {
5
+ styleId: string;
6
+ isGlobal: boolean;
7
+ isActive: boolean;
8
+ };
9
+ const ClassItemContext = createContext< ClassItemContextType >( {
10
+ styleId: '',
11
+ isGlobal: false,
12
+ isActive: false,
13
+ } );
14
+
15
+ type ClassItemProviderProps = PropsWithChildren< ClassItemContextType >;
16
+
17
+ export function CssClassItemProvider( { styleId, isGlobal, isActive, children }: ClassItemProviderProps ) {
18
+ return (
19
+ <ClassItemContext.Provider value={ { styleId, isGlobal, isActive } }>{ children }</ClassItemContext.Provider>
20
+ );
21
+ }
22
+
23
+ export function useCssClassItem() {
24
+ const context = React.useContext( ClassItemContext );
25
+
26
+ if ( ! context ) {
27
+ throw new Error( 'useCssClassItem must be used within a CssClassItemProvider' );
28
+ }
29
+
30
+ return context;
31
+ }
@@ -1,19 +1,20 @@
1
1
  import * as React from 'react';
2
2
  import { createContext, type Dispatch, type PropsWithChildren, useContext } from 'react';
3
- import { type StyleDefinition, type StyleVariant } from '@elementor/editor-styles';
3
+ import { type StyleDefinition, type StyleState, type StyleVariant } from '@elementor/editor-styles';
4
4
 
5
5
  type ContextValue = {
6
6
  id: StyleDefinition[ 'id' ] | null;
7
7
  setId: Dispatch< StyleDefinition[ 'id' ] | null >;
8
8
  meta: StyleVariant[ 'meta' ];
9
+ setMetaState: Dispatch< StyleState >;
9
10
  };
10
11
 
11
12
  const Context = createContext< ContextValue | null >( null );
12
13
 
13
14
  type Props = PropsWithChildren< ContextValue >;
14
15
 
15
- export function StyleProvider( { children, id, setId, meta }: Props ) {
16
- return <Context.Provider value={ { id, setId, meta } }>{ children }</Context.Provider>;
16
+ export function StyleProvider( { children, id, setId, meta, setMetaState }: Props ) {
17
+ return <Context.Provider value={ { id, setId, meta, setMetaState } }>{ children }</Context.Provider>;
17
18
  }
18
19
 
19
20
  export function useStyle() {
@@ -0,0 +1,37 @@
1
+ import { type StyleState } from '@elementor/editor-styles';
2
+
3
+ import { registerStateMenuItem } from './components/css-class-menu';
4
+
5
+ const STATES: NonNullable< StyleState >[] = [ 'hover', 'focus', 'active' ];
6
+
7
+ export function initCssClasses() {
8
+ registerStateItems();
9
+ registerGlobalClassItems();
10
+ }
11
+
12
+ function registerStateItems() {
13
+ registerStateMenuItem( {
14
+ id: 'normal',
15
+ props: {
16
+ state: null,
17
+ },
18
+ } );
19
+
20
+ STATES.forEach( ( state ) => {
21
+ registerStateMenuItem( {
22
+ id: state,
23
+ props: {
24
+ state,
25
+ },
26
+ } );
27
+ } );
28
+ }
29
+
30
+ 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
+ */
37
+ }
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export { useBoundProp } from '@elementor/editor-controls';
2
2
  export type { PopoverActionProps } from './popover-action';
3
3
  export { replaceControl } from './control-replacement';
4
+ export { registerGlobalClassMenuItem, registerStateMenuItem } from './components/css-class-menu';
4
5
 
5
6
  import init from './init';
6
7
 
package/src/init.ts CHANGED
@@ -3,6 +3,7 @@ import { __registerPanel as registerPanel } from '@elementor/editor-panels';
3
3
  import { __privateBlockDataCommand as blockDataCommand } from '@elementor/editor-v1-adapters';
4
4
 
5
5
  import { EditingPanelHooks } from './components/editing-panel-hooks';
6
+ import { initCssClasses } from './css-classes';
6
7
  import { init as initDynamics } from './dynamics/init';
7
8
  import { panel } from './panel';
8
9
  import { isAtomicWidgetSelected } from './sync/is-atomic-widget-selected';
@@ -18,6 +19,7 @@ export default function init() {
18
19
 
19
20
  // TODO: Move it from here once we have dynamic package.
20
21
  initDynamics();
22
+ initCssClasses();
21
23
  }
22
24
 
23
25
  const blockV1Panel = () => {