@elementor/editor-components 4.0.0-manual → 4.1.0-684

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 (101) hide show
  1. package/dist/index.d.mts +1422 -1
  2. package/dist/index.d.ts +1422 -1
  3. package/dist/index.js +1964 -4820
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +1901 -4849
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +23 -23
  8. package/src/components/components-tab/components-list.tsx +47 -4
  9. package/src/components/components-tab/components-pro-notification.tsx +9 -15
  10. package/src/components/components-tab/components.tsx +51 -3
  11. package/src/components/components-tab/loading-components.tsx +26 -14
  12. package/src/components/components-upgrade-alert.tsx +39 -0
  13. package/src/components/detach-instance-confirmation-dialog.tsx +50 -0
  14. package/src/components/instance-editing-panel/detach-action.tsx +76 -0
  15. package/src/components/instance-editing-panel/empty-state.tsx +9 -2
  16. package/src/components/instance-editing-panel/instance-editing-panel.tsx +25 -6
  17. package/src/components/instance-editing-panel/use-instance-panel-data.ts +2 -2
  18. package/src/consts.ts +1 -0
  19. package/src/create-component-type.ts +89 -26
  20. package/src/index.ts +92 -0
  21. package/src/init.ts +6 -4
  22. package/src/store/actions/update-overridable-prop.ts +4 -10
  23. package/src/store/dispatchers.ts +63 -0
  24. package/src/store/extensible-slice.ts +168 -0
  25. package/src/store/selectors.ts +53 -0
  26. package/src/store/store-types.ts +48 -0
  27. package/src/store/store.ts +7 -169
  28. package/src/sync/publish-draft-components-in-page-before-save.ts +42 -1
  29. package/src/types.ts +1 -1
  30. package/src/utils/detach-component-instance/detach-component-instance.ts +172 -0
  31. package/src/utils/detach-component-instance/index.ts +1 -0
  32. package/src/utils/detach-component-instance/regenerate-local-style-ids.ts +53 -0
  33. package/src/utils/detach-component-instance/resolve-detached-instance.ts +94 -0
  34. package/src/utils/detach-component-instance/resolve-overridable-settings.ts +121 -0
  35. package/src/utils/is-component-instance.ts +1 -1
  36. package/src/utils/tracking.ts +2 -1
  37. package/src/extended/components/component-introduction.tsx +0 -77
  38. package/src/extended/components/component-panel-header/component-badge.tsx +0 -73
  39. package/src/extended/components/component-panel-header/component-panel-header.tsx +0 -98
  40. package/src/extended/components/component-properties-panel/component-properties-panel-content.tsx +0 -176
  41. package/src/extended/components/component-properties-panel/component-properties-panel.tsx +0 -43
  42. package/src/extended/components/component-properties-panel/properties-empty-state.tsx +0 -51
  43. package/src/extended/components/component-properties-panel/properties-group.tsx +0 -196
  44. package/src/extended/components/component-properties-panel/property-item.tsx +0 -124
  45. package/src/extended/components/component-properties-panel/sortable.tsx +0 -92
  46. package/src/extended/components/component-properties-panel/use-current-editable-item.ts +0 -73
  47. package/src/extended/components/component-properties-panel/utils/generate-unique-label.ts +0 -21
  48. package/src/extended/components/component-properties-panel/utils/validate-group-label.ts +0 -24
  49. package/src/extended/components/components-tab/component-item.tsx +0 -180
  50. package/src/extended/components/components-tab/components.tsx +0 -58
  51. package/src/extended/components/components-tab/delete-confirmation-dialog.tsx +0 -26
  52. package/src/extended/components/create-component-form/create-component-form.tsx +0 -282
  53. package/src/extended/components/create-component-form/hooks/use-form.ts +0 -72
  54. package/src/extended/components/create-component-form/utils/get-component-event-data.ts +0 -54
  55. package/src/extended/components/edit-component/component-modal.tsx +0 -133
  56. package/src/extended/components/edit-component/edit-component.tsx +0 -166
  57. package/src/extended/components/edit-component/use-canvas-document.ts +0 -9
  58. package/src/extended/components/edit-component/use-element-rect.ts +0 -81
  59. package/src/extended/components/instance-editing-panel/instance-editing-panel.tsx +0 -60
  60. package/src/extended/components/overridable-props/indicator.tsx +0 -83
  61. package/src/extended/components/overridable-props/overridable-prop-control.tsx +0 -127
  62. package/src/extended/components/overridable-props/overridable-prop-form.tsx +0 -135
  63. package/src/extended/components/overridable-props/overridable-prop-indicator.tsx +0 -138
  64. package/src/extended/components/overridable-props/utils/validate-prop-label.ts +0 -38
  65. package/src/extended/consts.ts +0 -3
  66. package/src/extended/hooks/use-navigate-back.ts +0 -24
  67. package/src/extended/init.ts +0 -104
  68. package/src/extended/mcp/index.ts +0 -14
  69. package/src/extended/mcp/save-as-component-tool.ts +0 -436
  70. package/src/extended/store/actions/add-overridable-group.ts +0 -59
  71. package/src/extended/store/actions/archive-component.ts +0 -19
  72. package/src/extended/store/actions/create-unpublished-component.ts +0 -102
  73. package/src/extended/store/actions/delete-overridable-group.ts +0 -38
  74. package/src/extended/store/actions/delete-overridable-prop.ts +0 -70
  75. package/src/extended/store/actions/rename-component.ts +0 -49
  76. package/src/extended/store/actions/rename-overridable-group.ts +0 -39
  77. package/src/extended/store/actions/reorder-group-props.ts +0 -43
  78. package/src/extended/store/actions/reorder-overridable-groups.ts +0 -30
  79. package/src/extended/store/actions/reset-sanitized-components.ts +0 -7
  80. package/src/extended/store/actions/set-overridable-prop.ts +0 -117
  81. package/src/extended/store/actions/update-component-sanitized-attribute.ts +0 -8
  82. package/src/extended/store/actions/update-current-component.ts +0 -21
  83. package/src/extended/store/actions/update-overridable-prop-params.ts +0 -58
  84. package/src/extended/store/utils/groups-transformers.ts +0 -187
  85. package/src/extended/sync/before-save.ts +0 -52
  86. package/src/extended/sync/cleanup-overridable-props-on-delete.ts +0 -85
  87. package/src/extended/sync/create-components-before-save.ts +0 -113
  88. package/src/extended/sync/handle-component-edit-mode-container.ts +0 -114
  89. package/src/extended/sync/prevent-non-atomic-nesting.ts +0 -198
  90. package/src/extended/sync/revert-overridables-on-copy-or-duplicate.ts +0 -66
  91. package/src/extended/sync/sanitize-overridable-props.ts +0 -32
  92. package/src/extended/sync/set-component-overridable-props-settings-before-save.ts +0 -23
  93. package/src/extended/sync/update-archived-component-before-save.ts +0 -32
  94. package/src/extended/sync/update-component-title-before-save.ts +0 -19
  95. package/src/extended/utils/component-form-schema.ts +0 -32
  96. package/src/extended/utils/component-name-validation.ts +0 -27
  97. package/src/extended/utils/create-component-model.ts +0 -28
  98. package/src/extended/utils/get-container-for-new-element.ts +0 -49
  99. package/src/extended/utils/is-editing-component.ts +0 -13
  100. package/src/extended/utils/replace-element-with-component.ts +0 -11
  101. package/src/extended/utils/revert-overridable-settings.ts +0 -207
