@elementor/editor-components 4.0.0-663 → 4.0.0-665
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 +1246 -845
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1013 -613
- 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 +60 -22
- 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-665",
|
|
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-665",
|
|
44
|
+
"@elementor/editor-canvas": "4.0.0-665",
|
|
45
|
+
"@elementor/editor-controls": "4.0.0-665",
|
|
46
|
+
"@elementor/editor-documents": "4.0.0-665",
|
|
47
|
+
"@elementor/editor-editing-panel": "4.0.0-665",
|
|
48
|
+
"@elementor/editor-elements": "4.0.0-665",
|
|
49
|
+
"@elementor/editor-elements-panel": "4.0.0-665",
|
|
50
|
+
"@elementor/editor-mcp": "4.0.0-665",
|
|
51
|
+
"@elementor/editor-templates": "4.0.0-665",
|
|
52
|
+
"@elementor/editor-panels": "4.0.0-665",
|
|
53
|
+
"@elementor/editor-props": "4.0.0-665",
|
|
54
|
+
"@elementor/editor-styles-repository": "4.0.0-665",
|
|
55
|
+
"@elementor/editor-ui": "4.0.0-665",
|
|
56
|
+
"@elementor/editor-v1-adapters": "4.0.0-665",
|
|
57
|
+
"@elementor/http-client": "4.0.0-665",
|
|
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-665",
|
|
60
|
+
"@elementor/query": "4.0.0-665",
|
|
61
|
+
"@elementor/schema": "4.0.0-665",
|
|
62
|
+
"@elementor/store": "4.0.0-665",
|
|
63
63
|
"@elementor/ui": "1.36.17",
|
|
64
|
-
"@elementor/utils": "4.0.0-
|
|
64
|
+
"@elementor/utils": "4.0.0-665",
|
|
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-665",
|
|
67
|
+
"@elementor/editor-current-user": "4.0.0-665"
|
|
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,
|
|
@@ -22,15 +23,14 @@ import { apiClient } from './api';
|
|
|
22
23
|
import { type ComponentInstanceProp } from './prop-types/component-instance-prop-type';
|
|
23
24
|
import { type ComponentsSlice, selectComponentByUid } from './store/store';
|
|
24
25
|
import { type ComponentRenderContext, type ExtendedWindow } from './types';
|
|
26
|
+
import { detachComponentInstance } from './utils/detach-component-instance';
|
|
25
27
|
import { formatComponentElementsId } from './utils/format-component-elements-id';
|
|
26
28
|
import { switchToComponent } from './utils/switch-to-component';
|
|
27
29
|
import { trackComponentEvent } from './utils/tracking';
|
|
28
30
|
|
|
29
|
-
type ContextMenuEventData = { location: string; secondaryLocation: string; trigger: string };
|
|
30
|
-
|
|
31
31
|
type ContextMenuGroupConfig = {
|
|
32
32
|
disable: Record< string, string[] >;
|
|
33
|
-
add: Record< string, { index: number;
|
|
33
|
+
add: Record< string, { index: number; actions: ContextMenuAction[] } >;
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
type ContextMenuGroup = {
|
|
@@ -88,18 +88,21 @@ const updateGroups = ( groups: ContextMenuGroup[], config: ContextMenuGroupConfi
|
|
|
88
88
|
disabledActions.includes( action.name ) ? { ...action, isEnabled: () => false } : action
|
|
89
89
|
);
|
|
90
90
|
|
|
91
|
-
// Insert additional
|
|
91
|
+
// Insert additional actions if needed
|
|
92
92
|
if ( addConfig ) {
|
|
93
|
-
updatedActions.splice( addConfig.index, 0, addConfig.
|
|
93
|
+
updatedActions.splice( addConfig.index, 0, ...addConfig.actions );
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
return { ...group, actions: updatedActions };
|
|
97
97
|
} );
|
|
98
98
|
};
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
type ComponentTypeOptions = CreateTemplatedElementTypeOptions & {
|
|
101
|
+
showLockedByModal?: ( lockedBy: string ) => void;
|
|
102
|
+
showDetachConfirmDialog?: ( onConfirm: () => void ) => void;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export function createComponentType( options: ComponentTypeOptions ): typeof ElementType {
|
|
103
106
|
const legacyWindow = window as unknown as LegacyWindow;
|
|
104
107
|
const WidgetType = legacyWindow.elementor.modules.elements.types.Widget;
|
|
105
108
|
|
|
@@ -120,11 +123,7 @@ export function createComponentType(
|
|
|
120
123
|
};
|
|
121
124
|
}
|
|
122
125
|
|
|
123
|
-
function createComponentView(
|
|
124
|
-
options: CreateTemplatedElementTypeOptions & {
|
|
125
|
-
showLockedByModal?: ( lockedBy: string ) => void;
|
|
126
|
-
}
|
|
127
|
-
): typeof ElementView {
|
|
126
|
+
function createComponentView( options: ComponentTypeOptions ): typeof ElementView {
|
|
128
127
|
const legacyWindow = window as unknown as LegacyWindow & ExtendedWindow;
|
|
129
128
|
|
|
130
129
|
return class extends createTemplatedElementView( options ) {
|
|
@@ -241,17 +240,29 @@ function createComponentView(
|
|
|
241
240
|
const badgeClass = 'elementor-context-menu-list__item__shortcut__new-badge';
|
|
242
241
|
const proBadge = `<a href="${ EDIT_COMPONENT_UPGRADE_URL }" target="_blank" onclick="event.stopPropagation()" class="${ badgeClass }">${ proLabel }</a>`;
|
|
243
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
|
+
|
|
244
262
|
const addedGroup = {
|
|
245
263
|
general: {
|
|
246
264
|
index: 1,
|
|
247
|
-
|
|
248
|
-
name: 'edit component',
|
|
249
|
-
icon: 'eicon-edit',
|
|
250
|
-
title: () => __( 'Edit Component', 'elementor' ),
|
|
251
|
-
...( ! hasPro && { shortcut: proBadge, hasShortcutAction: true } ),
|
|
252
|
-
isEnabled: () => hasPro,
|
|
253
|
-
callback: ( _: unknown, eventData: ContextMenuEventData ) => this.editComponent( eventData ),
|
|
254
|
-
},
|
|
265
|
+
actions,
|
|
255
266
|
},
|
|
256
267
|
};
|
|
257
268
|
|
|
@@ -259,7 +270,7 @@ function createComponentView(
|
|
|
259
270
|
clipboard: [ 'pasteStyle', 'resetStyle' ],
|
|
260
271
|
};
|
|
261
272
|
|
|
262
|
-
return { add:
|
|
273
|
+
return { add: addedGroup, disable: disabledGroup };
|
|
263
274
|
}
|
|
264
275
|
|
|
265
276
|
async switchDocument() {
|
|
@@ -297,6 +308,33 @@ function createComponentView(
|
|
|
297
308
|
} );
|
|
298
309
|
}
|
|
299
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
|
+
|
|
300
338
|
handleDblClick( e: MouseEvent ) {
|
|
301
339
|
e.stopPropagation();
|
|
302
340
|
|
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';
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { generateElementId, type V1ElementData, type V1ElementSettingsProps } from '@elementor/editor-elements';
|
|
2
|
+
import { classesPropTypeUtil, type ClassesPropValue, type PropValue } from '@elementor/editor-props';
|
|
3
|
+
import { type StyleDefinition, type StyleDefinitionID } from '@elementor/editor-styles';
|
|
4
|
+
|
|
5
|
+
// Ts version for atomic-widgets/assets/js/editor/utils/regenerate-local-style-ids.js
|
|
6
|
+
export function regenerateLocalStyleIds( element: V1ElementData ): {
|
|
7
|
+
styles: Record< StyleDefinitionID, StyleDefinition > | undefined;
|
|
8
|
+
settings: V1ElementSettingsProps | undefined;
|
|
9
|
+
} {
|
|
10
|
+
const originalStyles = element.styles;
|
|
11
|
+
|
|
12
|
+
if ( ! originalStyles || Object.keys( originalStyles ).length === 0 ) {
|
|
13
|
+
return { styles: undefined, settings: undefined };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const newStyles: Record< string, StyleDefinition > = {};
|
|
17
|
+
const styleIdMapping: Record< string, string > = {};
|
|
18
|
+
|
|
19
|
+
for ( const [ originalStyleId, style ] of Object.entries( originalStyles ) ) {
|
|
20
|
+
const newStyleId = generateLocalStyleId( element.id );
|
|
21
|
+
|
|
22
|
+
newStyles[ newStyleId ] = { ...style, id: newStyleId };
|
|
23
|
+
styleIdMapping[ originalStyleId ] = newStyleId;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const settings = element.settings;
|
|
27
|
+
if ( ! settings || Object.keys( settings ).length === 0 ) {
|
|
28
|
+
return { styles: newStyles, settings: undefined };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const updatedSettings = { ...settings };
|
|
32
|
+
|
|
33
|
+
for ( const [ propKey, propValue ] of Object.entries( updatedSettings ) ) {
|
|
34
|
+
if ( isClassesProp( propValue ) && propValue.value.length > 0 ) {
|
|
35
|
+
const updatedClasses = propValue.value.map( ( classId ) => styleIdMapping[ classId ] ?? classId );
|
|
36
|
+
|
|
37
|
+
updatedSettings[ propKey ] = classesPropTypeUtil.create( updatedClasses );
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
styles: newStyles,
|
|
43
|
+
settings: updatedSettings,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isClassesProp( prop: PropValue ): prop is ClassesPropValue {
|
|
48
|
+
return classesPropTypeUtil.isValid( prop );
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function generateLocalStyleId( elementId: string ): string {
|
|
52
|
+
return `e-${ elementId }-${ generateElementId() }`;
|
|
53
|
+
}
|