@elementor/editor-editing-panel 1.34.0 → 1.36.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.34.0",
3
+ "version": "1.36.0",
4
4
  "private": false,
5
5
  "author": "Elementor Team",
6
6
  "homepage": "https://elementor.com/",
@@ -39,21 +39,21 @@
39
39
  "dev": "tsup --config=../../tsup.dev.ts"
40
40
  },
41
41
  "dependencies": {
42
- "@elementor/editor": "0.19.1",
43
- "@elementor/editor-canvas": "0.21.1",
44
- "@elementor/editor-controls": "0.29.0",
42
+ "@elementor/editor": "0.19.3",
43
+ "@elementor/editor-canvas": "0.21.3",
44
+ "@elementor/editor-controls": "0.30.1",
45
45
  "@elementor/editor-current-user": "0.3.2",
46
- "@elementor/editor-elements": "0.8.2",
47
- "@elementor/editor-panels": "0.15.1",
46
+ "@elementor/editor-elements": "0.8.3",
47
+ "@elementor/editor-panels": "0.15.3",
48
48
  "@elementor/editor-props": "0.12.0",
49
- "@elementor/editor-responsive": "0.13.4",
50
- "@elementor/editor-styles": "0.6.6",
51
- "@elementor/editor-styles-repository": "0.8.7",
52
- "@elementor/editor-ui": "0.8.1",
53
- "@elementor/editor-v1-adapters": "0.11.0",
49
+ "@elementor/editor-responsive": "0.13.5",
50
+ "@elementor/editor-styles": "0.6.7",
51
+ "@elementor/editor-styles-repository": "0.8.8",
52
+ "@elementor/editor-ui": "0.8.2",
53
+ "@elementor/editor-v1-adapters": "0.12.0",
54
54
  "@elementor/icons": "1.40.1",
55
- "@elementor/locations": "0.7.7",
56
- "@elementor/menus": "0.1.4",
55
+ "@elementor/locations": "0.8.0",
56
+ "@elementor/menus": "0.1.5",
57
57
  "@elementor/schema": "0.1.2",
58
58
  "@elementor/session": "0.1.0",
59
59
  "@elementor/ui": "1.34.2",
@@ -19,17 +19,28 @@ import { useInputState, useOpenState } from './use-autocomplete-states';
19
19
  import { useCreateOption } from './use-create-option';
20
20
  import { useFilterOptions } from './use-filter-options';
21
21
 
22
- export function CreatableAutocomplete< TOption extends SafeOptionConstraint >( {
23
- selected,
24
- options,
25
- entityName,
26
- onSelect,
27
- placeholder,
28
- onCreate,
29
- validate,
30
- ...props
31
- }: CreatableAutocompleteProps< TOption > ) {
32
- const { inputValue, setInputValue, error, setError, handleInputChange } = useInputState( validate );
22
+ export const CreatableAutocomplete = React.forwardRef( CreatableAutocompleteInner ) as <
23
+ TOption extends SafeOptionConstraint,
24
+ >(
25
+ props: CreatableAutocompleteProps< TOption > & {
26
+ ref?: React.ForwardedRef< HTMLElement >;
27
+ }
28
+ ) => ReturnType< typeof CreatableAutocompleteInner >;
29
+
30
+ function CreatableAutocompleteInner< TOption extends SafeOptionConstraint >(
31
+ {
32
+ selected,
33
+ options,
34
+ entityName,
35
+ onSelect,
36
+ placeholder,
37
+ onCreate,
38
+ validate,
39
+ ...props
40
+ }: CreatableAutocompleteProps< TOption >,
41
+ ref: React.ForwardedRef< HTMLElement >
42
+ ) {
43
+ const { inputValue, setInputValue, error, setError, inputHandlers } = useInputState( validate );
33
44
  const { open, openDropdown, closeDropdown } = useOpenState( props.open );
34
45
  const { createOption, loading } = useCreateOption( { onCreate, validate, setInputValue, setError, closeDropdown } );
35
46
 
@@ -60,6 +71,7 @@ export function CreatableAutocomplete< TOption extends SafeOptionConstraint >( {
60
71
  ) );
61
72
  } }
62
73
  { ...( props as AutocompleteProps< InternalOption< TOption >, true, true, true > ) }
74
+ ref={ ref }
63
75
  freeSolo
64
76
  multiple
65
77
  clearOnBlur
@@ -75,8 +87,8 @@ export function CreatableAutocomplete< TOption extends SafeOptionConstraint >( {
75
87
  options={ internalOptions }
76
88
  ListboxComponent={
77
89
  error
78
- ? React.forwardRef< HTMLElement, ErrorTextProps >( ( _, ref ) => (
79
- <ErrorText ref={ ref } error={ error } />
90
+ ? React.forwardRef< HTMLElement, ErrorTextProps >( ( _, errorTextRef ) => (
91
+ <ErrorText ref={ errorTextRef } error={ error } />
80
92
  ) )
81
93
  : undefined
82
94
  }
@@ -88,7 +100,7 @@ export function CreatableAutocomplete< TOption extends SafeOptionConstraint >( {
88
100
  { ...params }
89
101
  placeholder={ placeholder }
90
102
  error={ Boolean( error ) }
91
- onChange={ handleInputChange }
103
+ { ...inputHandlers }
92
104
  sx={ ( theme: Theme ) => ( {
93
105
  '.MuiAutocomplete-inputRoot.MuiInputBase-adornedStart': {
94
106
  paddingLeft: theme.spacing( 0.25 ),
@@ -149,7 +161,7 @@ const ErrorText = React.forwardRef< HTMLElement, ErrorTextProps >( ( { error = '
149
161
  padding: theme.spacing( 2 ),
150
162
  } ) }
151
163
  >
152
- <Typography variant="caption" sx={ { color: 'error.main' } }>
164
+ <Typography variant="caption" sx={ { color: 'error.main', display: 'inline-block' } }>
153
165
  { error }
154
166
  </Typography>
155
167
  </Box>
@@ -29,7 +29,21 @@ export function useInputState( validate?: ( value: string, event: ValidationEven
29
29
  }
30
30
  };
31
31
 
32
- return { inputValue, setInputValue, error, setError, handleInputChange };
32
+ const handleInputBlur = () => {
33
+ setInputValue( '' );
34
+ setError( null );
35
+ };
36
+
37
+ return {
38
+ inputValue,
39
+ setInputValue,
40
+ error,
41
+ setError,
42
+ inputHandlers: {
43
+ onChange: handleInputChange,
44
+ onBlur: handleInputBlur,
45
+ },
46
+ };
33
47
  }
34
48
 
35
49
  export function useOpenState( initialOpen: boolean = false ) {
@@ -30,6 +30,7 @@ type CssClassItemProps = {
30
30
  onClickActive: ( id: string | null ) => void;
31
31
  renameLabel: ( newLabel: string ) => void;
32
32
  validateLabel?: ( newLabel: string ) => string | undefined | null;
33
+ setError?: ( error: string | null ) => void;
33
34
  };
34
35
 
35
36
  const CHIP_SIZE = 'tiny';
@@ -44,6 +45,7 @@ export function CssClassItem( {
44
45
  chipProps,
45
46
  onClickActive,
46
47
  renameLabel,
48
+ setError,
47
49
  }: CssClassItemProps ) {
48
50
  const { meta, setMetaState } = useStyle();
49
51
  const popupState = usePopupState( { variant: 'popover' } );
@@ -60,6 +62,7 @@ export function CssClassItem( {
60
62
  value: label,
61
63
  onSubmit: renameLabel,
62
64
  validation: validateLabel,
65
+ onError: setError,
63
66
  } );
64
67
 
65
68
  const color = error ? 'error' : colorProp;
@@ -84,7 +87,7 @@ export function CssClassItem( {
84
87
  size={ CHIP_SIZE }
85
88
  label={
86
89
  isEditing ? (
87
- <EditableField ref={ ref } error={ error } { ...getEditableProps() } />
90
+ <EditableField ref={ ref } { ...getEditableProps() } />
88
91
  ) : (
89
92
  <EllipsisWithTooltip maxWidth="10ch" title={ label } as="div" />
90
93
  )
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { type ReactElement } from 'react';
2
+ import { type ReactElement, useRef, useState } from 'react';
3
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';
@@ -12,6 +12,7 @@ import {
12
12
  useProviders,
13
13
  validateStyleLabel,
14
14
  } from '@elementor/editor-styles-repository';
15
+ import { WarningInfotip } from '@elementor/editor-ui';
15
16
  import { MapPinIcon } from '@elementor/icons';
16
17
  import { createLocation } from '@elementor/locations';
17
18
  import { Chip, FormLabel, Stack } from '@elementor/ui';
@@ -60,6 +61,9 @@ export function CssClassSelector() {
60
61
  const { value: appliedIds, setValue: setAppliedIds, pushValue: pushAppliedId } = useAppliedClassesIds();
61
62
  const { id: activeId, setId: setActiveId } = useStyle();
62
63
 
64
+ const autocompleteRef = useRef< HTMLElement | null >( null );
65
+ const [ renameError, setRenameError ] = useState< string | null >( null );
66
+
63
67
  const handleApply = useHandleApply( appliedIds, setAppliedIds );
64
68
  const { create, validate, entityName } = useCreateAction( { pushAppliedId, setActiveId } );
65
69
 
@@ -78,49 +82,59 @@ export function CssClassSelector() {
78
82
  <ClassSelectorActionsSlot />
79
83
  </Stack>
80
84
  </Stack>
81
- <CreatableAutocomplete
82
- id={ ID }
83
- size="tiny"
84
- placeholder={ showPlaceholder ? __( 'Type class name', 'elementor' ) : undefined }
85
- options={ options }
86
- selected={ applied }
87
- entityName={ entityName }
88
- onSelect={ handleApply }
89
- onCreate={ create ?? undefined }
90
- validate={ validate ?? undefined }
91
- limitTags={ TAGS_LIMIT }
92
- getLimitTagsText={ ( more ) => (
93
- <Chip size="tiny" variant="standard" label={ `+${ more }` } clickable />
94
- ) }
95
- renderTags={ ( values, getTagProps ) =>
96
- values.map( ( value, index ) => {
97
- const chipProps = getTagProps( { index } );
98
- const isActive = value.value === active?.value;
99
-
100
- const renameLabel = ( newLabel: string ) => {
101
- if ( ! value.value ) {
102
- throw new Error( `Cannot rename a class without style id` );
103
- }
104
- return updateClassByProvider( value.provider, { label: newLabel, id: value.value } );
105
- };
106
-
107
- return (
108
- <CssClassItem
109
- key={ chipProps.key }
110
- label={ value.label }
111
- provider={ value.provider }
112
- id={ value.value }
113
- isActive={ isActive }
114
- color={ isActive && value.color ? value.color : 'default' }
115
- icon={ value.icon }
116
- chipProps={ chipProps }
117
- onClickActive={ () => setActiveId( value.value ) }
118
- renameLabel={ renameLabel }
119
- />
120
- );
121
- } )
122
- }
123
- />
85
+ <WarningInfotip
86
+ open={ Boolean( renameError ) }
87
+ text={ renameError ?? '' }
88
+ placement="bottom"
89
+ width={ autocompleteRef.current?.getBoundingClientRect().width }
90
+ offset={ [ 0, -15 ] }
91
+ >
92
+ <CreatableAutocomplete< StyleDefOption >
93
+ id={ ID }
94
+ ref={ autocompleteRef }
95
+ size="tiny"
96
+ placeholder={ showPlaceholder ? __( 'Type class name', 'elementor' ) : undefined }
97
+ options={ options }
98
+ selected={ applied }
99
+ entityName={ entityName }
100
+ onSelect={ handleApply }
101
+ onCreate={ create ?? undefined }
102
+ validate={ validate ?? undefined }
103
+ limitTags={ TAGS_LIMIT }
104
+ getLimitTagsText={ ( more ) => (
105
+ <Chip size="tiny" variant="standard" label={ `+${ more }` } clickable />
106
+ ) }
107
+ renderTags={ ( values, getTagProps ) =>
108
+ values.map( ( value, index ) => {
109
+ const chipProps = getTagProps( { index } );
110
+ const isActive = value.value === active?.value;
111
+
112
+ const renameLabel = ( newLabel: string ) => {
113
+ if ( ! value.value ) {
114
+ throw new Error( `Cannot rename a class without style id` );
115
+ }
116
+ return updateClassByProvider( value.provider, { label: newLabel, id: value.value } );
117
+ };
118
+
119
+ return (
120
+ <CssClassItem
121
+ key={ chipProps.key }
122
+ label={ value.label }
123
+ provider={ value.provider }
124
+ id={ value.value }
125
+ isActive={ isActive }
126
+ color={ isActive && value.color ? value.color : 'default' }
127
+ icon={ value.icon }
128
+ chipProps={ chipProps }
129
+ onClickActive={ () => setActiveId( value.value ) }
130
+ renameLabel={ renameLabel }
131
+ setError={ setRenameError }
132
+ />
133
+ );
134
+ } )
135
+ }
136
+ />
137
+ </WarningInfotip>
124
138
  </Stack>
125
139
  );
126
140
  }
@@ -5,6 +5,7 @@ import { __ } from '@wordpress/i18n';
5
5
 
6
6
  import { useElement } from '../contexts/element-context';
7
7
  import { ScrollProvider } from '../contexts/scroll-context';
8
+ import { useStateByElement } from '../hooks/use-state-by-element';
8
9
  import { SettingsTab } from './settings-tab';
9
10
  import { stickyHeaderStyles, StyleTab } from './style-tab';
10
11
 
@@ -12,30 +13,46 @@ type TabValue = 'settings' | 'style';
12
13
 
13
14
  export const EditingPanelTabs = () => {
14
15
  const { element } = useElement();
15
-
16
- const { getTabProps, getTabPanelProps, getTabsProps } = useTabs< TabValue >( 'settings' );
17
-
18
16
  return (
19
17
  // When switching between elements, the local states should be reset. We are using key to rerender the tabs.
20
18
  // Reference: https://react.dev/learn/preserving-and-resetting-state#resetting-a-form-with-a-key
21
19
  <Fragment key={ element.id }>
22
- <ScrollProvider>
23
- <Stack direction="column" sx={ { width: '100%' } }>
24
- <Stack sx={ { ...stickyHeaderStyles, top: 0 } }>
25
- <Tabs variant="fullWidth" size="small" sx={ { mt: 0.5 } } { ...getTabsProps() }>
26
- <Tab label={ __( 'General', 'elementor' ) } { ...getTabProps( 'settings' ) } />
27
- <Tab label={ __( 'Style', 'elementor' ) } { ...getTabProps( 'style' ) } />
28
- </Tabs>
29
- <Divider />
30
- </Stack>
31
- <TabPanel { ...getTabPanelProps( 'settings' ) } disablePadding>
32
- <SettingsTab />
33
- </TabPanel>
34
- <TabPanel { ...getTabPanelProps( 'style' ) } disablePadding>
35
- <StyleTab />
36
- </TabPanel>
37
- </Stack>
38
- </ScrollProvider>
20
+ <PanelTabContent />
39
21
  </Fragment>
40
22
  );
41
23
  };
24
+
25
+ const PanelTabContent = () => {
26
+ const defaultComponentTab = 'settings';
27
+
28
+ const [ currentTab, setCurrentTab ] = useStateByElement< TabValue >( 'tab', defaultComponentTab );
29
+ const { getTabProps, getTabPanelProps, getTabsProps } = useTabs< TabValue >( currentTab );
30
+ return (
31
+ <ScrollProvider>
32
+ <Stack direction="column" sx={ { width: '100%' } }>
33
+ <Stack sx={ { ...stickyHeaderStyles, top: 0 } }>
34
+ <Tabs
35
+ variant="fullWidth"
36
+ size="small"
37
+ sx={ { mt: 0.5 } }
38
+ { ...getTabsProps() }
39
+ onChange={ ( _: unknown, newValue: TabValue ) => {
40
+ getTabsProps().onChange( _, newValue );
41
+ setCurrentTab( newValue );
42
+ } }
43
+ >
44
+ <Tab label={ __( 'General', 'elementor' ) } { ...getTabProps( 'settings' ) } />
45
+ <Tab label={ __( 'Style', 'elementor' ) } { ...getTabProps( 'style' ) } />
46
+ </Tabs>
47
+ <Divider />
48
+ </Stack>
49
+ <TabPanel { ...getTabPanelProps( 'settings' ) } disablePadding>
50
+ <SettingsTab />
51
+ </TabPanel>
52
+ <TabPanel { ...getTabPanelProps( 'style' ) } disablePadding>
53
+ <StyleTab />
54
+ </TabPanel>
55
+ </Stack>
56
+ </ScrollProvider>
57
+ );
58
+ };
@@ -1,7 +1,8 @@
1
1
  import * as React from 'react';
2
- import { type PropsWithChildren, useId, useState } from 'react';
2
+ import { type PropsWithChildren, useId } from 'react';
3
3
  import { Collapse, Divider, ListItemButton, ListItemText, Stack } from '@elementor/ui';
4
4
 
5
+ import { useStateByElement } from '../hooks/use-state-by-element';
5
6
  import { CollapseIcon } from './collapse-icon';
6
7
 
7
8
  type Props = PropsWithChildren< {
@@ -10,7 +11,7 @@ type Props = PropsWithChildren< {
10
11
  } >;
11
12
 
12
13
  export function Section( { title, children, defaultExpanded = false }: Props ) {
13
- const [ isOpen, setIsOpen ] = useState( !! defaultExpanded );
14
+ const [ isOpen, setIsOpen ] = useStateByElement( title, !! defaultExpanded );
14
15
 
15
16
  const id = useId();
16
17
  const labelId = `label-${ id }`;
@@ -21,7 +22,7 @@ export function Section( { title, children, defaultExpanded = false }: Props ) {
21
22
  <ListItemButton
22
23
  id={ labelId }
23
24
  aria-controls={ contentId }
24
- onClick={ () => setIsOpen( ( prev ) => ! prev ) }
25
+ onClick={ () => setIsOpen( ! isOpen ) }
25
26
  sx={ { '&:hover': { backgroundColor: 'transparent' } } }
26
27
  >
27
28
  <ListItemText
@@ -1,11 +1,11 @@
1
1
  import * as React from 'react';
2
2
  import { type ToggleButtonGroupItem, ToggleControl } from '@elementor/editor-controls';
3
+ import { isExperimentActive } from '@elementor/editor-v1-adapters';
3
4
  import { Stack } from '@elementor/ui';
4
5
  import { __ } from '@wordpress/i18n';
5
6
 
6
7
  import { useStylesInheritanceField } from '../../../contexts/styles-inheritance-context';
7
8
  import { StylesField } from '../../../controls-registry/styles-field';
8
- import { isExperimentActive } from '../../../sync/is-experiment-active';
9
9
  import { ControlLabel } from '../../control-label';
10
10
 
11
11
  type Displays = 'block' | 'flex' | 'inline-block' | 'inline-flex' | 'none';
@@ -1,11 +1,11 @@
1
1
  import * as React from 'react';
2
2
  import { type StringPropValue } from '@elementor/editor-props';
3
+ import { isExperimentActive } from '@elementor/editor-v1-adapters';
3
4
  import { useSessionStorage } from '@elementor/session';
4
5
 
5
6
  import { useStyle } from '../../../contexts/style-context';
6
7
  import { useStylesField } from '../../../hooks/use-styles-field';
7
8
  import { useStylesFields } from '../../../hooks/use-styles-fields';
8
- import { isExperimentActive } from '../../../sync/is-experiment-active';
9
9
  import { PanelDivider } from '../../panel-divider';
10
10
  import { SectionContent } from '../../section-content';
11
11
  import { DimensionsField } from './dimensions-field';
@@ -0,0 +1,18 @@
1
+ import { useState } from 'react';
2
+ import { getSessionStorageItem, setSessionStorageItem } from '@elementor/session';
3
+
4
+ import { useElement } from '../contexts/element-context';
5
+
6
+ export const useStateByElement = < T >( key: string, initialValue: T ) => {
7
+ const { element } = useElement();
8
+ const lookup = `elementor/editor-state/${ element.id }/${ key }`;
9
+ const storedValue = getSessionStorageItem< T >( lookup );
10
+ const [ value, setValue ] = useState( storedValue ?? initialValue );
11
+
12
+ const doUpdate = ( newValue: T ) => {
13
+ setSessionStorageItem( lookup, newValue );
14
+ setValue( newValue );
15
+ };
16
+
17
+ return [ value, doUpdate ] as const;
18
+ };
@@ -2,13 +2,13 @@ import * as React from 'react';
2
2
  import { useState } from 'react';
3
3
  import { useBoundProp } from '@elementor/editor-controls';
4
4
  import { ELEMENTS_BASE_STYLES_PROVIDER_KEY, isElementsStylesProvider } from '@elementor/editor-styles-repository';
5
+ import { isExperimentActive } from '@elementor/editor-v1-adapters';
5
6
  import { IconButton, Infotip } from '@elementor/ui';
6
7
  import { __ } from '@wordpress/i18n';
7
8
 
8
9
  import { StyleIndicator } from '../components/style-indicator';
9
10
  import { useStyle } from '../contexts/style-context';
10
11
  import { useStylesInheritanceField } from '../contexts/styles-inheritance-context';
11
- import { isExperimentActive } from '../sync/is-experiment-active';
12
12
  import { StyleIndicatorInfotip } from './styles-inheritance-infotip';
13
13
 
14
14
  export const StylesInheritanceIndicator = () => {
@@ -11,9 +11,3 @@ export const getElementorFrontendConfig = () => {
11
11
 
12
12
  return extendedWindow.elementorFrontend?.config ?? {};
13
13
  };
14
-
15
- export const getElementorCommonConfig = () => {
16
- const extendedWindow = window as unknown as ExtendedWindow;
17
-
18
- return extendedWindow.elementorCommon?.config ?? {};
19
- };
@@ -1,7 +0,0 @@
1
- import { getElementorCommonConfig } from './get-elementor-globals';
2
-
3
- export const isExperimentActive = ( experiment: string ) => {
4
- const allFeatures = getElementorCommonConfig()?.experimentalFeatures ?? {};
5
-
6
- return !! allFeatures?.[ experiment ];
7
- };