@elementor/editor-components 4.0.0-manual → 4.0.1

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 (106) hide show
  1. package/dist/index.d.mts +1422 -1
  2. package/dist/index.d.ts +1422 -1
  3. package/dist/index.js +2096 -4814
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +2028 -4837
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +23 -23
  8. package/src/components/components-tab/components-list.tsx +92 -4
  9. package/src/components/components-tab/components-pro-notification.tsx +9 -15
  10. package/src/components/components-tab/components-update-notification.tsx +13 -0
  11. package/src/components/components-tab/components.tsx +52 -3
  12. package/src/components/components-tab/loading-components.tsx +26 -14
  13. package/src/components/components-update-alert.tsx +40 -0
  14. package/src/components/components-upgrade-alert.tsx +39 -0
  15. package/src/components/detach-instance-confirmation-dialog.tsx +50 -0
  16. package/src/components/instance-editing-panel/detach-action.tsx +76 -0
  17. package/src/components/instance-editing-panel/empty-state.tsx +9 -2
  18. package/src/components/instance-editing-panel/instance-editing-panel.tsx +34 -6
  19. package/src/components/instance-editing-panel/override-prop-control.tsx +14 -6
  20. package/src/components/instance-editing-panel/use-instance-panel-data.ts +2 -2
  21. package/src/components/instance-editing-panel/utils/correct-exposed-empty-override.ts +28 -0
  22. package/src/consts.ts +1 -0
  23. package/src/create-component-type.ts +130 -29
  24. package/src/index.ts +92 -0
  25. package/src/init.ts +6 -4
  26. package/src/store/actions/update-overridable-prop.ts +4 -10
  27. package/src/store/dispatchers.ts +63 -0
  28. package/src/store/extensible-slice.ts +168 -0
  29. package/src/store/selectors.ts +53 -0
  30. package/src/store/store-types.ts +48 -0
  31. package/src/store/store.ts +7 -169
  32. package/src/sync/publish-draft-components-in-page-before-save.ts +42 -1
  33. package/src/types.ts +1 -1
  34. package/src/utils/detach-component-instance/detach-component-instance.ts +172 -0
  35. package/src/utils/detach-component-instance/index.ts +1 -0
  36. package/src/utils/detach-component-instance/regenerate-local-style-ids.ts +53 -0
  37. package/src/utils/detach-component-instance/resolve-detached-instance.ts +94 -0
  38. package/src/utils/detach-component-instance/resolve-overridable-settings.ts +121 -0
  39. package/src/utils/is-component-instance.ts +1 -1
  40. package/src/utils/is-pro-components-supported.ts +11 -0
  41. package/src/utils/tracking.ts +2 -1
  42. package/src/extended/components/component-introduction.tsx +0 -77
  43. package/src/extended/components/component-panel-header/component-badge.tsx +0 -73
  44. package/src/extended/components/component-panel-header/component-panel-header.tsx +0 -98
  45. package/src/extended/components/component-properties-panel/component-properties-panel-content.tsx +0 -176
  46. package/src/extended/components/component-properties-panel/component-properties-panel.tsx +0 -43
  47. package/src/extended/components/component-properties-panel/properties-empty-state.tsx +0 -51
  48. package/src/extended/components/component-properties-panel/properties-group.tsx +0 -196
  49. package/src/extended/components/component-properties-panel/property-item.tsx +0 -124
  50. package/src/extended/components/component-properties-panel/sortable.tsx +0 -92
  51. package/src/extended/components/component-properties-panel/use-current-editable-item.ts +0 -73
  52. package/src/extended/components/component-properties-panel/utils/generate-unique-label.ts +0 -21
  53. package/src/extended/components/component-properties-panel/utils/validate-group-label.ts +0 -24
  54. package/src/extended/components/components-tab/component-item.tsx +0 -180
  55. package/src/extended/components/components-tab/components.tsx +0 -58
  56. package/src/extended/components/components-tab/delete-confirmation-dialog.tsx +0 -26
  57. package/src/extended/components/create-component-form/create-component-form.tsx +0 -282
  58. package/src/extended/components/create-component-form/hooks/use-form.ts +0 -72
  59. package/src/extended/components/create-component-form/utils/get-component-event-data.ts +0 -54
  60. package/src/extended/components/edit-component/component-modal.tsx +0 -133
  61. package/src/extended/components/edit-component/edit-component.tsx +0 -166
  62. package/src/extended/components/edit-component/use-canvas-document.ts +0 -9
  63. package/src/extended/components/edit-component/use-element-rect.ts +0 -81
  64. package/src/extended/components/instance-editing-panel/instance-editing-panel.tsx +0 -60
  65. package/src/extended/components/overridable-props/indicator.tsx +0 -83
  66. package/src/extended/components/overridable-props/overridable-prop-control.tsx +0 -127
  67. package/src/extended/components/overridable-props/overridable-prop-form.tsx +0 -135
  68. package/src/extended/components/overridable-props/overridable-prop-indicator.tsx +0 -138
  69. package/src/extended/components/overridable-props/utils/validate-prop-label.ts +0 -38
  70. package/src/extended/consts.ts +0 -3
  71. package/src/extended/hooks/use-navigate-back.ts +0 -24
  72. package/src/extended/init.ts +0 -104
  73. package/src/extended/mcp/index.ts +0 -14
  74. package/src/extended/mcp/save-as-component-tool.ts +0 -436
  75. package/src/extended/store/actions/add-overridable-group.ts +0 -59
  76. package/src/extended/store/actions/archive-component.ts +0 -19
  77. package/src/extended/store/actions/create-unpublished-component.ts +0 -102
  78. package/src/extended/store/actions/delete-overridable-group.ts +0 -38
  79. package/src/extended/store/actions/delete-overridable-prop.ts +0 -70
  80. package/src/extended/store/actions/rename-component.ts +0 -49
  81. package/src/extended/store/actions/rename-overridable-group.ts +0 -39
  82. package/src/extended/store/actions/reorder-group-props.ts +0 -43
  83. package/src/extended/store/actions/reorder-overridable-groups.ts +0 -30
  84. package/src/extended/store/actions/reset-sanitized-components.ts +0 -7
  85. package/src/extended/store/actions/set-overridable-prop.ts +0 -117
  86. package/src/extended/store/actions/update-component-sanitized-attribute.ts +0 -8
  87. package/src/extended/store/actions/update-current-component.ts +0 -21
  88. package/src/extended/store/actions/update-overridable-prop-params.ts +0 -58
  89. package/src/extended/store/utils/groups-transformers.ts +0 -187
  90. package/src/extended/sync/before-save.ts +0 -52
  91. package/src/extended/sync/cleanup-overridable-props-on-delete.ts +0 -85
  92. package/src/extended/sync/create-components-before-save.ts +0 -113
  93. package/src/extended/sync/handle-component-edit-mode-container.ts +0 -114
  94. package/src/extended/sync/prevent-non-atomic-nesting.ts +0 -198
  95. package/src/extended/sync/revert-overridables-on-copy-or-duplicate.ts +0 -66
  96. package/src/extended/sync/sanitize-overridable-props.ts +0 -32
  97. package/src/extended/sync/set-component-overridable-props-settings-before-save.ts +0 -23
  98. package/src/extended/sync/update-archived-component-before-save.ts +0 -32
  99. package/src/extended/sync/update-component-title-before-save.ts +0 -19
  100. package/src/extended/utils/component-form-schema.ts +0 -32
  101. package/src/extended/utils/component-name-validation.ts +0 -27
  102. package/src/extended/utils/create-component-model.ts +0 -28
  103. package/src/extended/utils/get-container-for-new-element.ts +0 -49
  104. package/src/extended/utils/is-editing-component.ts +0 -13
  105. package/src/extended/utils/replace-element-with-component.ts +0 -11
  106. package/src/extended/utils/revert-overridable-settings.ts +0 -207