@@ -1,176 +1,14 @@
1
- import { type V1Document } from '@elementor/editor-documents';
2
- import {
3
- __createSelector as createSelector,
4
- __createSlice as createSlice,
5
- __useSelector as useSelector,
6
- type PayloadAction,
7
- type SliceState,
8
- } from '@elementor/store';
1
+ import { __createSelector as createSelector, __useSelector as useSelector, type SliceState } from '@elementor/store';
9
2
 
10
- import {
11
- type Component,
12
- type ComponentId,
13
- type OverridableProps,
14
- type PublishedComponent,
15
- type StylesDefinition,
16
- type UnpublishedComponent,
17
- } from '../types';
18
- import { loadComponents } from './thunks';
19
-
20
- type GetComponentResponse = PublishedComponent[];
21
-
22
- type Status = 'idle' | 'pending' | 'error';
23
-
24
- export type SanitizeAttributes = 'overridableProps';
25
-
26
- type ComponentsState = {
27
- data: PublishedComponent[];
28
- unpublishedData: UnpublishedComponent[];
29
- loadStatus: Status;
30
- styles: StylesDefinition;
31
- createdThisSession: Component[ 'uid' ][];
32
- archivedThisSession: ComponentId[];
33
- path: ComponentsPathItem[];
34
- currentComponentId: V1Document[ 'id' ] | null;
35
- updatedComponentNames: Record< number, string >;
36
-
37
- // We use this map to flag any sanitized attribute of a given component
38
- // This map currently resets in response to the `editor/documents/open` command
39
- sanitized: Record< ComponentId, Partial< Record< SanitizeAttributes, boolean > > >;
40
- };
3
+ import type { ComponentId, OverridableProps, PublishedComponent, UnpublishedComponent } from '../types';
4
+ import { type slice } from './extensible-slice';
5
+ import { type SanitizeAttributes, SLICE_NAME } from './store-types';
41
6
 
