@elementor/editor-editing-panel 1.9.0 → 1.11.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/dist/index.d.mts +1 -35
  3. package/dist/index.d.ts +1 -35
  4. package/dist/index.js +996 -1059
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +905 -970
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +18 -14
  9. package/src/components/css-classes/css-class-item.tsx +130 -0
  10. package/src/components/css-classes/css-class-menu.tsx +151 -0
  11. package/src/components/{css-class-selector.tsx → css-classes/css-class-selector.tsx} +34 -160
  12. package/src/components/style-sections/layout-section/display-field.tsx +9 -1
  13. package/src/components/style-sections/layout-section/flex-order-field.tsx +5 -5
  14. package/src/components/style-sections/layout-section/flex-size-field.tsx +1 -1
  15. package/src/components/style-sections/layout-section/gap-control-field.tsx +0 -2
  16. package/src/components/style-sections/position-section/dimensions-field.tsx +1 -1
  17. package/src/components/style-sections/position-section/position-section.tsx +1 -1
  18. package/src/components/style-sections/typography-section/font-weight-field.tsx +9 -5
  19. package/src/components/style-sections/typography-section/text-alignment-field.tsx +16 -8
  20. package/src/components/style-sections/typography-section/transform-field.tsx +12 -3
  21. package/src/components/style-tab.tsx +1 -1
  22. package/src/contexts/style-context.tsx +36 -5
  23. package/src/controls-registry/control.tsx +3 -12
  24. package/src/controls-registry/controls-registry.tsx +3 -1
  25. package/src/controls-registry/settings-field.tsx +8 -1
  26. package/src/dynamics/components/dynamic-selection.tsx +1 -1
  27. package/src/dynamics/dynamic-control.tsx +1 -1
  28. package/src/dynamics/types.ts +2 -2
  29. package/src/dynamics/utils.ts +2 -2
  30. package/src/errors.ts +22 -0
  31. package/src/hooks/use-persist-dynamic-value.ts +1 -1
  32. package/src/hooks/use-styles-fields.ts +151 -9
  33. package/src/hooks/use-unapply-class.ts +4 -0
  34. package/src/index.ts +1 -2
  35. package/src/init.ts +2 -4
  36. package/src/sync/types.ts +4 -3
  37. package/src/components/collapsible-field.tsx +0 -36
  38. package/src/components/conditional-tooltip-wrapper.tsx +0 -58
  39. package/src/components/css-class-menu.tsx +0 -125
  40. package/src/components/editable-field.tsx +0 -166
  41. package/src/contexts/css-class-item-context.tsx +0 -31
  42. package/src/css-classes.ts +0 -45
  43. package/src/hooks/use-session-storage.ts +0 -46
  44. package/src/sync/enqueue-font.ts +0 -7
