@elementor/editor-components 4.0.0-662 → 4.0.0-664
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.d.mts +9 -4
- package/dist/index.d.ts +9 -4
- package/dist/index.js +1267 -843
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1026 -603
- package/dist/index.mjs.map +1 -1
- package/package.json +23 -23
- package/src/components/detach-instance-confirmation-dialog.tsx +50 -0
- package/src/components/instance-editing-panel/detach-action.tsx +76 -0
- package/src/components/instance-editing-panel/instance-editing-panel.tsx +12 -5
- package/src/components/instance-editing-panel/use-instance-panel-data.ts +2 -2
- package/src/consts.ts +1 -0
- package/src/create-component-type.ts +87 -23
- package/src/index.ts +1 -0
- package/src/init.ts +6 -1
- package/src/types.ts +1 -1
- package/src/utils/detach-component-instance/detach-component-instance.ts +172 -0
- package/src/utils/detach-component-instance/index.ts +1 -0
- package/src/utils/detach-component-instance/regenerate-local-style-ids.ts +53 -0
- package/src/utils/detach-component-instance/resolve-detached-instance.ts +94 -0
- package/src/utils/detach-component-instance/resolve-overridable-settings.ts +121 -0
- package/src/utils/is-component-instance.ts +1 -1
- package/src/utils/tracking.ts +2 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-components",
|
|
3
3
|
"description": "Elementor editor components",
|
|
4
|
-
"version": "4.0.0-
|
|
4
|
+
"version": "4.0.0-664",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Elementor Team",
|
|
7
7
|
"homepage": "https://elementor.com/",
|
|
@@ -40,31 +40,31 @@
|
|
|
40
40
|
"dev": "tsup --config=../../tsup.dev.ts"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@elementor/editor": "4.0.0-
|
|
44
|
-
"@elementor/editor-canvas": "4.0.0-
|
|
45
|
-
"@elementor/editor-controls": "4.0.0-
|
|
46
|
-
"@elementor/editor-documents": "4.0.0-
|
|
47
|
-
"@elementor/editor-editing-panel": "4.0.0-
|
|
48
|
-
"@elementor/editor-elements": "4.0.0-
|
|
49
|
-
"@elementor/editor-elements-panel": "4.0.0-
|
|
50
|
-
"@elementor/editor-mcp": "4.0.0-
|
|
51
|
-
"@elementor/editor-templates": "4.0.0-
|
|
52
|
-
"@elementor/editor-panels": "4.0.0-
|
|
53
|
-
"@elementor/editor-props": "4.0.0-
|
|
54
|
-
"@elementor/editor-styles-repository": "4.0.0-
|
|
55
|
-
"@elementor/editor-ui": "4.0.0-
|
|
56
|
-
"@elementor/editor-v1-adapters": "4.0.0-
|
|
57
|
-
"@elementor/http-client": "4.0.0-
|
|
43
|
+
"@elementor/editor": "4.0.0-664",
|
|
44
|
+
"@elementor/editor-canvas": "4.0.0-664",
|
|
45
|
+
"@elementor/editor-controls": "4.0.0-664",
|
|
46
|
+
"@elementor/editor-documents": "4.0.0-664",
|
|
47
|
+
"@elementor/editor-editing-panel": "4.0.0-664",
|
|
48
|
+
"@elementor/editor-elements": "4.0.0-664",
|
|
49
|
+
"@elementor/editor-elements-panel": "4.0.0-664",
|
|
50
|
+
"@elementor/editor-mcp": "4.0.0-664",
|
|
51
|
+
"@elementor/editor-templates": "4.0.0-664",
|
|
52
|
+
"@elementor/editor-panels": "4.0.0-664",
|
|
53
|
+
"@elementor/editor-props": "4.0.0-664",
|
|
54
|
+
"@elementor/editor-styles-repository": "4.0.0-664",
|
|
55
|
+
"@elementor/editor-ui": "4.0.0-664",
|
|
56
|
+
"@elementor/editor-v1-adapters": "4.0.0-664",
|
|
57
|
+
"@elementor/http-client": "4.0.0-664",
|
|
58
58
|
"@elementor/icons": "^1.68.0",
|
|
59
|
-
"@elementor/events": "4.0.0-
|
|
60
|
-
"@elementor/query": "4.0.0-
|
|
61
|
-
"@elementor/schema": "4.0.0-
|
|
62
|
-
"@elementor/store": "4.0.0-
|
|
59
|
+
"@elementor/events": "4.0.0-664",
|
|
60
|
+
"@elementor/query": "4.0.0-664",
|
|
61
|
+
"@elementor/schema": "4.0.0-664",
|
|
62
|
+
"@elementor/store": "4.0.0-664",
|
|
63
63
|
"@elementor/ui": "1.36.17",
|
|
64
|
-
"@elementor/utils": "4.0.0-
|
|
64
|
+
"@elementor/utils": "4.0.0-664",
|
|
65
65
|
"@wordpress/i18n": "^5.13.0",
|
|
66
|
-
"@elementor/editor-notifications": "4.0.0-
|
|
67
|
-
"@elementor/editor-current-user": "4.0.0-
|
|
66
|
+
"@elementor/editor-notifications": "4.0.0-664",
|
|
67
|
+
"@elementor/editor-current-user": "4.0.0-664"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
70
|
"react": "^18.3.1",
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { closeDialog, ConfirmationDialog, openDialog } from '@elementor/editor-ui';
|
|
3
|
+
import { AlertTriangleFilledIcon } from '@elementor/icons';
|
|
4
|
+
import { __ } from '@wordpress/i18n';
|
|
5
|
+
|
|
6
|
+
type DetachInstanceConfirmationDialogProps = {
|
|
7
|
+
open: boolean;
|
|
8
|
+
onClose: () => void;
|
|
9
|
+
onConfirm: () => void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function DetachInstanceConfirmationDialog( {
|
|
13
|
+
open,
|
|
14
|
+
onClose,
|
|
15
|
+
onConfirm,
|
|
16
|
+
}: DetachInstanceConfirmationDialogProps ) {
|
|
17
|
+
return (
|
|
18
|
+
<ConfirmationDialog open={ open } onClose={ onClose }>
|
|
19
|
+
<ConfirmationDialog.Title icon={ AlertTriangleFilledIcon } iconColor="secondary">
|
|
20
|
+
{ __( 'Detach from Component?', 'elementor' ) }
|
|
21
|
+
</ConfirmationDialog.Title>
|
|
22
|
+
<ConfirmationDialog.Content>
|
|
23
|
+
<ConfirmationDialog.ContentText>
|
|
24
|
+
{ __(
|
|
25
|
+
'Detaching this instance will break its link to the Component. Changes to the Component will no longer apply. Continue?',
|
|
26
|
+
'elementor'
|
|
27
|
+
) }
|
|
28
|
+
</ConfirmationDialog.ContentText>
|
|
29
|
+
</ConfirmationDialog.Content>
|
|
30
|
+
<ConfirmationDialog.Actions
|
|
31
|
+
onClose={ onClose }
|
|
32
|
+
onConfirm={ onConfirm }
|
|
33
|
+
confirmLabel={ __( 'Detach', 'elementor' ) }
|
|
34
|
+
color="primary"
|
|
35
|
+
/>
|
|
36
|
+
</ConfirmationDialog>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Used imperatively from the context menu (Marionette view).
|
|
41
|
+
export function openDetachConfirmDialog( onConfirm: () => void ) {
|
|
42
|
+
const handleConfirm = () => {
|
|
43
|
+
closeDialog();
|
|
44
|
+
onConfirm();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
openDialog( {
|
|
48
|
+
component: <DetachInstanceConfirmationDialog open onClose={ closeDialog } onConfirm={ handleConfirm } />,
|
|
49
|
+
} );
|
|
50
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { notify } from '@elementor/editor-notifications';
|
|
4
|
+
import { DetachIcon } from '@elementor/icons';
|
|
5
|
+
import { __ } from '@wordpress/i18n';
|
|
6
|
+
|
|
7
|
+
import { type ExtendedWindow } from '../../types';
|
|
8
|
+
import { detachComponentInstance } from '../../utils/detach-component-instance';
|
|
9
|
+
import { DetachInstanceConfirmationDialog } from '../detach-instance-confirmation-dialog';
|
|
10
|
+
import { EditComponentAction } from './instance-panel-header';
|
|
11
|
+
|
|
12
|
+
export const DetachAction = ( {
|
|
13
|
+
componentInstanceId,
|
|
14
|
+
componentId,
|
|
15
|
+
}: {
|
|
16
|
+
componentInstanceId: string;
|
|
17
|
+
componentId: number;
|
|
18
|
+
} ) => {
|
|
19
|
+
const [ isDetachDialogOpen, setIsDetachDialogOpen ] = useState( false );
|
|
20
|
+
|
|
21
|
+
const handleDetachConfirm = async () => {
|
|
22
|
+
setIsDetachDialogOpen( false );
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
await detachComponentInstance( {
|
|
26
|
+
instanceId: componentInstanceId,
|
|
27
|
+
componentId,
|
|
28
|
+
trackingInfo: getDetachTrackingInfo(),
|
|
29
|
+
} );
|
|
30
|
+
} catch {
|
|
31
|
+
notify( {
|
|
32
|
+
type: 'error',
|
|
33
|
+
message: __( 'Failed to detach component instance.', 'elementor' ),
|
|
34
|
+
id: 'detach-component-instance-failed',
|
|
35
|
+
} );
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const handleDetachCancel = () => {
|
|
40
|
+
setIsDetachDialogOpen( false );
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const handleDetachClick = () => {
|
|
44
|
+
setIsDetachDialogOpen( true );
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const detachLabel = __( 'Detach from Component', 'elementor' );
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<>
|
|
51
|
+
<EditComponentAction label={ detachLabel } icon={ DetachIcon } onClick={ handleDetachClick } />
|
|
52
|
+
<DetachInstanceConfirmationDialog
|
|
53
|
+
open={ isDetachDialogOpen }
|
|
54
|
+
onClose={ handleDetachCancel }
|
|
55
|
+
onConfirm={ handleDetachConfirm }
|
|
56
|
+
/>
|
|
57
|
+
</>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
function getDetachTrackingInfo() {
|
|
62
|
+
const extendedWindow = window as unknown as ExtendedWindow;
|
|
63
|
+
const config = extendedWindow?.elementorCommon?.eventsManager?.config;
|
|
64
|
+
|
|
65
|
+
if ( ! config ) {
|
|
66
|
+
return {
|
|
67
|
+
location: '',
|
|
68
|
+
trigger: '',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
location: ( config.locations.components as Record< string, string > ).instanceEditingPanel,
|
|
74
|
+
trigger: config.triggers.click,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { PencilIcon } from '@elementor/icons';
|
|
3
|
-
import { Box } from '@elementor/ui';
|
|
3
|
+
import { Box, Stack } from '@elementor/ui';
|
|
4
4
|
import { __ } from '@wordpress/i18n';
|
|
5
5
|
|
|
6
|
+
import { useComponentsPermissions } from '../../hooks/use-components-permissions';
|
|
6
7
|
import { ComponentInstanceProvider } from '../../provider/component-instance-context';
|
|
8
|
+
import { DetachAction } from './detach-action';
|
|
7
9
|
import { EmptyState } from './empty-state';
|
|
8
10
|
import { InstancePanelBody } from './instance-panel-body';
|
|
9
11
|
import { EditComponentAction, InstancePanelHeader } from './instance-panel-header';
|
|
10
12
|
import { useInstancePanelData } from './use-instance-panel-data';
|
|
11
13
|
|
|
12
14
|
export function InstanceEditingPanel() {
|
|
15
|
+
const { canEdit } = useComponentsPermissions();
|
|
13
16
|
const data = useInstancePanelData();
|
|
14
17
|
|
|
15
18
|
if ( ! data ) {
|
|
@@ -21,6 +24,13 @@ export function InstanceEditingPanel() {
|
|
|
21
24
|
/* translators: %s: component name. */
|
|
22
25
|
const panelTitle = __( 'Edit %s', 'elementor' ).replace( '%s', component.name );
|
|
23
26
|
|
|
27
|
+
const actions = (
|
|
28
|
+
<Stack direction="row" gap={ 0.5 }>
|
|
29
|
+
<DetachAction componentInstanceId={ componentInstanceId } componentId={ componentId } />
|
|
30
|
+
{ canEdit && <EditComponentAction disabled label={ panelTitle } icon={ PencilIcon } /> }
|
|
31
|
+
</Stack>
|
|
32
|
+
);
|
|
33
|
+
|
|
24
34
|
return (
|
|
25
35
|
<Box data-testid="instance-editing-panel">
|
|
26
36
|
<ComponentInstanceProvider
|
|
@@ -28,10 +38,7 @@ export function InstanceEditingPanel() {
|
|
|
28
38
|
overrides={ overrides }
|
|
29
39
|
overridableProps={ overridableProps }
|
|
30
40
|
>
|
|
31
|
-
<InstancePanelHeader
|
|
32
|
-
componentName={ component.name }
|
|
33
|
-
actions={ <EditComponentAction disabled label={ panelTitle } icon={ PencilIcon } /> }
|
|
34
|
-
/>
|
|
41
|
+
<InstancePanelHeader componentName={ component.name } actions={ actions } />
|
|
35
42
|
<InstancePanelBody
|
|
36
43
|
groups={ groups }
|
|
37
44
|
isEmpty={ isEmpty }
|
|
@@ -13,7 +13,7 @@ type InstancePanelData = {
|
|
|
13
13
|
overridableProps: NonNullable< ReturnType< typeof useSanitizeOverridableProps > >;
|
|
14
14
|
groups: OverridablePropsGroup[];
|
|
15
15
|
isEmpty: boolean;
|
|
16
|
-
componentInstanceId: string
|
|
16
|
+
componentInstanceId: string;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
export function useInstancePanelData(): InstancePanelData | null {
|
|
@@ -29,7 +29,7 @@ export function useInstancePanelData(): InstancePanelData | null {
|
|
|
29
29
|
|
|
30
30
|
const overridableProps = useSanitizeOverridableProps( componentId ?? null, componentInstanceId );
|
|
31
31
|
|
|
32
|
-
if ( ! componentId || ! overridableProps || ! component ) {
|
|
32
|
+
if ( ! componentId || ! overridableProps || ! component || ! componentInstanceId ) {
|
|
33
33
|
return null;
|
|
34
34
|
}
|
|
35
35
|
|
package/src/consts.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const COMPONENT_WIDGET_TYPE = 'e-component';
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
type BackboneModel,
|
|
3
3
|
type BackboneModelConstructor,
|
|
4
4
|
type ContextMenuAction,
|
|
5
|
+
type ContextMenuEventData,
|
|
5
6
|
type CreateTemplatedElementTypeOptions,
|
|
6
7
|
createTemplatedElementView,
|
|
7
8
|
type ElementModel,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
} from '@elementor/editor-canvas';
|
|
14
15
|
import { getCurrentDocument } from '@elementor/editor-documents';
|
|
15
16
|
import { type V1ElementData } from '@elementor/editor-elements';
|
|
17
|
+
import { notify } from '@elementor/editor-notifications';
|
|
16
18
|
import { __getState as getState } from '@elementor/store';
|
|
17
19
|
import { hasProInstalled } from '@elementor/utils';
|
|
18
20
|
import { __ } from '@wordpress/i18n';
|
|
@@ -21,15 +23,14 @@ import { apiClient } from './api';
|
|
|
21
23
|
import { type ComponentInstanceProp } from './prop-types/component-instance-prop-type';
|
|
22
24
|
import { type ComponentsSlice, selectComponentByUid } from './store/store';
|
|
23
25
|
import { type ComponentRenderContext, type ExtendedWindow } from './types';
|
|
26
|
+
import { detachComponentInstance } from './utils/detach-component-instance';
|
|
24
27
|
import { formatComponentElementsId } from './utils/format-component-elements-id';
|
|
25
28
|
import { switchToComponent } from './utils/switch-to-component';
|
|
26
29
|
import { trackComponentEvent } from './utils/tracking';
|
|
27
30
|
|
|
28
|
-
type ContextMenuEventData = { location: string; secondaryLocation: string; trigger: string };
|
|
29
|
-
|
|
30
31
|
type ContextMenuGroupConfig = {
|
|
31
32
|
disable: Record< string, string[] >;
|
|
32
|
-
add: Record< string, { index: number;
|
|
33
|
+
add: Record< string, { index: number; actions: ContextMenuAction[] } >;
|
|
33
34
|
};
|
|
34
35
|
|
|
35
36
|
type ContextMenuGroup = {
|
|
@@ -54,6 +55,26 @@ export const COMPONENT_WIDGET_TYPE = 'e-component';
|
|
|
54
55
|
|
|
55
56
|
const EDIT_COMPONENT_UPGRADE_URL = 'https://go.elementor.com/go-pro-components-edit/';
|
|
56
57
|
|
|
58
|
+
const COMPONENT_EDIT_UPGRADE_NOTIFICATION_ID = 'component-edit-upgrade';
|
|
59
|
+
|
|
60
|
+
function notifyComponentEditUpgrade() {
|
|
61
|
+
notify( {
|
|
62
|
+
type: 'promotion',
|
|
63
|
+
id: COMPONENT_EDIT_UPGRADE_NOTIFICATION_ID,
|
|
64
|
+
message: __( 'Your Pro subscription has expired. Renew to edit components.', 'elementor' ),
|
|
65
|
+
additionalActionProps: [
|
|
66
|
+
{
|
|
67
|
+
size: 'small',
|
|
68
|
+
variant: 'contained',
|
|
69
|
+
color: 'promotion',
|
|
70
|
+
href: EDIT_COMPONENT_UPGRADE_URL,
|
|
71
|
+
target: '_blank',
|
|
72
|
+
children: __( 'Upgrade Now', 'elementor' ),
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
} );
|
|
76
|
+
}
|
|
77
|
+
|
|
57
78
|
const updateGroups = ( groups: ContextMenuGroup[], config: ContextMenuGroupConfig ): ContextMenuGroup[] => {
|
|
58
79
|
const disableMap = new Map( Object.entries( config.disable ?? {} ) );
|
|
59
80
|
const addMap = new Map( Object.entries( config.add ?? {} ) );
|
|
@@ -67,18 +88,21 @@ const updateGroups = ( groups: ContextMenuGroup[], config: ContextMenuGroupConfi
|
|
|
67
88
|
disabledActions.includes( action.name ) ? { ...action, isEnabled: () => false } : action
|
|
68
89
|
);
|
|
69
90
|
|
|
70
|
-
// Insert additional
|
|
91
|
+
// Insert additional actions if needed
|
|
71
92
|
if ( addConfig ) {
|
|
72
|
-
updatedActions.splice( addConfig.index, 0, addConfig.
|
|
93
|
+
updatedActions.splice( addConfig.index, 0, ...addConfig.actions );
|
|
73
94
|
}
|
|
74
95
|
|
|
75
96
|
return { ...group, actions: updatedActions };
|
|
76
97
|
} );
|
|
77
98
|
};
|
|
78
99
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
100
|
+
type ComponentTypeOptions = CreateTemplatedElementTypeOptions & {
|
|
101
|
+
showLockedByModal?: ( lockedBy: string ) => void;
|
|
102
|
+
showDetachConfirmDialog?: ( onConfirm: () => void ) => void;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export function createComponentType( options: ComponentTypeOptions ): typeof ElementType {
|
|
82
106
|
const legacyWindow = window as unknown as LegacyWindow;
|
|
83
107
|
const WidgetType = legacyWindow.elementor.modules.elements.types.Widget;
|
|
84
108
|
|
|
@@ -99,11 +123,7 @@ export function createComponentType(
|
|
|
99
123
|
};
|
|
100
124
|
}
|
|
101
125
|
|
|
102
|
-
function createComponentView(
|
|
103
|
-
options: CreateTemplatedElementTypeOptions & {
|
|
104
|
-
showLockedByModal?: ( lockedBy: string ) => void;
|
|
105
|
-
}
|
|
106
|
-
): typeof ElementView {
|
|
126
|
+
function createComponentView( options: ComponentTypeOptions ): typeof ElementView {
|
|
107
127
|
const legacyWindow = window as unknown as LegacyWindow & ExtendedWindow;
|
|
108
128
|
|
|
109
129
|
return class extends createTemplatedElementView( options ) {
|
|
@@ -220,17 +240,29 @@ function createComponentView(
|
|
|
220
240
|
const badgeClass = 'elementor-context-menu-list__item__shortcut__new-badge';
|
|
221
241
|
const proBadge = `<a href="${ EDIT_COMPONENT_UPGRADE_URL }" target="_blank" onclick="event.stopPropagation()" class="${ badgeClass }">${ proLabel }</a>`;
|
|
222
242
|
|
|
243
|
+
const editComponentAction: ContextMenuAction = {
|
|
244
|
+
name: 'edit component',
|
|
245
|
+
icon: 'eicon-edit',
|
|
246
|
+
title: () => __( 'Edit Component', 'elementor' ),
|
|
247
|
+
...( ! hasPro && { shortcut: proBadge, hasShortcutAction: true } ),
|
|
248
|
+
isEnabled: () => hasPro,
|
|
249
|
+
callback: ( _: unknown, eventData: ContextMenuEventData ) => this.editComponent( eventData ),
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const detachInstanceAction: ContextMenuAction = {
|
|
253
|
+
name: 'detach instance',
|
|
254
|
+
icon: 'eicon-chain-broken',
|
|
255
|
+
title: () => __( 'Detach from Component', 'elementor' ),
|
|
256
|
+
isEnabled: () => true,
|
|
257
|
+
callback: ( _: unknown, eventData: ContextMenuEventData ) => this.detachInstance( eventData ),
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const actions = isAdministrator ? [ editComponentAction, detachInstanceAction ] : [ detachInstanceAction ];
|
|
261
|
+
|
|
223
262
|
const addedGroup = {
|
|
224
263
|
general: {
|
|
225
264
|
index: 1,
|
|
226
|
-
|
|
227
|
-
name: 'edit component',
|
|
228
|
-
icon: 'eicon-edit',
|
|
229
|
-
title: () => __( 'Edit Component', 'elementor' ),
|
|
230
|
-
...( ! hasPro && { shortcut: proBadge, hasShortcutAction: true } ),
|
|
231
|
-
isEnabled: () => hasPro,
|
|
232
|
-
callback: ( _: unknown, eventData: ContextMenuEventData ) => this.editComponent( eventData ),
|
|
233
|
-
},
|
|
265
|
+
actions,
|
|
234
266
|
},
|
|
235
267
|
};
|
|
236
268
|
|
|
@@ -238,7 +270,7 @@ function createComponentView(
|
|
|
238
270
|
clipboard: [ 'pasteStyle', 'resetStyle' ],
|
|
239
271
|
};
|
|
240
272
|
|
|
241
|
-
return { add:
|
|
273
|
+
return { add: addedGroup, disable: disabledGroup };
|
|
242
274
|
}
|
|
243
275
|
|
|
244
276
|
async switchDocument() {
|
|
@@ -276,10 +308,42 @@ function createComponentView(
|
|
|
276
308
|
} );
|
|
277
309
|
}
|
|
278
310
|
|
|
311
|
+
detachInstance( { trigger, location, secondaryLocation }: ContextMenuEventData ) {
|
|
312
|
+
const componentId = this.getComponentId();
|
|
313
|
+
const instanceId = this.model.get( 'id' );
|
|
314
|
+
|
|
315
|
+
if ( ! componentId || ! instanceId ) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const handleConfirm = async () => {
|
|
320
|
+
try {
|
|
321
|
+
await detachComponentInstance( {
|
|
322
|
+
instanceId,
|
|
323
|
+
componentId,
|
|
324
|
+
trackingInfo: { location, secondaryLocation, trigger },
|
|
325
|
+
} );
|
|
326
|
+
} catch {
|
|
327
|
+
notify( {
|
|
328
|
+
type: 'error',
|
|
329
|
+
message: __( 'Failed to detach component instance.', 'elementor' ),
|
|
330
|
+
id: 'detach-component-instance-failed',
|
|
331
|
+
} );
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
options.showDetachConfirmDialog?.( handleConfirm );
|
|
336
|
+
}
|
|
337
|
+
|
|
279
338
|
handleDblClick( e: MouseEvent ) {
|
|
280
339
|
e.stopPropagation();
|
|
281
340
|
|
|
282
|
-
if ( ! isUserAdministrator()
|
|
341
|
+
if ( ! isUserAdministrator() ) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if ( ! hasProInstalled() ) {
|
|
346
|
+
notifyComponentEditUpgrade();
|
|
283
347
|
return;
|
|
284
348
|
}
|
|
285
349
|
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ export { EmptyState as InstanceEmptyState } from './components/instance-editing-
|
|
|
17
17
|
export { InstancePanelBody } from './components/instance-editing-panel/instance-panel-body';
|
|
18
18
|
export { EditComponentAction, InstancePanelHeader } from './components/instance-editing-panel/instance-panel-header';
|
|
19
19
|
export { useInstancePanelData } from './components/instance-editing-panel/use-instance-panel-data';
|
|
20
|
+
export { DetachAction } from './components/instance-editing-panel/detach-action';
|
|
20
21
|
|
|
21
22
|
export { COMPONENT_WIDGET_TYPE } from './create-component-type';
|
|
22
23
|
|
package/src/init.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { componentInstanceTransformer } from './component-instance-transformer';
|
|
|
18
18
|
import { componentOverridableTransformer } from './component-overridable-transformer';
|
|
19
19
|
import { componentOverrideTransformer } from './component-override-transformer';
|
|
20
20
|
import { Components } from './components/components-tab/components';
|
|
21
|
+
import { openDetachConfirmDialog } from './components/detach-instance-confirmation-dialog';
|
|
21
22
|
import { openEditModeDialog } from './components/in-edit-mode';
|
|
22
23
|
import { InstanceEditingPanel } from './components/instance-editing-panel/instance-editing-panel';
|
|
23
24
|
import { LoadTemplateComponents } from './components/load-template-components';
|
|
@@ -42,7 +43,11 @@ export function init() {
|
|
|
42
43
|
registerSlice( slice );
|
|
43
44
|
|
|
44
45
|
registerElementType( COMPONENT_WIDGET_TYPE, ( options: CreateTemplatedElementTypeOptions ) =>
|
|
45
|
-
createComponentType( {
|
|
46
|
+
createComponentType( {
|
|
47
|
+
...options,
|
|
48
|
+
showLockedByModal: openEditModeDialog,
|
|
49
|
+
showDetachConfirmDialog: openDetachConfirmDialog,
|
|
50
|
+
} )
|
|
46
51
|
);
|
|
47
52
|
|
|
48
53
|
( window as unknown as ExtendedWindow ).elementorCommon.__beforeSave = beforeSave;
|
package/src/types.ts
CHANGED
|
@@ -74,7 +74,7 @@ export type ExtendedWindow = Window & {
|
|
|
74
74
|
elementorCommon: Record< string, unknown > & {
|
|
75
75
|
eventsManager: {
|
|
76
76
|
config: {
|
|
77
|
-
locations: Record< string, string >;
|
|
77
|
+
locations: Record< string, string | Record< string, string > >;
|
|
78
78
|
secondaryLocations: Record< string, string >;
|
|
79
79
|
triggers: Record< string, string >;
|
|
80
80
|
};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { getContainer, replaceElement, type V1Element, type V1ElementModelProps } from '@elementor/editor-elements';
|
|
2
|
+
import { undoable } from '@elementor/editor-v1-adapters';
|
|
3
|
+
import { __dispatch as dispatch, __getState as getState } from '@elementor/store';
|
|
4
|
+
import { __ } from '@wordpress/i18n';
|
|
5
|
+
|
|
6
|
+
import { componentInstanceOverridesPropTypeUtil } from '../../prop-types/component-instance-overrides-prop-type';
|
|
7
|
+
import {
|
|
8
|
+
type ComponentInstanceProp,
|
|
9
|
+
componentInstancePropTypeUtil,
|
|
10
|
+
} from '../../prop-types/component-instance-prop-type';
|
|
11
|
+
import { selectComponent, selectCurrentComponentId, selectOverridableProps, slice } from '../../store/store';
|
|
12
|
+
import { type OverridableProps } from '../../types';
|
|
13
|
+
import { getComponentDocumentData } from '../component-document-data';
|
|
14
|
+
import { trackComponentEvent } from '../tracking';
|
|
15
|
+
import { resolveDetachedInstance } from './resolve-detached-instance';
|
|
16
|
+
|
|
17
|
+
type DetachParams = {
|
|
18
|
+
instanceId: string;
|
|
19
|
+
componentId: number;
|
|
20
|
+
trackingInfo: {
|
|
21
|
+
location: string;
|
|
22
|
+
secondaryLocation?: string;
|
|
23
|
+
trigger: string;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type DoReturn = {
|
|
28
|
+
detachedElement: V1Element;
|
|
29
|
+
detachedInstanceElementData: V1ElementModelProps;
|
|
30
|
+
editedComponentOnDetach: number | null;
|
|
31
|
+
overridablePropsBeforeDetach: OverridableProps | null;
|
|
32
|
+
originalInstanceModel: V1ElementModelProps;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export async function detachComponentInstance( {
|
|
36
|
+
instanceId,
|
|
37
|
+
componentId,
|
|
38
|
+
trackingInfo,
|
|
39
|
+
}: DetachParams ): Promise< DoReturn > {
|
|
40
|
+
const instanceContainer = getContainer( instanceId );
|
|
41
|
+
|
|
42
|
+
if ( ! instanceContainer ) {
|
|
43
|
+
throw new Error( `Instance container with ID "${ instanceId }" not found.` );
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const componentData = await getComponentDocumentData( componentId );
|
|
47
|
+
|
|
48
|
+
if ( ! componentData ) {
|
|
49
|
+
throw new Error( `Component with ID "${ componentId }" not found.` );
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const rootElement = componentData.elements?.[ 0 ];
|
|
53
|
+
|
|
54
|
+
if ( ! rootElement ) {
|
|
55
|
+
throw new Error( `Component with ID "${ componentId }" has no root element.` );
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const undoableDetach = undoable(
|
|
59
|
+
{
|
|
60
|
+
do: (): DoReturn => {
|
|
61
|
+
const overrides = extractInstanceOverrides( instanceContainer );
|
|
62
|
+
const detachedInstanceElementData = resolveDetachedInstance(
|
|
63
|
+
rootElement,
|
|
64
|
+
overrides
|
|
65
|
+
) as V1ElementModelProps;
|
|
66
|
+
|
|
67
|
+
const editedComponentOnDetach = selectCurrentComponentId( getState() );
|
|
68
|
+
// We need to store the overridable props of the current component before detach to restore them on undo.
|
|
69
|
+
const overridablePropsBeforeDetach = editedComponentOnDetach
|
|
70
|
+
? selectOverridableProps( getState(), editedComponentOnDetach ) ?? null
|
|
71
|
+
: null;
|
|
72
|
+
|
|
73
|
+
const originalInstanceModel = instanceContainer.model.toJSON();
|
|
74
|
+
|
|
75
|
+
const detachedElement = replaceElement( {
|
|
76
|
+
currentElementId: instanceId,
|
|
77
|
+
newElement: detachedInstanceElementData,
|
|
78
|
+
withHistory: false,
|
|
79
|
+
} );
|
|
80
|
+
|
|
81
|
+
const componentUid = selectComponent( getState(), componentId )?.uid;
|
|
82
|
+
trackComponentEvent( {
|
|
83
|
+
action: 'detached',
|
|
84
|
+
source: 'user',
|
|
85
|
+
component_uid: componentUid,
|
|
86
|
+
instance_id: instanceId,
|
|
87
|
+
location: trackingInfo.location,
|
|
88
|
+
secondary_location: trackingInfo.secondaryLocation,
|
|
89
|
+
trigger: trackingInfo.trigger,
|
|
90
|
+
} );
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
detachedElement,
|
|
94
|
+
detachedInstanceElementData,
|
|
95
|
+
editedComponentOnDetach,
|
|
96
|
+
overridablePropsBeforeDetach,
|
|
97
|
+
originalInstanceModel,
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
undo: (
|
|
101
|
+
_: undefined,
|
|
102
|
+
{
|
|
103
|
+
detachedElement,
|
|
104
|
+
originalInstanceModel,
|
|
105
|
+
overridablePropsBeforeDetach,
|
|
106
|
+
editedComponentOnDetach,
|
|
107
|
+
}: DoReturn
|
|
108
|
+
): V1Element => {
|
|
109
|
+
const restoredInstance = replaceElement( {
|
|
110
|
+
currentElementId: detachedElement.id,
|
|
111
|
+
newElement: originalInstanceModel,
|
|
112
|
+
withHistory: false,
|
|
113
|
+
} );
|
|
114
|
+
|
|
115
|
+
const currentComponentId = selectCurrentComponentId( getState() );
|
|
116
|
+
if (
|
|
117
|
+
currentComponentId &&
|
|
118
|
+
currentComponentId === editedComponentOnDetach &&
|
|
119
|
+
overridablePropsBeforeDetach
|
|
120
|
+
) {
|
|
121
|
+
dispatch(
|
|
122
|
+
slice.actions.setOverridableProps( {
|
|
123
|
+
componentId: currentComponentId,
|
|
124
|
+
overridableProps: overridablePropsBeforeDetach,
|
|
125
|
+
} )
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return restoredInstance;
|
|
130
|
+
},
|
|
131
|
+
redo: ( _: undefined, doReturn: DoReturn, restoredInstance: V1Element ) => {
|
|
132
|
+
const { detachedInstanceElementData } = doReturn;
|
|
133
|
+
|
|
134
|
+
const editedComponentOnDetach = selectCurrentComponentId( getState() );
|
|
135
|
+
// We need to store the overridable props of the current component before detach to restore them on undo.
|
|
136
|
+
const overridablePropsBeforeDetach = editedComponentOnDetach
|
|
137
|
+
? selectOverridableProps( getState(), editedComponentOnDetach ) ?? null
|
|
138
|
+
: null;
|
|
139
|
+
|
|
140
|
+
const detachedElement = replaceElement( {
|
|
141
|
+
currentElementId: restoredInstance.id,
|
|
142
|
+
newElement: detachedInstanceElementData,
|
|
143
|
+
withHistory: false,
|
|
144
|
+
} );
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
...doReturn,
|
|
148
|
+
detachedElement,
|
|
149
|
+
editedComponentOnDetach,
|
|
150
|
+
overridablePropsBeforeDetach,
|
|
151
|
+
};
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
title: __( 'Detach from Component', 'elementor' ),
|
|
156
|
+
subtitle: __( 'Instance detached', 'elementor' ),
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
return undoableDetach();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function extractInstanceOverrides( instanceContainer: NonNullable< ReturnType< typeof getContainer > > ) {
|
|
164
|
+
const settings = instanceContainer.model.toJSON().settings;
|
|
165
|
+
const componentInstance = componentInstancePropTypeUtil.extract(
|
|
166
|
+
settings?.component_instance as ComponentInstanceProp
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const overrides = componentInstanceOverridesPropTypeUtil.extract( componentInstance?.overrides );
|
|
170
|
+
|
|
171
|
+
return overrides ?? [];
|
|
172
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { detachComponentInstance } from './detach-component-instance';
|