@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.
- package/dist/index.js +1419 -564
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1365 -494
- package/dist/index.mjs.map +1 -1
- package/package.json +22 -22
- package/src/components/component-panel-header/component-badge.tsx +8 -3
- package/src/components/component-panel-header/component-panel-header.tsx +5 -1
- package/src/components/component-properties-panel/component-properties-panel-content.tsx +165 -0
- package/src/components/component-properties-panel/component-properties-panel.tsx +51 -0
- package/src/components/component-properties-panel/properties-empty-state.tsx +44 -0
- package/src/components/component-properties-panel/properties-group.tsx +191 -0
- package/src/components/component-properties-panel/property-item.tsx +121 -0
- package/src/components/component-properties-panel/sortable.tsx +92 -0
- package/src/components/component-properties-panel/use-current-editable-item.ts +74 -0
- package/src/components/component-properties-panel/utils/generate-unique-label.ts +21 -0
- package/src/components/component-properties-panel/utils/validate-group-label.ts +24 -0
- package/src/components/instance-editing-panel/instance-editing-panel.tsx +1 -1
- package/src/components/overridable-props/overridable-prop-form.tsx +7 -4
- package/src/init.ts +3 -0
- package/src/store/actions/add-overridable-group.ts +47 -0
- package/src/store/actions/delete-overridable-group.ts +38 -0
- package/src/store/actions/delete-overridable-prop.ts +56 -0
- package/src/store/actions/rename-overridable-group.ts +39 -0
- package/src/store/actions/reorder-group-props.ts +43 -0
- package/src/store/actions/reorder-overridable-groups.ts +30 -0
- package/src/store/actions/set-overridable-prop.ts +21 -126
- package/src/store/actions/update-overridable-prop.ts +58 -0
- package/src/store/utils/groups-transformers.ts +185 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-components",
|
|
3
3
|
"description": "Elementor editor components",
|
|
4
|
-
"version": "3.35.0-
|
|
4
|
+
"version": "3.35.0-380",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Elementor Team",
|
|
7
7
|
"homepage": "https://elementor.com/",
|
|
@@ -40,30 +40,30 @@
|
|
|
40
40
|
"dev": "tsup --config=../../tsup.dev.ts"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@elementor/editor": "3.35.0-
|
|
44
|
-
"@elementor/editor-canvas": "3.35.0-
|
|
45
|
-
"@elementor/editor-controls": "3.35.0-
|
|
46
|
-
"@elementor/editor-documents": "3.35.0-
|
|
47
|
-
"@elementor/editor-editing-panel": "3.35.0-
|
|
48
|
-
"@elementor/editor-elements": "3.35.0-
|
|
49
|
-
"@elementor/editor-elements-panel": "3.35.0-
|
|
50
|
-
"@elementor/editor-mcp": "3.35.0-
|
|
51
|
-
"@elementor/editor-panels": "3.35.0-
|
|
52
|
-
"@elementor/editor-props": "3.35.0-
|
|
53
|
-
"@elementor/editor-styles-repository": "3.35.0-
|
|
54
|
-
"@elementor/editor-ui": "3.35.0-
|
|
55
|
-
"@elementor/editor-v1-adapters": "3.35.0-
|
|
56
|
-
"@elementor/http-client": "3.35.0-
|
|
43
|
+
"@elementor/editor": "3.35.0-380",
|
|
44
|
+
"@elementor/editor-canvas": "3.35.0-380",
|
|
45
|
+
"@elementor/editor-controls": "3.35.0-380",
|
|
46
|
+
"@elementor/editor-documents": "3.35.0-380",
|
|
47
|
+
"@elementor/editor-editing-panel": "3.35.0-380",
|
|
48
|
+
"@elementor/editor-elements": "3.35.0-380",
|
|
49
|
+
"@elementor/editor-elements-panel": "3.35.0-380",
|
|
50
|
+
"@elementor/editor-mcp": "3.35.0-380",
|
|
51
|
+
"@elementor/editor-panels": "3.35.0-380",
|
|
52
|
+
"@elementor/editor-props": "3.35.0-380",
|
|
53
|
+
"@elementor/editor-styles-repository": "3.35.0-380",
|
|
54
|
+
"@elementor/editor-ui": "3.35.0-380",
|
|
55
|
+
"@elementor/editor-v1-adapters": "3.35.0-380",
|
|
56
|
+
"@elementor/http-client": "3.35.0-380",
|
|
57
57
|
"@elementor/icons": "^1.63.0",
|
|
58
|
-
"@elementor/mixpanel": "3.35.0-
|
|
59
|
-
"@elementor/query": "3.35.0-
|
|
60
|
-
"@elementor/schema": "3.35.0-
|
|
61
|
-
"@elementor/store": "3.35.0-
|
|
58
|
+
"@elementor/mixpanel": "3.35.0-380",
|
|
59
|
+
"@elementor/query": "3.35.0-380",
|
|
60
|
+
"@elementor/schema": "3.35.0-380",
|
|
61
|
+
"@elementor/store": "3.35.0-380",
|
|
62
62
|
"@elementor/ui": "1.36.17",
|
|
63
|
-
"@elementor/utils": "3.35.0-
|
|
63
|
+
"@elementor/utils": "3.35.0-380",
|
|
64
64
|
"@wordpress/i18n": "^5.13.0",
|
|
65
|
-
"@elementor/editor-notifications": "3.35.0-
|
|
66
|
-
"@elementor/editor-current-user": "3.35.0-
|
|
65
|
+
"@elementor/editor-notifications": "3.35.0-380",
|
|
66
|
+
"@elementor/editor-current-user": "3.35.0-380"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
69
|
"react": "^18.3.1",
|
|
@@ -4,8 +4,8 @@ import { ComponentPropListIcon } from '@elementor/icons';
|
|
|
4
4
|
import { Badge, Box, keyframes, styled, ToggleButton } from '@elementor/ui';
|
|
5
5
|
import { __ } from '@wordpress/i18n';
|
|
6
6
|
|
|
7
|
-
export const ComponentsBadge = React.forwardRef< HTMLDivElement, { overridesCount: number } >(
|
|
8
|
-
( { overridesCount }, ref ) => {
|
|
7
|
+
export const ComponentsBadge = React.forwardRef< HTMLDivElement, { overridesCount: number; onClick: () => void } >(
|
|
8
|
+
( { overridesCount, onClick }, ref ) => {
|
|
9
9
|
const prevCount = usePrevious( overridesCount );
|
|
10
10
|
|
|
11
11
|
const isFirstOverride = prevCount === 0 && overridesCount === 1;
|
|
@@ -24,7 +24,12 @@ export const ComponentsBadge = React.forwardRef< HTMLDivElement, { overridesCoun
|
|
|
24
24
|
</Box>
|
|
25
25
|
}
|
|
26
26
|
>
|
|
27
|
-
<ToggleButton
|
|
27
|
+
<ToggleButton
|
|
28
|
+
value="overrides"
|
|
29
|
+
size="tiny"
|
|
30
|
+
onClick={ onClick }
|
|
31
|
+
aria-label={ __( 'View overrides', 'elementor' ) }
|
|
32
|
+
>
|
|
28
33
|
<ComponentPropListIcon fontSize="tiny" />
|
|
29
34
|
</ToggleButton>
|
|
30
35
|
</StyledBadge>
|
|
@@ -7,6 +7,7 @@ import { __ } from '@wordpress/i18n';
|
|
|
7
7
|
|
|
8
8
|
import { useNavigateBack } from '../../hooks/use-navigate-back';
|
|
9
9
|
import { useCurrentComponentId } from '../../store/store';
|
|
10
|
+
import { usePanelActions } from '../component-properties-panel/component-properties-panel';
|
|
10
11
|
import { ComponentIntroduction } from '../components-tab/component-introduction';
|
|
11
12
|
import { ComponentsBadge } from './component-badge';
|
|
12
13
|
import { useOverridableProps } from './use-overridable-props';
|
|
@@ -20,6 +21,9 @@ export const ComponentPanelHeader = () => {
|
|
|
20
21
|
const componentName = getComponentName();
|
|
21
22
|
const [ isMessageSuppressed, suppressMessage ] = useSuppressedMessage( MESSAGE_KEY );
|
|
22
23
|
const [ shouldShowIntroduction, setShouldShowIntroduction ] = React.useState( ! isMessageSuppressed );
|
|
24
|
+
|
|
25
|
+
const { open: openPropertiesPanel } = usePanelActions();
|
|
26
|
+
|
|
23
27
|
const overridesCount = overridableProps ? Object.keys( overridableProps.props ).length : 0;
|
|
24
28
|
const anchorRef = React.useRef< HTMLDivElement >( null );
|
|
25
29
|
|
|
@@ -53,7 +57,7 @@ export const ComponentPanelHeader = () => {
|
|
|
53
57
|
</Typography>
|
|
54
58
|
</Stack>
|
|
55
59
|
</Stack>
|
|
56
|
-
<ComponentsBadge overridesCount={ overridesCount } ref={ anchorRef } />
|
|
60
|
+
<ComponentsBadge overridesCount={ overridesCount } ref={ anchorRef } onClick={ openPropertiesPanel } />
|
|
57
61
|
</Stack>
|
|
58
62
|
<Divider />
|
|
59
63
|
<ComponentIntroduction
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useMemo, useState } from 'react';
|
|
3
|
+
import { setDocumentModifiedStatus } from '@elementor/editor-documents';
|
|
4
|
+
import { PanelBody, PanelHeader, PanelHeaderTitle } from '@elementor/editor-panels';
|
|
5
|
+
import { ComponentPropListIcon, FolderPlusIcon, XIcon } from '@elementor/icons';
|
|
6
|
+
import { Divider, IconButton, List, Stack, Tooltip } from '@elementor/ui';
|
|
7
|
+
import { generateUniqueId } from '@elementor/utils';
|
|
8
|
+
import { __ } from '@wordpress/i18n';
|
|
9
|
+
|
|
10
|
+
import { addOverridableGroup } from '../../store/actions/add-overridable-group';
|
|
11
|
+
import { deleteOverridableGroup } from '../../store/actions/delete-overridable-group';
|
|
12
|
+
import { deleteOverridableProp } from '../../store/actions/delete-overridable-prop';
|
|
13
|
+
import { reorderGroupProps } from '../../store/actions/reorder-group-props';
|
|
14
|
+
import { reorderOverridableGroups } from '../../store/actions/reorder-overridable-groups';
|
|
15
|
+
import { updateOverridableProp } from '../../store/actions/update-overridable-prop';
|
|
16
|
+
import { useCurrentComponentId } from '../../store/store';
|
|
17
|
+
import { useOverridableProps } from '../component-panel-header/use-overridable-props';
|
|
18
|
+
import { PropertiesEmptyState } from './properties-empty-state';
|
|
19
|
+
import { PropertiesGroup } from './properties-group';
|
|
20
|
+
import { SortableItem, SortableProvider } from './sortable';
|
|
21
|
+
import { useCurrentEditableItem } from './use-current-editable-item';
|
|
22
|
+
import { generateUniqueLabel } from './utils/generate-unique-label';
|
|
23
|
+
|
|
24
|
+
type Props = {
|
|
25
|
+
onClose: () => void;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function ComponentPropertiesPanelContent( { onClose }: Props ) {
|
|
29
|
+
const currentComponentId = useCurrentComponentId();
|
|
30
|
+
const overridableProps = useOverridableProps( currentComponentId );
|
|
31
|
+
const [ isAddingGroup, setIsAddingGroup ] = useState( false );
|
|
32
|
+
const groupLabelEditable = useCurrentEditableItem();
|
|
33
|
+
|
|
34
|
+
const groups = useMemo( () => {
|
|
35
|
+
if ( ! overridableProps ) {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return overridableProps.groups.order
|
|
40
|
+
.map( ( groupId ) => overridableProps.groups.items[ groupId ] ?? null )
|
|
41
|
+
.filter( Boolean );
|
|
42
|
+
}, [ overridableProps ] );
|
|
43
|
+
|
|
44
|
+
const allGroupsForSelect = useMemo(
|
|
45
|
+
() => groups.map( ( group ) => ( { value: group.id, label: group.label } ) ),
|
|
46
|
+
[ groups ]
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
if ( ! currentComponentId || ! overridableProps ) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const hasGroups = groups.length > 0;
|
|
54
|
+
const showEmptyState = ! hasGroups && ! isAddingGroup;
|
|
55
|
+
const groupIds = overridableProps.groups.order;
|
|
56
|
+
|
|
57
|
+
const handleAddGroupClick = () => {
|
|
58
|
+
if ( isAddingGroup ) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const newGroupId = generateUniqueId( 'group' );
|
|
63
|
+
const newLabel = generateUniqueLabel( groups );
|
|
64
|
+
|
|
65
|
+
addOverridableGroup( { componentId: currentComponentId, groupId: newGroupId, label: newLabel } );
|
|
66
|
+
setDocumentModifiedStatus( true );
|
|
67
|
+
setIsAddingGroup( false );
|
|
68
|
+
|
|
69
|
+
groupLabelEditable.setEditingGroupId( newGroupId );
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const handleGroupsReorder = ( newOrder: string[] ) => {
|
|
73
|
+
reorderOverridableGroups( { componentId: currentComponentId, newOrder } );
|
|
74
|
+
setDocumentModifiedStatus( true );
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const handlePropsReorder = ( groupId: string, newPropsOrder: string[] ) => {
|
|
78
|
+
reorderGroupProps( { componentId: currentComponentId, groupId, newPropsOrder } );
|
|
79
|
+
setDocumentModifiedStatus( true );
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const handlePropertyDelete = ( propKey: string ) => {
|
|
83
|
+
deleteOverridableProp( { componentId: currentComponentId, propKey } );
|
|
84
|
+
setDocumentModifiedStatus( true );
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const handlePropertyUpdate = ( propKey: string, data: { label: string; group: string | null } ) => {
|
|
88
|
+
updateOverridableProp( {
|
|
89
|
+
componentId: currentComponentId,
|
|
90
|
+
propKey,
|
|
91
|
+
label: data.label,
|
|
92
|
+
groupId: data.group,
|
|
93
|
+
} );
|
|
94
|
+
setDocumentModifiedStatus( true );
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const handleGroupDelete = ( groupId: string ) => {
|
|
98
|
+
deleteOverridableGroup( { componentId: currentComponentId, groupId } );
|
|
99
|
+
setDocumentModifiedStatus( true );
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<>
|
|
104
|
+
<PanelHeader sx={ { justifyContent: 'start', pl: 1.5, pr: 1, py: 1 } }>
|
|
105
|
+
<Stack direction="row" alignItems="center" gap={ 0.5 } flexGrow={ 1 }>
|
|
106
|
+
<ComponentPropListIcon fontSize="tiny" />
|
|
107
|
+
<PanelHeaderTitle variant="subtitle2">
|
|
108
|
+
{ __( 'Component properties', 'elementor' ) }
|
|
109
|
+
</PanelHeaderTitle>
|
|
110
|
+
</Stack>
|
|
111
|
+
|
|
112
|
+
{ ! showEmptyState && (
|
|
113
|
+
<Tooltip title={ __( 'Add new group', 'elementor' ) }>
|
|
114
|
+
<IconButton
|
|
115
|
+
size="tiny"
|
|
116
|
+
aria-label={ __( 'Add new group', 'elementor' ) }
|
|
117
|
+
onClick={ handleAddGroupClick }
|
|
118
|
+
>
|
|
119
|
+
<FolderPlusIcon fontSize="tiny" />
|
|
120
|
+
</IconButton>
|
|
121
|
+
</Tooltip>
|
|
122
|
+
) }
|
|
123
|
+
|
|
124
|
+
<Tooltip title={ __( 'Close panel', 'elementor' ) }>
|
|
125
|
+
<IconButton size="tiny" aria-label={ __( 'Close panel', 'elementor' ) } onClick={ onClose }>
|
|
126
|
+
<XIcon fontSize="tiny" />
|
|
127
|
+
</IconButton>
|
|
128
|
+
</Tooltip>
|
|
129
|
+
</PanelHeader>
|
|
130
|
+
|
|
131
|
+
<Divider />
|
|
132
|
+
|
|
133
|
+
<PanelBody>
|
|
134
|
+
{ showEmptyState ? (
|
|
135
|
+
<PropertiesEmptyState />
|
|
136
|
+
) : (
|
|
137
|
+
<List sx={ { p: 2, display: 'flex', flexDirection: 'column', gap: 2 } }>
|
|
138
|
+
<SortableProvider value={ groupIds } onChange={ handleGroupsReorder }>
|
|
139
|
+
{ groups.map( ( group ) => (
|
|
140
|
+
<SortableItem key={ group.id } id={ group.id }>
|
|
141
|
+
{ ( { triggerProps, triggerStyle, isDragPlaceholder } ) => (
|
|
142
|
+
<PropertiesGroup
|
|
143
|
+
group={ group }
|
|
144
|
+
props={ overridableProps.props }
|
|
145
|
+
allGroups={ allGroupsForSelect }
|
|
146
|
+
allGroupsRecord={ overridableProps.groups.items }
|
|
147
|
+
sortableTriggerProps={ { ...triggerProps, style: triggerStyle } }
|
|
148
|
+
isDragPlaceholder={ isDragPlaceholder }
|
|
149
|
+
setIsAddingGroup={ setIsAddingGroup }
|
|
150
|
+
onPropsReorder={ ( newOrder ) => handlePropsReorder( group.id, newOrder ) }
|
|
151
|
+
onPropertyDelete={ handlePropertyDelete }
|
|
152
|
+
onPropertyUpdate={ handlePropertyUpdate }
|
|
153
|
+
editableLabelProps={ groupLabelEditable }
|
|
154
|
+
onGroupDelete={ handleGroupDelete }
|
|
155
|
+
/>
|
|
156
|
+
) }
|
|
157
|
+
</SortableItem>
|
|
158
|
+
) ) }
|
|
159
|
+
</SortableProvider>
|
|
160
|
+
</List>
|
|
161
|
+
) }
|
|
162
|
+
</PanelBody>
|
|
163
|
+
</>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ElementProvider, usePanelActions as useEditingPanelActions } from '@elementor/editor-editing-panel';
|
|
3
|
+
import { useSelectedElement } from '@elementor/editor-elements';
|
|
4
|
+
import { __createPanel as createPanel, Panel } from '@elementor/editor-panels';
|
|
5
|
+
import { ThemeProvider } from '@elementor/editor-ui';
|
|
6
|
+
import { Alert, Box, ErrorBoundary } from '@elementor/ui';
|
|
7
|
+
import { __ } from '@wordpress/i18n';
|
|
8
|
+
|
|
9
|
+
import { ComponentPropertiesPanelContent } from './component-properties-panel-content';
|
|
10
|
+
|
|
11
|
+
const id = 'component-properties-panel';
|
|
12
|
+
|
|
13
|
+
export const { panel, usePanelActions } = createPanel( {
|
|
14
|
+
id,
|
|
15
|
+
component: ComponentPropertiesPanel,
|
|
16
|
+
} );
|
|
17
|
+
|
|
18
|
+
function ComponentPropertiesPanel() {
|
|
19
|
+
const { element, elementType } = useSelectedElement();
|
|
20
|
+
const { close: closePanel } = usePanelActions();
|
|
21
|
+
const { open: openEditingPanel } = useEditingPanelActions();
|
|
22
|
+
|
|
23
|
+
if ( ! element || ! elementType ) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<ThemeProvider>
|
|
29
|
+
<ErrorBoundary fallback={ <ErrorBoundaryFallback /> }>
|
|
30
|
+
<ElementProvider element={ element } elementType={ elementType }>
|
|
31
|
+
<Panel>
|
|
32
|
+
<ComponentPropertiesPanelContent
|
|
33
|
+
onClose={ () => {
|
|
34
|
+
closePanel();
|
|
35
|
+
openEditingPanel();
|
|
36
|
+
} }
|
|
37
|
+
/>
|
|
38
|
+
</Panel>
|
|
39
|
+
</ElementProvider>
|
|
40
|
+
</ErrorBoundary>
|
|
41
|
+
</ThemeProvider>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const ErrorBoundaryFallback = () => (
|
|
46
|
+
<Box role="alert" sx={ { minHeight: '100%', p: 2 } }>
|
|
47
|
+
<Alert severity="error" sx={ { mb: 2, maxWidth: 400, textAlign: 'center' } }>
|
|
48
|
+
<strong>{ __( 'Something went wrong', 'elementor' ) }</strong>
|
|
49
|
+
</Alert>
|
|
50
|
+
</Box>
|
|
51
|
+
);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ComponentPropListIcon } from '@elementor/icons';
|
|
3
|
+
import { Link, Stack, Typography } from '@elementor/ui';
|
|
4
|
+
import { __ } from '@wordpress/i18n';
|
|
5
|
+
|
|
6
|
+
const LEARN_MORE_URL = 'https://go.elementor.com/tbd/';
|
|
7
|
+
|
|
8
|
+
export function PropertiesEmptyState() {
|
|
9
|
+
return (
|
|
10
|
+
<Stack
|
|
11
|
+
alignItems="center"
|
|
12
|
+
justifyContent="flex-start"
|
|
13
|
+
height="100%"
|
|
14
|
+
color="text.secondary"
|
|
15
|
+
sx={ { px: 2.5, pt: 10, pb: 5.5 } }
|
|
16
|
+
gap={ 1 }
|
|
17
|
+
>
|
|
18
|
+
<ComponentPropListIcon fontSize="large" />
|
|
19
|
+
|
|
20
|
+
<Typography align="center" variant="subtitle2">
|
|
21
|
+
{ __( 'Add your first property', 'elementor' ) }
|
|
22
|
+
</Typography>
|
|
23
|
+
|
|
24
|
+
<Typography align="center" variant="caption">
|
|
25
|
+
{ __( 'Make instances flexible while keeping design synced.', 'elementor' ) }
|
|
26
|
+
</Typography>
|
|
27
|
+
|
|
28
|
+
<Typography align="center" variant="caption">
|
|
29
|
+
{ __( 'Select any element, then click + next to a setting to expose it.', 'elementor' ) }
|
|
30
|
+
</Typography>
|
|
31
|
+
|
|
32
|
+
<Link
|
|
33
|
+
variant="caption"
|
|
34
|
+
color="secondary"
|
|
35
|
+
href={ LEARN_MORE_URL }
|
|
36
|
+
target="_blank"
|
|
37
|
+
rel="noopener noreferrer"
|
|
38
|
+
sx={ { textDecorationLine: 'underline' } }
|
|
39
|
+
>
|
|
40
|
+
{ __( 'Learn more', 'elementor' ) }
|
|
41
|
+
</Link>
|
|
42
|
+
</Stack>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { EditableField, MenuListItem } from '@elementor/editor-ui';
|
|
3
|
+
import { DotsVerticalIcon } from '@elementor/icons';
|
|
4
|
+
import {
|
|
5
|
+
bindMenu,
|
|
6
|
+
bindTrigger,
|
|
7
|
+
Box,
|
|
8
|
+
IconButton,
|
|
9
|
+
List,
|
|
10
|
+
Menu,
|
|
11
|
+
Stack,
|
|
12
|
+
Tooltip,
|
|
13
|
+
Typography,
|
|
14
|
+
usePopupState,
|
|
15
|
+
} from '@elementor/ui';
|
|
16
|
+
import { __ } from '@wordpress/i18n';
|
|
17
|
+
|
|
18
|
+
import { type OverridableProp, type OverridablePropsGroup } from '../../types';
|
|
19
|
+
import { PropertyItem } from './property-item';
|
|
20
|
+
import { SortableItem, SortableProvider, SortableTrigger, type SortableTriggerProps } from './sortable';
|
|
21
|
+
import { type GroupLabelEditableState } from './use-current-editable-item';
|
|
22
|
+
|
|
23
|
+
type Props = {
|
|
24
|
+
group: OverridablePropsGroup;
|
|
25
|
+
props: Record< string, OverridableProp >;
|
|
26
|
+
allGroups: { value: string; label: string }[];
|
|
27
|
+
allGroupsRecord: Record< string, OverridablePropsGroup >;
|
|
28
|
+
sortableTriggerProps: SortableTriggerProps;
|
|
29
|
+
isDragPlaceholder?: boolean;
|
|
30
|
+
setIsAddingGroup: ( isAddingGroup: boolean ) => void;
|
|
31
|
+
onPropsReorder: ( newOrder: string[] ) => void;
|
|
32
|
+
onPropertyDelete: ( propKey: string ) => void;
|
|
33
|
+
onPropertyUpdate: ( propKey: string, data: { label: string; group: string | null } ) => void;
|
|
34
|
+
onGroupDelete: ( groupId: string ) => void;
|
|
35
|
+
editableLabelProps: GroupLabelEditableState;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export function PropertiesGroup( {
|
|
39
|
+
group,
|
|
40
|
+
props,
|
|
41
|
+
allGroups,
|
|
42
|
+
sortableTriggerProps,
|
|
43
|
+
isDragPlaceholder,
|
|
44
|
+
onPropsReorder,
|
|
45
|
+
onPropertyDelete,
|
|
46
|
+
onPropertyUpdate,
|
|
47
|
+
onGroupDelete,
|
|
48
|
+
editableLabelProps,
|
|
49
|
+
}: Props ) {
|
|
50
|
+
const groupProps = group.props
|
|
51
|
+
.map( ( propId ) => props[ propId ] )
|
|
52
|
+
.filter( ( prop ): prop is OverridableProp => Boolean( prop ) );
|
|
53
|
+
|
|
54
|
+
const popupState = usePopupState( {
|
|
55
|
+
variant: 'popover',
|
|
56
|
+
disableAutoFocus: true,
|
|
57
|
+
} );
|
|
58
|
+
|
|
59
|
+
const { editableRef, isEditing, error, getEditableProps, setEditingGroupId, editingGroupId } = editableLabelProps;
|
|
60
|
+
|
|
61
|
+
const hasProperties = group.props.length > 0;
|
|
62
|
+
const isThisGroupEditing = isEditing && editingGroupId === group.id;
|
|
63
|
+
|
|
64
|
+
const handleRenameClick = () => {
|
|
65
|
+
popupState.close();
|
|
66
|
+
setEditingGroupId( group.id );
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const handleDeleteClick = () => {
|
|
70
|
+
popupState.close();
|
|
71
|
+
onGroupDelete( group.id );
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<Box
|
|
76
|
+
sx={ {
|
|
77
|
+
opacity: isDragPlaceholder ? 0.5 : 1,
|
|
78
|
+
} }
|
|
79
|
+
>
|
|
80
|
+
<Stack gap={ 1 }>
|
|
81
|
+
<Box
|
|
82
|
+
className="group-header"
|
|
83
|
+
sx={ {
|
|
84
|
+
position: 'relative',
|
|
85
|
+
'&:hover .group-sortable-trigger': {
|
|
86
|
+
visibility: 'visible',
|
|
87
|
+
},
|
|
88
|
+
'& .group-sortable-trigger': {
|
|
89
|
+
visibility: 'hidden',
|
|
90
|
+
},
|
|
91
|
+
'&:hover .group-menu': {
|
|
92
|
+
visibility: 'visible',
|
|
93
|
+
},
|
|
94
|
+
'& .group-menu': {
|
|
95
|
+
visibility: 'hidden',
|
|
96
|
+
},
|
|
97
|
+
} }
|
|
98
|
+
>
|
|
99
|
+
<SortableTrigger triggerClassName="group-sortable-trigger" { ...sortableTriggerProps } />
|
|
100
|
+
<Stack direction="row" alignItems="center" justifyContent="space-between" gap={ 2 }>
|
|
101
|
+
{ isThisGroupEditing ? (
|
|
102
|
+
<Box
|
|
103
|
+
sx={ {
|
|
104
|
+
flex: 1,
|
|
105
|
+
height: 28,
|
|
106
|
+
display: 'flex',
|
|
107
|
+
alignItems: 'center',
|
|
108
|
+
border: 2,
|
|
109
|
+
borderColor: 'text.secondary',
|
|
110
|
+
borderRadius: 1,
|
|
111
|
+
pl: 0.5,
|
|
112
|
+
} }
|
|
113
|
+
>
|
|
114
|
+
<EditableField
|
|
115
|
+
ref={ editableRef }
|
|
116
|
+
as={ Typography }
|
|
117
|
+
variant="caption"
|
|
118
|
+
error={ error ?? undefined }
|
|
119
|
+
sx={ { color: 'text.primary', fontWeight: 400, lineHeight: 1.66 } }
|
|
120
|
+
{ ...getEditableProps() }
|
|
121
|
+
/>
|
|
122
|
+
</Box>
|
|
123
|
+
) : (
|
|
124
|
+
<Typography
|
|
125
|
+
variant="caption"
|
|
126
|
+
sx={ { color: 'text.primary', fontWeight: 400, lineHeight: 1.66 } }
|
|
127
|
+
>
|
|
128
|
+
{ group.label }
|
|
129
|
+
</Typography>
|
|
130
|
+
) }
|
|
131
|
+
<IconButton
|
|
132
|
+
className="group-menu"
|
|
133
|
+
size="tiny"
|
|
134
|
+
sx={ { p: 0.25, visibility: isThisGroupEditing ? 'visible' : undefined } }
|
|
135
|
+
aria-label={ __( 'Group actions', 'elementor' ) }
|
|
136
|
+
{ ...bindTrigger( popupState ) }
|
|
137
|
+
>
|
|
138
|
+
<DotsVerticalIcon fontSize="tiny" />
|
|
139
|
+
</IconButton>
|
|
140
|
+
</Stack>
|
|
141
|
+
</Box>
|
|
142
|
+
<List sx={ { p: 0, display: 'flex', flexDirection: 'column', gap: 1 } }>
|
|
143
|
+
<SortableProvider value={ group.props } onChange={ onPropsReorder }>
|
|
144
|
+
{ groupProps.map( ( prop ) => (
|
|
145
|
+
<SortableItem key={ prop.overrideKey } id={ prop.overrideKey }>
|
|
146
|
+
{ ( { triggerProps, triggerStyle, isDragPlaceholder: isItemDragPlaceholder } ) => (
|
|
147
|
+
<PropertyItem
|
|
148
|
+
prop={ prop }
|
|
149
|
+
sortableTriggerProps={ { ...triggerProps, style: triggerStyle } }
|
|
150
|
+
isDragPlaceholder={ isItemDragPlaceholder }
|
|
151
|
+
groups={ allGroups }
|
|
152
|
+
onDelete={ onPropertyDelete }
|
|
153
|
+
onUpdate={ ( data ) => onPropertyUpdate( prop.overrideKey, data ) }
|
|
154
|
+
/>
|
|
155
|
+
) }
|
|
156
|
+
</SortableItem>
|
|
157
|
+
) ) }
|
|
158
|
+
</SortableProvider>
|
|
159
|
+
</List>
|
|
160
|
+
</Stack>
|
|
161
|
+
<Menu
|
|
162
|
+
{ ...bindMenu( popupState ) }
|
|
163
|
+
anchorOrigin={ { vertical: 'bottom', horizontal: 'right' } }
|
|
164
|
+
transformOrigin={ { vertical: 'top', horizontal: 'right' } }
|
|
165
|
+
>
|
|
166
|
+
<MenuListItem sx={ { minWidth: '160px' } } onClick={ handleRenameClick }>
|
|
167
|
+
<Typography variant="caption" sx={ { color: 'text.primary' } }>
|
|
168
|
+
{ __( 'Rename', 'elementor' ) }
|
|
169
|
+
</Typography>
|
|
170
|
+
</MenuListItem>
|
|
171
|
+
<Tooltip
|
|
172
|
+
title={
|
|
173
|
+
hasProperties ? __( 'To delete the group, first remove all the properties', 'elementor' ) : ''
|
|
174
|
+
}
|
|
175
|
+
placement="right"
|
|
176
|
+
>
|
|
177
|
+
<span>
|
|
178
|
+
<MenuListItem onClick={ handleDeleteClick } disabled={ hasProperties }>
|
|
179
|
+
<Typography
|
|
180
|
+
variant="caption"
|
|
181
|
+
sx={ { color: hasProperties ? 'text.disabled' : 'error.light' } }
|
|
182
|
+
>
|
|
183
|
+
{ __( 'Delete', 'elementor' ) }
|
|
184
|
+
</Typography>
|
|
185
|
+
</MenuListItem>
|
|
186
|
+
</span>
|
|
187
|
+
</Tooltip>
|
|
188
|
+
</Menu>
|
|
189
|
+
</Box>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { getWidgetsCache } from '@elementor/editor-elements';
|
|
3
|
+
import { XIcon } from '@elementor/icons';
|
|
4
|
+
import { bindPopover, bindTrigger, Box, IconButton, Popover, Typography, usePopupState } from '@elementor/ui';
|
|
5
|
+
|
|
6
|
+
import { type OverridableProp } from '../../types';
|
|
7
|
+
import { OverridablePropForm } from '../overridable-props/overridable-prop-form';
|
|
8
|
+
import { SortableTrigger, type SortableTriggerProps } from './sortable';
|
|
9
|
+
|
|
10
|
+
type PropertyItemProps = {
|
|
11
|
+
prop: OverridableProp;
|
|
12
|
+
sortableTriggerProps: SortableTriggerProps;
|
|
13
|
+
isDragPlaceholder?: boolean;
|
|
14
|
+
groups: { value: string; label: string }[];
|
|
15
|
+
onDelete: ( propKey: string ) => void;
|
|
16
|
+
onUpdate: ( data: { label: string; group: string | null } ) => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function PropertyItem( {
|
|
20
|
+
prop,
|
|
21
|
+
sortableTriggerProps,
|
|
22
|
+
isDragPlaceholder,
|
|
23
|
+
groups,
|
|
24
|
+
onDelete,
|
|
25
|
+
onUpdate,
|
|
26
|
+
}: PropertyItemProps ) {
|
|
27
|
+
const popoverState = usePopupState( {
|
|
28
|
+
variant: 'popover',
|
|
29
|
+
} );
|
|
30
|
+
const icon = getElementIcon( prop );
|
|
31
|
+
const popoverProps = bindPopover( popoverState );
|
|
32
|
+
|
|
33
|
+
const handleSubmit = ( data: { label: string; group: string | null } ) => {
|
|
34
|
+
onUpdate( data );
|
|
35
|
+
popoverState.close();
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const handleDelete = ( event: React.MouseEvent ) => {
|
|
39
|
+
event.stopPropagation();
|
|
40
|
+
onDelete( prop.overrideKey );
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<>
|
|
45
|
+
<Box
|
|
46
|
+
{ ...bindTrigger( popoverState ) }
|
|
47
|
+
sx={ {
|
|
48
|
+
position: 'relative',
|
|
49
|
+
pl: 0.5,
|
|
50
|
+
pr: 1,
|
|
51
|
+
py: 0.25,
|
|
52
|
+
minHeight: 28,
|
|
53
|
+
borderRadius: 1,
|
|
54
|
+
border: '1px solid',
|
|
55
|
+
borderColor: 'divider',
|
|
56
|
+
display: 'flex',
|
|
57
|
+
alignItems: 'center',
|
|
58
|
+
gap: 0.5,
|
|
59
|
+
opacity: isDragPlaceholder ? 0.5 : 1,
|
|
60
|
+
cursor: 'pointer',
|
|
61
|
+
'&:hover': {
|
|
62
|
+
backgroundColor: 'action.hover',
|
|
63
|
+
},
|
|
64
|
+
'&:hover .sortable-trigger': {
|
|
65
|
+
visibility: 'visible',
|
|
66
|
+
},
|
|
67
|
+
'& .sortable-trigger': {
|
|
68
|
+
visibility: 'hidden',
|
|
69
|
+
},
|
|
70
|
+
'&:hover .delete-button': {
|
|
71
|
+
visibility: 'visible',
|
|
72
|
+
},
|
|
73
|
+
'& .delete-button': {
|
|
74
|
+
visibility: 'hidden',
|
|
75
|
+
},
|
|
76
|
+
} }
|
|
77
|
+
>
|
|
78
|
+
<SortableTrigger { ...sortableTriggerProps } />
|
|
79
|
+
<Box
|
|
80
|
+
sx={ { display: 'flex', alignItems: 'center', color: 'text.primary', fontSize: 12, padding: 0.25 } }
|
|
81
|
+
>
|
|
82
|
+
<i className={ icon } />
|
|
83
|
+
</Box>
|
|
84
|
+
<Typography variant="caption" sx={ { color: 'text.primary', flexGrow: 1, fontSize: 10 } }>
|
|
85
|
+
{ prop.label }
|
|
86
|
+
</Typography>
|
|
87
|
+
<IconButton size="tiny" onClick={ handleDelete } aria-label="Delete property" sx={ { p: 0.25 } }>
|
|
88
|
+
<XIcon fontSize="tiny" />
|
|
89
|
+
</IconButton>
|
|
90
|
+
</Box>
|
|
91
|
+
|
|
92
|
+
<Popover
|
|
93
|
+
{ ...popoverProps }
|
|
94
|
+
anchorOrigin={ { vertical: 'bottom', horizontal: 'left' } }
|
|
95
|
+
transformOrigin={ { vertical: 'top', horizontal: 'left' } }
|
|
96
|
+
PaperProps={ { sx: { width: popoverState.anchorEl?.getBoundingClientRect().width } } }
|
|
97
|
+
>
|
|
98
|
+
<OverridablePropForm
|
|
99
|
+
onSubmit={ handleSubmit }
|
|
100
|
+
currentValue={ prop }
|
|
101
|
+
groups={ groups }
|
|
102
|
+
sx={ { width: '100%' } }
|
|
103
|
+
/>
|
|
104
|
+
</Popover>
|
|
105
|
+
</>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function getElementIcon( prop: OverridableProp ): string {
|
|
110
|
+
const elType = prop.elType === 'widget' ? prop.widgetType : prop.elType;
|
|
111
|
+
|
|
112
|
+
const widgetsCache = getWidgetsCache();
|
|
113
|
+
|
|
114
|
+
if ( ! widgetsCache ) {
|
|
115
|
+
return 'eicon-apps';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const widgetConfig = widgetsCache[ elType ];
|
|
119
|
+
|
|
120
|
+
return widgetConfig?.icon || 'eicon-apps';
|
|
121
|
+
}
|