42
7
  export type ComponentsSlice = SliceState< typeof slice >;
43
8
 
44
- export type ComponentsPathItem = {
45
- instanceId?: string;
46
- instanceTitle?: string;
47
- componentId: V1Document[ 'id' ];
48
- };
49
-
50
- export const initialState: ComponentsState = {
51
- data: [],
52
- unpublishedData: [],
53
- loadStatus: 'idle',
54
- styles: {},
55
- createdThisSession: [],
56
- archivedThisSession: [],
57
- path: [],
58
- currentComponentId: null,
59
- updatedComponentNames: {},
60
- sanitized: {},
61
- };
62
-
63
- export const SLICE_NAME = 'components';
64
-
65
- export const slice = createSlice( {
66
- name: SLICE_NAME,
67
- initialState,
68
- reducers: {
69
- add: ( state, { payload }: PayloadAction< PublishedComponent | PublishedComponent[] > ) => {
70
- if ( Array.isArray( payload ) ) {
71
- state.data = [ ...payload, ...state.data ];
72
- } else {
73
- state.data.unshift( payload );
74
- }
75
- },
76
- load: ( state, { payload }: PayloadAction< PublishedComponent[] > ) => {
77
- state.data = payload;
78
- },
79
- addUnpublished: ( state, { payload }: PayloadAction< UnpublishedComponent > ) => {
80
- state.unpublishedData.unshift( payload );
81
- },
82
- removeUnpublished: ( state, { payload }: PayloadAction< string | string[] > ) => {
83
- const uidsToRemove = Array.isArray( payload ) ? payload : [ payload ];
84
- state.unpublishedData = state.unpublishedData.filter(
85
- ( component ) => ! uidsToRemove.includes( component.uid )
86
- );
87
- },
88
- resetUnpublished: ( state ) => {
89
- state.unpublishedData = [];
90
- },
91
- removeStyles( state, { payload }: PayloadAction< { id: ComponentId } > ) {
92
- const { [ payload.id ]: _, ...rest } = state.styles;
93
-
94
- state.styles = rest;
95
- },
96
- addStyles: ( state, { payload } ) => {
97
- state.styles = { ...state.styles, ...payload };
98
- },
99
- addCreatedThisSession: ( state, { payload }: PayloadAction< string > ) => {
100
- state.createdThisSession.push( payload );
101
- },
102
- removeCreatedThisSession: ( state, { payload }: PayloadAction< string > ) => {
103
- state.createdThisSession = state.createdThisSession.filter( ( uid ) => uid !== payload );
104
- },
105
- archive: ( state, { payload }: PayloadAction< number > ) => {
106
- const component = state.data.find( ( comp ) => comp.id === payload );
107
-
108
- if ( component ) {
109
- component.isArchived = true;
110
- state.archivedThisSession.push( payload );
111
- }
112
- },
113
- setCurrentComponentId: ( state, { payload }: PayloadAction< V1Document[ 'id' ] | null > ) => {
114
- state.currentComponentId = payload;
115
- },
116
- setPath: ( state, { payload }: PayloadAction< ComponentsPathItem[] > ) => {
117
- state.path = payload;
118
- },
119
- setOverridableProps: (
120
- state,
121
- { payload }: PayloadAction< { componentId: ComponentId; overridableProps: OverridableProps } >
122
- ) => {
123
- const component = state.data.find( ( comp ) => comp.id === payload.componentId );
124
-
125
- if ( ! component ) {
126
- return;
127
- }
128
-
129
- component.overridableProps = payload.overridableProps;
130
- },
131
- rename: ( state, { payload }: PayloadAction< { componentUid: string; name: string } > ) => {
132
- const component = state.data.find( ( comp ) => comp.uid === payload.componentUid );
133
-
134
- if ( ! component ) {
135
- return;
136
- }
137
- if ( component.id ) {
138
- state.updatedComponentNames[ component.id ] = payload.name;
139
- }
140
- component.name = payload.name;
141
- },
142
- cleanUpdatedComponentNames: ( state ) => {
143
- state.updatedComponentNames = {};
144
- },
145
- updateComponentSanitizedAttribute: (
146
- state,
147
- {
148
- payload: { componentId, attribute },
149
- }: PayloadAction< { componentId: ComponentId; attribute: SanitizeAttributes } >
150
- ) => {
151
- if ( ! state.sanitized[ componentId ] ) {
152
- state.sanitized[ componentId ] = {};
153
- }
154
-
155
- state.sanitized[ componentId ][ attribute ] = true;
156
- },
157
- resetSanitizedComponents: ( state ) => {
158
- state.sanitized = {};
159
- },
160
- },
161
- extraReducers: ( builder ) => {
162
- builder.addCase( loadComponents.fulfilled, ( state, { payload }: PayloadAction< GetComponentResponse > ) => {
163
- state.data = payload;
164
- state.loadStatus = 'idle';
165
- } );
166
- builder.addCase( loadComponents.pending, ( state ) => {
167
- state.loadStatus = 'pending';
168
- } );
169
- builder.addCase( loadComponents.rejected, ( state ) => {
170
- state.loadStatus = 'error';
171
- } );
172
- },
173
- } );
9
+ export { slice, registerComponentsReducer, createComponentsAction, __resetExtraReducers } from './extensible-slice';
10
+ export type { ComponentsState, ComponentsPathItem, SanitizeAttributes } from './store-types';
11
+ export { initialState, SLICE_NAME } from './store-types';
174
12
 
