@elementor/editor-components 3.35.0-379 → 3.35.0-380

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 (28) hide show
  1. package/dist/index.js +1419 -564
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.mjs +1365 -494
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +22 -22
  6. package/src/components/component-panel-header/component-badge.tsx +8 -3
  7. package/src/components/component-panel-header/component-panel-header.tsx +5 -1
  8. package/src/components/component-properties-panel/component-properties-panel-content.tsx +165 -0
  9. package/src/components/component-properties-panel/component-properties-panel.tsx +51 -0
  10. package/src/components/component-properties-panel/properties-empty-state.tsx +44 -0
  11. package/src/components/component-properties-panel/properties-group.tsx +191 -0
  12. package/src/components/component-properties-panel/property-item.tsx +121 -0
  13. package/src/components/component-properties-panel/sortable.tsx +92 -0
  14. package/src/components/component-properties-panel/use-current-editable-item.ts +74 -0
  15. package/src/components/component-properties-panel/utils/generate-unique-label.ts +21 -0
  16. package/src/components/component-properties-panel/utils/validate-group-label.ts +24 -0
  17. package/src/components/instance-editing-panel/instance-editing-panel.tsx +1 -1
  18. package/src/components/overridable-props/overridable-prop-form.tsx +7 -4
  19. package/src/init.ts +3 -0
  20. package/src/store/actions/add-overridable-group.ts +47 -0
  21. package/src/store/actions/delete-overridable-group.ts +38 -0
  22. package/src/store/actions/delete-overridable-prop.ts +56 -0
  23. package/src/store/actions/rename-overridable-group.ts +39 -0
  24. package/src/store/actions/reorder-group-props.ts +43 -0
  25. package/src/store/actions/reorder-overridable-groups.ts +30 -0
  26. package/src/store/actions/set-overridable-prop.ts +21 -126
  27. package/src/store/actions/update-overridable-prop.ts +58 -0
  28. package/src/store/utils/groups-transformers.ts +185 -0