@@ -0,0 +1,168 @@
1
+ import type { V1Document } from '@elementor/editor-documents';
2
+ import {
3
+ __createAction as createAction,
4
+ __createSlice as createSlice,
5
+ __dispatch as dispatch,
6
+ type AnyAction,
7
+ type PayloadAction,
8
+ } from '@elementor/store';
9
+
10
+ import type { ComponentId, OverridableProps, PublishedComponent, UnpublishedComponent } from '../types';
11
+ import { type ComponentsState, initialState, type SanitizeAttributes, SLICE_NAME } from './store-types';
12
+ import { loadComponents } from './thunks';
13
+
14
+ type GetComponentResponse = PublishedComponent[];
15
+
16
+ type ComponentsReducer< P > = ( state: ComponentsState, action: PayloadAction< P > ) => void;
17
+
18
+ const extraReducersMap = new Map< string, ComponentsReducer< unknown > >();
19
+
20
+ export function registerComponentsReducer< P >( name: string, reducer: ComponentsReducer< P > ) {
21
+ extraReducersMap.set( `${ SLICE_NAME }/${ name }`, reducer as ComponentsReducer< unknown > );
22
+ }
23
+
24
+ export function createComponentsAction< P >( name: string ) {
25
+ const action = createAction< P >( `${ SLICE_NAME }/${ name }` );
26
+
27
+ return {
28
+ action,
29
+ register( reducer: ComponentsReducer< P > ) {
30
+ registerComponentsReducer( name, reducer );
31
+ },
32
+ dispatch( payload: P ) {
33
+ dispatch( action( payload ) );
34
+ },
35
+ };
36
+ }
37
+
38
+ export function __resetExtraReducers() {
39
+ extraReducersMap.clear();
40
+ }
41
+
42
+ const baseSlice = createSlice( {
43
+ name: SLICE_NAME,
44
+ initialState,
45
+ reducers: {
46
+ add: ( state, { payload }: PayloadAction< PublishedComponent | PublishedComponent[] > ) => {
47
+ if ( Array.isArray( payload ) ) {
48
+ state.data = [ ...payload, ...state.data ];
49
+ } else {
50
+ state.data.unshift( payload );
51
+ }
52
+ },
53
+ load: ( state, { payload }: PayloadAction< PublishedComponent[] > ) => {
54
+ state.data = payload;
55
+ },
56
+ addUnpublished: ( state, { payload }: PayloadAction< UnpublishedComponent > ) => {
57
+ state.unpublishedData.unshift( payload );
58
+ },
59
+ removeUnpublished: ( state, { payload }: PayloadAction< string | string[] > ) => {
60
+ const uidsToRemove = Array.isArray( payload ) ? payload : [ payload ];
61
+ state.unpublishedData = state.unpublishedData.filter(
62
+ ( component ) => ! uidsToRemove.includes( component.uid )
63
+ );
64
+ },
65
+ resetUnpublished: ( state ) => {
66
+ state.unpublishedData = [];
67
+ },
68
+ removeStyles( state, { payload }: PayloadAction< { id: ComponentId } > ) {
69
+ const { [ payload.id ]: _, ...rest } = state.styles;
70
+
71
+ state.styles = rest;
72
+ },
73
+ addStyles: ( state, { payload } ) => {
74
+ state.styles = { ...state.styles, ...payload };
75
+ },
76
+ addCreatedThisSession: ( state, { payload }: PayloadAction< string > ) => {
77
+ state.createdThisSession.push( payload );
78
+ },
79
+ removeCreatedThisSession: ( state, { payload }: PayloadAction< string > ) => {
80
+ state.createdThisSession = state.createdThisSession.filter( ( uid ) => uid !== payload );
81
+ },
82
+ archive: ( state, { payload }: PayloadAction< number > ) => {
83
+ const component = state.data.find( ( comp ) => comp.id === payload );
84
+
85
+ if ( component ) {
86
+ component.isArchived = true;
87
+ state.archivedThisSession.push( payload );
88
+ }
89
+ },
90
+ setCurrentComponentId: ( state, { payload }: PayloadAction< V1Document[ 'id' ] | null > ) => {
91
+ state.currentComponentId = payload;
92
+ },
93
+ setPath: ( state, { payload }: PayloadAction< ComponentsState[ 'path' ] > ) => {
94
+ state.path = payload;
95
+ },
96
+ setOverridableProps: (
97
+ state,
98
+ { payload }: PayloadAction< { componentId: ComponentId; overridableProps: OverridableProps } >
99
+ ) => {
100
+ const component = state.data.find( ( comp ) => comp.id === payload.componentId );
101
+
102
+ if ( ! component ) {
103
+ return;
104
+ }
105
+
106
+ component.overridableProps = payload.overridableProps;
107
+ },
108
+ rename: ( state, { payload }: PayloadAction< { componentUid: string; name: string } > ) => {
109
+ const component = state.data.find( ( comp ) => comp.uid === payload.componentUid );
110
+
111
+ if ( ! component ) {
112
+ return;
113
+ }
114
+ if ( component.id ) {
115
+ state.updatedComponentNames[ component.id ] = payload.name;
116
+ }
117
+ component.name = payload.name;
118
+ },
119
+ cleanUpdatedComponentNames: ( state ) => {
120
+ state.updatedComponentNames = {};
121
+ },
122
+ updateComponentSanitizedAttribute: (
123
+ state,
124
+ {
125
+ payload: { componentId, attribute },
126
+ }: PayloadAction< { componentId: ComponentId; attribute: SanitizeAttributes } >
127
+ ) => {
128
+ if ( ! state.sanitized[ componentId ] ) {
129
+ state.sanitized[ componentId ] = {};
130
+ }
131
+
132
+ state.sanitized[ componentId ][ attribute ] = true;
133
+ },
134
+ resetSanitizedComponents: ( state ) => {
135
+ state.sanitized = {};
136
+ },
137
+ },
138
+ extraReducers: ( builder ) => {
139
+ builder.addCase( loadComponents.fulfilled, ( state, { payload }: PayloadAction< GetComponentResponse > ) => {
140
+ state.data = payload;
141
+ state.loadStatus = 'idle';
142
+ } );
143
+ builder.addCase( loadComponents.pending, ( state ) => {
144
+ state.loadStatus = 'pending';
145
+ } );
146
+ builder.addCase( loadComponents.rejected, ( state ) => {
147
+ state.loadStatus = 'error';
148
+ } );
149
+ },
150
+ } );
151
+
152
+ export const slice: typeof baseSlice = {
153
+ ...baseSlice,
154
+ reducer( state: ComponentsState | undefined, action: AnyAction ) {
155
+ const nextState = baseSlice.reducer( state, action );
156
+
157
+ const extraReducer = extraReducersMap.get( action.type );
158
+
159
+ if ( ! extraReducer || ! nextState ) {
160
+ return nextState;
161
+ }
162
+
163
+ const clonedState = structuredClone( nextState );
164
+ extraReducer( clonedState, action as PayloadAction< unknown > );
165
+
166
+ return clonedState;
167
+ },
168
+ };
@@ -0,0 +1,53 @@
1
+ import { __getState as getState, __getStore as getStore } from '@elementor/store';
2
+
3
+ import { type ComponentId } from '../types';
4
+ import {
5
+ type ComponentsSlice,
6
+ selectArchivedThisSession,
7
+ selectComponentByUid,
8
+ selectComponents,
9
+ selectCreatedThisSession,
10
+ selectCurrentComponent,
11
+ selectCurrentComponentId,
12
+ selectOverridableProps,
13
+ selectUnpublishedComponents,
14
+ selectUpdatedComponentNames,
15
+ } from './store';
16
+
17
+ function safeGetState(): ComponentsSlice | undefined {
18
+ return getStore()?.getState() as ComponentsSlice | undefined;
19
+ }
20
+
21
+ export const componentsSelectors = {
22
+ getOverridableProps( componentId: ComponentId ) {
23
+ return selectOverridableProps( getState(), componentId );
24
+ },
25
+ getCurrentComponent() {
26
+ return selectCurrentComponent( getState() );
27
+ },
28
+ getCurrentComponentId() {
29
+ const state = safeGetState();
30
+ if ( ! state ) {
31
+ return null;
32
+ }
33
+ return selectCurrentComponentId( state );
34
+ },
35
+ getUnpublishedComponents() {
36
+ return selectUnpublishedComponents( getState() );
37
+ },
38
+ getUpdatedComponentNames() {
39
+ return selectUpdatedComponentNames( getState() );
40
+ },
41
+ getArchivedThisSession() {
42
+ return selectArchivedThisSession( getState() );
43
+ },
44
+ getCreatedThisSession() {
45
+ return selectCreatedThisSession( getState() );
46
+ },
47
+ getComponents() {
48
+ return selectComponents( getState() );
49
+ },
50
+ getComponentByUid( componentUid: string ) {
51
+ return selectComponentByUid( getState(), componentUid );
52
+ },
53
+ };
@@ -0,0 +1,48 @@
1
+ import { type V1Document } from '@elementor/editor-documents';
2
+
3
+ import {
4
+ type Component,
5
+ type ComponentId,
6
+ type PublishedComponent,
7
+ type StylesDefinition,
8
+ type UnpublishedComponent,
9
+ } from '../types';
10
+
11
+ export type SanitizeAttributes = 'overridableProps';
12
+
13
+ export type ComponentsState = {
14
+ data: PublishedComponent[];
15
+ unpublishedData: UnpublishedComponent[];
16
+ loadStatus: 'idle' | 'pending' | 'error';
17
+ styles: StylesDefinition;
18
+ createdThisSession: Component[ 'uid' ][];
19
+ archivedThisSession: ComponentId[];
20
+ path: ComponentsPathItem[];
21
+ currentComponentId: V1Document[ 'id' ] | null;
22
+ updatedComponentNames: Record< number, string >;
23
+
24
+ // We use this map to flag any sanitized attribute of a given component
25
+ // This map currently resets in response to the `editor/documents/open` command
26
+ sanitized: Record< ComponentId, Partial< Record< SanitizeAttributes, boolean > > >;
27
+ };
28
+
29
+ export type ComponentsPathItem = {
30
+ instanceId?: string;
31
+ instanceTitle?: string;
32
+ componentId: V1Document[ 'id' ];
33
+ };
34
+
35
+ export const initialState: ComponentsState = {
36
+ data: [],
37
+ unpublishedData: [],
38
+ loadStatus: 'idle',
39
+ styles: {},
40
+ createdThisSession: [],
41
+ archivedThisSession: [],
42
+ path: [],
43
+ currentComponentId: null,
44
+ updatedComponentNames: {},
45
+ sanitized: {},
46
+ };
47
+
48
+ export const SLICE_NAME = 'components';
@@ -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-Instance-draft-failure/';
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';