175
13
  export const selectData = ( state: ComponentsSlice ) => state[ SLICE_NAME ].data;
176
14
  export const selectArchivedThisSession = ( state: ComponentsSlice ) => state[ SLICE_NAME ].archivedThisSession;
@@ -1,10 +1,17 @@
1
1
  import { invalidateDocumentData, isDocumentDirty } from '@elementor/editor-documents';
2
2
  import { type V1ElementData } from '@elementor/editor-elements';
3
+ import { notify } from '@elementor/editor-notifications';
4
+ import { AxiosError } from '@elementor/http-client';
5
+ import { __ } from '@wordpress/i18n';
3
6
 
4
7
  import { apiClient } from '../api';
5
8
  import { type DocumentSaveStatus } from '../types';
6
9
  import { getComponentDocuments } from '../utils/get-component-documents';
7
10
 
11
+ const INSUFFICIENT_PERMISSIONS_ERROR_CODE = 'insufficient_permissions';
12
+ const PUBLISH_UPGRADE_URL = 'https://go.elementor.com/go-pro-components-edit/';
13
+ const PUBLISH_UPGRADE_NOTIFICATION_ID = 'component-publish-upgrade';
14
+
8
15
  type Options = {
9
16
  status: DocumentSaveStatus;
10
17
  elements: V1ElementData[];
@@ -23,7 +30,41 @@ export async function publishDraftComponentsInPageBeforeSave( { status, elements
23
30
  return;
24
31
  }
25
32
 
26
- await apiClient.updateStatuses( draftIds, 'publish' );
33
+ try {
34
+ await apiClient.updateStatuses( draftIds, 'publish' );
35
+ } catch ( error ) {
36
+ if ( isInsufficientPermissionsError( error ) ) {
37
+ notifyPublishUpgrade();
38
+ return;
39
+ }
40
+
41
+ throw error;
42
+ }
27
43
 
28
44
  draftIds.forEach( ( id ) => invalidateDocumentData( id ) );
29
45
  }
46
+
47
+ function isInsufficientPermissionsError( error: unknown ): boolean {
48
+ return error instanceof AxiosError && error.response?.data?.code === INSUFFICIENT_PERMISSIONS_ERROR_CODE;
49
+ }
50
+
51
+ function notifyPublishUpgrade() {
52
+ notify( {
53
+ type: 'promotion',
54
+ id: PUBLISH_UPGRADE_NOTIFICATION_ID,
55
+ message: __(
56
+ 'You have unpublished component on this page. You need a pro version to publish it.',
57
+ 'elementor'
58
+ ),
59
+ additionalActionProps: [
60
+ {
61
+ size: 'small',
62
+ variant: 'contained',
63
+ color: 'promotion',
64
+ href: PUBLISH_UPGRADE_URL,
65
+ target: '_blank',
66
+ children: __( 'Upgrade Now', 'elementor' ),
67
+ },
68
+ ],
69
+ } );
70
+ }
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
+ }
@@ -0,0 +1,94 @@
1
+ import { generateElementId, type V1ElementData } from '@elementor/editor-elements';
2
+ import { type PropValue } from '@elementor/editor-props';
3
+
4
+ import {
5
+ type ComponentInstanceOverrideProp,
6
+ componentInstanceOverridePropTypeUtil,
7
+ } from '../../prop-types/component-instance-override-prop-type';
8
+ import { type ComponentInstanceOverride } from '../../prop-types/component-instance-overrides-prop-type';
9
+ import { componentOverridablePropTypeUtil } from '../../prop-types/component-overridable-prop-type';
10
+ import { regenerateLocalStyleIds } from './regenerate-local-style-ids';
11
+ import { resolveOverridableSettings } from './resolve-overridable-settings';
12
+
13
+ /**
14
+ * Creates detached element from component instance
15
+ * by applying overrides, reverting overridables, and regenerating IDs.
16
+ * This is used when detaching a component instance from its origin component.
17
+ *
18
+ * The function goes through all nested elements recursively and:
19
+ * - Regenerates element IDs and local style IDs
20
+ * - Resolves overridable settings - applies all instance overrides to element settings,
21
+ * or replaces overridable with origin_value when no matching override exists.
22
+ *
23
+ * @param element - The component's root element
24
+ * @param overrides - Array of overrides from the component instance
25
+ * @return A new element data with all overrides applied and new IDs
26
+ */
27
+ export function resolveDetachedInstance(
28
+ element: V1ElementData,
29
+ overrides: ComponentInstanceOverride[]
30
+ ): V1ElementData {
31
+ const overrideMap = createOverrideMap( overrides );
32
+
33
+ return resolveElementRecursive( structuredClone( element ), overrideMap );
34
+ }
35
+
36
+ function resolveElementRecursive(
37
+ element: V1ElementData,
38
+ overrideMap: Map< string, ComponentInstanceOverrideProp >
39
+ ): V1ElementData {
40
+ element.id = generateElementId();
41
+
42
+ if ( element.styles ) {
43
+ const { styles, settings } = regenerateLocalStyleIds( element );
44
+
45
+ element.styles = styles;
46
+ if ( settings ) {
47
+ element.settings = { ...element.settings, ...settings };
48
+ }
49
+ }
50
+
51
+ if ( element.settings ) {
52
+ element.settings = resolveOverridableSettings( element, overrideMap );
53
+ }
54
+
55
+ if ( element.elements?.length ) {
56
+ element.elements = element.elements.map( ( child ) => resolveElementRecursive( child, overrideMap ) );
57
+ }
58
+
59
+ return element;
60
+ }
61
+
62
+ function createOverrideMap( overrides: ComponentInstanceOverride[] ): Map< string, ComponentInstanceOverrideProp > {
63
+ const map = new Map< string, ComponentInstanceOverrideProp >();
64
+
65
+ overrides.forEach( ( item ) => {
66
+ let override: ComponentInstanceOverrideProp | null = null;
67
+
68
+ if ( componentInstanceOverridePropTypeUtil.isValid( item ) ) {
69
+ override = item;
70
+ } else if ( componentOverridablePropTypeUtil.isValid( item ) ) {
71
+ override = getOverridableOverride( item );
72
+ }
73
+
74
+ if ( override ) {
75
+ const overrideKey = override.value.override_key;
76
+ map.set( overrideKey, override );
77
+ }
78
+ } );
79
+
80
+ return map;
81
+ }
82
+
83
+ export function getOverridableOverride( propValue: PropValue ): ComponentInstanceOverrideProp | null {
84
+ if ( ! componentOverridablePropTypeUtil.isValid( propValue ) ) {
85
+ return null;
86
+ }
87
+
88
+ const originValue = componentOverridablePropTypeUtil.extract( propValue )?.origin_value;
89
+ if ( ! componentInstanceOverridePropTypeUtil.isValid( originValue ) ) {
90
+ return null;
91
+ }
92
+
93
+ return originValue;
94
+ }
@@ -0,0 +1,121 @@
1
+ import { type V1ElementData, type V1ElementSettingsProps } from '@elementor/editor-elements';
2
+ import { type PropValue } from '@elementor/editor-props';
3
+
4
+ import {
5
+ type ComponentInstanceOverrideProp,
6
+ componentInstanceOverridePropTypeUtil,
7
+ } from '../../prop-types/component-instance-override-prop-type';
8
+ import {
9
+ type ComponentInstanceProp,
10
+ componentInstancePropTypeUtil,
11
+ } from '../../prop-types/component-instance-prop-type';
12
+ import { componentOverridablePropTypeUtil } from '../../prop-types/component-overridable-prop-type';
13
+ import { isComponentInstance } from '../is-component-instance';
14
+
15
+ export function resolveOverridableSettings(
16
+ element: V1ElementData,
17
+ overrideMap: Map< string, ComponentInstanceOverrideProp >
18
+ ): V1ElementSettingsProps {
19
+ if ( isComponentInstance( { widgetType: element.widgetType, elType: element.elType } ) ) {
20
+ return resolveOverridableSettingsForComponentInstance( element, overrideMap );
21
+ }
22
+
23
+ return resolveOverridableSettingsForElement( element, overrideMap );
24
+ }
25
+
26
+ function resolveOverridableSettingsForElement(
27
+ element: V1ElementData,
28
+ overrideMap: Map< string, ComponentInstanceOverrideProp >
29
+ ): V1ElementSettingsProps {
30
+ const updatedSettings = element.settings ? { ...element.settings } : {};
31
+
32
+ for ( const [ settingKey, settingValue ] of Object.entries( element.settings ?? {} ) ) {
33
+ updatedSettings[ settingKey ] = resolvePropValue( settingValue, overrideMap );
34
+ }
35
+
36
+ return updatedSettings;
37
+ }
38
+
39
+ function resolveOverridableSettingsForComponentInstance(
40
+ element: V1ElementData,
41
+ overrideMap: Map< string, ComponentInstanceOverrideProp >
42
+ ): V1ElementSettingsProps {
43
+ const componentInstance = element.settings?.component_instance as ComponentInstanceProp | undefined;
44
+
45
+ if ( ! componentInstancePropTypeUtil.isValid( componentInstance ) ) {
46
+ return element.settings ?? {};
47
+ }
48
+
49
+ const instanceOverrides = componentInstance.value.overrides?.value;
50
+
51
+ if ( ! instanceOverrides?.length ) {
52
+ return element.settings ?? {};
53
+ }
54
+
55
+ const updatedOverrides = instanceOverrides.map( ( item ) =>
56
+ resolvePropValue( item, overrideMap, { isOverridableOverride: true } )
57
+ );
58
+
59
+ return {
60
+ ...element.settings,
61
+ component_instance: {
62
+ ...componentInstance,
63
+ value: {
64
+ ...componentInstance.value,
65
+ overrides: {
66
+ ...componentInstance.value.overrides,
67
+ value: updatedOverrides,
68
+ },
69
+ },
70
+ },
71
+ };
72
+ }
73
+
74
+ function resolvePropValue(
75
+ propValue: PropValue,
76
+ overrideMap: Map< string, ComponentInstanceOverrideProp >,
77
+ options?: { isOverridableOverride?: boolean }
78
+ ): PropValue | null {
79
+ const { isOverridableOverride = false } = options ?? {};
80
+
81
+ // if it's not an overridable, return the prop value as is
82
+ if ( ! componentOverridablePropTypeUtil.isValid( propValue ) ) {
83
+ return propValue;
84
+ }
85
+
86
+ const overridableKey = propValue.value.override_key;
87
+ const matchingOverride = overrideMap.get( overridableKey );
88
+
89
+ const originValue = componentOverridablePropTypeUtil.extract( propValue )?.origin_value;
90
+
91
+ // if no matching override, return the overridable's origin value
92
+ if ( ! matchingOverride ) {
93
+ return originValue;
94
+ }
95
+
96
+ if ( isOverridableOverride ) {
97
+ return resolveOverridableOverride( matchingOverride, originValue );
98
+ }
99
+
100
+ // for regular props, when there's a matching override, return the matching override value
101
+ const matchingOverrideValue = componentInstanceOverridePropTypeUtil.extract( matchingOverride )
102
+ ?.override_value as PropValue | null;
103
+ return matchingOverrideValue;
104
+ }
105
+
106
+ function resolveOverridableOverride(
107
+ matchingOverride: ComponentInstanceOverrideProp,
108
+ originValue: PropValue
109
+ ): ComponentInstanceOverrideProp | null {
110
+ if ( ! originValue || ! componentInstanceOverridePropTypeUtil.isValid( originValue ) ) {
111
+ return null;
112
+ }
113
+
114
+ // for overridable overrides, we should create a new override with the matching override value
115
+ // but keep the origin value's override key and schema source, so they'll match the inner component.
116
+ return componentInstanceOverridePropTypeUtil.create( {
117
+ override_value: matchingOverride.value.override_value,
118
+ override_key: originValue.value.override_key,
119
+ schema_source: originValue.value.schema_source,
120
+ } );
121
+ }