@@ -0,0 +1,92 @@
1
+ import * as React from 'react';
2
+ import { GripVerticalIcon } from '@elementor/icons';
3
+ import {
4
+ Box,
5
+ styled,
6
+ UnstableSortableItem,
7
+ type UnstableSortableItemProps,
8
+ type UnstableSortableItemRenderProps,
9
+ UnstableSortableProvider,
10
+ type UnstableSortableProviderProps,
11
+ } from '@elementor/ui';
12
+
13
+ export const SortableProvider = < T extends string >( props: UnstableSortableProviderProps< T > ) => (
14
+ <UnstableSortableProvider restrictAxis variant="static" dragPlaceholderStyle={ { opacity: '1' } } { ...props } />
15
+ );
16
+
17
+ export type SortableTriggerProps = React.HTMLAttributes< HTMLDivElement > & {
18
+ triggerClassName?: string;
19
+ };
20
+
21
+ export const SortableTrigger = ( { triggerClassName, ...props }: SortableTriggerProps ) => (
22
+ <StyledSortableTrigger
23
+ { ...props }
24
+ role="button"
25
+ className={ `sortable-trigger ${ triggerClassName ?? '' }`.trim() }
26
+ aria-label="sort"
27
+ >
28
+ <GripVerticalIcon fontSize="tiny" />
29
+ </StyledSortableTrigger>
30
+ );
31
+
32
+ type SortableItemProps = {
33
+ id: UnstableSortableItemProps[ 'id' ];
34
+ children: ( props: SortableItemRenderData ) => React.ReactNode;
35
+ };
36
+
37
+ export type SortableItemRenderData = {
38
+ isDragged: boolean;
39
+ isDragPlaceholder: boolean;
40
+ triggerProps: React.HTMLAttributes< HTMLElement >;
41
+ triggerStyle: React.CSSProperties;
42
+ };
43
+
44
+ export const SortableItem = ( { children, id }: SortableItemProps ) => (
45
+ <UnstableSortableItem
46
+ id={ id }
47
+ render={ ( {
48
+ itemProps,
49
+ isDragged,
50
+ triggerProps,
51
+ itemStyle,
52
+ triggerStyle,
53
+ dropIndicationStyle,
54
+ showDropIndication,
55
+ isDragOverlay,
56
+ isDragPlaceholder,
57
+ }: UnstableSortableItemRenderProps ) => (
58
+ <Box
59
+ { ...itemProps }
60
+ style={ itemStyle }
61
+ component="div"
62
+ role="listitem"
63
+ sx={ {
64
+ backgroundColor: isDragOverlay ? 'background.paper' : undefined,
65
+ } }
66
+ >
67
+ { children( {
68
+ isDragged,
69
+ isDragPlaceholder,
70
+ triggerProps,
71
+ triggerStyle,
72
+ } ) }
73
+ { showDropIndication && <SortableItemIndicator style={ dropIndicationStyle } /> }
74
+ </Box>
75
+ ) }
76
+ />
77
+ );
78
+
79
+ const StyledSortableTrigger = styled( 'div' )( ( { theme } ) => ( {
80
+ position: 'absolute',
81
+ left: 0,
82
+ top: '50%',
83
+ transform: `translate( -${ theme.spacing( 1.5 ) }, -50% )`,
84
+ color: theme.palette.action.active,
85
+ cursor: 'grab',
86
+ } ) );
87
+
88
+ const SortableItemIndicator = styled( Box )`
89
+ width: 100%;
90
+ height: 1px;
91
+ background-color: ${ ( { theme } ) => theme.palette.text.primary };
92
+ `;
@@ -0,0 +1,74 @@
1
+ import { useState } from 'react';
2
+ import type * as React from 'react';
3
+ import { setDocumentModifiedStatus } from '@elementor/editor-documents';
4
+ import { useEditable } from '@elementor/editor-ui';
5
+ import { __ } from '@wordpress/i18n';
6
+
7
+ import { renameOverridableGroup } from '../../store/actions/rename-overridable-group';
8
+ import { useCurrentComponentId } from '../../store/store';
9
+ import { useOverridableProps } from '../component-panel-header/use-overridable-props';
10
+ import { validateGroupLabel } from './utils/validate-group-label';
11
+
12
+ export type GroupLabelEditableState = {
13
+ editableRef: React.RefObject< HTMLElement | null >;
14
+ isEditing: boolean;
15
+ error: string | null;
16
+ getEditableProps: () => { value: string };
17
+ setEditingGroupId: ( groupId: string ) => void;
18
+ editingGroupId: string | null;
19
+ };
20
+
21
+ export function useCurrentEditableItem(): GroupLabelEditableState {
22
+ const [ editingGroupId, setEditingGroupId ] = useState< string | null >( null );
23
+ const currentComponentId = useCurrentComponentId();
24
+ const overridableProps = useOverridableProps( currentComponentId );
25
+
26
+ const allGroupsRecord = overridableProps?.groups?.items ?? {};
27
+ const currentGroup = editingGroupId ? allGroupsRecord[ editingGroupId ] : null;
28
+
29
+ const validateLabel = ( newLabel: string ): string | null => {
30
+ const otherGroups = Object.fromEntries(
31
+ Object.entries( allGroupsRecord ).filter( ( [ id ] ) => id !== editingGroupId )
32
+ );
33
+
34
+ return validateGroupLabel( newLabel, otherGroups ) || null;
35
+ };
36
+
37
+ const handleSubmit = ( newLabel: string ) => {
38
+ if ( ! editingGroupId || ! currentComponentId ) {
39
+ throw new Error( __( 'Group ID or component ID is missing', 'elementor' ) );
40
+ }
41
+
42
+ renameOverridableGroup( {
43
+ componentId: currentComponentId,
44
+ groupId: editingGroupId,
45
+ label: newLabel,
46
+ } );
47
+
48
+ setDocumentModifiedStatus( true );
49
+ };
50
+
51
+ const {
52
+ ref: editableRef,
53
+ openEditMode,
54
+ isEditing,
55
+ error,
56
+ getProps: getEditableProps,
57
+ } = useEditable( {
58
+ value: currentGroup?.label ?? '',
59
+ onSubmit: handleSubmit,
60
+ validation: validateLabel,
61
+ } );
62
+
63
+ return {
64
+ editableRef,
65
+ isEditing,
66
+ error,
67
+ getEditableProps,
68
+ setEditingGroupId: ( groupId ) => {
69
+ setEditingGroupId( groupId );
70
+ openEditMode();
71
+ },
72
+ editingGroupId,
73
+ };
74
+ }
@@ -0,0 +1,21 @@
1
+ import { type OverridablePropsGroup } from '../../../types';
2
+
3
+ const DEFAULT_NEW_GROUP_LABEL = 'New group';
4
+
5
+ export function generateUniqueLabel( groups: OverridablePropsGroup[] ): string {
6
+ const existingLabels = new Set( groups.map( ( group ) => group.label ) );
7
+
8
+ if ( ! existingLabels.has( DEFAULT_NEW_GROUP_LABEL ) ) {
9
+ return DEFAULT_NEW_GROUP_LABEL;
10
+ }
11
+
12
+ let index = 1;
13
+ let newLabel = `${ DEFAULT_NEW_GROUP_LABEL }-${ index }`;
14
+
15
+ while ( existingLabels.has( newLabel ) ) {
16
+ index++;
17
+ newLabel = `${ DEFAULT_NEW_GROUP_LABEL }-${ index }`;
18
+ }
19
+
20
+ return newLabel;
21
+ }
@@ -0,0 +1,24 @@
1
+ import { __ } from '@wordpress/i18n';
2
+
3
+ import { type OverridablePropsGroup } from '../../../types';
4
+
5
+ export const ERROR_MESSAGES = {
6
+ EMPTY_NAME: __( 'Group name is required', 'elementor' ),
7
+ DUPLICATE_NAME: __( 'Group name already exists', 'elementor' ),
8
+ } as const;
9
+
10
+ export function validateGroupLabel( label: string, existingGroups: Record< string, OverridablePropsGroup > ): string {
11
+ const trimmedLabel = label.trim();
12
+
13
+ if ( ! trimmedLabel ) {
14
+ return ERROR_MESSAGES.EMPTY_NAME;
15
+ }
16
+
17
+ const isDuplicate = Object.values( existingGroups ).some( ( group ) => group.label === trimmedLabel );
18
+
19
+ if ( isDuplicate ) {
20
+ return ERROR_MESSAGES.DUPLICATE_NAME;
21
+ }
22
+
23
+ return '';
24
+ }
@@ -18,7 +18,7 @@ import { OverridePropsGroup } from './override-props-group';
18
18
  export function InstanceEditingPanel() {
19
19
  const { element } = useElement();
20
20
  const settings = useElementSetting( element.id, 'component_instance' );
21
- const componentId = ( componentInstancePropTypeUtil.extract( settings )?.component_id as NumberPropValue ).value;
21
+ const componentId = ( componentInstancePropTypeUtil.extract( settings )?.component_id as NumberPropValue )?.value;
22
22
 
23
23
  const component = componentId ? selectComponent( getState(), componentId ) : null;
24
24
  const overridableProps = componentId ? selectOverridableProps( getState(), componentId ) : null;
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { useState } from 'react';
3
3
  import { Form, MenuListItem } from '@elementor/editor-ui';
4
- import { Button, FormLabel, Grid, Select, Stack, TextField, Typography } from '@elementor/ui';
4
+ import { Button, FormLabel, Grid, Select, Stack, type SxProps, TextField, Typography } from '@elementor/ui';
5
5
  import { __ } from '@wordpress/i18n';
6
6
 
7
7
  import { type OverridableProp } from '../../types';
@@ -14,9 +14,10 @@ type Props = {
14
14
  onSubmit: ( data: { label: string; group: string | null } ) => void;
15
15
  currentValue?: OverridableProp;
16
16
  groups?: { value: string; label: string }[];
17
+ sx?: SxProps;
17
18
  };
18
19
 
19
- export function OverridablePropForm( { onSubmit, groups, currentValue }: Props ) {
20
+ export function OverridablePropForm( { onSubmit, groups, currentValue, sx }: Props ) {
20
21
  const [ propLabel, setPropLabel ] = useState< string | null >( currentValue?.label ?? null );
21
22
  const [ group, setGroup ] = useState< string | null >( currentValue?.groupId ?? groups?.[ 0 ]?.value ?? null );
22
23
 
@@ -30,7 +31,7 @@ export function OverridablePropForm( { onSubmit, groups, currentValue }: Props )
30
31
 
31
32
  return (
32
33
  <Form onSubmit={ () => onSubmit( { label: propLabel ?? '', group } ) }>
33
- <Stack alignItems="start" width="268px">
34
+ <Stack alignItems="start" sx={ { width: '268px', ...sx } }>
34
35
  <Stack
35
36
  direction="row"
36
37
  alignItems="center"
@@ -67,7 +68,9 @@ export function OverridablePropForm( { onSubmit, groups, currentValue }: Props )
67
68
  size={ SIZE }
68
69
  fullWidth
69
70
  value={ group ?? null }
70
- onChange={ setGroup }
71
+ onChange={ ( e: React.ChangeEvent< HTMLSelectElement > ) =>
72
+ setGroup( e.target.value as string | null )
73
+ }
71
74
  displayEmpty
72
75
  renderValue={ ( selectedValue: string | null ) => {
73
76
  if ( ! selectedValue || selectedValue === '' ) {
package/src/init.ts CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  } from '@elementor/editor-editing-panel';
15
15
  import { type V1ElementData } from '@elementor/editor-elements';
16
16
  import { injectTab } from '@elementor/editor-elements-panel';
17
+ import { __registerPanel as registerPanel } from '@elementor/editor-panels';
17
18
  import { stylesRepository } from '@elementor/editor-styles-repository';
18
19
  import { registerDataHook } from '@elementor/editor-v1-adapters';
19
20
  import { __registerSlice as registerSlice } from '@elementor/store';
@@ -23,6 +24,7 @@ import { componentInstanceTransformer } from './component-instance-transformer';
23
24
  import { componentOverridableTransformer } from './component-overridable-transformer';
24
25
  import { componentOverrideTransformer } from './component-override-transformer';
25
26
  import { ComponentPanelHeader } from './components/component-panel-header/component-panel-header';
27
+ import { panel as componentPropertiesPanel } from './components/component-properties-panel/component-properties-panel';
26
28
  import { Components } from './components/components-tab/components';
27
29
  import { COMPONENT_DOCUMENT_TYPE } from './components/consts';
28
30
  import { CreateComponentForm } from './components/create-component-form/create-component-form';
@@ -47,6 +49,7 @@ export function init() {
47
49
  stylesRepository.register( componentsStylesProvider );
48
50
 
49
51
  registerSlice( slice );
52
+ registerPanel( componentPropertiesPanel );
50
53
 
51
54
  registerElementType( TYPE, ( options: CreateTemplatedElementTypeOptions ) =>
52
55
  createComponentType( { ...options, showLockedByModal: openEditModeDialog } )
@@ -0,0 +1,47 @@
1
+ import { __dispatch as dispatch, __getState as getState } from '@elementor/store';
2
+
3
+ import { type ComponentId, type OverridablePropsGroup } from '../../types';
4
+ import { selectOverridableProps, slice } from '../store';
5
+
6
+ type AddGroupParams = {
7
+ componentId: ComponentId;
8
+ groupId: string;
9
+ label: string;
10
+ };
11
+
12
+ export function addOverridableGroup( {
13
+ componentId,
14
+ groupId,
15
+ label,
16
+ }: AddGroupParams ): OverridablePropsGroup | undefined {
17
+ const overridableProps = selectOverridableProps( getState(), componentId );
18
+
19
+ if ( ! overridableProps ) {
20
+ return;
21
+ }
22
+
23
+ const newGroup: OverridablePropsGroup = {
24
+ id: groupId,
25
+ label,
26
+ props: [],
27
+ };
28
+
29
+ dispatch(
30
+ slice.actions.setOverridableProps( {
31
+ componentId,
32
+ overridableProps: {
33
+ ...overridableProps,
34
+ groups: {
35
+ ...overridableProps.groups,
36
+ items: {
37
+ ...overridableProps.groups.items,
38
+ [ groupId ]: newGroup,
39
+ },
40
+ order: [ groupId, ...overridableProps.groups.order ],
41
+ },
42
+ },
43
+ } )
44
+ );
45
+
46
+ return newGroup;
47
+ }
@@ -0,0 +1,38 @@
1
+ import { __dispatch as dispatch, __getState as getState } from '@elementor/store';
2
+
3
+ import { type ComponentId } from '../../types';
4
+ import { selectOverridableProps, slice } from '../store';
5
+ import { deleteGroup } from '../utils/groups-transformers';
6
+
7
+ type DeleteGroupParams = {
8
+ componentId: ComponentId;
9
+ groupId: string;
10
+ };
11
+
12
+ export function deleteOverridableGroup( { componentId, groupId }: DeleteGroupParams ): boolean {
13
+ const overridableProps = selectOverridableProps( getState(), componentId );
14
+
15
+ if ( ! overridableProps ) {
16
+ return false;
17
+ }
18
+
19
+ const group = overridableProps.groups.items[ groupId ];
20
+
21
+ if ( ! group || group.props.length > 0 ) {
22
+ return false;
23
+ }
24
+
25
+ const updatedGroups = deleteGroup( overridableProps.groups, groupId );
26
+
27
+ dispatch(
28
+ slice.actions.setOverridableProps( {
29
+ componentId,
30
+ overridableProps: {
31
+ ...overridableProps,
32
+ groups: updatedGroups,
33
+ },
34
+ } )
35
+ );
36
+
37
+ return true;
38
+ }
@@ -0,0 +1,56 @@
1
+ import { getContainer, updateElementSettings } from '@elementor/editor-elements';
2
+ import { __dispatch as dispatch, __getState as getState } from '@elementor/store';
3
+
4
+ import { type ComponentId } from '../../types';
5
+ import { selectOverridableProps, slice } from '../store';
6
+ import { removePropFromAllGroups } from '../utils/groups-transformers';
7
+
8
+ type DeletePropParams = {
9
+ componentId: ComponentId;
10
+ propKey: string;
11
+ };
12
+
13
+ export function deleteOverridableProp( { componentId, propKey }: DeletePropParams ): void {
14
+ const overridableProps = selectOverridableProps( getState(), componentId );
15
+
16
+ if ( ! overridableProps ) {
17
+ return;
18
+ }
19
+
20
+ const prop = overridableProps.props[ propKey ];
21
+
22
+ if ( ! prop ) {
23
+ return;
24
+ }
25
+
26
+ revertElementSetting( prop.elementId, prop.propKey, prop.originValue );
27
+
28
+ const { [ propKey ]: removedProp, ...remainingProps } = overridableProps.props;
29
+
30
+ const updatedGroups = removePropFromAllGroups( overridableProps.groups, propKey );
31
+
32
+ dispatch(
33
+ slice.actions.setOverridableProps( {
34
+ componentId,
35
+ overridableProps: {
36
+ ...overridableProps,
37
+ props: remainingProps,
38
+ groups: updatedGroups,
39
+ },
40
+ } )
41
+ );
42
+ }
43
+
44
+ function revertElementSetting( elementId: string, settingKey: string, originValue: unknown ): void {
45
+ const container = getContainer( elementId );
46
+
47
+ if ( ! container ) {
48
+ return;
49
+ }
50
+
51
+ updateElementSettings( {
52
+ id: elementId,
53
+ props: { [ settingKey ]: originValue ?? null },
54
+ withHistory: false,
55
+ } );
56
+ }
@@ -0,0 +1,39 @@
1
+ import { __dispatch as dispatch, __getState as getState } from '@elementor/store';
2
+
3
+ import { type ComponentId } from '../../types';
4
+ import { selectOverridableProps, slice } from '../store';
5
+ import { renameGroup } from '../utils/groups-transformers';
6
+
7
+ type RenameGroupParams = {
8
+ componentId: ComponentId;
9
+ groupId: string;
10
+ label: string;
11
+ };
12
+
13
+ export function renameOverridableGroup( { componentId, groupId, label }: RenameGroupParams ): boolean {
14
+ const overridableProps = selectOverridableProps( getState(), componentId );
15
+
16
+ if ( ! overridableProps ) {
17
+ return false;
18
+ }
19
+
20
+ const group = overridableProps.groups.items[ groupId ];
21
+
22
+ if ( ! group ) {
23
+ return false;
24
+ }
25
+
26
+ const updatedGroups = renameGroup( overridableProps.groups, groupId, label );
27
+
28
+ dispatch(
29
+ slice.actions.setOverridableProps( {
30
+ componentId,
31
+ overridableProps: {
32
+ ...overridableProps,
33
+ groups: updatedGroups,
34
+ },
35
+ } )
36
+ );
37
+
38
+ return true;
39
+ }
@@ -0,0 +1,43 @@
1
+ import { __dispatch as dispatch, __getState as getState } from '@elementor/store';
2
+
3
+ import { type ComponentId } from '../../types';
4
+ import { selectOverridableProps, slice } from '../store';
5
+
6
+ type ReorderGroupPropsParams = {
7
+ componentId: ComponentId;
8
+ groupId: string;
9
+ newPropsOrder: string[];
10
+ };
11
+
12
+ export function reorderGroupProps( { componentId, groupId, newPropsOrder }: ReorderGroupPropsParams ): void {
13
+ const overridableProps = selectOverridableProps( getState(), componentId );
14
+
15
+ if ( ! overridableProps ) {
16
+ return;
17
+ }
18
+
19
+ const group = overridableProps.groups.items[ groupId ];
20
+
21
+ if ( ! group ) {
22
+ return;
23
+ }
24
+
25
+ dispatch(
26
+ slice.actions.setOverridableProps( {
27
+ componentId,
28
+ overridableProps: {
29
+ ...overridableProps,
30
+ groups: {
31
+ ...overridableProps.groups,
32
+ items: {
33
+ ...overridableProps.groups.items,
34
+ [ groupId ]: {
35
+ ...group,
36
+ props: newPropsOrder,
37
+ },
38
+ },
39
+ },
40
+ },
41
+ } )
42
+ );
43
+ }
@@ -0,0 +1,30 @@
1
+ import { __dispatch as dispatch, __getState as getState } from '@elementor/store';
2
+
3
+ import { type ComponentId } from '../../types';
4
+ import { selectOverridableProps, slice } from '../store';
5
+
6
+ type ReorderGroupsParams = {
7
+ componentId: ComponentId;
8
+ newOrder: string[];
9
+ };
10
+
11
+ export function reorderOverridableGroups( { componentId, newOrder }: ReorderGroupsParams ): void {
12
+ const overridableProps = selectOverridableProps( getState(), componentId );
13
+
14
+ if ( ! overridableProps ) {
15
+ return;
16
+ }
17
+
18
+ dispatch(
19
+ slice.actions.setOverridableProps( {
20
+ componentId,
21
+ overridableProps: {
22
+ ...overridableProps,
23
+ groups: {
24
+ ...overridableProps.groups,
25
+ order: newOrder,
26
+ },
27
+ },
28
+ } )
29
+ );
30
+ }