@@ -1,58 +0,0 @@
1
- import { useEffect, useRef, useState } from 'react';
2
- import * as React from 'react';
3
- import { Box, Tooltip } from '@elementor/ui';
4
-
5
- type ConditionalTooltipWrapperProps = {
6
- maxWidth: React.CSSProperties[ 'maxWidth' ];
7
- title: string;
8
- };
9
-
10
- export const ConditionalTooltipWrapper = ( { maxWidth, title }: ConditionalTooltipWrapperProps ) => {
11
- const elRef = useRef< HTMLElement >( null );
12
- const [ isOverflown, setIsOverflown ] = useState( false );
13
-
14
- useEffect( () => {
15
- const onResize = () => {
16
- const element = elRef.current;
17
-
18
- if ( element ) {
19
- setIsOverflown( element.scrollWidth > element.clientWidth );
20
- }
21
- };
22
-
23
- onResize();
24
-
25
- window.addEventListener( 'resize', onResize );
26
-
27
- return () => {
28
- window.removeEventListener( 'resize', onResize );
29
- };
30
- }, [] );
31
-
32
- if ( isOverflown ) {
33
- return (
34
- <Tooltip title={ title } placement="top">
35
- <Content maxWidth={ maxWidth } ref={ elRef }>
36
- { title }
37
- </Content>
38
- </Tooltip>
39
- );
40
- }
41
-
42
- return (
43
- <Content maxWidth={ maxWidth } ref={ elRef }>
44
- { title }
45
- </Content>
46
- );
47
- };
48
-
49
- type ContentProps = React.PropsWithChildren< Omit< ConditionalTooltipWrapperProps, 'title' > >;
50
-
51
- const Content = React.forwardRef( ( { maxWidth, ...tooltipProps }: ContentProps, ref ) => (
52
- <Box
53
- ref={ ref }
54
- position="relative"
55
- sx={ { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth } }
56
- { ...tooltipProps }
57
- />
58
- ) );
@@ -1,125 +0,0 @@
1
- import * as React from 'react';
2
- import { type RefObject } from 'react';
3
- import { type StyleDefinitionState } 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: StyleDefinitionState;
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,166 +0,0 @@
1
- import * as React from 'react';
2
- import { type ComponentProps, createContext, useContext, useEffect, useRef, useState } from 'react';
3
- import { Tooltip } from '@elementor/ui';
4
-
5
- type EditableFieldContext = {
6
- isEditing: boolean;
7
- openEditMode: () => void;
8
- closeEditMode: () => void;
9
- onChange: ( event: React.ChangeEvent< HTMLInputElement > ) => void;
10
- value: string;
11
- error?: string | null;
12
- submit: ( value: string ) => Promise< void >;
13
- editable?: boolean;
14
- submitting: boolean;
15
- };
16
-
17
- const Context = createContext< EditableFieldContext | null >( null );
18
-
19
- export type EditableFieldProviderProps = React.PropsWithChildren< {
20
- value: string;
21
- onSubmit: ( value: string ) => unknown | Promise< unknown >;
22
- validation?: ( value: string ) => string | undefined | null;
23
- editable?: boolean;
24
- } >;
25
-
26
- export const EditableFieldProvider = ( {
27
- children,
28
- value,
29
- onSubmit,
30
- validation,
31
- editable,
32
- }: EditableFieldProviderProps ) => {
33
- const [ isEditing, setIsEditing ] = useState( false );
34
- const [ submitting, setSubmitting ] = useState( false );
35
- const [ error, setError ] = useState< string | null | undefined >( null );
36
-
37
- const openEditMode = () => {
38
- setIsEditing( true );
39
- };
40
-
41
- const closeEditMode = () => {
42
- setError( null );
43
- setIsEditing( false );
44
- };
45
-
46
- const submit = async ( newValue: string ) => {
47
- if ( ! error ) {
48
- setSubmitting( true );
49
-
50
- try {
51
- await onSubmit( newValue );
52
- } finally {
53
- setSubmitting( false );
54
- }
55
- }
56
-
57
- closeEditMode();
58
- };
59
-
60
- const onChange = ( event: React.ChangeEvent< HTMLSpanElement > ) => {
61
- const { innerText: newValue } = event.target;
62
-
63
- if ( validation ) {
64
- setError( validation( newValue ) );
65
- }
66
- };
67
-
68
- return (
69
- <Context.Provider
70
- value={ {
71
- isEditing,
72
- openEditMode,
73
- closeEditMode,
74
- onChange,
75
- value,
76
- error,
77
- submit,
78
- editable,
79
- submitting,
80
- } }
81
- >
82
- { children }
83
- </Context.Provider>
84
- );
85
- };
86
-
87
- type EditableFieldProps = ComponentProps< 'div' >;
88
-
89
- export const EditableField = ( { children, onClick, ...props }: EditableFieldProps ) => {
90
- const ref = useRef< HTMLElement >( null );
91
- const { isEditing, closeEditMode, value, onChange, error, submit, editable } = useEditableField();
92
-
93
- useEffect( () => {
94
- if ( isEditing ) {
95
- ref.current?.focus();
96
- selectAll();
97
- }
98
- }, [ isEditing ] );
99
-
100
- const handleKeyDown = ( event: React.KeyboardEvent ) => {
101
- event.stopPropagation();
102
-
103
- if ( [ 'Escape' ].includes( event.key ) ) {
104
- return closeEditMode();
105
- }
106
-
107
- if ( [ 'Enter' ].includes( event.key ) ) {
108
- event.preventDefault();
109
- return submit( ( event.target as HTMLElement ).innerText );
110
- }
111
- };
112
-
113
- const handleClick = ( event: React.MouseEvent< HTMLDivElement > ) => {
114
- if ( isEditing ) {
115
- event.stopPropagation();
116
- }
117
-
118
- onClick?.( event );
119
- };
120
-
121
- const selectAll = () => {
122
- const selection = getSelection();
123
-
124
- if ( ! selection || ! ref.current ) {
125
- return;
126
- }
127
-
128
- const range = document.createRange();
129
- range.selectNodeContents( ref.current );
130
-
131
- selection.removeAllRanges();
132
- selection.addRange( range );
133
- };
134
-
135
- if ( ! editable ) {
136
- return children;
137
- }
138
-
139
- return (
140
- <Tooltip open={ !! error } title={ error } placement="top">
141
- { /* eslint-disable-next-line jsx-a11y/no-static-element-interactions */ }
142
- <div onKeyDown={ handleKeyDown } onClick={ handleClick } { ...props }>
143
- <span
144
- ref={ ref }
145
- role="textbox"
146
- onInput={ onChange }
147
- contentEditable={ isEditing }
148
- suppressContentEditableWarning
149
- onBlur={ closeEditMode }
150
- >
151
- { isEditing ? value : children }
152
- </span>
153
- </div>
154
- </Tooltip>
155
- );
156
- };
157
-
158
- export const useEditableField = () => {
159
- const contextValue = useContext( Context );
160
-
161
- if ( ! contextValue ) {
162
- throw new Error( 'useEditableField must be used within a EditableFieldProvider' );
163
- }
164
-
165
- return contextValue;
166
- };
@@ -1,31 +0,0 @@
1
- import * as React from 'react';
2
- import { createContext, type PropsWithChildren, useContext } 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 = useContext( ClassItemContext );
25
-
26
- if ( ! context ) {
27
- throw new Error( 'useCssClassItem must be used within a CssClassItemProvider' );
28
- }
29
-
30
- return context;
31
- }
@@ -1,45 +0,0 @@
1
- import { type StyleDefinitionState } from '@elementor/editor-styles';
2
-
3
- import { registerGlobalClassMenuItem, registerStateMenuItem } from './components/css-class-menu';
4
- import { useCssClassItem } from './contexts/css-class-item-context';
5
- import { useUnapplyClass } from './hooks/use-unapply-class';
6
-
7
- const STATES: NonNullable< StyleDefinitionState >[] = [ 'hover', 'focus', 'active' ];
8
-
9
- export function initCssClasses() {
10
- registerStateItems();
11
- registerGlobalClassItems();
12
- }
13
-
14
- function registerStateItems() {
15
- registerStateMenuItem( {
16
- id: 'normal',
17
- props: {
18
- state: null,
19
- },
20
- } );
21
-
22
- STATES.forEach( ( state ) => {
23
- registerStateMenuItem( {
24
- id: state,
25
- props: {
26
- state,
27
- },
28
- } );
29
- } );
30
- }
31
-
32
- function registerGlobalClassItems() {
33
- registerGlobalClassMenuItem( {
34
- id: 'unapply-class',
35
- useProps: () => {
36
- const { styleId: currentClass } = useCssClassItem();
37
- const unapplyClass = useUnapplyClass( currentClass );
38
-
39
- return {
40
- text: 'Remove',
41
- onClick: unapplyClass,
42
- };
43
- },
44
- } );
45
- }
@@ -1,46 +0,0 @@
1
- import { useEffect, useState } from 'react';
2
- import { getSessionStorageItem, removeSessionStorageItem, setSessionStorageItem } from '@elementor/utils';
3
-
4
- export const useSessionStorage = < T >( key: string ) => {
5
- const prefixedKey = `elementor/${ key }`;
6
-
7
- const [ value, setValue ] = useState< T | null >();
8
-
9
- useEffect( () => {
10
- return subscribeToSessionStorage< T | null >( prefixedKey, ( newValue ) => {
11
- setValue( newValue ?? null );
12
- } );
13
- }, [ prefixedKey ] );
14
-
15
- const saveValue = ( newValue: T ) => {
16
- setSessionStorageItem( prefixedKey, newValue );
17
- };
18
-
19
- const removeValue = () => {
20
- removeSessionStorageItem( prefixedKey );
21
- };
22
-
23
- return [ value, saveValue, removeValue ] as const;
24
- };
25
-
26
- const subscribeToSessionStorage = < T >( key: string, subscriber: ( value: T ) => void ) => {
27
- subscriber( getSessionStorageItem( key ) as T );
28
-
29
- const abortController = new AbortController();
30
-
31
- window.addEventListener(
32
- 'storage',
33
- ( e ) => {
34
- if ( e.key !== key || e.storageArea !== sessionStorage ) {
35
- return;
36
- }
37
-
38
- subscriber( getSessionStorageItem( key ) as T );
39
- },
40
- { signal: abortController.signal }
41
- );
42
-
43
- return () => {
44
- abortController.abort();
45
- };
46
- };
@@ -1,7 +0,0 @@
1
- import { type EnqueueFont, type ExtendedWindow } from './types';
2
-
3
- export const enqueueFont: EnqueueFont = ( fontFamily, context = 'editor' ) => {
4
- const extendedWindow = window as unknown as ExtendedWindow;
5
-
6
- return extendedWindow.elementor?.helpers?.enqueueFont( fontFamily, context ) ?? null;
7
- };